与 Service Worker 的双向通信

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

在某些情况下,Web 应用可能需要在 页面和 Service Worker。

例如:在播客 PWA 中,用户可以构建一项功能,让用户下载 离线使用,让 Service Worker 定期向网页发送进度信息,以便主 线程可以更新界面。

在本指南中,我们将探索在 Google Cloud 之间实现双向通信的不同方式, Window服务 通过探索工作器 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);

Service Worker 在另一端实现消息监听器,并响应注册的 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,我们将在下一部分进行介绍:消息 渠道, 更便于使用,同时利用宽浏览器 支持该 API 所具有的各种功能。

展示使用 Workbox 窗口的页面与 Service Worker 进行双向通信的示意图。

使用浏览器 API

如果 Workbox 库无法满足您的需求,有几个较低级别的 API 可供使用 在页面和 Service Worker 之间实现“双向”通信。它们有一些相似之处 差异:

相似之处:

  • 在所有情况下,通信都从一端通过 postMessage() 接口开始,然后接收 另一方面,您可以实现 message 处理程序。
  • 事实上,所有可用的 API 都让我们能够实现相同的用例, 在某些情况下可能会简化开发工作。

差异:

  • 他们通过不同的方式来识别沟通的另一方:其中一些人使用 显式引用其他上下文,而其他上下文可通过代理隐式通信 该对象实例化。
  • 支持的浏览器因具体情况而异。
显示页面与 Service Worker 以及可用浏览器 API 之间的双向通信的示意图。

广播频道 API

浏览器支持

  • Chrome:54。 <ph type="x-smartling-placeholder">
  • Edge:79。 <ph type="x-smartling-placeholder">
  • Firefox:38。 <ph type="x-smartling-placeholder">
  • Safari:15.4. <ph type="x-smartling-placeholder">

来源

Broadcast Channel API 允许通过 BroadcastChannel 在浏览上下文之间进行基本通信 对象的操作

如需实现它,首先必须为每个上下文创建一个具有相同 ID 的 BroadcastChannel 对象 并通过它收发消息:

const broadcast = new BroadcastChannel('channel-123');

BroadcastChannel 对象公开 postMessage() 接口,用于向任何监听器发送消息 上下文:

//send message
broadcast.postMessage({ type: 'MSG_ID', });

任何浏览器上下文都可以通过 BroadcastChannelonmessage 方法监听消息 对象:

//listen to messages
broadcast.onmessage = (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //process message...
  }
};

如您所见,由于没有对特定上下文的显式引用,因此无需获取 引用 Service Worker 或任何特定的客户端。

展示页面与 Service Worker 使用广播通道对象进行双向通信的示意图。

其缺点是,在撰写本文时,该 API 支持 Chrome、Firefox 但 Safari 等其他浏览器则不支持

Client API

浏览器支持

  • Chrome:40。 <ph type="x-smartling-placeholder">
  • Edge:17。 <ph type="x-smartling-placeholder">
  • Firefox:44。 <ph type="x-smartling-placeholder">
  • Safari:11.1. <ph type="x-smartling-placeholder">

来源

借助客户端 API,您可以获取 引用表示 Service Worker 控制的活跃标签页的所有 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
  }
};

同样,Service Worker 通过实现 onmessage 监听器来监听消息:

//listen to messages
self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'MSG_ID') {
    //Process message
  }
});

为了与其任何客户端进行反向通信,Service Worker 获取一个数组, WindowClient 对象(通过执行) 方法,例如 Clients.matchAll()Clients.get()。然后它可以 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'});
  }
});
显示 Service Worker 与一组客户端进行通信的示意图。

Client API 是与 Service Worker 中的所有活动标签页轻松通信的好选项 编写代码。该 API 受到各大主流 浏览器, 但并非所有其方法都可用,因此请务必先检查浏览器支持,然后再 在网站上植入代码

消息渠道

浏览器支持

  • Chrome:2. <ph type="x-smartling-placeholder">
  • Edge:12。 <ph type="x-smartling-placeholder">
  • Firefox:41。 <ph type="x-smartling-placeholder">
  • Safari:5. <ph type="x-smartling-placeholder">

来源

消息频道要求 定义端口并将其从一个上下文传递到另一个上下文,以建立双向通信 。

为了初始化渠道,页面会实例化 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,以建立双向通信。

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,用于处理特定的 场景:网络连接中断和下载时间长。

后台同步

浏览器支持

  • Chrome:49。 <ph type="x-smartling-placeholder">
  • Edge:79。 <ph type="x-smartling-placeholder">
  • Firefox:不支持。 <ph type="x-smartling-placeholder">
  • Safari:不支持。 <ph type="x-smartling-placeholder">

来源

聊天应用可能需要确保消息永不因网络连接状况不佳而丢失。通过 Background Sync API 可用来 在用户连接稳定时延迟重试操作。这有助于确保 无论用户想要发送什么内容,它都会实际发送出去。

该页面不会注册 postMessage() 接口,而是注册 sync

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() 应返回一个 promise,表明它无论成功还是失败 尝试。如果满足,则同步完成。如果失败,系统会安排另一次同步 重试。重试同步也会等待连接,并采用指数退避算法。

在执行操作之后,Service Worker 便可与页面进行反通信, 更新界面。

Google 搜索会使用后台同步功能来保留由于网络连接不良而失败的查询,然后重试 在用户上线后使用它们。操作完成后,它们会将结果传达给 通过 Web 推送通知向用户发送:

示意图:一个页面将端口传递给 Service Worker,以建立双向通信。

后台提取

浏览器支持

  • Chrome:74。 <ph type="x-smartling-placeholder">
  • Edge:79。 <ph type="x-smartling-placeholder">
  • Firefox:不支持。 <ph type="x-smartling-placeholder">
  • Safari:不支持。 <ph type="x-smartling-placeholder">

来源

对于相对较短的工作量(如发送消息或要缓存的网址列表),选项 都是不错的选择。如果任务耗时过长,浏览器将终止服务 否则就会给用户隐私和电池带来风险。

后台提取 API 允许您将长任务分流给 Service Worker,例如下载电影、播客或关卡 。

要从页面与 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}%`);
});
<ph type="x-smartling-placeholder">
</ph> 示意图:一个页面将端口传递给 Service Worker,以建立双向通信。
界面会进行更新,显示下载进度(左侧)。得益于 Service Worker,操作可以在关闭所有标签页(右)后继续运行。

后续步骤

在本指南中,我们探讨了页面与 Service Worker 之间的最常见通信情形 (双向通信)。

很多时候,一个用户可能只需要一个上下文来相互通信, 响应。请查看以下指南,了解如何在 来自和传出 Service Worker 的网页,以及用例和生产示例:

  • 命令式缓存指南:从页面调用 Service Worker 提前缓存资源(例如在预提取场景中)。
  • 广播更新:从 Service Worker 调用页面以通知 关于重要更新的信息(例如,有新版本的 Web 应用)。