推送 API

W3C 工作草案

关于本文档的更多细节
当前版本:
https://www.w3.org/TR/2024/WD-push-api-20240903/
最新发布版本:
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 (Apple 公司)
前任编辑:
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 技术报告索引中找到, 地址为 https://www.w3.org/TR/。

本文档由 Web 应用工作组 作为工作草案发布, 并采用了 推荐流程

作为工作草案发布并不意味着 W3C 及其成员的认可。

这是一个草案文档,可能会随时更新、替换或被其他文档废弃。在引用此文档时,除了作为进行中的工作外,不应作为其他用途引用。

本文档由一个遵循 W3C 专利政策 的小组制作。W3C 维护了 与本小组成果相关的专利披露公开列表, 该页面还包括披露专利的说明。任何知晓可能包含 基本声明 的专利的个人,必须按照 W3C 专利政策 第6节披露该信息。

本文档受 2023年11月3日 W3C 流程文档 管辖。

1. 介绍

本节为非规范性内容。

推送 API 允许 Web 应用程序与 用户代理 异步通信。 这使得 应用服务器 可以在掌握时间敏感信息时, 向 用户代理 提供这些信息,而无需等待用户打开 Web 应用程序。

如本文所定义,推送服务 支持随时传递 推送消息

特别地,推送消息 即使在 Web 应用程序当前没有在浏览器窗口中运行时也会被传送: 这与用户关闭 Web 应用程序但仍能从应用程序接收消息的情况相关。 例如,当接收到 推送消息 时,Web 应用程序可能会被重新启动, 例如推送消息可能用于通知用户有 WebRTC 来电。

用户代理 暂时离线时,也可以发送 推送消息。 为了支持这一点,推送服务 会存储消息,直到 用户代理 可用为止。 这支持了用户离线时 web 应用程序获知变更的用例,并确保 用户代理 能够及时获得相关信息。推送消息推送服务 存储,直到 用户代理 可访问并可以传递消息。

用户代理 正在积极使用 Web 应用程序时,推送 API 也会确保推送消息的可靠传递, 例如用户正在积极使用 Web 应用程序,或 Web 应用程序通过活动的 worker、框架或后台窗口与 应用服务器 进行通信时。 这并不是推送 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 推送订阅

推送订阅 是在 用户代理 和代表 Web 应用程序的 推送服务 之间建立的消息传递上下文。 每个 推送订阅 都与一个 service worker 注册 关联, 并且一个 service worker 注册 最多只能有一个 推送订阅

一个 推送订阅 关联了一个 推送端点。它 必须 是由 推送服务 暴露的绝对 URL,应用服务器可在该处发送 推送消息。推送端点 必须 唯一标识该 推送订阅

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

一个 推送订阅 具有用于 P-256 ECDH 密钥对和身份验证密钥的内部槽, 根据 [RFC8291]。这些槽在创建 推送订阅必须 被填充。

如果 用户代理 出于任何原因需要更改密钥,则它 必须 触发 "pushsubscriptionchange" 事件, 该事件应与 service worker 注册 关联, 该注册与拥有旧密钥的 oldSubscription 实例和拥有新密钥的 newSubscription 实例关联的 PushSubscription 实例表示。

若要 创建推送订阅,给定 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. 请求新的 推送订阅。当设置了 optionsapplicationServerKey 属性时,请求中应包括该属性。重抛任何异常。
  7. 推送订阅 请求成功完成时:
    1. subscriptionendpoint 属性设置为该 推送订阅推送端点
    2. 如果 推送订阅 提供了到期时间,则设置 subscriptionexpirationTime 属性。
  8. 返回 subscription

3.3.1 订阅刷新

