@statewalker/webrun-http-browser 0.12.1 → 0.12.3

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/sw.js CHANGED
@@ -706,7 +706,7 @@ const LAST_CHUNK = /* @__PURE__ */ new Uint8Array([
706
706
  13,
707
707
  10
708
708
  ]);
709
- const encoder = /* @__PURE__ */ new TextEncoder();
709
+ const encoder$2 = /* @__PURE__ */ new TextEncoder();
710
710
  /**
711
711
  * Chunk sizes are written in HEXADECIMAL — a 21-byte chunk is `15`. Writing
712
712
  * them in decimal is the defect note 15 found in @libp2p/http, and it is the
@@ -719,7 +719,7 @@ const encoder = /* @__PURE__ */ new TextEncoder();
719
719
  async function* encodeChunked(body) {
720
720
  for await (const chunk of body) {
721
721
  if (chunk.byteLength === 0) continue;
722
- yield encoder.encode(`${chunk.byteLength.toString(16)}\r\n`);
722
+ yield encoder$2.encode(`${chunk.byteLength.toString(16)}\r\n`);
723
723
  yield chunk;
724
724
  yield CRLF;
725
725
  }
@@ -1183,7 +1183,10 @@ async function httpFetch(call, env, body, options = {}) {
1183
1183
  }
1184
1184
  const onAbort = () => void release();
1185
1185
  signal?.addEventListener("abort", onAbort, { once: true });
1186
- drain(output).finally(() => signal?.removeEventListener("abort", onAbort));
1186
+ options.keepAlive;
1187
+ drain(output).finally(() => {
1188
+ signal?.removeEventListener("abort", onAbort);
1189
+ });
1187
1190
  };
1188
1191
  let decoded;
