與服務工作人員之間的雙向通訊

Andrew Guan
Andrew Guan
Demián Renzulli
Demián Renzulli

在某些情況下,網頁應用程式可能需要在網頁和 Service Worker 之間建立雙向通訊管道。

舉例來說,在 Podcast PWA 中,開發人員可以建構功能,讓使用者下載集數以供離線收聽,並允許服務工作人員定期將進度通知頁面,以便主要執行緒更新 UI。

在本指南中,我們將探討在 Window服務工作人員環境之間實作雙向通訊的不同方式,包括探索不同 API、Workbox 程式庫,以及一些進階案例。

圖表:Service Worker 和網頁交換訊息。

使用 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);

服務工作人員會在另一端實作訊息監聽器,並回應已註冊的服務工作人員:

const SW_VERSION = '1.0.0';

self.addEventListener('message', (event) => {
  if (event.data.type === 'GET_VERSION') {
    event.ports[0].postMessage(SW_VERSION);
  }
});

在幕後,程式庫會使用瀏覽器 API (我們將在下一節中查看:MessageChannel),但會抽象化許多實作詳細資料,讓您更輕鬆地使用,同時利用這個 API 廣泛的瀏覽器支援

圖表:網頁與 Service Worker 之間使用 Workbox Window 進行雙向通訊。

使用瀏覽器 API

如果 Workbox 程式庫無法滿足您的需求,您可以使用多種低階 API,在網頁和 Service Worker 之間實作「雙向」通訊。兩者有異同之處:

相似處:

  • 無論是哪種情況,通訊都會從一端透過 postMessage() 介面啟動,並在另一端透過實作 message 處理常式接收。
  • 實際上,所有可用的 API 都允許我們實作相同的使用案例,但其中有些 API 可能會在某些情況下簡化開發作業。

差異:

  • 這些方法識別通訊另一端的方式不同:有些方法會明確參照其他內容,有些則可透過在每一端例項化的 Proxy 物件隱含通訊。
  • 各項功能支援的瀏覽器不盡相同。
這張圖表顯示網頁和 Service Worker 之間的雙向通訊,以及可用的瀏覽器 API。

Broadcast Channel API

Browser Support

  • Chrome: 54.
  • Edge: 79.
  • Firefox: 38.
  • Safari: 15.4.

Source

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 之間使用 Broadcast Channel 物件的雙向通訊。

缺點是撰寫本文時,這項 API 僅支援 Chrome、Firefox 和 Edge,其他瀏覽器 (例如 Safari) 尚未支援

Client API

Browser Support

  • Chrome: 40.
  • Edge: 17.
  • Firefox: 44.
  • Safari: 11.1.

Source

用戶端 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 是個不錯的選項,可讓您以相對簡單的方式,輕鬆與服務工作人員的所有有效分頁通訊。所有主要瀏覽器都支援這項 API,但並非所有方法都適用,因此請務必先檢查瀏覽器支援情形,再於網站中實作。

訊息管道

Browser Support

  • Chrome: 2.
  • Edge: 12.
  • Firefox: 41.
  • Safari: 5.

Source

訊息管道需要定義並將一個環境的通訊埠傳遞至另一個環境,才能建立雙向通訊管道。

如要初始化管道,網頁會例項化 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:背景同步處理和背景擷取

在本指南中,我們探討了如何實作雙向通訊技術,以處理相對簡單的情況,例如將描述要執行的作業的字串訊息,或要從一個環境快取到另一個環境的網址清單,從一個環境傳遞到另一個環境。在本節中,我們將探討兩種 API,用來處理特定情境:連線中斷和下載時間過長。

背景同步

Browser Support

  • Chrome: 49.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

即時通訊應用程式可能想確保訊息不會因連線品質不佳而遺失。Background Sync API 可讓您延後執行動作,待使用者連線穩定時再重試。這項功能可確保使用者想傳送的內容確實會傳送出去。

頁面會註冊 sync,而不是 postMessage() 介面:

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,指出嘗試執行的任何作業是否成功。如果 Promise 順利完成,表示同步完成。如果失敗,系統會排定另一次同步作業,重試同步作業時,系統也會等待連線,並採用指數輪詢。

作業完成後,服務工作站就能使用先前探討的任何通訊 API,與網頁通訊並更新 UI。

如果連線品質不佳導致查詢失敗,Google 搜尋會使用 Background Sync 持續嘗試,並在使用者連線後重試。作業完成後,系統會透過網頁推播通知將結果傳達給使用者:

圖表:網頁將通訊埠傳遞至 Service Worker,建立雙向通訊。

背景擷取

Browser Support

  • Chrome: 74.
  • Edge: 79.
  • Firefox: not supported.
  • Safari: not supported.

Source

對於相對較短的工作 (例如傳送訊息或要快取的網址清單),目前探討的選項是不錯的選擇。如果工作耗時過長,瀏覽器會終止 Service Worker,否則會對使用者的隱私權和電池造成風險。

Background Fetch API 可將長時間執行的工作卸載至 Service Worker,例如下載電影、Podcast 或遊戲關卡。

如要從網頁與 Service Worker 通訊,請使用 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,建立雙向通訊。
使用者介面會更新,顯示下載進度 (左側)。有了服務工作人員,即使所有分頁都已關閉 (右側),作業仍可繼續執行。

後續步驟

在本指南中,我們探討了網頁與 Service Worker 之間最常見的通訊案例 (雙向通訊)。

很多時候,您可能只需要一個內容來與另一個內容通訊,不需要收到回應。請參閱下列指南,瞭解如何在網頁中從服務工作人員傳送資料,以及傳送資料至服務工作人員,並查看相關用途和實際工作環境範例:

  • 命令式快取指南:從網頁呼叫 Service Worker,預先快取資源 (例如在預先擷取情境中)。
  • 播送更新:從 Service Worker 呼叫網頁,通知重要更新 (例如有新版網頁應用程式可用)。