推送 API

W3C 工作草案

关于本文档的更多信息
此版本:
https://www.w3.org/TR/2026/WD-push-api-20261005/
最新发布版本:
https://www.w3.org/TR/push-api/
最新编辑草案:
https://w3c.github.io/push-api/
历史记录:
https://www.w3.org/standards/history/push-api/
提交历史
编辑:
Marcos Caceres (Apple Inc.)
Kagami Rosylight (Mozilla基金会)
曾任编辑:
Peter Beverloo (Google) - 至
Martin Thomson (Mozilla基金会) - 至
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 头,如 [RFC9110] 第 8.4 节所述, 用于指示应用于 推送消息 负载的内容编码类型。

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(一个布尔值)。

声明式推送消息解析器给定一个字节序列 bytes、origin origin、URL 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. 令 notificationOptions 为 NotificationOptions 字典。

  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. 令 notificationAction 为 NotificationAction 字典 «[ "action" → actionInput["action"], "title" → actionInput["title"], "navigate" → actionNavigate ]»。

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

      7. 追加 notificationAction到 notificationActions。

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

  26. 令 notification 为运行 创建通知,参数为 notificationTitle、notificationOptions、origin、 baseURL 和 fallbackTimestamp 的结果。若抛出异常,则返回失败。

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

  28. 若 notification 的actions 中任一导航 URL为 null,则返回失败。

  29. 令 mutable = false。

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

  31. 返回 (notification, mutable)。

3.4 推送订阅

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

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

当推送订阅的 scope的 path为 list,长度为 1,且 scope的 path[0]为空字符串时,认为其具有 窗口可访问作用域。

注意

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

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

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

一个推送订阅具有一个关联的P-256 ECDH 密钥对和一个 身份验证秘密,符合 [RFC8291]。

如果用户代理出于任何原因必须更改某个推送订阅的密钥 且该推送订阅的关联的服务工作线程注册 非 null, 则它必须刷新该推送 订阅。

要创建推送订阅,给定一个PushSubscriptionOptionsInit optionsDictionary:

  1. 令subscription为一个新的PushSubscription。
  2. 令options为一个新创建的PushSubscriptionOptions 对象,并使用 optionsDictionary中对应的成员和值初始化其属性。
  3. 将subscription的options属性设置为options。
  4. 将subscription的P-256 ECDH 密钥对设置为生成一个新的 P-256 ECDH 密钥对的结果 [ANSI-X9-62]。
  5. 将subscription的身份验证秘密设置为生成一个 新的 身份验证秘密的结果,该秘密是 [RFC8291] 第 3.2 节中定义的八位字节序列。
  6. 请求一个新的推送订阅。当其已被 设置时,包括options的applicationServerKey 属性。重新抛出任何异常。
  7. 当推送订阅请求成功完成时:
    1. 将subscription的endpoint属性设置为该 推送 订阅的推送端点。
    2. 如果由推送订阅提供,则设置subscription的 expirationTime。
  8. 返回subscription。

3.4.1 与 Service Worker 注册的关系

一个推送订阅的关联的服务工作线程注册是 服务工作线程 注册,其作用域 URL 等于该推送订阅的作用域(如果存在); 否则为 null。

注

一个推送订阅的关联的 服务工作线程注册只能为 null 当它具有一个窗口可访问作用域时。

反之,一个服务工作线程 注册的关联的推送 订阅是这样的推送订阅,其作用域 等于该服务工作线程 注册的作用域 URL(如果存在);否则 为 null。

3.4.2 订阅刷新

用户代理或推送服务可以选择在任何时候刷新一个推送 订阅,其关联的服务工作线程 注册非 null,例如因为它已达到一定 时限。

发生这种情况时,用户代理 必须运行以下步骤,以创建推送 订阅,所给定的 PushSubscriptionOptions 是 为 创建当前推送订阅而提供的,并将新的推送订阅的 作用域设置为原订阅的作用域。 新的推送订阅 必须具有一个 与 原订阅不同的密钥对。

成功后,用户代理随后必须触发 "pushsubscriptionchange" 事件,并将与该服务工作线程 注册关联的推送 订阅作为registration,将一个表示 初始推送订阅的PushSubscription实例作为oldSubscription,并将一个 PushSubscription 表示新的推送订阅的实例作为 newSubscription。

