# Notifications API

> 用 showNotification() 与 getNotifications() 显示系统通知、持久与非持久通知之分、各引擎都支持的选项，以及 TypeError 路径。

Notifications API 通过操作系统自己的通知界面在页面之外显示消息，用户切换标签页或应用后消息仍然可见。PWA 以两种形态使用它：由 `ServiceWorkerRegistration.showNotification()` 创建的持久通知，它比页面活得久，也是移动端浏览器唯一允许的形态；以及页面里 `new Notification()` 创建的非持久通知。本条目讲持久路径与两者之分；构造函数见[它自己的条目](/zh/reference/notifications/notification/)。

## 语法

```js
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](/zh/reference/notifications/notification-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](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/notifications/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` 里。

## 各引擎的支持情况

桌面端支持完整：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 负责处理点击。

### 从页面显示持久通知

等待活跃的 worker，然后在注册上调用 `showNotification()`。`data` 成员携带点击处理函数需要的内容。

```js
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 中处理点击

持久通知在 worker 上触发 `notificationclick`。先关闭通知，然后若目标 URL 已有打开的窗口就聚焦它，否则打开新窗口。

```js
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；上面的处理函数对两者一视同仁。

### 检测可用的通知器并兜底

三个条件必须同时成立：接口存在、权限已授予、注册有活跃 worker。逐一检查，否则返回页内兜底，而不是等待 `ready`，后者在没有注册的页面上不会兑现。

```js
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 });
}
```

两个分支返回的函数签名相同，调用方不必关心拿到的是哪一个。

:::observed
在 `Notification.permission` 为 `"default"` 或 `"denied"` 时于 Chrome 的 Console 调用 `registration.showNotification('x')`，会打出 `Uncaught (in promise) TypeError: Failed to execute 'showNotification' on 'ServiceWorkerRegistration': No notification permission has been granted for this origin.`；该字符串定义在 Chromium 的 [service_worker_registration_notifications.cc](https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/modules/notifications/service_worker_registration_notifications.cc)（chromium.googlesource.com）中。同一文件还包含 `renotify` 与 `silent` 的校验信息；DevTools 的 Application > Service workers 在录制 **Notifications** 后台服务时，会在注册下方显示每条已显示通知的 `title` 与 `tag`。
:::

## 另请参阅

- [Notifications API Standard](https://notifications.spec.whatwg.org/)（whatwg.org）
- [ServiceWorkerRegistration: showNotification() method](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/showNotification)（developer.mozilla.org）
- [Notification 接口](/zh/reference/notifications/notification/)
- [Notification.requestPermission()](/zh/reference/notifications/permissions/)
- [Web Push 与 PushManager.subscribe()](/zh/reference/notifications/web-push/)
- [通知的 actions 与 badge 选项](/zh/reference/notifications/notification-actions-badge/)