推送 API

W3C 工作草案

关于本文档的更多信息
本版本:
https://www.w3.org/TR/2025/WD-push-api-20250910/
最新发布版本:
https://www.w3.org/TR/push-api/
最新编辑草案:
https://w3c.github.io/push-api/
历史记录:
https://www.w3.org/standards/history/push-api/
提交历史
编辑:
Peter Beverloo (Google)
Martin Thomson (Mozilla 基金会)
Marcos Caceres (苹果公司)
前任编辑:
Bryan Sullivan (AT&T) - 截止至
Eduardo Fullea (Telefonica) - 截止至
Michaël van Ouwerkerk (Google) - 截止至
反馈:
GitHub w3c/push-api (拉取请求, 新建议, 开放议题)

摘要

推送 API 允许通过 推送服务 向 Web 应用发送 推送消息应用服务器 可以在任何时间发送 推送消息,即使 Web 应用或 用户代理 处于非活动状态。推送服务 能确保消息可靠高效地传递给 用户代理推送消息会发送到运行在 Web 应用源下的 Service Worker,Service Worker 可以使用消息中的信息更新本地状态或向用户显示通知。

本规范旨在与 Web 推送协议配合使用,该协议描述了 应用服务器用户代理 如何与 推送服务进行交互。

本文档状态

本节描述了本文档在发布时的状态。当前 W3C 出版物列表及本技术报告的最新修订版可在 W3C 标准与草案索引中查阅。

本文档由 Web 应用工作组推荐轨道发布为工作草案。

作为工作草案发布不代表 W3C 及其成员的认可。

本文档为草稿,可能随时被更新、替换或废弃。除作为正在进行的工作外,不应引用本文件。

本文档由遵循 W3C 专利政策的工作组编制。 W3C维护着 与工作组交付物相关的专利公开的公开列表; 该页面还包括专利披露的说明。任何知晓包含 必要声明 的专利的个人,必须根据 《W3C 专利政策》第 6 节披露相关信息。

本文档受 2025 年 8 月 18 日 W3C 流程文件约束。

1. 简介

本节为非规范性内容。

推送 API 允许 Web 应用与 用户代理 异步通信。这使得 应用服务器 能够在信息变得可用时,及时向 用户代理 提供时效性信息,而无需等待用户打开 Web 应用。

如本规范所定义,推送服务支持在任何时间投递推送消息

特别地,即使 Web 应用当前未在浏览器窗口中活动,推送消息也会被投递到 Web 应用:这适用于用户关闭 Web 应用但仍希望在收到推送消息时能重新启动 Web 应用的场景。例如,推送消息可用于通知用户有来电的 WebRTC 呼叫。

用户代理临时离线时,也可发送推送消息。对此,推送服务会为用户代理存储消息,直到用户代理可用。这支持 Web 应用在用户离线期间获知变化,并确保用户代理能及时获得相关信息。推送消息推送服务存储,直到用户代理可达且消息可被投递。

推送 API 也确保在用户代理主动使用 Web 应用时可靠投递推送消息,例如用户正在使用 Web 应用或 Web 应用通过活动 worker、frame 或后台窗口与应用服务器进行通信时。这并非推送 API 的主要应用场景。Web 应用可选择使用推送 API 发送不频繁的消息,以避免与 应用服务器保持持续的通信。

推送消息更适合于用户代理与 Web 应用之间尚未建立活动通信通道的场合。发送推送消息相比直接通信方式(如 Fetch API 或 [WebSockets])消耗更多资源。推送消息通常比直接通信延迟更高,并且可能受限于使用条件。大多数推送服务会限制可发送推送消息的大小和数量。

2. 依赖关系

Web 推送协议 [RFC8030] 描述了一种协议,使 用户代理应用服务器 能够与 推送服务通信。可以使用其他协议替代本协议,但本规范假定采用该协议;其他协议应提供兼容语义。

Content-Encoding HTTP 头部,由 [RFC7231] 第 3.1.2.2 节描述,指示应用于推送消息负载的内容编码方式。

3. 概念

3.1 应用服务器

应用服务器一词指 Web 应用的服务端组件。

3.2 推送消息

推送消息是从应用服务器发送到 Web 应用的数据。

推送消息会投递到与消息所提交的推送订阅相关联的活动 worker。如果 Service Worker 当前未运行,则会启动 worker 以进行消息投递。

3.3 声明式推送消息

声明式推送消息是一个推送消息,其数据为用户代理能够理解的 JSON 文档。用户代理会机会性地解析每个收到的 推送消息,以判断其是否为声明式推送消息,解析过程使用 声明式推送消息解析器

声明式推送消息允许创建和显示通知,无需 Service Worker 的参与。当然,如果 应用服务器需要,Service Worker 仍可以参与。在这种情况下,推送消息的声明式属性可作为备用,例如 Service Worker 因存储压力被驱逐时。同时,这种方式为通知数据的传递提供了更面向对象的方案。

{
    "web_push": 8030,
    "notification": {
        "title": "Ada emailed ‘London’",
        "lang": "en-US",
        "dir": "ltr",
        "body": "Did you hear about the tube strikes?",
        "navigate": "https://email.example/message/12"
    }
    }

3.3.1 成员

声明式推送消息包含以下成员:

web_push(必填)

一个整数,值必须为 8030。用于将声明式推送消息 与其它 JSON 文档区分开。

notification(必填)

一个 JSON 对象,包含如下成员,均类似于 Notifications API 的特性,但类型有时更严格。除 title 外,所有成员均来自 NotificationOptions 字典,并需保持同步。 [NOTIFICATIONS]

title(必填)

字符串。

dir

"auto"、"ltr" 或 "rtl"。

lang

表示语言标签的字符串。

body

字符串。

navigate(必填)

表示 URL 的字符串。

tag

字符串。

image

表示 URL 的字符串。

icon

表示 URL 的字符串。

badge

表示 URL 的字符串。

vibrate

32 位无符号整数数组。

timestamp

64 位无符号整数

renotify

布尔值。

silent

布尔值。

requireInteraction

布尔值。

注意

该字段未命名为 require_interaction,以与 NotificationOptions 字典保持一致。

data

任意 JSON 值。

actions

JSON 对象数组,包含如下成员,均来自 NotificationAction 字典,并需保持同步。

action(必填)

字符串。

title(必填)

字符串。

navigate(必填)

表示 URL 的字符串。

