推送

注意
本文档描述了 Firefox 如何在内部实现 Web 推送标准,旨在供直接参与推送开发的开发者使用。如果您想了解如何使用推送功能,请参考 以下 MDN 文档

高级推送架构

以下时序图描述了 Web 应用程序观察到的高级推送架构。该图描述了在浏览器中运行的 Web 应用程序客户端代码、Firefox、Autopush(Firefox 的推送服务器,用于传递推送通知)以及将推送通知发送到 Autopush 的第三方服务器之间的交互。

虚线表示由推送的使用者完成。

sequenceDiagram participant TP as Web 应用程序 JS participant F as Firefox participant A as Autopush participant TPS as 第三方服务器 TP->>F: subscribe(scope) activate TP activate F F->>A: subscribe(scope) using web socket activate A A->>F: URL deactivate A F->>F: 创建公钥/私钥加密对 F->>F: 使用从 scope 派生的 ID 持久化 URL、公钥和私钥 F->>TP: URL + 公钥 deactivate F TP-->>TPS: URL + 公钥 deactivate TP TPS-->>TPS: 使用公钥加密负载 TPS-->>A: 使用 URL 发送加密负载 activate A A->>F: 使用 WebSocket 发送加密负载 deactivate A activate F F->>F: 使用私钥解密负载 F->>F: 显示通知 deactivate F

源代码流程图

推送的源代码位于 mozilla-central 中的 dom/push

以下流程图描述了不同模块如何相互交互,以便为使用者提供推送 API。

flowchart TD subgraph 公共 API W[第三方 Web 应用]-->|导入| P[PushManager.webidl] end subgraph 浏览器代码 P-->|由...实现| MM MM{主线程?}-->|是| B[Push.sys.mjs] MM -->|否| A[PushManager.cpp] B-->|subscribe,getSubscription| D[PushComponents.sys.mjs] A-->|subscribe,getSubscription| D D-->|subscribe,getSubscription| M[PushService.sys.mjs] M-->|存储| S[PushDB.sys.mjs] M-->|网络| N[PushWebSocket.sys.mjs] F[FxAccountsPush.sys.mjs] -->|使用| D end subgraph 服务器 N-. 发送,接收.-> O[Autopush] end subgraph 本地存储 S-->|读取,写入| PP[(IndexedDB)] end

推送 WebSocket

Firefox 桌面版中的推送功能使用 WebSocket 连接与 Autopush 通信。

WebSocket 连接在浏览器初始化时创建,并由以下状态图管理。

stateDiagram-v2 state "关闭" as SD state "等待 WebSocket 启动" as W1 state "等待服务器 Hello" as W2 state "就绪" as R [*] --> SD SD --> W1: beginWSSetup W1 --> W2: wsOnStart 成功 W2 --> R: handleHelloReply R --> R: 发送 (订阅) R --> R: 接收 + 通知观察者 R --> SD: wsOnStop R --> SD: 发送 Ping 失败 W1 --> SD: wsOnStart 失败 W2 --> SD: 无效的服务器 Hello R --> [*]

一旦推送 WebSocket 处于 Ready 状态,它就可以向 Autopush 发送新的订阅,并接收来自这些订阅的推送通知。

推送使用观察者模式来通知观察者任何传入的推送通知。请参阅 高级架构 部分。

推送存储

推送使用 IndexedDB 存储订阅,原因如下:

  1. 如果使用者尝试重新订阅,则存储用作缓存来提供 URL 和公钥。

  2. 为了持久化私钥,以便将其用于解密任何传入的推送通知。

以下是持久化的内容:

erDiagram Subscription { string channelID "键,从 scope 派生" string pushEndpoint "此订阅的唯一端点" string scope "通常是来源,内部使用者的唯一值" Object p256dhPublicKey "表示公钥的对象" Object p256dhPrivateKey "表示私钥的对象" }