为了留出时间将更改传播到应用服务器,用户 代理可以在刷新后的一小段时间内继续接受旧推送订阅的 消息。一旦已为刷新的推送 订阅接收到消息,任何旧的推送订阅必须被停用。

如果用户代理无法刷新推送订阅,则其应该 定期重试刷新。当推送订阅无法再 被 使用时,例如因为其已过期,用户代理 必须 触发 "pushsubscriptionchange" 事件,并将与服务工作线程 注册 相关联的推送订阅作为registration,将一个PushSubscription 实例作为oldSubscription,该实例表示正在停用的推送订阅,并将 oldSubscription 以及将null作为newSubscription。

3.4.3 订阅停用

当一个推送订阅被停用时, 用户代理和推送服务都必须删除其详细信息的任何已存储副本 。后续用于此推送消息的推送 订阅不得被 传递。

一个不具有窗口可访问作用域的推送订阅必须 在其关联的服务工作线程 注册被 注销时停用,但推送订阅可以更早被停用。

注

一个不具有推送订阅的窗口可访问作用域的订阅会在 服务工作线程 注册被清除时移除。

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 应用程序具有相同源的应用服务器 发送的。应用服务器能够自行 决定与第三方共享使用推送订阅所必需的详细信息 。

以下要求旨在尽可能保护用户的隐私和安全,并且 在满足该目标的前提下,保护 应用服务器与用户之间通信的完整性。

Push API 可能必须唤醒与服务工作线程 注册关联的服务工作线程,以运行开发者提供的事件处理程序。这可能导致 资源使用,例如网络流量,用户代理应该将其 归因于创建推送订阅的 Web 应用程序。

用户 代理在获取 权限或确定权限状态时可以考虑PushSubscriptionOptions。

当权限被撤销时,用户代理可以为使用该权限创建的订阅触发 "pushsubscriptionchange" 事件,并将与服务工作线程 注册关联的推送订阅作为 registration,将表示该PushSubscription实例 所表示的推送订阅 作为oldSubscription,并将null作为newSubscription。用户 代理必须 并行停用受影响的订阅。

推送端点不得暴露可供 推送服务以外的参与方推导出有关用户的信息,例如用户的设备、身份或位置。有关 确切要求,请参阅 [RFC8030] 中的隐私注意事项。

已停用的推送订阅的推送端点不得被 重用于新的推送订阅。这可防止创建用户无法移除的持久 标识符。这也可防止重复使用一个 推送订阅的详细信息,向另一个推送消息发送至另一个 推送订阅。

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 混入, 扩展了 Window 和 ServiceWorkerRegistration 对象。 [HTML] [SERVICE-WORKERS]

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

Window 和 ServiceWorkerRegistration 对象都拥有一个关联的 PushManager 对象。

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

在 Window 对象上, PushManager 的 service worker registration 为 null。

