42
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

ServiceWorkerのBackground Syncでオンライン復帰時にデータ送信

はじめに

2016年3月にリリースされたChrome 49からBackground Syncという新しい機能を使えるようになった。この機能を使うと、オフライン時に送信しようとしたデータを、その後ブラウザが閉じられたとしても、オンラインに復帰した際に自動的に送信することができる。そこで、LocChat.comというチャットサイトでこの機能を試してみた。
Background Sync demo
左のAndroidの画面で、機内モードに設定後、送信ボタンを押し、ブラウザを終了後、機内モードをオフにすると、右のPCのブラウザにチャットが届いている。

Background Syncの使い方

ServiceWorkerの登録

まず、Background SyncはServiceWorkerを使うので、下記のコードでServiceWorkerを登録する。

navigator.serviceWorker.register('./sw.js', {scope: './'})

送信処理

ユーザーが送信ボタンを押した際には下記のようにして、ブラウザがBackground Syncに対応している場合はthis._sendMassageWithSync(data)を実行し、それに失敗したり、ブラウザが対応していない場合は、this._sendMessage(data)を実行している。

if (navigator.serviceWorker && window.SyncManager) {
  this._sendMassageWithSync(data)
    .catch((function() {
      this._sendMessage(data);
    }).bind(this));
} else {
  this._sendMessage(data);
}

Syncの登録

this._sendMassageWithSync(data)では下記のようにIndexedDBの中にdataを保存してから、reg.sync.register()を呼び出しSyncを登録している。引数として渡すタグ名に使っているresultは、IndexedDBで自動採番されたidが渡ってくる。

  _sendMassageWithSync: function(data) {
    var INDEXED_DB_NAME = 'send_queue';
    var INDEXED_DB_VERSION = 1;
    var STORE_NAME = 'messages';
    function openDataBase() {
      return new Promise(function(resolve, reject) {
          var req = indexedDB.open(INDEXED_DB_NAME, INDEXED_DB_VERSION);
          req.onupgradeneeded = function (event) {
            req.result.createObjectStore(
                STORE_NAME, { keyPath: 'id' , autoIncrement: true });
          };
          req.onsuccess = function (event) { resolve(req.result); }
          req.onerror = reject;
        });
    }
    function addMessageToDataBase(data) {
      return openDataBase().then(function(db) {
          return new Promise(function(resolve, reject) {
            var transaction = db.transaction(STORE_NAME, 'readwrite');
            var store = transaction.objectStore(STORE_NAME);
            var req = store.add(data);
            req.onsuccess = function() { resolve(req.result); }
            req.onerror = reject;
          });
        });
    }
    return addMessageToDataBase(data).then(function(result) {
        return navigator.serviceWorker.ready.then(function(reg) {
            return reg.sync.register('send-msg:' + result);
          });
      });
  },

this._sendMessage(data)では単純にXMLHttpRequestで送信しているだけなので省略。

ServiceWorkerのSyncEventハンドラ

オンライン時に呼ばれるSyncEventハンドラではSyncのタグ名から先程のidを取り出し、sendAndDeleteMessage(id)を実行して返ってくるPromiseをevt.waitUntil()に渡している。こうすることで、sendAndDeleteMessageが失敗した場合、つまりネットワークが再度切断された場合に、再度オンライン復帰時にSyncEventハンドラが呼ばれるようになる。

self.addEventListener('sync', function(evt) {
  if (evt.tag.startsWith('send-msg:')) {
    var id = parseInt(evt.tag.substr(9))
    if (isNaN(id))
      return;
    evt.waitUntil(sendAndDeleteMessage(id));
  }
});

sendAndDeleteMessage()ではgetMessage(id)でIndexedDBからデータを取ってきて、sendRequestで実際に送信し、deleteMessage()でIndexedDBからデータを削除している。

function sendAndDeleteMessage(id) {
  return getMessage(id).then(sendRequest).then(function() {
    return deleteMessage(id);
  });
}

IndexedDBからデータの取得

getMessage(id)ではopenDataBase()で取得したIDBDatabaseからIDBObjectStoreを取得し、store.get(id)でデータを取得している。

function getMessage(id) {
  return openDataBase().then(function(db) {
      return new Promise(function(resolve, reject) {
        var transaction = db.transaction(STORE_NAME, 'readonly');
        var store = transaction.objectStore(STORE_NAME);
        var req = store.get(id);
        req.onsuccess = function() { resolve(req.result); }
        req.onerror = reject;
      });
    });
}

データの送信

sendRequestではFetch APIを使ってデータを実際に送信している。

function sendRequest(request) {
  if (!request)
    return Promise.resolve();
  var formData = new FormData();
  formData.append('pid', request.pid);
  formData.append('content', request.content);
  return fetch(new Request(
      '/m',
      {method: 'POST', body: formData, credentials: 'include'}));
}

IndexedDBからデータの削除

deleteMessage()では、不要になったデータをIndexedDBから削除している。

function deleteMessage(id) {
  return openDataBase().then(function(db) {
      return new Promise(function(resolve, reject) {
        var transaction = db.transaction(STORE_NAME, 'readwrite');
        var store = transaction.objectStore(STORE_NAME);
        var req = store.delete(id);
        req.onsuccess = resolve;
        req.onerror = reject;
      });
    });
}

参考

https://developers.google.com/web/updates/2015/12/background-sync
https://wicg.github.io/BackgroundSync/spec/

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
42
Help us understand the problem. What are the problem?