icon

表示 URL 的字符串。

mutable

布尔值。当为 true 时,会向(如有)Service Worker 分发包含该Notification 对象的 push 事件,该对象由 声明式推送消息描述。

3.3.2 解析器

声明式推送消息解析器结果是一个元组,包含 notification(一个 notification)和 mutable(一个布尔值)。

声明式推送消息解析器给定一个字节序列 bytesorigin originURL baseURL,以及 EpochTimeStamp fallbackTimestamp,执行如下步骤。它们返回失败或声明式推送消息解析器结果

  1. message解析 JSON 字节为 Infra 值的结果,参数为 bytes。若抛出异常,则返回失败。

  2. message 不是有序映射,则返回失败。

  3. message["web_push"] 不存在或不是 8030,则返回失败。

  4. message["notification"] 不存在,则返回失败。

  5. notificationInput = message["notification"]。

  6. notificationInput 不是有序映射,则返回失败。

  7. notificationInput["title"] 不存在或不是字符串,则返回失败。

  8. notificationInput["navigate"] 不存在或不是字符串,则返回失败。

  9. notificationTitle = notificationInput["title"]。

  10. notificationOptionsNotificationOptions 字典。

  11. notificationInput["dir"]存在且值为"auto"、"ltr"或"rtl",则设置 notificationOptions["dir"] 为 notificationInput["dir"]。

  12. notificationInput["lang"]存在且为字符串,则设置 notificationOptions["lang"] 为 notificationInput["lang"]。

  13. notificationInput["body"]存在且为字符串,则设置 notificationOptions["body"] 为 notificationInput["body"]。

  14. 设置 notificationOptions["navigate"] 为 notificationInput["navigate"],并转换

  15. notificationInput["tag"]存在且为字符串,则设置 notificationOptions["tag"] 为 notificationInput["tag"]。

  16. notificationInput["image"]存在且为字符串,则设置 notificationOptions["image"] 为 notificationInput["image"],并转换

  17. notificationInput["icon"]存在且为字符串,则设置 notificationOptions["icon"] 为 notificationInput["icon"],并转换

  18. notificationInput["badge"]存在且为字符串,则设置 notificationOptions["badge"] 为 notificationInput["badge"],并转换

  19. notificationInput["vibrate"]存在且为列表,且每个 都是32 位无符号整数,则设置 notificationOptions["vibrate"] 为 notificationInput["vibrate"]。

  20. notificationInput["timestamp"]存在且为64 位无符号整数,则设置 notificationOptions["timestamp"] 为 notificationInput["timestamp"]。

  21. notificationInput["renotify"]存在且为布尔值,则 设置 notificationOptions["renotify"] 为 notificationInput["renotify"]。

  22. notificationInput["silent"]存在且为布尔值,则 设置 notificationOptions["silent"] 为 notificationInput["silent"]。

  23. notificationInput["requireInteraction"]存在且为布尔值,则设置 notificationOptions["requireInteraction"] 为 notificationInput["requireInteraction"]。

  24. notificationInput["data"]存在,则设置 notificationOptions["data"] 为运行 将 Infra 值转换为 JSON 兼容的 JavaScript 值的结果,参数为 notificationInput["data"]。

  25. notificationInput["actions"]存在且为列表

    1. notificationActions = « »。

    2. 对每个 actionInput 属于 notificationInput["actions"]:

      1. actionInput["action"] 不存在或不是字符串,则跳过

      2. actionInput["title"] 不存在或不是字符串,则跳过

      3. actionInput["navigate"] 不存在或不是字符串,则跳过

      4. actionNavigate = actionInput["navigate"], 转换

      5. notificationActionNotificationAction 字典 «[ "action" → actionInput["action"], "title" → actionInput["title"], "navigate" → actionNavigate ]»。

      6. actionInput["icon"]存在且为字符串,则 设置 notificationAction["icon"] 为 actionInput["icon"],并转换

      7. 追加 notificationActionnotificationActions

    3. 设置 notificationOptions["actions"] 为 notificationActions

  26. notification 为运行 创建通知,参数为 notificationTitlenotificationOptionsoriginbaseURLfallbackTimestamp 的结果。若抛出异常,则返回失败。

  27. notification导航 URL为 null,则返回失败。

  28. notificationactions 中任一导航 URL为 null,则返回失败。

  29. mutable = false。

  30. message["mutable"]存在message["mutable"] 为布尔值,则设置 mutablemessage["mutable"]。

  31. 返回 (notification, mutable)。

3.4 推送订阅

推送订阅是为 Web 应用在 用户代理推送服务之间建立的消息传递上下文。

推送订阅有一个关联的 scope(作用域),它是一个 URL

推送订阅scopepathlist,长度为 1,且 scopepath[0]为空字符串时,认为其具有 窗口可访问作用域

注意

即,path 组件在 URL中序列化为 "/"。

推送订阅有一个关联的 推送端点。 它必须是由 推送服务公开的绝对 URL, 应用服务器可向其发送 推送消息推送端点 必须唯一标识该 推送订阅

推送订阅 可以有一个关联的 订阅过期时间。 设置时,它必须是自 1970 年 1 月 1 日 00:00:00 UTC 起的毫秒数,到该时间点订阅将被 停用用户代理 应当在订阅过期前尝试 刷新推送订阅。

推送订阅 有用于 P-256 ECDH 密钥对和认证密钥的内部槽,符合 [RFC8291]。 在创建推送订阅时,这些槽 必须被填充。

如果用户代理因任何原因需要更换 推送订阅的密钥, 且推送订阅关联 Service Worker 注册非空, 则必须 刷新 推送订阅

创建推送订阅,给定 PushSubscriptionOptionsInit optionsDictionary

  1. subscription 为一个新建的 PushSubscription
  2. options 为新建的 PushSubscriptionOptions 对象,属性由 optionsDictionary 的相应成员和值初始化。
  3. subscriptionoptions 属性设置为 options
  4. 生成新的 P-256 ECDH 密钥对 [ANSI-X9-62]。 私钥存储在 subscription 的内部槽中,不得向应用公开。 公钥也存储于内部槽,可通过调用 getKey() 并传递参数 "p256dh" 获取。
  5. 生成新的认证密钥,为一组八位字节序列,定义见 [RFC8291]。 认证密钥存储在 subscription 的内部槽中。 可通过调用 getKey() 并传递参数 "auth" 获取该密钥。
  6. 请求新的 推送订阅。 如果设置了 applicationServerKey 属性,则一同发送。重新抛出任何 异常
  7. 推送订阅 请求成功完成时:
    1. subscriptionendpoint 属性设置为 推送订阅推送端点
    2. 如果由 推送订阅提供,则设置 subscriptionexpirationTime
  8. 返回 subscription

