深入解读 @angular/service-worker 公开 API:注册、版本更新与推送通知的完整接口面
@angular/service-worker 是 Angular 官方的 Service Worker 工具包,负责 Service Worker 的注册、版本更新管理和 Web Push 通知。本文以仓库中由 API Extractor 生成的公开 API 报告 index.api.md 为核心,逐一解读 provideServiceWorker、SwUpdate、SwPush 等全部 @public 导出的签名与语义,并结合 packages/service-worker/src 目录下的源码实现,说明每个接口的默认值、运行时机与底层通信机制,帮助你在项目中正确使用这套 API 并通过 golden 文件约束其接口稳定性。
一、API 报告文件是什么
index.api.md 顶部明确声明:
Do not edit this file. It is a report generated by API Extractor.
该文件是 Angular 仓库 golden(黄金文件)机制的一部分,通过 tools/symbol-extractor 工具链对 @angular/service-worker 包做 API 表面提取后自动生成。文件中每个 // @public 注释标记了对应的导出符号属于对外承诺的稳定 API。仓库通过 goldens/public-api/manage.js 管理这批报告,任何对包公共接口的增删改都会体现在该文件中并在 CI 中被校验——这决定了本文讨论的所有符号都是经过审查、可被下游项目依赖的正式接口,而非内部实现细节。
从报告内容看,该包的公开 API 表面由以下符号构成:
| 导出符号 | 类型 | 职责 |
|---|---|---|
provideServiceWorker |
函数 | 功能式 API(standalone/bootstrap)入口,注册 Service Worker 脚本 |
ServiceWorkerModule |
类 | NgModule 入口,提供静态 register 方法 |
SwRegistrationOptions |
抽象类(作为配置对象类型) | 注册选项:开关、策略、作用域、Worker 类型、缓存更新策略 |
SwUpdate |
可注入类 | 订阅版本更新事件、触发更新检查、激活新版本 |
SwPush |
可注入类 | 管理 Web Push 订阅与通知点击/关闭事件 |
VersionEvent 及相关接口 |
类型 | versionUpdates 流中事件载荷的类型定义 |
UnrecoverableStateEvent |
接口 | 不可恢复状态事件载荷 |
以下逐节展开,每个接口的行为都以 packages/service-worker/src 中对应源文件为准。
二、注册入口:provideServiceWorker 与 ServiceWorkerModule.register
报告中声明了两个注册入口:
// @public
export function provideServiceWorker(script: string, options?: SwRegistrationOptions): EnvironmentProviders;
// @public (undocumented)
export class ServiceWorkerModule {
static register(script: string, options?: SwRegistrationOptions): ModuleWithProviders<ServiceWorkerModule>;
}
2.1 功能式 API:provideServiceWorker
源码位于 provider.ts。它在 bootstrapApplication 场景下返回一组 EnvironmentProviders,实际注册的 provider 集合为:
return makeEnvironmentProviders([
SwPush,
SwUpdate,
{provide: SCRIPT, useValue: script},
{provide: SwRegistrationOptions, useValue: options},
{provide: NgswCommChannel, useFactory: ngswCommChannelFactory},
provideAppInitializer(ngswAppInitializer),
]);
由此可以推断其完整职责链:
- 直接提供
SwPush与SwUpdate两个可注入服务; - 用私有
SCRIPTtoken 保存脚本 URL(如'ngsw-worker.js'),用SwRegistrationOptions抽象类 token 保存配置对象; - 通过工厂
ngswCommChannelFactory构造底层通信信道NgswCommChannel(详见第六节); - 借助
provideAppInitializer挂入应用初始化函数ngswAppInitializer,注册动作实际发生在应用初始化阶段。
provideServiceWorker 的 JSDoc 中给出了标准用法:
bootstrapApplication(AppComponent, {
providers: [
provideServiceWorker('ngsw-worker.js')
],
});
其测试用例见 provider_spec.ts,验证了该功能式入口与 NgModule 入口行为一致。
2.2 NgModule 入口:ServiceWorkerModule.register
module.ts 中的实现非常薄:
static register(
script: string,
options: SwRegistrationOptions = {},
): ModuleWithProviders<ServiceWorkerModule> {
return {
ngModule: ServiceWorkerModule,
providers: [provideServiceWorker(script, options)],
};
}
即 register 只是把 provideServiceWorker 包进 ModuleWithProviders 返回,两条入口最终走同一套 provider 与初始化逻辑。若 options.enabled 为 false,两个入口都会“表现得像浏览器不支持 Service Worker 一样,直接不注册”。
2.3 初始化函数 ngswAppInitializer 的注册时机
真正的浏览器注册逻辑在 provider.ts 的 ngswAppInitializer 中,关键行为如下:
- 服务端跳过:
ngServerMode为真时直接返回,SSR 环境下不注册; - 开关判断:
!('serviceWorker' in navigator && options.enabled !== false)时直接返回; - controllerchange 监听:在 Zone 外监听
controllerchange,每当新 Worker 成为 active 就向其postMessage({action: 'INITIALIZE'}),使 Worker 即使没有应用流量也能完成自初始化;并在ApplicationRef.onDestroy时移除监听; - 按策略注册:解析
options.registrationStrategy得到readyToRegisterPromise,Promise 完成后若应用未被销毁,则调用navigator.serviceWorker.register(script, {scope, updateViaCache, type}); - 失败不阻塞:注册失败仅打印格式化运行时错误(错误码
SERVICE_WORKER_REGISTRATION_FAILED),不会让应用卡住。
scope、updateViaCache、type 三个字段会原样透传给浏览器原生 ServiceWorkerContainer#register 的 options 参数,因此其行为与 MDN 定义的语义一致。
三、SwRegistrationOptions:注册选项逐项说明
报告中的定义为:
// @public (undocumented)
export abstract class SwRegistrationOptions {
enabled?: boolean;
registrationStrategy?: string | (() => Observable<unknown>);
scope?: string;
type?: WorkerType;
updateViaCache?: ServiceWorkerUpdateViaCache;
}
它是一个抽象类,但实际用法是配置对象形状(TypeScript 允许以对象字面量实现抽象类结构),并可通过同名 DI token 在 register() 之外单独提供。各字段语义(结合 provider.ts 的 JSDoc 与实现):
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled |
boolean |
true |
为 false 时不注册 Worker,SwPush/SwUpdate 退化为不可用状态 |
registrationStrategy |
string | (() => Observable<unknown>) |
'registerWhenStable:30000' |
决定注册时机,见下方四种策略 |
scope |
string |
浏览器默认(脚本所在目录) | Worker 可控制的 URL 范围 |
type |
WorkerType |
'classic' |
'classic' 不允许 import/export;'module' 允许 ES Module 语法 |
updateViaCache |
ServiceWorkerUpdateViaCache |
浏览器默认 | 浏览器更新 Worker 脚本时是否查阅 HTTP 缓存 |
registrationStrategy 是实践中最重要的选项,源码中的分支逻辑(provider.ts)支持:
'registerWhenStable:<timeout>'(默认'registerWhenStable:30000'):Promise.race([appRef.whenStable(), delayWithTimeout(timeout)])——应用一旦稳定(无待处理微/宏任务)立即注册,但最迟不超过<timeout>毫秒;<timeout>省略则只等稳定。默认值的设计意图是“尽快注册,但不影响首次加载”。'registerImmediately':Promise.resolve(),同步路径上立即注册。'registerWithDelay:<timeout>':固定延时<timeout>毫秒后注册;省略时默认0,即“尽快但仍是异步(等待所有微任务完成后)”。- Observable 工厂函数:
() => Observable,运行期订阅该 Observable,首个值发出即注册——适合“用户登录后才注册”这类自定义条件。
字符串形式按 strategy:args 用 : 分割解析;遇到未知策略会抛出运行时错误 UNKNOWN_REGISTRATION_STRATEGY(错误码 5600)。
四、SwUpdate:版本更新事件的消费端
报告签名:
// @public
export class SwUpdate {
constructor(sw: NgswCommChannel);
activateUpdate(): Promise<boolean>;
checkForUpdate(): Promise<boolean>;
get isEnabled(): boolean;
readonly unrecoverable: Observable<UnrecoverableStateEvent>;
readonly versionUpdates: Observable<VersionEvent>;
}
实现见 update.ts。
4.1 versionUpdates 事件流
构造器中,若 NgswCommChannel.isEnabled 为假,两个流都会退化为 NEVER(永不发射、永不结束),从而在 Worker 不可用的浏览器里订阅方不会收到任何事件;否则:
this.versionUpdates = this.sw.eventsOfType<VersionEvent>([
'VERSION_DETECTED',
'VERSION_INSTALLATION_FAILED',
'VERSION_READY',
'NO_NEW_VERSION_DETECTED',
]);
this.unrecoverable = this.sw.eventsOfType<UnrecoverableStateEvent>('UNRECOVERABLE_STATE');
即应用侧看到的 VersionEvent 联合类型(报告中的定义):
export type VersionEvent = VersionDetectedEvent | VersionInstallationFailedEvent
| VersionReadyEvent | NoNewVersionDetectedEvent;
四个事件接口的 type 字段是字面量判别式,便于 switch 分发:
| 事件接口 | type 值 |
载荷字段 | 语义 |
|---|---|---|---|
VersionDetectedEvent |
'VERSION_DETECTED' |
version: {hash, appData?} |
检测到服务器上存在新版本,即将开始下载 |
VersionInstallationFailedEvent |
'VERSION_INSTALLATION_FAILED' |
version、error |
检查或下载新版本失败,适合接入日志/监控 |
VersionReadyEvent |
'VERSION_READY' |
currentVersion、latestVersion |
新版本已下载完毕、可激活 |
NoNewVersionDetectedEvent |
'NO_NEW_VERSION_DETECTED' |
version |
检查完毕,未发现新版本 |
每个 version 对象都携带 hash(版本哈希,即 ngsw.json 的 hash)与可选的 appData。
4.2 checkForUpdate 与 activateUpdate
checkForUpdate():向 Worker 发送CHECK_FOR_UPDATES动作并携带一次性nonce,Promise 在新版本“已下载且可激活”时 resolve 为true,无新版本时为false,出错则 reject。源码里用ongoingCheckForUpdate做了并发去重——重复调用会返回同一个 Promise。activateUpdate():将当前客户端(标签页)切换到已就绪的最新版本。官方注释明确警告:大多数场景应通过整页刷新完成更新,因为不刷新就激活新 Worker 可能造成应用 shell 与懒加载 chunk 文件名不一致的版本错配;activateUpdate仅在确定安全时使用。
两个方法都通过 sw.postMessageWithOperation 实现“请求—nonce—OPERATION_COMPLETED 回执”的 RPC 语义(见第六节)。isEnabled getter 则透传 NgswCommChannel.isEnabled,用于 UI 上判断推送/更新功能是否可用。
4.3 unrecoverable:不可恢复状态
UnrecoverableStateEvent 定义:
export interface UnrecoverableStateEvent {
reason: string;
type: 'UNRECOVERABLE_STATE';
}
触发场景(见 low_level.ts 的注释):Worker 用于向本客户端提供服务的某个应用版本进入了“不整页刷新就无法恢复”的损坏状态,例如缓存被浏览器部分清除后,某资源既取不到缓存也取不到服务器。监听该流并在收到事件后执行 location.reload() 是推荐的处理方式。
五、SwPush:Web Push 订阅与通知事件
报告签名(对应 push.ts):
export class SwPush {
constructor(sw: NgswCommChannel);
get isEnabled(): boolean;
readonly messages: Observable<object>;
readonly notificationClicks: Observable<{action: string; notification: NotificationOptions & {title: string}}>;
readonly notificationCloses: Observable<{action: string; notification: NotificationOptions & {title: string}}>;
readonly pushSubscriptionChanges: Observable<{oldSubscription: PushSubscription | null; newSubscription: PushSubscription | null}>;
requestSubscription(options: {serverPublicKey: string}): Promise<PushSubscription>;
readonly subscription: Observable<PushSubscription | null>;
unsubscribe(): Promise<void>;
}
5.1 五个只读事件流
构造器把 NgswCommChannel 上的原始 message 事件按 type 过滤(eventsOfType)映射成五个语义化流;Worker 不可用时全部退化为 NEVER:
messages:来自 Worker 的PUSH事件载荷(message.data),即收到推送消息的原始数据;notificationClicks:NOTIFICATION_CLICK事件,载荷为{action, notification}。action是用户点击的动作 id(无动作时为空字符串'');notification是含title的NotificationOptions对象,而非Notification实例;notificationCloses:NOTIFICATION_CLOSE事件,结构同上,携带关闭时的动作;pushSubscriptionChanges:PUSH_SUBSCRIPTION_CHANGE事件,载荷{oldSubscription, newSubscription}。任一可为null:无旧订阅时oldSubscription为null,订阅失效且未被替换时newSubscription为null。该流用于响应浏览器自动触发的订阅变化(如浏览器过期、密钥轮换);subscription:合成流——把 registration 的pushManager.getSubscription()与本地subscriptionChangesSubject 合并,始终反映当前生效的PushSubscription | null。
5.2 requestSubscription 与 unsubscribe
requestSubscription(options: {serverPublicKey: string}) 的实现细节(push.ts):
- Worker 不可用(
sw.isEnabled为假)时直接 rejectERR_SW_NOT_SUPPORTED; - 将
serverPublicKey从 URL-safe Base64(_/-)还原为标准 Base64(//+)后atob解码为字节,构造applicationServerKey; - 以
userVisibleOnly: true调用pushManager.subscribe,成功则发出subscriptionChanges并 resolve 新的PushSubscription。
用户拒绝授权、浏览器不支持 Push API 或 Service Worker 都会导致 Promise reject,调用前可先检查 isEnabled。
unsubscribe() 先取 subscription 流的首个值,若为 null 则抛出 NOT_SUBSCRIBED_TO_PUSH_NOTIFICATIONS(错误码 5602);否则调用 sub.unsubscribe(),返回 false 时抛 PUSH_SUBSCRIPTION_UNSUBSCRIBE_FAILED(5603),成功后向 subscriptionChanges 发出 null。
5.3 服务端推送的消息格式
源码注释约定了推送消息载荷结构,服务端应推送形如:
{
"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 监听 PushEvent 并据此创建 Notification 实例,应用侧再通过 messages / notificationClicks / notificationCloses 三个流消费后续交互。
六、底层通信信道:NgswCommChannel
报告中 SwUpdate/SwPush 的构造函数参数 sw: NgswCommChannel 揭示了两者的共同底座:low_level.ts 中的 NgswCommChannel(该类型本身是 @publicApi 导出,但属于内部基础设施,日常应用开发通常无需直接使用)。它封装了浏览器与 Worker 之间的全部消息通道:
- Worker 追踪:监听
controllerchange,用 Subject 把“当前控制该页的 ServiceWorker”暴露为worker流;registration流则由worker派生,调用getRegistration()——在非安全上下文或隐私模式下可能取不到 registration,此时以SERVICE_WORKER_DISABLED_OR_NOT_SUPPORTED_BY_THIS_BROWSER(错误码 5601)报错; - 事件分发:
message事件中带type字段的对象进入统一事件流,eventsOfType按type过滤——这正是第四节、第五节所有语义化流的来源; - RPC 语义:
postMessageWithOperation(type, payload, nonce)发送动作后等待带同一nonce的OPERATION_COMPLETED回执,result为布尔则 resolve,携带error则 reject。SwUpdate.checkForUpdate/activateUpdate与 Worker 侧msg.ts的动作处理(worker/src/msg.ts)由此配对。
应用销毁时(ApplicationRef.onDestroy)信道会移除 controllerchange 与 message 监听,避免内存泄漏。
七、错误码速查
该包的运行时错误集中在 errors.ts,保留错误码区间为 5600–5699:
| 错误码 | 枚举名 | 触发场景 |
|---|---|---|
| 5600 | UNKNOWN_REGISTRATION_STRATEGY |
registrationStrategy 是无法识别的字符串 |
| 5601 | SERVICE_WORKER_DISABLED_OR_NOT_SUPPORTED_BY_THIS_BROWSER |
浏览器不支持 SW、enabled: false、或取不到 registration |
| 5602 | NOT_SUBSCRIBED_TO_PUSH_NOTIFICATIONS |
未订阅时调用 SwPush.unsubscribe() |
| 5603 | PUSH_SUBSCRIPTION_UNSUBSCRIBE_FAILED |
sub.unsubscribe() 返回 false |
| 5604 | SERVICE_WORKER_REGISTRATION_FAILED |
navigator.serviceWorker.register 失败(仅打印,不抛出) |
这些编号与 Angular 运行时错误码规范一致,生产环境日志中可通过编号直接定位问题。
八、包的出口与配套工件
package.json 定义了除 TS 入口外的额外产物出口:
@angular/service-worker/ngsw-worker.js:编译后的主 Worker 脚本(源码在 worker/src,由worker/main.ts汇聚 adapter、driver、data、manifest 等模块);@angular/service-worker/safety-worker.js:兜底 Worker(safety-worker.js),在主 Worker 脚本加载失败时接管并尝试恢复;@angular/service-worker/config/schema.json:构建期ngsw-config工具的ngsw-config.json配置 schema(config/schema.json,bin 入口为ngsw-config);- 依赖约束:
peerDependencies要求@angular/core同版本与rxjs ^6.5.3 || ^7.4.0,这也解释了报告头部import { Observable } from 'rxjs'的来源。
九、总结:如何正确消费这套 API
- 注册:standalone/bootstrap 应用用
provideServiceWorker('ngsw-worker.js', options),NgModule 应用用ServiceWorkerModule.register(...),两者等价;默认registerWhenStable:30000策略通常无需修改,长轮询/持续定时器场景可显式指定registerImmediately或自定义 Observable 策略; - 更新:订阅
SwUpdate.versionUpdates按type分发四类VersionEvent,在VERSION_READY时提示用户刷新;订阅unrecoverable并在事件到达时整页重载;确有需要才调用activateUpdate; - 推送:注入
SwPush后以serverPublicKey调requestSubscription获取订阅,服务端按notification载荷约定推送,应用侧用notificationClicks/notificationCloses处理交互,用pushSubscriptionChanges同步服务端订阅状态; - 稳定性:以上符号的签名受 goldens/public-api/service-worker/index.api.md golden 文件约束,依赖这些 API 的上游项目可以放心将其视为稳定契约。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00