透過 Service Worker 向網頁廣播更新

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

在某些情況下,服務工作者可能需要主動與其控制的任何有效分頁進行通訊,以便通知特定事件。例如:

  • 在安裝新版服務工作者時通知網頁,以便網頁向使用者顯示「Update to refresh」按鈕,讓使用者立即存取新功能。
  • 讓使用者知道服務工作者端快取資料的變更,方法是顯示指示,例如:「應用程式現在可離線運作」「可使用新版內容」
這張圖表顯示服務工作人員正在與網頁通訊以傳送更新。

我們將這類用途稱為「廣播更新」,服務工作者不必從網頁接收訊息即可開始通訊。在本指南中,我們將介紹如何使用標準瀏覽器 API 和 Workbox 程式庫,在網頁和服務工作者之間實作這類通訊。

實際工作環境案例

Tinder

Tinder PWA 會使用 workbox-window 接聽頁面中的重要服務工作者生命週期時刻 (「已安裝」、「已控制」和「已啟用」)。這樣一來,當新的服務工作者開始運作時,就會顯示「Update Available」橫幅,讓使用者可以重新整理 PWA 並存取最新功能:

Tinder 網頁應用程式「更新可用」功能的螢幕擷取畫面。
在 Tinder PWA 中,Service Worker 會通知網頁新版本已就緒,而網頁會向使用者顯示「可更新」橫幅。

Squoosh

Squoosh PWA 中,當服務工作者快取所有必要的資產,以便離線運作時,就會傳送訊息至網頁,顯示「Ready to work offline」訊息彈出式通知,讓使用者瞭解這項功能:

Squoosh 網頁應用程式「Ready to work offline」功能的螢幕截圖。
在 Squoosh PWA 中,Service Worker 會在快取完成後廣播網頁更新,而網頁會顯示「Ready to work offline」(可離線運作) 浮動視窗。

使用 Workbox

監聽服務工作程生命週期事件

workbox-window 提供簡單明瞭的介面,方便您監聽重要的 Service Worker 生命週期事件。實際上,程式庫會使用用戶端 API (例如 updatefoundstatechange),並在 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 實作「廣播更新」

廣播頻道 API

Service Worker 會建立 BroadcastChannel 物件,並開始傳送訊息。任何有意接收這些訊息的情境 (例如網頁) 都可以將 BroadcastChannel 物件例項化,並實作訊息處理常式以接收訊息。

如要通知網頁安裝新的服務工作者,請使用以下程式碼:

// 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

用戶端 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,但並非所有方法都支援這個 API。使用前請先檢查瀏覽器支援。

訊息管道

訊息管道需要初始設定步驟,透過從網頁傳遞至服務 worker 的連接埠,建立兩者之間的通訊管道。頁面將 MessageChannel 物件例項化,並透過 postMessage() 介面將通訊埠傳遞至服務工作站:

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
};

服務工作站會接收通訊埠並儲存其參照:

// Initialize
let communicationPort;

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'PORT_INITIALIZATION') {
    communicationPort = event.ports[0];
  }
});

從那個時間點開始,它可以透過呼叫 port 參照中的 postMessage(),將訊息傳送至頁面:

// Communicate
communicationPort.postMessage({type: 'MSG_ID' });

MessageChannel 需要初始化連接埠,因此導入作業可能較為複雜,但所有主要瀏覽器都支援這項功能。

後續步驟

在本指南中,我們探討了一個與 Window 和服務工作者通訊相關的特殊案例:「廣播更新」。探討的例子包括監聽重要的 Service Worker 生命週期事件,以及向頁面傳達內容或快取資料變更的相關資訊。您可以思考更多有趣的用途,例如服務工作者主動與網頁通訊,而不需要先收到任何訊息。

如要進一步瞭解 Window 和服務工作者通訊的其他模式,請參閱:

  • 強制快取指南:從頁面呼叫 Service Worker,以便預先快取資源 (例如在預先載入情境中)。
  • 雙向通訊:將工作委派給服務工作者 (例如大量下載),並持續向頁面提供進度資訊。

其他資源