@statewalker/webrun-http-browser 0.4.2 → 0.6.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022-2026 statewalker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -48,8 +48,9 @@ npm install @statewalker/webrun-http-browser
48
48
  | Subpath | Purpose |
49
49
  | --- | --- |
50
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 |
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 |
51
+ | `@statewalker/webrun-http-browser/sw` | Same-origin adapter classes: `SwHttpAdapter` (page), `SwHttpDispatcher` (SW), `startHttpDispatcher` bootstrap; `start()` options `timeout` and `reloadIfUncontrolled` |
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. No declarations: it takes its options from `self.RELAY_OPTIONS` |
53
+ | `@statewalker/webrun-http-browser/relay-worker` | The same runtime as a typed ES module, for a host that bundles its own relay worker: `startRelayServiceWorker(self, options)`, plus `RelayServiceWorkerOptions` and `MountSpec` to type them (including a hand-written `self.RELAY_OPTIONS`) |
53
54
  | `@statewalker/webrun-http-browser/sw-worker` | IIFE bundle of the same-origin SW runtime — ditto, for same-origin apps |
54
55
 
55
56
  ## Examples
@@ -101,6 +102,125 @@ const res = await callHttpService(
101
102
  serving a mini site; [`demo/demo-2.html`](./demo/demo-2.html) pipes a
102
103
  local-disk folder (File System Access API) through it.
103
104
 
105
+ ### Mounting a service at a path
106
+
107
+ A service can claim a path prefix instead of living at `/~<key>/`. Both the
108
+ key and the path are the caller's, and several services can share **one**
109
+ relay connection — the shape mounts exist for is an app at the origin root
110
+ and, say, a gateway one level down, both reachable through the same iframe:
111
+
112
+ ```ts
113
+ const connection = await newRemoteRelayChannel(/* … */);
114
+
115
+ await initHttpService(appHandler, { key: "app", path: "/", port: connection.port });
116
+ await initHttpService(meshHandler, { key: "mesh", path: "/peers/", port: connection.port });
117
+ ```
118
+
119
+ A `CONNECT` is routed to the service named in its `key`, so the two never see
120
+ each other's calls even though they share one port. (Earlier builds routed
121
+ every `CONNECT` on a connection to every registered service, which collided
122
+ whenever more than one service shared a port — fixed before this shipped.)
123
+
124
+ The worker routes by the longest matching path prefix, so a catch-all at `/`
125
+ does not shadow `/peers/`, and registration order does not matter. A service
126
+ registered with no `path` is reachable at `/~<key>/`, exactly as before —
127
+ unless the host declared that key in `mounts`, in which case the host's mount
128
+ stands and the page need not repeat it.
129
+
130
+ **A request that matches no mount is not the relay's** — the worker does not
131
+ answer it at all, so the browser performs it exactly as it would with no
132
+ worker installed. That is what lets a host serve its own files from the same
133
+ origin, and it is why a root mount needs `exclude`.
134
+
135
+ **The matched prefix is NOT stripped.** A handler mounted at `/peers/`
136
+ receives `/peers/12D3Koo/llm`, not `/12D3Koo/llm` — the request reaches it
137
+ with the path the browser asked for, whichever mount matched. A handler that
138
+ wants to route relative to its mount keeps its own `basePath` and strips the
139
+ prefix itself.
140
+
141
+ These options are read by `startRelayServiceWorker`, which a host that
142
+ bundles its own relay worker imports from
143
+ `@statewalker/webrun-http-browser/relay-worker` and calls directly (see the
144
+ options table below for the prebuilt-bundle equivalent):
145
+
146
+ ```ts
147
+ import { startRelayServiceWorker } from "@statewalker/webrun-http-browser/relay-worker";
148
+
149
+ startRelayServiceWorker(self, {
150
+ exclude: (url) =>
151
+ url.pathname === "/index.html" ||
152
+ url.pathname === "/relay.html" ||
153
+ url.pathname === "/relay-sw.js",
154
+ takeover: "first-wins",
155
+ canRegister: (client, _key) => new URL(client.url).pathname === "/relay.html",
156
+ decorateResponse: (response) => withMyHeaders(response),
157
+ });
158
+ ```
159
+
160
+ > **Mounting at `/` — set `takeover: "first-wins"` and `canRegister`.**
161
+ > The default is `takeover: "last-wins"` with no `canRegister`, which is what
162
+ > the relay has always done: the last page to REGISTER a key gets it. Before
163
+ > mounts the worst that bought a rogue or buggy same-origin page was
164
+ > `/~<key>/`; with mounts it can claim the **origin root**, and the mount is
165
+ > persisted in IndexedDB, so it outlives the page and every worker restart.
166
+ > On an origin where more than the host's own page can reach the relay,
167
+ > `takeover: "first-wins"` keeps a live holder's key and `canRegister` says
168
+ > which client may ask for it — set both, together, for any mount at `/`.
169
+
170
+ A root mount claims *every* path under the worker's scope, including the
171
+ host's own navigation. If `exclude` only covers the relay page and its
172
+ worker script, a reload requests the host's own entry page — say
173
+ `/index.html` — through the mount too, the mount has no handler for it, and
174
+ the origin cannot come back. `exclude` needs three things, always: the relay
175
+ page, the worker script, and the host's own entry page. That third one is
176
+ easy to miss because nothing fails until the first reload.
177
+
178
+ An excluded page is one the worker does not answer, and in Firefox such a
179
+ page can load **uncontrolled** even while the worker is running — then its
180
+ own `fetch()` never reaches the worker and its mounts look dead. The remedy
181
+ is the one this package already ships for that case: call
182
+ `awaitServiceWorkerControl(registration)` (exported from the package root)
183
+ before relying on `fetch()` from an excluded page. A page *served by* a mount
184
+ is a navigation the worker answers, so it is controlled from its first byte
185
+ and needs nothing.
186
+
187
+ #### `self.RELAY_OPTIONS` — options for the prebuilt worker
188
+
189
+ `dist/relay-sw.js` is an IIFE loaded via classic `importScripts`, so a host
190
+ that uses the shipped worker (rather than building its own from
191
+ `startRelayServiceWorker`) cannot pass options as arguments. It reads them
192
+ instead from `self.RELAY_OPTIONS`, which the host's own tiny worker script
193
+ sets *before* importing the bundle:
194
+
195
+ ```js
196
+ // relay-sw.js — served next to your relay page.
197
+ self.RELAY_OPTIONS = {
198
+ exclude: (url) => url.pathname === "/index.html" || url.pathname === "/relay.html" || url.pathname === "/relay-sw.js",
199
+ takeover: "first-wins",
200
+ };
201
+ importScripts("/path/to/node_modules/@statewalker/webrun-http-browser/dist/relay-sw.js");
202
+ ```
203
+
204
+ This is the only way a prebuilt-worker host reaches `exclude`, `takeover`,
205
+ `canRegister` or `decorateResponse` — omit it and the worker boots with `{}`,
206
+ exactly as it did before mounts. The bundle ships no declarations, so to type
207
+ that object (in a TypeScript loader script, or to check it before shipping)
208
+ import the type from the runtime entry:
209
+
210
+ ```ts
211
+ import type { RelayServiceWorkerOptions } from "@statewalker/webrun-http-browser/relay-worker";
212
+
213
+ declare const self: ServiceWorkerGlobalScope & { RELAY_OPTIONS?: RelayServiceWorkerOptions };
214
+ ```
215
+
216
+ | Option | Default | What it does |
217
+ | --- | --- | --- |
218
+ | `mounts` | none | A fixed table, for a host that knows its services at build time. Each entry is `{ key, path? , match? }`. A key declared here is the host's: a page's REGISTER or UNREGISTER for the same key never replaces or removes it, so the page can register with no `path` of its own. |
219
+ | `exclude` | none | Paths the relay never claims — neither through the table nor through the `/~<key>/` spelling. Checked first. |
220
+ | `canRegister` | everyone | Refuse a registration from the wrong page. |
221
+ | `takeover` | `"last-wins"` | `"first-wins"` keeps a live holder's key. |
222
+ | `decorateResponse` | none | Stamp headers on responses the relay makes; not applied to network fetches. |
223
+
104
224
  ### Same-origin mode