3.4.1 与 Service Worker 注册的关系

推送订阅关联 Service Worker 注册 是其 Service Worker 注册, 当其 scope URL 等于 推送订阅scope 时(如果有);否则为 null。

注意

推送订阅关联 Service Worker 注册 只有在其具有 窗口可访问作用域 时才可能为 null。

反过来, Service Worker 注册关联推送订阅 是其 推送订阅, 当其 scope 等于 Service Worker 注册scope URL 时(如果有);否则为 null。

3.4.2 订阅刷新

user agent 或者 push service MAY 选择 refresh 一个 push subscription,当它的 associated service worker registration 在任何时候都不是 null 的时候,例如因为它已经达到了一定的时间。

当这种情况发生时,用户代理 必须按照用于创建当前 推送订阅 时所提供的 PushSubscriptionOptions ,运行创建推送订阅的步骤, 并将新推送订阅scope 设置为原始订阅的scope。 新的推送订阅 必须拥有与原订阅不同的密钥对。

当刷新成功时,用户代理 必须 触发 "pushsubscriptionchange" 事件, 事件参数为与推送订阅相关联的 Service Worker 注册 作为 registration, 表示初始推送订阅PushSubscription 实例作为 oldSubscription, 表示新推送订阅PushSubscription 实例作为 newSubscription

为了让更改有时间传播到应用服务器用户代理 可以在刷新后短暂继续接受旧推送订阅的消息。 一旦收到刷新后的推送订阅的消息, 所有旧的推送订阅 必须停用

如果用户代理 无法刷新推送订阅, 则应当定期重试刷新。 当推送订阅无法再使用, 例如因其已过期时,用户代理 必须 触发 "pushsubscriptionchange" 事件, 事件参数为与推送订阅相关联的 Service Worker 注册 作为 registration, 表示即将停用的推送订阅PushSubscription 实例作为 oldSubscription, 并将 newSubscription 设为 null

3.4.3 订阅停用

推送订阅停用时, 用户代理推送服务 必须删除其详细信息的所有已存储副本。后续针对该 推送订阅推送消息 不得被投递。

一个没有 窗口可访问范围推送订阅,在其关联的 service worker 注册被注销时必须停用,不过推送订阅可以停用得更早。

注意

没有窗口可访问作用域推送订阅 会在Service Worker 注册 被清除时移除。

3.5 推送服务

推送服务 指的是一种允许应用服务器向 Web 应用发送 推送消息的系统。 推送服务为其所服务的推送订阅 提供推送端点端点

用户代理 会连接用于创建推送订阅推送服务用户代理 可以限制可用的推送服务的选择。 这样做的原因包括与性能相关的考虑,例如服务可用性(包括服务是否在特定国家或工作场所等网络被防火墙阻断)、可靠性、对电池寿命的影响,以及将元数据导向或避开特定 推送服务的协议等。

3.6 权限

Push API 是一个被 强大功能识别的功能, 其名称"push"

为了与Permissions规范集成, 本规范定义了 PushPermissionDescriptor 权限描述符类型

WebIDLdictionary PushPermissionDescriptor : PermissionDescriptor {
    boolean userVisibleOnly = false;
    };

userVisibleOnly 具有与 userVisibleOnly 相同的语义。

{name: "push", userVisibleOnly: false} 强于 {name: "push", userVisibleOnly: true}

4. 安全与隐私注意事项

推送消息的内容是加密的 [RFC8291]。但 推送服务 仍然可以看到应用服务器通过推送订阅用户代理发送消息时的元数据,包括消息的时间、频率和大小。除了更换 推送服务 (用户代理可能不允许),已知的缓解措施只有通过填充数据来增加表观消息大小。

无法保证推送消息是由与 Web 应用同源的 应用服务器发送的。 应用服务器可以自行决定将使用 推送订阅所需的详细信息分享给第三方。

以下要求旨在尽可能保护用户的隐私和安全,同时在实现该目标的前提下,保护 应用服务器与用户通信的完整性。

用户代理 不得在未获得用户 明确许可 的情况下向 Web 应用开放 Push API。 用户代理 必须通过用户界面在每次调用 subscribe() 方法时获取许可, 除非之前的许可已被持久保存或存在预先安排的信任关系。 超出当前浏览会话保存的权限必须可被撤销。

Push API 可能需要唤醒与 Service Worker 注册 关联的 Service Worker,以运行开发者提供的事件处理器。这可能会导致资源消耗,比如网络流量, 用户代理 应当将这些消耗归因于创建 推送订阅的 Web 应用。

用户代理 可以在获取权限或判断权限状态时考虑 PushSubscriptionOptions

当权限被撤销时, 用户代理 可以为使用该权限创建的订阅 触发 "pushsubscriptionchange" 事件, 事件参数为与 Service Worker 注册 关联的 推送订阅作为 registration, 表示该推送订阅PushSubscription 实例作为 oldSubscriptionnull 作为 newSubscription用户代理 必须并行 停用相关订阅。

推送端点 不得推送服务以外的参与方泄露可推断用户信息,如设备、身份或位置。 具体要求见 [RFC8030] 的隐私注意事项。

一个已停用的推送订阅推送端点不得用于新的推送订阅。这可以防止创建用户无法删除的持久性标识符。这也防止了重复使用一个推送订阅的细节来向另一个推送订阅发送推送消息

用户代理 必须实现 Push API 仅在 安全上下文 中可用。这样可以更好地防止针对推送订阅数据的中间人攻击。浏览器仅可在开发目的下忽略此规则。

5. 推送框架

本节为非规范性内容。

推送消息应用服务器发送到 Web 应用的过程如下:

该整体框架允许应用服务器在发生事件时激活Service Worker。这些事件的信息可包含在推送消息中,从而使 Web 应用能适当响应相关事件,无需主动发起网络请求。

下列代码及流程图展示了推送 API 的一个假设使用场景。

5.1 示例

本节为非规范性内容。