在 ServiceWorkerRegistration 对象上, PushManager 的 service 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. 令 global 为this 的相关 全局对象。
  3. 令 scope 为 null。
  4. 令 registration 为 null。
  5. 如果this 的服务工作线程注册为 null:
    1. 将 scope 设置为运行基本 URL 解析器的结果,给定 "/" 和 global 的关联的 Document 的URL。
  6. 否则:
    1. 断言:this 的服务工作线程 注册是一个服务 工作线程注册。
    2. 将 registration 设置为this 的服务工作线程 注册。
  7. 如果 scope 为失败或为一个URL,且其方案不是 "https", 则 将一个全局 任务排入队列,在网络 任务源上使用 global 来拒绝 promise,并带有一个 "NotAllowedError" DOMException,然后 终止这些步骤。
  8. 如果 options["userVisibleOnly"] 为 false 且用户 代理要求其为 true,则将一个全局 任务排入队列,在网络 任务源上使用 global 来拒绝 promise,并带有一个 "NotAllowedError" DOMException,然后 终止这些步骤。
  9. 如果 options["applicationServerKey"] 为 null 且 推送服务要求其非 null,则将一个全局 任务排入队列,在 网络 任务源上使用 global 来拒绝 promise,并带有一个 "NotSupportedError" DOMException,然后 终止这些步骤。
  10. 如果 options["applicationServerKey"] 非 null:
    1. 如果 options["applicationServerKey"] 是字符串,则 将其值设置为一个ArrayBuffer, 其中包含通过 使用 base64url 编码 [RFC7515] 解码 options["applicationServerKey"] 所得到的八位字节序列。
    2. 如果解码失败,则将 一个全局任务排入队列,在网络 任务源上 使用 global 来拒绝 promise,并带有一个 "InvalidCharacterError" DOMException ,然后终止这些步骤。
    3. 确保 options["applicationServerKey"] 描述 P-256 曲线上的一个有效点。如果其值无效,则将 一个全局任务排入队列 到网络 任务源上,使用 global 来拒绝 promise,并带有一个 "InvalidAccessError" DOMException ,然后终止这些步骤。
  11. 并行运行这些步骤:
    1. 令 subscription 为 null。
    2. 如果 scope 非 null:
      1. 如果存在一个推送订阅, 它具有一个窗口可访问 作用域,且其作用域 等于 scope,则将 subscription 设置为该推送订阅。
    3. 否则:
      1. 断言: registration 非 null。
      2. 如果 registration 的活动 工作线程为 null,则 将 一个全局任务排入队列,在网络 任务源上使用 global 来 拒绝 promise,并带有一个 "InvalidStateError" DOMException ,然后终止 这些步骤。
      3. 如果 registration 的关联的推送订阅 非 null,则将 subscription 设置为 registration 的关联的 推送订阅。
      4. 将 scope 设置为 registration 的作用域 URL。
    4. 令 permission 为请求 使用权限 "push" 的结果。
    5. 如果 permission 为 "denied", 则将 一个全局任务排入队列,在 用户 交互任务源上使用 global 来拒绝 promise,并带有一个 "NotAllowedError" DOMException ,然后终止这些步骤。
    6. 如果 subscription 非 null:
      1. 如果 subscription 存在错误,则将 一个全局任务排入队列,在 网络 任务源上使用 global 来拒绝 promise,并带有 一个 "AbortError" DOMException ,然后终止这些步骤。
      2. 将 options 参数与 subscription 的 options 属性进行比较。 BufferSource 值的内容按相等性进行比较,而不是按 引用进行比较。
      3. 如果 options 上的任何属性所包含的值与为 subscription 存储的值不同,则将 一个全局任务排入队列,在网络 任务源上 使用 global 来拒绝 promise,并带有 一个 "InvalidStateError" DOMException ,然后终止这些步骤。
      4. 将 一个全局任务排入队列,在网络 任务源上使用 global 来 兑现 promise,其值为 subscription,然后终止这些步骤。
    7. 断言: subscription 为 null,且 scope 是一个URL。
    8. 将 subscription 设置为尝试使用 options 创建推送订阅的结果。如果创建订阅抛出一个异常,则将 一个全局任务排入队列,在网络 任务源上使用 global 来拒绝 promise,并带有该异常,然后终止这些 步骤。
    9. 将 subscription 的作用域设置为 scope。
    10. 将 一个全局任务排入队列,在网络 任务源上使用 global 来 兑现 promise,其值为与 subscription 对应的PushSubscription。
  12. 返回 promise。

getSubscription() 方法的步骤如下:

  1. 令 promise 为一个新 promise。
  2. 令 global 为this 的相关 全局对象。
  3. 令 windowScope 为 null。
  4. 令 registration 为 null。
  5. 如果this 的服务工作线程注册为 null,则将 windowScope 设置为运行基本 URL 解析器的结果,给定 "/" 和 global 的 关联的 Document 的URL。
  6. 否则:
    1. 断言:this 的服务工作线程 注册是一个服务 工作线程注册。
    2. 将 registration 设置为this 的服务工作线程 注册。
  7. 并行运行这些步骤:
    1. 令 subscription 为 null。
    2. 如果 windowScope 非 null:
      1. 如果存在一个推送订阅, 其作用域是 windowScope,则将 subscription 设置为该推送订阅。
    3. 否则:
      1. 断言: registration 非 null。
      2. 如果 registration 的关联的推送订阅 非 null,则将 subscription 设置为 registration 的关联的 推送订阅。
    4. 如果 subscription 为 null,则以 null 兑现 promise。
    5. 如果 subscription 存在错误,则拒绝 promise,并带有一个DOMException, 其名称为 "AbortError",然后 终止这些步骤。
    6. 以与 subscription 对应的PushSubscription兑现 promise。
  8. 返回 promise。

