@statewalker/webrun-http-browser 0.9.0 → 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 +102 -20
- package/dist/relay-sw.js +46 -7
- package/dist/relay-worker.js +46 -7
- package/dist/sw-worker.js +95 -19
- package/dist/sw.js +95 -19
- package/dist/worker.js +95 -19
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -2126,6 +2126,12 @@ function asyncDisposable$2(target, dispose) {
|
|
|
2126
2126
|
}
|
|
2127
2127
|
/** Carries the serialized JS error between two webrun peers. */
|
|
2128
2128
|
const PEER_ERROR_HEADER = "x-webrun-error";
|
|
2129
|
+
/**
|
|
2130
|
+
* The reason of `HttpExchange.signal` when the exchange is abandoned with no
|
|
2131
|
+
* error of its own (`return()`); one abandoned by `throw(error)` aborts with
|
|
2132
|
+
* that error.
|
|
2133
|
+
*/
|
|
2134
|
+
const EXCHANGE_ABANDONED_MESSAGE = "webrun-http-streams: the exchange was abandoned before it was over";
|
|
2129
2135
|
/** Hard cap on the peer-error header (M3): see `encodeErrorResponse`. */
|
|
2130
2136
|
const MAX_DETAIL_CHARS = 4096;
|
|
2131
2137
|
/**
|
|
@@ -2306,7 +2312,11 @@ async function drain(output) {
|
|
|
2306
2312
|
*/
|
|
2307
2313
|
function httpServe(handler, options = {}) {
|
|
2308
2314
|
const codec = options.codec ?? defaultCodec;
|
|
2309
|
-
return
|
|
2315
|
+
return (input) => {
|
|
2316
|
+
const abandoned = new AbortController();
|
|
2317
|
+
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")));
|
|
2318
|
+
};
|
|
2319
|
+
async function* exchange(input, context) {
|
|
2310
2320
|
let decoded;
|
|
2311
2321
|
try {
|
|
2312
2322
|
decoded = await codec.decodeRequest(input);
|
|
@@ -2318,7 +2328,7 @@ function httpServe(handler, options = {}) {
|
|
|
2318
2328
|
const replyCodec = decoded.codec ?? codec;
|
|
2319
2329
|
let result;
|
|
2320
2330
|
try {
|
|
2321
|
-
result = await handler(decoded.envelope, decoded.body);
|
|
2331
|
+
result = await handler(decoded.envelope, decoded.body, context);
|
|
2322
2332
|
} catch (error) {
|
|
2323
2333
|
yield* encodeErrorResponse(replyCodec, error, decoded.envelope.method);
|
|
2324
2334
|
return;
|
|
@@ -2333,7 +2343,36 @@ function httpServe(handler, options = {}) {
|
|
|
2333
2343
|
await result.release?.();
|
|
2334
2344
|
} catch {}
|
|
2335
2345
|
}
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
/**
|
|
2349
|
+
* `gen`, except that `return()` and `throw()` from its consumer call `abandon`
|
|
2350
|
+
* at once — before they are queued behind a `next()` still parked inside the
|
|
2351
|
+
* generator (the handler's `await`) — unless the generator already finished.
|
|
2352
|
+
*/
|
|
2353
|
+
function abortingOnReturn(gen, abandon) {
|
|
2354
|
+
let finished = false;
|
|
2355
|
+
const settle = (r) => {
|
|
2356
|
+
if (r.done) finished = true;
|
|
2357
|
+
return r;
|
|
2358
|
+
};
|
|
2359
|
+
const wrapped = {
|
|
2360
|
+
next: (...args) => gen.next(...args).then(settle),
|
|
2361
|
+
return(value) {
|
|
2362
|
+
if (!finished) abandon();
|
|
2363
|
+
finished = true;
|
|
2364
|
+
return gen.return(value);
|
|
2365
|
+
},
|
|
2366
|
+
throw(error) {
|
|
2367
|
+
if (!finished) abandon(error);
|
|
2368
|
+
finished = true;
|
|
2369
|
+
return gen.throw(error);
|
|
2370
|
+
},
|
|
2371
|
+
[Symbol.asyncIterator]() {
|
|
2372
|
+
return wrapped;
|
|
2373
|
+
}
|
|
2336
2374
|
};
|
|
2375
|
+
return asyncDisposable$2(wrapped, () => wrapped.return(void 0));
|
|
2337
2376
|
}
|
|
2338
2377
|
/**
|
|
2339
2378
|
* The one place that knows a runtime may not implement request body streams,
|
|
@@ -2605,10 +2644,11 @@ async function fetchOverDuplex(call, request, options = {}) {
|
|
|
2605
2644
|
* registered with any `webrun-streams-*` adapter's `serve`.
|
|
2606
2645
|
*/
|
|
2607
2646
|
function serveFetchOverDuplex(handler, options = {}) {
|
|
2608
|
-
return httpServe(async (env, body) => {
|
|
2647
|
+
return httpServe(async (env, body, exchange) => {
|
|
2609
2648
|
const reqInit = {
|
|
2610
2649
|
method: env.method,
|
|
2611
|
-
headers: forwardableHeaders(env.headers)
|
|
2650
|
+
headers: forwardableHeaders(env.headers),
|
|
2651
|
+
signal: exchange?.signal
|
|
2612
2652
|
};
|
|
2613
2653
|
let upload;
|
|
2614
2654
|
if (env.method !== "GET" && env.method !== "HEAD") {
|
|
@@ -3469,7 +3509,11 @@ function receiveChunks(port, channelName, controller, touch) {
|
|
|
3469
3509
|
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8).
|
|
3470
3510
|
*/
|
|
3471
3511
|
async function sendChunks(port, channelName, output, { maxMessageSize, log }, signal, touch) {
|
|
3472
|
-
const
|
|
3512
|
+
const framed = maxMessageSize ? toChunks(maxMessageSize)(output) : output;
|
|
3513
|
+
const stream = throughAbort(framed, signal);
|
|
3514
|
+
const producer = output;
|
|
3515
|
+
const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
|
|
3516
|
+
if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
|
|
3473
3517
|
try {
|
|
3474
3518
|
await sendIterator(async ({ done, value, error }) => {
|
|
3475
3519
|
if (signal.aborted) return;
|
|
@@ -3493,6 +3537,8 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
|
|
|
3493
3537
|
} catch (err) {
|
|
3494
3538
|
if (signal.aborted) return;
|
|
3495
3539
|
throw err;
|
|
3540
|
+
} finally {
|
|
3541
|
+
signal.removeEventListener("abort", returnProducer);
|
|
3496
3542
|
}
|
|
3497
3543
|
}
|
|
3498
3544
|
/**
|
|
@@ -3631,10 +3677,18 @@ function estimateMessageSize(payload) {
|
|
|
3631
3677
|
*/
|
|
3632
3678
|
function multiplexPort(port, options) {
|
|
3633
3679
|
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
3680
|
+
const maxReassembledBytes = options.maxReassembledBytes ?? 67108864;
|
|
3634
3681
|
const misses = keepalive?.misses ?? 3;
|
|
3635
3682
|
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`);
|
|
3636
3683
|
if (frameSize != null && !(Number.isSafeInteger(frameSize) && frameSize > 0)) throw new RangeError(`webrun-rpc: frameSize must be a positive integer, got ${frameSize}`);
|
|
3684
|
+
if (!(Number.isSafeInteger(maxReassembledBytes) && maxReassembledBytes > 0)) throw new RangeError(`webrun-rpc: maxReassembledBytes must be a positive integer, got ${maxReassembledBytes}`);
|
|
3637
3685
|
const open = /* @__PURE__ */ new Map();
|
|
3686
|
+
let reassembling = 0;
|
|
3687
|
+
const freePartial = (entry) => {
|
|
3688
|
+
if (!entry?.partial) return;
|
|
3689
|
+
reassembling -= entry.partial.charged;
|
|
3690
|
+
entry.partial = void 0;
|
|
3691
|
+
};
|
|
3638
3692
|
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
3639
3693
|
const entry = open.get(id);
|
|
3640
3694
|
if (entry?.handle !== record) return;
|
|
@@ -3782,12 +3836,12 @@ function multiplexPort(port, options) {
|
|
|
3782
3836
|
...envelope,
|
|
3783
3837
|
payload: structuredClone(payload, { transfer })
|
|
3784
3838
|
} : void 0;
|
|
3785
|
-
const send = () => {
|
|
3839
|
+
const send = (whole = false) => {
|
|
3786
3840
|
if (!bytes) {
|
|
3787
3841
|
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
3788
3842
|
return;
|
|
3789
3843
|
}
|
|
3790
|
-
if (frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
3844
|
+
if (!whole && frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
3791
3845
|
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
3792
3846
|
const data = bytes.subarray(at, at + frameSize);
|
|
3793
3847
|
const frame = at + frameSize >= bytes.byteLength ? {
|
|
@@ -3817,10 +3871,11 @@ function multiplexPort(port, options) {
|
|
|
3817
3871
|
record.markClosed(reason);
|
|
3818
3872
|
});
|
|
3819
3873
|
function release(reason) {
|
|
3874
|
+
freePartial(open.get(id));
|
|
3820
3875
|
open.delete(id);
|
|
3821
3876
|
leaks?.unregister(record);
|
|
3822
3877
|
if (c?.queue.length) {
|
|
3823
|
-
for (const item of c.queue.splice(0)) item.send();
|
|
3878
|
+
for (const item of c.queue.splice(0)) item.send(true);
|
|
3824
3879
|
c.queued = 0;
|
|
3825
3880
|
}
|
|
3826
3881
|
sendOnPort(id, () => post({
|
|
@@ -3959,15 +4014,38 @@ function multiplexPort(port, options) {
|
|
|
3959
4014
|
if (envelope.type === "frame") {
|
|
3960
4015
|
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
3961
4016
|
touch(envelope.id);
|
|
4017
|
+
const c = entry.credit;
|
|
4018
|
+
const owed = c.unconsumed + c.toGrant;
|
|
3962
4019
|
const partial = entry.partial ?? {
|
|
3963
4020
|
chunks: [],
|
|
3964
|
-
bytes: 0
|
|
4021
|
+
bytes: 0,
|
|
4022
|
+
owed,
|
|
4023
|
+
charged: 0,
|
|
4024
|
+
...owed > 0 ? { room: c.window - owed } : {}
|
|
3965
4025
|
};
|
|
3966
4026
|
entry.partial = partial;
|
|
4027
|
+
const size = envelope.data.byteLength;
|
|
4028
|
+
const charged = partial.room == null ? Math.max(0, partial.bytes + size - c.window) : 0;
|
|
4029
|
+
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;
|
|
4030
|
+
if (overrun) {
|
|
4031
|
+
freePartial(entry);
|
|
4032
|
+
dropOutgoing(envelope.id);
|
|
4033
|
+
open.delete(envelope.id);
|
|
4034
|
+
releaseWaiters(c);
|
|
4035
|
+
entry.handle.markClosed(/* @__PURE__ */ new RangeError(`webrun-rpc: a framed message passed ${overrun}`));
|
|
4036
|
+
post({
|
|
4037
|
+
type: "close",
|
|
4038
|
+
id: envelope.id,
|
|
4039
|
+
reason: "max-reassembled-bytes"
|
|
4040
|
+
});
|
|
4041
|
+
return;
|
|
4042
|
+
}
|
|
4043
|
+
partial.bytes += size;
|
|
4044
|
+
reassembling += charged - partial.charged;
|
|
4045
|
+
partial.charged = charged;
|
|
3967
4046
|
partial.chunks.push(envelope.data);
|
|
3968
|
-
partial.bytes += envelope.data.byteLength;
|
|
3969
4047
|
if (!envelope.last) return;
|
|
3970
|
-
entry
|
|
4048
|
+
freePartial(entry);
|
|
3971
4049
|
const whole = new Uint8Array(partial.bytes);
|
|
3972
4050
|
let at = 0;
|
|
3973
4051
|
for (const chunk of partial.chunks) {
|
|
@@ -3981,6 +4059,7 @@ function multiplexPort(port, options) {
|
|
|
3981
4059
|
}
|
|
3982
4060
|
dropOutgoing(envelope.id);
|
|
3983
4061
|
if (!entry) return;
|
|
4062
|
+
freePartial(entry);
|
|
3984
4063
|
open.delete(envelope.id);
|
|
3985
4064
|
releaseWaiters(entry.credit);
|
|
3986
4065
|
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
|
@@ -4171,14 +4250,17 @@ const serve = async ({ mux: factory, timeout }, handler) => {
|
|
|
4171
4250
|
const mux = await factory((port) => {
|
|
4172
4251
|
let off;
|
|
4173
4252
|
let finished = false;
|
|
4174
|
-
const tracked = (input) =>
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4253
|
+
const tracked = (input) => {
|
|
4254
|
+
const output = handler(input);
|
|
4255
|
+
return returningAtOnce((async function* () {
|
|
4256
|
+
try {
|
|
4257
|
+
yield* output;
|
|
4258
|
+
} finally {
|
|
4259
|
+
finished = true;
|
|
4260
|
+
if (off) live.delete(off);
|
|
4261
|
+
}
|
|
4262
|
+
})(), () => void output.return(void 0).catch(() => {}));
|
|
4263
|
+
};
|
|
4182
4264
|
off = serveDuplexOverPort(port, tracked, streamOptions);
|
|
4183
4265
|
if (!finished) live.add(off);
|
|
4184
4266
|
});
|
|
@@ -7408,4 +7490,4 @@ function serveHttpInWorker(handler, options = {}) {
|
|
|
7408
7490
|
};
|
|
7409
7491
|
}
|
|
7410
7492
|
//#endregion
|
|
7411
|
-
export { CLAIM_CALL, DEFAULT_CLOSE_TIMEOUT_MS, DEFAULT_HEARTBEAT_MS, DEFAULT_SERVICE_WORKER_TIMEOUT, DuplexSiteBuilder, HANDLER_LOST_MESSAGE, HTTP_LINK_PROTOCOL, HandlerEndpoint, HandlerTable, HttpError, HttpParseError, LinkLostError, PEER_ERROR_HEADER, PortRendezvous, ROUTER_LOST_MESSAGE, ServerWebSocket, ServiceWorkerControlError, SwHttpAdapter, SwHttpDispatcher, SwPortDispatcher, SwPortHandler, TransportClosedError, WS_PORT_PROTOCOL_VERSION, WS_VERSION_HEADER, WsCloseEvent, answerConnect, awaitActiveServiceWorker, awaitServiceWorkerControl, callChannel, callChannelWithin, callHttpService, callWebSocketService, collect, collectBytes, collectString, connectHttpLink, connectWebSocketThroughServiceWorker, createWebSocketClass, decodeJsonl, decodeMessage, decodeText, defaultCodec, deserializeError, emulateMux, encodeJsonl, encodeMessage, encodeText, fetchOverDuplex, fromReadableStream, getRelayWindowMessageHandler, handleChannelCalls, handleClaimRequests, handleHttpRequests, handleStreams, holdLock, httpCodec, httpFetch, httpServe, initHttpService, initServiceWorker, initializeConnection, isWebSocketUpgrade, joinLines, jsonEnvelopeCodec, leastInflightRoute, locksAvailable, map, newAsyncGenerator, newCreditGrantor, newCreditLedger, newHttpClientStub, newHttpCodec, newHttpServerStub, newInvokationChannel, newLockName, newRegistry, newRemoteRelayChannel, newServiceWorkerPort, newSniffingCodec, newWebSocketUpgradeRequest, normalizeToUint8Array, recieveIterator, registerConnectionsHandler, relayWebSocketClass, requestedProtocols, sendHttpRequest, sendIterator, sendOverConnection, sendStream, serializeError, serveFetchOverDuplex, serveHttpInWorker, serveHttpLink, serveWebSocketUpgrade, serviceWorkerWebSocketClass, splitLines, splitServiceUrl, startHttpDispatcher, takePort, takeUpgrade, toChunks, toReadableStream, trackedLink, unwrapTransfer, upgradeWebSocket, watchLock, withHeaders, withTransfer };
|
|
7493
|
+
export { CLAIM_CALL, DEFAULT_CLOSE_TIMEOUT_MS, DEFAULT_HEARTBEAT_MS, DEFAULT_SERVICE_WORKER_TIMEOUT, DuplexSiteBuilder, EXCHANGE_ABANDONED_MESSAGE, HANDLER_LOST_MESSAGE, HTTP_LINK_PROTOCOL, HandlerEndpoint, HandlerTable, HttpError, HttpParseError, LinkLostError, PEER_ERROR_HEADER, PortRendezvous, ROUTER_LOST_MESSAGE, ServerWebSocket, ServiceWorkerControlError, SwHttpAdapter, SwHttpDispatcher, SwPortDispatcher, SwPortHandler, TransportClosedError, WS_PORT_PROTOCOL_VERSION, WS_VERSION_HEADER, WsCloseEvent, answerConnect, awaitActiveServiceWorker, awaitServiceWorkerControl, callChannel, callChannelWithin, callHttpService, callWebSocketService, collect, collectBytes, collectString, connectHttpLink, connectWebSocketThroughServiceWorker, createWebSocketClass, decodeJsonl, decodeMessage, decodeText, defaultCodec, deserializeError, emulateMux, encodeJsonl, encodeMessage, encodeText, fetchOverDuplex, fromReadableStream, getRelayWindowMessageHandler, handleChannelCalls, handleClaimRequests, handleHttpRequests, handleStreams, holdLock, httpCodec, httpFetch, httpServe, initHttpService, initServiceWorker, initializeConnection, isWebSocketUpgrade, joinLines, jsonEnvelopeCodec, leastInflightRoute, locksAvailable, map, newAsyncGenerator, newCreditGrantor, newCreditLedger, newHttpClientStub, newHttpCodec, newHttpServerStub, newInvokationChannel, newLockName, newRegistry, newRemoteRelayChannel, newServiceWorkerPort, newSniffingCodec, newWebSocketUpgradeRequest, normalizeToUint8Array, recieveIterator, registerConnectionsHandler, relayWebSocketClass, requestedProtocols, sendHttpRequest, sendIterator, sendOverConnection, sendStream, serializeError, serveFetchOverDuplex, serveHttpInWorker, serveHttpLink, serveWebSocketUpgrade, serviceWorkerWebSocketClass, splitLines, splitServiceUrl, startHttpDispatcher, takePort, takeUpgrade, toChunks, toReadableStream, trackedLink, unwrapTransfer, upgradeWebSocket, watchLock, withHeaders, withTransfer };
|
package/dist/relay-sw.js
CHANGED
|
@@ -2141,7 +2141,11 @@
|
|
|
2141
2141
|
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8).
|
|
2142
2142
|
*/
|
|
2143
2143
|
async function sendChunks(port, channelName, output, { maxMessageSize, log }, signal, touch) {
|
|
2144
|
-
const
|
|
2144
|
+
const framed = maxMessageSize ? toChunks(maxMessageSize)(output) : output;
|
|
2145
|
+
const stream = throughAbort(framed, signal);
|
|
2146
|
+
const producer = output;
|
|
2147
|
+
const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
|
|
2148
|
+
if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
|
|
2145
2149
|
try {
|
|
2146
2150
|
await sendIterator(async ({ done, value, error }) => {
|
|
2147
2151
|
if (signal.aborted) return;
|
|
@@ -2165,6 +2169,8 @@
|
|
|
2165
2169
|
} catch (err) {
|
|
2166
2170
|
if (signal.aborted) return;
|
|
2167
2171
|
throw err;
|
|
2172
|
+
} finally {
|
|
2173
|
+
signal.removeEventListener("abort", returnProducer);
|
|
2168
2174
|
}
|
|
2169
2175
|
}
|
|
2170
2176
|
/**
|
|
@@ -2303,10 +2309,18 @@
|
|
|
2303
2309
|
*/
|
|
2304
2310
|
function multiplexPort(port, options) {
|
|
2305
2311
|
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
2312
|
+
const maxReassembledBytes = options.maxReassembledBytes ?? 67108864;
|
|
2306
2313
|
const misses = keepalive?.misses ?? 3;
|
|
2307
2314
|
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`);
|
|
2308
2315
|
if (frameSize != null && !(Number.isSafeInteger(frameSize) && frameSize > 0)) throw new RangeError(`webrun-rpc: frameSize must be a positive integer, got ${frameSize}`);
|
|
2316
|
+
if (!(Number.isSafeInteger(maxReassembledBytes) && maxReassembledBytes > 0)) throw new RangeError(`webrun-rpc: maxReassembledBytes must be a positive integer, got ${maxReassembledBytes}`);
|
|
2309
2317
|
const open = /* @__PURE__ */ new Map();
|
|
2318
|
+
let reassembling = 0;
|
|
2319
|
+
const freePartial = (entry) => {
|
|
2320
|
+
if (!entry?.partial) return;
|
|
2321
|
+
reassembling -= entry.partial.charged;
|
|
2322
|
+
entry.partial = void 0;
|
|
2323
|
+
};
|
|
2310
2324
|
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
2311
2325
|
const entry = open.get(id);
|
|
2312
2326
|
if (entry?.handle !== record) return;
|
|
@@ -2454,12 +2468,12 @@
|
|
|
2454
2468
|
...envelope,
|
|
2455
2469
|
payload: structuredClone(payload, { transfer })
|
|
2456
2470
|
} : void 0;
|
|
2457
|
-
const send = () => {
|
|
2471
|
+
const send = (whole = false) => {
|
|
2458
2472
|
if (!bytes) {
|
|
2459
2473
|
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
2460
2474
|
return;
|
|
2461
2475
|
}
|
|
2462
|
-
if (frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2476
|
+
if (!whole && frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2463
2477
|
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
2464
2478
|
const data = bytes.subarray(at, at + frameSize);
|
|
2465
2479
|
const frame = at + frameSize >= bytes.byteLength ? {
|
|
@@ -2489,10 +2503,11 @@
|
|
|
2489
2503
|
record.markClosed(reason);
|
|
2490
2504
|
});
|
|
2491
2505
|
function release(reason) {
|
|
2506
|
+
freePartial(open.get(id));
|
|
2492
2507
|
open.delete(id);
|
|
2493
2508
|
leaks?.unregister(record);
|
|
2494
2509
|
if (c?.queue.length) {
|
|
2495
|
-
for (const item of c.queue.splice(0)) item.send();
|
|
2510
|
+
for (const item of c.queue.splice(0)) item.send(true);
|
|
2496
2511
|
c.queued = 0;
|
|
2497
2512
|
}
|
|
2498
2513
|
sendOnPort(id, () => post({
|
|
@@ -2631,15 +2646,38 @@
|
|
|
2631
2646
|
if (envelope.type === "frame") {
|
|
2632
2647
|
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
2633
2648
|
touch(envelope.id);
|
|
2649
|
+
const c = entry.credit;
|
|
2650
|
+
const owed = c.unconsumed + c.toGrant;
|
|
2634
2651
|
const partial = entry.partial ?? {
|
|
2635
2652
|
chunks: [],
|
|
2636
|
-
bytes: 0
|
|
2653
|
+
bytes: 0,
|
|
2654
|
+
owed,
|
|
2655
|
+
charged: 0,
|
|
2656
|
+
...owed > 0 ? { room: c.window - owed } : {}
|
|
2637
2657
|
};
|
|
2638
2658
|
entry.partial = partial;
|
|
2659
|
+
const size = envelope.data.byteLength;
|
|
2660
|
+
const charged = partial.room == null ? Math.max(0, partial.bytes + size - c.window) : 0;
|
|
2661
|
+
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;
|
|
2662
|
+
if (overrun) {
|
|
2663
|
+
freePartial(entry);
|
|
2664
|
+
dropOutgoing(envelope.id);
|
|
2665
|
+
open.delete(envelope.id);
|
|
2666
|
+
releaseWaiters(c);
|
|
2667
|
+
entry.handle.markClosed(/* @__PURE__ */ new RangeError(`webrun-rpc: a framed message passed ${overrun}`));
|
|
2668
|
+
post({
|
|
2669
|
+
type: "close",
|
|
2670
|
+
id: envelope.id,
|
|
2671
|
+
reason: "max-reassembled-bytes"
|
|
2672
|
+
});
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
partial.bytes += size;
|
|
2676
|
+
reassembling += charged - partial.charged;
|
|
2677
|
+
partial.charged = charged;
|
|
2639
2678
|
partial.chunks.push(envelope.data);
|
|
2640
|
-
partial.bytes += envelope.data.byteLength;
|
|
2641
2679
|
if (!envelope.last) return;
|
|
2642
|
-
entry
|
|
2680
|
+
freePartial(entry);
|
|
2643
2681
|
const whole = new Uint8Array(partial.bytes);
|
|
2644
2682
|
let at = 0;
|
|
2645
2683
|
for (const chunk of partial.chunks) {
|
|
@@ -2653,6 +2691,7 @@
|
|
|
2653
2691
|
}
|
|
2654
2692
|
dropOutgoing(envelope.id);
|
|
2655
2693
|
if (!entry) return;
|
|
2694
|
+
freePartial(entry);
|
|
2656
2695
|
open.delete(envelope.id);
|
|
2657
2696
|
releaseWaiters(entry.credit);
|
|
2658
2697
|
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
package/dist/relay-worker.js
CHANGED
|
@@ -2140,7 +2140,11 @@ function receiveChunks(port, channelName, controller, touch) {
|
|
|
2140
2140
|
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8).
|
|
2141
2141
|
*/
|
|
2142
2142
|
async function sendChunks(port, channelName, output, { maxMessageSize, log }, signal, touch) {
|
|
2143
|
-
const
|
|
2143
|
+
const framed = maxMessageSize ? toChunks(maxMessageSize)(output) : output;
|
|
2144
|
+
const stream = throughAbort(framed, signal);
|
|
2145
|
+
const producer = output;
|
|
2146
|
+
const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
|
|
2147
|
+
if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
|
|
2144
2148
|
try {
|
|
2145
2149
|
await sendIterator(async ({ done, value, error }) => {
|
|
2146
2150
|
if (signal.aborted) return;
|
|
@@ -2164,6 +2168,8 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
|
|
|
2164
2168
|
} catch (err) {
|
|
2165
2169
|
if (signal.aborted) return;
|
|
2166
2170
|
throw err;
|
|
2171
|
+
} finally {
|
|
2172
|
+
signal.removeEventListener("abort", returnProducer);
|
|
2167
2173
|
}
|
|
2168
2174
|
}
|
|
2169
2175
|
/**
|
|
@@ -2302,10 +2308,18 @@ function estimateMessageSize(payload) {
|
|
|
2302
2308
|
*/
|
|
2303
2309
|
function multiplexPort(port, options) {
|
|
2304
2310
|
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
2311
|
+
const maxReassembledBytes = options.maxReassembledBytes ?? 67108864;
|
|
2305
2312
|
const misses = keepalive?.misses ?? 3;
|
|
2306
2313
|
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`);
|
|
2307
2314
|
if (frameSize != null && !(Number.isSafeInteger(frameSize) && frameSize > 0)) throw new RangeError(`webrun-rpc: frameSize must be a positive integer, got ${frameSize}`);
|
|
2315
|
+
if (!(Number.isSafeInteger(maxReassembledBytes) && maxReassembledBytes > 0)) throw new RangeError(`webrun-rpc: maxReassembledBytes must be a positive integer, got ${maxReassembledBytes}`);
|
|
2308
2316
|
const open = /* @__PURE__ */ new Map();
|
|
2317
|
+
let reassembling = 0;
|
|
2318
|
+
const freePartial = (entry) => {
|
|
2319
|
+
if (!entry?.partial) return;
|
|
2320
|
+
reassembling -= entry.partial.charged;
|
|
2321
|
+
entry.partial = void 0;
|
|
2322
|
+
};
|
|
2309
2323
|
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
2310
2324
|
const entry = open.get(id);
|
|
2311
2325
|
if (entry?.handle !== record) return;
|
|
@@ -2453,12 +2467,12 @@ function multiplexPort(port, options) {
|
|
|
2453
2467
|
...envelope,
|
|
2454
2468
|
payload: structuredClone(payload, { transfer })
|
|
2455
2469
|
} : void 0;
|
|
2456
|
-
const send = () => {
|
|
2470
|
+
const send = (whole = false) => {
|
|
2457
2471
|
if (!bytes) {
|
|
2458
2472
|
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
2459
2473
|
return;
|
|
2460
2474
|
}
|
|
2461
|
-
if (frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2475
|
+
if (!whole && frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2462
2476
|
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
2463
2477
|
const data = bytes.subarray(at, at + frameSize);
|
|
2464
2478
|
const frame = at + frameSize >= bytes.byteLength ? {
|
|
@@ -2488,10 +2502,11 @@ function multiplexPort(port, options) {
|
|
|
2488
2502
|
record.markClosed(reason);
|
|
2489
2503
|
});
|
|
2490
2504
|
function release(reason) {
|
|
2505
|
+
freePartial(open.get(id));
|
|
2491
2506
|
open.delete(id);
|
|
2492
2507
|
leaks?.unregister(record);
|
|
2493
2508
|
if (c?.queue.length) {
|
|
2494
|
-
for (const item of c.queue.splice(0)) item.send();
|
|
2509
|
+
for (const item of c.queue.splice(0)) item.send(true);
|
|
2495
2510
|
c.queued = 0;
|
|
2496
2511
|
}
|
|
2497
2512
|
sendOnPort(id, () => post({
|
|
@@ -2630,15 +2645,38 @@ function multiplexPort(port, options) {
|
|
|
2630
2645
|
if (envelope.type === "frame") {
|
|
2631
2646
|
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
2632
2647
|
touch(envelope.id);
|
|
2648
|
+
const c = entry.credit;
|
|
2649
|
+
const owed = c.unconsumed + c.toGrant;
|
|
2633
2650
|
const partial = entry.partial ?? {
|
|
2634
2651
|
chunks: [],
|
|
2635
|
-
bytes: 0
|
|
2652
|
+
bytes: 0,
|
|
2653
|
+
owed,
|
|
2654
|
+
charged: 0,
|
|
2655
|
+
...owed > 0 ? { room: c.window - owed } : {}
|
|
2636
2656
|
};
|
|
2637
2657
|
entry.partial = partial;
|
|
2658
|
+
const size = envelope.data.byteLength;
|
|
2659
|
+
const charged = partial.room == null ? Math.max(0, partial.bytes + size - c.window) : 0;
|
|
2660
|
+
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;
|
|
2661
|
+
if (overrun) {
|
|
2662
|
+
freePartial(entry);
|
|
2663
|
+
dropOutgoing(envelope.id);
|
|
2664
|
+
open.delete(envelope.id);
|
|
2665
|
+
releaseWaiters(c);
|
|
2666
|
+
entry.handle.markClosed(/* @__PURE__ */ new RangeError(`webrun-rpc: a framed message passed ${overrun}`));
|
|
2667
|
+
post({
|
|
2668
|
+
type: "close",
|
|
2669
|
+
id: envelope.id,
|
|
2670
|
+
reason: "max-reassembled-bytes"
|
|
2671
|
+
});
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
partial.bytes += size;
|
|
2675
|
+
reassembling += charged - partial.charged;
|
|
2676
|
+
partial.charged = charged;
|
|
2638
2677
|
partial.chunks.push(envelope.data);
|
|
2639
|
-
partial.bytes += envelope.data.byteLength;
|
|
2640
2678
|
if (!envelope.last) return;
|
|
2641
|
-
entry
|
|
2679
|
+
freePartial(entry);
|
|
2642
2680
|
const whole = new Uint8Array(partial.bytes);
|
|
2643
2681
|
let at = 0;
|
|
2644
2682
|
for (const chunk of partial.chunks) {
|
|
@@ -2652,6 +2690,7 @@ function multiplexPort(port, options) {
|
|
|
2652
2690
|
}
|
|
2653
2691
|
dropOutgoing(envelope.id);
|
|
2654
2692
|
if (!entry) return;
|
|
2693
|
+
freePartial(entry);
|
|
2655
2694
|
open.delete(envelope.id);
|
|
2656
2695
|
releaseWaiters(entry.credit);
|
|
2657
2696
|
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
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,
|
|
@@ -1660,10 +1693,11 @@
|
|
|
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") {
|
|
@@ -2565,7 +2599,11 @@
|
|
|
2565
2599
|
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8).
|
|
2566
2600
|
*/
|
|
2567
2601
|
async function sendChunks(port, channelName, output, { maxMessageSize, log }, signal, touch) {
|
|
2568
|
-
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 });
|
|
2569
2607
|
try {
|
|
2570
2608
|
await sendIterator(async ({ done, value, error }) => {
|
|
2571
2609
|
if (signal.aborted) return;
|
|
@@ -2589,6 +2627,8 @@
|
|
|
2589
2627
|
} catch (err) {
|
|
2590
2628
|
if (signal.aborted) return;
|
|
2591
2629
|
throw err;
|
|
2630
|
+
} finally {
|
|
2631
|
+
signal.removeEventListener("abort", returnProducer);
|
|
2592
2632
|
}
|
|
2593
2633
|
}
|
|
2594
2634
|
/**
|
|
@@ -2727,10 +2767,18 @@
|
|
|
2727
2767
|
*/
|
|
2728
2768
|
function multiplexPort(port, options) {
|
|
2729
2769
|
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
2770
|
+
const maxReassembledBytes = options.maxReassembledBytes ?? 67108864;
|
|
2730
2771
|
const misses = keepalive?.misses ?? 3;
|
|
2731
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`);
|
|
2732
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}`);
|
|
2733
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
|
+
};
|
|
2734
2782
|
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
2735
2783
|
const entry = open.get(id);
|
|
2736
2784
|
if (entry?.handle !== record) return;
|
|
@@ -2878,12 +2926,12 @@
|
|
|
2878
2926
|
...envelope,
|
|
2879
2927
|
payload: structuredClone(payload, { transfer })
|
|
2880
2928
|
} : void 0;
|
|
2881
|
-
const send = () => {
|
|
2929
|
+
const send = (whole = false) => {
|
|
2882
2930
|
if (!bytes) {
|
|
2883
2931
|
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
2884
2932
|
return;
|
|
2885
2933
|
}
|
|
2886
|
-
if (frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2934
|
+
if (!whole && frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2887
2935
|
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
2888
2936
|
const data = bytes.subarray(at, at + frameSize);
|
|
2889
2937
|
const frame = at + frameSize >= bytes.byteLength ? {
|
|
@@ -2913,10 +2961,11 @@
|
|
|
2913
2961
|
record.markClosed(reason);
|
|
2914
2962
|
});
|
|
2915
2963
|
function release(reason) {
|
|
2964
|
+
freePartial(open.get(id));
|
|
2916
2965
|
open.delete(id);
|
|
2917
2966
|
leaks?.unregister(record);
|
|
2918
2967
|
if (c?.queue.length) {
|
|
2919
|
-
for (const item of c.queue.splice(0)) item.send();
|
|
2968
|
+
for (const item of c.queue.splice(0)) item.send(true);
|
|
2920
2969
|
c.queued = 0;
|
|
2921
2970
|
}
|
|
2922
2971
|
sendOnPort(id, () => post({
|
|
@@ -3055,15 +3104,38 @@
|
|
|
3055
3104
|
if (envelope.type === "frame") {
|
|
3056
3105
|
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
3057
3106
|
touch(envelope.id);
|
|
3107
|
+
const c = entry.credit;
|
|
3108
|
+
const owed = c.unconsumed + c.toGrant;
|
|
3058
3109
|
const partial = entry.partial ?? {
|
|
3059
3110
|
chunks: [],
|
|
3060
|
-
bytes: 0
|
|
3111
|
+
bytes: 0,
|
|
3112
|
+
owed,
|
|
3113
|
+
charged: 0,
|
|
3114
|
+
...owed > 0 ? { room: c.window - owed } : {}
|
|
3061
3115
|
};
|
|
3062
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;
|
|
3063
3136
|
partial.chunks.push(envelope.data);
|
|
3064
|
-
partial.bytes += envelope.data.byteLength;
|
|
3065
3137
|
if (!envelope.last) return;
|
|
3066
|
-
entry
|
|
3138
|
+
freePartial(entry);
|
|
3067
3139
|
const whole = new Uint8Array(partial.bytes);
|
|
3068
3140
|
let at = 0;
|
|
3069
3141
|
for (const chunk of partial.chunks) {
|
|
@@ -3077,6 +3149,7 @@
|
|
|
3077
3149
|
}
|
|
3078
3150
|
dropOutgoing(envelope.id);
|
|
3079
3151
|
if (!entry) return;
|
|
3152
|
+
freePartial(entry);
|
|
3080
3153
|
open.delete(envelope.id);
|
|
3081
3154
|
releaseWaiters(entry.credit);
|
|
3082
3155
|
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
|
@@ -3267,14 +3340,17 @@
|
|
|
3267
3340
|
const mux = await factory((port) => {
|
|
3268
3341
|
let off;
|
|
3269
3342
|
let finished = false;
|
|
3270
|
-
const tracked = (input) =>
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
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
|
+
};
|
|
3278
3354
|
off = serveDuplexOverPort(port, tracked, streamOptions);
|
|
3279
3355
|
if (!finished) live.add(off);
|
|
3280
3356
|
});
|
package/dist/sw.js
CHANGED
|
@@ -1266,7 +1266,11 @@ async function drain(output) {
|
|
|
1266
1266
|
*/
|
|
1267
1267
|
function httpServe(handler, options = {}) {
|
|
1268
1268
|
const codec = options.codec ?? defaultCodec;
|
|
1269
|
-
return
|
|
1269
|
+
return (input) => {
|
|
1270
|
+
const abandoned = new AbortController();
|
|
1271
|
+
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")));
|
|
1272
|
+
};
|
|
1273
|
+
async function* exchange(input, context) {
|
|
1270
1274
|
let decoded;
|
|
1271
1275
|
try {
|
|
1272
1276
|
decoded = await codec.decodeRequest(input);
|
|
@@ -1278,7 +1282,7 @@ function httpServe(handler, options = {}) {
|
|
|
1278
1282
|
const replyCodec = decoded.codec ?? codec;
|
|
1279
1283
|
let result;
|
|
1280
1284
|
try {
|
|
1281
|
-
result = await handler(decoded.envelope, decoded.body);
|
|
1285
|
+
result = await handler(decoded.envelope, decoded.body, context);
|
|
1282
1286
|
} catch (error) {
|
|
1283
1287
|
yield* encodeErrorResponse(replyCodec, error, decoded.envelope.method);
|
|
1284
1288
|
return;
|
|
@@ -1293,7 +1297,36 @@ function httpServe(handler, options = {}) {
|
|
|
1293
1297
|
await result.release?.();
|
|
1294
1298
|
} catch {}
|
|
1295
1299
|
}
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* `gen`, except that `return()` and `throw()` from its consumer call `abandon`
|
|
1304
|
+
* at once — before they are queued behind a `next()` still parked inside the
|
|
1305
|
+
* generator (the handler's `await`) — unless the generator already finished.
|
|
1306
|
+
*/
|
|
1307
|
+
function abortingOnReturn(gen, abandon) {
|
|
1308
|
+
let finished = false;
|
|
1309
|
+
const settle = (r) => {
|
|
1310
|
+
if (r.done) finished = true;
|
|
1311
|
+
return r;
|
|
1312
|
+
};
|
|
1313
|
+
const wrapped = {
|
|
1314
|
+
next: (...args) => gen.next(...args).then(settle),
|
|
1315
|
+
return(value) {
|
|
1316
|
+
if (!finished) abandon();
|
|
1317
|
+
finished = true;
|
|
1318
|
+
return gen.return(value);
|
|
1319
|
+
},
|
|
1320
|
+
throw(error) {
|
|
1321
|
+
if (!finished) abandon(error);
|
|
1322
|
+
finished = true;
|
|
1323
|
+
return gen.throw(error);
|
|
1324
|
+
},
|
|
1325
|
+
[Symbol.asyncIterator]() {
|
|
1326
|
+
return wrapped;
|
|
1327
|
+
}
|
|
1296
1328
|
};
|
|
1329
|
+
return asyncDisposable$2(wrapped, () => wrapped.return(void 0));
|
|
1297
1330
|
}
|
|
1298
1331
|
/**
|
|
1299
1332
|
* The one place that knows a runtime may not implement request body streams,
|
|
@@ -1532,10 +1565,11 @@ async function fetchOverDuplex(call, request, options = {}) {
|
|
|
1532
1565
|
* registered with any `webrun-streams-*` adapter's `serve`.
|
|
1533
1566
|
*/
|
|
1534
1567
|
function serveFetchOverDuplex(handler, options = {}) {
|
|
1535
|
-
return httpServe(async (env, body) => {
|
|
1568
|
+
return httpServe(async (env, body, exchange) => {
|
|
1536
1569
|
const reqInit = {
|
|
1537
1570
|
method: env.method,
|
|
1538
|
-
headers: forwardableHeaders(env.headers)
|
|
1571
|
+
headers: forwardableHeaders(env.headers),
|
|
1572
|
+
signal: exchange?.signal
|
|
1539
1573
|
};
|
|
1540
1574
|
let upload;
|
|
1541
1575
|
if (env.method !== "GET" && env.method !== "HEAD") {
|
|
@@ -2234,7 +2268,11 @@ function receiveChunks(port, channelName, controller, touch) {
|
|
|
2234
2268
|
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8).
|
|
2235
2269
|
*/
|
|
2236
2270
|
async function sendChunks(port, channelName, output, { maxMessageSize, log }, signal, touch) {
|
|
2237
|
-
const
|
|
2271
|
+
const framed = maxMessageSize ? toChunks(maxMessageSize)(output) : output;
|
|
2272
|
+
const stream = throughAbort(framed, signal);
|
|
2273
|
+
const producer = output;
|
|
2274
|
+
const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
|
|
2275
|
+
if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
|
|
2238
2276
|
try {
|
|
2239
2277
|
await sendIterator(async ({ done, value, error }) => {
|
|
2240
2278
|
if (signal.aborted) return;
|
|
@@ -2258,6 +2296,8 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
|
|
|
2258
2296
|
} catch (err) {
|
|
2259
2297
|
if (signal.aborted) return;
|
|
2260
2298
|
throw err;
|
|
2299
|
+
} finally {
|
|
2300
|
+
signal.removeEventListener("abort", returnProducer);
|
|
2261
2301
|
}
|
|
2262
2302
|
}
|
|
2263
2303
|
/**
|
|
@@ -2396,10 +2436,18 @@ function estimateMessageSize(payload) {
|
|
|
2396
2436
|
*/
|
|
2397
2437
|
function multiplexPort(port, options) {
|
|
2398
2438
|
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
2439
|
+
const maxReassembledBytes = options.maxReassembledBytes ?? 67108864;
|
|
2399
2440
|
const misses = keepalive?.misses ?? 3;
|
|
2400
2441
|
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`);
|
|
2401
2442
|
if (frameSize != null && !(Number.isSafeInteger(frameSize) && frameSize > 0)) throw new RangeError(`webrun-rpc: frameSize must be a positive integer, got ${frameSize}`);
|
|
2443
|
+
if (!(Number.isSafeInteger(maxReassembledBytes) && maxReassembledBytes > 0)) throw new RangeError(`webrun-rpc: maxReassembledBytes must be a positive integer, got ${maxReassembledBytes}`);
|
|
2402
2444
|
const open = /* @__PURE__ */ new Map();
|
|
2445
|
+
let reassembling = 0;
|
|
2446
|
+
const freePartial = (entry) => {
|
|
2447
|
+
if (!entry?.partial) return;
|
|
2448
|
+
reassembling -= entry.partial.charged;
|
|
2449
|
+
entry.partial = void 0;
|
|
2450
|
+
};
|
|
2403
2451
|
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
2404
2452
|
const entry = open.get(id);
|
|
2405
2453
|
if (entry?.handle !== record) return;
|
|
@@ -2547,12 +2595,12 @@ function multiplexPort(port, options) {
|
|
|
2547
2595
|
...envelope,
|
|
2548
2596
|
payload: structuredClone(payload, { transfer })
|
|
2549
2597
|
} : void 0;
|
|
2550
|
-
const send = () => {
|
|
2598
|
+
const send = (whole = false) => {
|
|
2551
2599
|
if (!bytes) {
|
|
2552
2600
|
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
2553
2601
|
return;
|
|
2554
2602
|
}
|
|
2555
|
-
if (frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2603
|
+
if (!whole && frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2556
2604
|
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
2557
2605
|
const data = bytes.subarray(at, at + frameSize);
|
|
2558
2606
|
const frame = at + frameSize >= bytes.byteLength ? {
|
|
@@ -2582,10 +2630,11 @@ function multiplexPort(port, options) {
|
|
|
2582
2630
|
record.markClosed(reason);
|
|
2583
2631
|
});
|
|
2584
2632
|
function release(reason) {
|
|
2633
|
+
freePartial(open.get(id));
|
|
2585
2634
|
open.delete(id);
|
|
2586
2635
|
leaks?.unregister(record);
|
|
2587
2636
|
if (c?.queue.length) {
|
|
2588
|
-
for (const item of c.queue.splice(0)) item.send();
|
|
2637
|
+
for (const item of c.queue.splice(0)) item.send(true);
|
|
2589
2638
|
c.queued = 0;
|
|
2590
2639
|
}
|
|
2591
2640
|
sendOnPort(id, () => post({
|
|
@@ -2724,15 +2773,38 @@ function multiplexPort(port, options) {
|
|
|
2724
2773
|
if (envelope.type === "frame") {
|
|
2725
2774
|
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
2726
2775
|
touch(envelope.id);
|
|
2776
|
+
const c = entry.credit;
|
|
2777
|
+
const owed = c.unconsumed + c.toGrant;
|
|
2727
2778
|
const partial = entry.partial ?? {
|
|
2728
2779
|
chunks: [],
|
|
2729
|
-
bytes: 0
|
|
2780
|
+
bytes: 0,
|
|
2781
|
+
owed,
|
|
2782
|
+
charged: 0,
|
|
2783
|
+
...owed > 0 ? { room: c.window - owed } : {}
|
|
2730
2784
|
};
|
|
2731
2785
|
entry.partial = partial;
|
|
2786
|
+
const size = envelope.data.byteLength;
|
|
2787
|
+
const charged = partial.room == null ? Math.max(0, partial.bytes + size - c.window) : 0;
|
|
2788
|
+
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;
|
|
2789
|
+
if (overrun) {
|
|
2790
|
+
freePartial(entry);
|
|
2791
|
+
dropOutgoing(envelope.id);
|
|
2792
|
+
open.delete(envelope.id);
|
|
2793
|
+
releaseWaiters(c);
|
|
2794
|
+
entry.handle.markClosed(/* @__PURE__ */ new RangeError(`webrun-rpc: a framed message passed ${overrun}`));
|
|
2795
|
+
post({
|
|
2796
|
+
type: "close",
|
|
2797
|
+
id: envelope.id,
|
|
2798
|
+
reason: "max-reassembled-bytes"
|
|
2799
|
+
});
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2802
|
+
partial.bytes += size;
|
|
2803
|
+
reassembling += charged - partial.charged;
|
|
2804
|
+
partial.charged = charged;
|
|
2732
2805
|
partial.chunks.push(envelope.data);
|
|
2733
|
-
partial.bytes += envelope.data.byteLength;
|
|
2734
2806
|
if (!envelope.last) return;
|
|
2735
|
-
entry
|
|
2807
|
+
freePartial(entry);
|
|
2736
2808
|
const whole = new Uint8Array(partial.bytes);
|
|
2737
2809
|
let at = 0;
|
|
2738
2810
|
for (const chunk of partial.chunks) {
|
|
@@ -2746,6 +2818,7 @@ function multiplexPort(port, options) {
|
|
|
2746
2818
|
}
|
|
2747
2819
|
dropOutgoing(envelope.id);
|
|
2748
2820
|
if (!entry) return;
|
|
2821
|
+
freePartial(entry);
|
|
2749
2822
|
open.delete(envelope.id);
|
|
2750
2823
|
releaseWaiters(entry.credit);
|
|
2751
2824
|
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
|
@@ -2936,14 +3009,17 @@ const serve = async ({ mux: factory, timeout }, handler) => {
|
|
|
2936
3009
|
const mux = await factory((port) => {
|
|
2937
3010
|
let off;
|
|
2938
3011
|
let finished = false;
|
|
2939
|
-
const tracked = (input) =>
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
3012
|
+
const tracked = (input) => {
|
|
3013
|
+
const output = handler(input);
|
|
3014
|
+
return returningAtOnce((async function* () {
|
|
3015
|
+
try {
|
|
3016
|
+
yield* output;
|
|
3017
|
+
} finally {
|
|
3018
|
+
finished = true;
|
|
3019
|
+
if (off) live.delete(off);
|
|
3020
|
+
}
|
|
3021
|
+
})(), () => void output.return(void 0).catch(() => {}));
|
|
3022
|
+
};
|
|
2947
3023
|
off = serveDuplexOverPort(port, tracked, streamOptions);
|
|
2948
3024
|
if (!finished) live.add(off);
|
|
2949
3025
|
});
|
package/dist/worker.js
CHANGED
|
@@ -1207,7 +1207,11 @@ async function drain(output) {
|
|
|
1207
1207
|
*/
|
|
1208
1208
|
function httpServe(handler, options = {}) {
|
|
1209
1209
|
const codec = options.codec ?? defaultCodec;
|
|
1210
|
-
return
|
|
1210
|
+
return (input) => {
|
|
1211
|
+
const abandoned = new AbortController();
|
|
1212
|
+
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")));
|
|
1213
|
+
};
|
|
1214
|
+
async function* exchange(input, context) {
|
|
1211
1215
|
let decoded;
|
|
1212
1216
|
try {
|
|
1213
1217
|
decoded = await codec.decodeRequest(input);
|
|
@@ -1219,7 +1223,7 @@ function httpServe(handler, options = {}) {
|
|
|
1219
1223
|
const replyCodec = decoded.codec ?? codec;
|
|
1220
1224
|
let result;
|
|
1221
1225
|
try {
|
|
1222
|
-
result = await handler(decoded.envelope, decoded.body);
|
|
1226
|
+
result = await handler(decoded.envelope, decoded.body, context);
|
|
1223
1227
|
} catch (error) {
|
|
1224
1228
|
yield* encodeErrorResponse(replyCodec, error, decoded.envelope.method);
|
|
1225
1229
|
return;
|
|
@@ -1234,7 +1238,36 @@ function httpServe(handler, options = {}) {
|
|
|
1234
1238
|
await result.release?.();
|
|
1235
1239
|
} catch {}
|
|
1236
1240
|
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
/**
|
|
1244
|
+
* `gen`, except that `return()` and `throw()` from its consumer call `abandon`
|
|
1245
|
+
* at once — before they are queued behind a `next()` still parked inside the
|
|
1246
|
+
* generator (the handler's `await`) — unless the generator already finished.
|
|
1247
|
+
*/
|
|
1248
|
+
function abortingOnReturn(gen, abandon) {
|
|
1249
|
+
let finished = false;
|
|
1250
|
+
const settle = (r) => {
|
|
1251
|
+
if (r.done) finished = true;
|
|
1252
|
+
return r;
|
|
1253
|
+
};
|
|
1254
|
+
const wrapped = {
|
|
1255
|
+
next: (...args) => gen.next(...args).then(settle),
|
|
1256
|
+
return(value) {
|
|
1257
|
+
if (!finished) abandon();
|
|
1258
|
+
finished = true;
|
|
1259
|
+
return gen.return(value);
|
|
1260
|
+
},
|
|
1261
|
+
throw(error) {
|
|
1262
|
+
if (!finished) abandon(error);
|
|
1263
|
+
finished = true;
|
|
1264
|
+
return gen.throw(error);
|
|
1265
|
+
},
|
|
1266
|
+
[Symbol.asyncIterator]() {
|
|
1267
|
+
return wrapped;
|
|
1268
|
+
}
|
|
1237
1269
|
};
|
|
1270
|
+
return asyncDisposable$2(wrapped, () => wrapped.return(void 0));
|
|
1238
1271
|
}
|
|
1239
1272
|
/**
|
|
1240
1273
|
* The one place that knows a runtime may not implement request body streams,
|
|
@@ -1418,10 +1451,11 @@ async function fetchOverDuplex(call, request, options = {}) {
|
|
|
1418
1451
|
* registered with any `webrun-streams-*` adapter's `serve`.
|
|
1419
1452
|
*/
|
|
1420
1453
|
function serveFetchOverDuplex(handler, options = {}) {
|
|
1421
|
-
return httpServe(async (env, body) => {
|
|
1454
|
+
return httpServe(async (env, body, exchange) => {
|
|
1422
1455
|
const reqInit = {
|
|
1423
1456
|
method: env.method,
|
|
1424
|
-
headers: forwardableHeaders(env.headers)
|
|
1457
|
+
headers: forwardableHeaders(env.headers),
|
|
1458
|
+
signal: exchange?.signal
|
|
1425
1459
|
};
|
|
1426
1460
|
let upload;
|
|
1427
1461
|
if (env.method !== "GET" && env.method !== "HEAD") {
|
|
@@ -2120,7 +2154,11 @@ function receiveChunks(port, channelName, controller, touch) {
|
|
|
2120
2154
|
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8).
|
|
2121
2155
|
*/
|
|
2122
2156
|
async function sendChunks(port, channelName, output, { maxMessageSize, log }, signal, touch) {
|
|
2123
|
-
const
|
|
2157
|
+
const framed = maxMessageSize ? toChunks(maxMessageSize)(output) : output;
|
|
2158
|
+
const stream = throughAbort(framed, signal);
|
|
2159
|
+
const producer = output;
|
|
2160
|
+
const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
|
|
2161
|
+
if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
|
|
2124
2162
|
try {
|
|
2125
2163
|
await sendIterator(async ({ done, value, error }) => {
|
|
2126
2164
|
if (signal.aborted) return;
|
|
@@ -2144,6 +2182,8 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
|
|
|
2144
2182
|
} catch (err) {
|
|
2145
2183
|
if (signal.aborted) return;
|
|
2146
2184
|
throw err;
|
|
2185
|
+
} finally {
|
|
2186
|
+
signal.removeEventListener("abort", returnProducer);
|
|
2147
2187
|
}
|
|
2148
2188
|
}
|
|
2149
2189
|
/**
|
|
@@ -2282,10 +2322,18 @@ function estimateMessageSize(payload) {
|
|
|
2282
2322
|
*/
|
|
2283
2323
|
function multiplexPort(port, options) {
|
|
2284
2324
|
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
2325
|
+
const maxReassembledBytes = options.maxReassembledBytes ?? 67108864;
|
|
2285
2326
|
const misses = keepalive?.misses ?? 3;
|
|
2286
2327
|
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`);
|
|
2287
2328
|
if (frameSize != null && !(Number.isSafeInteger(frameSize) && frameSize > 0)) throw new RangeError(`webrun-rpc: frameSize must be a positive integer, got ${frameSize}`);
|
|
2329
|
+
if (!(Number.isSafeInteger(maxReassembledBytes) && maxReassembledBytes > 0)) throw new RangeError(`webrun-rpc: maxReassembledBytes must be a positive integer, got ${maxReassembledBytes}`);
|
|
2288
2330
|
const open = /* @__PURE__ */ new Map();
|
|
2331
|
+
let reassembling = 0;
|
|
2332
|
+
const freePartial = (entry) => {
|
|
2333
|
+
if (!entry?.partial) return;
|
|
2334
|
+
reassembling -= entry.partial.charged;
|
|
2335
|
+
entry.partial = void 0;
|
|
2336
|
+
};
|
|
2289
2337
|
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
2290
2338
|
const entry = open.get(id);
|
|
2291
2339
|
if (entry?.handle !== record) return;
|
|
@@ -2433,12 +2481,12 @@ function multiplexPort(port, options) {
|
|
|
2433
2481
|
...envelope,
|
|
2434
2482
|
payload: structuredClone(payload, { transfer })
|
|
2435
2483
|
} : void 0;
|
|
2436
|
-
const send = () => {
|
|
2484
|
+
const send = (whole = false) => {
|
|
2437
2485
|
if (!bytes) {
|
|
2438
2486
|
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
2439
2487
|
return;
|
|
2440
2488
|
}
|
|
2441
|
-
if (frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2489
|
+
if (!whole && frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2442
2490
|
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
2443
2491
|
const data = bytes.subarray(at, at + frameSize);
|
|
2444
2492
|
const frame = at + frameSize >= bytes.byteLength ? {
|
|
@@ -2468,10 +2516,11 @@ function multiplexPort(port, options) {
|
|
|
2468
2516
|
record.markClosed(reason);
|
|
2469
2517
|
});
|
|
2470
2518
|
function release(reason) {
|
|
2519
|
+
freePartial(open.get(id));
|
|
2471
2520
|
open.delete(id);
|
|
2472
2521
|
leaks?.unregister(record);
|
|
2473
2522
|
if (c?.queue.length) {
|
|
2474
|
-
for (const item of c.queue.splice(0)) item.send();
|
|
2523
|
+
for (const item of c.queue.splice(0)) item.send(true);
|
|
2475
2524
|
c.queued = 0;
|
|
2476
2525
|
}
|
|
2477
2526
|
sendOnPort(id, () => post({
|
|
@@ -2610,15 +2659,38 @@ function multiplexPort(port, options) {
|
|
|
2610
2659
|
if (envelope.type === "frame") {
|
|
2611
2660
|
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
2612
2661
|
touch(envelope.id);
|
|
2662
|
+
const c = entry.credit;
|
|
2663
|
+
const owed = c.unconsumed + c.toGrant;
|
|
2613
2664
|
const partial = entry.partial ?? {
|
|
2614
2665
|
chunks: [],
|
|
2615
|
-
bytes: 0
|
|
2666
|
+
bytes: 0,
|
|
2667
|
+
owed,
|
|
2668
|
+
charged: 0,
|
|
2669
|
+
...owed > 0 ? { room: c.window - owed } : {}
|
|
2616
2670
|
};
|
|
2617
2671
|
entry.partial = partial;
|
|
2672
|
+
const size = envelope.data.byteLength;
|
|
2673
|
+
const charged = partial.room == null ? Math.max(0, partial.bytes + size - c.window) : 0;
|
|
2674
|
+
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;
|
|
2675
|
+
if (overrun) {
|
|
2676
|
+
freePartial(entry);
|
|
2677
|
+
dropOutgoing(envelope.id);
|
|
2678
|
+
open.delete(envelope.id);
|
|
2679
|
+
releaseWaiters(c);
|
|
2680
|
+
entry.handle.markClosed(/* @__PURE__ */ new RangeError(`webrun-rpc: a framed message passed ${overrun}`));
|
|
2681
|
+
post({
|
|
2682
|
+
type: "close",
|
|
2683
|
+
id: envelope.id,
|
|
2684
|
+
reason: "max-reassembled-bytes"
|
|
2685
|
+
});
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
partial.bytes += size;
|
|
2689
|
+
reassembling += charged - partial.charged;
|
|
2690
|
+
partial.charged = charged;
|
|
2618
2691
|
partial.chunks.push(envelope.data);
|
|
2619
|
-
partial.bytes += envelope.data.byteLength;
|
|
2620
2692
|
if (!envelope.last) return;
|
|
2621
|
-
entry
|
|
2693
|
+
freePartial(entry);
|
|
2622
2694
|
const whole = new Uint8Array(partial.bytes);
|
|
2623
2695
|
let at = 0;
|
|
2624
2696
|
for (const chunk of partial.chunks) {
|
|
@@ -2632,6 +2704,7 @@ function multiplexPort(port, options) {
|
|
|
2632
2704
|
}
|
|
2633
2705
|
dropOutgoing(envelope.id);
|
|
2634
2706
|
if (!entry) return;
|
|
2707
|
+
freePartial(entry);
|
|
2635
2708
|
open.delete(envelope.id);
|
|
2636
2709
|
releaseWaiters(entry.credit);
|
|
2637
2710
|
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
|
@@ -2822,14 +2895,17 @@ const serve = async ({ mux: factory, timeout }, handler) => {
|
|
|
2822
2895
|
const mux = await factory((port) => {
|
|
2823
2896
|
let off;
|
|
2824
2897
|
let finished = false;
|
|
2825
|
-
const tracked = (input) =>
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2898
|
+
const tracked = (input) => {
|
|
2899
|
+
const output = handler(input);
|
|
2900
|
+
return returningAtOnce((async function* () {
|
|
2901
|
+
try {
|
|
2902
|
+
yield* output;
|
|
2903
|
+
} finally {
|
|
2904
|
+
finished = true;
|
|
2905
|
+
if (off) live.delete(off);
|
|
2906
|
+
}
|
|
2907
|
+
})(), () => void output.return(void 0).catch(() => {}));
|
|
2908
|
+
};
|
|
2833
2909
|
off = serveDuplexOverPort(port, tracked, streamOptions);
|
|
2834
2910
|
if (!finished) live.add(off);
|
|
2835
2911
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statewalker/webrun-http-browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
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-
|
|
61
|
+
"@statewalker/webrun-rpc": "^0.8.0",
|
|
62
62
|
"@statewalker/webrun-streams": "^0.2.3",
|
|
63
|
-
"@statewalker/webrun-
|
|
63
|
+
"@statewalker/webrun-http-streams": "^0.4.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"@biomejs/biome": "^2.5.15",
|