@statewalker/webrun-http-browser 0.6.5 → 0.7.1

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.
Files changed (95) hide show
  1. package/README.md +302 -52
  2. package/dist/core/data-calls.d.ts +17 -0
  3. package/dist/core/data-calls.d.ts.map +1 -1
  4. package/dist/core/service-worker-control.d.ts +3 -1
  5. package/dist/core/service-worker-control.d.ts.map +1 -1
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +4218 -301
  9. package/dist/link/async-dispose.d.ts +10 -0
  10. package/dist/link/async-dispose.d.ts.map +1 -0
  11. package/dist/link/errors.d.ts +2 -0
  12. package/dist/link/errors.d.ts.map +1 -0
  13. package/dist/link/http-link.d.ts +72 -0
  14. package/dist/link/http-link.d.ts.map +1 -0
  15. package/dist/link/index.d.ts +4 -0
  16. package/dist/link/index.d.ts.map +1 -0
  17. package/dist/link/locks.d.ts +39 -0
  18. package/dist/link/locks.d.ts.map +1 -0
  19. package/dist/link/rendezvous.d.ts +21 -0
  20. package/dist/link/rendezvous.d.ts.map +1 -0
  21. package/dist/relay/index-sw.d.ts +12 -2
  22. package/dist/relay/index-sw.d.ts.map +1 -1
  23. package/dist/relay/index.d.ts +66 -8
  24. package/dist/relay/index.d.ts.map +1 -1
  25. package/dist/relay-sw.js +1328 -64
  26. package/dist/relay-worker.js +1329 -64
  27. package/dist/sw/handler-endpoint.d.ts +81 -0
  28. package/dist/sw/handler-endpoint.d.ts.map +1 -0
  29. package/dist/sw/handler-table.d.ts +53 -0
  30. package/dist/sw/handler-table.d.ts.map +1 -0
  31. package/dist/sw/http-sw-dispatcher.d.ts +162 -21
  32. package/dist/sw/http-sw-dispatcher.d.ts.map +1 -1
  33. package/dist/sw/index.d.ts +5 -0
  34. package/dist/sw/index.d.ts.map +1 -1
  35. package/dist/sw/protocol.d.ts +115 -0
  36. package/dist/sw/protocol.d.ts.map +1 -0
  37. package/dist/sw/sw-dispatcher.d.ts.map +1 -1
  38. package/dist/sw/sw-http-adapter.d.ts +199 -0
  39. package/dist/sw/sw-http-adapter.d.ts.map +1 -0
  40. package/dist/sw/sw-websocket.d.ts +16 -0
  41. package/dist/sw/sw-websocket.d.ts.map +1 -0
  42. package/dist/sw/worker-service.d.ts +45 -0
  43. package/dist/sw/worker-service.d.ts.map +1 -0
  44. package/dist/sw/worker-url.d.ts +9 -0
  45. package/dist/sw/worker-url.d.ts.map +1 -0
  46. package/dist/sw-worker.js +2403 -265
  47. package/dist/sw.d.ts +2 -0
  48. package/dist/sw.d.ts.map +1 -1
  49. package/dist/sw.js +4189 -546
  50. package/dist/worker.d.ts +10 -0
  51. package/dist/worker.d.ts.map +1 -0
  52. package/dist/worker.js +3780 -0
  53. package/dist/ws/client.d.ts +73 -0
  54. package/dist/ws/client.d.ts.map +1 -0
  55. package/dist/ws/frames.d.ts +68 -0
  56. package/dist/ws/frames.d.ts.map +1 -0
  57. package/dist/ws/index.d.ts +6 -0
  58. package/dist/ws/index.d.ts.map +1 -0
  59. package/dist/ws/server.d.ts +53 -0
  60. package/dist/ws/server.d.ts.map +1 -0
  61. package/dist/ws/socket-core.d.ts +91 -0
  62. package/dist/ws/socket-core.d.ts.map +1 -0
  63. package/dist/ws/upgrade.d.ts +93 -0
  64. package/dist/ws/upgrade.d.ts.map +1 -0
  65. package/package.json +10 -5
  66. package/src/core/data-calls.ts +52 -2
  67. package/src/core/service-worker-control.ts +7 -1
  68. package/src/index.ts +3 -0
  69. package/src/link/async-dispose.ts +22 -0
  70. package/src/link/errors.ts +3 -0
  71. package/src/link/http-link.ts +238 -0
  72. package/src/link/index.ts +3 -0
  73. package/src/link/locks.ts +97 -0
  74. package/src/link/rendezvous.ts +60 -0
  75. package/src/relay/index-sw.ts +84 -12
  76. package/src/relay/index.ts +203 -27
  77. package/src/sw/handler-endpoint.ts +278 -0
  78. package/src/sw/handler-table.ts +130 -0
  79. package/src/sw/http-sw-dispatcher.ts +649 -90
  80. package/src/sw/index.ts +5 -0
  81. package/src/sw/protocol.ts +103 -0
  82. package/src/sw/sw-dispatcher.ts +1 -20
  83. package/src/sw/sw-http-adapter.ts +613 -0
  84. package/src/sw/sw-websocket.ts +43 -0
  85. package/src/sw/worker-service.ts +133 -0
  86. package/src/sw/worker-url.ts +19 -0
  87. package/src/sw-worker.ts +8 -3
  88. package/src/sw.ts +2 -0
  89. package/src/worker.ts +9 -0
  90. package/src/ws/client.ts +346 -0
  91. package/src/ws/frames.ts +119 -0
  92. package/src/ws/index.ts +11 -0
  93. package/src/ws/server.ts +132 -0
  94. package/src/ws/socket-core.ts +251 -0
  95. package/src/ws/upgrade.ts +200 -0
