@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/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region ../webrun-streams/src/collect.ts
1
+ //#region ../webrun-streams/dist/index.js
2
2
  /** Collect all items from an async iterable into an array. */
3
3
  async function collect(input) {
4
4
  const items = [];
@@ -28,8 +28,17 @@ async function collectString(input) {
28
28
  for await (const chunk of input) result += chunk;
29
29
  return result;
30
30
  }
31
- //#endregion
32
- //#region ../webrun-streams/src/errors.ts
31
+ /**
32
+ * Thrown by `emulateMux` and adapters when the underlying transport closes
33
+ * while one or more `Duplex` calls are in flight. Consumers can catch by
34
+ * `instanceof TransportClosedError` or by checking `error.name`.
35
+ */
36
+ var TransportClosedError = class extends Error {
37
+ name = "TransportClosedError";
38
+ constructor(message = "transport closed") {
39
+ super(message);
40
+ }
41
+ };
33
42
  function serializeError(error) {
34
43
  if (error instanceof Error) {
35
44
  const out = {
@@ -53,19 +62,113 @@ function deserializeError(error) {
53
62
  const payload = typeof error === "string" ? { message: error } : error;
54
63
  return Object.assign(new Error(payload.message), payload);
55
64
  }
56
- //#endregion
57
- //#region ../webrun-streams/src/emulate-mux.ts
65
+ function newCreditLedger(initial = 0) {
66
+ let available = initial;
67
+ let failure;
68
+ const waiters = [];
69
+ const pump = () => {
70
+ while (waiters.length > 0 && available > 0) {
71
+ const next = waiters[0];
72
+ if (!next) return;
73
+ waiters.shift();
74
+ const granted = Math.min(next.upTo, available);
75
+ available -= granted;
76
+ next.resolve(granted);
77
+ }
78
+ };
79
+ return {
80
+ get available() {
81
+ return available;
82
+ },
83
+ reserve(upTo) {
84
+ if (failure) return Promise.reject(failure);
85
+ if (!(upTo >= 1)) return Promise.reject(/* @__PURE__ */ new RangeError(`newCreditLedger: reserve(${upTo}) — upTo must be at least 1`));
86
+ if (waiters.length === 0 && available > 0) {
87
+ const granted = Math.min(upTo, available);
88
+ available -= granted;
89
+ return Promise.resolve(granted);
90
+ }
91
+ return new Promise((resolve, reject) => {
92
+ waiters.push({
93
+ upTo,
94
+ resolve,
95
+ reject
96
+ });
97
+ });
98
+ },
99
+ grant(units) {
100
+ if (failure) return;
101
+ available += units;
102
+ pump();
103
+ },
104
+ fail(err) {
105
+ failure ??= err;
106
+ while (waiters.length > 0) waiters.shift()?.reject(err);
107
+ }
108
+ };
109
+ }
58
110
  /**
59
- * Thrown by `emulateMux` and adapters when the underlying transport closes
60
- * while one or more `Duplex` calls are in flight. Consumers can catch by
61
- * `instanceof TransportClosedError` or by checking `error.name`.
111
+ * Grants are batched: replenishing on every chunk while the receiver is
112
+ * behind would reinvent the per-frame ACK this change exists to remove.
113
+ * `threshold` is the fraction of the window that must drain before a grant is
114
+ * emitted.
115
+ *
116
+ * The batch is flushed unconditionally once the receive queue is empty, even
117
+ * below the threshold, so the receiver never sits on credit it owes. Note what
118
+ * this is and is not: paired with a {@link CreditLedger}, a sender blocks only
119
+ * at *exactly* zero credit, and at that point the receiver holds the entire
120
+ * window as `pending` — above any threshold at or below the whole window — so
121
+ * the threshold alone cannot deadlock that pairing. The flush is what keeps
122
+ * that from being an argument about a global accounting identity: it is
123
+ * locally decidable from one boolean, and it returns owed credit now rather
124
+ * than at the next threshold crossing. It costs an extra frame only when the
125
+ * consumer is keeping pace, which is exactly when the sender is not blocked
126
+ * and the frame is cheap.
62
127
  */
63
- var TransportClosedError = class extends Error {
64
- name = "TransportClosedError";
65
- constructor(message = "transport closed") {
66
- super(message);
67
- }
68
- };
128
+ function newCreditGrantor(window, threshold = .5) {
129
+ const trigger = Math.max(1, Math.floor(window * threshold));
130
+ let pending = 0;
131
+ return { consumed(units, queueEmpty) {
132
+ pending += units;
133
+ if (pending === 0) return 0;
134
+ if (pending < trigger && !queueEmpty) return 0;
135
+ const grant = pending;
136
+ pending = 0;
137
+ return grant;
138
+ } };
139
+ }
140
+ /**
141
+ * Credit payloads are a single big-endian uint32, matching the frame header's
142
+ * byte order.
143
+ *
144
+ * Deliberately **not** re-exported from `index.ts`: this is an internal codec
145
+ * for the `emulateMux` wire format, not a compatibility commitment. Tests
146
+ * import it by path.
147
+ */
148
+ /** The largest credit a single frame can advertise or grant: 2^32 - 1. */
149
+ const MAX_UINT32 = 4294967295;
150
+ /**
151
+ * Clamps rather than wraps. `n >>> 0` is the obvious spelling and it is wrong
152
+ * here: 2^32 becomes **0**, so a 4 GiB window advertises *zero credit* and the
153
+ * peer stalls forever with no error — the exact silent hang credit exists to
154
+ * remove. 2^32 + 5 becomes 5, which looks like a working window and is worse.
155
+ * Anything above the ceiling is advertised as the ceiling.
156
+ */
157
+ function encodeUint32(n) {
158
+ const bytes = /* @__PURE__ */ new Uint8Array(4);
159
+ const clamped = Number.isFinite(n) ? Math.min(MAX_UINT32, Math.max(0, Math.floor(n))) : 0;
160
+ new DataView(bytes.buffer).setUint32(0, clamped, false);
161
+ return bytes;
162
+ }
163
+ /**
164
+ * Returns `undefined` rather than a garbage number when the payload is too
165
+ * short, so a truncated frame — or one from a peer predating credit — is
166
+ * detectable at the call site instead of silently granting nonsense.
167
+ */
168
+ function decodeUint32(bytes) {
169
+ if (bytes.byteLength < 4) return void 0;
170
+ return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0, false);
171
+ }
69
172
  const TYPE_OPEN = 1;
70
173
  const TYPE_DATA = 2;
71
174
  const TYPE_ACK = 3;
@@ -81,6 +184,7 @@ function emulateMux(channel, opts = {}) {
81
184
  const maxStreams = opts.maxStreams ?? DEFAULT_MAX_STREAMS;
82
185
  const mtu = opts.mtu ?? DEFAULT_MTU;
83
186
  const maxStreamBuffer = opts.maxStreamBuffer ?? DEFAULT_MAX_STREAM_BUFFER;
187
+ if (!(maxStreamBuffer >= 1)) throw new RangeError(`emulateMux: maxStreamBuffer must be at least 1, got ${maxStreamBuffer}`);
84
188
  const side = opts.side ?? "initiator";
85
189
  const streams = /* @__PURE__ */ new Map();
86
190
  let nextLocalId = side === "initiator" ? 2 : 1;
@@ -101,13 +205,9 @@ function emulateMux(channel, opts = {}) {
101
205
  const teardownStream = (s, err) => {
102
206
  if (s.closed) return;
103
207
  s.closed = true;
104
- const resolve = s.resolveAck;
105
- const reject = s.rejectAck;
106
- s.resolveAck = null;
107
- s.rejectAck = null;
108
- if (reject && err) reject(err);
109
- else resolve?.();
208
+ s.outboundCredit.fail(err ?? new TransportClosedError("emulateMux: stream closed"));
110
209
  s.doneIn(err);
210
+ s.cancelInput?.();
111
211
  streams.delete(s.id);
112
212
  };
113
213
  /**
@@ -141,8 +241,8 @@ function emulateMux(channel, opts = {}) {
141
241
  }),
142
242
  pushIn: queue.push,
143
243
  doneIn: queue.done,
144
- resolveAck: null,
145
- rejectAck: null,
244
+ outboundCredit: newCreditLedger(0),
245
+ grantor: newCreditGrantor(maxStreamBuffer),
146
246
  closed: false,
147
247
  inDone: false,
148
248
  outDone: false,
@@ -151,20 +251,24 @@ function emulateMux(channel, opts = {}) {
151
251
  return state;
152
252
  };
153
253
  const pumpOutbound = async (s, input) => {
254
+ const iterator = input[Symbol.asyncIterator]?.() ?? input[Symbol.iterator]();
255
+ s.cancelInput = () => {
256
+ iterator.return?.(void 0);
257
+ };
154
258
  try {
155
- for await (const chunk of input) {
259
+ for (;;) {
260
+ const next = await iterator.next();
261
+ if (next.done === true) break;
262
+ const chunk = next.value;
156
263
  if (s.closed || muxClosed) return;
157
264
  if (chunk.byteLength === 0) continue;
158
265
  let off = 0;
159
266
  while (off < chunk.byteLength) {
160
267
  if (s.closed || muxClosed) return;
161
- const end = Math.min(off + mtu, chunk.byteLength);
162
- const piece = chunk.subarray(off, end);
163
- sendFrame(s.id, TYPE_DATA, piece);
164
- await new Promise((resolve, reject) => {
165
- s.resolveAck = resolve;
166
- s.rejectAck = reject;
167
- });
268
+ const take = await s.outboundCredit.reserve(Math.min(mtu, chunk.byteLength - off));
269
+ if (s.closed || muxClosed) return;
270
+ const end = off + take;
271
+ sendFrame(s.id, TYPE_DATA, chunk.subarray(off, end));
168
272
  off = end;
169
273
  }
170
274
  }
@@ -222,6 +326,9 @@ function emulateMux(channel, opts = {}) {
222
326
  }
223
327
  const s = createStream(id);
224
328
  streams.set(id, s);
329
+ const advertised = decodeUint32(payload);
330
+ if (advertised !== void 0) s.outboundCredit.grant(advertised);
331
+ sendFrame(id, TYPE_ACK, encodeUint32(maxStreamBuffer));
225
332
  runHandler(s, handler);
226
333
  return;
227
334
  }
@@ -239,15 +346,15 @@ function emulateMux(channel, opts = {}) {
239
346
  const copy = payload.byteLength === 0 ? payload : new Uint8Array(payload);
240
347
  s.pushIn(copy).then((handled) => {
241
348
  s.queuedBytes -= copy.byteLength;
242
- if (handled && !s.closed && !muxClosed) sendFrame(id, TYPE_ACK);
349
+ if (!handled || s.closed || muxClosed) return;
350
+ const grant = s.grantor.consumed(copy.byteLength, s.queuedBytes === 0);
351
+ if (grant > 0) sendFrame(id, TYPE_ACK, encodeUint32(grant));
243
352
  });
244
353
  return;
245
354
  }
246
355
  case TYPE_ACK: {
247
- const r = s.resolveAck;
248
- s.resolveAck = null;
249
- s.rejectAck = null;
250
- r?.();
356
+ const granted = decodeUint32(payload);
357
+ if (granted !== void 0) s.outboundCredit.grant(granted);
251
358
  return;
252
359
  }
253
360
  case TYPE_END:
@@ -284,7 +391,7 @@ function emulateMux(channel, opts = {}) {
284
391
  nextLocalId += 2;
285
392
  const s = createStream(id);
286
393
  streams.set(id, s);
287
- sendFrame(id, TYPE_OPEN);
394
+ sendFrame(id, TYPE_OPEN, encodeUint32(maxStreamBuffer));
288
395
  pumpOutbound(s, input);
289
396
  return s.inbound;
290
397
  };
@@ -424,8 +531,6 @@ function decodeError(buf) {
424
531
  return new Error(textDecoder.decode(buf));
425
532
  }
426
533
  }
427
- //#endregion
428
- //#region ../webrun-streams/src/lines.ts
429
534
  /** Split a stream of string chunks into individual lines (delimited by \n). */
430
535
  async function* splitLines(input) {
431
536
  let buffer = "";
@@ -444,14 +549,10 @@ async function* splitLines(input) {
444
549
  async function* joinLines(input) {
445
550
  for await (const line of input) yield `${line}\n`;
446
551
  }
447
- //#endregion
448
- //#region ../webrun-streams/src/map.ts
449
552
  /** Apply a sync or async function to each item in a stream. */
450
553
  async function* map(input, fn) {
451
554
  for await (const item of input) yield await fn(item);
452
555
  }
453
- //#endregion
454
- //#region ../webrun-streams/src/jsonl.ts
455
556
  /** Encode each value as a JSON line (terminated by \n). */
456
557
  async function* encodeJsonl(input) {
457
558
  yield* map(input, (item) => `${JSON.stringify(item)}\n`);
@@ -463,8 +564,6 @@ async function* decodeJsonl(input) {
463
564
  if (trimmed) yield JSON.parse(trimmed);
464
565
  }
465
566
  }
466
- //#endregion
467
- //#region ../webrun-streams/src/new-async-generator.ts
468
567
  /**
469
568
  * The newAsyncGenerator function creates async generators from callback-based initialization
470
569
  * functions, providing a bridge between imperative event handling and declarative async
@@ -617,8 +716,6 @@ async function* newAsyncGenerator(init, skipValues = false) {
617
716
  drainQueue();
618
717
  }
619
718
  }
620
- //#endregion
621
- //#region ../webrun-streams/src/normalize.ts
622
719
  const utf8 = new TextEncoder();
623
720
  /**
624
721
  * Coerce common byte-like inputs into a `Uint8Array`. Strings encode as UTF-8.
@@ -639,36 +736,65 @@ function describe(value) {
639
736
  if (t !== "object") return t;
640
737
  return value.constructor?.name ?? "object";
641
738
  }
642
- //#endregion
643
- //#region ../webrun-streams/src/readable-streams.ts
739
+ /**
740
+ * The iterator ↔ `ReadableStream` boundary.
741
+ *
742
+ * Both adapters must carry CANCELLATION, not just data: a response body leaves
743
+ * a handler as a `ReadableStream`, crosses a transport as an iterator, and
744
+ * becomes a `ReadableStream` again at the caller — so when the caller walks
745
+ * away, the only path back to the handler's producer runs through both of
746
+ * these functions. Teardown that stops at an adapter leaves a producer running
747
+ * for ever.
748
+ */
644
749
  function toReadableStream(it) {
645
- return new ReadableStream({ async pull(controller) {
646
- let handled = false;
647
- try {
648
- while (true) {
750
+ return new ReadableStream({
751
+ /**
752
+ * One chunk per pull. An earlier version drained the whole iterator inside
753
+ * a single `pull`, which defeated the stream's own backpressure (every
754
+ * chunk was enqueued as fast as the producer could make them, however slow
755
+ * the reader was) and left no point between chunks at which a cancellation
756
+ * could take effect.
757
+ */
758
+ async pull(controller) {
759
+ try {
649
760
  const slot = await it.next();
650
- if (!slot || slot.done) break;
651
- const value = await slot.value;
652
- controller.enqueue(value);
761
+ if (!slot || slot.done) {
762
+ controller.close();
763
+ return;
764
+ }
765
+ controller.enqueue(await slot.value);
766
+ } catch (error) {
767
+ controller.error(error);
653
768
  }
654
- } catch (error) {
655
- handled = true;
656
- controller.error(error);
657
- } finally {
658
- if (!handled) controller.close();
769
+ },
770
+ /**
771
+ * Release the source. NOT awaited: `.return()` on an async generator that
772
+ * is parked awaiting its own source is queued behind that pending
773
+ * `next()`, so awaiting it here would hang `reader.cancel()` on exactly
774
+ * the producers that most need cancelling.
775
+ */
776
+ cancel(reason) {
777
+ Promise.resolve(it.return?.(reason)).catch(() => {});
659
778
  }
660
- } });
779
+ });
661
780
  }
662
781
  async function* fromReadableStream(stream) {
663
782
  const reader = stream.getReader();
664
- while (true) {
665
- const { done, value } = await reader.read();
666
- if (done) break;
667
- if (value !== void 0) yield value;
783
+ let drained = false;
784
+ try {
785
+ while (true) {
786
+ const { done, value } = await reader.read();
787
+ if (done) {
788
+ drained = true;
789
+ break;
790
+ }
791
+ if (value !== void 0) yield value;
792
+ }
793
+ } finally {
794
+ if (drained) reader.releaseLock();
795
+ else await reader.cancel().catch(() => {});
668
796
  }
669
797
  }
670
- //#endregion
671
- //#region ../webrun-streams/src/recieve-iterator.ts
672
798
  /**
673
799
  * Inverse of {@link sendIterator}: turns a sequence of `{done, value, error}`
674
800
  * chunks (delivered to the supplied callback by `installer`) into an async
@@ -691,8 +817,6 @@ function recieveIterator(installer) {
691
817
  };
692
818
  });
693
819
  }
694
- //#endregion
695
- //#region ../webrun-streams/src/send-iterator.ts
696
820
  /**
697
821
  * Drain an async iterator into a sink that consumes one chunk at a time.
698
822
  *
@@ -716,8 +840,6 @@ async function sendIterator(send, it) {
716
840
  });
717
841
  }
718
842
  }
719
- //#endregion
720
- //#region ../webrun-streams/src/text.ts
721
843
  /** Decode Uint8Array chunks to string chunks via TextDecoder, handling split multi-byte characters. */
722
844
  async function* decodeText(input) {
723
845
  const decoder = new TextDecoder("utf-8", { fatal: false });
@@ -733,8 +855,6 @@ async function* encodeText(input) {
733
855
  const encoder = new TextEncoder();
734
856
  for await (const str of input) yield encoder.encode(str);
735
857
  }
736
- //#endregion
737
- //#region ../webrun-streams/src/to-chunks.ts
738
858
  const DEFAULT_CHUNK_SIZE = 16384;
739
859
  /**
740
860
  * Curried `Duplex`-shaped transformer that splits incoming `Uint8Array`s into
@@ -877,11 +997,14 @@ async function* sendStream(communicationPort, input, params = {}) {
877
997
  params
878
998
  }, [messageChannel.port2]);
879
999
  const channel = newStreamChannel(messageChannel.port1);
1000
+ let drained = false;
880
1001
  try {
881
1002
  await channel.start();
882
1003
  channel.sendAll(input);
883
1004
  yield* channel.recieveAll();
1005
+ drained = true;
884
1006
  } finally {
1007
+ if (!drained) channel.cancel();
885
1008
  await channel.close();
886
1009
  }
887
1010
  }
@@ -909,11 +1032,33 @@ function newStreamChannel(port) {
909
1032
  const notifyAll = async (data) => {
910
1033
  for (const listener of listeners) await listener(data);
911
1034
  };
1035
+ /**
1036
+ * Release whatever we are sending. NOT awaited: `.return()` on an async
1037
+ * generator parked awaiting its own source is queued behind that pending
1038
+ * `next()`, so awaiting it here would block the message handler — and the
1039
+ * chunk that would unblock it can only arrive through that same handler.
1040
+ */
1041
+ const cancelOutgoing = () => {
1042
+ for (const it of [...iterators]) {
1043
+ const iterable = it;
1044
+ Promise.resolve(iterable.return?.()).catch(() => {});
1045
+ }
1046
+ };
912
1047
  const channel = newInvokationChannel({
913
1048
  port,
914
- handler: (data) => notifyAll(data)
1049
+ handler: (data) => {
1050
+ const message = data;
1051
+ if (message?.cancel) {
1052
+ cancelOutgoing();
1053
+ return;
1054
+ }
1055
+ return notifyAll(message);
1056
+ }
915
1057
  });
916
1058
  const start = () => channel.start();
1059
+ const cancel = () => {
1060
+ channel.invoke({ cancel: true }).catch(() => {});
1061
+ };
917
1062
  const close = async () => {
918
1063
  await notifyAll({ done: true });
919
1064
  for (const it of [...iterators]) await it.return?.();
@@ -940,6 +1085,7 @@ function newStreamChannel(port) {
940
1085
  return {
941
1086
  start,
942
1087
  close,
1088
+ cancel,
943
1089
  recieveAll,
944
1090
  sendAll
945
1091
  };
@@ -979,7 +1125,193 @@ function newRegistry(onError = console.error) {
979
1125
  });
980
1126
  }
981
1127
  //#endregion
982
- //#region ../webrun-http-streams/src/bytes.ts
1128
+ //#region src/core/deadline.ts
1129
+ /**
1130
+ * Settles like `promise` if it settles before `deadline` (a `Date.now()`
1131
+ * value). Otherwise calls `onTimeout`: an `Error` it returns rejects, any
1132
+ * other value resolves. Internal; not re-exported.
1133
+ */
1134
+ function withDeadline(deadline, promise, onTimeout) {
1135
+ return new Promise((resolve, reject) => {
1136
+ const timer = setTimeout(() => {
1137
+ const outcome = onTimeout();
1138
+ if (outcome instanceof Error) reject(outcome);
1139
+ else resolve(outcome);
1140
+ }, Math.max(0, deadline - Date.now()));
1141
+ promise.then((value) => {
1142
+ clearTimeout(timer);
1143
+ resolve(value);
1144
+ }, (error) => {
1145
+ clearTimeout(timer);
1146
+ reject(error);
1147
+ });
1148
+ });
1149
+ }
1150
+ //#endregion
1151
+ //#region src/core/service-worker-control.ts
1152
+ /**
1153
+ * How long, by default, the page waits for its ServiceWorker to activate and
1154
+ * to take control before giving up. Generous: a first install downloads and
1155
+ * evaluates the worker script, which on a slow link takes seconds.
1156
+ */
1157
+ const DEFAULT_SERVICE_WORKER_TIMEOUT = 3e4;
1158
+ /**
1159
+ * How long the page waits for `controllerchange` once the worker has
1160
+ * answered the `CLAIM` request. `clients.claim()` resolves only after the
1161
+ * browser has queued that event, so this is a grace period for delivery, not
1162
+ * a second budget.
1163
+ */
1164
+ const CLAIM_GRACE_MS = 1e3;
1165
+ /** Channel call a page sends to ask its ServiceWorker to `clients.claim()` it. */
1166
+ const CLAIM_CALL = "CLAIM";
1167
+ /**
1168
+ * Why a page could not get a working ServiceWorker. `reason` says which wait
1169
+ * failed; check it (or `name`) rather than `instanceof`, because each of this
1170
+ * package's bundles carries its own copy of this class.
1171
+ */
1172
+ var ServiceWorkerControlError = class extends Error {
1173
+ reason;
1174
+ constructor(reason, message) {
1175
+ super(message);
1176
+ this.name = "ServiceWorkerControlError";
1177
+ this.reason = reason;
1178
+ }
1179
+ };
1180
+ /**
1181
+ * Resolves with the registration's worker once it is `activated`. Rejects
1182
+ * with a `ServiceWorkerControlError` (`reason: "activation-timeout"`) if that
1183
+ * takes longer than `timeout` — an install that throws or never finishes
1184
+ * would otherwise leave the caller waiting forever.
1185
+ */
1186
+ async function awaitActiveServiceWorker(registration, { timeout = DEFAULT_SERVICE_WORKER_TIMEOUT } = {}) {
1187
+ return await withDeadline(Date.now() + timeout, waitForActivated(registration), () => {
1188
+ const worker = registration.installing ?? registration.waiting ?? registration.active;
1189
+ return new ServiceWorkerControlError("activation-timeout", `ServiceWorker ${scriptUrl(worker)} (scope ${registration.scope}) did not activate within ${timeout} ms` + (worker ? `; it is "${worker.state}"` : "") + ". Check the worker script for errors during install (DevTools → Application → Service Workers), or raise the `timeout` option.");
1190
+ });
1191
+ }
1192
+ /**
1193
+ * Resolves with the ServiceWorker that controls this page, once `registration`
1194
+ * has an activated worker and the page is controlled by it.
1195
+ *
1196
+ * A page can stay uncontrolled while its worker is active, and then no
1197
+ * `controllerchange` ever fires on its own:
1198
+ * - a hard reload (Ctrl+Shift+R) bypasses the worker for that load, and the
1199
+ * worker's `clients.claim()` already ran when it activated;
1200
+ * - Firefox can leave a page loaded while the worker is running uncontrolled.
1201
+ *
1202
+ * So when the page is uncontrolled, this asks the active worker to claim it
1203
+ * again (a `CLAIM` channel call — this package's workers answer it) and waits
1204
+ * for `controllerchange`. Every wait is bounded by `timeout`. If control never
1205
+ * comes it reloads once (`reloadIfUncontrolled`) or rejects with a
1206
+ * `ServiceWorkerControlError` (`reason: "uncontrolled"`) that says what
1207
+ * happened and what to do.
1208
+ */
1209
+ async function awaitServiceWorkerControl(registration, { timeout = DEFAULT_SERVICE_WORKER_TIMEOUT, reloadIfUncontrolled = false, container = navigator.serviceWorker } = {}) {
1210
+ const deadline = Date.now() + timeout;
1211
+ const controlled = waitForController(container);
1212
+ try {
1213
+ const active = await awaitActiveServiceWorker(registration, { timeout });
1214
+ let controller = container.controller;
1215
+ if (!controller) controller = await withDeadline(deadline, (async () => {
1216
+ await Promise.race([callChannel(active, CLAIM_CALL, {}), controlled.promise]);
1217
+ return await Promise.race([controlled.promise, delay(CLAIM_GRACE_MS, null)]);
1218
+ })(), () => null);
1219
+ if (controller) {
1220
+ forgetReload(registration);
1221
+ return controller;
1222
+ }
1223
+ if (reloadIfUncontrolled && markReload(registration)) {
1224
+ location.reload();
1225
+ return await new Promise(() => {});
1226
+ }
1227
+ forgetReload(registration);
1228
+ throw new ServiceWorkerControlError("uncontrolled", `This page is not controlled by its ServiceWorker ${scriptUrl(active)} (scope ${registration.scope}), although the worker is active, and the worker did not take control when asked (clients.claim()) within ${timeout} ms. This happens after a hard reload (Ctrl+Shift+R / Cmd+Shift+R), which bypasses ServiceWorkers for that load, and in Firefox for some pages opened while the worker was already running. Requests from this page would not reach the worker. Reload the page normally, pass \`reloadIfUncontrolled: true\` to do that automatically, check that the page is inside the worker's scope, and that the worker answers the "${CLAIM_CALL}" request (this package's workers do).`);
1229
+ } finally {
1230
+ controlled.cancel();
1231
+ }
1232
+ }
1233
+ /**
1234
+ * ServiceWorker side: answers the page's `CLAIM` request with
1235
+ * `clients.claim()`, which takes over every uncontrolled client in scope.
1236
+ * Returns a function that stops answering.
1237
+ */
1238
+ function handleClaimRequests(self) {
1239
+ return handleChannelCalls(self, CLAIM_CALL, (event) => {
1240
+ const claimed = self.clients.claim().then(() => true);
1241
+ event.waitUntil?.(claimed);
1242
+ return claimed;
1243
+ });
1244
+ }
1245
+ function waitForActivated(registration) {
1246
+ return new Promise((resolve) => {
1247
+ const watched = /* @__PURE__ */ new Set();
1248
+ const check = () => {
1249
+ const active = registration.active;
1250
+ if (active?.state === "activated") {
1251
+ registration.removeEventListener("updatefound", watch);
1252
+ for (const worker of watched) worker.removeEventListener("statechange", check);
1253
+ resolve(active);
1254
+ return;
1255
+ }
1256
+ watch();
1257
+ };
1258
+ function watch() {
1259
+ for (const worker of [
1260
+ registration.installing,
1261
+ registration.waiting,
1262
+ registration.active
1263
+ ]) {
1264
+ if (!worker || watched.has(worker)) continue;
1265
+ watched.add(worker);
1266
+ worker.addEventListener("statechange", check);
1267
+ }
1268
+ }
1269
+ registration.addEventListener("updatefound", watch);
1270
+ check();
1271
+ });
1272
+ }
1273
+ function waitForController(container) {
1274
+ let cancel = () => {};
1275
+ return {
1276
+ promise: new Promise((resolve) => {
1277
+ const onChange = () => {
1278
+ if (!container.controller) return;
1279
+ cancel();
1280
+ resolve(container.controller);
1281
+ };
1282
+ cancel = () => container.removeEventListener("controllerchange", onChange);
1283
+ container.addEventListener("controllerchange", onChange);
1284
+ }),
1285
+ cancel
1286
+ };
1287
+ }
1288
+ function delay(ms, value) {
1289
+ return new Promise((resolve) => setTimeout(() => resolve(value), ms));
1290
+ }
1291
+ function scriptUrl(worker) {
1292
+ return worker?.scriptURL ? `"${worker.scriptURL}"` : "(no worker)";
1293
+ }
1294
+ function reloadKey(registration) {
1295
+ return `webrun-http-browser:reloaded-uncontrolled:${registration.scope}`;
1296
+ }
1297
+ /** Records the reload about to happen. `false` if one already happened, or if it cannot be recorded. */
1298
+ function markReload(registration) {
1299
+ try {
1300
+ const key = reloadKey(registration);
1301
+ if (sessionStorage.getItem(key)) return false;
1302
+ sessionStorage.setItem(key, "1");
1303
+ return true;
1304
+ } catch {
1305
+ return false;
1306
+ }
1307
+ }
1308
+ function forgetReload(registration) {
1309
+ try {
1310
+ sessionStorage.removeItem(reloadKey(registration));
1311
+ } catch {}
1312
+ }
1313
+ //#endregion
1314
+ //#region ../webrun-http-streams/dist/index.js
983
1315
  const CR = 13;
984
1316
  const LF = 10;
985
1317
  const EMPTY = /* @__PURE__ */ new Uint8Array(0);
@@ -1104,8 +1436,6 @@ var ByteReader = class {
1104
1436
  }
1105
1437
  }
1106
1438
  };
1107
- //#endregion
1108
- //#region ../webrun-http-streams/src/http1/errors.ts
1109
1439
  /**
1110
1440
  * Raised for any byte sequence this codec refuses to interpret. Every case is
1111
1441
  * a refusal to guess: HTTP/1.1 parsers that guess are how request smuggling
@@ -1114,8 +1444,6 @@ var ByteReader = class {
1114
1444
  var HttpParseError = class extends Error {
1115
1445
  name = "HttpParseError";
1116
1446
  };
1117
- //#endregion
1118
- //#region ../webrun-http-streams/src/envelope.ts
1119
1447
  const NEWLINE = 10;
1120
1448
  /** Mirrors the HTTP/1.1 codec's default `maxHeaderBytes`. */
1121
1449
  const MAX_ENVELOPE_BYTES = 65536;
@@ -1199,8 +1527,6 @@ const jsonEnvelopeCodec = {
1199
1527
  decodeRequest: (input) => decodeMessage(input),
1200
1528
  decodeResponse: (input) => decodeMessage(input)
1201
1529
  };
1202
- //#endregion
1203
- //#region ../webrun-http-streams/src/http1/headers.ts
1204
1530
  const TCHAR = /* @__PURE__ */ new Set("!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
1205
1531
  const SP = 32;
1206
1532
  const HTAB = 9;
@@ -1376,8 +1702,6 @@ function resolveFraming(headers, version) {
1376
1702
  };
1377
1703
  return { kind: "none" };
1378
1704
  }
1379
- //#endregion
1380
- //#region ../webrun-http-streams/src/http1/chunked.ts
1381
1705
  const CRLF = new Uint8Array([13, 10]);
1382
1706
  const LAST_CHUNK = new Uint8Array([
1383
1707
  48,
@@ -1444,8 +1768,6 @@ async function* decodeChunked(reader, maxLineBytes) {
1444
1768
  }
1445
1769
  }
1446
1770
  }
1447
- //#endregion
1448
- //#region ../webrun-http-streams/src/http1/decode.ts
1449
1771
  const VERSIONS = /* @__PURE__ */ new Set(["HTTP/1.1", "HTTP/1.0"]);
1450
1772
  const ABSOLUTE_FORM = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//;
1451
1773
  /**
@@ -1583,8 +1905,6 @@ async function decodeResponse(input, opts, method) {
1583
1905
  toHttpParseError(err);
1584
1906
  }
1585
1907
  }
1586
- //#endregion
1587
- //#region ../webrun-http-streams/src/http1/encode.ts
1588
1908
  /** Headers the codec owns: a caller-supplied copy is dropped and re-derived. */
1589
1909
  const REQUEST_OWNED = [
1590
1910
  "host",
@@ -1680,8 +2000,6 @@ async function* encodeResponse(env, body, _opts, requestMethod) {
1680
2000
  }
1681
2001
  yield* emitBody(body, declared);
1682
2002
  }
1683
- //#endregion
1684
- //#region ../webrun-http-streams/src/http1/index.ts
1685
2003
  function newHttpCodec(options = {}) {
1686
2004
  const opts = {
1687
2005
  scheme: options.scheme ?? "http",
@@ -1698,8 +2016,6 @@ function newHttpCodec(options = {}) {
1698
2016
  };
1699
2017
  }
1700
2018
  const httpCodec = newHttpCodec();
1701
- //#endregion
1702
- //#region ../webrun-http-streams/src/sniff.ts
1703
2019
  /**
1704
2020
  * Dispatches on byte 0. The formats are self-identifying — a JSON envelope
1705
2021
  * always begins `{`, which is not a token character and so can never begin an
@@ -1745,8 +2061,6 @@ function newSniffingCodec(options) {
1745
2061
  }
1746
2062
  };
1747
2063
  }
1748
- //#endregion
1749
- //#region ../webrun-http-streams/src/codec-default.ts
1750
2064
  /**
1751
2065
  * Writes HTTP/1.1; accepts HTTP/1.1 or the legacy JSON envelope. Used whenever
1752
2066
  * no `codec` option is supplied.
@@ -1755,8 +2069,6 @@ const defaultCodec = newSniffingCodec({
1755
2069
  write: httpCodec,
1756
2070
  accept: [httpCodec, jsonEnvelopeCodec]
1757
2071
  });
1758
- //#endregion
1759
- //#region ../webrun-http-streams/src/http-data.ts
1760
2072
  /** Carries the serialized JS error between two webrun peers. */
1761
2073
  const PEER_ERROR_HEADER = "x-webrun-error";
1762
2074
  /** Hard cap on the peer-error header (M3): see `encodeErrorResponse`. */
@@ -1865,8 +2177,6 @@ function httpServe(handler, options = {}) {
1865
2177
  yield* replyCodec.encodeResponse(result.envelope, result.body, { method: decoded.envelope.method });
1866
2178
  };
1867
2179
  }
1868
- //#endregion
1869
- //#region ../webrun-http-streams/src/request-streams.ts
1870
2180
  /**
1871
2181
  * The one place that knows a runtime may not implement request body streams,
1872
2182
  * and the buffering both fallbacks need. `fetch.ts` and `http-stubs.ts` each
@@ -1935,8 +2245,6 @@ async function collectBytes$1(source) {
1935
2245
  }
1936
2246
  return out;
1937
2247
  }
1938
- //#endregion
1939
- //#region ../webrun-http-streams/src/http-stubs.ts
1940
2248
  const NULL_BODY_STATUSES = /* @__PURE__ */ new Set([
1941
2249
  101,
1942
2250
  103,
@@ -2032,8 +2340,6 @@ function newHttpServerStub(handler) {
2032
2340
  };
2033
2341
  };
2034
2342
  }
2035
- //#endregion
2036
- //#region ../webrun-http-streams/src/fetch.ts
2037
2343
  /**
2038
2344
  * Connection-scoped headers. The codec surfaces them verbatim (decision 12),
2039
2345
  * but they are meaningless to a `Request`/`Response`, and re-emitting them
@@ -2184,8 +2490,6 @@ function abortReason(signal) {
2184
2490
  err.name = "AbortError";
2185
2491
  return err;
2186
2492
  }
2187
- //#endregion
2188
- //#region ../webrun-http-streams/src/duplex-site-builder.ts
2189
2493
  /**
2190
2494
  * Host a `SiteHandler` over a `Connect/Serve` pair. The `start(serve, params)`
2191
2495
  * call hands the handler to any `webrun-streams-*` adapter's `serve`.
@@ -2193,7 +2497,7 @@ function abortReason(signal) {
2193
2497
  * ```ts
2194
2498
  * import { SiteBuilder } from "@statewalker/webrun-site-builder";
2195
2499
  * import { DuplexSiteBuilder } from "@statewalker/webrun-http-streams";
2196
- * import { serve } from "@statewalker/webrun-streams-port";
2500
+ * import { serve } from "@statewalker/webrun-rpc";
2197
2501
  *
2198
2502
  * const handler = new SiteBuilder()
2199
2503
  * .setEndpoint("/api/time", () => new Response(new Date().toISOString()))
@@ -2224,8 +2528,6 @@ var DuplexSiteBuilder = class {
2224
2528
  return serve(params, serveFetchOverDuplex(async (req) => handler(req), { codec: this.#codec }));
2225
2529
  }
2226
2530
  };
2227
- //#endregion
2228
- //#region ../webrun-http-streams/src/http-error.ts
2229
2531
  var HttpError = class HttpError extends Error {
2230
2532
  status;
2231
2533
  statusText;
@@ -2308,13 +2610,21 @@ async function httpFromIterator(iterable) {
2308
2610
  };
2309
2611
  }
2310
2612
  /**
2311
- * @deprecated For new code, prefer the `MessagePort`-based stack from
2312
- * `@statewalker/webrun-http-port`. Once a `MessagePort` is established between
2313
- * page and worker, `httpServe(port, handler)` provides equivalent semantics
2314
- * with `callBidi` multiplexing, full-duplex streaming, and `AbortSignal`.
2315
- * This helper remains for existing ServiceWorker setups that still consume the
2316
- * `MessageTarget` surface; it will be reimplemented on top of
2317
- * `webrun-http-port` in a follow-up release.
2613
+ * Serve an `HttpHandler` over a `MessageTarget`, using this package's own
2614
+ * `handleStreams` transport.
2615
+ *
2616
+ * @deprecated Prefer the port stack in `@statewalker/webrun-rpc`: open a port
2617
+ * (`multiplexPort` over one pipe, or `transferPortMux` where the platform can
2618
+ * transfer a real `MessagePort`), turn it into a `Duplex` with
2619
+ * `serveDuplexOverPort`, and serve HTTP on that with
2620
+ * `httpServe(handler, options)` from `@statewalker/webrun-http-streams`.
2621
+ *
2622
+ * That path has backpressure; **this one does not.** `sendStream`'s chunk
2623
+ * sender discards the promise it is given, so a fast producer over a slow
2624
+ * consumer accumulates without bound. It also has no per-stream timeout and no
2625
+ * chunking to a transport's message ceiling.
2626
+ *
2627
+ * Kept for existing ServiceWorker setups built on the `MessageTarget` surface.
2318
2628
  */
2319
2629
  function handleHttpRequests(communicationPort, handler) {
2320
2630
  const serverStub = newHttpServerStub(handler);
@@ -2324,13 +2634,18 @@ function handleHttpRequests(communicationPort, handler) {
2324
2634
  });
2325
2635
  }
2326
2636
  /**
2327
- * @deprecated For new code, prefer the `MessagePort`-based stack from
2328
- * `@statewalker/webrun-http-port/fetch`. Once the page and SW share a
2329
- * `MessagePort`, `fetchOverPort(port, request)` provides the same
2330
- * `Request → Response` semantics with multiplexing via `callBidi`, JSONL
2331
- * envelope framing, and native `AbortSignal` support. This helper remains for
2332
- * existing ServiceWorker setups; it will be reimplemented on top of
2333
- * `webrun-http-port` in a follow-up release.
2637
+ * Ship a `Request` over a `MessageTarget` and await the `Response`, using this
2638
+ * package's own `sendStream` transport.
2639
+ *
2640
+ * @deprecated Prefer the port stack in `@statewalker/webrun-rpc`: open a port
2641
+ * (`multiplexPort` over one pipe, or `transferPortMux` where the platform can
2642
+ * transfer a real `MessagePort`), turn it into a `Duplex` with
2643
+ * `duplexOverPort`, and drive HTTP over it with `httpFetch` from
2644
+ * `@statewalker/webrun-http-streams`.
2645
+ *
2646
+ * Same caveat as {@link handleHttpRequests}: the transport underneath this
2647
+ * helper has no backpressure, no per-stream timeout, and no chunking to a
2648
+ * transport's message ceiling. Kept for existing ServiceWorker setups.
2334
2649
  */
2335
2650
  async function sendHttpRequest(communicationPort, request) {
2336
2651
  return await newHttpClientStub(async (req) => {
@@ -2369,13 +2684,25 @@ function splitServiceUrl(url, separator = "~") {
2369
2684
  //#endregion
2370
2685
  //#region src/relay/index.ts
2371
2686
  /**
2687
+ * The URL of this module, kept in a variable on purpose. Bundlers (Vite
2688
+ * among them) rewrite every literal `new URL("<path>", import.meta.url)` into
2689
+ * an emitted asset at build time, before tree-shaking — so the defaults below
2690
+ * made every Vite consumer of this entry emit a dead copy of the package's
2691
+ * own `dist/index.js` (`"../"` resolves to the package, hence to its `main`).
2692
+ * Resolving against a variable is the same URL at run time and invisible to
2693
+ * that transform.
2694
+ */
2695
+ const moduleUrl = import.meta.url;
2696
+ /**
2372
2697
  * Returns a MessagePort that transparently bridges messages to/from the
2373
- * ServiceWorker controlling this page.
2698
+ * page's ServiceWorker: the one controlling the page, or — when the page is
2699
+ * not controlled (a hard reload, or a page Firefox left uncontrolled) — the
2700
+ * active worker of `registration`, which answers messages all the same.
2374
2701
  */
2375
- function newServiceWorkerPort() {
2702
+ function newServiceWorkerPort(registration) {
2376
2703
  const channel = new MessageChannel();
2377
2704
  channel.port1.onmessage = (event) => {
2378
- navigator.serviceWorker.controller?.postMessage(event.data, [...event.ports]);
2705
+ (navigator.serviceWorker.controller ?? registration?.active)?.postMessage(event.data, [...event.ports]);
2379
2706
  };
2380
2707
  navigator.serviceWorker.addEventListener("message", (event) => {
2381
2708
  channel.port1.postMessage(event.data, [...event.ports]);
@@ -2383,45 +2710,26 @@ function newServiceWorkerPort() {
2383
2710
  return channel.port2;
2384
2711
  }
2385
2712
  /**
2386
- * Registers a ServiceWorker and resolves once it's activated and controlling the page.
2713
+ * Registers a ServiceWorker and resolves with it once it is activated: the
2714
+ * worker controlling the page, or the registration's active worker when the
2715
+ * page is not controlled. Messaging works either way, which is all the relay
2716
+ * needs; nothing here waits for control, because an uncontrolled page (hard
2717
+ * reload; Firefox) may never get it. Rejects with a
2718
+ * `ServiceWorkerControlError` if activation takes longer than `timeout`.
2387
2719
  */
2388
- async function initServiceWorker({ swUrl, scopeUrl, type }) {
2389
- await navigator.serviceWorker.register(swUrl, {
2720
+ async function initServiceWorker(options) {
2721
+ return (await registerServiceWorker(options)).worker;
2722
+ }
2723
+ async function registerServiceWorker({ swUrl, scopeUrl, type, timeout = DEFAULT_SERVICE_WORKER_TIMEOUT }) {
2724
+ const registration = await navigator.serviceWorker.register(swUrl, {
2390
2725
  type,
2391
2726
  scope: scopeUrl
2392
2727
  });
2393
- const worker = await getServiceWorkerController();
2394
- await awaitServiceWorkerActivation(worker);
2395
- return worker;
2396
- }
2397
- function getServiceWorkerController() {
2398
- return new Promise((resolve) => {
2399
- const container = navigator.serviceWorker;
2400
- if (container.controller) {
2401
- resolve(container.controller);
2402
- return;
2403
- }
2404
- const onChange = () => {
2405
- if (!container.controller) return;
2406
- resolve(container.controller);
2407
- container.removeEventListener("controllerchange", onChange);
2408
- };
2409
- container.addEventListener("controllerchange", onChange);
2410
- });
2411
- }
2412
- function awaitServiceWorkerActivation(worker) {
2413
- return new Promise((resolve) => {
2414
- if (worker.state === "activated") {
2415
- resolve();
2416
- return;
2417
- }
2418
- const onStateChange = () => {
2419
- if (worker.state !== "activated") return;
2420
- worker.removeEventListener("statechange", onStateChange);
2421
- resolve();
2422
- };
2423
- worker.addEventListener("statechange", onStateChange);
2424
- });
2728
+ const active = await awaitActiveServiceWorker(registration, { timeout });
2729
+ return {
2730
+ registration,
2731
+ worker: navigator.serviceWorker.controller ?? active
2732
+ };
2425
2733
  }
2426
2734
  /**
2427
2735
  * Registers `handler` as the server for the given service `key` on the relay.
@@ -2452,9 +2760,11 @@ async function callHttpService(request, { key, port }) {
2452
2760
  /**
2453
2761
  * Returns a `window.onmessage` handler for use inside the relay iframe:
2454
2762
  * it accepts a CONNECT message, starts the relay ServiceWorker, and bridges
2455
- * the parent's MessagePort with the SW.
2763
+ * the parent's MessagePort with the SW. If the worker cannot be started, every
2764
+ * call the parent makes on that port is answered with the error, so the
2765
+ * parent's `initHttpService` / `callHttpService` reject instead of waiting.
2456
2766
  */
2457
- function getRelayWindowMessageHandler({ swUrl = `${new URL("./index-sw.js", import.meta.url)}`, scopeUrl = `${new URL("../", import.meta.url)}` } = {}) {
2767
+ function getRelayWindowMessageHandler({ swUrl = `${new URL("./index-sw.js", moduleUrl)}`, scopeUrl = `${new URL("../", moduleUrl)}`, timeout } = {}) {
2458
2768
  let externalPort;
2459
2769
  return async (ev) => {
2460
2770
  if (ev.data?.type !== "CONNECT") return;
@@ -2465,11 +2775,19 @@ function getRelayWindowMessageHandler({ swUrl = `${new URL("./index-sw.js", impo
2465
2775
  return;
2466
2776
  }
2467
2777
  externalPort = newExternalPort;
2468
- await initServiceWorker({
2469
- swUrl,
2470
- scopeUrl
2471
- });
2472
- const serviceWorkerPort = newServiceWorkerPort();
2778
+ let registration;
2779
+ try {
2780
+ ({registration} = await registerServiceWorker({
2781
+ swUrl,
2782
+ scopeUrl,
2783
+ timeout
2784
+ }));
2785
+ } catch (error) {
2786
+ const serialized = serializeError(error);
2787
+ externalPort.onmessage = (event) => event.ports[0]?.postMessage({ error: serialized });
2788
+ throw error;
2789
+ }
2790
+ const serviceWorkerPort = newServiceWorkerPort(registration);
2473
2791
  serviceWorkerPort.onmessage = (event) => {
2474
2792
  externalPort?.postMessage(event.data, [...event.ports]);
2475
2793
  };
@@ -2482,7 +2800,7 @@ function getRelayWindowMessageHandler({ swUrl = `${new URL("./index-sw.js", impo
2482
2800
  * Embeds a hidden relay iframe, establishes a MessageChannel with it, and
2483
2801
  * returns the port to be used with `initHttpService` / `callHttpService`.
2484
2802
  */
2485
- async function newRemoteRelayChannel({ baseUrl = new URL("../public-relay/", import.meta.url), url = new URL("relay.html", baseUrl), container = document.body } = {}) {
2803
+ async function newRemoteRelayChannel({ baseUrl = new URL("../public-relay/", moduleUrl), url = new URL("relay.html", baseUrl), container = document.body } = {}) {
2486
2804
  const messageChannel = new MessageChannel();
2487
2805
  const { iframe, promise } = newIFrame(url);
2488
2806
  Object.assign(iframe.style, {
@@ -2551,4 +2869,4 @@ async function registerConnectionsHandler({ key, handler, communicationPort }) {
2551
2869
  return cleanup;
2552
2870
  }
2553
2871
  //#endregion
2554
- export { DuplexSiteBuilder, HttpError, HttpParseError, PEER_ERROR_HEADER, TransportClosedError, callChannel, callHttpService, collect, collectBytes, collectString, decodeJsonl, decodeMessage, decodeText, defaultCodec, deserializeError, emulateMux, encodeJsonl, encodeMessage, encodeText, fetchOverDuplex, fromReadableStream, getRelayWindowMessageHandler, handleChannelCalls, handleHttpRequests, handleStreams, httpCodec, httpFetch, httpServe, initHttpService, initServiceWorker, initializeConnection, joinLines, jsonEnvelopeCodec, map, newAsyncGenerator, newHttpClientStub, newHttpCodec, newHttpServerStub, newInvokationChannel, newRegistry, newRemoteRelayChannel, newServiceWorkerPort, newSniffingCodec, normalizeToUint8Array, recieveIterator, registerConnectionsHandler, sendHttpRequest, sendIterator, sendStream, serializeError, serveFetchOverDuplex, splitLines, splitServiceUrl, toChunks, toReadableStream };
2872
+ export { CLAIM_CALL, DEFAULT_SERVICE_WORKER_TIMEOUT, DuplexSiteBuilder, HttpError, HttpParseError, PEER_ERROR_HEADER, ServiceWorkerControlError, TransportClosedError, awaitActiveServiceWorker, awaitServiceWorkerControl, callChannel, callHttpService, collect, collectBytes, collectString, decodeJsonl, decodeMessage, decodeText, defaultCodec, deserializeError, emulateMux, encodeJsonl, encodeMessage, encodeText, fetchOverDuplex, fromReadableStream, getRelayWindowMessageHandler, handleChannelCalls, handleClaimRequests, handleHttpRequests, handleStreams, httpCodec, httpFetch, httpServe, initHttpService, initServiceWorker, initializeConnection, joinLines, jsonEnvelopeCodec, map, newAsyncGenerator, newCreditGrantor, newCreditLedger, newHttpClientStub, newHttpCodec, newHttpServerStub, newInvokationChannel, newRegistry, newRemoteRelayChannel, newServiceWorkerPort, newSniffingCodec, normalizeToUint8Array, recieveIterator, registerConnectionsHandler, sendHttpRequest, sendIterator, sendStream, serializeError, serveFetchOverDuplex, splitLines, splitServiceUrl, toChunks, toReadableStream };