@statewalker/webrun-http-browser 0.3.4 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,6 +30,15 @@ the raw platform APIs, and this package fills them:
30
30
  Combining both modes means the same handler code works in an app you
31
31
  control *and* in an embed you don't.
32
32
 
33
+ ## Install
34
+
35
+ ```sh
36
+ npm install @statewalker/webrun-http-browser
37
+ ```
38
+
39
+ Browser-only — it needs `navigator.serviceWorker`, so a secure context
40
+ (`https://` or `localhost`) is required. No peer dependencies.
41
+
33
42
  ## How to use
34
43
 
35
44
  ```sh
@@ -38,8 +47,8 @@ npm install @statewalker/webrun-http-browser
38
47
 
39
48
  | Subpath | Purpose |
40
49
  | --- | --- |
41
- | `@statewalker/webrun-http-browser` | Page-side relay API: `newRemoteRelayChannel`, `initHttpService`, `callHttpService`, `splitServiceUrl`, `initServiceWorker`, `newServiceWorkerPort`, `getRelayWindowMessageHandler`; the MessagePort call primitives (`callChannel`, `handleChannelCalls`, `newInvokationChannel`, `sendStream`, `handleStreams`, `newRegistry`); plus everything re-exported from `@statewalker/webrun-http-streams` (`HttpError`, the client/server stubs) and `@statewalker/webrun-streams` (stream and error helpers) |
42
- | `@statewalker/webrun-http-browser/sw` | Same-origin adapter classes: `SwHttpAdapter` (page), `SwHttpDispatcher` (SW), `startHttpDispatcher` bootstrap |
50
+ | `@statewalker/webrun-http-browser` | Page-side relay API: `newRemoteRelayChannel`, `initHttpService`, `callHttpService`, `splitServiceUrl`, `initServiceWorker`, `newServiceWorkerPort`, `getRelayWindowMessageHandler`; the MessagePort call primitives (`callChannel`, `handleChannelCalls`, `newInvokationChannel`, `sendStream`, `handleStreams`, `newRegistry`); plus everything re-exported from `@statewalker/webrun-http-streams` (`HttpError`, the client/server stubs), `@statewalker/webrun-streams` (stream and error helpers) and the `MessageTarget` family from `@statewalker/webrun-rpc` |
51
+ | `@statewalker/webrun-http-browser/sw` | Same-origin adapter classes: `SwHttpAdapter` (page), `SwHttpDispatcher` (SW), `startHttpDispatcher` bootstrap; `start()` options `timeout` and `reloadIfUncontrolled` |
43
52
  | `@statewalker/webrun-http-browser/relay-sw` | IIFE bundle of the relay SW runtime — load via `importScripts` from a loader script in your relay origin |
44
53
  | `@statewalker/webrun-http-browser/sw-worker` | IIFE bundle of the same-origin SW runtime — ditto, for same-origin apps |
45
54
 
@@ -115,6 +124,45 @@ const { baseUrl } = await adapter.register(`${KEY}/api/`, async (request) => {
115
124
  // fetch(`${baseUrl}anything`) is intercepted by the SW.
116
125
  ```
117
126
 