// https://example.com/serviceworker.js
    this.onpush = event => {
    console.log(event.data);
    // 此处可以将数据写入 IndexedDB、发送到任何已打开窗口、显示通知等。
    }

    // https://example.com/webapp.js
    // 在一个 async 函数内部...
    try {
    const serviceWorkerRegistration = await navigator.serviceWorker.register(
        "serviceworker.js"
    );
    const pushSubscription = await serviceWorkerRegistration.pushManager.subscribe();
    // 应用服务器所需的推送订阅详情现在已可用,可通过 XMLHttpRequest 等方式发送给服务器。
    console.log(pushSubscription.endpoint);
    console.log(pushSubscription.getKey("p256dh"));
    console.log(pushSubscription.getKey("auth"));
    } catch (err) {
    // 在生产环境中,可能还需要将错误信息反馈给应用服务器。
    console.log(error);
    }

5.2 序列图

本节为非规范性内容。

订阅、推送消息投递和退订的事件流程示例
1 订阅、推送消息投递和退订的事件流程示例

5.3 推送服务使用

PushSubscription 包含应用服务器发送 推送消息所需的全部信息。与 Push API 兼容的推送服务会提供符合 Web 推送协议推送端点。这些参数和属性包括:

6. PushManagerAttribute 混入

本规范通过 PushManagerAttribute 混入, 扩展了 WindowServiceWorkerRegistration 对象。 [HTML] [SERVICE-WORKERS]

WebIDL[SecureContext]
    interface mixin PushManagerAttribute {
    readonly attribute PushManager pushManager;
    };
    Window includes PushManagerAttribute;
    ServiceWorkerRegistration includes PushManagerAttribute;

WindowServiceWorkerRegistration 对象都拥有一个关联的 PushManager 对象。

pushManager getter 步骤为返回 this 关联的 PushManager 对象。

Window 对象上, PushManagerservice worker registration 为 null。

ServiceWorkerRegistration 对象上, PushManagerservice worker registration 即为该 service worker registration 所代表的 ServiceWorkerRegistration 对象。

7. PushManager 接口

PushManager 接口定义了访问 推送服务的操作。

WebIDL[Exposed=(Window,Worker), SecureContext]
    interface PushManager {
    [SameObject] static readonly attribute FrozenArray<DOMString> supportedContentEncodings;

    Promise<PushSubscription> subscribe(optional PushSubscriptionOptionsInit options = {});
    Promise<PushSubscription?> getSubscription();
    Promise<PermissionState> permissionState(optional PushSubscriptionOptionsInit options = {});
    };

PushManager 具有一个关联的 service worker 注册,其值为 null 或一个 service worker 注册

supportedContentEncodings 属性公开了一系列可用于加密 推送消息 负载的内容编码。内容编码通过请求 推送服务发送 推送消息时的 Content-Encoding 头字段指示。

用户代理 必须支持 [RFC8291] 中定义的 aes128gcm 内容编码, 并且可以为了兼容性原因支持旧版本草案中定义的内容编码。

7.1 subscribe() 方法

subscribe() 方法的步骤如下:

  1. promise一个新的 promise
  2. globalthis相关全局对象
  3. scope 为 null。
  4. registration 为 null。
  5. 如果 thisservice worker 注册 为 null:
    1. scope 设置为运行 基本 URL 解析器,参数为 "/" 和 global关联 DocumentURL 的结果。
  6. 否则:
    1. 断言thisservice worker 注册 是一个 service worker 注册
    2. registration 设置为 thisservice worker 注册
  7. 并行运行以下步骤:
    注意: 验证顺序在不同用户代理间可能不同
    1. 如果 scope 解析失败或其 URLscheme 不是 "https", 则在 networking task source 上, 使用 global 拒绝 promise,抛出 "NotAllowedError" DOMException
    2. 如果 options 参数中的 userVisibleOnlyfalse,而用户代理要求必须为 true, 则拒绝 promise 并抛出 "NotAllowedError" DOMException
    3. 如果 options 参数未包含 applicationServerKey 的非 null 值,且 推送服务 要求必须提供, 则拒绝 promise 并抛出 "NotSupportedError" DOMException
    4. 如果 options 参数包含 applicationServerKey 属性:
      1. 如果 optionsapplicationServerKeyDOMString, 则其值为通过 base64url 解码得到的八位字节序列 [RFC7515]。
      2. 解码失败时,拒绝 promise 并抛出 "InvalidCharacterError" DOMException,终止后续步骤。
      3. 确保 optionsapplicationServerKey 描述的是 P-256 曲线上的有效点。无效则拒绝 promise 并抛出 "InvalidAccessError" DOMException,终止后续步骤。
    5. subscription 为 null。
    6. 如果 scope 非 null:
      1. 如果存在一个 推送订阅, 且其 窗口可访问作用域scope 等于 scope, 则将 subscription 设为该 推送订阅
    7. 否则:
      1. 断言registration 非 null。
      2. 如果 registrationactive worker 为 null, 则拒绝 promise 并抛出 "InvalidStateError" DOMException,终止后续步骤。
      3. 如果 registration关联推送订阅 非 null, 则将 subscription 设为 registration关联推送订阅
      4. scope 设为 registrationscope URL
    8. permission请求使用权限 "push"。
    9. 如果 permission 为 "denied", 则拒绝 promise 并抛出 "NotAllowedError" DOMException,终止后续步骤。
    10. 如果 subscription 非 null:
      1. 如果 subscription 有错误,则拒绝 promise 并抛出 "AbortError" DOMException,终止后续步骤。
      2. options 参数与 subscriptionoptions 属性对比, BufferSource 的内容按值比较。
      3. 如果 options 的任一属性与 subscription 存储的不同, 则拒绝 promise 并抛出 "InvalidStateError" DOMException,终止后续步骤。
      4. networking task source 上, 使用 global resolve promise,返回 subscription 并终止后续步骤。
    11. 断言subscription 为 null,且 scopeURL
    12. subscription 设置为尝试用 options 创建推送订阅 的结果。如果抛出异常,则拒绝 promise 并抛出该异常,终止后续步骤。
    13. subscriptionscope 设为 scope
    14. networking task source 上, 使用 global resolve promise,返回一个与 subscription 对应的 PushSubscription
  8. 返回 promise

