通知 · API
Notifications API
发布于 更新于
Notifications API 通过操作系统自己的通知界面在页面之外显示消息,用户切换标签页或应用后消息仍然可见。PWA 以两种形态使用它:由 ServiceWorkerRegistration.showNotification() 创建的持久通知,它比页面活得久,也是移动端浏览器唯一允许的形态;以及页面里 new Notification() 创建的非持久通知。本条目讲持久路径与两者之分;构造函数见它自己的条目。
registration.showNotification(title)registration.showNotification(title, options)registration.getNotifications()registration.getNotifications({ tag })showNotification() 返回 Promise<void>,通知交给操作系统后兑现;getNotifications() 以该注册已显示的 Notification 对象兑现,按创建顺序排列,可按 tag 过滤。两者在窗口和 Service Worker 自身(self.registration)中都可用,且要求安全上下文(BCD api.ServiceWorkerRegistration.showNotification:Chrome 42、Firefox 44、macOS Ventura 上的 Safari 16、iOS 上的 Safari 16.4 仅限主屏幕 Web App)。
| 参数 | 类型 | 含义 |
|---|---|---|
title |
string | 通知的第一行。必填。 |
options.body |
string | 标题下方的正文。 |
options.icon |
string(URL) | 文字旁的大图。 |
options.badge |
string(URL) | 完整通知放不下时使用的小型单色图,例如 Android 状态栏。仅 Chromium;见 actions 与 badge。 |
options.image |
string(URL) | 文字下方的大图。仅 Chromium。 |
options.tag |
string | 标识符;相同 tag 的新通知替换旧通知。 |
options.renotify |
boolean | 按 tag 替换时再次提醒用户。要求非空 tag。仅 Chromium。 |
options.requireInteraction |
boolean | 通知保留在屏幕上直到用户操作。Chromium;Firefox 117 仅限 Windows。 |
options.silent |
boolean | 不发声、不振动。Chrome 43、Firefox 132、Safari 16.6。与 vibrate 不兼容。 |
options.data |
任意可结构化克隆的值 | 在 notificationclick 中以 event.notification.data 读回的载荷。 |
options.actions |
array | 按钮;仅持久通知。见 actions 条目。 |
options.navigate |
string(URL) | Safari 18.4:激活时打开该 URL,而不触发 notificationclick。 |
options.timestamp、options.vibrate、options.dir、options.lang |
number、array、string、string | 显示时间、振动模式(Android 上的 Chromium)、文字方向、BCP 47 语言标签。 |
持久通知拥有非空的 Service Worker 注册;规范仅凭这一点决定其余一切。它的 notificationclick 与 notificationclose 事件在 ServiceWorkerGlobalScope 上触发,它应当出现在平台的通知中心,并且可以携带 actions。非持久通知在其 Notification 对象上触发 click 与 close,显示几秒后由浏览器关闭,且拒绝 actions。
| 异常 | 触发条件 |
|---|---|
TypeError |
注册没有处于 activating 或 activated 状态的 worker。navigator.serviceWorker.ready 只在存在这样的 worker 时兑现,示例等待它正是为此。 |
TypeError |
Notification.permission 不是 "granted"。Chromium 的报错文本为 Failed to execute 'showNotification' on 'ServiceWorkerRegistration': No notification permission has been granted for this origin.(service_worker_registration_notifications.cc,chromium.googlesource.com)。 |
TypeError |
renotify 为 true 但 tag 为空,或 silent 为 true 同时设置了 vibrate。 |
DataCloneError DOMException |
options.data 无法结构化克隆(函数、DOM 节点)。 |
Promise 被拒绝,不会同步抛出。push 处理函数内被拒绝的 showNotification() 同时违背了 userVisibleOnly 的承诺,WebKit 对此的回应是撤销订阅,所以这些情况要在调用之前处理,而不是放在 catch 里。
各引擎的支持情况
Section titled “各引擎的支持情况”桌面端支持完整:Chrome 42、Edge 17、Firefox 44、macOS Ventura 上的 Safari 16。移动端才是形态真正起作用的地方。Chrome for Android(42)与 Samsung Internet 只通过 Service Worker 暴露通知,并让构造函数抛出;iOS 与 iPadOS 上的 Safari 16.4 只在主屏幕 Web App 内暴露该接口,而且只能通过 Service Worker;Android WebView 没有实现(BCD api.Notification)。Chrome 自 49 起还在无痕窗口中禁用通知。因此写成持久路径是可移植的选择,而不是优化。
示例按代码运行位置划分:页面负责显示,Service Worker 负责处理点击。
从页面显示持久通知
Section titled “从页面显示持久通知”等待活跃的 worker,然后在注册上调用 showNotification()。data 成员携带点击处理函数需要的内容。
async function notify(title, body, url) { const registration = await navigator.serviceWorker.ready; await registration.showNotification(title, { body, icon: '/icons/icon-192.png', tag: 'inbox', data: { url }, });}tag: 'inbox' 把重复调用合并成一条通知,这是「3 条新消息」想要的效果,却不是三个独立聊天线程想要的。
在 Service Worker 中处理点击
Section titled “在 Service Worker 中处理点击”持久通知在 worker 上触发 notificationclick。先关闭通知,然后若目标 URL 已有打开的窗口就聚焦它,否则打开新窗口。
self.addEventListener('notificationclick', (event) => { event.notification.close(); const url = new URL(event.notification.data?.url ?? '/', self.location.origin).href; event.waitUntil((async () => { const windows = await self.clients.matchAll({ type: 'window', includeUncontrolled: true }); const open = windows.find((client) => client.url === url); if (open) return open.focus(); return self.clients.openWindow(url); })());});点击通知主体时 event.action 为空字符串,点击按钮时为该按钮的 action id;上面的处理函数对两者一视同仁。
检测可用的通知器并兜底
Section titled “检测可用的通知器并兜底”三个条件必须同时成立:接口存在、权限已授予、注册有活跃 worker。逐一检查,否则返回页内兜底,而不是等待 ready,后者在没有注册的页面上不会兑现。
async function getNotifier() { if (!('Notification' in window) || !('serviceWorker' in navigator)) { return showInPageBanner; // iOS 标签页或应用内嵌网页视图:没有系统通知 } const registration = await navigator.serviceWorker.getRegistration(); if (!registration?.active || Notification.permission !== 'granted') { return showInPageBanner; // 尚未安装,或权限未授予 } return (title, body) => registration.showNotification(title, { body });}两个分支返回的函数签名相同,调用方不必关心拿到的是哪一个。
- Notifications API Standard(whatwg.org)
- ServiceWorkerRegistration: showNotification() method(developer.mozilla.org)
- Notification 接口
- Notification.requestPermission()
- Web Push 与 PushManager.subscribe()
- 通知的 actions 与 badge 选项
规范
| 规范 | 状态 |
|---|---|
| 无。 | |