用户代理推送服务 可以 选择随时刷新(refresh推送订阅,例如因为其已达到某个年龄。

当这种情况发生时,用户代理 必须 根据为创建当前 推送订阅 提供的 PushSubscriptionOptions 执行 创建推送订阅 的步骤。新的 推送订阅 必须 拥有一个与原始订阅不同的密钥对。

成功后,用户代理 必须 触发pushsubscriptionchange”事件, 其中 service worker 注册推送订阅 关联,registration 表示最初的 推送订阅PushSubscription 实例为 oldSubscription, 新的 推送订阅PushSubscription 实例为 newSubscription

为了提供足够的时间将更改传播到 应用服务器用户代理 可以 在刷新后的一段时间内继续接受来自旧推送订阅的消息。 一旦为刷新后的推送订阅 接收到消息,所有旧的推送订阅 必须停用

如果 用户代理 无法刷新 推送订阅,则 定期重试刷新。 当 推送订阅 无法再使用时,例如它已过期,用户代理 必须 触发pushsubscriptionchange”事件, 其中 service worker 注册推送订阅 关联,registration 表示即将停用的 推送订阅PushSubscription 实例为 oldSubscription, 而 newSubscriptionnull

3.3.2 订阅停用

推送订阅停用时,用户代理推送服务 必须 删除其存储的任何副本。之后针对该推送消息的任何推送订阅 不得再传递。

当关联的service worker 注册被注销时, 该推送订阅 会被停用,尽管推送订阅 可以 更早被停用

注意

推送订阅关联的service worker 注册 被清除时,该推送订阅将被删除。

3.4 推送服务

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

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

3.5 权限

推送 API 是一种被称为 “推送”强大功能

为了与 权限 规范集成,本规范定义了 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用户代理必须并行停用受影响的订阅。

Service Worker注册被注销时,任何关联的推送订阅必须停用

推送端点不得推送服务之外的其他参与方暴露任何有关用户设备、身份或位置的信息。有关确切要求,请参阅[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
    // 在一个异步函数内部...
    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. ServiceWorkerRegistration接口的扩展

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

WebIDL[SecureContext]
    partial interface ServiceWorkerRegistration {
      readonly attribute PushManager pushManager;
    };

pushManager属性暴露了一个PushManager,它与Service Worker注册相关联,由暴露该属性的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 = {});
    };

supportedContentEncodings属性公开了可用于加密推送消息的有效载荷的支持内容编码的序列。请求推送服务发送推送消息时,使用Content-Encoding头字段指示内容编码。

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

7.1 subscribe() 方法

当调用 subscribe() 方法时,必须执行以下步骤:

  1. promise 设为一个新的promise
  2. global 设为 this相关全局对象
  3. 返回 promise 并继续并行执行。
    注释: 验证顺序可能因用户代理而异
  4. 如果 options 参数的 userVisibleOnly 值设置为 false 且用户代理要求其为 true,则使用 global网络任务源拒绝 promise 并返回一个 NotAllowedError DOMException
  5. 如果 options 参数未包含 applicationServerKey 的非空值,且推送服务需要提供一个,则使用 global网络任务源拒绝 promise 并返回一个 NotSupportedError DOMException
  6. 如果 options 参数包含 applicationServerKey 的非空值,执行以下子步骤:
    1. 如果 optionsapplicationServerKey 是一个 DOMString,将其值设置为一个包含optionsapplicationServerKey 的 Base64URL 编码的ArrayBuffer
    2. 如果解码失败,使用 global网络任务源拒绝 promise 并返回一个 InvalidCharacterError DOMException 并终止这些步骤。
    3. 确保 optionsapplicationServerKey 描述了 P-256 曲线上的有效点。如果其值无效,使用 global网络任务源拒绝 promise 并返回一个 InvalidAccessError DOMException 并终止这些步骤。
  7. registration 设为 this 相关联的service worker registration
  8. 如果 registrationactive worker 为 null,使用 global网络任务源拒绝 promise 并返回一个 InvalidStateError DOMException 并终止这些步骤。
  9. permission 设为请求使用“推送”的权限。
  10. 如果 permission 为 "denied",使用 global用户交互任务源拒绝 promise 并返回一个 NotAllowedError DOMException 并终止这些步骤。
  11. 如果 registration 具有 推送订阅
    1. subscription 设为获取 registration推送 订阅的结果。如果出现错误,使用 global网络任务源拒绝 promise 并返回一个 AbortError DOMException 并终止这些步骤。
    2. 比较 options 参数与 subscriptionoptions 属性。BufferSource 的内容按值比较,而不是引用
    3. 如果 options 中的任何属性值与存储在 subscription 中的值不同,则使用 global网络任务源拒绝 promise 并返回一个 InvalidStateError DOMException 并终止这些步骤。
    4. 当请求完成时,使用 global网络任务源解决 promise,返回 subscription 并终止这些步骤。
  12. subscription 设为尝试使用 options创建推送订阅 的结果。如果创建订阅时抛出 异常,使用 global网络任务源拒绝 promise 并返回该异常 并终止这些步骤。
  13. 否则,使用 global网络任务源解决 promise,返回一个提供新订阅详细信息的PushSubscription

