Angular Service Worker 推送通知实战:SwPush 订阅、通知负载与点击操作(onActionClick)解析
【免费下载链接】angularDeliver web apps with confidence 🚀项目地址: https://gitcode.com/GitHub_Trending/an/angular
本文围绕 Angular 官方文档中 Service Worker 推送通知指南展开,讲解如何在 Angular 应用中通过SwPush服务接收推送、构造合规的通知负载(payload),以及如何利用onActionClick与四种内置操作(Operations)精确控制通知点击后的行为。读完本文,你将掌握从订阅推送、本地调试到点击路由处理的完整链路,并能对照 Angular 仓库源码理解 Service Worker 侧的真实事件处理逻辑与 URL 解析规则。
推送通知在 Angular 中的角色
推送通知(Push notifications)是一种强用户触达手段:借助 Service Worker,通知可以在你的应用没有处于焦点状态(甚至已关闭页面)时依然送达设备。Angular 内置的 Service Worker(即@angular/service-worker,由构建工具生成的ngsw-worker.js)在原生 Push API / Notifications API 之上封装了一层应用级能力——它负责展示推送通知,并统一处理通知点击事件,应用侧则通过SwPush服务订阅这些交互结果。
从源码结构看,整条链路分为两侧:
- 应用侧(主线程):
SwPush服务定义在 push.ts,它通过NgswCommChannel(low_level.ts)与 Worker 通信,把 Worker 广播的消息流映射为若干Observable; - Worker 侧:事件处理核心是 driver.ts 中的
Driver类,入口在 main.ts 中实例化。Driver监听push、notificationclick、notificationclose等事件,决定是弹出系统通知、打开/聚焦客户端窗口,还是向所有已注册客户端广播消息。
通知负载(Notification payload)
要触发一次推送通知,你需要向用户的设备推送一条携带合法 payload 的消息。Angular 对负载的约定是:顶层对象包含一个notification键,其值即为浏览器NotificationOptions的合法字段。官方文档中给出的完整字段集如下(见 SwPush 的 API 注释):
{ "notification": { "actions": NotificationAction[], "badge": USVString, "body": DOMString, "data": any, "dir": "auto"|"ltr"|"rtl", "icon": USVString, "image": USVString, "lang": DOMString, "renotify": boolean, "requireInteraction": boolean, "silent": boolean, "tag": DOMString, "timestamp": DOMTimeStamp, "title": DOMString, "vibrate": number[] } }其中只有title是必需的,其余字段可选。这一点在 Worker 源码中得到直接印证:Driver的handlePush方法中,若负载缺少notification或notification.title,则只广播消息、不弹出通知(driver.ts):
private async handlePush(data: any): Promise<void> { await this.broadcast({ type: 'PUSH', data, }); if (!data.notification || !data.notification.title) { return; } const desc = data.notification as {[key: string]: string | undefined}; let options: {[key: string]: string | undefined} = {}; NOTIFICATION_OPTION_NAMES.filter((name) => Object.hasOwn(desc, name)).forEach( (name) => (options[name] = desc[name]), ); await this.scope.registration.showNotification(desc['title']!, options); }这里有两个值得注意的实现细节:
- 先广播、后弹通知。无论负载是否包含可展示的通知,Worker 都会先向所有客户端广播一条
PUSH类型消息——这就是应用侧SwPush.messages可感知任意推送数据(包括纯业务数据)的原因。 - 白名单过滤通知选项。Worker 仅透传
NOTIFICATION_OPTION_NAMES中列出的字段(driver.ts:actions、badge、body、data、dir、icon、image、lang、renotify、requireInteraction、silent、tag、timestamp、title、vibrate),再调用registration.showNotification(title, options)展示通知。负载中data字段会原样挂到通知上,后续点击处理时再从notification.data中读取。
无后端场景下的 Chrome 本地调试
官方指南给出了一个非常实用的免后端调试技巧:在 Chrome 中打开DevTools → Application → Service Workers,直接使用面板提供的Push输入框,粘贴一个 JSON 通知负载即可模拟一次推送事件,验证 Worker 是否正确弹出了通知。
订阅与退订:SwPush 的应用侧用法
应用侧所有推送交互都收敛到SwPush这一个服务上(push.ts)。典型用法是把它注入组件或服务中:
- 订阅:调用
requestSubscription({serverPublicKey}),该方法会先请求用户授权,返回一个解析为PushSubscription的Promise。 - 退订:调用
unsubscribe()。 - 状态检查:通过
SwPush.isEnabled确认 Service Worker 是否受浏览器支持且已通过ServiceWorkerModule启用。
从 requestSubscription 的实现 可以看到关键行为:
requestSubscription(options: {serverPublicKey: string}): Promise<PushSubscription> { if (!this.sw.isEnabled || this.pushManager === null) { return Promise.reject(new Error(ERR_SW_NOT_SUPPORTED)); } const pushOptions: PushSubscriptionOptionsInit = {userVisibleOnly: true}; let key = this.decodeBase64(options.serverPublicKey.replace(/_/g, '/').replace(/-/g, '+')); // ...将密钥字符串转为 Uint8Array 后赋值给 applicationServerKey pushOptions.applicationServerKey = applicationServerKey; // ...pushManager.subscribe(pushOptions) }serverPublicKey需要是 URL-safe Base64 编码的应用服务器公钥(源码中会先把_换回/、-换回+再解码);- 订阅固定设置
userVisibleOnly: true; - 用户拒绝授权、浏览器不支持 Push API 或 Service Worker 时,
Promise会被拒绝; unsubscribe()在没有活跃订阅时会抛出NOT_SUBSCRIBED_TO_PUSH_NOTIFICATIONS运行时错误(push.ts)。
通知点击处理(Notification click handling)
notificationclick事件的默认行为是:关闭该通知,并向SwPush.notificationClicks发射一条消息。这个行为由 Worker 侧的handleClick实现(driver.ts)——方法开头即notification.close(),末尾再广播NOTIFICATION_CLICK消息。
如果希望在点击时执行额外的浏览器端操作(尤其是当前没有任何打开的客户端窗口时),可以在负载的data对象中添加onActionClick属性,并为default键提供一个操作条目:
{ "notification": { "title": "New Notification!", "data": { "onActionClick": { "default": {"operation": "openWindow", "url": "foo"} } } } }Worker 处理点击时的核心逻辑是:先把点击动作名归一化——若action为空字符串或undefined则取default条目,否则按具体动作名查找,然后解析目标 URL:
const notificationAction = action === '' || action === undefined ? 'default' : action; const onActionClick = notification?.data?.onActionClick?.[notificationAction]; const urlToOpen = new URL(onActionClick?.url ?? '', this.scope.registration.scope).href;内置操作(Operations)
Angular Service Worker 支持以下四种操作(完整表格继承自官方文档):
| 操作 | 说明 |
|---|---|
openWindow | 在指定 URL 处打开一个新标签页。 |
focusLastFocusedOrOpen | 聚焦最近一次聚焦的客户端;如果没有打开的客户端,则在指定 URL 打开新标签页。 |
navigateLastFocusedOrOpen | 聚焦最近一次聚焦的客户端并将其导航到指定 URL;如果没有打开的客户端,则在指定 URL 打开新标签页。 |
sendRequest | 向指定 URL 发起一个简单的 GET 请求。 |
重要(原文档 IMPORTANT 提示):URL 是相对于 Service Worker 注册作用域(registration scope)解析的。如果某个
onActionClick条目没有定义url,则直接使用 Service Worker 的注册作用域本身。
这条规则与源码严格对应:new URL(onActionClick?.url ?? '', this.scope.registration.scope)正是以注册作用域为基准做相对 URL 解析。各操作的底层实现同样可在 handleClick 中逐一对照:
openWindow→clients.openWindow(urlToOpen);focusLastFocusedOrOpen→ 先getLastFocusedMatchingClient(this.scope)找到最后一个聚焦客户端并focus(),找不到才openWindow(urlToOpen);navigateLastFocusedOrOpen→ 找到客户端则client.navigate(urlToOpen)后focus(),找不到则openWindow(urlToOpen);sendRequest→this.scope.fetch(urlToOpen)(fetch不传 method 时默认即为 GET,在 Worker 后台静默完成);- 未识别的操作名走
default分支,不做任何窗口操作。
无论执行哪种操作,handleClick最后都会广播NOTIFICATION_CLICK消息(携带action与被过滤后的通知选项),由应用侧SwPush.notificationClicks接收。
上述行为并非仅靠阅读源码推断,仓库内的测试用例对每个操作都做了独立验证,包括「指定url时打开scope + url」「未指定url时回退到注册作用域根路径」「无最后聚焦客户端时回退为openWindow」等分支(见 happy_spec.ts 中openWindow与focusLastFocusedOrOpen的 describe 块,以及后续对navigateLastFocusedOrOpen、sendRequest和缺失onActionClick场景的断言)。
通知动作按钮(Actions)
Actions 提供了一种方式,让用户可以与通知进行更细粒度的交互:通过actions属性定义一组可用动作,每个动作在通知上呈现为一个可点击的按钮;再配合data中的onActionClick,把每个按钮绑定到具体的操作上:
{ "notification": { "title": "New Notification!", "actions": [ {"action": "foo", "title": "Open new tab"}, {"action": "bar", "title": "Focus last"}, {"action": "baz", "title": "Navigate last"}, {"action": "qux", "title": "Send request in the background"}, {"action": "other", "title": "Just notify existing clients"} ], "data": { "onActionClick": { "default": {"operation": "openWindow"}, "foo": {"operation": "openWindow", "url": "/absolute/path"}, "bar": {"operation": "focusLastFocusedOrOpen", "url": "relative/path"}, "baz": {"operation": "navigateLastFocusedOrOpen", "url": "https://other.domain.com/"}, "qux": {"operation": "sendRequest", "url": "https://yet.another.domain.com/"} } } } }这个示例覆盖了四种操作加上一个“兜底”场景:other按钮在onActionClick中没有对应条目。
重要(原文档 IMPORTANT 提示):如果某个动作没有对应的
onActionClick条目,则通知会被关闭,并且会向已存在的客户端通知SwPush.notificationClicks——也就是退回默认行为,只做应用内消息分发,不产生任何窗口操作。
对照handleClick源码可以推断其兜底机制:当onActionClick?.[action]为undefined时,switch (onActionClick?.operation)不命中任何分支直接break,随后依旧广播NOTIFICATION_CLICK。测试用例中也专门验证了 “No matching onActionClick field” 与 “no onActionClick default” 两类场景下的行为(happy_spec.ts)。
应用侧可订阅的完整事件流
SwPush不止暴露notificationClicks。从 构造函数的消息映射 可以看到,它与 Worker 广播的消息类型一一对应:
SwPush成员 | 对应 Worker 广播类型 | 触发时机 |
|---|---|---|
messages | PUSH | 每次收到推送负载(先于通知展示) |
notificationClicks | NOTIFICATION_CLICK | 用户点击通知主体或某个动作按钮 |
notificationCloses | NOTIFICATION_CLOSE | 通知被关闭(含程序性关闭) |
pushSubscriptionChanges | PUSH_SUBSCRIPTION_CHANGE | 浏览器自动轮换/失效订阅时 |
subscription | pushManager.getSubscription() | 当前活跃订阅(或null) |
两个值得注意的 API 契约(来自 push.ts 的文档注释):
notificationClicks/notificationCloses的notification字段不是Notification对象,而是NotificationOptions对象(并额外包含title);未使用动作按钮时action为空字符串'';pushSubscriptionChanges携带oldSubscription/newSubscription,二者任一可为null,用于应对浏览器因订阅过期或密钥轮换引发的自动变更——这是生产推送系统中维护“订阅 → 用户”映射时不可缺少的信号。
当 Service Worker 不可用时,上述所有 Observable 都被替换为NEVER(不发射任何值),保证应用代码可以无条件注入SwPush而不抛异常。
小结与适用前提
回到官方指南的要点,Angular Service Worker 的推送通知能力可以概括为三层:
- 负载层:以
notification为顶层键的 JSON 负载,title必填,其余遵循NotificationOptions白名单; - 点击层:
onActionClick(default+ 各动作键)声明式绑定四种操作,URL 一律相对注册作用域解析,缺失条目自动回退为“关闭通知 + 通知已有客户端”; - 应用层:
SwPush的五个 Observable 把 Worker 侧事件转成响应式流,应用负责路由跳转、状态恢复等最终 UI 决策。
适用前提与限制:功能依赖浏览器对 Push API、Notifications API 和 Service Worker 的支持(可用SwPush.isEnabled检查),生产环境推送需要自备推送后端与 VAPID 公钥(requestSubscription的serverPublicKey);本地开发则可完全依赖 Chrome DevTools 的 Service Workers 面板进行无后端验证。仓库中的示例组件 service_worker_component.ts 演示了SwPush注入、订阅与点击监听的完整组合,可作为动手实践的起点。
【免费下载链接】angularDeliver web apps with confidence 🚀项目地址: https://gitcode.com/GitHub_Trending/an/angular
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考