はじめに
2016年3月にリリースされたChrome 49からBackground Syncという新しい機能を使えるようになった。この機能を使うと、オフライン時に送信しようとしたデータを、その後ブラウザが閉じられたとしても、オンラインに復帰した際に自動的に送信することができる。そこで、LocChat.comというチャットサイトでこの機能を試してみた。
左の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/