getSubscription() 方法的步骤如下:

  1. promise一个新的 promise
  2. globalthis相关全局对象
  3. windowScope 为 null。
  4. registration 为 null。
  5. 如果 thisservice worker 注册 为 null, 则将 windowScope 设置为运行 基本 URL 解析器, 参数为 "/" 和 global关联 DocumentURL 的结果。
  6. 否则:
    1. 断言thisservice worker 注册 是一个 service worker 注册
    2. registration 设置为 thisservice worker 注册
  7. 并行运行以下步骤:
    1. subscription 为 null。
    2. 如果 windowScope 非 null:
      1. 如果存在一个 推送订阅, 且其 scopewindowScope, 则将 subscription 设为该 推送订阅
    3. 否则:
      1. 断言registration 非 null。
      2. 如果 registration关联推送订阅 非 null, 则将 subscription 设为 registration关联推送订阅
    4. 如果 subscription 为 null,则 resolve promise 为 null。
    5. 如果 subscription 有错误,则 reject promise,抛出 DOMException, 名称为 "AbortError",终止后续步骤。
    6. resolve promise,返回一个与 subscription 对应的 PushSubscription
  8. 返回 promise

permissionState() 方法被调用时 必须运行以下步骤:

  1. promise一个新的 promise
  2. 返回 promise,异步继续以下步骤。
  3. descriptor 为一个新的 PermissionDescriptor, 其 name 初始化为 "push"。
  4. statedescriptor权限状态 及结果。
  5. resolve promisestate

使用推送服务的权限可以是持久的,即如果存在有效权限,则无需为后续订阅重新确认。

如果需要申请权限,应通过调用 subscribe() 方法完成。

7.2 PushSubscriptionOptions 接口

WebIDL[Exposed=(Window,Worker), SecureContext]
    interface PushSubscriptionOptions {
    readonly attribute boolean userVisibleOnly;
    [SameObject] readonly attribute ArrayBuffer? applicationServerKey;
    };

userVisibleOnly 属性在获取时返回初始化值。

applicationServerKey 属性在获取时返回初始化值。

如果存在,applicationServerKey 的值必须包含一个 P-256 椭圆曲线上的点 [DSS],采用 [ANSI-X9-62] 附录 A 描述的未压缩格式(即 65 字节,首字节为 0x04)。如以 DOMString 提供,则值必须用 [RFC7515] 的 base64url 编码。

applicationServerKey 不存在且 推送服务因运维需要要求时,用户代理可以拒绝订阅请求。

applicationServerKey 的值必须与用于消息加密的密钥不同 [RFC8291]。

7.3 PushSubscriptionOptionsInit 字典

WebIDLdictionary PushSubscriptionOptionsInit {
    boolean userVisibleOnly = false;
    (BufferSource or DOMString)? applicationServerKey = null;
    };

userVisibleOnly 成员设置为 true 时,表示 推送订阅仅用于对用户可见效果的 推送消息,例如显示 Web 通知。[NOTIFICATIONS]

PushSubscriptionOptionsInit 表示与 推送订阅关联的附加选项。用户代理可以在向用户请求明确权限时考虑这些选项。如选项被考虑,用户代理应当在收到的 推送消息上强制执行相关约束。

这些选项是可选的,用户代理可以只支持其中一部分。用户代理不得暴露不支持的选项。

一旦设置,推送订阅的选项不可更改。已有的 推送订阅可通过 unsubscribe 退订,以创建带新选项的 推送订阅

applicationServerKey 成员在与 推送服务建立 推送订阅时,由 用户代理使用。applicationServerKey 选项包含 应用服务器的椭圆曲线公钥。该密钥用于 应用服务器在向该 推送订阅发送 推送消息时进行身份验证,具体见 [RFC8292];推送服务会拒绝未用对应私钥生成认证令牌的 推送消息

8. PushSubscription 接口

PushSubscription 对象表示一个 推送订阅

WebIDL[Exposed=(Window,Worker), SecureContext]
    interface PushSubscription {
    readonly attribute USVString endpoint;
    readonly attribute EpochTimeStamp? expirationTime;
    [SameObject] readonly attribute PushSubscriptionOptions options;
    ArrayBuffer? getKey(PushEncryptionKeyName name);
    Promise<boolean> unsubscribe();

    PushSubscriptionJSON toJSON();
    };

    dictionary PushSubscriptionJSON {
    USVString endpoint;
    EpochTimeStamp? expirationTime = null;
    record<DOMString, USVString> keys;
    };

获取 endpoint 属性时,用户代理必须返回与该推送订阅关联的推送端点用户代理 必须使用不包含依赖输入分支(即常数时间)的序列化方法。

获取 expirationTime 属性时,用户代理必须返回与该推送订阅关联的订阅过期时间,如无则返回null

获取 options 属性时,用户代理必须返回一个 PushSubscriptionOptions对象,表示该推送订阅的相关选项。

getKey() 方法用于检索可用于加密和认证消息的密钥材料。调用 getKey() 时,执行以下过程:

  1. 查找与 name 参数指定的密钥名对应的内部槽。
  2. 如果未找到槽,则返回 null
  3. 用新实例化的 ArrayBuffer 初始化变量 key
  4. 如果内部槽包含非对称密钥对,则将 key 的内容设置为密钥对中的公钥序列化值。序列化格式见密钥名定义的规范。例如,[RFC8291] 规定 "p256dh" 公钥采用 [ANSI-X9-62] 附录 A 的未压缩格式(65 字节,首字节为 0x04)。
  5. 否则,如内部槽包含对称密钥,则将 key 的内容设置为内部槽值的副本。例如,auth 参数包含用户代理用于认证应用服务器发送消息的字节序列。
  6. 返回 key

"p256dh" 和 "auth" 两个密钥名必须支持,且其值必须与用户代理根据 [RFC8291] 解密收到推送消息所需一致。

unsubscribe() 方法调用时必须执行如下步骤:

  1. promise新 promise
  2. 返回 promise,并异步继续后续步骤。
  3. 如该 推送订阅已被 停用,则 resolve promisefalse,终止后续步骤。
  4. 并行运行以下步骤:
    1. 停用推送订阅用户代理不得为该 推送订阅继续投递 推送消息

      用户代理未能向 推送服务请求 停用推送订阅(如因网络失败),则应当在合理时间内重试请求。

  5. resolve promisetrue

