在某些情況下,網頁應用程式可能需要在頁面與服務工作站之間建立「雙向」通訊管道。
舉例來說,在 Podcast PWA 中,您可以建構一項功能,讓使用者下載節目供離線使用,並讓服務工作站定期顯示頁面通知進度,讓主執行緒能夠更新使用者介面。
本指南將探索各種 API、Workbox 程式庫及一些進階案例,探討在視窗和服務工作站結構定義之間實作雙向通訊的不同方法。
使用 Workbox
workbox-window
是 Workbox 程式庫的一組模組,可在視窗環境內執行。Workbox
類別提供 messageSW()
方法,可傳送訊息給執行個體已註冊的 Service Worker,並等待回應。
以下頁面程式碼會建立新的 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);
Service Worker 會在另一端實作訊息事件監聽器,並回應已註冊的服務工作站:
const SW_VERSION = '1.0.0';
self.addEventListener('message', (event) => {
if (event.data.type === 'GET_VERSION') {
event.ports[0].postMessage(SW_VERSION);
}
});
基本上,程式庫使用瀏覽器 API;這部分將會在下一節說明:Message Channel,但提取許多實作詳細資料,提供更易於使用的功能,同時利用這個 API 的寬版瀏覽器支援。
使用瀏覽器 API
如果 Workbox 程式庫不足以滿足您的需求,您可以使用幾個較低層級的 API 實作頁面和服務工作站之間的「雙向」通訊。兩者的相似和不同之處:
相似處:
- 在所有情況下,通訊都是透過
postMessage()
介面啟動,另一端則藉由實作message
處理常式接收通訊。 - 實際上,所有可用的 API 都讓我們能夠實作相同的用途,但在某些情況下,部分 API 或許可簡化開發作業。
不同之處:
- 它們有不同的識別通訊另一端的方法:有些工具使用對另一端的明確參照,其他則可以透過在每個端執行個體化的 Proxy 物件以隱含方式進行通訊。
- 瀏覽器支援設定因瀏覽器而異。
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...
}
};
如您所見,沒有特定背景資訊的明確參照,因此不需要先取得 Service Worker 或任何特定用戶端的參照。
缺點是,目前此 API 支援 Chrome、Firefox 和 Edge,但其他瀏覽器 (例如 Safari) 尚未支援這個 API。
用戶端 API
Client API 可讓您取得所有 WindowClient
物件的參照,這些物件代表服務工作站控制的有效分頁。
由於頁面由單一 Service Worker 控管,因此會直接透過 serviceWorker
介面監聽訊息,並傳送訊息至使用中的 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
}
};
同樣地,服務工作站會實作 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,但並非所有方法均可使用,因此在將 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
};
服務工作站接收通訊埠、儲存該通訊埠的參照內容,並使用該通訊埠將訊息傳送至另一端:
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:背景同步處理和背景擷取
在本指南中,我們探討了實作「雙向」通訊技巧的方法,適用於相對簡單的情況,例如傳送描述作業所執行作業的字串訊息,或是從一個結構定義到另一內容快取的網址清單。本節將探討兩種 API 來處理特定情境:連線能力不足和下載時間過長。
背景同步處理
即時通訊應用程式可能會想確保訊息不會因連線品質不佳而遺失。Background Sync API 可讓使用者在連線穩定時,延後重試動作。有助於確保無論使用者要傳送的內容都能確實傳送。
頁面會註冊 sync
,而非 postMessage()
介面:
navigator.serviceWorker.ready.then(function (swRegistration) {
return swRegistration.sync.register('myFirstSync');
});
然後,Service Worker 會監聽 sync
事件來處理訊息:
self.addEventListener('sync', function (event) {
if (event.tag == 'myFirstSync') {
event.waitUntil(doSomeStuff());
}
});
doSomeStuff()
函式應會傳回承諾,指出其嘗試執行的作業成功/失敗。如果運作正常,即代表同步處理作業已完成。如果失敗,系統會安排再次同步處理。重試同步作業也會等待連線,並且採用指數輪詢策略。
執行作業後,服務工作站就能使用先前探索的任何通訊 API 與頁面通訊,更新 UI。
Google 搜尋會使用 Background Sync 來保留因連線品質不佳而失敗的查詢,並在使用者連上網路時重試。作業執行後,使用者會透過網路推播通知將結果告知使用者:
背景擷取
針對相對較短的作業 (例如傳送訊息,或要快取的網址清單),目前探索選項是不錯的選擇。如果工作時間過長,瀏覽器就會終止服務工作站,否則可能會造成使用者隱私和電池風險。
Background Fetch API 可讓您將長時間工作卸載至服務工作站,例如下載電影、Podcast 或遊戲關卡。
如要從頁面連線至服務工作站,請使用 backgroundFetch.fetch
,而非 postMessage()
:
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}%`);
});
後續步驟
我們在本指南中探討了頁面與 Service Worker (雙向通訊) 之間最常見的通訊情況,
很多時候,一個內容可能只需要一個內容就能與其他內容通訊,而無需接收回應。請參閱下列指南,瞭解如何在網頁中實作單向技術至服務 Worker,以及使用案例和實際工作環境範例: