first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -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`.