2.7 KiB
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
BroadcastChannelper controller instance, named@suaveplan/pwa/updateby default. - Typed message envelope (
UpdateMessage) so consumers cannot accidentally broadcast unrelated payloads on the same channel. - Injectable
BroadcastChannelLikefactory +WindowLikeseam — production uses the platformBroadcastChannel+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
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:
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.