50
42

More than 5 years have passed since last update.

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

Posted at

はじめに

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/

50
42
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
50
42