ウェブアプリで、ページとサービス ワーカーの間で双方向の通信チャネルを確立する必要がある場合があります。
たとえば、ポッドキャスト PWA では、ユーザーがオフラインで視聴できるようにエピソードをダウンロードする機能を構築し、Service Worker がページに進行状況を定期的に通知し、メインスレッドが UI を更新できるようにします。
このガイドでは、さまざまな API、ワークボックス ライブラリ、高度なケースを使用して、Window コンテキストと Service Worker コンテキスト間で双方向通信を実装するさまざまな方法を見ていきます。
ワークボックスの使用
workbox-window
は、ウィンドウ コンテキストで実行することを目的とした Workbox ライブラリのモジュールのセットです。Workbox
クラスには、インスタンスに登録されている Service Worker にメッセージを送信し、レスポンスを待機する messageSW()
メソッドが用意されています。
次のページコードは、新しい Workbox
インスタンスを作成し、Service Worker にメッセージを送信してバージョンを取得します。
const wb = new Workbox('/sw.js');
wb.register();
const swVersion = await wb.messageSW({type: 'GET_VERSION'});
console.log('Service Worker version:', swVersion);
サービス ワーカーは、相手側でメッセージ リスナーを実装し、登録されたサービス ワーカーに応答します。
const SW_VERSION = '1.0.0';
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
ライブラリは、次のセクション「メッセージ チャンネル」で説明するブラウザ API を使用しますが、多くの実装の詳細を抽象化して使いやすくし、この API の幅広いブラウザ サポートを活用しています。
ブラウザ API の使用
Workbox ライブラリがニーズを満たしていない場合は、ページとサービス ワーカー間の「双方向」通信を実装するために使用できる下位レベルの API がいくつかあります。類似点と相違点は次のとおりです。
類似点:
- いずれの場合も、通信は一方の端で
postMessage()
インターフェースを介して開始され、message
ハンドラを実装することでもう一方の端で受信されます。 - 実際には、利用可能なすべての API で同じユースケースを実装できますが、シナリオによっては一部の API で開発を簡素化できる場合があります。
相違点:
- 通信の相手側を特定する方法は異なります。一方のコンテナから他方のコンテナを明示的に参照するものもあれば、各側でインスタンス化されたプロキシ オブジェクトを介して暗黙的に通信するものもあります。
- ブラウザのサポートはそれぞれ異なります。
Broadcast Channel API
Broadcast Channel API を使用すると、BroadcastChannel オブジェクトを介してブラウジング コンテキスト間の基本的な通信を行うことができます。
これを実装するには、まず、各コンテキストで同じ ID の BroadcastChannel
オブジェクトをインスタンス化し、そこからメッセージを送受信する必要があります。
const broadcast = new BroadcastChannel('channel-123');
BroadcastChannel オブジェクトは、リスニング コンテキストにメッセージを送信するための postMessage()
インターフェースを公開します。
//send message
broadcast.postMessage({ type: 'MSG_ID', });
任意のブラウザ コンテキストで、BroadcastChannel
オブジェクトの onmessage
メソッドを介してメッセージをリッスンできます。
//listen to messages
broadcast.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process message...
}
};
ご覧のとおり、特定のコンテキストへの明示的な参照がないため、サービス ワーカーや特定のクライアントへの参照を最初に取得する必要はありません。
デメリットは、本稿執筆時点では Chrome、Firefox、Edge でサポートされているものの、Safari などの他のブラウザではまだサポートされていないことです。
Client API
Client API を使用すると、サービス ワーカーが制御しているアクティブなタブを表すすべての WindowClient
オブジェクトへの参照を取得できます。
ページは単一の Service Worker によって制御されるため、serviceWorker
インターフェースを介してアクティブな Service Worker をリッスンし、その Service Worker に直接メッセージを送信します。
//send message
navigator.serviceWorker.controller.postMessage({
type: 'MSG_ID',
});
//listen to messages
navigator.serviceWorker.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//process response
}
};
同様に、Service Worker は onmessage
リスナーを実装してメッセージをリッスンします。
//listen to messages
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'MSG_ID') {
//Process message
}
});
サービス ワーカーは、クライアントと通信するために、Clients.matchAll()
や Clients.get()
などのメソッドを実行して WindowClient
オブジェクトの配列を取得します。次に、次のいずれかをpostMessage()
します。
//Obtain an array of Window client objects
self.clients.matchAll(options).then(function (clients) {
if (clients && clients.length) {
//Respond to last focused tab
clients[0].postMessage({type: 'MSG_ID'});
}
});
Client API
は、Service Worker からすべてのアクティブなタブと比較的簡単な方法で簡単に通信する場合に適しています。この API はすべての主要ブラウザでサポートされていますが、すべてのメソッドが使用できるとは限りません。サイトに実装する前に、ブラウザのサポート状況を確認してください。
メッセージ チャンネル
メッセージ チャネルでは、あるコンテキストから別のコンテキストにポートを定義して渡して、双方向通信チャネルを確立する必要があります。
チャネルを初期化するために、このページは MessageChannel
オブジェクトをインスタンス化し、それを使用して登録済みの Service Worker にポートを送信します。このページには、他のコンテキストからメッセージを受信するための onmessage
リスナーも実装されています。
const messageChannel = new MessageChannel();
//Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
messageChannel.port2,
]);
//Listen to messages
messageChannel.port1.onmessage = (event) => {
// Process message
};
Service Worker はポートを受信してポートへの参照を保存し、そのポートを使用して相手側にメッセージを送信します。
let communicationPort;
//Save reference to port
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'PORT_INITIALIZATION') {
communicationPort = event.ports[0];
}
});
//Send messages
communicationPort.postMessage({type: 'MSG_ID'});
MessageChannel
は現在、すべての主要なブラウザでサポートされています。
高度な API: バックグラウンド同期とバックグラウンド取得
このガイドでは、実行するオペレーションを記述する文字列メッセージや、キャッシュに保存する URL のリストをコンテキスト間で渡すなど、比較的単純なケースで双方向通信手法を実装する方法について説明しました。このセクションでは、特定のシナリオ(接続の不足とダウンロードに時間がかかる)に対処するための 2 つの API について説明します。
バックグラウンド同期
チャットアプリでは、接続が不安定なためにメッセージが失われることがないようにする必要があります。Background Sync API を使用すると、ユーザーの接続が安定したときにアクションを再試行できます。これは、ユーザーが送信したいものが実際に送信されるようにするのに役立ちます。
ページは、postMessage()
インターフェースではなく sync
を登録します。
navigator.serviceWorker.ready.then(function (swRegistration) {
return swRegistration.sync.register('myFirstSync');
});
サービス ワーカーは、sync
イベントをリッスンしてメッセージを処理します。
self.addEventListener('sync', function (event) {
if (event.tag == 'myFirstSync') {
event.waitUntil(doSomeStuff());
}
});
関数 doSomeStuff()
は、実行しようとしている処理の成功/失敗を示す Promise を返す必要があります。処理が完了すると、同期は完了です。失敗した場合は、別の同期が再試行されるようにスケジュールされます。再試行の同期も接続を待機し、指数バックオフを使用します。
オペレーションが実行されると、サービス ワーカーは、前述の通信 API のいずれかを使用して、ページと通信して UI を更新できます。
Google 検索では、バックグラウンド シンクを使用して、接続不良が原因で失敗したクエリを保持し、ユーザーがオンラインになったときに再試行します。オペレーションが実行されると、ウェブプッシュ通知を介して結果がユーザーに通知されます。
バックグラウンド フェッチ
メッセージの送信やキャッシュに保存する URL のリストなど、比較的短い処理の場合は、これまで説明したオプションが適しています。タスクに時間がかかりすぎると、ブラウザはサービス ワーカーを強制終了します。そうしないと、ユーザーのプライバシーとバッテリーにリスクが生じます。
Background Fetch API を使用すると、映画、ポッドキャスト、ゲームのレベルのダウンロードなど、長時間かかるタスクをサービス ワーカーにオフロードできます。
ページからサービス ワーカーに通信するには、postMessage()
ではなく backgroundFetch.fetch
を使用します。
navigator.serviceWorker.ready.then(async (swReg) => {
const bgFetch = await swReg.backgroundFetch.fetch(
'my-fetch',
['/ep-5.mp3', 'ep-5-artwork.jpg'],
{
title: 'Episode 5: Interesting things.',
icons: [
{
sizes: '300x300',
src: '/ep-5-icon.png',
type: 'image/png',
},
],
downloadTotal: 60 * 1024 * 1024,
},
);
});
BackgroundFetchRegistration
オブジェクトを使用すると、ページは progress
イベントをリッスンしてダウンロードの進行状況を追跡できます。
bgFetch.addEventListener('progress', () => {
// If we didn't provide a total, we can't provide a %.
if (!bgFetch.downloadTotal) return;
const percent = Math.round(
(bgFetch.downloaded / bgFetch.downloadTotal) * 100,
);
console.log(`Download progress: ${percent}%`);
});
次のステップ
このガイドでは、ページとサービス ワーカー間の通信(双方向通信)の最も一般的なケースについて説明しました。
多くの場合、一方が他方と通信するために必要なコンテキストは 1 つだけで、レスポンスを受信する必要はありません。ページで Service Worker との間で単方向のテクニックを実装する方法、ユースケース、本番環境の例については、次のガイドをご覧ください。
- 強制キャッシュ ガイド: ページから Service Worker を呼び出して、リソースを事前にキャッシュに保存します(プリフェッチ シナリオなど)。
- ブロードキャスト アップデート: Service Worker からページを呼び出して、重要なアップデート(新しいバージョンのウェブアプリが利用可能になったなど)を通知します。