permissionState() 方法的步骤如下:

  1. 令 promise 为一个新的 promise。

  2. 令 global 为this的相关全局对象。

  3. 并行执行:

    1. 令 state 为 "push" 的权限状态。

    2. 在 网络任务源上,使用 global 排队一个全局任务,以 state 解决 promise。

  4. 返回 promise。

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

如果需要申请权限,应通过调用 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 提供, 该值 必须 使用 base64url 编码 [RFC7515]。

当 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 PushSubscriptionKeys {
  USVString p256dh;
  USVString auth;
};

dictionary PushSubscriptionJSON {
  USVString endpoint;
  EpochTimeStamp? expirationTime = null;
  PushSubscriptionKeys keys;
};

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

当获取 expirationTime 属性时,用户 代理必须返回 与订阅到期时间关联的推送订阅(如果 存在),否则返回 null。

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

getKey(name) 方法的步骤如下:

  1. 令 key 为一个新的ArrayBuffer 对象。
  2. 如果 name 为 "p256dh":
    1. 将 key 的内容设置为this 的 P-256 ECDH 密钥对中公钥的序列化值,使用 [ANSI-X9-62] 附录 A 中定义的未压缩格式(即,以 0x04 八位字节开头的 65 八位字节序列),如 [RFC8291] 所规定。
  3. 否则:
    1. 断言:name 为 "auth"
    2. 将 key 的内容设置为this 的身份验证秘密值的副本。
  4. 返回 key。

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

  1. 令 promise 为一个 新 promise。
  2. 返回 promise,并异步继续以下步骤。
  3. 如果推送订阅已经被停用,则以 false 兑现 promise 并终止这些步骤。
  4. 并行运行以下步骤:
    1. 停用该推送 订阅。用户代理不得 再传递任何用于该推送消息的推送 订阅。

      如果用户代理未能请求推送服务 停用该推送 订阅,例如由于网络 故障,则它应该在合理的 时间内重试对推送服务的请求。

  5. 以 true 兑现 promise。

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

  1. 令 json 为一个新的PushSubscriptionJSON 字典。
  2. 将 json["endpoint"] 设置为获取 endpoint 属性的结果,该属性属于 this。
  3. 将 json["expirationTime"] 设置为获取 expirationTime 属性的结果,该属性属于this。
  4. 令 keys 为一个新的PushSubscriptionKeys 字典。
  5. 将 keys["p256dh"] 设置为 getKey("p256dh") 返回值的 不带填充的 URL 安全 base64 编码 [RFC4648],并作为USVString。
  6. 将 keys["auth"] 设置为 getKey("auth") 返回值的 不带填充的 URL 安全 base64 编码 [RFC4648],并作为USVString。
  7. 将 json["keys"] 设置为 keys。
  8. 返回 json。

PushSubscriptionJSON 字典表示 PushSubscription 的JSON 类型。在 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,其内容即 this 的 bytes。创建 ArrayBuffer 时抛出的异常会被重新抛出。

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

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

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

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

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

  1. 令 bytes 为空字节序列。
  2. 根据 object 的类型进行分支:
    BufferSource
    设置 bytes 为 object 内容的副本。
    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 接口或继承自 PushEvent 接口的某个接口的constructor被调用时,通常的事件 构造 步骤会扩展为包括以下步骤:

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

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

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

10.3 接收 推送消息

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

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

  2. 令 registration 为 subscription 的 关联 Service Worker 注册。

  3. 令 bytes 为 null。

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

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

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

      注

      对于未成功解密的推送 消息,不会触发 push 事件,该消息未成功 使用与推送订阅关联的密钥对进行解密。

  5. 如果 bytes 非 null:

    1. 令 baseURL 为 subscription 的 scope。

    2. 令 origin 为 baseURL 的 origin。

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

    4. 令 declarativeResult 为运行 声明式推送消息解析器, 参数为 bytes、origin、baseURL 和 fallbackTimestamp 的结果。

    5. 如果 declarativeResult 非失败:

      1. 令 notification 为 declarativeResult 的 notification。

      2. 设置 notification 的 Service Worker 注册 为 registration。

      3. 令 notificationShown 为 false。

      4. 如果 declarativeResult 的 mutable 为 true 且 registration 非 null:

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

        2. 如果 result 非失败,则将 notificationShown 设为 result 的 notification shown。

      5. 如果 notificationShown 为 false,则运行通知 显示步骤 ,给定 notification 和 null。

        注

        由于不存在环境 设置对象,因此传入 null。这也 确保 notification 的资源(例如其图标)在获取时 不会涉及服务 工作线程。

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

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

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

  8. 令 result 为 触发 push 事件的结果, 参数为 registration、data 和 null。

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

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

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

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

  1. 令 notificationResult 为 null。

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

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

    data
    data
    notification
    notification

    然后与 dispatchedEvent 并行执行:

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

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

    3. 否则,设置 notificationResult 为 registration 的 showNotification() 是否已成功调用。

  4. 等待 notificationResult 非 null。

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

  6. 断言:notificationResult 是 布尔值。

  7. 返回 (notificationResult)。

