在某些情况下,Service Worker 可能需要主动与它控制的任何有效标签页通信,以告知某个事件。例如:
- 在安装了新版 Service Worker 时通知网页,以便网页可以向用户显示“更新以刷新”按钮,让用户立即访问新功能。
- 通过显示指示信息(例如“应用现在可以离线运行”或“有新版内容可用”),让用户了解在 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 策略搭配使用。
如需广播更新,请在 Service Worker 端的策略选项中添加 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(),
],
})
);
在 Web 应用中,您可以按如下方式监听这些事件:
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
Service Worker 会创建一个 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
客户端 API 提供了一种简单的方法,通过迭代 Client 对象数组,从 Service Worker 与多个客户端进行通信。
使用以下 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,但并非所有方法都支持。在使用之前,请先检查浏览器是否支持。
消息渠道
消息渠道需要一个初始配置步骤,即通过将端口从网页传递到 Service Worker,在它们之间建立通信渠道。该网页会实例化一个 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 可能更难实现,因为需要初始化端口,但所有主流浏览器都支持它。
后续步骤
在本指南中,我们探讨了一种特定的窗口到 Service Worker 通信情况:“广播更新”。我们探讨的示例包括监听重要的 Service Worker 生命周期事件,以及向网页传达内容或缓存数据的变化。您可以考虑更多有趣的用例,其中 Service Worker 主动与网页通信,而之前未收到任何消息。
如需了解有关窗口和服务工作器通信的更多模式,请参阅: