@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/index.js +98 -64
- package/dist/link/http-link.d.ts.map +1 -1
- package/dist/relay-sw.js +72 -45
- package/dist/relay-worker.js +72 -45
- package/dist/sw/worker-service.d.ts.map +1 -1
- package/dist/sw-worker.js +72 -45
- package/dist/sw.js +98 -64
- package/dist/worker.js +98 -64
- package/dist/ws/frames.d.ts +6 -1
- package/dist/ws/frames.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/link/http-link.ts +41 -26
- package/src/sw/worker-service.ts +12 -1
- package/src/ws/frames.ts +18 -12
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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)
|
|
3614
|
-
*
|
|
3615
|
-
*
|
|
3616
|
-
*
|
|
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,
|
|
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
|
|
3653
|
+
const fail = (error) => {
|
|
3628
3654
|
if (ended) return;
|
|
3629
3655
|
end();
|
|
3630
3656
|
reader.cancel().catch(() => {});
|
|
3631
3657
|
try {
|
|
3632
|
-
controller.error(
|
|
3658
|
+
controller.error(error);
|
|
3633
3659
|
} catch {}
|
|
3634
3660
|
};
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
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
|
-
|
|
3651
|
-
|
|
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
|
-
|
|
3659
|
-
|
|
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
|
-
|
|
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
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
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()),
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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)
|
|
3500
|
-
*
|
|
3501
|
-
*
|
|
3502
|
-
*
|
|
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,
|
|
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
|
|
3539
|
+
const fail = (error) => {
|
|
3514
3540
|
if (ended) return;
|
|
3515
3541
|
end();
|
|
3516
3542
|
reader.cancel().catch(() => {});
|
|
3517
3543
|
try {
|
|
3518
|
-
controller.error(
|
|
3544
|
+
controller.error(error);
|
|
3519
3545
|
} catch {}
|
|
3520
3546
|
};
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
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
|
-
|
|
3537
|
-
|
|
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
|
-
|
|
3545
|
-
|
|
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
|
-
|
|
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
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
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()),
|
|
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,
|
package/dist/ws/frames.d.ts
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
package/dist/ws/frames.d.ts.map
CHANGED
|
@@ -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;
|
|
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.
|
|
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.
|
|
62
|
-
"@statewalker/webrun-rpc": "^0.10.
|
|
63
|
-
"@statewalker/webrun-
|
|
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",
|