package/README.md CHANGED
@@ -2,17 +2,24 @@
2
2
 
3
3
  ## What it is
4
4
 
5
- A ServiceWorker-based HTTP server for the browser. You write ordinary
6
- `(Request) => Response` handlers in page JavaScript; a ServiceWorker intercepts
7
- `fetch()` calls and routes them to those handlers over a `MessagePort`, with no
8
- network round-trip and no external server. It has two modes:
9
-
10
- - **Same-origin** (`./sw` + `./sw-worker`): your app registers its own worker
11
- next to its pages and mounts handlers under URL prefixes.
5
+ A ServiceWorker-based HTTP and WebSocket server for the browser. You write
6
+ ordinary `(Request) => Response` handlers in a page or in a Dedicated Worker; a
7
+ ServiceWorker intercepts `fetch()` calls and routes them to those handlers over
8
+ `MessagePort`s, with no network round-trip and no external server. Bodies
9
+ stream with backpressure in both directions, and an aborted `fetch` reaches the
10
+ handler as a cancellation. It has two modes:
11
+
12
+ - **Same-origin** (`./sw` + `./sw-worker`, and `./worker` in Dedicated
13
+ Workers): your app registers its own worker next to its pages and mounts
14
+ handlers under URL prefixes, in the page itself or in workers the page
15
+ attaches.
12
16
  - **Relay** (`.` + `./relay-sw` or `./relay-worker`): a worker hosted on a relay
13
17
  origin serves requests for any page that embeds a hidden relay iframe. The
14
18
  page never registers a worker of its own.
15
19
 
20
+ In both modes a handler can also accept WebSockets: a socket is carried by a
21
+ `MessagePort`, and the caller gets a class with the browser's `WebSocket` API.
22
+
16
23
  ## Why it exists
17
24
 
18
25
  The browser already has `Request`, `Response`, `ReadableStream` and
@@ -42,9 +49,10 @@ pnpm add @statewalker/webrun-http-browser
42
49
 
43
50
  | Subpath | Format | Runs in | What it gives |
44
51
  | --- | --- | --- | --- |
45
- | `.` | ESM, `dist/index.js` + `.d.ts` | page, relay iframe | Relay page API (`newRemoteRelayChannel`, `initHttpService`, `callHttpService`, `getRelayWindowMessageHandler`, `splitServiceUrl`), ServiceWorker lifecycle helpers (`initServiceWorker`, `newServiceWorkerPort`, `awaitActiveServiceWorker`, `awaitServiceWorkerControl`, `handleClaimRequests`, `ServiceWorkerControlError`), the `MessagePort` call primitives, and everything re-exported from `@statewalker/webrun-streams` and `@statewalker/webrun-http-streams`. |
46
- | `./sw` | ESM, `dist/sw.js` + `.d.ts` | page and worker | Same-origin classes: `SwHttpAdapter` (page), `SwHttpDispatcher` and `startHttpDispatcher` (worker), and their bases `SwPortHandler` / `SwPortDispatcher`. |
47
- | `./sw-worker` | IIFE, `dist/sw-worker.js`, no types | ServiceWorker | Prebuilt same-origin worker: calls `startHttpDispatcher({ self, log: console.log })`. Load it with `importScripts`. |
52
+ | `.` | ESM, `dist/index.js` + `.d.ts` | page, relay iframe | Relay page API (`newRemoteRelayChannel`, `initHttpService`, `callHttpService`, `callWebSocketService`, `relayWebSocketClass`, `getRelayWindowMessageHandler`, `splitServiceUrl`), ServiceWorker lifecycle helpers (`initServiceWorker`, `newServiceWorkerPort`, `awaitActiveServiceWorker`, `awaitServiceWorkerControl`, `handleClaimRequests`, `ServiceWorkerControlError`), everything `./sw` exports, the `MessagePort` call primitives, and everything re-exported from `@statewalker/webrun-streams` and `@statewalker/webrun-http-streams`. |
53
+ | `./sw` | ESM, `dist/sw.js` + `.d.ts` | page and worker | Same-origin classes: `SwHttpAdapter` (page), `SwHttpDispatcher` and `startHttpDispatcher` (worker), the WebSocket module (`createWebSocketClass`, `upgradeWebSocket`, ...), `serviceWorkerWebSocketClass`, the HTTP link layer (`connectHttpLink`, `serveHttpLink`), and the version-1 bases `SwPortHandler` / `SwPortDispatcher`. |
54
+ | `./worker` | ESM, `dist/worker.js` + `.d.ts` | Dedicated Worker | `serveHttpInWorker(handler)`, and the WebSocket helpers a handler needs (`upgradeWebSocket`, `isWebSocketUpgrade`, `requestedProtocols`, `createWebSocketClass`). |
55
+ | `./sw-worker` | IIFE, `dist/sw-worker.js`, no types | ServiceWorker | Prebuilt same-origin worker: calls `startHttpDispatcher({ self, log: console.log, ...self.HTTP_DISPATCHER_OPTIONS })`. Load it with `importScripts`. |
48
56
  | `./relay-sw` | IIFE, `dist/relay-sw.js`, no types | ServiceWorker | Prebuilt relay worker: calls `startRelayServiceWorker(self, self.RELAY_OPTIONS ?? {})`. Load it with `importScripts`. |
49
57
  | `./relay-worker` | ESM, `dist/relay-worker.js` + `.d.ts` | ServiceWorker | The relay worker runtime as a typed module, for a host that bundles its own worker: `startRelayServiceWorker`, `RelayServiceWorkerOptions`, `MountSpec`. Not re-exported from `.`, because it only runs inside a worker. |
50
58
 
