first commit
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
# `service-worker/service-worker.ts`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`ServiceWorkerManager` wraps the browser `ServiceWorkerContainer` API behind an
|
||||
injectable seam so production code reaches `navigator.serviceWorker` exactly once
|
||||
(via the constructor default) while tests pass an in-process fake container that
|
||||
drives every lifecycle transition deterministically. The manager handles
|
||||
registration, update polling, `skipWaiting` coordination, state-change emission,
|
||||
and OTel counter updates without any `vi.stubGlobal` shenanigans.
|
||||
|
||||
## Features
|
||||
|
||||
- Registration with scope, custom `scriptUrl`, `onUpdate` / `onSuccess` /
|
||||
`onError` callbacks, and configurable update-poll interval.
|
||||
- Lifecycle state machine — `installing → installed → activating → activated →
|
||||
redundant` plus `unsupported` for runtimes lacking `ServiceWorkerContainer`.
|
||||
- `skipWaiting()` posts `{ type: "SKIP_WAITING" }` to the waiting worker.
|
||||
- `unregister()` clears the update timer and tears down the registration.
|
||||
- `checkForUpdate()` invokes `registration.update()` and returns success.
|
||||
- Typed errors via `PWA_CODES.SW_UNSUPPORTED` / `PWA_CODES.SW_REGISTRATION_FAILED`.
|
||||
- OTel counters for `pwa_sw_installed_total`, `pwa_sw_activated_total`,
|
||||
`pwa_sw_updated_total`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { ServiceWorkerManager } from "@suaveplan/pwa";
|
||||
|
||||
const swm = new ServiceWorkerManager();
|
||||
const result = await swm.register({ scriptUrl: "/sw.js" });
|
||||
if (!result.success && result.error !== undefined) {
|
||||
console.error(result.error.code, result.error.message);
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Inject a fake container + interval scheduler for deterministic tests:
|
||||
|
||||
```ts
|
||||
import { ServiceWorkerManager } from "@suaveplan/pwa";
|
||||
|
||||
const swm = new ServiceWorkerManager({
|
||||
container: fakeContainer,
|
||||
intervals: {
|
||||
setInterval: (cb, ms) => fakeTimers.schedule(cb, ms),
|
||||
clearInterval: (h) => fakeTimers.cancel(h),
|
||||
},
|
||||
observability: testObs,
|
||||
});
|
||||
await swm.register({ scriptUrl: "/sw.js", updateInterval: 100 });
|
||||
fakeTimers.tick(100);
|
||||
expect(fakeContainer.lastRegistration?.update).toHaveBeenCalled();
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `new ServiceWorkerManager(options?: ServiceWorkerManagerOptions)`.
|
||||
- `state: SwState` — current lifecycle state (readonly).
|
||||
- `registration: ServiceWorkerRegistration | null`.
|
||||
- `isSupported: boolean`.
|
||||
- `register(options): Promise<SwRegistrationResult>`.
|
||||
- `unregister(): Promise<boolean>`.
|
||||
- `checkForUpdate(): Promise<boolean>`.
|
||||
- `skipWaiting(): void`.
|
||||
- `onStateChange(listener): () => void`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The manager never reaches `globalThis.navigator` from the body of any method —
|
||||
`this._container` is captured in the constructor and threaded through every
|
||||
subsequent call so an injected fake completely replaces the real container.
|
||||
The interval scheduler is a separate seam so tests can advance time without
|
||||
hooking the global timer, which under `happy-dom` + Vitest's fork pool can race
|
||||
the next test file. Errors surfaced from `container.register()` are wrapped in
|
||||
a `PwaError` with the original thrown error preserved as `cause`, so callers
|
||||
can `instanceof PwaError` + `error.code === "PWA.SW_REGISTRATION_FAILED"` and
|
||||
still recover the underlying `TypeError` / `SecurityError` via `error.cause`.
|
||||
Reference in New Issue
Block a user