Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,91 @@
# `cross-origin` — multi-origin Service Worker registration
## Purpose
`registerCrossOriginPackedResourceServiceWorker` lets an application
register a packed-resource Service Worker hosted on a different origin
(typically a CDN subdomain). Before any browser request is dispatched
the helper validates the SW URL's origin against a caller-supplied
allowlist, so attacker-controlled or misconfigured CDN origins cannot
slip past. On success the helper returns both the new registration and
the container's current registration list so multi-tenant apps can
confirm the CDN-origin SW is actually controlling the page.
## Features
- Strict allowlist validation BEFORE `register()` fires
- Parses `swUrl` via the `URL` constructor — malformed URLs reject with
`PACKER_BROWSER.UNAUTHORIZED_ORIGIN`
- Delegates to `registerPackedResourceServiceWorker` so all the
underlying contract (AbortSignal, error wrapping, message resolution)
remains intact
- Returns the full registration list (parity with
`clients.matchAll({ includeUncontrolled: true })`)
## Usage: Basic
```ts
import { registerCrossOriginPackedResourceServiceWorker } from "@suaveplan/packer-browser/utils";
await registerCrossOriginPackedResourceServiceWorker({
swUrl: "https://cdn.example.com/sw.js",
scope: "/packed/",
allowedOrigins: ["https://cdn.example.com"],
});
```
## Usage: Advanced
```ts
import {
PACKER_BROWSER_CODES,
PackerBrowserError,
registerCrossOriginPackedResourceServiceWorker,
} from "@suaveplan/packer-browser";
try {
const { registration, registrations } =
await registerCrossOriginPackedResourceServiceWorker({
swUrl: "https://cdn.example.com/sw.js",
scope: "/",
allowedOrigins: [
"https://cdn.example.com",
"https://cdn-fallback.example.com",
],
container: navigator.serviceWorker,
signal: shutdownController.signal,
});
console.log("registered", registration.scope, "of", registrations.length);
} catch (err) {
if (
err instanceof PackerBrowserError &&
err.code === PACKER_BROWSER_CODES.UNAUTHORIZED_ORIGIN
) {
console.warn("Origin not allowed; falling back to first-party SW.");
}
}
```
## API Reference
- `registerCrossOriginPackedResourceServiceWorker(opts: RegisterCrossOriginOptions): Promise<RegisterCrossOriginResult>`
- `opts.swUrl: string` — absolute SW URL. Required.
- `opts.scope: string` — registration scope. Required.
- `opts.allowedOrigins: ReadonlyArray<string>` — exact-string match. Required.
- `opts.container?` — injectable container. Default: `navigator.serviceWorker`.
- `opts.messageResolver?` — i18n resolver.
- `opts.signal?` — AbortSignal forwarded to `register()`.
- Returns `{ registration, registrations }`.
## Implementation Notes
- Origin equality is exact-string — there is no host-suffix matching.
Allowlisting both `https://cdn.example.com` and
`https://cdn-fallback.example.com` is explicit by design.
- Malformed `swUrl` strings (relative paths, plain identifiers) yield
`UNAUTHORIZED_ORIGIN`, not `SW_REGISTRATION_FAILED`, because the
helper deliberately rejects before consulting the container.
- The `registrations` list is the full `container.getRegistrations()`
return value when a container is supplied; without one (e.g. in a
Node smoke harness) the underlying `register()` already rejects with
`SW_REGISTRATION_FAILED` so the caller never sees a partial list.

View File

@@ -0,0 +1,95 @@
# `register-service-worker`
## Purpose
`register-service-worker.ts` collects four lifecycle helpers exposed via
the `/utils` subpath: `registerPackedResourceServiceWorker`,
`unregisterPackedResourceServiceWorker`, `clearServiceWorkerCache`, and
`getServiceWorkerCacheStatus`. They wrap the
`navigator.serviceWorker` API with typed errors, injectable container
support (for tests), and `AbortSignal` honoured at every yield. Every
boundary error uses `MessageResolver`-keyed messages rather than
hard-coded English literals.
## Features
- Injectable `container: ServiceWorkerContainerLike` — tests pass an
in-memory implementation; production defaults to
`navigator.serviceWorker`.
- `AbortSignal` honoured pre-call (synchronous reject with
`PackerBrowserError(ABORTED)`) and mid-call (channel teardown +
reject) on both `clearServiceWorkerCache` and
`getServiceWorkerCacheStatus`.
- `getServiceWorkerCacheStatus` includes a hard 5-second timeout so a
hung controller cannot leak a pending Promise.
- Boundary errors:
`SW_REGISTRATION_FAILED` (no SW support, register rejected, no
controller attached), `LOAD_FAILED` (timeout / negative reply),
`ABORTED` (signal fires).
- Returns typed shapes: `ServiceWorkerRegistration`, `boolean`,
`void`, `CacheStatusReply`.
## Usage: Basic
```ts
import {
clearServiceWorkerCache,
getServiceWorkerCacheStatus,
registerPackedResourceServiceWorker,
} from "@suaveplan/packer-browser/utils";
await registerPackedResourceServiceWorker();
const status = await getServiceWorkerCacheStatus();
console.log(status.cacheSize, status.urls);
await clearServiceWorkerCache();
```
## Usage: Advanced
```ts
import type { ServiceWorkerContainerLike } from "@suaveplan/packer-browser/utils";
const ac = new AbortController();
const recorded: string[] = [];
const container: ServiceWorkerContainerLike = {
controller: createMockController(recorded),
register: async () => fakeRegistration(),
getRegistrations: async () => [],
ready: Promise.resolve(fakeRegistration()),
};
await registerPackedResourceServiceWorker({
container,
scriptUrl: "/custom.js",
signal: ac.signal,
});
```
## API Reference
- `registerPackedResourceServiceWorker(opts?: RegisterOptions):
Promise<ServiceWorkerRegistration>` —
- throws `SW_REGISTRATION_FAILED` for no SW support / rejected
`register()`.
- throws `ABORTED` on a pre-aborted signal.
- `unregisterPackedResourceServiceWorker(opts?): Promise<boolean>` —
resolves `false` when no container is available.
- `clearServiceWorkerCache(opts?): Promise<void>` — throws
`SW_REGISTRATION_FAILED` if no controller is attached; throws
`ABORTED` if the signal fires.
- `getServiceWorkerCacheStatus(opts?): Promise<CacheStatusReply>` —
throws `LOAD_FAILED` on 5 s timeout; throws `ABORTED` on signal.
- `ServiceWorkerContainerLike` / `RegisterOptions` /
`ServiceWorkerLifecycleOptions` — type-only injection points.
## Implementation Notes
The helpers route every `controller.postMessage` through a
`MessageChannel` and listen on `port1` for the reply. The
`AbortSignal` integration closes `port1` before rejecting so the
underlying SW does not see a half-open channel. The 5-second
`getServiceWorkerCacheStatus` timeout is hard-coded because the
upstream form had the same value; Cluster B will widen this once the
SW gains range-request support and a single status query can take
arbitrarily long under load.