toJSON() 方法调用时必须执行如下步骤:

  1. json 为新的 PushSubscriptionJSON 字典。
  2. 设置 json["endpoint"] 为 获取 endpoint 属性的结果。
  3. 设置 json["expirationTime"] 为 获取 expirationTime 属性的结果。
  4. keys 为新的空 record<DOMString, USVString> 实例。
  5. 对该 PushSubscription 内部槽的每个标识符 i,按密钥名排序:
    1. 如内部槽对应非对称密钥对,令 b 为密钥名 i 的公钥编码值,编码格式见密钥名定义(参见 getKey())。
    2. 否则,令 bgetKey 返回的值。
    3. sb 作为 USVString 的 URL 安全 base64 编码(无填充)[RFC4648]。用户代理必须使用不依赖 b 值的分支序列化方法。
    4. 设置 keys[i] 为 s
  6. 设置 json["keys"] 为 keys
  7. 返回 json

PushSubscriptionJSON 字典表示 JSON 类型PushSubscription。在 ECMAScript 中可通过 JSON.stringify() 方法转换为 JSON 字符串。

keys 记录包含每个已支持 PushEncryptionKeyName 条目的 URL 安全 base64 编码表示 [RFC4648]。

注意:PushSubscription 的选项不会被序列化。

8.1 PushEncryptionKeyName 枚举

用于 推送消息加密的密钥通过 getKey() 方法或 PushSubscription 的序列化器提供给 Web 应用。每个密钥都用 PushEncryptionKeyName 枚举中的值命名。

WebIDLenum PushEncryptionKeyName {
    "p256dh",
    "auth"
    };

p256dh 用于获取 [RFC8291] 描述的 P-256 ECDH Diffie-Hellman 公钥。

auth 用于获取 [RFC8291] 中描述的认证密钥。

9. PushMessageData 接口

WebIDL[Exposed=ServiceWorker, SecureContext]
    interface PushMessageData {
    ArrayBuffer arrayBuffer();
    Blob blob();
    Uint8Array bytes();
    any json();
    USVString text();
    };

PushMessageData 对象有一个关联的 bytes(一个 字节序列),在创建时设置。

arrayBuffer() 方法的步骤是返回一个 ArrayBuffer,其内容即 thisbytes。创建 ArrayBuffer 时抛出的异常会被重新抛出。

blob() 方法的步骤是返回一个新的 Blob 对象,其内容为 thisbytes

bytes() 方法的步骤是返回一个新的 Uint8Array,其底层 ArrayBuffer 的内容为 thisbytes。创建 ArrayBuffer 时抛出的异常会被重新抛出。

json() 方法的步骤是返回运行 解析 JSON 字节为 JavaScript 值,参数为 thisbytes

text() 方法的步骤是对 thisbytes 运行 UTF-8 解码

object 提取字节序列,步骤如下:

  1. bytes 为空字节序列。
  2. 根据 object 的类型进行分支:
    BufferSource
    设置 bytesobject 内容的副本。
    USVString
    设置 bytes 为对 object 运行 UTF-8 编码 的结果。
  3. 返回 bytes

10. 事件

10.1 ServiceWorkerGlobalScope 接口的扩展

Service Worker 规范定义了 ServiceWorkerGlobalScope 接口 [SERVICE-WORKERS],本规范对其进行了扩展。

WebIDL[Exposed=ServiceWorker, SecureContext]
    partial interface ServiceWorkerGlobalScope {
    attribute EventHandler onpush;
    attribute EventHandler onpushsubscriptionchange;
    };

onpush 属性是一个 事件处理器 IDL 属性, 其对应的 事件类型 为 "push"。"push" 事件表示 推送消息已被投递到 推送订阅

onpushsubscriptionchange 属性是一个 事件处理器 IDL 属性, 对应的 事件类型为 "pushsubscriptionchange"。

10.2 PushEvent 接口

WebIDL[Exposed=ServiceWorker, SecureContext]
    interface PushEvent : ExtendableEvent {
    constructor(DOMString type, optional PushEventInit eventInitDict = {});
    readonly attribute PushMessageData? data;
    readonly attribute Notification? notification;
    };

    dictionary PushEventInit : ExtendableEventInit {
    PushMessageDataInit? data = null;
    Notification? notification = null;
    };

    typedef (BufferSource or USVString) PushMessageDataInit;

PushEvent 接口或继承自该接口的接口的 constructor 被调用时,标准事件构造步骤会扩展如下:

  1. 如果 eventInitDictdata 成员不存在,则将事件的 data 属性设为 null,结束步骤。
  2. b 为对 eventInitDict 的 "data" 成员 提取字节序列的结果。
  3. 将事件的 data 属性设为新的 PushMessageData 实例,其 bytesb

data 属性必须返回初始化时的值。

notification 属性必须返回初始化时的值。

10.3 接收 推送消息

用户代理推送服务 收到推送消息时, 必须执行以下步骤。

  1. subscription 为与该 推送消息 对应的活动推送订阅

  2. registrationsubscription关联 Service Worker 注册

  3. bytes 为 null。

  4. 如果推送消息包含负载:

    1. 使用与 subscription 关联的密钥对中的私钥,并按照 [RFC8291] 所述过程, 解密推送消息的负载。 将 bytes 设为所得的字节序列

    2. 如果由于任何原因无法解密推送消息的负载, 则确认推送消息,并中止后续步骤。

      注意

      未能通过与推送订阅关联的密钥对成功解密的 推送消息, 不会触发 push 事件。

  5. 如果 bytes 非 null:

    1. baseURLsubscriptionscope

    2. originbaseURLorigin

    3. fallbackTimestamp当前粗粒度墙上时间

    4. declarativeResult 为运行 声明式推送消息解析器, 参数为 bytesoriginbaseURLfallbackTimestamp 的结果。

    5. 如果 declarativeResult 非失败:

      1. notificationdeclarativeResultnotification

      2. 设置 notificationService Worker 注册registration

      3. notificationShown 为 false。

      4. 如果 declarativeResultmutable 为 true 且 registration 非 null:

        1. result触发 push 事件的结果, 参数为 registration、null 和一个表示 notification 的新的 Notification 对象。

        2. 如果 result 非失败,则将 notificationShown 设为 resultnotification shown

      5. 如果 notificationShown 为 false,则按 通知显示步骤, 参数为 notification

      6. 确认 推送消息,并中止后续步骤。

  6. 如果 registration 为 null,则中止后续步骤。

  7. data 为一个新的 PushMessageData 对象, 其 bytesbytes(如果 bytes 非 null),否则为 null。

  8. result触发 push 事件的结果, 参数为 registrationdata 和 null。

  9. 如果 result 为失败,且同一个 推送消息 已多次投递给 Service Worker 注册且均未成功, 则确认 推送消息

  10. 如果 result 非失败,则确认 推送消息

