# 声明式 Web Push

> 说明声明式 Web Push 的 JSON 格式、window.pushManager 订阅、Safari 支持范围、Service Worker 兜底、特性检测与解析失败条件。

声明式 Web Push 让浏览器直接读取标准 JSON 推送消息并显示通知，不必调用 Service Worker。Service Worker 仍可处理消息；如果它没有给出替代通知，浏览器可以显示声明内容作为兜底。

## 工作原理

Push API 在窗口上暴露 `window.pushManager`，用于管理窗口作用域的订阅。`subscribe()` 的选项均可省略，`userVisibleOnly` 默认为 `false`，`applicationServerKey` 默认为 `null`；用户代理可以要求前者为 `true`，推送服务也可以要求后者不是 `null`。

声明式消息顶层的 `web_push` 值为 `8030`，并带有 `notification` 对象。Push API 要求该对象包含字符串类型的 `title` 与 `navigate`。用户激活通知时，浏览器打开 `navigate` 指定的 URL。`body`、`lang`、`dir`、`tag`、`icon` 和 `actions` 等通知选项也可按规范定义的类型加入对象。

:::caution
窗口作用域管理器的 URL 不使用 HTTPS 时，Push API 让 `subscribe()` 以 `NotAllowedError` `DOMException` 拒绝。载荷不是 JSON、缺少 `web_push`、其值不等于 `8030`、`notification` 不是对象，或者 `title`、`navigate` 缺失或不是字符串时，解析器返回失败（[PushManager.subscribe()](https://w3c.github.io/push-api/#dom-pushmanager-subscribe)，W3C；[Declarative push message parser](https://w3c.github.io/push-api/#declarative-push-message-parser)，W3C）。
:::

## 支持范围

WebKit 从 iOS 与 iPadOS 18.4 起在主屏幕 Web App 中支持声明式 Web Push，macOS 则从 Safari 18.5 起支持。WebKit 把同一份 JSON 设计成向后兼容的载荷：支持声明式处理的浏览器直接读取它，其余浏览器可由 Service Worker 把 `notification` 对象交给 `showNotification()`（[Meet Declarative Web Push](https://webkit.org/blog/16535/meet-declarative-web-push/)，WebKit）。

## 示例

部署时可优先使用窗口作用域订阅，同时保留注册作用域管理器与 `push` 处理函数作为兜底。

### 检测窗口作用域的推送管理器

使用声明式订阅路径前，先检查 `Notification` 与 `window.pushManager`。兜底分支会取得 Service Worker 注册，并检查它是否有 `pushManager`。如果没有找到任何一种管理器，则返回 `null`。

```js
async function getPushManager() {
  if ('Notification' in window && 'pushManager' in window) {
    return window.pushManager;
  }

  if ('serviceWorker' in navigator) {
    const registration = await navigator.serviceWorker.getRegistration();
    if (registration && 'pushManager' in registration) {
      return registration.pushManager;
    }
  }

  return null;
}
```

把选中的管理器交给应用现有的 `subscribe()` 流程，取得并保存 `PushSubscription`。

### 发送标准 JSON 结构

在应用服务器上创建载荷，再沿现有 Web Push 投递路径发送序列化的 JSON。浏览器会验证 `navigate` 是字符串，并在创建通知时解析其 URL。用户激活通知时，浏览器会导航到这个先前已经验证的 URL。

```js
const message = {
  web_push: 8030,
  notification: {
    title: '构建完成',
    body: '生产环境构建已可供检查。',
    lang: 'zh-CN',
    dir: 'ltr',
    navigate: 'https://example.com/builds/482',
  },
};

const payload = JSON.stringify(message);
```

将 `payload` 发往接收者 `PushSubscription` 中的 endpoint。浏览器可以显示声明的标题与正文，并在激活时使用 `navigate`。

### 在 Service Worker 中处理同一载荷

保留 `push` 处理函数，让通过 JavaScript 接收消息的浏览器也能显示通知。WebKit 的迁移示例从 `notification` 取出标题，再把其余成员传给 `showNotification()`。

```js
self.addEventListener('push', (event) => {
  event.waitUntil((async () => {
    const { notification } = event.data.json();
    const { title, ...options } = notification;
    await self.registration.showNotification(title, options);
  })());
});
```

支持声明式 Web Push 时，浏览器直接处理标准载荷；否则由该处理函数沿原有 Service Worker 路径显示相同的通知数据。

## 另请参阅

- [Push API: Declarative push message](https://w3c.github.io/push-api/#declarative-push-message)（W3C）
- [Meet Declarative Web Push](https://webkit.org/blog/16535/meet-declarative-web-push/)（WebKit）
- [Web Push 与 PushManager.subscribe()](/zh/reference/notifications/web-push/)
- [iOS 与 iPadOS 上的 Web Push](/zh/reference/notifications/ios-safari-push/)
- [Notifications API](/zh/reference/notifications/notifications-api/)