105
225
 
106
226
  Your page registers its own SW, handlers are local to the page:
@@ -124,6 +244,45 @@ const { baseUrl } = await adapter.register(`${KEY}/api/`, async (request) => {
124
244
  // fetch(`${baseUrl}anything`) is intercepted by the SW.
125
245
  ```
126
246
 
247
+ `start()` resolves once the worker is activated **and controls the page** —
248
+ only a controlled page's `fetch()` reaches the worker. Two options bound it:
249
+
250
+ | Option | Default | Meaning |
251
+ | --- | --- | --- |
252
+ | `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. |
253
+ | `reloadIfUncontrolled` | `false` | If the page is still uncontrolled once the worker is active, reload it once instead of rejecting (see below). |
254
+
255
+ #### When the page is not controlled
256
+
257
+ A page can load **uncontrolled although its worker is active**: a hard reload
258
+ (Ctrl+Shift+R / Cmd+Shift+R) bypasses ServiceWorkers for that load, and the
259
+ worker's `clients.claim()` already ran when it activated, so nothing ever
260
+ hands the page to it. (Firefox has also been seen leaving a second page of a
261
+ running worker uncontrolled.) `start()` then asks the worker to claim the page
262
+ again — a `CLAIM` call this package's workers answer with `clients.claim()` —
263
+ and waits for `controllerchange`. That takes the page over in Chromium and
264
+ Firefox, both verified by the browser tests.
265
+
266
+ If it still is not controlled (a worker that does not answer `CLAIM`, a page
267
+ outside the worker's scope), `start()` rejects with a
268
+ `ServiceWorkerControlError` whose `reason` is `"uncontrolled"` and whose message
269
+ says what happened and what to do. With `reloadIfUncontrolled: true` it reloads
270
+ the page instead — a normal reload is a controlled navigation — at most once:
271
+ a `sessionStorage` marker makes a second uncontrolled load reject rather than
272
+ loop. After a failure, calling `start()` again retries.
273
+
274
+ ```ts
275
+ try {
276
+ await adapter.start();
277
+ } catch (error) {
278
+ if ((error as Error).name === "ServiceWorkerControlError") showReloadPrompt();
279
+ else throw error;
280
+ }
281
+ ```
282
+
283
+ Check `name` or `reason`, not `instanceof`: each bundle of this package carries
284
+ its own copy of the class.
285
+
127
286
  The SW script itself ships as a pre-built IIFE bundle. Put a tiny loader
128
287
  next to your app pages so the SW's default scope covers them:
129
288
 
@@ -238,21 +397,28 @@ imports keep working after those extractions. Its own surface is below.
238
397
  | `newRemoteRelayChannel(opts?)` | function | Embeds the hidden relay iframe, handshakes a `MessageChannel`, resolves a `RemoteRelayChannel`. |
239
398
  | `RemoteRelayChannelOptions` | interface | `baseUrl`, `url`, `container` — where the relay lives and what to append the iframe to. |
240
399
  | `RemoteRelayChannel` | interface | `{ baseUrl, port, close() }`. |
241
- | `initHttpService(handler, opts)` | function | Registers `handler` as the server for a service `key` on the relay. Returns a cleanup. |
400
+ | `initHttpService(handler, opts)` | function | Registers `handler` as the server for a service `key` on the relay, optionally mounted at `path`. Several services may share one `port`. Returns a cleanup. |
242
401
  | `callHttpService(request, opts)` | function | Sends a `Request` to the service under `key`; resolves its `Response`. |
243
- | `ServiceOptions` | interface | `{ key: string; port: MessageTarget }` — shared by the two above. |
402
+ | `ServiceOptions` | interface | `{ key: string; path?: string; port: MessageTarget }` — shared by the two above. `path` mounts the service (see [Mounting a service at a path](#mounting-a-service-at-a-path)); omitted, it stays at `/~<key>/`. |
244
403
  | `getRelayWindowMessageHandler(opts?)` | function | The `window.onmessage` handler that runs *inside* the relay iframe. |
245
404
  | `RelayWindowHandlerOptions` | interface | `swUrl`, `scopeUrl` for that handler. |
246
- | `splitServiceUrl(url, separator?)` | function | Splits a relay URL into service key + remaining path (default separator `~`). |
405
+ | `splitServiceUrl(url, separator?)` | function | Splits a relay URL into service key + remaining path (default separator `~`); anchored to the pathname, so a query string like `?q=~foo` is never read as a service. |
247
406
  | `SplitServiceUrl` | interface | Its result shape. |
407
+ | `startRelayServiceWorker(self, opts?)` | function | The SW side of the relay: routes `fetch` to the client that registered a mount, and answers `REGISTER`/`UNREGISTER`/`CONNECT`. Not re-exported from the package root — it is what the prebuilt `dist/relay-sw.js` calls internally; see [`self.RELAY_OPTIONS`](#selfrelay_options--options-for-the-prebuilt-worker). |
408
+ | `RelayServiceWorkerOptions` | interface | `{ mounts?, exclude?, canRegister?, takeover?, decorateResponse? }` — see the options table in [Mounting a service at a path](#mounting-a-service-at-a-path). |
248
409
 
249
410
  ### ServiceWorker lifecycle
250
411
 
251
412
  | Export | Kind | Purpose |
252
413
  | --- | --- | --- |
253
- | `initServiceWorker(opts)` | function | Registers a SW and resolves once it is activated **and controlling the page**. |
254
- | `InitServiceWorkerOptions` | interface | `{ swUrl, scopeUrl?, type? }`. |
255
- | `newServiceWorkerPort()` | function | A `MessagePort` that transparently bridges to the controlling SW. |
414
+ | `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`. |
415
+ | `InitServiceWorkerOptions` | interface | `{ swUrl, scopeUrl?, type?, timeout? }`. |
416
+ | `newServiceWorkerPort(registration?)` | function | A `MessagePort` that transparently bridges to the controlling SW, or to `registration.active` while the page is not controlled. |
417
+ | `awaitActiveServiceWorker(registration, opts?)` | function | Resolves with the registration's worker once `activated`; rejects (`reason: "activation-timeout"`) past `timeout`. |
418
+ | `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. |
419
+ | `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. |
420
+ | `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). |
421
+ | `DEFAULT_SERVICE_WORKER_TIMEOUT` / `CLAIM_CALL` | const | `30_000` ms / `"CLAIM"`. |
256
422
 
257
423
  ### Connection registry
258
424
 
@@ -297,7 +463,7 @@ imports keep working after those extractions. Its own surface is below.
297
463
  | Entry | Purpose |
298
464
  | --- | --- |
299
465
  | `@statewalker/webrun-http-browser/sw` | `SwHttpAdapter` — the same-origin ServiceWorker adapter. |
300
- | `@statewalker/webrun-http-browser/relay-sw` | IIFE relay SW runtime, loadable via `importScripts(...)`. |
466
+ | `@statewalker/webrun-http-browser/relay-sw` | IIFE relay SW runtime, loadable via `importScripts(...)`. Reads its options from `self.RELAY_OPTIONS`, set before the `importScripts` call. |
301
467
  | `@statewalker/webrun-http-browser/sw-worker` | IIFE same-origin SW runtime, loadable via `importScripts(...)`. |
302
468
 
303
469
  ## Internals
@@ -310,10 +476,14 @@ src/
310
476
  │ ├── data-calls.ts │ Transport primitives over a
311
477
  │ ├── data-channels.ts │ `MessageTarget`: one-shot
312
478
  │ ├── message-target.ts │ `callChannel` / `handleChannelCalls`,
313
- │ └── registry.ts │ the request/response
479
+ │ ├── registry.ts │ the request/response
314
480
  │ │ `newInvokationChannel`, streaming
315
- │ │ `sendStream` / `handleStreams` with
481
+ │ └── service-worker-control.ts │ `sendStream` / `handleStreams` with
316
482
  │ │ backpressure, and `newRegistry`.
483
+ │ │ Bounded waits for a worker to
484
+ │ │ activate / take control, and the
485
+ │ │ `CLAIM` request that takes over an
486
+ │ │ uncontrolled page.
317
487
  │ │ Also re-exports
318
488
  │ │ `@statewalker/webrun-streams`.
319
489
  │ ┘
@@ -378,6 +548,17 @@ src/
378
548
  backpressure — each `next(value)` returns a `Promise<boolean>` that resolves
379
549
  once the consumer has dequeued — and drains in-flight producers on consumer
380
550
  exit.
551
+ - **No literal `new URL("…", import.meta.url)` in page-side code**.
552
+ Bundlers (Vite among them) turn that pattern into an emitted asset at build
553
+ time, before tree-shaking; the relay defaults resolved `"../"` that way,
554
+ which is the package itself, so every Vite consumer shipped a dead copy of
555
+ `dist/index.js`. They resolve against a `moduleUrl` variable instead, and
556
+ `tests/dist/vite-consumer.dist.ts` builds a Vite app to prove nothing is
557
+ emitted.
558
+ - **Uncontrolled pages are handled per mode**. Same-origin mode needs
559
+ control — an uncontrolled page's `fetch()` never reaches the worker — so it
560
+ asks the worker to claim the page. Relay mode needs only a worker to message,
561
+ so an uncontrolled relay page bridges to `registration.active`.
381
562
  - **SW client registry is IndexedDB-persisted**. Both `SwPortDispatcher`
382
563
  (same-origin) and `relay/index-sw.ts` keep their client-lookup tables in
383
564
  IndexedDB so a SW wake-up after idle doesn't lose its bindings.
@@ -398,6 +579,11 @@ src/
398
579
  only controls pages and fetches under `/public/`. If you need a broader
399
580
  scope, the SW script must be served with the
400
581
  `Service-Worker-Allowed` HTTP header, *or* live higher in the origin.
582
+ - **A hard reload loads the page without its worker.** Handled — see
583
+ [When the page is not controlled](#when-the-page-is-not-controlled) — as long
584
+ as the worker answers `CLAIM`. A worker script of your own that
585
+ `importScripts` this package's `sw-worker.js` or `relay-sw.js` does; one
586
+ that does not should call `handleClaimRequests(self)`.
401
587
  - **`http://localhost` or HTTPS only.** Browsers refuse to register SWs
402
588
  on other `http://` origins.
403
589
  - **Relay mode needs an iframe-capable sandbox.** Pages with strict CSP
@@ -421,7 +607,16 @@ Runtime:
421
607
 
422
608
  Dev: TypeScript, vitest, rolldown, rimraf, `http-server` (for the
423
609
  `example:*` scripts), `@types/node` (catalog versions from the monorepo
424
- root).
610
+ root), `playwright` and `vite` (for `test:browser`).
611
+
612
+ ## Tests
613
+
614
+ - `pnpm test` — unit tests under Node, against the source.
615
+ - `pnpm test:browser` — builds, then runs `tests/browser/` in real Chromium and
616
+ Firefox through Playwright against the built bundles (first visit, normal
617
+ reload, hard reload, second tab, the relay page, and the timeout and
618
+ uncontrolled errors), plus `tests/dist/`, which builds a Vite consumer of
619
+ the bundles. Needs the browsers: `npx playwright install chromium firefox`.
425
620
 
426
621
  ## License
427
622
 
@@ -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"}
@@ -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"}