push 事件结果是一个元组,包含一个notification shown(一个 布尔值)。

触发 push 事件,参数为 Service Worker 注册 registrationPushMessageData 对象或 null data, 以及 notification 或 null notification,步骤如下。返回失败或 push 事件结果

  1. notificationResult 为 null。

  2. 设置 registrationshowNotification() 是否已成功调用 为 false。

  3. 使用 PushEvent,在 registration触发功能事件,事件名为 "push",并设置如下属性:

    data
    data
    notification
    notification

    然后与 dispatchedEvent 并行执行:

    1. 等待 dispatchedEvent 的所有 延长生命周期 promise 都 resolve。

    2. 若未全部成功 resolve,则设置 notificationResult 为失败。

    3. 否则,设置 notificationResultregistrationshowNotification() 是否已成功调用

  4. 等待 notificationResult 非 null。

  5. 如果 notificationResult 为失败,则返回失败。

  6. 断言notificationResult布尔值

  7. 返回 (notificationResult)。

确认推送消息,参数为 pushMessage 表示按照 [RFC8030] 确认收到 pushMessage

确认 推送消息会让 推送服务停止投递该消息,并向 应用服务器报告成功。这能防止 推送消息推送服务无限重试投递。

确认意味着 应用服务器可能错误地收到投递成功的回执。因此,在确认前应当允许多次拒绝,建议至少允许三次尝试。

10.4 pushsubscriptionchange 事件

pushsubscriptionchange 事件表示 推送订阅发生了应用无法控制的变更,例如被刷新、撤销或丢失。

触发 "pushsubscriptionchange" 事件,参数为 Service Worker 注册 registrationnewSubscriptionoldSubscription用户代理需要在 registration 上使用 PushSubscriptionChangeEvent 触发功能事件, 事件名为 "pushsubscriptionchange",并设置如下属性:

newSubscription
newSubscription
oldSubscription
oldSubscription
注意

在将新的 推送订阅 的详细信息发送到你的 应用服务器时, 考虑使用更可靠的同步机制,如 [WEB-BACKGROUND-SYNC]。 用户可能受到不可靠的网络条件的影响,这可能导致获取失败。

10.4.1 PushSubscriptionChangeEvent 接口

WebIDL[Exposed=ServiceWorker, SecureContext]
    interface PushSubscriptionChangeEvent : ExtendableEvent {
    constructor(DOMString type, optional PushSubscriptionChangeEventInit eventInitDict = {});
    readonly attribute PushSubscription? newSubscription;
    readonly attribute PushSubscription? oldSubscription;
    };

newSubscription 属性,获取时,返回初始化时的值。

oldSubscription 属性,获取时,返回初始化时的值。

10.4.2 PushSubscriptionChangeEventInit 接口

WebIDLdictionary PushSubscriptionChangeEventInit : ExtendableEventInit {
    PushSubscription newSubscription = null;
    PushSubscription oldSubscription = null;
    };

newSubscription 成员详细说明了每次调用 推送订阅时有效的 pushsubscriptionchange 事件。 当无法建立新的推送订阅时,该值将为 null,例如,因为 Web 应用程序已失去明确许可

oldSubscription 成员详细说明了不应再使用的 推送订阅。 当用户代理无法提供全套详细信息时,该值将为 null,例如,由于部分数据库损坏。

11. 可访问性

推送 API 本身不提供任何方式来展示从 推送服务接收到的数据。相反,推送 API 依赖其他 API——主要是 通知 API 标准——来向最终用户展示收到的信息。因此,推送 API 本身没有可访问性要求。不过,[NOTIFICATIONS] 等规范对如何以无障碍方式展示通知提供了指导。

此外,呈现一个无障碍界面可能需要传递比推送消息所能承载的更多信息。推送消息更适合携带少量内容或标识符。如需传递较大的资源,则需要从服务器获取。

12. 一致性

除了标记为非规范性的章节外,本规范中的所有编写指南、图示、示例和注释均为非规范性内容。规范中的其他内容均为规范性要求。

本文档中的关键词 MAYMUSTMUST NOTSHOULDSHOULD NOT 应按照 BCP 14 [RFC2119] [RFC8174] 的说明进行解释,仅当它们以全大写形式出现时,才具有上述含义。

本规范定义了一套一致性标准,适用于单一产品:实现其中接口的 用户代理

A. IDL 索引

WebIDLdictionary PushPermissionDescriptor : PermissionDescriptor {
    boolean userVisibleOnly = false;
    };

    [SecureContext]
    interface mixin PushManagerAttribute {
    readonly attribute PushManager pushManager;
    };
    Window includes PushManagerAttribute;
    ServiceWorkerRegistration includes PushManagerAttribute;

    [Exposed=(Window,Worker), SecureContext]
    interface PushManager {
    [SameObject] static readonly attribute FrozenArray<DOMString> supportedContentEncodings;

    Promise<PushSubscription> subscribe(optional PushSubscriptionOptionsInit options = {});
    Promise<PushSubscription?> getSubscription();
    Promise<PermissionState> permissionState(optional PushSubscriptionOptionsInit options = {});
    };

    [Exposed=(Window,Worker), SecureContext]
    interface PushSubscriptionOptions {
    readonly attribute boolean userVisibleOnly;
    [SameObject] readonly attribute ArrayBuffer? applicationServerKey;
    };

    dictionary PushSubscriptionOptionsInit {
    boolean userVisibleOnly = false;
    (BufferSource or DOMString)? applicationServerKey = null;
    };

    [Exposed=(Window,Worker), SecureContext]
    interface PushSubscription {
    readonly attribute USVString endpoint;
    readonly attribute EpochTimeStamp? expirationTime;
    [SameObject] readonly attribute PushSubscriptionOptions options;
    ArrayBuffer? getKey(PushEncryptionKeyName name);
    Promise<boolean> unsubscribe();

    PushSubscriptionJSON toJSON();
    };

    dictionary PushSubscriptionJSON {
    USVString endpoint;
    EpochTimeStamp? expirationTime = null;
    record<DOMString, USVString> keys;
    };

    enum PushEncryptionKeyName {
    "p256dh",
    "auth"
    };

    [Exposed=ServiceWorker, SecureContext]
    interface PushMessageData {
    ArrayBuffer arrayBuffer();
    Blob blob();
    Uint8Array bytes();
    any json();
    USVString text();
    };

    [Exposed=ServiceWorker, SecureContext]
    partial interface ServiceWorkerGlobalScope {
    attribute EventHandler onpush;
    attribute EventHandler onpushsubscriptionchange;
    };

    [Exposed=ServiceWorker, SecureContext]
    interface PushEvent : ExtendableEvent {
    constructor(DOMString type, optional PushEventInit eventInitDict = {});
    readonly attribute PushMessageData? data;
    readonly attribute Notification? notification;
    };

    dictionary PushEventInit : ExtendableEventInit {
    PushMessageDataInit? data = null;
    Notification? notification = null;
    };

    typedef (BufferSource or USVString) PushMessageDataInit;

    [Exposed=ServiceWorker, SecureContext]
    interface PushSubscriptionChangeEvent : ExtendableEvent {
    constructor(DOMString type, optional PushSubscriptionChangeEventInit eventInitDict = {});
    readonly attribute PushSubscription? newSubscription;
    readonly attribute PushSubscription? oldSubscription;
    };

    dictionary PushSubscriptionChangeEventInit : ExtendableEventInit {
    PushSubscription newSubscription = null;
    PushSubscription oldSubscription = null;
    };