127
+ `start()` resolves once the worker is activated **and controls the page** —
128
+ only a controlled page's `fetch()` reaches the worker. Two options bound it:
129
+
130
+ | Option | Default | Meaning |
131
+ | --- | --- | --- |
132
+ | `timeout` | `30_000` | Upper bound, in ms, for the wait for the worker to activate, take control and answer the adapter's handshake. Past it `start()` rejects with a `ServiceWorkerControlError` (see `reason`) instead of waiting forever. |
133
+ | `reloadIfUncontrolled` | `false` | If the page is still uncontrolled once the worker is active, reload it once instead of rejecting (see below). |
134
+
135
+ #### When the page is not controlled
136
+
137
+ A page can load **uncontrolled although its worker is active**: a hard reload
138
+ (Ctrl+Shift+R / Cmd+Shift+R) bypasses ServiceWorkers for that load, and the
139
+ worker's `clients.claim()` already ran when it activated, so nothing ever
140
+ hands the page to it. (Firefox has also been seen leaving a second page of a
141
+ running worker uncontrolled.) `start()` then asks the worker to claim the page
142
+ again — a `CLAIM` call this package's workers answer with `clients.claim()` —
143
+ and waits for `controllerchange`. That takes the page over in Chromium and
144
+ Firefox, both verified by the browser tests.
145
+
146
+ If it still is not controlled (a worker that does not answer `CLAIM`, a page
147
+ outside the worker's scope), `start()` rejects with a
148
+ `ServiceWorkerControlError` whose `reason` is `"uncontrolled"` and whose message
149
+ says what happened and what to do. With `reloadIfUncontrolled: true` it reloads
150
+ the page instead — a normal reload is a controlled navigation — at most once:
151
+ a `sessionStorage` marker makes a second uncontrolled load reject rather than
152
+ loop. After a failure, calling `start()` again retries.
153
+
154
+ ```ts
155
+ try {
156
+ await adapter.start();
157
+ } catch (error) {
158
+ if ((error as Error).name === "ServiceWorkerControlError") showReloadPrompt();
159
+ else throw error;
160
+ }
161
+ ```
162
+
163
+ Check `name` or `reason`, not `instanceof`: each bundle of this package carries
164
+ its own copy of the class.
165
+
118
166
  The SW script itself ships as a pre-built IIFE bundle. Put a tiny loader
119
167
  next to your app pages so the SW's default scope covers them:
120
168
 
@@ -215,6 +263,87 @@ Why it's interesting:
215
263
  no tooling. Shows how small the glue between a platform API and a
216
264
  `(Request) ⇒ Response` handler can be.
217
265
 
266
+ ## Exports
267
+
268
+ The package root re-exports everything from
269
+ [`@statewalker/webrun-streams`](../webrun-streams) and
270
+ [`@statewalker/webrun-http-streams`](../webrun-http-streams), so existing
271
+ imports keep working after those extractions. Its own surface is below.
272
+
273
+ ### Relay mode
274
+
275
+ | Export | Kind | Purpose |
276
+ | --- | --- | --- |
277
+ | `newRemoteRelayChannel(opts?)` | function | Embeds the hidden relay iframe, handshakes a `MessageChannel`, resolves a `RemoteRelayChannel`. |
278
+ | `RemoteRelayChannelOptions` | interface | `baseUrl`, `url`, `container` — where the relay lives and what to append the iframe to. |
279
+ | `RemoteRelayChannel` | interface | `{ baseUrl, port, close() }`. |
280
+ | `initHttpService(handler, opts)` | function | Registers `handler` as the server for a service `key` on the relay. Returns a cleanup. |
281
+ | `callHttpService(request, opts)` | function | Sends a `Request` to the service under `key`; resolves its `Response`. |
282
+ | `ServiceOptions` | interface | `{ key: string; port: MessageTarget }` — shared by the two above. |
283
+ | `getRelayWindowMessageHandler(opts?)` | function | The `window.onmessage` handler that runs *inside* the relay iframe. |
284
+ | `RelayWindowHandlerOptions` | interface | `swUrl`, `scopeUrl` for that handler. |
285
+ | `splitServiceUrl(url, separator?)` | function | Splits a relay URL into service key + remaining path (default separator `~`). |
286
+ | `SplitServiceUrl` | interface | Its result shape. |
287
+
288
+ ### ServiceWorker lifecycle
289
+
290
+ | Export | Kind | Purpose |
291
+ | --- | --- | --- |
292
+ | `initServiceWorker(opts)` | function | Registers a SW and resolves with it once it is activated: the controller, or the registration's active worker when the page is not controlled (the relay only needs to message it). Rejects with a `ServiceWorkerControlError` past `timeout`. |
293
+ | `InitServiceWorkerOptions` | interface | `{ swUrl, scopeUrl?, type?, timeout? }`. |
294
+ | `newServiceWorkerPort(registration?)` | function | A `MessagePort` that transparently bridges to the controlling SW, or to `registration.active` while the page is not controlled. |
295
+ | `awaitActiveServiceWorker(registration, opts?)` | function | Resolves with the registration's worker once `activated`; rejects (`reason: "activation-timeout"`) past `timeout`. |
296
+ | `awaitServiceWorkerControl(registration, opts?)` | function | Resolves with the controller once the page is controlled, asking the worker to claim an uncontrolled page; bounded by `timeout`, optional `reloadIfUncontrolled`. What `SwHttpAdapter.start()` uses. |
297
+ | `handleClaimRequests(self)` | function | Worker side: answers the page's `CLAIM` call with `clients.claim()`. Both of this package's workers install it; use it in a worker of your own. |
298
+ | `ServiceWorkerControlError` | class | `name: "ServiceWorkerControlError"`, `reason`: `"activation-timeout"` (worker did not activate in time), `"uncontrolled"` (active, but the page is not controlled), `"unresponsive"` (controls the page, did not answer `SwHttpAdapter`'s handshake). |
299
+ | `DEFAULT_SERVICE_WORKER_TIMEOUT` / `CLAIM_CALL` | const | `30_000` ms / `"CLAIM"`. |
300
+
301
+ ### Connection registry
302
+
303
+ | Export | Kind | Purpose |
304
+ | --- | --- | --- |
305
+ | `initializeConnection(opts)` | function | Sends `CONNECT` for a service `key`; resolves a `MessagePort`, or `null` if no such service. |
306
+ | `InitializeConnectionOptions` | interface | `{ key, communicationPort, ...extra }` — extra fields ride along in the CONNECT payload. |
307
+ | `registerConnectionsHandler(opts)` | function | Registers a `key` and answers inbound `CONNECT`s. Returns a cleanup that unregisters. |
308
+ | `RegisterConnectionsHandlerOptions` | interface | `{ key, handler, communicationPort }`. |
309
+
310
+ ### Messaging primitives
311
+
312
+ | Export | Kind | Purpose |
313
+ | --- | --- | --- |
314
+ | `callChannel(target, type, data, port?)` | function | One typed request/response over a `MessageTarget`. |
315
+ | `handleChannelCalls(target, type, handler)` | function | Answer those calls. Returns an unsubscribe. |
316
+ | `ChannelCallHandler` | type | The handler signature the two above exchange. |
317
+ | `newInvokationChannel(opts)` | function | Multiplexed invocations over one target. |
318
+ | `InvocationChannel` / `NewInvocationChannelOptions` | interface | Its result and options. |
319
+ | `handleStreams(...)` / `StreamHandler<T>` | function / type | Stream-shaped invocations over the same channel. |
320
+
321
+ > **These stream primitives have no backpressure.** `sendStream`'s chunk sender
322
+ > discards the promise it is handed, so a fast producer over a slow consumer
323
+ > accumulates without bound; there is also no per-stream timeout and no chunking
324
+ > to a transport's message ceiling. `@statewalker/webrun-rpc`'s `duplexOverPort`
325
+ > is the replacement — one `Duplex` over one port, with the confirmation
326
+ > withheld until the consumer has pulled. Migrating this package onto it is
327
+ > planned, not done.
328
+ | `MessageTarget` / `MessageSource` / `MessageSink` / `MessageListener` | interface / type | The structural port view everything above accepts — a `MessagePort`, a `Worker`, or a SW bridge. Defined in [`@statewalker/webrun-streams`](../webrun-streams) and re-exported here. |
329
+ | `newRegistry(onError?)` | function | Small cleanup registry used for teardown. |
330
+ | `Registry` / `NewRegistryResult` / `CleanupAction` | interface / type | Its shapes. |
331
+
332
+ ### HTTP over a port
333
+
334
+ | Export | Kind | Purpose |
335
+ | --- | --- | --- |
336
+ | `sendHttpRequest(port, request)` | function | **Deprecated.** Ship a `Request` over a `MessageTarget`, await the `Response`. |
337
+ | `handleHttpRequests(port, handler)` | function | **Deprecated.** Serve an `HttpHandler` on the other end of one. |
338
+
339
+ ### Subpath entry points
340
+
341
+ | Entry | Purpose |
342
+ | --- | --- |
343
+ | `@statewalker/webrun-http-browser/sw` | `SwHttpAdapter` — the same-origin ServiceWorker adapter. |
344
+ | `@statewalker/webrun-http-browser/relay-sw` | IIFE relay SW runtime, loadable via `importScripts(...)`. |
345
+ | `@statewalker/webrun-http-browser/sw-worker` | IIFE same-origin SW runtime, loadable via `importScripts(...)`. |
346
+
218
347
  ## Internals
219
348
 
220
349
  ### Source layout
@@ -225,10 +354,14 @@ src/
225
354
  │ ├── data-calls.ts │ Transport primitives over a
226
355
  │ ├── data-channels.ts │ `MessageTarget`: one-shot
227
356
  │ ├── message-target.ts │ `callChannel` / `handleChannelCalls`,
228
- │ └── registry.ts │ the request/response
357
+ │ ├── registry.ts │ the request/response
229
358
  │ │ `newInvokationChannel`, streaming
230
- │ │ `sendStream` / `handleStreams` with
359
+ │ └── service-worker-control.ts │ `sendStream` / `handleStreams` with
231
360
  │ │ backpressure, and `newRegistry`.
361
+ │ │ Bounded waits for a worker to
362
+ │ │ activate / take control, and the
363
+ │ │ `CLAIM` request that takes over an
364
+ │ │ uncontrolled page.
232
365
  │ │ Also re-exports
233
366
  │ │ `@statewalker/webrun-streams`.
234
367
  │ ┘
@@ -293,6 +426,17 @@ src/
293
426
  backpressure — each `next(value)` returns a `Promise<boolean>` that resolves
294
427
  once the consumer has dequeued — and drains in-flight producers on consumer
295
428
  exit.
429
+ - **No literal `new URL("…", import.meta.url)` in page-side code**.
430
+ Bundlers (Vite among them) turn that pattern into an emitted asset at build
431
+ time, before tree-shaking; the relay defaults resolved `"../"` that way,
432
+ which is the package itself, so every Vite consumer shipped a dead copy of
433
+ `dist/index.js`. They resolve against a `moduleUrl` variable instead, and
434
+ `tests/dist/vite-consumer.dist.ts` builds a Vite app to prove nothing is
435
+ emitted.
436
+ - **Uncontrolled pages are handled per mode**. Same-origin mode needs
437
+ control — an uncontrolled page's `fetch()` never reaches the worker — so it
438
+ asks the worker to claim the page. Relay mode needs only a worker to message,
439
+ so an uncontrolled relay page bridges to `registration.active`.
296
440
  - **SW client registry is IndexedDB-persisted**. Both `SwPortDispatcher`
297
441
  (same-origin) and `relay/index-sw.ts` keep their client-lookup tables in
298
442
  IndexedDB so a SW wake-up after idle doesn't lose its bindings.
@@ -313,6 +457,11 @@ src/
313
457
  only controls pages and fetches under `/public/`. If you need a broader
314
458
  scope, the SW script must be served with the
315
459
  `Service-Worker-Allowed` HTTP header, *or* live higher in the origin.
460
+ - **A hard reload loads the page without its worker.** Handled — see
461
+ [When the page is not controlled](#when-the-page-is-not-controlled) — as long
462
+ as the worker answers `CLAIM`. A worker script of your own that
463
+ `importScripts` this package's `sw-worker.js` or `relay-sw.js` does; one
464
+ that does not should call `handleClaimRequests(self)`.
316
465
  - **`http://localhost` or HTTPS only.** Browsers refuse to register SWs
317
466
  on other `http://` origins.
318
467
  - **Relay mode needs an iframe-capable sandbox.** Pages with strict CSP
@@ -336,8 +485,17 @@ Runtime:
336
485
 
337
486
  Dev: TypeScript, vitest, rolldown, rimraf, `http-server` (for the
338
487
  `example:*` scripts), `@types/node` (catalog versions from the monorepo
339
- root).
488
+ root), `playwright` and `vite` (for `test:browser`).
489
+
490
+ ## Tests
491
+
492
+ - `pnpm test` — unit tests under Node, against the source.
493
+ - `pnpm test:browser` — builds, then runs `tests/browser/` in real Chromium and
494
+ Firefox through Playwright against the built bundles (first visit, normal
495
+ reload, hard reload, second tab, the relay page, and the timeout and
496
+ uncontrolled errors), plus `tests/dist/`, which builds a Vite consumer of
497
+ the bundles. Needs the browsers: `npx playwright install chromium firefox`.
340
498
 
341
499
  ## License
342
500
 
343
- MIT © statewalker
501
+ MIT © statewalker — see [LICENSE](../../LICENSE).
@@ -1 +1 @@
1
- {"version":3,"file":"data-channels.d.ts","sourceRoot":"","sources":["../../src/core/data-channels.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAqBzD,MAAM,WAAW,iBAAiB;IAChC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,SAAS,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAClF;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,WAAW,EAAE,KAAK,OAAO,CAAC;IACjE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,MAAM,CAAC;CAC1B;AAED,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,OAEC,EACD,OAAuB,EACvB,SAAuC,GACxC,EAAE,2BAA2B,GAAG,iBAAiB,CAoEjD;AAED,wBAAuB,UAAU,CAAC,CAAC,EACjC,iBAAiB,EAAE,aAAa,EAChC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAYlC;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAC7B,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AAElD,wBAAgB,aAAa,CAAC,CAAC,EAC7B,iBAAiB,EAAE,aAAa,EAChC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GACxB,MAAM,IAAI,CAqBZ"}
1
+ {"version":3,"file":"data-channels.d.ts","sourceRoot":"","sources":["../../src/core/data-channels.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAqBzD,MAAM,WAAW,iBAAiB;IAChC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,GAAG,SAAS,EAAE,YAAY,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;CAClF;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,aAAa,CAAC;IACpB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,KAAK,EAAE,WAAW,EAAE,KAAK,OAAO,CAAC;IACjE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,MAAM,CAAC;CAC1B;AAED,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,OAEC,EACD,OAAuB,EACvB,SAAuC,GACxC,EAAE,2BAA2B,GAAG,iBAAiB,CAoEjD;AAED,wBAAuB,UAAU,CAAC,CAAC,EACjC,iBAAiB,EAAE,aAAa,EAChC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACnC,cAAc,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,CAmBlC;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,CAC7B,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,EACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC5B,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;AAElD,wBAAgB,aAAa,CAAC,CAAC,EAC7B,iBAAiB,EAAE,aAAa,EAChC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GACxB,MAAM,IAAI,CAqBZ"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Settles like `promise` if it settles before `deadline` (a `Date.now()`
3
+ * value). Otherwise calls `onTimeout`: an `Error` it returns rejects, any
4
+ * other value resolves. Internal; not re-exported.
5
+ */
6
+ export declare function withDeadline<T, F>(deadline: number, promise: Promise<T>, onTimeout: () => F): Promise<T | (F extends Error ? never : F)>;
7
+ //# sourceMappingURL=deadline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deadline.d.ts","sourceRoot":"","sources":["../../src/core/deadline.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,EAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,SAAS,EAAE,MAAM,CAAC,GACjB,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAqB5C"}
@@ -3,4 +3,5 @@ export * from "./data-calls.js";
3
3
  export * from "./data-channels.js";
4
4
  export * from "./message-target.js";
5
5
  export * from "./registry.js";
6
+ export * from "./service-worker-control.js";
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAGA,cAAc,6BAA6B,CAAC;AAE5C,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAGA,cAAc,6BAA6B,CAAC;AAE5C,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,eAAe,CAAC;AAC9B,cAAc,6BAA6B,CAAC"}
@@ -1,16 +1,2 @@
1
- export type MessageListener = (event: MessageEvent) => void | Promise<void>;
2
- /** An object we can listen for `"message"` events on. */
3
- export interface MessageSource {
4
- addEventListener(type: "message", listener: MessageListener): void;
5
- removeEventListener(type: "message", listener: MessageListener): void;
6
- start?(): void | Promise<void>;
7
- }
8
- /** An object we can post messages to (with optional transferable list). */
9
- export interface MessageSink {
10
- postMessage(message: unknown, transfer?: Transferable[]): void;
11
- }
12
- /** Full-duplex message target: both sends and receives. */
13
- export interface MessageTarget extends MessageSource, MessageSink {
14
- close?(): void | Promise<void>;
15
- }
1
+ export type { MessageListener, MessageSink, MessageSource, MessageTarget, } from "@statewalker/webrun-rpc";
16
2
  //# sourceMappingURL=message-target.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"message-target.d.ts","sourceRoot":"","sources":["../../src/core/message-target.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE5E,yDAAyD;AACzD,MAAM,WAAW,aAAa;IAC5B,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACnE,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACtE,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAED,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;CAChE;AAED,2DAA2D;AAC3D,MAAM,WAAW,aAAc,SAAQ,aAAa,EAAE,WAAW;IAC/D,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC"}
1
+ {"version":3,"file":"message-target.d.ts","sourceRoot":"","sources":["../../src/core/message-target.ts"],"names":[],"mappings":"AAEA,YAAY,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACb,aAAa,GACd,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * How long, by default, the page waits for its ServiceWorker to activate and
3
+ * to take control before giving up. Generous: a first install downloads and
4
+ * evaluates the worker script, which on a slow link takes seconds.
5
+ */
6
+ export declare const DEFAULT_SERVICE_WORKER_TIMEOUT = 30000;
7
+ /** Channel call a page sends to ask its ServiceWorker to `clients.claim()` it. */
8
+ export declare const CLAIM_CALL = "CLAIM";
9
+ /**
10
+ * - `activation-timeout`: the worker did not activate in time.
11
+ * - `uncontrolled`: the worker is active but the page is not controlled by it.
12
+ * - `unresponsive`: the page is controlled, but the worker did not answer the
13
+ * adapter's handshake in time.
14
+ */
15
+ export type ServiceWorkerControlFailure = "activation-timeout" | "uncontrolled" | "unresponsive";
16
+ /**
17
+ * Why a page could not get a working ServiceWorker. `reason` says which wait
18
+ * failed; check it (or `name`) rather than `instanceof`, because each of this
19
+ * package's bundles carries its own copy of this class.
20
+ */
21
+ export declare class ServiceWorkerControlError extends Error {
22
+ readonly reason: ServiceWorkerControlFailure;
23
+ constructor(reason: ServiceWorkerControlFailure, message: string);
24
+ }
25
+ export interface AwaitServiceWorkerOptions {
26
+ /** Upper bound for the whole wait, in ms. Default `DEFAULT_SERVICE_WORKER_TIMEOUT`. */
27
+ timeout?: number;
28
+ }
29
+ export interface AwaitServiceWorkerControlOptions extends AwaitServiceWorkerOptions {
30
+ /**
31
+ * When the page is still uncontrolled after asking the worker to claim it,
32
+ * reload the page once instead of rejecting. A normal reload is a
33
+ * navigation, and navigations are controlled. Guarded by `sessionStorage`
34
+ * so it never loops: if the reloaded page is uncontrolled too, it rejects.
35
+ * Default `false`.
36
+ */
37
+ reloadIfUncontrolled?: boolean;
38
+ /** For tests. Default `navigator.serviceWorker`. */
39
+ container?: ServiceWorkerContainer;
40
+ }
41
+ /**
42
+ * Resolves with the registration's worker once it is `activated`. Rejects
43
+ * with a `ServiceWorkerControlError` (`reason: "activation-timeout"`) if that
44
+ * takes longer than `timeout` — an install that throws or never finishes
45
+ * would otherwise leave the caller waiting forever.
46
+ */
47
+ export declare function awaitActiveServiceWorker(registration: ServiceWorkerRegistration, { timeout }?: AwaitServiceWorkerOptions): Promise<ServiceWorker>;
48
+ /**
49
+ * Resolves with the ServiceWorker that controls this page, once `registration`
50
+ * has an activated worker and the page is controlled by it.
51
+ *
52
+ * A page can stay uncontrolled while its worker is active, and then no
53
+ * `controllerchange` ever fires on its own:
54
+ * - a hard reload (Ctrl+Shift+R) bypasses the worker for that load, and the
55
+ * worker's `clients.claim()` already ran when it activated;
56
+ * - Firefox can leave a page loaded while the worker is running uncontrolled.
57
+ *
58
+ * So when the page is uncontrolled, this asks the active worker to claim it
59
+ * again (a `CLAIM` channel call — this package's workers answer it) and waits
60
+ * for `controllerchange`. Every wait is bounded by `timeout`. If control never
61
+ * comes it reloads once (`reloadIfUncontrolled`) or rejects with a
62
+ * `ServiceWorkerControlError` (`reason: "uncontrolled"`) that says what
63
+ * happened and what to do.
64
+ */
65
+ export declare function awaitServiceWorkerControl(registration: ServiceWorkerRegistration, { timeout, reloadIfUncontrolled, container, }?: AwaitServiceWorkerControlOptions): Promise<ServiceWorker>;
66
+ /**
67
+ * ServiceWorker side: answers the page's `CLAIM` request with
68
+ * `clients.claim()`, which takes over every uncontrolled client in scope.
69
+ * Returns a function that stops answering.
70
+ */
71
+ export declare function handleClaimRequests(self: ServiceWorkerGlobalScope): () => void;
72
+ //# sourceMappingURL=service-worker-control.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service-worker-control.d.ts","sourceRoot":"","sources":["../../src/core/service-worker-control.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,QAAS,CAAC;AAUrD,kFAAkF;AAClF,eAAO,MAAM,UAAU,UAAU,CAAC;AAElC;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG,oBAAoB,GAAG,cAAc,GAAG,cAAc,CAAC;AAEjG;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAC;IAC7C,YAAY,MAAM,EAAE,2BAA2B,EAAE,OAAO,EAAE,MAAM,EAI/D;CACF;AAED,MAAM,WAAW,yBAAyB;IACxC,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gCAAiC,SAAQ,yBAAyB;IACjF;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,oDAAoD;IACpD,SAAS,CAAC,EAAE,sBAAsB,CAAC;CACpC;AAED;;;;;GAKG;AACH,wBAAsB,wBAAwB,CAC5C,YAAY,EAAE,yBAAyB,EACvC,EAAE,OAAwC,EAAE,GAAE,yBAA8B,GAC3E,OAAO,CAAC,aAAa,CAAC,CAaxB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,yBAAyB,CAC7C,YAAY,EAAE,yBAAyB,EACvC,EACE,OAAwC,EACxC,oBAA4B,EAC5B,SAAmC,GACpC,GAAE,gCAAqC,GACvC,OAAO,CAAC,aAAa,CAAC,CA6CxB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,wBAAwB,GAAG,MAAM,IAAI,CAM9E"}
@@ -1,23 +1,36 @@
1
1
  import { type HttpHandler } from "@statewalker/webrun-http-streams";
2
2
  import type { MessageTarget } from "../core/message-target.js";
3
3
  /**
4
- * @deprecated For new code, prefer the `MessagePort`-based stack from
5
- * `@statewalker/webrun-http-port`. Once a `MessagePort` is established between
6
- * page and worker, `httpServe(port, handler)` provides equivalent semantics
7
- * with `callBidi` multiplexing, full-duplex streaming, and `AbortSignal`.
8
- * This helper remains for existing ServiceWorker setups that still consume the
9
- * `MessageTarget` surface; it will be reimplemented on top of
10
- * `webrun-http-port` in a follow-up release.
4
+ * Serve an `HttpHandler` over a `MessageTarget`, using this package's own
5
+ * `handleStreams` transport.
6
+ *
7
+ * @deprecated Prefer the port stack in `@statewalker/webrun-rpc`: open a port
8
+ * (`multiplexPort` over one pipe, or `transferPortMux` where the platform can
9
+ * transfer a real `MessagePort`), turn it into a `Duplex` with
10
+ * `serveDuplexOverPort`, and serve HTTP on that with
11
+ * `httpServe(handler, options)` from `@statewalker/webrun-http-streams`.
12
+ *
13
+ * That path has backpressure; **this one does not.** `sendStream`'s chunk
14
+ * sender discards the promise it is given, so a fast producer over a slow
15
+ * consumer accumulates without bound. It also has no per-stream timeout and no
16
+ * chunking to a transport's message ceiling.
17
+ *
18
+ * Kept for existing ServiceWorker setups built on the `MessageTarget` surface.
11
19
  */
12
20
  export declare function handleHttpRequests(communicationPort: MessageTarget, handler: HttpHandler): () => void;
13
21
  /**
14
- * @deprecated For new code, prefer the `MessagePort`-based stack from
15
- * `@statewalker/webrun-http-port/fetch`. Once the page and SW share a
16
- * `MessagePort`, `fetchOverPort(port, request)` provides the same
17
- * `Request → Response` semantics with multiplexing via `callBidi`, JSONL
18
- * envelope framing, and native `AbortSignal` support. This helper remains for
19
- * existing ServiceWorker setups; it will be reimplemented on top of
20
- * `webrun-http-port` in a follow-up release.
22
+ * Ship a `Request` over a `MessageTarget` and await the `Response`, using this
23
+ * package's own `sendStream` transport.
24
+ *
25
+ * @deprecated Prefer the port stack in `@statewalker/webrun-rpc`: open a port
26
+ * (`multiplexPort` over one pipe, or `transferPortMux` where the platform can
27
+ * transfer a real `MessagePort`), turn it into a `Duplex` with
28
+ * `duplexOverPort`, and drive HTTP over it with `httpFetch` from
29
+ * `@statewalker/webrun-http-streams`.
30
+ *
31
+ * Same caveat as {@link handleHttpRequests}: the transport underneath this
32
+ * helper has no backpressure, no per-stream timeout, and no chunking to a
33
+ * transport's message ceiling. Kept for existing ServiceWorker setups.
21
34
  */
22
35
  export declare function sendHttpRequest(communicationPort: MessageTarget, request: Request): Promise<Response>;
23
36
  //# sourceMappingURL=http-send-recieve.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-send-recieve.d.ts","sourceRoot":"","sources":["../../src/http/http-send-recieve.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAMjB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AA+B/D;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,iBAAiB,EAAE,aAAa,EAChC,OAAO,EAAE,WAAW,GACnB,MAAM,IAAI,CAOZ;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,iBAAiB,EAAE,aAAa,EAChC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,QAAQ,CAAC,CAOnB"}
1
+ {"version":3,"file":"http-send-recieve.d.ts","sourceRoot":"","sources":["../../src/http/http-send-recieve.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAMjB,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AA+B/D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,iBAAiB,EAAE,aAAa,EAChC,OAAO,EAAE,WAAW,GACnB,MAAM,IAAI,CAOZ;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CACnC,iBAAiB,EAAE,aAAa,EAChC,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,QAAQ,CAAC,CAOnB"}