@statewalker/webrun-http-browser 0.7.1 → 0.10.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 +716 -84
- package/dist/relay-sw.js +647 -90
- package/dist/relay-worker.js +647 -90
- package/dist/sw/protocol.d.ts +2 -0
- package/dist/sw/protocol.d.ts.map +1 -1
- package/dist/sw/sw-http-adapter.d.ts +2 -0
- package/dist/sw/sw-http-adapter.d.ts.map +1 -1
- package/dist/sw/worker-service.d.ts.map +1 -1
- package/dist/sw-worker.js +695 -81
- package/dist/sw.js +709 -83
- package/dist/worker.js +704 -82
- package/package.json +3 -3
- package/src/link/http-link.ts +42 -6
- package/src/sw/protocol.ts +4 -3
- package/src/sw/sw-http-adapter.ts +7 -0
- package/src/sw/worker-service.ts +14 -1
package/dist/sw-worker.js
CHANGED
|
@@ -1394,7 +1394,11 @@
|
|
|
1394
1394
|
*/
|
|
1395
1395
|
function httpServe(handler, options = {}) {
|
|
1396
1396
|
const codec = options.codec ?? defaultCodec;
|
|
1397
|
-
return
|
|
1397
|
+
return (input) => {
|
|
1398
|
+
const abandoned = new AbortController();
|
|
1399
|
+
return abortingOnReturn(exchange(input, { signal: abandoned.signal }), (reason) => abandoned.abort(reason ?? /* @__PURE__ */ new Error("webrun-http-streams: the exchange was abandoned before it was over")));
|
|
1400
|
+
};
|
|
1401
|
+
async function* exchange(input, context) {
|
|
1398
1402
|
let decoded;
|
|
1399
1403
|
try {
|
|
1400
1404
|
decoded = await codec.decodeRequest(input);
|
|
@@ -1406,7 +1410,7 @@
|
|
|
1406
1410
|
const replyCodec = decoded.codec ?? codec;
|
|
1407
1411
|
let result;
|
|
1408
1412
|
try {
|
|
1409
|
-
result = await handler(decoded.envelope, decoded.body);
|
|
1413
|
+
result = await handler(decoded.envelope, decoded.body, context);
|
|
1410
1414
|
} catch (error) {
|
|
1411
1415
|
yield* encodeErrorResponse(replyCodec, error, decoded.envelope.method);
|
|
1412
1416
|
return;
|
|
@@ -1421,7 +1425,36 @@
|
|
|
1421
1425
|
await result.release?.();
|
|
1422
1426
|
} catch {}
|
|
1423
1427
|
}
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
/**
|
|
1431
|
+
* `gen`, except that `return()` and `throw()` from its consumer call `abandon`
|
|
1432
|
+
* at once — before they are queued behind a `next()` still parked inside the
|
|
1433
|
+
* generator (the handler's `await`) — unless the generator already finished.
|
|
1434
|
+
*/
|
|
1435
|
+
function abortingOnReturn(gen, abandon) {
|
|
1436
|
+
let finished = false;
|
|
1437
|
+
const settle = (r) => {
|
|
1438
|
+
if (r.done) finished = true;
|
|
1439
|
+
return r;
|
|
1440
|
+
};
|
|
1441
|
+
const wrapped = {
|
|
1442
|
+
next: (...args) => gen.next(...args).then(settle),
|
|
1443
|
+
return(value) {
|
|
1444
|
+
if (!finished) abandon();
|
|
1445
|
+
finished = true;
|
|
1446
|
+
return gen.return(value);
|
|
1447
|
+
},
|
|
1448
|
+
throw(error) {
|
|
1449
|
+
if (!finished) abandon(error);
|
|
1450
|
+
finished = true;
|
|
1451
|
+
return gen.throw(error);
|
|
1452
|
+
},
|
|
1453
|
+
[Symbol.asyncIterator]() {
|
|
1454
|
+
return wrapped;
|
|
1455
|
+
}
|
|
1424
1456
|
};
|
|
1457
|
+
return asyncDisposable$2(wrapped, () => wrapped.return(void 0));
|
|
1425
1458
|
}
|
|
1426
1459
|
/**
|
|
1427
1460
|
* The one place that knows a runtime may not implement request body streams,
|
|
@@ -1653,17 +1686,18 @@
|
|
|
1653
1686
|
else await release?.();
|
|
1654
1687
|
return new Response(null, responseInit);
|
|
1655
1688
|
}
|
|
1656
|
-
return new Response(
|
|
1689
|
+
return new Response(responseBody(respBody, request.signal, release), responseInit);
|
|
1657
1690
|
}
|
|
1658
1691
|
/**
|
|
1659
1692
|
* Wrap a `(Request) => Promise<Response>` handler as a `Duplex` so it can be
|
|
1660
1693
|
* registered with any `webrun-streams-*` adapter's `serve`.
|
|
1661
1694
|
*/
|
|
1662
1695
|
function serveFetchOverDuplex(handler, options = {}) {
|
|
1663
|
-
return httpServe(async (env, body) => {
|
|
1696
|
+
return httpServe(async (env, body, exchange) => {
|
|
1664
1697
|
const reqInit = {
|
|
1665
1698
|
method: env.method,
|
|
1666
|
-
headers: forwardableHeaders(env.headers)
|
|
1699
|
+
headers: forwardableHeaders(env.headers),
|
|
1700
|
+
signal: exchange?.signal
|
|
1667
1701
|
};
|
|
1668
1702
|
let upload;
|
|
1669
1703
|
if (env.method !== "GET" && env.method !== "HEAD") {
|
|
@@ -1784,15 +1818,78 @@
|
|
|
1784
1818
|
settle
|
|
1785
1819
|
};
|
|
1786
1820
|
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1821
|
+
/**
|
|
1822
|
+
* How long a cancelled body waits for the read in flight before it abandons the call anyway.
|
|
1823
|
+
* The read's answer tells a body that has ended (the call is finished: an upload the handler
|
|
1824
|
+
* reads after answering goes on) from one that has not; a peer that sends nothing more never
|
|
1825
|
+
* answers it. A body whose end comes later than this after the cancel is abandoned too, so a
|
|
1826
|
+
* handler still streaming its response and still reading the upload then loses the upload.
|
|
1827
|
+
*/
|
|
1828
|
+
const CANCEL_GRACE_MS = 1e3;
|
|
1829
|
+
/**
|
|
1830
|
+
* The response body as a stream that the request's signal can end at any time.
|
|
1831
|
+
*
|
|
1832
|
+
* An abort errors the stream with the abort's reason at once — a read waiting
|
|
1833
|
+
* for a peer that sends nothing more rejects then, not when a next chunk
|
|
1834
|
+
* arrives, which may be never — and abandons the call (`release`), so on a
|
|
1835
|
+
* multiplexed transport its port is given back whether or not anyone still
|
|
1836
|
+
* reads the body. The listener is removed once the body ends, however it ends:
|
|
1837
|
+
* read to its end, cancelled, failed or aborted. A body nobody reads or cancels
|
|
1838
|
+
* keeps it until the signal aborts (and keeps the call open until then anyway).
|
|
1839
|
+
* A cancel abandons the call too — unless the read in flight comes back with the
|
|
1840
|
+
* body's end, which finishes it instead (see `cancel`).
|
|
1841
|
+
*/
|
|
1842
|
+
function responseBody(body, signal, release) {
|
|
1843
|
+
const it = body[Symbol.asyncIterator]();
|
|
1844
|
+
let controller;
|
|
1845
|
+
let ended = false;
|
|
1846
|
+
let reading = false;
|
|
1847
|
+
const end = () => {
|
|
1848
|
+
ended = true;
|
|
1849
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1850
|
+
};
|
|
1851
|
+
const onAbort = () => {
|
|
1852
|
+
if (ended) return;
|
|
1853
|
+
end();
|
|
1854
|
+
try {
|
|
1855
|
+
controller.error(abortReason$1(signal));
|
|
1856
|
+
} catch {}
|
|
1857
|
+
release?.();
|
|
1858
|
+
};
|
|
1859
|
+
return new ReadableStream({
|
|
1860
|
+
start(c) {
|
|
1861
|
+
controller = c;
|
|
1862
|
+
if (signal?.aborted) onAbort();
|
|
1863
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
1864
|
+
},
|
|
1865
|
+
async pull(c) {
|
|
1866
|
+
try {
|
|
1867
|
+
reading = true;
|
|
1868
|
+
const { done, value } = await it.next().finally(() => {
|
|
1869
|
+
reading = false;
|
|
1870
|
+
});
|
|
1871
|
+
if (ended) return;
|
|
1872
|
+
if (done) {
|
|
1873
|
+
end();
|
|
1874
|
+
c.close();
|
|
1875
|
+
return;
|
|
1876
|
+
}
|
|
1877
|
+
c.enqueue(value);
|
|
1878
|
+
} catch (err) {
|
|
1879
|
+
if (ended) return;
|
|
1880
|
+
end();
|
|
1881
|
+
c.error(err);
|
|
1882
|
+
}
|
|
1883
|
+
},
|
|
1884
|
+
cancel() {
|
|
1885
|
+
end();
|
|
1886
|
+
const returned = Promise.resolve(it.return?.()).catch(() => {});
|
|
1887
|
+
if (!reading) return;
|
|
1888
|
+
const grace = setTimeout(() => void release?.(), CANCEL_GRACE_MS);
|
|
1889
|
+
grace.unref?.();
|
|
1890
|
+
returned.finally(() => clearTimeout(grace));
|
|
1891
|
+
}
|
|
1892
|
+
});
|
|
1796
1893
|
}
|
|
1797
1894
|
//#endregion
|
|
1798
1895
|
//#region src/core/data-channels.ts
|
|
@@ -2120,8 +2217,11 @@
|
|
|
2120
2217
|
error
|
|
2121
2218
|
});
|
|
2122
2219
|
};
|
|
2123
|
-
|
|
2124
|
-
|
|
2220
|
+
const listener = (event) => {
|
|
2221
|
+
onMessage(event);
|
|
2222
|
+
};
|
|
2223
|
+
port.addEventListener("message", listener);
|
|
2224
|
+
return () => port.removeEventListener("message", listener);
|
|
2125
2225
|
}
|
|
2126
2226
|
/**
|
|
2127
2227
|
* Wraps an async iterable so that an `AbortSignal` firing causes the wrapper
|
|
@@ -2499,7 +2599,11 @@
|
|
|
2499
2599
|
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8).
|
|
2500
2600
|
*/
|
|
2501
2601
|
async function sendChunks(port, channelName, output, { maxMessageSize, log }, signal, touch) {
|
|
2502
|
-
const
|
|
2602
|
+
const framed = maxMessageSize ? toChunks(maxMessageSize)(output) : output;
|
|
2603
|
+
const stream = throughAbort(framed, signal);
|
|
2604
|
+
const producer = output;
|
|
2605
|
+
const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
|
|
2606
|
+
if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
|
|
2503
2607
|
try {
|
|
2504
2608
|
await sendIterator(async ({ done, value, error }) => {
|
|
2505
2609
|
if (signal.aborted) return;
|
|
@@ -2523,9 +2627,38 @@
|
|
|
2523
2627
|
} catch (err) {
|
|
2524
2628
|
if (signal.aborted) return;
|
|
2525
2629
|
throw err;
|
|
2630
|
+
} finally {
|
|
2631
|
+
signal.removeEventListener("abort", returnProducer);
|
|
2526
2632
|
}
|
|
2527
2633
|
}
|
|
2528
2634
|
/**
|
|
2635
|
+
* Runs `fn` on a later task, never a microtask: a producer looping on
|
|
2636
|
+
* `whenPortReady` must let the transport's own events run. `setImmediate`
|
|
2637
|
+
* where it exists; elsewhere a `MessageChannel`, because nested `setTimeout`s
|
|
2638
|
+
* are clamped to 4 ms in browsers.
|
|
2639
|
+
*/
|
|
2640
|
+
const setImmediateFn = globalThis.setImmediate;
|
|
2641
|
+
/** @internal */
|
|
2642
|
+
const nextTask = setImmediateFn ? (fn) => {
|
|
2643
|
+
setImmediateFn(fn);
|
|
2644
|
+
} : (() => {
|
|
2645
|
+
let channel;
|
|
2646
|
+
const queue = [];
|
|
2647
|
+
return (fn) => {
|
|
2648
|
+
if (!channel) {
|
|
2649
|
+
channel = new MessageChannel();
|
|
2650
|
+
channel.port1.onmessage = () => queue.shift()?.();
|
|
2651
|
+
}
|
|
2652
|
+
queue.push(fn);
|
|
2653
|
+
channel.port2.postMessage(0);
|
|
2654
|
+
};
|
|
2655
|
+
})();
|
|
2656
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
2657
|
+
/** @internal Registered by the multiplexer for each port with a window. */
|
|
2658
|
+
function setPortSendState(port, state) {
|
|
2659
|
+
states.set(port, state);
|
|
2660
|
+
}
|
|
2661
|
+
/**
|
|
2529
2662
|
* One virtual port.
|
|
2530
2663
|
*
|
|
2531
2664
|
* `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
|
|
@@ -2540,6 +2673,14 @@
|
|
|
2540
2673
|
if (closing.signal.aborted) return;
|
|
2541
2674
|
closing.abort(reason ?? /* @__PURE__ */ new Error("webrun-rpc: the port was closed"));
|
|
2542
2675
|
};
|
|
2676
|
+
const close = (reason) => {
|
|
2677
|
+
if (closed) return;
|
|
2678
|
+
closed = true;
|
|
2679
|
+
const notify = requestClose;
|
|
2680
|
+
listeners.clear();
|
|
2681
|
+
notify(reason);
|
|
2682
|
+
abort(reason);
|
|
2683
|
+
};
|
|
2543
2684
|
const port = {
|
|
2544
2685
|
addEventListener(_type, listener) {
|
|
2545
2686
|
listeners.add(listener);
|
|
@@ -2551,24 +2692,28 @@
|
|
|
2551
2692
|
if (closed) return;
|
|
2552
2693
|
send(message, transfer);
|
|
2553
2694
|
},
|
|
2554
|
-
close
|
|
2555
|
-
if (closed) return;
|
|
2556
|
-
closed = true;
|
|
2557
|
-
const notify = requestClose;
|
|
2558
|
-
listeners.clear();
|
|
2559
|
-
notify();
|
|
2560
|
-
abort();
|
|
2561
|
-
}
|
|
2695
|
+
close
|
|
2562
2696
|
};
|
|
2563
2697
|
setPortCloseSignal(port, closing.signal);
|
|
2698
|
+
const dispose = Symbol.dispose;
|
|
2699
|
+
if (dispose) Object.defineProperty(port, dispose, {
|
|
2700
|
+
value: () => close(),
|
|
2701
|
+
configurable: true
|
|
2702
|
+
});
|
|
2564
2703
|
return {
|
|
2565
2704
|
port,
|
|
2566
2705
|
deliver(payload) {
|
|
2567
2706
|
if (closed) return;
|
|
2568
2707
|
const event = new MessageEvent("message", { data: payload });
|
|
2708
|
+
let pending;
|
|
2569
2709
|
for (const listener of [...listeners]) try {
|
|
2570
|
-
listener(event);
|
|
2710
|
+
const result = listener(event);
|
|
2711
|
+
if (result && typeof result.then === "function") {
|
|
2712
|
+
pending ??= [];
|
|
2713
|
+
pending.push(result);
|
|
2714
|
+
}
|
|
2571
2715
|
} catch {}
|
|
2716
|
+
return pending ? Promise.allSettled(pending) : void 0;
|
|
2572
2717
|
},
|
|
2573
2718
|
markClosed(reason) {
|
|
2574
2719
|
closed = true;
|
|
@@ -2577,53 +2722,318 @@
|
|
|
2577
2722
|
},
|
|
2578
2723
|
isClosed() {
|
|
2579
2724
|
return closed;
|
|
2725
|
+
},
|
|
2726
|
+
hasListeners() {
|
|
2727
|
+
return listeners.size > 0;
|
|
2580
2728
|
}
|
|
2581
2729
|
};
|
|
2582
2730
|
}
|
|
2583
2731
|
/**
|
|
2584
|
-
* The
|
|
2585
|
-
*
|
|
2732
|
+
* The id of keepalive's `ping` / `pong`: one no port ever takes. An older mux
|
|
2733
|
+
* whose codec passes unknown envelopes through reads them as a close of that
|
|
2734
|
+
* id, which must therefore not be a real port (0 is the initiator's first).
|
|
2586
2735
|
*/
|
|
2587
|
-
const
|
|
2736
|
+
const KEEPALIVE_ID = Number.MAX_SAFE_INTEGER;
|
|
2737
|
+
/** The longest delay a timer holds; past it `setInterval` fires at once. */
|
|
2738
|
+
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
2588
2739
|
/** Ceiling on concurrently open virtual ports. Bounds the id table only. */
|
|
2589
2740
|
const DEFAULT_MAX_PORTS = 1024;
|
|
2590
2741
|
/**
|
|
2742
|
+
* What a message costs a port's window when the codec cannot say
|
|
2743
|
+
* (`PortCodec.encode`): its bytes and string lengths, found in its values up
|
|
2744
|
+
* to 4 levels deep (at most 256 values), and at least 64. Both ends compute it
|
|
2745
|
+
* from the same structured value, so they agree.
|
|
2746
|
+
*/
|
|
2747
|
+
function estimateMessageSize(payload) {
|
|
2748
|
+
let budget = 256;
|
|
2749
|
+
const walk = (v, depth) => {
|
|
2750
|
+
if (budget-- <= 0) return 0;
|
|
2751
|
+
if (typeof v === "string") return v.length;
|
|
2752
|
+
if (v instanceof ArrayBuffer) return v.byteLength;
|
|
2753
|
+
if (ArrayBuffer.isView(v)) return v.byteLength;
|
|
2754
|
+
if (v === null || typeof v !== "object" || depth >= 4) return 8;
|
|
2755
|
+
let n = 16;
|
|
2756
|
+
if (Array.isArray(v)) for (const x of v) n += walk(x, depth + 1);
|
|
2757
|
+
else for (const k in v) n += k.length + walk(v[k], depth + 1);
|
|
2758
|
+
return n;
|
|
2759
|
+
};
|
|
2760
|
+
return Math.max(64, walk(payload, 0));
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2591
2763
|
* The default `PortMux`: emulates multiplexing over a single port.
|
|
2592
2764
|
*
|
|
2593
2765
|
* A transport that already multiplexes natively supplies its own `PortMux`
|
|
2594
2766
|
* instead — this implementation is for transports that offer one pipe.
|
|
2595
2767
|
*/
|
|
2596
2768
|
function multiplexPort(port, options) {
|
|
2597
|
-
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize } = options;
|
|
2769
|
+
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
2770
|
+
const maxReassembledBytes = options.maxReassembledBytes ?? 67108864;
|
|
2771
|
+
const misses = keepalive?.misses ?? 3;
|
|
2772
|
+
if (keepalive && !(Number.isFinite(keepalive.intervalMs) && keepalive.intervalMs > 0 && keepalive.intervalMs <= MAX_TIMER_MS && Number.isSafeInteger(misses) && misses > 0)) throw new RangeError(`webrun-rpc: keepalive needs an intervalMs in (0, ${MAX_TIMER_MS}] and a positive integer misses`);
|
|
2773
|
+
if (frameSize != null && !(Number.isSafeInteger(frameSize) && frameSize > 0)) throw new RangeError(`webrun-rpc: frameSize must be a positive integer, got ${frameSize}`);
|
|
2774
|
+
if (!(Number.isSafeInteger(maxReassembledBytes) && maxReassembledBytes > 0)) throw new RangeError(`webrun-rpc: maxReassembledBytes must be a positive integer, got ${maxReassembledBytes}`);
|
|
2598
2775
|
const open = /* @__PURE__ */ new Map();
|
|
2776
|
+
let reassembling = 0;
|
|
2777
|
+
const freePartial = (entry) => {
|
|
2778
|
+
if (!entry?.partial) return;
|
|
2779
|
+
reassembling -= entry.partial.charged;
|
|
2780
|
+
entry.partial = void 0;
|
|
2781
|
+
};
|
|
2782
|
+
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
2783
|
+
const entry = open.get(id);
|
|
2784
|
+
if (entry?.handle !== record) return;
|
|
2785
|
+
const { handle: _, credit: __, partial: ___, ...info } = entry;
|
|
2786
|
+
if (!record.hasListeners()) {
|
|
2787
|
+
release("leaked");
|
|
2788
|
+
record.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the port was dropped without close()"));
|
|
2789
|
+
}
|
|
2790
|
+
try {
|
|
2791
|
+
onLeak?.(info);
|
|
2792
|
+
} catch {}
|
|
2793
|
+
}) : void 0;
|
|
2794
|
+
const touch = (id) => {
|
|
2795
|
+
const entry = open.get(id);
|
|
2796
|
+
if (entry) entry.lastActivity = Date.now();
|
|
2797
|
+
};
|
|
2599
2798
|
let nextId = side === "initiator" ? 0 : 1;
|
|
2600
2799
|
let muxClosed = false;
|
|
2800
|
+
/** This end counts encoded bytes and reassembles frames. */
|
|
2801
|
+
const exactCodec = codec.encode != null && codec.decode != null;
|
|
2601
2802
|
const post = (envelope, transfer) => {
|
|
2602
2803
|
if (muxClosed) return;
|
|
2804
|
+
codecPost(envelope, transfer);
|
|
2805
|
+
};
|
|
2806
|
+
/** Like `post`, also while closing: the mux's own close notices. */
|
|
2807
|
+
const codecPost = (envelope, transfer) => {
|
|
2603
2808
|
try {
|
|
2604
2809
|
codec.post(port, envelope, transfer);
|
|
2605
2810
|
} catch {}
|
|
2606
2811
|
};
|
|
2607
|
-
const
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2812
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
2813
|
+
const ring = [];
|
|
2814
|
+
let pumping = false;
|
|
2815
|
+
const pump = async () => {
|
|
2816
|
+
pumping = true;
|
|
2817
|
+
try {
|
|
2818
|
+
while (ring.length && !muxClosed) {
|
|
2819
|
+
if (port.whenDrained) await port.whenDrained();
|
|
2820
|
+
if (muxClosed) break;
|
|
2821
|
+
const id = ring.shift();
|
|
2822
|
+
const queue = outgoing.get(id);
|
|
2823
|
+
const item = queue?.shift();
|
|
2824
|
+
if (queue?.length) ring.push(id);
|
|
2825
|
+
else outgoing.delete(id);
|
|
2826
|
+
item?.();
|
|
2827
|
+
}
|
|
2828
|
+
} catch (err) {
|
|
2829
|
+
close(err).catch(() => {});
|
|
2830
|
+
} finally {
|
|
2831
|
+
pumping = false;
|
|
2832
|
+
}
|
|
2833
|
+
};
|
|
2834
|
+
const sendOnPort = (id, item) => {
|
|
2835
|
+
if (frameSize == null) {
|
|
2836
|
+
item();
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
let queue = outgoing.get(id);
|
|
2840
|
+
if (!queue) {
|
|
2841
|
+
queue = [];
|
|
2842
|
+
outgoing.set(id, queue);
|
|
2843
|
+
ring.push(id);
|
|
2844
|
+
}
|
|
2845
|
+
queue.push(item);
|
|
2846
|
+
if (!pumping) pump();
|
|
2847
|
+
};
|
|
2848
|
+
const dropOutgoing = (id) => {
|
|
2849
|
+
if (!outgoing.delete(id)) return;
|
|
2850
|
+
const at = ring.indexOf(id);
|
|
2851
|
+
if (at >= 0) ring.splice(at, 1);
|
|
2852
|
+
};
|
|
2853
|
+
const postBytes = (bytes) => {
|
|
2854
|
+
if (muxClosed) return;
|
|
2855
|
+
try {
|
|
2856
|
+
port.postMessage(bytes);
|
|
2857
|
+
} catch {}
|
|
2858
|
+
};
|
|
2859
|
+
const releaseWaiters = (c) => {
|
|
2860
|
+
for (const w of c?.waiters.splice(0) ?? []) w.resolve();
|
|
2861
|
+
};
|
|
2862
|
+
const flush = (c) => {
|
|
2863
|
+
while (c.queue.length) {
|
|
2864
|
+
const next = c.queue[0];
|
|
2865
|
+
if (c.enforced && c.outstanding > 0 && c.outstanding + next.size > c.window) break;
|
|
2866
|
+
c.queue.shift();
|
|
2867
|
+
c.queued -= next.size;
|
|
2868
|
+
c.outstanding += next.size;
|
|
2869
|
+
next.send();
|
|
2870
|
+
}
|
|
2871
|
+
for (const w of [...c.waiters]) if (c.queued <= w.below) {
|
|
2872
|
+
c.waiters.splice(c.waiters.indexOf(w), 1);
|
|
2873
|
+
w.resolve();
|
|
2874
|
+
}
|
|
2875
|
+
};
|
|
2876
|
+
const grant = (id, c, bytes) => {
|
|
2877
|
+
c.unconsumed -= bytes;
|
|
2878
|
+
c.toGrant += bytes;
|
|
2879
|
+
const send = () => {
|
|
2880
|
+
c.grantScheduled = false;
|
|
2881
|
+
if (!c.toGrant || !open.has(id)) return;
|
|
2614
2882
|
post({
|
|
2883
|
+
type: "credit",
|
|
2884
|
+
id,
|
|
2885
|
+
bytes: c.toGrant
|
|
2886
|
+
});
|
|
2887
|
+
c.toGrant = 0;
|
|
2888
|
+
};
|
|
2889
|
+
if (c.toGrant >= c.window / 4) {
|
|
2890
|
+
send();
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2893
|
+
if (c.grantScheduled) return;
|
|
2894
|
+
c.grantScheduled = true;
|
|
2895
|
+
setTimeout(send, 0);
|
|
2896
|
+
};
|
|
2897
|
+
const attach = (id, meta, portWindow, opener, exact) => {
|
|
2898
|
+
const c = typeof portWindow === "number" && Number.isSafeInteger(portWindow) && portWindow > 0 ? {
|
|
2899
|
+
window: portWindow,
|
|
2900
|
+
enforced: false,
|
|
2901
|
+
opener,
|
|
2902
|
+
exact,
|
|
2903
|
+
outstanding: 0,
|
|
2904
|
+
queue: [],
|
|
2905
|
+
queued: 0,
|
|
2906
|
+
unconsumed: 0,
|
|
2907
|
+
waiters: [],
|
|
2908
|
+
toGrant: 0,
|
|
2909
|
+
grantScheduled: false
|
|
2910
|
+
} : void 0;
|
|
2911
|
+
const self = newVirtualPort((payload, transfer) => {
|
|
2912
|
+
touch(id);
|
|
2913
|
+
const envelope = {
|
|
2914
|
+
type: "message",
|
|
2915
|
+
id,
|
|
2916
|
+
payload
|
|
2917
|
+
};
|
|
2918
|
+
if (!c) {
|
|
2919
|
+
post(envelope, transfer);
|
|
2920
|
+
return;
|
|
2921
|
+
}
|
|
2922
|
+
const bytes = c.exact ? codec.encode?.(envelope) : void 0;
|
|
2923
|
+
const size = bytes?.byteLength ?? estimateMessageSize(payload);
|
|
2924
|
+
const waits = frameSize != null || c.queue.length > 0 || c.enforced && c.outstanding > 0 && c.outstanding + size > c.window;
|
|
2925
|
+
const held = !bytes && waits ? {
|
|
2926
|
+
...envelope,
|
|
2927
|
+
payload: structuredClone(payload, { transfer })
|
|
2928
|
+
} : void 0;
|
|
2929
|
+
const send = (whole = false) => {
|
|
2930
|
+
if (!bytes) {
|
|
2931
|
+
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
2932
|
+
return;
|
|
2933
|
+
}
|
|
2934
|
+
if (!whole && frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2935
|
+
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
2936
|
+
const data = bytes.subarray(at, at + frameSize);
|
|
2937
|
+
const frame = at + frameSize >= bytes.byteLength ? {
|
|
2938
|
+
type: "frame",
|
|
2939
|
+
id,
|
|
2940
|
+
data,
|
|
2941
|
+
last: true
|
|
2942
|
+
} : {
|
|
2943
|
+
type: "frame",
|
|
2944
|
+
id,
|
|
2945
|
+
data
|
|
2946
|
+
};
|
|
2947
|
+
sendOnPort(id, () => post(frame));
|
|
2948
|
+
}
|
|
2949
|
+
return;
|
|
2950
|
+
}
|
|
2951
|
+
sendOnPort(id, () => postBytes(bytes));
|
|
2952
|
+
};
|
|
2953
|
+
c.queue.push({
|
|
2954
|
+
send,
|
|
2955
|
+
size
|
|
2956
|
+
});
|
|
2957
|
+
c.queued += size;
|
|
2958
|
+
flush(c);
|
|
2959
|
+
}, (reason) => {
|
|
2960
|
+
release(reason);
|
|
2961
|
+
record.markClosed(reason);
|
|
2962
|
+
});
|
|
2963
|
+
function release(reason) {
|
|
2964
|
+
freePartial(open.get(id));
|
|
2965
|
+
open.delete(id);
|
|
2966
|
+
leaks?.unregister(record);
|
|
2967
|
+
if (c?.queue.length) {
|
|
2968
|
+
for (const item of c.queue.splice(0)) item.send(true);
|
|
2969
|
+
c.queued = 0;
|
|
2970
|
+
}
|
|
2971
|
+
sendOnPort(id, () => post({
|
|
2615
2972
|
type: "close",
|
|
2616
2973
|
id,
|
|
2617
2974
|
reason
|
|
2975
|
+
}));
|
|
2976
|
+
releaseWaiters(c);
|
|
2977
|
+
}
|
|
2978
|
+
const { markClosed, isClosed, hasListeners } = self;
|
|
2979
|
+
if (c) {
|
|
2980
|
+
const deliver = self.deliver;
|
|
2981
|
+
self.deliver = (payload, size = estimateMessageSize(payload)) => {
|
|
2982
|
+
c.unconsumed += size;
|
|
2983
|
+
const done = deliver(payload);
|
|
2984
|
+
if (done) done.then(() => grant(id, c, size));
|
|
2985
|
+
else grant(id, c, size);
|
|
2986
|
+
return done;
|
|
2987
|
+
};
|
|
2988
|
+
setPortSendState(self.port, {
|
|
2989
|
+
bufferedAmount: () => c.queued,
|
|
2990
|
+
ready: (below = 0) => new Promise((resolve) => {
|
|
2991
|
+
if (c.queued <= below || isClosed()) nextTask(resolve);
|
|
2992
|
+
else c.waiters.push({
|
|
2993
|
+
below,
|
|
2994
|
+
resolve: () => nextTask(resolve)
|
|
2995
|
+
});
|
|
2996
|
+
})
|
|
2618
2997
|
});
|
|
2619
|
-
|
|
2620
|
-
|
|
2998
|
+
}
|
|
2999
|
+
const record = {
|
|
3000
|
+
deliver: self.deliver,
|
|
3001
|
+
markClosed,
|
|
3002
|
+
isClosed,
|
|
3003
|
+
hasListeners,
|
|
3004
|
+
close: (reason) => {
|
|
3005
|
+
if (isClosed()) return;
|
|
3006
|
+
release(reason);
|
|
3007
|
+
markClosed(reason);
|
|
3008
|
+
}
|
|
3009
|
+
};
|
|
3010
|
+
leaks?.register(self.port, {
|
|
3011
|
+
id,
|
|
3012
|
+
record,
|
|
3013
|
+
release
|
|
3014
|
+
}, record);
|
|
3015
|
+
const now = Date.now();
|
|
3016
|
+
open.set(id, {
|
|
3017
|
+
id,
|
|
3018
|
+
meta,
|
|
3019
|
+
openedAt: now,
|
|
3020
|
+
lastActivity: now,
|
|
3021
|
+
handle: record,
|
|
3022
|
+
credit: c
|
|
2621
3023
|
});
|
|
2622
|
-
open.set(id, self);
|
|
2623
3024
|
return self;
|
|
2624
3025
|
};
|
|
2625
|
-
const handleEnvelope = (envelope) => {
|
|
3026
|
+
const handleEnvelope = (envelope, event) => {
|
|
2626
3027
|
if (muxClosed) return;
|
|
3028
|
+
heardSincePing = true;
|
|
3029
|
+
if (envelope.type === "ping" || envelope.type === "pong") {
|
|
3030
|
+
peerKeepsAlive = true;
|
|
3031
|
+
if (envelope.type === "ping") post({
|
|
3032
|
+
type: "pong",
|
|
3033
|
+
id: KEEPALIVE_ID
|
|
3034
|
+
});
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
2627
3037
|
if (envelope.type === "open") {
|
|
2628
3038
|
if (open.has(envelope.id)) return;
|
|
2629
3039
|
if (open.size >= maxPorts) {
|
|
@@ -2634,7 +3044,18 @@
|
|
|
2634
3044
|
});
|
|
2635
3045
|
return;
|
|
2636
3046
|
}
|
|
2637
|
-
const handle = attach(envelope.id);
|
|
3047
|
+
const handle = attach(envelope.id, envelope.meta, envelope.window, false, envelope.exact === true && exactCodec);
|
|
3048
|
+
const credit = open.get(envelope.id)?.credit;
|
|
3049
|
+
if (credit) post(credit.exact ? {
|
|
3050
|
+
type: "credit",
|
|
3051
|
+
id: envelope.id,
|
|
3052
|
+
bytes: 0,
|
|
3053
|
+
exact: true
|
|
3054
|
+
} : {
|
|
3055
|
+
type: "credit",
|
|
3056
|
+
id: envelope.id,
|
|
3057
|
+
bytes: 0
|
|
3058
|
+
});
|
|
2638
3059
|
let accepted = false;
|
|
2639
3060
|
if (onPort) try {
|
|
2640
3061
|
accepted = onPort(handle.port, envelope.meta) !== false;
|
|
@@ -2649,57 +3070,215 @@
|
|
|
2649
3070
|
id: envelope.id,
|
|
2650
3071
|
reason: "rejected"
|
|
2651
3072
|
});
|
|
3073
|
+
return;
|
|
2652
3074
|
}
|
|
2653
3075
|
return;
|
|
2654
3076
|
}
|
|
2655
|
-
const
|
|
3077
|
+
const entry = open.get(envelope.id);
|
|
3078
|
+
if (envelope.type === "credit") {
|
|
3079
|
+
const c = entry?.credit;
|
|
3080
|
+
if (!c || !(envelope.bytes >= 0)) return;
|
|
3081
|
+
if (!c.enforced && c.opener) {
|
|
3082
|
+
if (c.exact && envelope.exact !== true) {
|
|
3083
|
+
c.exact = false;
|
|
3084
|
+
c.outstanding = 0;
|
|
3085
|
+
}
|
|
3086
|
+
post({
|
|
3087
|
+
type: "credit",
|
|
3088
|
+
id: envelope.id,
|
|
3089
|
+
bytes: 0
|
|
3090
|
+
});
|
|
3091
|
+
}
|
|
3092
|
+
c.enforced = true;
|
|
3093
|
+
c.outstanding = Math.max(0, c.outstanding - envelope.bytes);
|
|
3094
|
+
flush(c);
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
2656
3097
|
if (envelope.type === "message") {
|
|
2657
|
-
|
|
2658
|
-
|
|
3098
|
+
const sizeOf = (c) => (c.exact ? byteLengthOf(event.data) : void 0) ?? estimateMessageSize(envelope.payload);
|
|
3099
|
+
if (!entry) return;
|
|
3100
|
+
touch(envelope.id);
|
|
3101
|
+
entry.handle.deliver(envelope.payload, entry.credit ? sizeOf(entry.credit) : void 0);
|
|
2659
3102
|
return;
|
|
2660
3103
|
}
|
|
2661
|
-
if (
|
|
3104
|
+
if (envelope.type === "frame") {
|
|
3105
|
+
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
3106
|
+
touch(envelope.id);
|
|
3107
|
+
const c = entry.credit;
|
|
3108
|
+
const owed = c.unconsumed + c.toGrant;
|
|
3109
|
+
const partial = entry.partial ?? {
|
|
3110
|
+
chunks: [],
|
|
3111
|
+
bytes: 0,
|
|
3112
|
+
owed,
|
|
3113
|
+
charged: 0,
|
|
3114
|
+
...owed > 0 ? { room: c.window - owed } : {}
|
|
3115
|
+
};
|
|
3116
|
+
entry.partial = partial;
|
|
3117
|
+
const size = envelope.data.byteLength;
|
|
3118
|
+
const charged = partial.room == null ? Math.max(0, partial.bytes + size - c.window) : 0;
|
|
3119
|
+
const overrun = partial.room != null && partial.bytes + size > partial.room ? `what the window left (${partial.room} of ${c.window} bytes, ${partial.owed} still owed)` : reassembling - partial.charged + charged > maxReassembledBytes ? `maxReassembledBytes (${maxReassembledBytes} bytes past the window, across all ports)` : void 0;
|
|
3120
|
+
if (overrun) {
|
|
3121
|
+
freePartial(entry);
|
|
3122
|
+
dropOutgoing(envelope.id);
|
|
3123
|
+
open.delete(envelope.id);
|
|
3124
|
+
releaseWaiters(c);
|
|
3125
|
+
entry.handle.markClosed(/* @__PURE__ */ new RangeError(`webrun-rpc: a framed message passed ${overrun}`));
|
|
3126
|
+
post({
|
|
3127
|
+
type: "close",
|
|
3128
|
+
id: envelope.id,
|
|
3129
|
+
reason: "max-reassembled-bytes"
|
|
3130
|
+
});
|
|
3131
|
+
return;
|
|
3132
|
+
}
|
|
3133
|
+
partial.bytes += size;
|
|
3134
|
+
reassembling += charged - partial.charged;
|
|
3135
|
+
partial.charged = charged;
|
|
3136
|
+
partial.chunks.push(envelope.data);
|
|
3137
|
+
if (!envelope.last) return;
|
|
3138
|
+
freePartial(entry);
|
|
3139
|
+
const whole = new Uint8Array(partial.bytes);
|
|
3140
|
+
let at = 0;
|
|
3141
|
+
for (const chunk of partial.chunks) {
|
|
3142
|
+
whole.set(chunk, at);
|
|
3143
|
+
at += chunk.byteLength;
|
|
3144
|
+
}
|
|
3145
|
+
const inner = codec.decode(whole);
|
|
3146
|
+
if (inner?.type !== "message" || inner.id !== envelope.id) return;
|
|
3147
|
+
entry.handle.deliver(inner.payload, whole.byteLength);
|
|
3148
|
+
return;
|
|
3149
|
+
}
|
|
3150
|
+
dropOutgoing(envelope.id);
|
|
3151
|
+
if (!entry) return;
|
|
3152
|
+
freePartial(entry);
|
|
2662
3153
|
open.delete(envelope.id);
|
|
2663
|
-
|
|
3154
|
+
releaseWaiters(entry.credit);
|
|
3155
|
+
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
|
2664
3156
|
};
|
|
2665
3157
|
const listener = (event) => {
|
|
2666
3158
|
const envelope = codec.read(event);
|
|
2667
|
-
if (envelope) handleEnvelope(envelope);
|
|
3159
|
+
if (envelope) handleEnvelope(envelope, event);
|
|
3160
|
+
};
|
|
3161
|
+
let peerKeepsAlive = false;
|
|
3162
|
+
let heardSincePing = true;
|
|
3163
|
+
let unanswered = 0;
|
|
3164
|
+
let pinger;
|
|
3165
|
+
const tick = () => {
|
|
3166
|
+
if (!keepalive) return;
|
|
3167
|
+
const backlogged = (port.bufferedAmount ?? 0) > 0;
|
|
3168
|
+
if (heardSincePing) unanswered = 0;
|
|
3169
|
+
else if (peerKeepsAlive && !backlogged) unanswered++;
|
|
3170
|
+
if (unanswered >= misses) {
|
|
3171
|
+
close(/* @__PURE__ */ new Error(`webrun-rpc: the peer did not answer ${misses} keepalive pings, ${keepalive.intervalMs} ms apart`)).catch(() => {});
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3174
|
+
heardSincePing = false;
|
|
3175
|
+
post({
|
|
3176
|
+
type: "ping",
|
|
3177
|
+
id: KEEPALIVE_ID
|
|
3178
|
+
});
|
|
3179
|
+
};
|
|
3180
|
+
const close = async (reason) => {
|
|
3181
|
+
if (muxClosed) return;
|
|
3182
|
+
clearInterval(pinger);
|
|
3183
|
+
muxClosed = true;
|
|
3184
|
+
signal?.removeEventListener("abort", onTransportAbort);
|
|
3185
|
+
port.removeEventListener("message", listener);
|
|
3186
|
+
const closedNow = /* @__PURE__ */ new Set();
|
|
3187
|
+
for (const [id, { handle, credit }] of [...open]) {
|
|
3188
|
+
closedNow.add(id);
|
|
3189
|
+
codecPost({
|
|
3190
|
+
type: "close",
|
|
3191
|
+
id
|
|
3192
|
+
});
|
|
3193
|
+
open.delete(id);
|
|
3194
|
+
releaseWaiters(credit);
|
|
3195
|
+
handle.markClosed(reason ?? /* @__PURE__ */ new Error("webrun-rpc: the multiplexer was closed"));
|
|
3196
|
+
}
|
|
3197
|
+
for (const id of outgoing.keys()) if (!closedNow.has(id)) codecPost({
|
|
3198
|
+
type: "close",
|
|
3199
|
+
id
|
|
3200
|
+
});
|
|
3201
|
+
outgoing.clear();
|
|
3202
|
+
ring.length = 0;
|
|
3203
|
+
await port.close?.();
|
|
3204
|
+
};
|
|
3205
|
+
const onTransportAbort = () => {
|
|
3206
|
+
close(signal?.reason).catch(() => {});
|
|
2668
3207
|
};
|
|
2669
3208
|
port.addEventListener("message", listener);
|
|
2670
3209
|
port.start?.();
|
|
3210
|
+
if (keepalive) {
|
|
3211
|
+
pinger = setInterval(tick, keepalive.intervalMs);
|
|
3212
|
+
pinger.unref?.();
|
|
3213
|
+
}
|
|
3214
|
+
if (signal?.aborted) close(signal.reason).catch(() => {});
|
|
3215
|
+
else signal?.addEventListener("abort", onTransportAbort, { once: true });
|
|
2671
3216
|
return {
|
|
2672
3217
|
maxMessageSize,
|
|
2673
|
-
async openPort(meta) {
|
|
3218
|
+
async openPort(meta, { signal: portSignal } = {}) {
|
|
2674
3219
|
if (muxClosed) throw new Error("webrun-rpc: the multiplexer is closed");
|
|
2675
|
-
if (
|
|
3220
|
+
if (portSignal?.aborted) throw portSignal.reason;
|
|
3221
|
+
if (open.size >= maxPorts) throw new RangeError(`webrun-rpc: maxPorts (${maxPorts}) reached; open ports by kind: ${describeKinds(open.values())}`);
|
|
2676
3222
|
while (open.has(nextId)) nextId += 2;
|
|
2677
3223
|
const id = nextId;
|
|
2678
3224
|
nextId += 2;
|
|
2679
|
-
const handle = attach(id);
|
|
2680
|
-
post({
|
|
3225
|
+
const handle = attach(id, meta, window, true, exactCodec);
|
|
3226
|
+
post(window ? exactCodec ? {
|
|
3227
|
+
type: "open",
|
|
3228
|
+
id,
|
|
3229
|
+
meta,
|
|
3230
|
+
window,
|
|
3231
|
+
exact: true
|
|
3232
|
+
} : {
|
|
3233
|
+
type: "open",
|
|
3234
|
+
id,
|
|
3235
|
+
meta,
|
|
3236
|
+
window
|
|
3237
|
+
} : {
|
|
2681
3238
|
type: "open",
|
|
2682
3239
|
id,
|
|
2683
3240
|
meta
|
|
2684
3241
|
});
|
|
3242
|
+
if (portSignal) {
|
|
3243
|
+
const record = open.get(id)?.handle;
|
|
3244
|
+
const onAbort = () => {
|
|
3245
|
+
if (record && open.get(id)?.handle === record) record.close(portSignal.reason);
|
|
3246
|
+
};
|
|
3247
|
+
portSignal.addEventListener("abort", onAbort, { once: true });
|
|
3248
|
+
getPortCloseSignal(handle.port)?.addEventListener("abort", () => portSignal.removeEventListener("abort", onAbort), { once: true });
|
|
3249
|
+
}
|
|
2685
3250
|
return handle.port;
|
|
2686
3251
|
},
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
}
|
|
2697
|
-
muxClosed = true;
|
|
2698
|
-
port.removeEventListener("message", listener);
|
|
2699
|
-
await port.close?.();
|
|
3252
|
+
close,
|
|
3253
|
+
ports() {
|
|
3254
|
+
return [...open.values()].map(({ handle: _, credit, ...info }) => credit ? {
|
|
3255
|
+
...info,
|
|
3256
|
+
...credit.enforced ? { window: credit.window } : {},
|
|
3257
|
+
queued: credit.queued,
|
|
3258
|
+
outstanding: credit.outstanding,
|
|
3259
|
+
unconsumed: credit.unconsumed
|
|
3260
|
+
} : info);
|
|
2700
3261
|
}
|
|
2701
3262
|
};
|
|
2702
3263
|
}
|
|
3264
|
+
/** The byte length of a received message's data, or `undefined` if it is not bytes. */
|
|
3265
|
+
function byteLengthOf(data) {
|
|
3266
|
+
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) return data.byteLength;
|
|
3267
|
+
}
|
|
3268
|
+
/**
|
|
3269
|
+
* `"http: 1000, ws: 24"`: the three commonest `meta.kind`s among `entries`, so
|
|
3270
|
+
* a full table names who filled it. A port without a kind counts as `"?"`.
|
|
3271
|
+
*/
|
|
3272
|
+
function describeKinds(entries) {
|
|
3273
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3274
|
+
for (const { meta } of entries) {
|
|
3275
|
+
const kind = meta?.kind;
|
|
3276
|
+
const name = typeof kind === "string" ? kind : "?";
|
|
3277
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
3278
|
+
}
|
|
3279
|
+
if (!counts.size) return "none";
|
|
3280
|
+
return [...counts].sort((x, y) => y[1] - x[1]).slice(0, 3).map(([name, n]) => `${name}: ${n}`).join(", ");
|
|
3281
|
+
}
|
|
2703
3282
|
/**
|
|
2704
3283
|
* Announced with every port this pair opens. Layer 1 never reads `meta` — it is
|
|
2705
3284
|
* here so a peer running something else over the same multiplexer can tell a
|
|
@@ -2761,14 +3340,17 @@
|
|
|
2761
3340
|
const mux = await factory((port) => {
|
|
2762
3341
|
let off;
|
|
2763
3342
|
let finished = false;
|
|
2764
|
-
const tracked = (input) =>
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
3343
|
+
const tracked = (input) => {
|
|
3344
|
+
const output = handler(input);
|
|
3345
|
+
return returningAtOnce((async function* () {
|
|
3346
|
+
try {
|
|
3347
|
+
yield* output;
|
|
3348
|
+
} finally {
|
|
3349
|
+
finished = true;
|
|
3350
|
+
if (off) live.delete(off);
|
|
3351
|
+
}
|
|
3352
|
+
})(), () => void output.return(void 0).catch(() => {}));
|
|
3353
|
+
};
|
|
2772
3354
|
off = serveDuplexOverPort(port, tracked, streamOptions);
|
|
2773
3355
|
if (!finished) live.add(off);
|
|
2774
3356
|
});
|
|
@@ -2806,7 +3388,7 @@
|
|
|
2806
3388
|
const candidate = value;
|
|
2807
3389
|
if (typeof candidate.id !== "number") return false;
|
|
2808
3390
|
if (!Number.isInteger(candidate.id) || candidate.id < 0) return false;
|
|
2809
|
-
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close";
|
|
3391
|
+
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close" || candidate.type === "credit" || candidate.type === "frame" || candidate.type === "ping" || candidate.type === "pong";
|
|
2810
3392
|
}
|
|
2811
3393
|
/**
|
|
2812
3394
|
* For ports whose messages are structured values — a real `MessagePort`, a
|
|
@@ -2968,7 +3550,7 @@
|
|
|
2968
3550
|
finish();
|
|
2969
3551
|
return response;
|
|
2970
3552
|
}
|
|
2971
|
-
return new Response(guardBody(response.body, lost, () => lostReason, finish), {
|
|
3553
|
+
return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
|
|
2972
3554
|
status: response.status,
|
|
2973
3555
|
statusText: response.statusText,
|
|
2974
3556
|
headers: response.headers
|
|
@@ -2987,33 +3569,65 @@
|
|
|
2987
3569
|
return asyncDisposable(link, () => link.close());
|
|
2988
3570
|
}
|
|
2989
3571
|
/**
|
|
2990
|
-
* A response body that reports its end (done, cancelled or
|
|
2991
|
-
* with the link's reason when the link is lost mid-stream.
|
|
3572
|
+
* A response body that reports its end (done, cancelled, failed or aborted) and
|
|
3573
|
+
* fails with the link's reason when the link is lost mid-stream. An abort of the
|
|
3574
|
+
* request's signal ends it at once, read or not: the exchange is over (the layer
|
|
3575
|
+
* below has given its port back), so the request leaves `inflight` then.
|
|
2992
3576
|
*/
|
|
2993
|
-
function guardBody(body, lost, reason, finish) {
|
|
3577
|
+
function guardBody(body, lost, reason, finish, signal) {
|
|
2994
3578
|
const reader = body.getReader();
|
|
3579
|
+
let controller;
|
|
3580
|
+
let ended = false;
|
|
3581
|
+
const end = () => {
|
|
3582
|
+
ended = true;
|
|
3583
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3584
|
+
finish();
|
|
3585
|
+
};
|
|
3586
|
+
const onAbort = () => {
|
|
3587
|
+
if (ended) return;
|
|
3588
|
+
end();
|
|
3589
|
+
reader.cancel().catch(() => {});
|
|
3590
|
+
try {
|
|
3591
|
+
controller.error(abortError(signal));
|
|
3592
|
+
} catch {}
|
|
3593
|
+
};
|
|
2995
3594
|
return new ReadableStream({
|
|
3595
|
+
start(c) {
|
|
3596
|
+
controller = c;
|
|
3597
|
+
if (signal?.aborted) onAbort();
|
|
3598
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
3599
|
+
},
|
|
2996
3600
|
async pull(controller) {
|
|
2997
3601
|
try {
|
|
2998
3602
|
const { done, value } = await Promise.race([reader.read(), lost]);
|
|
3603
|
+
if (ended) return;
|
|
2999
3604
|
if (done) {
|
|
3000
|
-
|
|
3605
|
+
end();
|
|
3001
3606
|
controller.close();
|
|
3002
3607
|
return;
|
|
3003
3608
|
}
|
|
3004
3609
|
controller.enqueue(value);
|
|
3005
3610
|
} catch (error) {
|
|
3006
|
-
|
|
3611
|
+
if (ended) return;
|
|
3612
|
+
end();
|
|
3007
3613
|
reader.cancel().catch(() => {});
|
|
3008
3614
|
controller.error(reason() ?? error);
|
|
3009
3615
|
}
|
|
3010
3616
|
},
|
|
3011
3617
|
async cancel(why) {
|
|
3012
|
-
|
|
3618
|
+
end();
|
|
3013
3619
|
await reader.cancel(why).catch(() => {});
|
|
3014
3620
|
}
|
|
3015
3621
|
});
|
|
3016
3622
|
}
|
|
3623
|
+
/** The signal's reason as an `Error` (`AbortError` when it gave none). */
|
|
3624
|
+
function abortError(signal) {
|
|
3625
|
+
const why = signal.reason;
|
|
3626
|
+
if (why instanceof Error) return why;
|
|
3627
|
+
const err = new Error(why === void 0 ? "Aborted" : String(why));
|
|
3628
|
+
err.name = "AbortError";
|
|
3629
|
+
return err;
|
|
3630
|
+
}
|
|
3017
3631
|
//#endregion
|
|
3018
3632
|
//#region src/link/locks.ts
|
|
3019
3633
|
function lockManager() {
|