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,77 @@
# `update-controller/update-controller.ts`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## 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.