B. 致谢

编辑们感谢 Mozilla 和 Telefónica Digital 团队实现 Firefox OS 推送消息解决方案,同时感谢下列在本文件中提供重要技术意见的人员:Antonio Amaya、Miguel García Arribas、Ben Bangert、Kit Cambridge、José Manuel Cantera、JR Conlin、Albert Crespell、Matt Gaunt、Phil Jenvey、Guillermo López、Nikhil Marathe、John Mellor、Pınar Özlen、Fernando R. Sela、Shijun Sun 和 Doug Turner。

C. 参考文献

C.1 规范性引用

[ANSI-X9-62]
金融服务业公钥密码学,椭圆曲线数字签名算法(ECDSA)。ANSI。2005。URL: https://webstore.ansi.org/RecordDetail.aspx?sku=ANSI+X9.62%3a2005
[dom]
DOM 标准。Anne van Kesteren。WHATWG。现行标准。URL: https://dom.spec.whatwg.org/
[DSS]
FIPS PUB 186-5:数字签名标准(DSS)。美国商务部/国家标准与技术研究院。2023年2月3日。国家标准。URL: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf
[ecmascript]
ECMAScript 语言规范。Ecma International。URL: https://tc39.es/ecma262/multipage/
[encoding]
编码标准。Anne van Kesteren。WHATWG。现行标准。URL: https://encoding.spec.whatwg.org/
[fileapi]
文件 API。Marijn Kruisselbrink。W3C。2024年12月4日。W3C 工作草案。URL: https://www.w3.org/TR/FileAPI/
[hr-time]
高精度时间。Yoav Weiss。W3C。2024年11月7日。W3C 工作草案。URL: https://www.w3.org/TR/hr-time-3/
[HTML]
HTML 标准。Anne van Kesteren;Domenic Denicola;Dominic Farolino;Ian Hickson;Philip Jägenstedt;Simon Pieters。WHATWG。现行标准。URL: https://html.spec.whatwg.org/multipage/
[infra]
Infra 标准。Anne van Kesteren;Domenic Denicola。WHATWG。现行标准。URL: https://infra.spec.whatwg.org/
[NOTIFICATIONS]
通知 API 标准。Anne van Kesteren。WHATWG。现行标准。URL: https://notifications.spec.whatwg.org/
[Permissions]
权限。Marcos Caceres;Mike Taylor。W3C。2025年6月24日。W3C 工作草案。URL: https://www.w3.org/TR/permissions/
[RFC2119]
RFC 中用于指示需求级别的关键词。S. Bradner。IETF。1997年3月。最佳当前实践。URL: https://www.rfc-editor.org/rfc/rfc2119
[RFC4648]
Base16、Base32 和 Base64 数据编码。S. Josefsson。IETF。2006年10月。建议标准。URL: https://www.rfc-editor.org/rfc/rfc4648
[RFC7231]
超文本传输协议(HTTP/1.1):语义与内容。R. Fielding, Ed.;J. Reschke, Ed. IETF。2014年6月。建议标准。URL: https://httpwg.org/specs/rfc7231.html
[RFC7515]
JSON Web 签名(JWS)。M. Jones;J. Bradley;N. Sakimura。IETF。2015年5月。建议标准。URL: https://www.rfc-editor.org/rfc/rfc7515
[RFC8030]
使用 HTTP 推送通用事件投递。M. Thomson;E. Damaggio;B. Raymor, Ed. IETF。2016年12月。建议标准。URL: https://www.rfc-editor.org/rfc/rfc8030
[RFC8174]
RFC 2119 关键词大小写歧义。B. Leiba。IETF。2017年5月。最佳当前实践。URL: https://www.rfc-editor.org/rfc/rfc8174
[RFC8291]
Web 推送消息加密。M. Thomson。IETF。2017年11月。建议标准。URL: https://www.rfc-editor.org/rfc/rfc8291
[RFC8292]
Web 推送自愿应用服务器标识(VAPID)。M. Thomson;P. Beverloo。IETF。2017年11月。建议标准。URL: https://www.rfc-editor.org/rfc/rfc8292
[SERVICE-WORKERS]
Service Workers。Yoshisato Yanagisawa;Monica CHINTALA。W3C。2025年3月6日。CRD。URL: https://www.w3.org/TR/service-workers/
[url]
URL 标准。Anne van Kesteren。WHATWG。现行标准。URL: https://url.spec.whatwg.org/
[WEBIDL]
Web IDL 标准。Edgar Chen;Timothy Gu。WHATWG。现行标准。URL: https://webidl.spec.whatwg.org/

C.2 参考性引用

[Fetch]
Fetch 标准。Anne van Kesteren。WHATWG。现行标准。URL: https://fetch.spec.whatwg.org/
[WEB-BACKGROUND-SYNC]
Web 后台同步。W3C。社区组草案报告。URL: https://wicg.github.io/background-sync/spec/
[WebSockets]
WebSockets 标准。Adam Rice。WHATWG。现行标准。URL: https://websockets.spec.whatwg.org/