当调用 getSubscription 方法时,必须执行以下步骤:

  1. promise 设为一个新的promise
  2. 返回 promise 并异步执行以下步骤。
  3. 如果 Service Worker 没有订阅,使用 null 解决 promise
  4. 检索与 推送订阅 相关联的Service Worker
  5. 如果出现错误,使用一个名称为AbortErrorDOMException 拒绝 promise 并终止这些步骤。
  6. 当请求完成时,使用一个提供检索到的推送订阅详细信息的PushSubscription解决 promise

当调用 permissionState() 方法时,必须执行以下步骤:

  1. promise 设为一个新的promise
  2. 返回 promise 并异步执行以下步骤。
  3. descriptor 设为一个新的PermissionDescriptor,并将其 name 初始化为 "push"。
  4. state 设为 descriptor权限状态 并返回结果。
  5. 使用 state 解决 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 PushSubscriptionJSON {
      USVString endpoint;
      EpochTimeStamp? expirationTime = null;
      record<DOMString, USVString> keys;
    };

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

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

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

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

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

p256dhauth两个密钥名称必须被支持,其值必须与用户代理在接收推送消息时解密所需的值相对应,按照[RFC8291]的规定。

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

  1. promise成为一个新的promise
  2. 返回promise,并继续以下步骤异步进行。
  3. 如果推送订阅已被停用,则用false解析promise并终止这些步骤。
  4. 并行运行以下步骤:
    1. 停用推送订阅用户代理不得推送订阅提供任何进一步的推送消息

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

  5. true解析promise

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

  1. json成为一个新的PushSubscriptionJSON字典。
  2. json["endpoint"]设置为获取endpoint属性的结果。
  3. json["expirationTime"]设置为获取expirationTime属性的结果。
  4. keys成为record<DOMString, USVString>的新空实例。
  5. 对于每个标识符i,按键的名称顺序对应内部槽中的键:
    1. 如果内部槽对应于非对称密钥对,则让b成为密钥名称i对应的公钥的编码值,使用定义的编码(请参阅getKey())。
    2. 否则,让bgetKey返回的值。
    3. s为URL安全的base64编码不带填充的b[RFC4648],作为USVString用户代理必须使用不基于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 值用于检索P-256 ECDH Diffie-Hellman 公钥, 如[RFC8291] 中所描述。

auth 值用于检索身份验证密钥, 如[RFC8291] 中所描述。

9. PushMessageData 接口

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

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

arrayBuffer() 方法步骤是返回一个 ArrayBuffer,其内容为 this字节序列。在创建 ArrayBuffer 对象时抛出的异常将被重新抛出。

blob() 方法步骤是返回一个新的 Blob 对象,其内容为 this字节序列

bytes() 方法步骤是返回一个新的 Uint8Array,其背后的 ArrayBuffer 的内容为 this字节序列。在创建 ArrayBuffer 对象时抛出的异常将被重新抛出。

json() 方法步骤是返回 将字节序列解析为JavaScript值的结果,给定 this字节序列

text() 方法步骤是返回运行 UTF-8 解码的结果, 给定 this字节序列

要从object提取字节序列,请运行以下步骤:

  1. bytes成为一个空字节序列。
  2. 根据object的类型切换:
    BufferSource
    bytes设置为object内容的副本。
    USVString
    bytes设置为运行 utf-8 编码object上的结果。
  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;
    };

constructorPushEvent 接口或继承自 PushEvent 接口的接口被调用时,通常的 事件构造步骤 会扩展为包括以下步骤:

  1. 如果 eventInitDictdata 成员不存在,将事件的 data 属性设置为 null 并终止这些步骤。
  2. b 设置为从 eventInitDict 的 "data" 成员中 提取字节序列 的结果。
  3. 将事件的 data 属性设置为一个新的 PushMessageData 实例,并将 bytes 设置为 b

当获取 data 时,返回其初始化的值。

10.3 PushEventInit 字典

WebIDLtypedef (BufferSource or USVString) PushMessageDataInit;
    
    dictionary PushEventInit : ExtendableEventInit {
      PushMessageDataInit data;
    };

data 成员包含了在 推送消息 中包含的数据,并且在 用户代理 验证其真实性时会被设置。 在所有其他情况下,该值将被设置为 null

