Web Push 订阅生命周期与隐私
从权限和 endpoint 创建,到轮换、退订清理与隐私保留策略,完整管理 Push API 订阅。
BotBrowser Team
Web Push 不是永久权限,也不是稳定的用户标识。浏览器可以授予通知权限、为 service worker 创建带有 endpoint 和密钥的订阅、轮换该订阅,随后再删除它。BotBrowser 可以在经授权的受控 profile 中保留声明的权限状态,帮助团队检查自己的应用流程;它不会授予权限、发送消息、替用户决定是否需要通知,也不会代表应用从 push 服务删除 endpoint。Permissions Policy 指南将策略允许与这里记录的权限状态分开。
Push API 规范、MDN Push API 参考 和 Service Workers 规范描述了浏览器对象与事件。对应的 BotBrowser permissions 文档说明了权限状态作为 profile 输入的用途;该页面不覆盖订阅生命周期、失效 endpoint 发现、unsubscribe() 后清理,以及 endpoint 和密钥的隐私保留边界。
TL;DR
- 将
Notification.permission、PushSubscription和服务器记录视为三个相关但不同的状态。 - 只有在用户看得到的操作和明确的通知说明之后,才创建订阅。
- endpoint 和公钥只为声明的目的保存,并记录负责人、期限与删除路径。
- 对
pushsubscriptionchange、发送失败、退出登录、关闭账户和unsubscribe()做对账,不要假设订阅永久有效。 - endpoint 是可关联的路由能力,不是身份主键。
目录
- 区分权限、订阅与投递状态
- 在明确意图下创建订阅
- 建模 endpoint 轮换和 service worker 事件
- 清理退订和账户转换
- 最小化 endpoint 隐私与保留
- 使用基于证据的生命周期记录
- 实践结论
区分权限、订阅与投递状态
Notifications API 暴露权限状态,而 Push API 为 service worker registration 创建 PushSubscription。两者不能互换。Notification.permission === "granted" 只表示当前上下文允许使用通知;它不表示已经有订阅、用户已经登录,或 push provider 一定会投递。即使权限仍为 granted,也可能没有订阅。
应用记录至少要有三套状态。权限可以是 default、denied 或 granted;订阅可以是 none、active、rotating、unsubscribed 或 expired;投递可以根据 provider 响应标为 unknown、accepted、rejected 或 stale。一个 push_enabled 字段会混淆责任,也会让清理过程无法审计。
registration.pushManager.getSubscription() 返回订阅或 null。null 只是这个 registration、origin 和 scope 的观察结果,不能证明用户从未在别的上下文订阅。读取权限和订阅后再说明下一步,不要猜测身份。最小记录可以包括随机内部 ID、origin、service worker scope、用于去重的不可逆引用、协议所需公钥、创建与最近观察时间、生命周期状态和删除原因。不要把完整 endpoint 放进分析维度或 URL。
支持人员需要区分几种情况:权限已授予但 worker 尚未就绪;provider 拒绝最近的 endpoint;用户本地退订但服务器行仍然 active。对应负责人可能是浏览器、页面、worker、provider 或数据库。浏览器权限与指纹文章说明权限状态不是人的标识;endpoint 长期保留则会产生额外的关联风险。
在明确意图下创建订阅
调用 subscribe() 前,说明通知类别、频率、关闭方式,以及 endpoint 是否会与账号或设备上下文关联。不要在页面加载、隐藏定时器或无关功能打开时触发订阅。先注册 service worker,等待 navigator.serviceWorker.ready,确认 scope,再调用 getSubscription()。如果没有现有订阅,展示明确的用户选择。
启用控件必须有同样清楚的关闭路径。“启用订单通知”比“允许通知”更具体。设置页要显示应用状态;权限被拒绝时,应说明需要到浏览器设置处理。应用开关不能撤销浏览器级拒绝。
applicationServerKey 将订阅关联到服务公钥。只有在计划好的迁移中才切换 key version,并在服务器保存版本号,不要保存私钥。通过认证且可重复的请求发送 endpoint、所需公钥、origin 和短期 request ID。endpoint 是投递能力,不是账号所有权证明。
把拒绝、service worker 不可用、上下文政策阻止、provider 错误和应用响应错误分开处理。失败时保留用户正在做的事情,必要时提供应用内更新。不要不断重新打开已拒绝的 prompt,也不要把未脱敏 endpoint 或异常字符串送入无关的遥测系统。
建模 endpoint 轮换和 service worker 事件
订阅 endpoint 可能改变。service worker 全局作用域中的 pushsubscriptionchange 是对账信号,但不能假设所有浏览器都会在每种情况下触发。worker 可以获取当前订阅、通过有界且认证的请求更新服务器,并把旧记录标为待复核。如果没有可用的新订阅,应保留 unknown rotation,由之后的一次明确页面访问完成对账。
轮换不等于新用户。原因可能是浏览器维护、provider 政策、key 修改、profile 重置或 worker 替换。用短期迁移引用把新记录关联到旧应用记录,再重新检查账号和目的;新 endpoint 被接受后,尽快让旧 endpoint 退出 active 投递。
provider 返回 endpoint 不存在时,可以将记录设为 stale 并停止发送。临时错误应按 provider 的规则有限重试,不能立即删除。发送成功也不证明用户看见或仍然需要通知;投递确认与用户互动必须分开。
更新后的 service worker 可能改变 push 和 notificationclick 处理方式。要将记录 schema 与 worker 激活协调起来;如果旧服务器记录缺少新字段,应保留兼容路径。Service worker 更新文章讨论客户端版本协调;Service worker 缓存生命周期说明为什么激活和存储清理需要明确负责人。
清理退订和账户转换
PushSubscription.unsubscribe() 会从浏览器的 push manager 删除订阅并返回 boolean。返回 true 不会删除服务器记录、provider 队列或外部日志。应用还要发送独立的认证禁用请求,并处理客户端离线导致的延迟。
清理接口要幂等:验证账号所有权、将记录标为 inactive,并从投递存储移除 endpoint 与密钥。为了防止意外重新激活,可以短期保留只含随机 ID、时间和原因的 tombstone,但不要仅为方便而保留完整 endpoint。
退出登录和关闭账户不是一回事。退出可以停止当前会话的账号投递,同时保留用户在另一个获准账号中的设备选择;关闭账户则应按保留政策撤销该账户的所有记录并删除应用副本。关闭标签页或停止 worker 都不能证明上述任何动作已经完成。
如果浏览器本地操作成功但网络失败,只保存会过期的最小对账标记,并在下次认证访问时重试。如果服务器已禁用记录而浏览器操作失败,要说明本地清理仍待完成。只有在掌握每个受控系统的证据时,才可以说“已在所有地方删除”。
最小化 endpoint 隐私与保留
不透明的 endpoint 仍然是可以关联的路由值。应把 endpoint 和公钥视为个人数据或敏感的假名标识,并把完整值放在访问受限的投递存储中。分析只需要生命周期事件、目的类别和不可逆内部 ID;支持视图只显示最近时间、状态、key version 和必要的脱敏后缀。不要把 endpoint 放进截图、URL、客户端日志或长期导出。
保留期限应由用户目的决定。只要用户选择接收声明类别且服务仍需要投递,可以保持 active;stale、unconfirmed 或 unsubscribed 记录应快速过期。目的结束、账户关闭或期限到达后,删除或不可逆脱敏 endpoint 与密钥,只保留删除原因。备份流程也要覆盖这些字段,否则主表删除并不等于副本删除。
通知内容同样要最小化。通知可能出现在共享锁屏上,push provider 也可能处理元数据。优先发送短期 opaque reference,让用户在正常授权的应用页面中读取详细内容。不要把 “subscribed”“rotated”“clicked”“unsubscribed” 与其他浏览器信号拼成长期行为画像。请在策略中明确 retentionUntil 字段和 accountClosed 触发条件,方便核对期限。
使用基于证据的生命周期记录
记录声明的目的、origin、service worker scope、permission 观察、subscription 状态、key version、脱敏 record ID、应用动作、时间和 retention deadline。可用于文档的最小形状如下:
{
"purpose": "order-status",
"origin": "https://shop.example",
"serviceWorkerScope": "/app/",
"permission": "granted",
"subscriptionState": "active",
"keyVersion": "v2",
"recordId": "psr_7f2c",
"lastReconciledAt": "2026-10-25T14:00:00Z",
"retentionUntil": "2026-11-24T14:00:00Z",
"nextAction": "send"
}
这些值只是 fixture 形状,不代表真实订阅。如果 permission 是 granted、subscription 是 none,就解释状态并提供明确 opt-in;如果 subscription 是 active 但投递是 stale,停止发送并对账;如果是 unsubscribed,删除 active 路由数据,不要静默重新订阅;如果超过 retention,删除记录并要求新的用户决定。
| 观察结果 | 含义 | 负责人 | 安全下一步 |
|---|---|---|---|
permission=granted、subscription=null | 浏览器允许通知,但本地没有订阅 | 页面与用户 | 解释并提供明确 opt-in |
subscription=active、provider 返回 gone | 投递路线已经失效 | 投递服务 | 标为 stale 并对账 |
unsubscribe() 返回 true、服务器行仍 active | 本地完成,服务器清理待完成 | 应用 API | 重试幂等禁用请求 |
permission=denied、服务器行 active | 浏览器选择与投递意图冲突 | 用户与应用 | 停止发送并指导浏览器设置 |
| retention deadline 已过 | 目的结束或记录被放弃 | 数据负责人 | 删除 endpoint 和密钥 |
BotBrowser 支持在经授权的 profile 中复现声明的 permission 输入,并打开自有页面让应用记录 getSubscription() 等状态变化;它不会召唤原生 prompt、替用户选择、创建用户订阅、查看 provider 私有队列、保证 pushsubscriptionchange 发生,或证明所有外部系统都已删除。它适合做范围受限的兼容性观察。
常见问题
granted 权限是否代表用户已经订阅?
不是。应用仍要创建 PushSubscription、提交服务器注册并检查自己的记录。
endpoint 是设备指纹吗?
它不能证明设备或个人,但可以连接投递事件。因此要限制访问、保留期限和与其他信号的关联。
unsubscribe() 之后应该做什么?
把浏览器结果和服务器清理分开处理,调用认证且幂等的禁用接口,删除 active endpoint 与 key。没有 provider 证据时,不要承诺队列或日志也被清除。
endpoint 轮换是否应该创建新账号?
通常不应该。创建新的投递行并暂时关联旧应用记录,重新检查账号和目的,再让旧 endpoint 退出 active。
BotBrowser 能为 profile 开启 Web Push 吗?
它可以为授权的受控检查保留声明权限,但不会授予权限、创建用户订阅或保证投递。
来源
实践结论
把 Web Push 建模为生命周期:permission 开启浏览器能力,service worker registration 拥有订阅,服务器授权投递,清理动作结束目的。对账 endpoint 轮换、provider 失败、权限变化、退出登录、关闭账户和 unsubscribe();不要把 endpoint 与 key 放入分析或身份系统。BotBrowser 可以重复经授权的权限输入,但用户意图、投递状态和删除责任仍属于应用及其 push provider。
本文属于指南: Web 平台能力面:一致性复核地图