# `service-worker/service-worker.ts` _(no submodules)_ ## 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`. - `unregister(): Promise`. - `checkForUpdate(): Promise`. - `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`.