要确认推送消息,给定一个推送消息 pushMessage,意味着按照 [RFC8030] 确认已收到 pushMessage 。

确认该推送消息会使推送服务停止 传递 该消息,并向应用服务器报告成功。这可以防止 同一 个推送消息被推送服务无限期重试。

确认还意味着应用服务器可能会错误地 收到一份 表明推送消息已成功传递的传递回执。因此, 在确认之前应该允许多次拒绝;建议至少允许 三次 尝试。

10.4 pushsubscriptionchange 事件

pushsubscriptionchange 事件表示 推送订阅发生了更改, 该更改是在应用程序控制之外触发的,例如因为它已被 刷新、撤销或丢失。

要触发 "pushsubscriptionchange" 事件,给定一个服务工作线程 注册 registration、newSubscription 和 oldSubscription,用户 代理必须触发一个功能事件 ,名称为 "pushsubscriptionchange",并使用 PushSubscriptionChangeEvent 在 registration 上设置以下属性:

newSubscription
newSubscription
oldSubscription
oldSubscription
注

将新的推送订阅的详细信息发送到你的应用 服务器时,请考虑使用更可靠的同步机制,例如 [WEB-BACKGROUND-SYNC]。用户可能处于不可靠的网络环境中,这可能导致 fetch 失败。

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. 一致性

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

本文档中的关键词 MAY、MUST、MUST NOT、SHOULD 和 SHOULD 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 PushSubscriptionKeys {
  USVString p256dh;
  USVString auth;
};

dictionary PushSubscriptionJSON {
  USVString endpoint;
  EpochTimeStamp? expirationTime = null;
  PushSubscriptionKeys 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。2026年 9月12日。W3C 工作草案。URL:https://www.w3.org/TR/FileAPI/
[hr-time]
高精度时间。Yoav Weiss。W3C。2026年 9月1日。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年10月6日。W3C 工作草案。URL:https://www.w3.org/TR/permissions/
[RFC2119]
RFC 中用于指示 要求级别的关键词。S. Bradner。IETF。1997年3月。最佳当前实践。URL:https://www.rfc-editor.org/info/rfc2119/
[RFC4648]
Base16、Base32 和 Base64 数据 编码。S. Josefsson。IETF。2006年10月。提议标准。URL:https://www.rfc-editor.org/info/rfc4648/
[RFC7515]
JSON Web 签名(JWS)。M. Jones; J. Bradley; N. Sakimura。IETF。2015年5月。提议标准。URL:https://www.rfc-editor.org/info/rfc7515/
[RFC8030]
使用 HTTP Push 的通用事件传递。M. Thomson; E. Damaggio; B. Raymor, Ed.。IETF。2016年12月。提议 标准。URL:https://www.rfc-editor.org/info/rfc8030/
[RFC8174]
RFC 2119 关键词中大写与小写的歧义。B. Leiba。IETF。2017年5月。最佳当前实践。URL:https://www.rfc-editor.org/info/rfc8174/
[RFC8291]
Web Push 消息加密。 M. Thomson。IETF。2017年11月。提议标准。URL:https://www.rfc-editor.org/info/rfc8291/
[RFC8292]
Web Push 的自愿应用服务器标识 (VAPID)。M. Thomson; P. Beverloo。IETF。2017年11月。提议 标准。URL:https://www.rfc-editor.org/info/rfc8292/
[RFC9110]
HTTP 语义。R. Fielding, Ed.; M. Nottingham, Ed.; J. Reschke, Ed.。IETF。2022年6月。互联网标准。URL:https://httpwg.org/specs/rfc9110.html
[SERVICE-WORKERS]
Service Workers 每夜版。Monica CHINTALA; Yoshisato Yanagisawa。W3C。2026年9月17日。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/