1189
1192
  try {
@@ -1546,7 +1549,8 @@ async function fetchOverDuplex(call, request, options = {}) {
1546
1549
  }
1547
1550
  const { envelope, body: respBody, release, finish } = await httpFetch(call, env, body, {
1548
1551
  ...options,
1549
- signal: request.signal ?? void 0
1552
+ signal: request.signal ?? void 0,
1553
+ keepAlive: request
1550
1554
  });
1551
1555
  const responseInit = {
1552
1556
  status: envelope.status,
@@ -1558,7 +1562,7 @@ async function fetchOverDuplex(call, request, options = {}) {
1558
1562
  else await release?.();
1559
1563
  return new Response(null, responseInit);
1560
1564
  }
1561
- return new Response(responseBody(respBody, request.signal, release), responseInit);
1565
+ return new Response(responseBody(respBody, request, release), responseInit);
1562
1566
  }
1563
1567
  /**
1564
1568
  * Wrap a `(Request) => Promise<Response>` handler as a `Duplex` so it can be
@@ -1711,7 +1715,9 @@ const CANCEL_GRACE_MS = 1e3;
1711
1715
  * A cancel abandons the call too — unless the read in flight comes back with the
1712
1716
  * body's end, which finishes it instead (see `cancel`).
1713
1717
  */
1714
- function responseBody(body, signal, release) {
1718
+ function responseBody(body, request, release) {
1719
+ const keep = { request };
1720
+ const signal = request.signal ?? void 0;
1715
1721
  const it = body[Symbol.asyncIterator]();
1716
1722
  let controller;
1717
1723
  let ended = false;
@@ -1719,6 +1725,7 @@ function responseBody(body, signal, release) {
1719
1725
  const end = () => {
1720
1726
  ended = true;
1721
1727
  signal?.removeEventListener("abort", onAbort);
1728
+ keep.request = void 0;
1722
1729
  };
1723
1730
  const onAbort = () => {
1724
1731
  if (ended) return;
@@ -2089,7 +2096,7 @@ const STREAM_ABORT = "webrun-rpc:stream-abort";
2089
2096
  * request, sent only on a port where both ends agreed `pipeline`. The window's
2090
2097
  * credit is its confirmation.
2091
2098
  */
2092
- const CHUNK = "webrun-rpc:chunk";
2099
+ const CHUNK$1 = "webrun-rpc:chunk";
2093
2100
  /**
2094
2101
  * What a pipelined chunk counts beyond its bytes, against the window, on the
2095
2102
  * receiving side. Both codecs' windows count more for the chunk's envelope
@@ -2420,7 +2427,7 @@ function receiveChunks(port, channelName, controller, touch, notice) {
2420
2427
  const watcher = (event) => {
2421
2428
  const data = event.data;
2422
2429
  if (data?.channelName !== channelName) return;
2423
- const chunk = data.type === CHUNK ? data.chunk : data.type === "request" ? data.params : void 0;
2430
+ const chunk = data.type === CHUNK$1 ? data.chunk : data.type === "request" ? data.params : void 0;
2424
2431
  if (!chunk) return;
2425
2432
  if (!watchPeerEnd()) {
2426
2433
  port.removeEventListener("message", watcher);
@@ -2455,7 +2462,7 @@ function receiveChunks(port, channelName, controller, touch, notice) {
2455
2462
  }, { channelName });
2456
2463
  const onChunk = (event) => {
2457
2464
  const data = event.data;
2458
- if (data?.type !== CHUNK || data.channelName !== channelName || !data.chunk) return;
2465
+ if (data?.type !== CHUNK$1 || data.channelName !== channelName || !data.chunk) return;
2459
2466
  if (poison) return;
2460
2467
  const agreement = portAgreement(port);
2461
2468
  if (!agreement?.caps.has("pipeline")) {
@@ -2547,12 +2554,6 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2547
2554
  const producer = output;
2548
2555
  const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
2549
2556
  if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
2550
- let onAborted = () => {};
2551
- const aborted = new Promise((resolve) => {
2552
- onAborted = () => resolve();
2553
- });
2554
- if (signal.aborted) onAborted();
2555
- else signal.addEventListener("abort", onAborted, { once: true });
2556
2557
  try {
2557
2558
  await sendIterator(async ({ done, value, error }) => {
2558
2559
  if (signal.aborted) return;
@@ -2568,13 +2569,13 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2568
2569
  });
2569
2570
  if (mayPipeline(port)) {
2570
2571
  port.postMessage({
2571
- type: CHUNK,
2572
+ type: CHUNK$1,
2572
2573
  channelName,
2573
2574
  chunk
2574
2575
  });
2575
2576
  touch();
2576
2577
  if (done && !error) onSent();
2577
- if (portBufferedAmount(port) > 0) await Promise.race([whenPortReady(port), aborted]);
2578
+ if (portBufferedAmount(port) > 0) await whenReadyOrAborted(port, signal);
2578
2579
  return;
2579
2580
  }
2580
2581
  await callPort(port, chunk, {
@@ -2590,10 +2591,25 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2590
2591
  throw err;
2591
2592
  } finally {
2592
2593
  signal.removeEventListener("abort", returnProducer);
2593
- signal.removeEventListener("abort", onAborted);
2594
2594
  }
2595
2595
  }
2596
2596
  /**
2597
+ * A pipelined sender waits only for room in the window, or for the abort. The
2598
+ * abort listener lives for this one wait: a promise held for the whole stream
2599
+ * would keep one reaction per wait until the stream ends.
2600
+ */
2601
+ function whenReadyOrAborted(port, signal) {
2602
+ if (signal.aborted) return Promise.resolve();
2603
+ return new Promise((resolve) => {
2604
+ const settle = () => {
2605
+ signal.removeEventListener("abort", settle);
2606
+ resolve();
2607
+ };
2608
+ signal.addEventListener("abort", settle, { once: true });
2609
+ whenPortReady(port).then(settle, settle);
2610
+ });
2611
+ }
2612
+ /**
2597
2613
  * One virtual port.
2598
2614
  *
2599
2615
  * `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
@@ -3591,7 +3607,12 @@ function trackedLink(send, release) {
3591
3607
  finish();
3592
3608
  return response;
3593
3609
  }
3594
- return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
3610
+ failers.delete(fail);
3611
+ const body = guardBody(response.body, finish, request);
3612
+ fail = body.fail;
3613
+ if (closed) fail(lostReason);
3614
+ else if (!finished) failers.add(fail);
3615
+ return new Response(body.stream, {
3595
3616
  status: response.status,
3596
3617
  statusText: response.statusText,
3597
3618
  headers: response.headers
@@ -3610,56 +3631,62 @@ function trackedLink(send, release) {
3610
3631
  return asyncDisposable(link, () => link.close());
3611
3632
  }
3612
3633
  /**
3613
- * A response body that reports its end (done, cancelled, failed or aborted) and
3614
- * fails with the link's reason when the link is lost mid-stream. An abort of the
3615
- * request's signal ends it at once, read or not: the exchange is over (the layer
3616
- * below has given its port back), so the request leaves `inflight` then.
3634
+ * A response body that reports its end (done, cancelled, failed or aborted).
3635
+ * `fail(error)` ends it with `error` at once — the link calls it when it is lost
3636
+ * mid-stream. An abort of the request's signal does the same with the signal's
3637
+ * reason, read or not: the exchange is over (the layer below has given its port
3638
+ * back), so the request leaves `inflight` then. A read waits on the reader
3639
+ * alone, so nothing outlives the chunk it brings.
3617
3640
  */
3618
- function guardBody(body, lost, reason, finish, signal) {
3641
+ function guardBody(body, finish, request) {
3642
+ const keep = { request };
3643
+ const signal = request.signal ?? void 0;
3619
3644
  const reader = body.getReader();
3620
3645
  let controller;
3621
3646
  let ended = false;
3622
3647
  const end = () => {
3623
3648
  ended = true;
3624
3649
  signal?.removeEventListener("abort", onAbort);
3650
+ keep.request = void 0;
3625
3651
  finish();
3626
3652
  };
3627
- const onAbort = () => {
3653
+ const fail = (error) => {
3628
3654
  if (ended) return;
3629
3655
  end();
3630
3656
  reader.cancel().catch(() => {});
3631
3657
  try {
3632
- controller.error(abortError(signal));
3658
+ controller.error(error);
3633
3659
  } catch {}
3634
3660
  };
3635
- return new ReadableStream({
3636
- start(c) {
3637
- controller = c;
3638
- if (signal?.aborted) onAbort();
3639
- else signal?.addEventListener("abort", onAbort, { once: true });
3640
- },
3641
- async pull(controller) {
3642
- try {
3643
- const { done, value } = await Promise.race([reader.read(), lost]);
3644
- if (ended) return;
3645
- if (done) {
3646
- end();
3647
- controller.close();
3648
- return;
3661
+ const onAbort = () => fail(abortError(signal));
3662
+ return {
3663
+ stream: new ReadableStream({
3664
+ start(c) {
3665
+ controller = c;
3666
+ if (signal?.aborted) onAbort();
3667
+ else signal?.addEventListener("abort", onAbort, { once: true });
3668
+ },
3669
+ async pull(controller) {
3670
+ try {
3671
+ const { done, value } = await reader.read();
3672
+ if (ended) return;
3673
+ if (done) {
3674
+ end();
3675
+ controller.close();
3676
+ return;
3677
+ }
3678
+ controller.enqueue(value);
3679
+ } catch (error) {
3680
+ fail(error);
3649
3681
  }
3650
- controller.enqueue(value);
3651
- } catch (error) {
3652
- if (ended) return;
3682
+ },
3683
+ cancel(why) {
3653
3684
  end();
3654
- reader.cancel().catch(() => {});
3655
- controller.error(reason() ?? error);
3685
+ setTimeout(() => void reader.cancel(why).catch(() => {}), 0);
3656
3686
  }
3657
- },
3658
- async cancel(why) {
3659
- end();
3660
- await reader.cancel(why).catch(() => {});
3661
- }
3662
- });
3687
+ }),
3688
+ fail
3689
+ };
3663
3690
  }
3664
3691
  /** The signal's reason as an `Error` (`AbortError` when it gave none). */
3665
3692
  function abortError(signal) {
@@ -3867,20 +3894,24 @@ function postFrame(port, frame) {
3867
3894
  function messageSize(data) {
3868
3895
  return typeof data === "string" ? utf8Length(data) : data.byteLength;
3869
3896
  }
3870
- /** The UTF-8 length of `text`, without encoding it. */
3897
+ const encoder = new TextEncoder();
3898
+ /** Characters encoded per `encodeInto` call: at most 3 bytes each, so the scratch stays at 96 KiB. */
3899
+ const CHUNK = 32768;
3900
+ const scratch = new Uint8Array(CHUNK * 3);
3901
+ /**
3902
+ * The UTF-8 length of `text`, as `TextEncoder` would encode it (a lone surrogate counts as the 3
3903
+ * bytes of U+FFFD). Every message is measured on both ends (`bufferedAmount` and the byte acks), so
3904
+ * this runs natively: `encodeInto` a reused buffer, a chunk at a time, never splitting a surrogate
3905
+ * pair across two chunks. A loop over the characters in JavaScript took about 30 times as long.
3906
+ */
3871
3907
  function utf8Length(text) {
3872
3908
  let length = 0;
3873
- for (let i = 0; i < text.length; i++) {
3874
- const code = text.charCodeAt(i);
3875
- if (code < 128) length += 1;
3876
- else if (code < 2048) length += 2;
3877
- else if (code >= 55296 && code <= 56319 && i + 1 < text.length) {
3878
- const next = text.charCodeAt(i + 1);
3879
- if (next >= 56320 && next <= 57343) {
3880
- length += 4;
3881
- i++;
3882
- } else length += 3;
3883
- } else length += 3;
3909
+ for (let start = 0; start < text.length;) {
3910
+ let end = Math.min(start + CHUNK, text.length);
3911
+ const last = text.charCodeAt(end - 1);
3912
+ if (end < text.length && last >= 55296 && last <= 56319) end--;
3913
+ length += encoder.encodeInto(text.slice(start, end), scratch).written;
3914
+ start = end;
3884
3915
  }
3885
3916
  return length;
3886
3917
  }
@@ -6924,7 +6955,10 @@ function serveHttpInWorker(handler, options = {}) {
6924
6955
  let WebSocketClass;
6925
6956
  return {
6926
6957
  fetch(input, init) {
6927
- const request = input instanceof Request && init === void 0 ? input : new Request(input instanceof Request ? input : new URL(`${input}`, baseUrl()), init);
6958
+ const request = input instanceof Request && init === void 0 ? input : new Request(input instanceof Request ? input : new URL(`${input}`, baseUrl()), {
6959
+ ...init,
6960
+ signal: init && "signal" in init ? init.signal : input instanceof Request ? input.signal : void 0
6961
+ });
6928
6962
  return endpoint.fetch(request);
6929
6963
  },
6930
6964
  connectWebSocket,
package/dist/worker.js CHANGED
@@ -647,7 +647,7 @@ const LAST_CHUNK = /* @__PURE__ */ new Uint8Array([
647
647
  13,
648
648
  10
649
649
  ]);
650
- const encoder = /* @__PURE__ */ new TextEncoder();
650
+ const encoder$2 = /* @__PURE__ */ new TextEncoder();
651
651
  /**
652
652
  * Chunk sizes are written in HEXADECIMAL — a 21-byte chunk is `15`. Writing
653
653
  * them in decimal is the defect note 15 found in @libp2p/http, and it is the
@@ -660,7 +660,7 @@ const encoder = /* @__PURE__ */ new TextEncoder();
660
660
  async function* encodeChunked(body) {
661
661
  for await (const chunk of body) {
662
662
  if (chunk.byteLength === 0) continue;
663
- yield encoder.encode(`${chunk.byteLength.toString(16)}\r\n`);
663
+ yield encoder$2.encode(`${chunk.byteLength.toString(16)}\r\n`);
664
664
  yield chunk;
665
665
  yield CRLF;
666
666
  }
@@ -1124,7 +1124,10 @@ async function httpFetch(call, env, body, options = {}) {
1124
1124
  }
1125
1125
  const onAbort = () => void release();
1126
1126
  signal?.addEventListener("abort", onAbort, { once: true });
1127
- drain(output).finally(() => signal?.removeEventListener("abort", onAbort));
1127
+ options.keepAlive;
1128
+ drain(output).finally(() => {
1129
+ signal?.removeEventListener("abort", onAbort);
1130
+ });
1128
1131
  };
1129
1132
  let decoded;
1130
1133
  try {
@@ -1432,7 +1435,8 @@ async function fetchOverDuplex(call, request, options = {}) {
1432
1435
  }
1433
1436
  const { envelope, body: respBody, release, finish } = await httpFetch(call, env, body, {
1434
1437
  ...options,
1435
- signal: request.signal ?? void 0
1438
+ signal: request.signal ?? void 0,
1439
+ keepAlive: request
1436
1440
  });
1437
1441
  const responseInit = {
1438
1442
  status: envelope.status,
@@ -1444,7 +1448,7 @@ async function fetchOverDuplex(call, request, options = {}) {
1444
1448
  else await release?.();
1445
1449
  return new Response(null, responseInit);
1446
1450
  }
1447
- return new Response(responseBody(respBody, request.signal, release), responseInit);
1451
+ return new Response(responseBody(respBody, request, release), responseInit);
1448
1452
  }
1449
1453
  /**
1450
1454
  * Wrap a `(Request) => Promise<Response>` handler as a `Duplex` so it can be
@@ -1597,7 +1601,9 @@ const CANCEL_GRACE_MS = 1e3;
1597
1601
  * A cancel abandons the call too — unless the read in flight comes back with the
1598
1602
  * body's end, which finishes it instead (see `cancel`).
1599
1603
  */
1600
- function responseBody(body, signal, release) {
1604
+ function responseBody(body, request, release) {
1605
+ const keep = { request };
1606
+ const signal = request.signal ?? void 0;
1601
1607
  const it = body[Symbol.asyncIterator]();
1602
1608
  let controller;
1603
1609
  let ended = false;
@@ -1605,6 +1611,7 @@ function responseBody(body, signal, release) {
1605
1611
  const end = () => {
1606
1612
  ended = true;
1607
1613
  signal?.removeEventListener("abort", onAbort);
1614
+ keep.request = void 0;
1608
1615
  };
1609
1616
  const onAbort = () => {
1610
1617
  if (ended) return;
@@ -1975,7 +1982,7 @@ const STREAM_ABORT = "webrun-rpc:stream-abort";
1975
1982
  * request, sent only on a port where both ends agreed `pipeline`. The window's
1976
1983
  * credit is its confirmation.
1977
1984
  */
1978
- const CHUNK = "webrun-rpc:chunk";
1985
+ const CHUNK$1 = "webrun-rpc:chunk";
1979
1986
  /**
1980
1987
  * What a pipelined chunk counts beyond its bytes, against the window, on the
1981
1988
  * receiving side. Both codecs' windows count more for the chunk's envelope
@@ -2306,7 +2313,7 @@ function receiveChunks(port, channelName, controller, touch, notice) {
2306
2313
  const watcher = (event) => {
2307
2314
  const data = event.data;
2308
2315
  if (data?.channelName !== channelName) return;
2309
- const chunk = data.type === CHUNK ? data.chunk : data.type === "request" ? data.params : void 0;
2316
+ const chunk = data.type === CHUNK$1 ? data.chunk : data.type === "request" ? data.params : void 0;
2310
2317
  if (!chunk) return;
2311
2318
  if (!watchPeerEnd()) {
2312
2319
  port.removeEventListener("message", watcher);
@@ -2341,7 +2348,7 @@ function receiveChunks(port, channelName, controller, touch, notice) {
2341
2348
  }, { channelName });
2342
2349
  const onChunk = (event) => {
2343
2350
  const data = event.data;
2344
- if (data?.type !== CHUNK || data.channelName !== channelName || !data.chunk) return;
2351
+ if (data?.type !== CHUNK$1 || data.channelName !== channelName || !data.chunk) return;
2345
2352
  if (poison) return;
2346
2353
  const agreement = portAgreement(port);
2347
2354
  if (!agreement?.caps.has("pipeline")) {
@@ -2433,12 +2440,6 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2433
2440
  const producer = output;
2434
2441
  const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
2435
2442
  if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
2436
- let onAborted = () => {};
2437
- const aborted = new Promise((resolve) => {
2438
- onAborted = () => resolve();
2439
- });
2440
- if (signal.aborted) onAborted();
2441
- else signal.addEventListener("abort", onAborted, { once: true });
2442
2443
  try {
2443
2444
  await sendIterator(async ({ done, value, error }) => {
2444
2445
  if (signal.aborted) return;
@@ -2454,13 +2455,13 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2454
2455
  });
2455
2456
  if (mayPipeline(port)) {
2456
2457
  port.postMessage({
2457
- type: CHUNK,
2458
+ type: CHUNK$1,
2458
2459
  channelName,
2459
2460
  chunk
2460
2461
  });
2461
2462
  touch();
2462
2463
  if (done && !error) onSent();
2463
- if (portBufferedAmount(port) > 0) await Promise.race([whenPortReady(port), aborted]);
2464
+ if (portBufferedAmount(port) > 0) await whenReadyOrAborted(port, signal);
2464
2465
  return;
2465
2466
  }
2466
2467
  await callPort(port, chunk, {
@@ -2476,10 +2477,25 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
2476
2477
  throw err;
2477
2478
  } finally {
2478
2479
  signal.removeEventListener("abort", returnProducer);
2479
- signal.removeEventListener("abort", onAborted);
2480
2480
  }
2481
2481
  }
2482
2482
  /**
2483
+ * A pipelined sender waits only for room in the window, or for the abort. The
2484
+ * abort listener lives for this one wait: a promise held for the whole stream
2485
+ * would keep one reaction per wait until the stream ends.
2486
+ */
2487
+ function whenReadyOrAborted(port, signal) {
2488
+ if (signal.aborted) return Promise.resolve();
2489
+ return new Promise((resolve) => {
2490
+ const settle = () => {
2491
+ signal.removeEventListener("abort", settle);
2492
+ resolve();
2493
+ };
2494
+ signal.addEventListener("abort", settle, { once: true });
2495
+ whenPortReady(port).then(settle, settle);
2496
+ });
2497
+ }
2498
+ /**
2483
2499
  * One virtual port.
2484
2500
  *
2485
2501
  * `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
@@ -3477,7 +3493,12 @@ function trackedLink(send, release) {
3477
3493
  finish();
3478
3494
  return response;
3479
3495
  }
3480
- return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
3496
+ failers.delete(fail);
3497
+ const body = guardBody(response.body, finish, request);
3498
+ fail = body.fail;
3499
+ if (closed) fail(lostReason);
3500
+ else if (!finished) failers.add(fail);
3501
+ return new Response(body.stream, {
3481
3502
  status: response.status,
3482
3503
  statusText: response.statusText,
3483
3504
  headers: response.headers
@@ -3496,56 +3517,62 @@ function trackedLink(send, release) {
3496
3517
  return asyncDisposable(link, () => link.close());
3497
3518
  }
3498
3519
  /**
3499
- * A response body that reports its end (done, cancelled, failed or aborted) and
3500
- * fails with the link's reason when the link is lost mid-stream. An abort of the
3501
- * request's signal ends it at once, read or not: the exchange is over (the layer
3502
- * below has given its port back), so the request leaves `inflight` then.
3520
+ * A response body that reports its end (done, cancelled, failed or aborted).
3521
+ * `fail(error)` ends it with `error` at once — the link calls it when it is lost
3522
+ * mid-stream. An abort of the request's signal does the same with the signal's
3523
+ * reason, read or not: the exchange is over (the layer below has given its port
3524
+ * back), so the request leaves `inflight` then. A read waits on the reader
3525
+ * alone, so nothing outlives the chunk it brings.
3503
3526
  */
3504
- function guardBody(body, lost, reason, finish, signal) {
3527
+ function guardBody(body, finish, request) {
3528
+ const keep = { request };
3529
+ const signal = request.signal ?? void 0;
3505
3530
  const reader = body.getReader();
3506
3531
  let controller;
3507
3532
  let ended = false;
3508
3533
  const end = () => {
3509
3534
  ended = true;
3510
3535
  signal?.removeEventListener("abort", onAbort);
3536
+ keep.request = void 0;
3511
3537
  finish();
3512
3538
  };
3513
- const onAbort = () => {
3539
+ const fail = (error) => {
3514
3540
  if (ended) return;
3515
3541
  end();
3516
3542
  reader.cancel().catch(() => {});
3517
3543
  try {
3518
- controller.error(abortError(signal));
3544
+ controller.error(error);
3519
3545
  } catch {}
3520
3546
  };
3521
- return new ReadableStream({
3522
- start(c) {
3523
- controller = c;
3524
- if (signal?.aborted) onAbort();
3525
- else signal?.addEventListener("abort", onAbort, { once: true });
3526
- },
3527
- async pull(controller) {
3528
- try {
3529
- const { done, value } = await Promise.race([reader.read(), lost]);
3530
- if (ended) return;
3531
- if (done) {
3532
- end();
3533
- controller.close();
3534
- return;
3547
+ const onAbort = () => fail(abortError(signal));
3548
+ return {
3549
+ stream: new ReadableStream({
3550
+ start(c) {
3551
+ controller = c;
3552
+ if (signal?.aborted) onAbort();
3553
+ else signal?.addEventListener("abort", onAbort, { once: true });
3554
+ },
3555
+ async pull(controller) {
3556
+ try {
3557
+ const { done, value } = await reader.read();
3558
+ if (ended) return;
3559
+ if (done) {
3560
+ end();
3561
+ controller.close();
3562
+ return;
3563
+ }
3564
+ controller.enqueue(value);
3565
+ } catch (error) {
3566
+ fail(error);
3535
3567
  }
3536
- controller.enqueue(value);
3537
- } catch (error) {
3538
- if (ended) return;
3568
+ },
3569
+ cancel(why) {
3539
3570
  end();
3540
- reader.cancel().catch(() => {});
3541
- controller.error(reason() ?? error);
3571
+ setTimeout(() => void reader.cancel(why).catch(() => {}), 0);
3542
3572
  }
3543
- },
3544
- async cancel(why) {
3545
- end();
3546
- await reader.cancel(why).catch(() => {});
3547
- }
3548
- });
3573
+ }),
3574
+ fail
3575
+ };
3549
3576
  }
3550
3577
  /** The signal's reason as an `Error` (`AbortError` when it gave none). */
3551
3578
  function abortError(signal) {
@@ -3678,20 +3705,24 @@ function postFrame(port, frame) {
3678
3705
  function messageSize(data) {
3679
3706
  return typeof data === "string" ? utf8Length(data) : data.byteLength;
3680
3707
  }
3681
- /** The UTF-8 length of `text`, without encoding it. */
3708
+ const encoder = new TextEncoder();
3709
+ /** Characters encoded per `encodeInto` call: at most 3 bytes each, so the scratch stays at 96 KiB. */
3710
+ const CHUNK = 32768;
3711
+ const scratch = new Uint8Array(CHUNK * 3);
3712
+ /**
3713
+ * The UTF-8 length of `text`, as `TextEncoder` would encode it (a lone surrogate counts as the 3
3714
+ * bytes of U+FFFD). Every message is measured on both ends (`bufferedAmount` and the byte acks), so
3715
+ * this runs natively: `encodeInto` a reused buffer, a chunk at a time, never splitting a surrogate
3716
+ * pair across two chunks. A loop over the characters in JavaScript took about 30 times as long.
3717
+ */
3682
3718
  function utf8Length(text) {
3683
3719
  let length = 0;
3684
- for (let i = 0; i < text.length; i++) {
3685
- const code = text.charCodeAt(i);
3686
- if (code < 128) length += 1;
3687
- else if (code < 2048) length += 2;
3688
- else if (code >= 55296 && code <= 56319 && i + 1 < text.length) {
3689
- const next = text.charCodeAt(i + 1);
3690
- if (next >= 56320 && next <= 57343) {
3691
- length += 4;
3692
- i++;
3693
- } else length += 3;
3694
- } else length += 3;
3720
+ for (let start = 0; start < text.length;) {
3721
+ let end = Math.min(start + CHUNK, text.length);
3722
+ const last = text.charCodeAt(end - 1);
3723
+ if (end < text.length && last >= 55296 && last <= 56319) end--;
3724
+ length += encoder.encodeInto(text.slice(start, end), scratch).written;
3725
+ start = end;
3695
3726
  }
3696
3727
  return length;
3697
3728
  }
@@ -4875,7 +4906,10 @@ function serveHttpInWorker(handler, options = {}) {
4875
4906
  let WebSocketClass;
4876
4907
  return {
4877
4908
  fetch(input, init) {
4878
- const request = input instanceof Request && init === void 0 ? input : new Request(input instanceof Request ? input : new URL(`${input}`, baseUrl()), init);
4909
+ const request = input instanceof Request && init === void 0 ? input : new Request(input instanceof Request ? input : new URL(`${input}`, baseUrl()), {
4910
+ ...init,
4911
+ signal: init && "signal" in init ? init.signal : input instanceof Request ? input.signal : void 0
4912
+ });
4879
4913
  return endpoint.fetch(request);
4880
4914
  },
4881
4915
  connectWebSocket,
@@ -63,6 +63,11 @@ export declare function readFrame(data: unknown): WsFrame | undefined;
63
63
  export declare function postFrame(port: MessagePort, frame: WsFrame): void;
64
64
  /** The size a message counts for in `bufferedAmount`: UTF-8 bytes, or the buffer's length. */
65
65
  export declare function messageSize(data: string | ArrayBuffer): number;
66
- /** The UTF-8 length of `text`, without encoding it. */
66
+ /**
67
+ * The UTF-8 length of `text`, as `TextEncoder` would encode it (a lone surrogate counts as the 3
68
+ * bytes of U+FFFD). Every message is measured on both ends (`bufferedAmount` and the byte acks), so
69
+ * this runs natively: `encodeInto` a reused buffer, a chunk at a time, never splitting a surrogate
70
+ * pair across two chunks. A loop over the characters in JavaScript took about 30 times as long.
71
+ */
67
72
  export declare function utf8Length(text: string): number;
68
73
  //# sourceMappingURL=frames.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"frames.d.ts","sourceRoot":"","sources":["../../src/ws/frames.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,qDAAqD;AACrD,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAE3C,oEAAoE;AACpE,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAE9C,MAAM,MAAM,OAAO,GACf;IAAE,CAAC,EAAE,GAAG,CAAC;IAAC,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;CAAE,GACnC;IAAE,CAAC,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,CAAC,EAAE,GAAG,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACtB;IAAE,CAAC,EAAE,GAAG,CAAA;CAAE,GACV;IAAE,CAAC,EAAE,GAAG,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1B;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAChD,YAAY,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACjD,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;CAClC;AAED,eAAO,MAAM,aAAa,EAAE,QAK3B,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CA2B5D;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAGjE;AAED,8FAA8F;AAC9F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,CAE9D;AAED,uDAAuD;AACvD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAe/C"}
1
+ {"version":3,"file":"frames.d.ts","sourceRoot":"","sources":["../../src/ws/frames.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,0EAA0E;AAC1E,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,qDAAqD;AACrD,eAAO,MAAM,oBAAoB,QAAS,CAAC;AAE3C,oEAAoE;AACpE,eAAO,MAAM,wBAAwB,OAAQ,CAAC;AAE9C,MAAM,MAAM,OAAO,GACf;IAAE,CAAC,EAAE,GAAG,CAAC;IAAC,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;CAAE,GACnC;IAAE,CAAC,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,CAAC,EAAE,GAAG,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACtB;IAAE,CAAC,EAAE,GAAG,CAAA;CAAE,GACV;IAAE,CAAC,EAAE,GAAG,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1B;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,UAAU,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IAChD,YAAY,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC,WAAW,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;IACjD,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;CAClC;AAED,eAAO,MAAM,aAAa,EAAE,QAK3B,CAAC;AAEF,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CA2B5D;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAGjE;AAED,8FAA8F;AAC9F,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,CAE9D;AAOD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW/C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/webrun-http-browser",
3
- "version": "0.12.1",
3
+ "version": "0.12.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "ServiceWorker-based HTTP and WebSocket server for browsers: handlers in pages and Dedicated Workers, same-origin and relay modes, streams with backpressure",
@@ -58,9 +58,9 @@
58
58
  ],
59
59
  "dependencies": {
60
60
  "idb-keyval": "^6.3.0",
61
- "@statewalker/webrun-streams": "^0.2.3",
62
- "@statewalker/webrun-rpc": "^0.10.0",
63
- "@statewalker/webrun-http-streams": "^0.4.0"
61
+ "@statewalker/webrun-http-streams": "^0.4.1",
62
+ "@statewalker/webrun-rpc": "^0.10.1",
63
+ "@statewalker/webrun-streams": "^0.2.3"
64
64
  },
65
65
  "devDependencies": {
66
66
  "@biomejs/biome": "^2.5.15",