@@ -80,7 +88,8 @@ const connection = await newRemoteRelayChannel({
80
88
  url: new URL("https://my-relay.example/relay.html"),
81
89
  });
82
90
 
83
- // 2. Register a handler for service "FS". Returns a cleanup that unregisters it.
91
+ // 2. Register a handler for service "FS". Resolves a function that unregisters
92
+ // it; that function's promise settles once the relay has dropped the service.
84
93
  const unregister = await initHttpService(
85
94
  async (request) => new Response(`Hello ${new URL(request.url).pathname}`),
86
95
  { key: "FS", port: connection.port },
@@ -120,6 +129,28 @@ prefix is not stripped: a handler mounted at `/peers/` receives
120
129
  `/peers/12D3Koo/llm`. A handler that routes relative to its mount strips the
121
130
  prefix itself.
122
131
 
132
+ ### Relay mode: WebSockets
133
+
134
+ The same handler accepts sockets: on a `GET` with `Upgrade: websocket` it
135
+ answers with `upgradeWebSocket` (see [WebSockets](#websockets)). A caller on
136
+ any origin opens one through the relay connection:
137
+
138
+ ```ts
139
+ import { relayWebSocketClass } from "@statewalker/webrun-http-browser";
140
+
141
+ const WS = relayWebSocketClass({ key: "FS", port: connection.port });
142
+ const ws = new WS("https://my-relay.example/~FS/chat", ["v1"]);
143
+ ws.onmessage = (event) => console.log(event.data);
144
+ ```
145
+
146
+ `callWebSocketService(url, protocols, { key, port })` is the lower level: it
147
+ resolves `{ port, protocol }` once the service accepted. The relay worker only
148
+ brokers the port; after that the socket runs between the caller and the
149
+ serving page directly. Content served on the relay origin itself opens sockets
150
+ through the relay worker with `serviceWorkerWebSocketClass()`, routed by its url
151
+ like a fetch. The relay's `canRegister` and `takeover` rules apply to sockets
152
+ as to requests, since both go to the service that holds the key.
153
+
123
154
  ### Relay mode: build your own relay worker
124
155
 
125
156
  ```ts
@@ -134,7 +165,8 @@ const stop = startRelayServiceWorker(self, {
134
165
  url.pathname === "/relay.html" ||
135
166
  url.pathname === "/relay-sw.js",
136
167
  takeover: "first-wins",
137
- canRegister: (client, _key) => new URL(client.url).pathname === "/relay.html",
168
+ canRegister: (client, _key, path) =>
169
+ new URL(client.url).pathname === "/relay.html" && path !== "/index.html",
138
170
  decorateResponse: (response) => response,
139
171
  });
140
172
  ```
@@ -143,9 +175,15 @@ const stop = startRelayServiceWorker(self, {
143
175
  | --- | --- | --- |
144
176
  | `mounts` | none | Fixed table `Array<{ key, path?, match? }>`, for a host that knows its services at build time. A key declared here belongs to the host: a page's `REGISTER` or `UNREGISTER` for it never replaces or removes the mount, so the page can register with no `path`. |
145
177
  | `exclude` | none | `(url) => boolean`. Paths the relay never claims, through the table or through `/~<key>/`. Checked first. |
146
- | `canRegister` | everyone | `(client, key) => boolean \| Promise<boolean>`. Refuses a registration with `this client may not register "<key>"`. |
178
+ | `canRegister` | everyone | `(client, key, path?) => boolean \| Promise<boolean>`. `path` is where the registrant asks to be mounted (`undefined` for `/~<key>/`), so a host can vet where a key lands, not only who takes it. Refuses with `this client may not register "<key>"` (`... at "<path>"`). Also consulted for `UNREGISTER`, with the key's current path. |
147
179
  | `takeover` | `"last-wins"` | `"first-wins"` keeps a live holder's key; a second page gets `"<key>" is already served by another client`. A holder that reloaded is no longer live, so it can always re-register. |
148
180
  | `decorateResponse` | none | `(response, request) => Response`. Stamps headers on responses the relay makes, including its error responses. Not applied to requests the worker leaves to the network. |
181
+ | `streamTimeout` | none | Per-stream inactivity timeout, in ms, of the links to services. |
182
+
183
+ `UNREGISTER` is accepted only from the client that holds the key, or, once
184
+ that client is gone, from any client `canRegister` allows: any page of the
185
+ relay origin can post to the worker, including foreign content the host serves
186
+ there.
149
187
 
150
188
  `startRelayServiceWorker` returns a function that removes its listeners.
151
189
 
@@ -235,10 +273,172 @@ importScripts("/path/to/@statewalker/webrun-http-browser/dist/sw-worker.js");
235
273
  | `scope` | directory of `serviceWorkerUrl` | Registration scope. |
236
274
  | `timeout` | `30_000` | Upper bound, in ms, for activation, control and the adapter's handshake. Past it `start()` rejects with a `ServiceWorkerControlError`. |
237
275
  | `reloadIfUncontrolled` | `false` | Reload the page once instead of rejecting when it stays uncontrolled. |
276
+ | `streamTimeout` | none | Per-stream inactivity timeout of the links, in ms. Unset, a slow reader is throttled, never failed. |
277
+ | `id`, `meta` | a fresh id, none | The handler slot of this page's handlers, and what a `route` function sees of them. |
278
+ | `webSocket` | defaults | Options of `adapter.WebSocket` (`heartbeatMs`, `timers`, `closeTimeoutMs`). |
238
279
 
239
280
  `register(prefix, handler)` resolves `{ baseUrl, prefix, remove() }`. The
240
281
  prefix must start with the adapter's `key`.
241
282
 
283
+ ### Same-origin mode: handlers in Dedicated Workers
284
+
285
+ A handler that does heavy work (SQLite on OPFS, WASM, a build) belongs in a
286
+ worker. Attached with `attachWorker`, the worker talks to the ServiceWorker
287
+ directly: the page makes the channels, hands one end of each to the worker and
288
+ the other to the ServiceWorker, and carries nothing afterwards, so a busy or
289
+ background-throttled page does not slow the requests down.
290
+
291
+ ```ts
292
+ // page
293
+ const adapter = new SwHttpAdapter({ key: "app", serviceWorkerUrl: "/sw-worker.js" });
294
+ await adapter.start();
295
+ const worker = new Worker(new URL("./site-worker.js", import.meta.url), { type: "module" });
296
+ const link = await adapter.attachWorker(worker, { key: "site", id: "shard-0" });
297
+
298
+ // site-worker.js
299
+ import { serveHttpInWorker } from "@statewalker/webrun-http-browser/worker";
300
+
301
+ const service = serveHttpInWorker(async (request) => new Response("from a worker"));
302
+ // Requests through the ServiceWorker's router, to other handlers:
303
+ const other = await service.fetch("/app/api/x");
304
+ ```
305
+
306
+ `attachWorker(worker, { key?, id?, meta? })` resolves a `WorkerLink`
307
+ `{ key, id, owner, drain({ ms }), detach() }`. Several workers may serve one key;
308
+ the ServiceWorker picks one per request (see below). Attaching an `id` the key
309
+ already has replaces that handler: the old one finishes its requests in flight
310
+ and gets no new ones.
311
+
312
+ `serveHttpInWorker(handler, options?)` returns `{ fetch, connectWebSocket,
313
+ WebSocket, linked, close() }`. Its `fetch` goes through the router without a
314
+ `fetch` event, so it works whether or not the worker is controlled.
315
+
316
+ ### Same-origin mode: routing and hand-over
317
+
318
+ The ServiceWorker keeps a table of handlers per key and asks a `route`
319
+ function which one answers a request:
320
+
321
+ ```js
322
+ // sw-worker.js, served next to your pages
323
+ self.HTTP_DISPATCHER_OPTIONS = {
324
+ route: (request, key, handlers) =>
325
+ handlers.find((h) => h.id === request.headers.get("x-shard")) ?? handlers[0],
326
+ };
327
+ importScripts("/path/to/@statewalker/webrun-http-browser/dist/sw-worker.js");
328
+ ```
329
+
330
+ `handlers` are the live (not draining) handlers of the key, each a
331
+ `HandlerInfo` `{ id, key, owner, inflight, meta, attachedAt, draining }`. The
332
+ default, `leastInflightRoute`, takes the one with the fewest requests in flight,
333
+ the oldest on a tie: with one tab per key, the same page as before. A worker of
334
+ your own passes the options to `startHttpDispatcher({ self, ...options })`.
335
+
336
+ | Option | Default | What it does |
337
+ | --- | --- | --- |
338
+ | `route` | `leastInflightRoute` | Picks the handler. `undefined` makes the request wait for another to attach. |
339
+ | `attachTimeoutMs` | `15_000` | How long a request waits for a handler during a hand-over. Past it, `503`. |
340
+ | `relinkTimeoutMs` | `3_000` | How long a request waits for the pages to attach again after a restart. |
341
+ | `handoverMs` | `5_000` | How long a replaced handler may finish its requests. |
342
+ | `wsConnectTimeoutMs` | `15_000` | How long a socket waits for its handler's answer. |
343
+ | `streamTimeout` | none | Per-stream inactivity timeout of the links. |
344
+ | `livenessCheckMs` | `5_000` | Period of the backup liveness check (`navigator.locks.query()`); `0` turns it off. |
345
+
346
+ `link.drain({ ms })` hands a slot over: no new requests reach the handler (they
347
+ wait for the next owner of the key, up to `attachTimeoutMs`), and those in
348
+ flight may finish for up to `ms`. Then the link is closed. A drain or detach
349
+ names the attachment's `owner` token, so a late one from a previous owner never
350
+ touches the current one.
351
+
352
+ ### Same-origin mode: restarts and liveness
353
+
354
+ The browser stops an idle ServiceWorker (Chromium after about 30 s), and every
355
+ port it held dies with it. Nothing reports that on the other end, so:
356
+
357
+ - **Re-linking.** The ServiceWorker keeps its table in memory only. The first
358
+ request after a restart, for a key it served before, asks every window to
359
+ attach its links again (`NEED_LINKS`) and waits for them. Pages answer by
360
+ attaching their own handlers and their workers.
361
+ - **Handler liveness.** Each handler holds a Web Lock while it lives; the
362
+ ServiceWorker watches it. When a tab closes or a worker is terminated, its
363
+ requests in flight fail at once with `502` and
364
+ `The handler was lost while this request was in flight; it may or may not
365
+ have been applied.`
366
+ - **ServiceWorker liveness.** The ServiceWorker holds a Web Lock too, and the
367
+ handlers watch it. When it dies, a worker's calls in flight over
368
+ `service.fetch` fail with `LinkLostError` (`The ServiceWorker was lost while
369
+ this call was in flight; ...`) instead of hanging, and new calls wait: the
370
+ worker asks its page to attach it again, and that message wakes the
371
+ ServiceWorker.
372
+ - **Keep-alive.** Port messages are not events, so a long call over
373
+ `service.fetch` would not keep the ServiceWorker awake. While such calls are
374
+ in flight, the worker asks its page every 20 s to post a keep-alive message
375
+ to the ServiceWorker.
376
+
377
+ Measured in Chromium with a ServiceWorker that may idle out
378
+ (`tests/browser/lifetime.browser.ts`, which drives Chromium over raw CDP:
379
+ Playwright keeps every ServiceWorker running):
380
+
381
+ - without the keep-alive, a 45 s call over `service.fetch` fails at about 30 s
382
+ with the loss error; with it, the call completes;
383
+ - a response streaming through the ServiceWorker keeps it alive by itself: a
384
+ 60 s event stream arrives whole in the test, and a 6-minute one did in a
385
+ one-off run.
386
+
387
+ Without Web Locks, a page handler's liveness falls back to `clients.get()`, and
388
+ worker deaths are not noticed until a request times out.
389
+
390
+ ### WebSockets
391
+
392
+ A ServiceWorker never sees WebSocket connections, so a socket is opened
393
+ explicitly and carried by a `MessagePort`. The handler side is an HTTP upgrade:
394
+
395
+ ```ts
396
+ import { isWebSocketUpgrade, upgradeWebSocket } from "@statewalker/webrun-http-browser/worker";
397
+
398
+ async function handler(request: Request): Promise<Response> {
399
+ if (isWebSocketUpgrade(request)) {
400
+ const { socket, response } = upgradeWebSocket(request, { protocol: "v1" });
401
+ socket.accept();
402
+ socket.addEventListener("message", (event) => socket.send(event.data));
403
+ return response; // status 101
404
+ }
405
+ return new Response("not a socket", { status: 426 });
406
+ }
407
+ ```
408
+
409
+ The caller gets a class with the browser's `WebSocket` API, which leaves the
410
+ global `WebSocket` alone:
411
+
412
+ ```ts
413
+ const ws = new adapter.WebSocket(`${baseUrl}chat`, ["v1"]); // page with an adapter
414
+ const WS = serviceWorkerWebSocketClass(); // any controlled page
415
+ const WS2 = service.WebSocket; // in a worker, via the router
416
+ ```
417
+
418
+ The ServiceWorker only brokers the port: it routes the url like a fetch, asks
419
+ the handler, and hands the accepted port to the caller. The socket then runs
420
+ between the two directly, so it survives a ServiceWorker restart. A socket to
421
+ a handler in the same page needs no ServiceWorker at all.
422
+
423
+ - **Upgrade request.** A browser drops `Upgrade` and `Sec-*` headers from every
424
+ `new Request()`, so the upgrade request carries them as its own `headers`
425
+ property. `requestedProtocols(request)` reads the subprotocols.
426
+ - **Server end.** `ServerWebSocket` delivers events only after `accept()`
427
+ (frames that arrive earlier are queued), answers pings before it, and has
428
+ `send`, `close`, `bufferedAmount` and `whenDrained(limit)` for
429
+ backpressure.
430
+ - **Client.** `createWebSocketClass(connect, options)` validates as the
431
+ standard does (url, scheme, fragment, subprotocol tokens, close codes and
432
+ reasons), has `binaryType`, the `on*` handlers and the close handshake.
433
+ `bufferedAmount` is real: the other end acknowledges what it consumed.
434
+ - **Heartbeat.** A port never reports that its other side died. The client
435
+ pings every `heartbeatMs` (default 10 s) and closes with 1006 when pongs stop
436
+ for twice that; the server closes with 1006 when pings stop for three times
437
+ that. On a page's main thread, pass unthrottled `timers`.
438
+ - **Wire protocol.** Frames `m` (message), `c` (close), `p`/`q` (ping/pong) and
439
+ `a` (bytes consumed), version `WS_PORT_PROTOCOL_VERSION`; see
440
+ `src/ws/frames.ts`.
441
+
242
442
  ### Same-origin mode: a page that is not controlled
243
443
 
244
444
  ```ts
@@ -285,22 +485,30 @@ callHttpService(req, {key, port}) ──port──► relay iframe ──► rel
285
485
  Same-origin mode:
286
486
 
287
487
  ```
288
- page fetch(<scope>/KEY/...) ──► same-origin worker ── first path segment = KEY ──► page's port
289
- └► SwHttpAdapter
290
- (first registered prefix that matches)
488
+ page fetch(<scope>/KEY/...) ──► same-origin worker ── KEY = first path segment
489
+ route(request, KEY, handlers) ──down──► page handler (SwHttpAdapter.register)
490
+ ──down──► worker handler (serveHttpInWorker)
491
+ worker service.fetch(url) ──up──► same-origin worker ── same routing
492
+ new WebSocket(...) ──WS_CONNECT──► same-origin worker ──ctl──► handler ── port ──► caller
291
493
  ```
292
494
 
293
- Each call opens its own `MessageChannel`. The request is turned into a
294
- `SerializedHttpEnvelope` by `newHttpClientStub` and rebuilt on the other side
295
- by `newHttpServerStub` (both from `@statewalker/webrun-http-streams`), with the
296
- body streamed as chunks over the port.
495
+ Every link is one `MessagePort` (`down`: requests a handler serves; `up`:
496
+ requests a worker sends; `ctl`: socket hand-over). Calls on a link are
497
+ multiplexed by `@statewalker/webrun-rpc` (`connect` / `serve` over `overPipe`),
498
+ each a stream with a one-chunk window in both directions, and HTTP runs over
499
+ them with `fetchOverDuplex` / `serveFetchOverDuplex` from
500
+ `@statewalker/webrun-http-streams`. The relay uses the same links, one per
501
+ `CONNECT`. Every hand-shake carries `HTTP_LINK_PROTOCOL`: a peer of version 1
502
+ (this package's own stream transport) is still served over that transport, and
503
+ a page whose worker runs an older version gets a `ServiceWorkerControlError`
504
+ with `reason: "incompatible"`.
297
505
 
298
506
  ### Every bundle is self-contained
299
507
 
300
508
  This package does not externalise its dependencies. Its own HTML loads
301
509
  `../dist/index.js` from a static host with no import map, and the two IIFE
302
510
  workers are loaded with `importScripts`, which cannot resolve a bare
303
- specifier. So each of the five outputs is one file with `idb-keyval`,
511
+ specifier. So each of the six outputs is one file with `idb-keyval`,
304
512
  `@statewalker/webrun-streams` and `@statewalker/webrun-http-streams` inlined,
305
513
  built from its own rolldown config so no chunks are shared. The cost is
306
514
  duplicated code across bundles, and that `instanceof` does not hold across this
@@ -380,19 +588,24 @@ When the relay worker cannot hand a request to a service, it answers with JSON
380
588
 
381
589
  ### Same-origin routing
382
590
 
383
- The worker takes the first path segment under its scope as the key and forwards
384
- the request to the page that registered it. A key that was registered at some
385
- point but has no live page answers `404 Not Found (no active handler)` rather
591
+ The worker takes the first path segment under its scope as the key and routes
592
+ the request to one of the key's handlers. A key that was served at some point
593
+ but has no live handler answers `404 Not Found (no active handler)` rather
386
594
  than falling through to the network: a dev server's SPA fallback would
387
- otherwise serve the app shell for every site URL. Any other URL is passed
388
- through with `fetch(event.request)` unchanged; rebuilding the request would
389
- drop `mode`, and the constructor throws for `only-if-cached` navigations. On the
390
- page, a request matching no registered prefix gets `404 Error 404: Not found`.
391
-
392
- The worker keeps the claimed keys and client ids in IndexedDB (`claimedKeys`,
393
- `clientIds`) and asks each known client for a fresh port when it restarts. The
394
- relay keeps its registry under `clientsIds` as `{ clientId, path }` entries and
395
- still reads the older bare-client-id shape.
595
+ otherwise serve the app shell for every site URL. (After a restart, the first
596
+ such request first asks the pages to link again; see above.) A URL outside the
597
+ scope, or of a key never served, is not answered at all: the worker does not
598
+ call `respondWith`, so the browser performs the request as without a worker.
599
+ Navigations under the scope are the exception: they are passed through with
600
+ `fetch(request)`, because Firefox can leave a page uncontrolled when its worker
601
+ did not answer the navigation. On
602
+ the page, a request matching no registered prefix gets
603
+ `404 Error 404: Not found`.
604
+
605
+ The worker keeps only the served keys in IndexedDB (`claimedKeys`); the
606
+ handlers are attached again after a restart. The relay keeps its registry
607
+ under `clientsIds` as `{ clientId, path }` entries and still reads the older
608
+ bare-client-id shape.
396
609
 
397
610
  `SwPortHandler.stop()` unregisters only the registration its own `start()`
398
611
  made; other workers on the origin stay registered. After a `start()` that
@@ -427,20 +640,31 @@ answered by the worker and can load uncontrolled in Firefox; call
427
640
  (`ServiceWorker "<url>" (scope <scope>) did not activate within <n> ms …`);
428
641
  - `uncontrolled`: active, but the page is not controlled;
429
642
  - `unresponsive`: controls the page but did not answer the adapter's
430
- `UPDATE_COMMUNICATION_PORT` handshake in time.
431
-
432
- ### The port transport has no backpressure
643
+ `UPDATE_COMMUNICATION_PORT` handshake in time;
644
+ - `incompatible`: answered the handshake but runs an older version of this
645
+ package (reload the page once the updated worker is active).
646
+
647
+ ### Backpressure, and where the browser buffers
648
+
649
+ Both modes move requests over webrun-rpc links: a handler producing a body is
650
+ never more than a few chunks ahead of whoever reads it through this package's
651
+ links, and a reader that stops (an aborted `fetch`, a cancelled body) releases
652
+ the producer. Between a ServiceWorker's `respondWith` and the page, though,
653
+ Chromium buffers by itself, tens of megabytes of a fast stream: a page reading
654
+ slowly sees the handler run that far ahead before backpressure reaches it
655
+ (about 55 MiB, the same for a plain ServiceWorker with no library). Firefox
656
+ sets no such bound: it keeps reading a ServiceWorker's response while the page
657
+ does not, so there a handler streaming to a page that stopped reading keeps
658
+ producing (this package still holds only a few chunks of it). A worker
659
+ reading through `service.fetch` has no such buffer. `links.browser.ts` pins
660
+ the split: once a page stops reading, the handler is never more than a few
661
+ chunks ahead of what the ServiceWorker handed to Chromium, and stops.
433
662
 
434
663
  `sendStream` / `handleStreams`, and the `sendHttpRequest` /
435
- `handleHttpRequests` pair built on them, discard the promise their chunk
436
- sender returns, so a fast producer over a slow consumer accumulates without
437
- bound. There is also no per-stream timeout and no chunking to a transport's
438
- message size limit. `sendHttpRequest` and `handleHttpRequests` are marked
439
- `@deprecated` in favour of `duplexOverPort` / `serveDuplexOverPort` from
440
- `@statewalker/webrun-rpc` with `httpFetch` / `httpServe` from
441
- `@statewalker/webrun-http-streams`, which do have backpressure. Both modes of
442
- this package still use the deprecated pair internally. A caller that stops
443
- reading early does tell the peer to stop producing.
664
+ `handleHttpRequests` pair built on them, are version 1's transport, kept for
665
+ peers that still speak it. They have no backpressure (a fast producer over a
666
+ slow consumer accumulates without bound), no per-stream timeout and no
667
+ chunking, and are `@deprecated`.
444
668
 
445
669
  ### The adapter's default worker url follows the scope
446
670
 
@@ -464,6 +688,11 @@ computed from the other.
464
688
  relay origin cannot use it.
465
689
  - **Plain `fetch()` works only from pages under the worker's scope.** A caller
466
690
  on another origin uses `callHttpService`.
691
+ - **`Set-Cookie` does not reach a page through a ServiceWorker.** A
692
+ browser-built `Response` drops it; carry state another way.
693
+ - **Liveness needs Web Locks** (`navigator.locks`, in every current browser).
694
+ Without it, handler deaths are noticed late and ServiceWorker deaths not at
695
+ all.
467
696
 
468
697
  ### Exports of the root entry
469
698
 
@@ -472,12 +701,14 @@ Relay, page side:
472
701
  | Export | Purpose |
473
702
  | --- | --- |
474
703
  | `newRemoteRelayChannel(opts?)` / `RemoteRelayChannel`, `RemoteRelayChannelOptions` | Embed the relay iframe; resolves `{ baseUrl, port, close() }`. |
475
- | `initHttpService(handler, { key, path?, port })` | Register a service; resolves a cleanup that unregisters it. |
704
+ | `initHttpService(handler, { key, path?, port, streamTimeout? })` | Register a service (HTTP and sockets); resolves a function that unregisters it, whose promise settles once the relay acknowledged. |
476
705
  | `callHttpService(request, { key, port })` | Call a service through the port. |
477
- | `ServiceOptions` | `{ key, path?, port }`. |
706
+ | `callWebSocketService(url, protocols, { key, port })` / `relayWebSocketClass({ key, port }, options?)` | Open a socket to a service through the port. |
707
+ | `answerConnect(handler, data, port)`, `sendOverConnection(port, reply, request)` | The two ends of one `CONNECT`, for a host with its own registration. |
708
+ | `ServiceOptions`, `ConnectReply`, `WebSocketConnectParams` | `{ key, path?, port, streamTimeout? }`, and the `CONNECT` shapes. |
478
709
  | `getRelayWindowMessageHandler(opts?)` / `RelayWindowHandlerOptions` | The relay iframe's `window.onmessage`; options `swUrl`, `scopeUrl`, `timeout`. |
479
710
  | `initializeConnection(opts)` / `InitializeConnectionOptions` | Send `CONNECT` for a key; resolves a `MessagePort`, or `null` if refused. Extra fields ride in the payload. |
480
- | `registerConnectionsHandler(opts)` / `RegisterConnectionsHandlerOptions` | Register a key (and optional `path`) and answer its `CONNECT`s; resolves a cleanup. |
711
+ | `registerConnectionsHandler(opts)` / `RegisterConnectionsHandlerOptions` | Register a key (and optional `path`) and answer its `CONNECT`s; resolves an awaitable cleanup. |
481
712
  | `splitServiceUrl(url, separator = "~")` / `SplitServiceUrl` | Parse `<origin>/~<key>/<path>` into `{ url, key, baseUrl, path }`. |
482
713
 
483
714
  ServiceWorker lifecycle:
@@ -504,6 +735,21 @@ Messaging primitives:
504
735
  | `newRegistry(onError?)` / `Registry`, `NewRegistryResult`, `CleanupAction` | Cleanup registry. |
505
736
  | `MessageTarget`, `MessageSource`, `MessageSink`, `MessageListener` | Port view types, re-exported from `@statewalker/webrun-rpc`. |
506
737
 
738
+ Same-origin, links and WebSockets (also on `./sw`):
739
+
740
+ | Export | Purpose |
741
+ | --- | --- |
742
+ | `SwHttpAdapter`, `SwHttpAdapterOptions`, `SwHttpRegistration`, `AttachWorkerOptions`, `WorkerLink` | The page side. |
743
+ | `SwHttpDispatcher`, `startHttpDispatcher`, `SwHttpDispatcherOptions` | The ServiceWorker side. |
744
+ | `HandlerTable`, `HandlerInfo`, `RouteFunction`, `leastInflightRoute` | Routing. |
745
+ | `serveHttpInWorker`, `WorkerHttpService`, `HandlerEndpoint` | The handler side (also on `./worker`). |
746
+ | `connectHttpLink`, `serveHttpLink`, `trackedLink`, `HttpLink`, `LinkLostError`, `HTTP_LINK_PROTOCOL` | HTTP over one `MessagePort`. |
747
+ | `holdLock`, `watchLock`, `newLockName`, `locksAvailable`, `PortRendezvous` | Liveness and reply-matching helpers. |
748
+ | `createWebSocketClass`, `PortWebSocket`, `WebSocketConnector`, `WebSocketConnection` | The client. |
749
+ | `upgradeWebSocket`, `ServerWebSocket`, `takePort`, `takeUpgrade`, `serveWebSocketUpgrade`, `newWebSocketUpgradeRequest`, `isWebSocketUpgrade`, `requestedProtocols`, `withHeaders` | The server end and the upgrade. |
750
+ | `serviceWorkerWebSocketClass`, `connectWebSocketThroughServiceWorker` | Sockets from any page a ServiceWorker of this package controls. |
751
+ | `SwPortHandler`, `SwPortDispatcher` | Version 1's bases, kept for subclasses. |
752
+
507
753
  The root entry also re-exports all of `@statewalker/webrun-streams` and
508
754
  `@statewalker/webrun-http-streams`.
509
755
 
@@ -511,10 +757,12 @@ The root entry also re-exports all of `@statewalker/webrun-streams` and
511
757
 
512
758
  All four runtime dependencies are bundled into `dist/`:
513
759
 
514
- - `@statewalker/webrun-http-streams`: the client/server stubs the port
515
- transport is built on, and `HttpError`.
760
+ - `@statewalker/webrun-rpc`: the link transport (`connect` / `serve` over
761
+ `overPipe`), and the `MessageTarget` types.
762
+ - `@statewalker/webrun-http-streams`: HTTP over those streams
763
+ (`fetchOverDuplex` / `serveFetchOverDuplex`), version 1's stubs, and
764
+ `HttpError`.
516
765
  - `@statewalker/webrun-streams`: iterator helpers and error serialisation.
517
- - `@statewalker/webrun-rpc`: only the `MessageTarget` types.
518
766
  - `idb-keyval`: a small IndexedDB key-value store that keeps registrations
519
767
  across worker restarts.
520
768
 
@@ -524,7 +772,9 @@ All four runtime dependencies are bundled into `dist/`:
524
772
  - `pnpm run test:browser`: builds, then runs `tests/browser/` in Chromium and
525
773
  Firefox through Playwright against the built bundles, plus
526
774
  `tests/packaging/`, which checks the published exports map. Needs the
527
- browsers: `pnpm exec playwright install chromium firefox`.
775
+ browsers: `pnpm exec playwright install chromium firefox`. `links.browser.ts`
776
+ (workers, routing, restarts, sockets) and `relay-links.browser.ts` run in
777
+ Chromium only: they stop the ServiceWorker through CDP and use two origins.
528
778
 
529
779
  ## License
530
780
 
@@ -1,5 +1,22 @@
1
1
  import type { MessageSink, MessageSource } from "./message-target.js";
2
2
  export type ChannelCallHandler = (event: MessageEvent, params: unknown, ...transfers: MessagePort[]) => unknown;
3
+ /**
4
+ * A channel call's result that carries transferables, a `MessagePort` say:
5
+ * return `withTransfer({ port }, [port])` from a `handleChannelCalls` handler
6
+ * and `callChannel` resolves with `{ port }`.
7
+ */
8
+ export declare function withTransfer<T>(value: T, transfer: Transferable[]): T;
9
+ /** Splits a handler's result into the value to post and its transfer list. */
10
+ export declare function unwrapTransfer(result: unknown): {
11
+ value: unknown;
12
+ transfer: Transferable[];
13
+ };
3
14
  export declare function callChannel<T = unknown>(target: MessageSink, callType: string, params: unknown, ...transfers: Transferable[]): Promise<T>;
4
15
  export declare function handleChannelCalls(target: MessageSource, callType: string, handler: ChannelCallHandler): () => void;
16
+ /**
17
+ * `callChannel`, failing after `ms` instead of waiting for ever: a peer that
18
+ * died (a ServiceWorker stopped mid-call) or that does not know `callType` (an
19
+ * older version) never answers.
20
+ */
21
+ export declare function callChannelWithin<T = unknown>(ms: number, target: MessageSink, callType: string, params: unknown, ...transfers: Transferable[]): Promise<T>;
5
22
  //# sourceMappingURL=data-calls.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"data-calls.d.ts","sourceRoot":"","sources":["../../src/core/data-calls.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEtE,MAAM,MAAM,kBAAkB,GAAG,CAC/B,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,OAAO,EACf,GAAG,SAAS,EAAE,WAAW,EAAE,KACxB,OAAO,CAAC;AAEb,wBAAgB,WAAW,CAAC,CAAC,GAAG,OAAO,EACrC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,EACf,GAAG,SAAS,EAAE,YAAY,EAAE,GAC3B,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,GAC1B,MAAM,IAAI,CAeZ"}
1
+ {"version":3,"file":"data-calls.d.ts","sourceRoot":"","sources":["../../src/core/data-calls.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEtE,MAAM,MAAM,kBAAkB,GAAG,CAC/B,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,OAAO,EACf,GAAG,SAAS,EAAE,WAAW,EAAE,KACxB,OAAO,CAAC;AAUb;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,CAAC,CAErE;AAMD,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,MAAM,EAAE,OAAO,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,YAAY,EAAE,CAAA;CAAE,CAI5F;AAED,wBAAgB,WAAW,CAAC,CAAC,GAAG,OAAO,EACrC,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,EACf,GAAG,SAAS,EAAE,YAAY,EAAE,GAC3B,OAAO,CAAC,CAAC,CAAC,CAUZ;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,GAC1B,MAAM,IAAI,CAiBZ;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,GAAG,OAAO,EAC3C,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,WAAW,EACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,OAAO,EACf,GAAG,SAAS,EAAE,YAAY,EAAE,GAC3B,OAAO,CAAC,CAAC,CAAC,CAMZ"}
@@ -11,8 +11,10 @@ export declare const CLAIM_CALL = "CLAIM";
11
11
  * - `uncontrolled`: the worker is active but the page is not controlled by it.
12
12
  * - `unresponsive`: the page is controlled, but the worker did not answer the
13
13
  * adapter's handshake in time.
14
+ * - `incompatible`: the worker answered, but runs another version of this
15
+ * package's link protocol.
14
16
  */
15
- export type ServiceWorkerControlFailure = "activation-timeout" | "uncontrolled" | "unresponsive";
17
+ export type ServiceWorkerControlFailure = "activation-timeout" | "uncontrolled" | "unresponsive" | "incompatible";
16
18
  /**
17
19
  * Why a page could not get a working ServiceWorker. `reason` says which wait
18
20
  * failed; check it (or `name`) rather than `instanceof`, because each of this
@@ -1 +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
+ {"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;;;;;;;GAOG;AACH,MAAM,MAAM,2BAA2B,GACnC,oBAAoB,GACpB,cAAc,GACd,cAAc,GACd,cAAc,CAAC;AAEnB;;;;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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  export * from "./core/index.js";
2
2
  export * from "./http/index.js";
3
+ export * from "./link/index.js";
3
4
  export * from "./relay/index.js";
5
+ export * from "./sw/index.js";
6
+ export * from "./ws/index.js";
4
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC"}