10.4 接收 推送消息

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

  1. registration 设置为与 service worker 注册 对应的 推送消息
  2. 如果找不到 registration,中止这些步骤。
  3. subscription 设置为 registration 的活动 推送订阅
  4. bytes 设置为 null。
  5. 如果 推送消息 包含负载:
    1. 使用与 subscription 相关的密钥对中的私钥解密 推送消息 的负载,并使用 [RFC8291] 中描述的过程。 将 bytes 设置为生成的 字节序列
    2. 如果 推送消息 的负载由于任何原因无法解密:
      1. 根据 [RFC8030] 确认收到 推送消息。尽管消息未成功接收和处理,但这可以防止推送服务尝试重新传输消息; 错误加密的消息是无法恢复的。
      2. 中止这些步骤。
      注意

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

  6. 触发名为 push 的功能事件,并在 registration 上使用 PushEvent,具有以下属性:

    data
    一个新的 PushMessageData 对象,其 字节bytes

    然后并行运行以下步骤,并使用 dispatchedEvent

    1. 等待 dispatchedEvent 的所有 延长生命周期的 promise 全部解决。
    2. 如果所有的 promise 都成功解决,则根据 [RFC8030] 确认收到 推送消息 并中止这些步骤。
    3. 如果相同的 推送消息 已多次未成功地传递给 service worker 注册, 则根据 [RFC8030] 确认收到 推送消息

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

      确认还意味着 应用服务器 可能会错误地接收到表示推送消息成功传递的交付收据。因此, 允许在确认之前进行多次拒绝;建议至少允许三次尝试。

10.5 pushsubscriptionchange 事件

pushsubscriptionchange 事件表示 推送订阅 的变更,这些变更是在应用程序控制之外触发的,例如由于订阅已刷新、撤销或丢失。

要在给定 registrationservice worker 注册newSubscriptionoldSubscription 的情况下 触发 "pushsubscriptionchange" 事件用户代理 必须触发一个功能事件, 名为 "pushsubscriptionchange",使用 PushSubscriptionChangeEventregistration 上, 并具有以下属性:

newSubscription
newSubscription
oldSubscription
oldSubscription
注意

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

10.5.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.5.2 PushSubscriptionChangeEventInit 接口

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

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

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

11. 无障碍访问

推送 API 本身不提供任何呈现从 推送服务 接收到的数据的手段。相反,推送 API 依赖其他 API—— 主要是 通知 API 标准 ——将接收到的信息呈现给最终用户。 因此,推送 API 本身没有无障碍要求。然而,诸如 [通知] 等规范提供了关于如何以无障碍方式呈现通知的指导。

此外,呈现无障碍界面可能需要传输比推送消息所能传达的更多信息。推送消息最适合传递少量内容或标识符。任何更大的资源都需要从服务器获取。

12. 一致性

与标记为非规范性的部分一样,本规范中的所有作者指南、图表、示例和注释均为非规范性内容。本规范中的其他内容均为规范性内容。

本文档中的关键字 可以必须不得不应 应按照 BCP 14 中的描述进行解释, 当且仅当这些词汇以大写形式出现时,如此处所示。 [RFC2119] [RFC8174]

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

A. IDL 索引

WebIDLdictionary PushPermissionDescriptor : PermissionDescriptor {
      boolean userVisibleOnly = false;
    };
    
    [SecureContext]
    partial interface ServiceWorkerRegistration {
      readonly attribute PushManager pushManager;
    };
    
    [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;
    };
    
    typedef (BufferSource or USVString) PushMessageDataInit;
    
    dictionary PushEventInit : ExtendableEventInit {
      PushMessageDataInit data;
    };
    
    [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. 致谢

编辑们要感谢实施 Firefox OS 推送消息解决方案的 Mozilla 和 Telefónica Digital 团队,以及以下为本文档提供了重要技术意见的人: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 年 5 月 24 日。W3C 工作草案。URL: https://www.w3.org/TR/FileAPI/
[hr-time]
高精度时间。Yoav Weiss。W3C。2023 年 7 月 19 日。W3C 工作草案。URL: https://www.w3.org/TR/hr-time-3/
[html]
HTML 标准。Anne van Kesteren; Domenic Denicola;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。2024 年 3 月 19 日。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]
服务工作者。Jake Archibald; Marijn Kruisselbrink。W3C。2022 年 7 月 12 日。W3C 候选推荐标准。URL: https://www.w3.org/TR/service-workers/
[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 后台同步。WICG。cg-draft。URL: https://wicg.github.io/background-sync/spec/
[WebSockets]
WebSockets 标准。Adam Rice。WHATWG。现行标准。URL: https://websockets.spec.whatwg.org/