# `update-controller/update-controller.ts` _(no submodules)_ ## Purpose `UpdateController` coordinates a synchronised page reload across every tab the application is open in when a new service worker activates. It opens a single `BroadcastChannel`, listens for a typed `{ type: "RELOAD" }` envelope, and invokes `window.location.reload()` on receipt. The controller solves the "tab A is already running the new SW while tabs B/C are still serving the old one" problem that bites every multi-tab PWA without explicit cross-tab coordination. ## Features - Single `BroadcastChannel` per controller instance, named `@suaveplan/pwa/update` by default. - Typed message envelope (`UpdateMessage`) so consumers cannot accidentally broadcast unrelated payloads on the same channel. - Injectable `BroadcastChannelLike` factory + `WindowLike` seam — production uses the platform `BroadcastChannel` + `window`, tests inject a shared in-memory hub that lets multiple controllers observe each other's messages. - `close()` removes listeners and closes the channel so the controller can be discarded after activation, preventing leaks in single-page apps. ## Usage: Basic ```ts import { UpdateController } from "@suaveplan/pwa"; const updates = new UpdateController(); swm.onStateChange((state) => { if (state === "activated") updates.signalReload(); }); ``` ## Usage: Advanced Test two controllers communicating through one shared in-memory channel hub: ```ts import { UpdateController } from "@suaveplan/pwa"; const hub = createSharedChannelHub(); const reloaded = { a: 0, b: 0 }; const a = new UpdateController({ broadcastChannelFactory: (n) => hub.open(n), window: { location: { reload: () => reloaded.a++ } }, }); const b = new UpdateController({ broadcastChannelFactory: (n) => hub.open(n), window: { location: { reload: () => reloaded.b++ } }, }); a.signalReload(); // receivers run synchronously inside the hub expect(reloaded.b).toBe(1); ``` ## API Reference - `new UpdateController(options?: UpdateControllerOptions)`. - `signalReload(): void`. - `close(): void`. - `UpdateMessage` — `{ type: "RELOAD" }`. - `BroadcastChannelLike`, `BroadcastChannelFactory`, `UpdateWindowLike` — seams. ## Implementation Notes The controller does not invoke `window.location.reload()` in response to its own broadcast — `BroadcastChannel` already excludes the sending context from receipt, so the originating tab is expected to reload via the SW's own `controllerchange` path. The injected `WindowLike` is optional so the controller can also be constructed inside a SW global scope (which has no `window`) for diagnostic purposes; in that mode reloads are no-ops.