状況によっては、サービス ワーカーが制御するアクティブなタブと事前に通信して、特定のイベントを通知する必要がある場合があります。次に例を示します。
- 新しいバージョンの Service Worker がインストールされたときにページに通知し、ページで [更新して更新] ボタンをユーザーに表示して、新しい機能にすぐにアクセスできるようにします。
- サービス ワーカー側で行われたキャッシュに保存されたデータの変更をユーザーに知らせます。たとえば、「アプリはオフラインで使用できるようになりました」や「コンテンツの新しいバージョンが利用可能になりました」などのメッセージを表示します。
通信を開始するために Service Worker がページからメッセージを受信する必要がないユースケースを、「ブロードキャスト アップデート」と呼びます。このガイドでは、標準のブラウザ API と Workbox ライブラリを使用して、ページとサービス ワーカー間のこのタイプの通信を実装するさまざまな方法について説明します。
本番環境のケース
Tinder
Tinder PWA は workbox-window
を使用して、ページの重要な Service Worker ライフサイクルのタイミング(「インストール済み」、「制御中」、「有効化済み」)をリッスンします。これにより、新しい Service Worker が機能すると、[更新可能] バナーが表示され、ユーザーは PWA を更新して最新の機能にアクセスできるようになります。
Squoosh
Squoosh PWA では、Service Worker がオフラインで動作するために必要なアセットをすべてキャッシュに保存すると、ページにメッセージを送信して「オフラインで動作する準備ができました」というトーストが表示され、この機能についてユーザーに知らせます。
Workbox の使用
Service Worker のライフサイクル イベントをリッスンする
workbox-window
は、重要な Service Worker ライフサイクル イベントをリッスンするための簡単なインターフェースを提供します。内部的には、このライブラリは updatefound
や statechange などのクライアントサイド API を使用し、workbox-window
オブジェクトで高レベルのイベント リスナーを提供します。これにより、ユーザーはこれらのイベントを簡単に使用できます。
次のページコードを使用すると、新しいバージョンの Service Worker がインストールされるたびに検出できるため、ユーザーに通知できます。
const wb = new Workbox('/sw.js');
wb.addEventListener('installed', (event) => {
if (event.isUpdate) {
// Show "Update App" banner
}
});
wb.register();
キャッシュデータの変更をページに通知する
Workbox パッケージ workbox-broadcast-update
には、キャッシュに保存されたレスポンスが更新されたことをウィンドウ クライアントに通知する標準の方法が用意されています。これは、StaleWhileRevalidate 戦略とともに最もよく使用されます。
更新をブロードキャストするには、サービス ワーカー側の戦略オプションに broadcastUpdate.BroadcastUpdatePlugin
を追加します。
import {registerRoute} from 'workbox-routing';
import {StaleWhileRevalidate} from 'workbox-strategies';
import {BroadcastUpdatePlugin} from 'workbox-broadcast-update';
registerRoute(
({url}) => url.pathname.startsWith('/api/'),
new StaleWhileRevalidate({
plugins: [
new BroadcastUpdatePlugin(),
],
})
);
ウェブアプリでこれらのイベントをリッスンするには、次のようにします。
navigator.serviceWorker.addEventListener('message', async (event) => {
// Optional: ensure the message came from workbox-broadcast-update
if (event.data.meta === 'workbox-broadcast-update') {
const {cacheName, updatedUrl} = event.data.payload;
// Do something with cacheName and updatedUrl.
// For example, get the cached content and update
// the content on the page.
const cache = await caches.open(cacheName);
const updatedResponse = await cache.match(updatedUrl);
const updatedText = await updatedResponse.text();
}
});
ブラウザ API の使用
Workbox が提供する機能がニーズを満たしていない場合は、次のブラウザ API を使用して「ブロードキャスト アップデート」を実装します。
Broadcast Channel API
サービス ワーカーは BroadcastChannel オブジェクトを作成し、そのオブジェクトにメッセージを送信します。これらのメッセージを受信するコンテキスト(ページなど)は、BroadcastChannel
オブジェクトをインスタンス化し、メッセージ ハンドラを実装してメッセージを受信できます。
新しい Service Worker がインストールされたことをページに通知するには、次のコードを使用します。
// Create Broadcast Channel to send messages to the page
const broadcast = new BroadcastChannel('sw-update-channel');
self.addEventListener('install', function (event) {
// Inform the page every time a new service worker is installed
broadcast.postMessage({type: 'CRITICAL_SW_UPDATE'});
});
ページは、sw-update-channel
をサブスクライブして、これらのイベントをリッスンします。
// Create Broadcast Channel and listen to messages sent to it
const broadcast = new BroadcastChannel('sw-update-channel');
broadcast.onmessage = (event) => {
if (event.data && event.data.type === 'CRITICAL_SW_UPDATE') {
// Show "update to refresh" banner to the user.
}
};
これは簡単な手法ですが、ブラウザのサポートが制限されます。この記事の執筆時点では、Safari はこの API をサポートしていません。
Client API
Client API は、Client
オブジェクトの配列を反復処理することで、サービス ワーカーから複数のクライアントと通信する簡単な方法を提供します。
次の Service Worker コードを使用して、最後にフォーカスされたタブにメッセージを送信します。
// 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'});
}
});
このページでは、これらのメッセージをインターセプトするメッセージ ハンドラを実装しています。
// Listen to messages
navigator.serviceWorker.onmessage = (event) => {
if (event.data && event.data.type === 'MSG_ID') {
// Process response
}
};
クライアント API は、複数のアクティブなタブに情報をブロードキャストする場合に最適なオプションです。この API はすべての主要なブラウザでサポートされていますが、すべてのメソッドがサポートされているわけではありません。使用する前に、ブラウザのサポートを確認してください。
メッセージ チャンネル
メッセージ チャンネルでは、ページからサービス ワーカーにポートを渡して、ページとサービス ワーカー間の通信チャネルを確立する初期構成ステップが必要です。ページは MessageChannel
オブジェクトをインスタンス化し、postMessage()
インターフェースを介して Service Worker にポートを渡します。
const messageChannel = new MessageChannel();
// Init port
navigator.serviceWorker.controller.postMessage({type: 'PORT_INITIALIZATION'}, [
messageChannel.port2,
]);
ページは、そのポートに「onmessage」ハンドラを実装することでメッセージをリッスンします。
// Listen to messages
messageChannel.port1.onmessage = (event) => {
// Process message
};
Service Worker はポートを受け取り、その参照を保存します。
// Initialize
let communicationPort;
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'PORT_INITIALIZATION') {
communicationPort = event.ports[0];
}
});
その後、ポートへの参照で postMessage()
を呼び出して、ページにメッセージを送信できます。
// Communicate
communicationPort.postMessage({type: 'MSG_ID' });
MessageChannel
はポートの初期化が必要になるため、実装が複雑になる可能性がありますが、すべての主要ブラウザでサポートされています。
次のステップ
このガイドでは、Window とサービス ワーカー間の通信の 1 つの特別なケースである「ブロードキャスト アップデート」について説明しました。説明する例には、重要なサービス ワーカーのライフサイクル イベントのリッスンや、コンテンツやキャッシュに保存されたデータの変更に関するページへの通知などがあります。サービス ワーカーが事前にメッセージを受信せずにページとプロアクティブに通信する、より興味深いユースケースも考えられます。
Window とサービス ワーカーの通信のその他のパターンについては、以下をご覧ください。
- 強制キャッシュ ガイド: ページから Service Worker を呼び出して、リソースを事前にキャッシュに保存します(プリフェッチ シナリオなど)。
- 双方向通信: サービス ワーカーにタスク(負荷の高いダウンロードなど)を委任し、ページに進行状況を通知します。