跳转到内容

添加推送通知

发布于

目标: 即使应用不在前台,服务器也能把消息送达用户。据 MDN,Push API 让 Web 应用能够接收 服务器推送来的消息,无论该 Web 应用是否处于前台,甚至是否已被加载;据 MDN,Service Worker 会在需要时被启动以处理到达的推送消息。

这里涉及两个彼此独立、却常被混淆的 API。推送是传输层:据 MDN,它把消息投递到 worker 的 push 事件。通知是展示层:据 MDN,worker 对消息的响应方式之一,就是用 ServiceWorkerRegistration.showNotification() 显示一条通知。

据 MDN,Push API 属于 Baseline「广泛可用」——在大量设备与浏览器版本上已经成熟,并自 2023 年 3 月起在各浏览器中可用。据 MDN,PushManager.subscribe() 在部分或全部支持它的浏览器中仅在 安全上下文(HTTPS)可用。较弱的一环是 Notification.requestPermission():据 MDN,该静态方法 不属于 Baseline,因为它在一些使用最广泛的浏览器中并不工作。

  1. 先有一个激活的 Service Worker。 据 MDN,应用要接收推送消息,必须拥有一个激活的 Service Worker;激活之后,它才能通过 PushManager.subscribe() 订阅。若还没有,请先看 入门。

  2. 在用户手势中请求权限。 据 MDN,Notification.requestPermission() 返回一个 Promise, 其结果为 granted、denied 或 default,并且该请求应当在用户交互的响应中发起。据 MDN, 当取值为 default 时,用户的决定未知,此时应用会按照权限被拒绝来处理。

  3. 订阅同样要来自用户手势。 据 MDN,subscribe() 调用应当在用户手势(例如点击按钮)的 响应中完成;这不仅是最佳实践,也是因为浏览器正在明确禁止非用户手势触发的通知,MDN 指出 Firefox 自 72 版起已经这样做。

  4. 把订阅信息发给你的服务器。 据 MDN,得到的 PushSubscription 包含应用发送推送消息所 需的全部信息:一个 endpoint,以及发送数据所需的加密密钥。

  5. 在 worker 中处理 push 并展示内容。 据 MDN,到达的推送消息被投递给 push 事件处理 函数,它可以通过 ServiceWorkerRegistration.showNotification() 显示通知来作出响应。

据 MDN,ServiceWorkerRegistration.pushManager 是使用推送消息的入口,并返回对 PushManager 接口的引用——因此在展示订阅界面之前,先检测这个接口:

async function subscribeToPush(applicationServerKey) {
if (!('PushManager' in window)) {
// 不支持推送:保持订阅界面隐藏,回退到你已有的应用内渠道
// (例如站内收件箱视图,或邮件摘要)。
return null;
}
const permission = await Notification.requestPermission();
if (permission !== 'granted') return null;
const registration = await navigator.serviceWorker.ready;
return registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey,
});
}

据 MDN,applicationServerKey 是一个 Base64 编码的字符串或 ArrayBuffer,内含一个 ECDSA P-256 公钥,推送服务器用它来认证你的应用服务器;一旦指定,你的应用服务器发出的所有消息都 必须使用 VAPID 认证方案,并带上用对应私钥签名的 JWT。据 MDN,该密钥不是你用来加密数据 的那个 ECDH 密钥。

在 worker 中,把消息变成一条通知:

self.addEventListener('push', (event) => {
const body = event.data ? event.data.text() : '你有一条新消息。';
event.waitUntil(self.registration.showNotification('新消息', { body }));
});
  • userVisibleOnly: true 实际上不可省略。 据 MDN,applicationServerKey 在 Chrome、 Edge 等部分浏览器中是必需的,并且当 userVisibleOnly 未设为 true 时它们会拒绝该 Promise。因此请按「每条推送都对应一条可见通知」来设计,而不是静默的后台投递。
  • 把 endpoint 当作机密。 据 MDN,订阅 endpoint 是一个唯一的能力 URL:知道这个 endpoint 就足以向你的应用发送消息,因此必须保密,否则其他应用也能向你的用户推送。
  • 订阅时要防 CSRF。 MDN 明确警告:实现 PushManager 订阅时,防范应用中的 CSRF/XSRF 问题至关重要。
  • 投递量并非到处都无限制。 据 MDN,唤醒 Service Worker 投递推送消息会增加资源消耗,尤其 是电量,而目前尚无标准机制来处理这一点:Firefox 对每个应用允许有限的推送消息配额(每次 访问站点时刷新,且会产生通知的消息不计入该上限),Chrome 则没有限制。
  • 订阅会过期、会变化。 据 MDN,当推送订阅已失效或即将失效时(例如推送服务设置了过期 时间)会触发 pushsubscriptionchange 事件——所以要把已保存的订阅当作可能失效的东西看待, 并在失效时重新订阅。
  • 每个订阅只属于一个 worker。 据 MDN,每个订阅对某个 Service Worker 是唯一的。
  • 拒绝是明确的。 据 MDN,denied 表示用户已明确拒绝当前源展示系统通知的权限;MDN 自己的 示例也注明,这种情况下应当保持尊重,不必再去打扰用户。

← 返回指南总览。