@statewalker/webrun-http-browser 0.10.0 → 0.12.1
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/README.md +155 -10
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/service-worker-control.d.ts +2 -1
- package/dist/core/service-worker-control.d.ts.map +1 -1
- package/dist/core/sw-scope.d.ts +31 -0
- package/dist/core/sw-scope.d.ts.map +1 -0
- package/dist/index.js +816 -211
- package/dist/link/http-link.d.ts +16 -3
- package/dist/link/http-link.d.ts.map +1 -1
- package/dist/relay/index-sw.d.ts +8 -2
- package/dist/relay/index-sw.d.ts.map +1 -1
- package/dist/relay/index.d.ts +1 -1
- package/dist/relay/index.d.ts.map +1 -1
- package/dist/relay-sw.js +433 -83
- package/dist/relay-worker.d.ts +1 -0
- package/dist/relay-worker.d.ts.map +1 -1
- package/dist/relay-worker.js +433 -83
- package/dist/sw/handler-endpoint.d.ts +5 -2
- package/dist/sw/handler-endpoint.d.ts.map +1 -1
- package/dist/sw/http-sw-dispatcher.d.ts +114 -7
- package/dist/sw/http-sw-dispatcher.d.ts.map +1 -1
- package/dist/sw/index.d.ts +1 -0
- package/dist/sw/index.d.ts.map +1 -1
- package/dist/sw/protocol.d.ts +3 -0
- package/dist/sw/protocol.d.ts.map +1 -1
- package/dist/sw/sw-dispatcher.d.ts +5 -2
- package/dist/sw/sw-dispatcher.d.ts.map +1 -1
- package/dist/sw/sw-http-adapter.d.ts +19 -4
- package/dist/sw/sw-http-adapter.d.ts.map +1 -1
- package/dist/sw/worker-service.d.ts +5 -2
- package/dist/sw/worker-service.d.ts.map +1 -1
- package/dist/sw-worker.js +596 -132
- package/dist/sw.js +811 -209
- package/dist/worker.js +604 -106
- package/dist/ws/server.d.ts +50 -2
- package/dist/ws/server.d.ts.map +1 -1
- package/dist/ws/socket-core.d.ts +5 -2
- package/dist/ws/socket-core.d.ts.map +1 -1
- package/dist/ws/upgrade.d.ts +12 -1
- package/dist/ws/upgrade.d.ts.map +1 -1
- package/package.json +3 -2
- package/src/core/index.ts +1 -0
- package/src/core/service-worker-control.ts +5 -3
- package/src/core/sw-scope.ts +27 -0
- package/src/link/http-link.ts +20 -4
- package/src/relay/index-sw.ts +15 -2
- package/src/relay/index.ts +2 -2
- package/src/relay-worker.ts +1 -0
- package/src/sw/handler-endpoint.ts +17 -4
- package/src/sw/http-sw-dispatcher.ts +267 -67
- package/src/sw/index.ts +5 -0
- package/src/sw/protocol.ts +11 -1
- package/src/sw/sw-dispatcher.ts +17 -11
- package/src/sw/sw-http-adapter.ts +36 -10
- package/src/sw/worker-service.ts +11 -4
- package/src/ws/server.ts +166 -11
- package/src/ws/socket-core.ts +7 -2
- package/src/ws/upgrade.ts +22 -4
package/dist/relay-sw.js
CHANGED
|
@@ -1689,8 +1689,9 @@
|
|
|
1689
1689
|
* Returns a function that stops answering.
|
|
1690
1690
|
*/
|
|
1691
1691
|
function handleClaimRequests(self) {
|
|
1692
|
-
|
|
1693
|
-
|
|
1692
|
+
const sw = self;
|
|
1693
|
+
return handleChannelCalls(sw, CLAIM_CALL, (event) => {
|
|
1694
|
+
const claimed = sw.clients.claim().then(() => true);
|
|
1694
1695
|
event.waitUntil?.(claimed);
|
|
1695
1696
|
return claimed;
|
|
1696
1697
|
});
|
|
@@ -1852,6 +1853,99 @@
|
|
|
1852
1853
|
}
|
|
1853
1854
|
}
|
|
1854
1855
|
/**
|
|
1856
|
+
* Runs `fn` on a later task, never a microtask: a producer looping on
|
|
1857
|
+
* `whenPortReady` must let the transport's own events run. `setImmediate`
|
|
1858
|
+
* where it exists; elsewhere a `MessageChannel`, because nested `setTimeout`s
|
|
1859
|
+
* are clamped to 4 ms in browsers.
|
|
1860
|
+
*/
|
|
1861
|
+
const setImmediateFn = globalThis.setImmediate;
|
|
1862
|
+
/** @internal */
|
|
1863
|
+
const nextTask = setImmediateFn ? (fn) => {
|
|
1864
|
+
setImmediateFn(fn);
|
|
1865
|
+
} : (() => {
|
|
1866
|
+
let channel;
|
|
1867
|
+
const queue = [];
|
|
1868
|
+
return (fn) => {
|
|
1869
|
+
if (!channel) {
|
|
1870
|
+
channel = new MessageChannel();
|
|
1871
|
+
channel.port1.onmessage = () => queue.shift()?.();
|
|
1872
|
+
}
|
|
1873
|
+
queue.push(fn);
|
|
1874
|
+
channel.port2.postMessage(0);
|
|
1875
|
+
};
|
|
1876
|
+
})();
|
|
1877
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
1878
|
+
/** @internal Registered by the multiplexer for each port with a window. */
|
|
1879
|
+
function setPortSendState(port, state) {
|
|
1880
|
+
states.set(port, state);
|
|
1881
|
+
}
|
|
1882
|
+
/**
|
|
1883
|
+
* Bytes queued on `port` because its window is spent (0 for a port without
|
|
1884
|
+
* flow control). A sender that should not queue without bound waits for
|
|
1885
|
+
* `whenPortReady` before posting more.
|
|
1886
|
+
*/
|
|
1887
|
+
function portBufferedAmount(port) {
|
|
1888
|
+
return states.get(port)?.bufferedAmount() ?? 0;
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Resolves once at most `below` bytes are queued on `port`, always on a later
|
|
1892
|
+
* task (for a port without flow control, on the next one).
|
|
1893
|
+
*/
|
|
1894
|
+
function whenPortReady(port, below = 0) {
|
|
1895
|
+
return states.get(port)?.ready(below) ?? new Promise((resolve) => nextTask(resolve));
|
|
1896
|
+
}
|
|
1897
|
+
const agreements = /* @__PURE__ */ new WeakMap();
|
|
1898
|
+
/** @internal Registered by the multiplexer for each port with a window. */
|
|
1899
|
+
function setPortAgreement(port, agreement) {
|
|
1900
|
+
agreements.set(port, agreement);
|
|
1901
|
+
}
|
|
1902
|
+
/**
|
|
1903
|
+
* @internal What has been agreed with the peer on `port` so far; `undefined`
|
|
1904
|
+
* on a port without a window. Not exported from the package.
|
|
1905
|
+
*/
|
|
1906
|
+
function portAgreement(port) {
|
|
1907
|
+
return agreements.get(port)?.();
|
|
1908
|
+
}
|
|
1909
|
+
const ends = /* @__PURE__ */ new WeakMap();
|
|
1910
|
+
/** @internal Registered by the multiplexer for each port with a window. */
|
|
1911
|
+
function setPortEndState(port, state) {
|
|
1912
|
+
ends.set(port, state);
|
|
1913
|
+
}
|
|
1914
|
+
/**
|
|
1915
|
+
* @internal End this side's direction of `port`: an `end` is queued behind
|
|
1916
|
+
* everything already posted on it, and anything posted afterwards is dropped.
|
|
1917
|
+
* `false`, and nothing changes, when `end` was not agreed (no window, the
|
|
1918
|
+
* handshake not answered yet, or a peer that does not speak it): the caller
|
|
1919
|
+
* then closes the port. Never waits on the peer. Not exported from the package.
|
|
1920
|
+
*/
|
|
1921
|
+
function endPort(port) {
|
|
1922
|
+
return ends.get(port)?.end() ?? false;
|
|
1923
|
+
}
|
|
1924
|
+
/**
|
|
1925
|
+
* @internal Resolves once the peer has ended its direction of `port`. It never
|
|
1926
|
+
* settles otherwise: not on a port where `end` is not agreed when it is asked
|
|
1927
|
+
* (no window, the handshake not answered yet, a peer or codec without it), and
|
|
1928
|
+
* not when the port closes first, so a caller races it against the port's
|
|
1929
|
+
* close signal. Not exported from the package.
|
|
1930
|
+
*/
|
|
1931
|
+
function whenPortEnded(port) {
|
|
1932
|
+
let waiting = peerEnds.get(port);
|
|
1933
|
+
if (!waiting) {
|
|
1934
|
+
let resolve = () => {};
|
|
1935
|
+
const promise = new Promise((r) => {
|
|
1936
|
+
resolve = r;
|
|
1937
|
+
});
|
|
1938
|
+
if (!ends.get(port)?.watchPeerEnd(resolve)) return promise;
|
|
1939
|
+
waiting = {
|
|
1940
|
+
promise,
|
|
1941
|
+
resolve
|
|
1942
|
+
};
|
|
1943
|
+
peerEnds.set(port, waiting);
|
|
1944
|
+
}
|
|
1945
|
+
return waiting.promise;
|
|
1946
|
+
}
|
|
1947
|
+
const peerEnds = /* @__PURE__ */ new WeakMap();
|
|
1948
|
+
/**
|
|
1855
1949
|
* Makes `target` disposable with `await using`: installs `dispose` as its
|
|
1856
1950
|
* `[Symbol.asyncDispose]`, where the runtime defines the symbol. Hand-written
|
|
1857
1951
|
* iterators lose what a native generator gets from `AsyncIteratorPrototype`;
|
|
@@ -1923,6 +2017,61 @@
|
|
|
1923
2017
|
* tests and adapters assert on it.
|
|
1924
2018
|
*/
|
|
1925
2019
|
const STREAM_ABORT = "webrun-rpc:stream-abort";
|
|
2020
|
+
/**
|
|
2021
|
+
* The `type` of a pipelined chunk: a plain port message, not a `callPort`
|
|
2022
|
+
* request, sent only on a port where both ends agreed `pipeline`. The window's
|
|
2023
|
+
* credit is its confirmation.
|
|
2024
|
+
*/
|
|
2025
|
+
const CHUNK = "webrun-rpc:chunk";
|
|
2026
|
+
/**
|
|
2027
|
+
* What a pipelined chunk counts beyond its bytes, against the window, on the
|
|
2028
|
+
* receiving side. Both codecs' windows count more for the chunk's envelope
|
|
2029
|
+
* (the estimate at least 64, msgpack's keys alone more than this), so an honest
|
|
2030
|
+
* sender never makes the receiver count past the window; a sender of empty
|
|
2031
|
+
* chunks still cannot have it hold an unbounded number of them.
|
|
2032
|
+
*/
|
|
2033
|
+
const CHUNK_OVERHEAD = 32;
|
|
2034
|
+
/**
|
|
2035
|
+
* The bytes of `view` as a plain `Uint8Array` that owns its whole buffer:
|
|
2036
|
+
* `view` itself when it already is one, else a copy. Structured clone carries
|
|
2037
|
+
* and keeps a view's whole `ArrayBuffer`, and a byte codec may decode into
|
|
2038
|
+
* views of the frame, while the window and the receiver's count see only the
|
|
2039
|
+
* view: a 4 KiB `subarray` of a 4 MiB buffer would cost 4 MiB per chunk.
|
|
2040
|
+
* Copied with the `Uint8Array` constructor, not `slice()`: a Node `Buffer`'s
|
|
2041
|
+
* `slice()` is another view on the same memory.
|
|
2042
|
+
*/
|
|
2043
|
+
function ownBytes(view) {
|
|
2044
|
+
if (view instanceof Uint8Array && view.constructor === Uint8Array && view.byteOffset === 0 && view.byteLength === view.buffer.byteLength) return view;
|
|
2045
|
+
return new Uint8Array(new Uint8Array(view.buffer, view.byteOffset, view.byteLength));
|
|
2046
|
+
}
|
|
2047
|
+
/**
|
|
2048
|
+
* A producer's chunk as bytes this end may send, in either mode: any view or
|
|
2049
|
+
* `ArrayBuffer`, normalised by `ownBytes`. Anything else fails the stream here,
|
|
2050
|
+
* as the producer's own error, rather than reaching a peer that would refuse it.
|
|
2051
|
+
*/
|
|
2052
|
+
/**
|
|
2053
|
+
* A received confirmed chunk's value, as its own bytes, put back in place of
|
|
2054
|
+
* the original in `chunk`: `listenPort` keeps the request until the handler
|
|
2055
|
+
* returns, and the original would keep its whole buffer that long. A function
|
|
2056
|
+
* of its own, so that no temporary of the suspended handler holds the
|
|
2057
|
+
* original either.
|
|
2058
|
+
*/
|
|
2059
|
+
function adoptValue(chunk) {
|
|
2060
|
+
const original = chunk.value;
|
|
2061
|
+
if (ArrayBuffer.isView(original)) chunk.value = ownBytes(original);
|
|
2062
|
+
return chunk.value;
|
|
2063
|
+
}
|
|
2064
|
+
function chunkBytes(value) {
|
|
2065
|
+
if (ArrayBuffer.isView(value)) return ownBytes(value);
|
|
2066
|
+
if (value instanceof ArrayBuffer || Object.prototype.toString.call(value) === "[object ArrayBuffer]") return new Uint8Array(value);
|
|
2067
|
+
throw new TypeError(`webrun-rpc: a stream chunk must be bytes (a typed array, a DataView or an ArrayBuffer), got ${value === null ? "null" : typeof value}`);
|
|
2068
|
+
}
|
|
2069
|
+
const MIXED_MESSAGE = "webrun-rpc: peer mixed confirmed and pipelined chunks; the stream port is closed";
|
|
2070
|
+
/** This end may pipeline its own chunks: agreed, and the window holds them. */
|
|
2071
|
+
const mayPipeline = (port) => {
|
|
2072
|
+
const agreement = portAgreement(port);
|
|
2073
|
+
return !!agreement?.enforced && agreement.caps.has("pipeline");
|
|
2074
|
+
};
|
|
1926
2075
|
/** Caller's input travels on this channel; the handler listens on it. */
|
|
1927
2076
|
const CHANNEL_IN = "in";
|
|
1928
2077
|
/** The handler's output travels on this channel; the caller listens on it. */
|
|
@@ -1935,7 +2084,9 @@
|
|
|
1935
2084
|
* same way on the other channel. Within each direction the next chunk is never
|
|
1936
2085
|
* sent until the previous one has been delivered *and* pulled past by the
|
|
1937
2086
|
* consumer (spec D11) — the reply to a chunk call *is* the confirmation, and
|
|
1938
|
-
* `listenPort` withholds it until then.
|
|
2087
|
+
* `listenPort` withholds it until then. On a port whose window both ends
|
|
2088
|
+
* speak and that agreed `pipeline`, chunks go as plain messages instead, and
|
|
2089
|
+
* the window's credit is the confirmation: up to a window is in flight.
|
|
1939
2090
|
*
|
|
1940
2091
|
* A stream port carries exactly one invocation. To make several calls, open
|
|
1941
2092
|
* several ports: `mux.openPort({ kind: "stream" })` per call.
|
|
@@ -1948,22 +2099,31 @@
|
|
|
1948
2099
|
const notice = installAbortNotice(port, controller);
|
|
1949
2100
|
abortOnPortClose(port, controller);
|
|
1950
2101
|
const clock = installStreamTimeout(controller, options.timeout);
|
|
1951
|
-
const inbound = receiveChunks(port, CHANNEL_OUT, controller, clock.touch);
|
|
1952
|
-
|
|
2102
|
+
const inbound = receiveChunks(port, CHANNEL_OUT, controller, clock.touch, notice);
|
|
2103
|
+
let sent = false;
|
|
2104
|
+
const pump = sendChunks(port, CHANNEL_IN, input, options, controller.signal, clock.touch, () => {
|
|
2105
|
+
sent = true;
|
|
2106
|
+
});
|
|
1953
2107
|
pump.catch(() => {});
|
|
1954
2108
|
return returningAtOnce((async function* () {
|
|
2109
|
+
let received = false;
|
|
1955
2110
|
try {
|
|
1956
2111
|
yield* inbound.stream;
|
|
2112
|
+
received = true;
|
|
1957
2113
|
} finally {
|
|
2114
|
+
const ended = received && sent;
|
|
1958
2115
|
if (!controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("webrun-rpc: the caller abandoned the stream"));
|
|
1959
|
-
notice.post();
|
|
2116
|
+
if (!ended) notice.post();
|
|
1960
2117
|
notice.stop();
|
|
1961
2118
|
clock.stop();
|
|
1962
2119
|
inbound.stop();
|
|
1963
2120
|
await pump.catch(() => {});
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
2121
|
+
if (!(ended && endPort(port))) {
|
|
2122
|
+
notice.post();
|
|
2123
|
+
try {
|
|
2124
|
+
await port.close?.();
|
|
2125
|
+
} catch {}
|
|
2126
|
+
}
|
|
1967
2127
|
}
|
|
1968
2128
|
})(), () => {
|
|
1969
2129
|
if (!controller.signal.aborted) controller.abort(/* @__PURE__ */ new Error("webrun-rpc: the caller abandoned the stream"));
|
|
@@ -2057,7 +2217,7 @@
|
|
|
2057
2217
|
* the local consumer has not started iterating, an inbound chunk waits for it —
|
|
2058
2218
|
* which is the correct backpressure, and different from having no listener.
|
|
2059
2219
|
*/
|
|
2060
|
-
function receiveChunks(port, channelName, controller, touch) {
|
|
2220
|
+
function receiveChunks(port, channelName, controller, touch, notice) {
|
|
2061
2221
|
let deliver;
|
|
2062
2222
|
const waiting = [];
|
|
2063
2223
|
let finished = false;
|
|
@@ -2067,25 +2227,64 @@
|
|
|
2067
2227
|
const ended = new Promise((resolve) => {
|
|
2068
2228
|
resolveEnded = resolve;
|
|
2069
2229
|
});
|
|
2230
|
+
let resolveOver = () => {};
|
|
2231
|
+
const over = new Promise((resolve) => {
|
|
2232
|
+
resolveOver = resolve;
|
|
2233
|
+
});
|
|
2070
2234
|
let aborted = false;
|
|
2071
2235
|
let abortReason;
|
|
2072
2236
|
const ready = () => deliver || finished ? Promise.resolve() : new Promise((r) => waiting.push(r));
|
|
2073
2237
|
const wake = () => {
|
|
2074
2238
|
for (const r of waiting.splice(0)) r();
|
|
2075
2239
|
};
|
|
2076
|
-
const
|
|
2240
|
+
const violate = (message) => {
|
|
2241
|
+
poison = new Error(message);
|
|
2242
|
+
controller.abort(poison);
|
|
2243
|
+
setTimeout(() => {
|
|
2244
|
+
try {
|
|
2245
|
+
port.close?.();
|
|
2246
|
+
} catch {}
|
|
2247
|
+
}, 0);
|
|
2248
|
+
return poison;
|
|
2249
|
+
};
|
|
2250
|
+
let lastChunkArrived = false;
|
|
2251
|
+
let watching = false;
|
|
2252
|
+
const watchPeerEnd = () => {
|
|
2253
|
+
if (watching) return true;
|
|
2254
|
+
if (!portAgreement(port)?.caps.has("end")) return false;
|
|
2255
|
+
watching = true;
|
|
2256
|
+
whenPortEnded(port).then(() => {
|
|
2257
|
+
port.removeEventListener("message", watcher);
|
|
2258
|
+
if (lastChunkArrived) {
|
|
2259
|
+
resolveOver();
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
notice.post();
|
|
2263
|
+
violate("webrun-rpc: the peer ended its direction without finishing it; the stream port is closed");
|
|
2264
|
+
});
|
|
2265
|
+
return true;
|
|
2266
|
+
};
|
|
2267
|
+
const watcher = (event) => {
|
|
2268
|
+
const data = event.data;
|
|
2269
|
+
if (data?.channelName !== channelName) return;
|
|
2270
|
+
const chunk = data.type === CHUNK ? data.chunk : data.type === "request" ? data.params : void 0;
|
|
2271
|
+
if (!chunk) return;
|
|
2272
|
+
if (!watchPeerEnd()) {
|
|
2273
|
+
port.removeEventListener("message", watcher);
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
if (chunk.done && !chunk.error) lastChunkArrived = true;
|
|
2277
|
+
};
|
|
2278
|
+
port.addEventListener("message", watcher);
|
|
2279
|
+
watchPeerEnd();
|
|
2280
|
+
let held = 0;
|
|
2281
|
+
const off = listenPort(port, async (params) => {
|
|
2282
|
+
const { done, error } = params;
|
|
2283
|
+
const value = adoptValue(params);
|
|
2077
2284
|
touch();
|
|
2078
2285
|
if (poison) throw poison;
|
|
2079
|
-
if (
|
|
2080
|
-
|
|
2081
|
-
controller.abort(poison);
|
|
2082
|
-
setTimeout(() => {
|
|
2083
|
-
try {
|
|
2084
|
-
port.close?.();
|
|
2085
|
-
} catch {}
|
|
2086
|
-
}, 0);
|
|
2087
|
-
throw poison;
|
|
2088
|
-
}
|
|
2286
|
+
if (held > 0) throw violate(MIXED_MESSAGE);
|
|
2287
|
+
if (outstanding) throw violate("webrun-rpc: peer sent a second unconfirmed chunk; the stream port is closed");
|
|
2089
2288
|
outstanding = true;
|
|
2090
2289
|
try {
|
|
2091
2290
|
await ready();
|
|
@@ -2095,15 +2294,60 @@
|
|
|
2095
2294
|
value,
|
|
2096
2295
|
error: error ? deserializeError(error) : void 0
|
|
2097
2296
|
});
|
|
2297
|
+
if (done && !error) resolveOver();
|
|
2098
2298
|
} finally {
|
|
2099
2299
|
outstanding = false;
|
|
2100
2300
|
}
|
|
2101
2301
|
if (done) resolveEnded();
|
|
2102
2302
|
}, { channelName });
|
|
2303
|
+
const onChunk = (event) => {
|
|
2304
|
+
const data = event.data;
|
|
2305
|
+
if (data?.type !== CHUNK || data.channelName !== channelName || !data.chunk) return;
|
|
2306
|
+
if (poison) return;
|
|
2307
|
+
const agreement = portAgreement(port);
|
|
2308
|
+
if (!agreement?.caps.has("pipeline")) {
|
|
2309
|
+
violate("webrun-rpc: peer sent a pipelined chunk where pipelining was not agreed");
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
if (outstanding) {
|
|
2313
|
+
violate(MIXED_MESSAGE);
|
|
2314
|
+
return;
|
|
2315
|
+
}
|
|
2316
|
+
if (data.chunk.value !== void 0 && !ArrayBuffer.isView(data.chunk.value)) {
|
|
2317
|
+
violate("webrun-rpc: peer sent a pipelined chunk whose value is not bytes");
|
|
2318
|
+
return;
|
|
2319
|
+
}
|
|
2320
|
+
if (held > agreement.window) {
|
|
2321
|
+
violate(`webrun-rpc: peer sent past the window (${agreement.window} bytes) without waiting for credit; the stream port is closed`);
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
touch();
|
|
2325
|
+
const { done, error } = data.chunk;
|
|
2326
|
+
const value = data.chunk.value && ownBytes(data.chunk.value);
|
|
2327
|
+
const size = (value?.byteLength ?? 0) + CHUNK_OVERHEAD;
|
|
2328
|
+
held += size;
|
|
2329
|
+
return (async () => {
|
|
2330
|
+
try {
|
|
2331
|
+
await ready();
|
|
2332
|
+
if (finished) return;
|
|
2333
|
+
await deliver?.({
|
|
2334
|
+
done,
|
|
2335
|
+
value,
|
|
2336
|
+
error: error ? deserializeError(error) : void 0
|
|
2337
|
+
});
|
|
2338
|
+
if (done && !error) resolveOver();
|
|
2339
|
+
if (done) resolveEnded();
|
|
2340
|
+
} finally {
|
|
2341
|
+
held -= size;
|
|
2342
|
+
}
|
|
2343
|
+
})();
|
|
2344
|
+
};
|
|
2345
|
+
port.addEventListener("message", onChunk);
|
|
2103
2346
|
const detach = () => {
|
|
2104
2347
|
finished = true;
|
|
2105
2348
|
wake();
|
|
2106
2349
|
off();
|
|
2350
|
+
port.removeEventListener("message", onChunk);
|
|
2107
2351
|
controller.signal.removeEventListener("abort", onAbort);
|
|
2108
2352
|
resolveEnded();
|
|
2109
2353
|
};
|
|
@@ -2133,25 +2377,35 @@
|
|
|
2133
2377
|
return detach;
|
|
2134
2378
|
}),
|
|
2135
2379
|
stop: detach,
|
|
2136
|
-
ended
|
|
2380
|
+
ended,
|
|
2381
|
+
over
|
|
2137
2382
|
};
|
|
2138
2383
|
}
|
|
2139
2384
|
/**
|
|
2140
2385
|
* The sending half of one direction: one `callPort` per chunk, one call
|
|
2141
|
-
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8)
|
|
2386
|
+
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8) —
|
|
2387
|
+
* or, once pipelining is agreed, plain chunk messages held back by the window.
|
|
2388
|
+
* `onSent` runs once the end of the output has left without an error: posted
|
|
2389
|
+
* when pipelined, confirmed when not.
|
|
2142
2390
|
*/
|
|
2143
|
-
async function sendChunks(port, channelName, output, { maxMessageSize, log }, signal, touch) {
|
|
2391
|
+
async function sendChunks(port, channelName, output, { maxMessageSize, log }, signal, touch, onSent) {
|
|
2144
2392
|
const framed = maxMessageSize ? toChunks(maxMessageSize)(output) : output;
|
|
2145
2393
|
const stream = throughAbort(framed, signal);
|
|
2146
2394
|
const producer = output;
|
|
2147
2395
|
const returnProducer = () => void Promise.resolve(producer.return?.()).catch(() => {});
|
|
2148
2396
|
if (framed !== output) signal.addEventListener("abort", returnProducer, { once: true });
|
|
2397
|
+
let onAborted = () => {};
|
|
2398
|
+
const aborted = new Promise((resolve) => {
|
|
2399
|
+
onAborted = () => resolve();
|
|
2400
|
+
});
|
|
2401
|
+
if (signal.aborted) onAborted();
|
|
2402
|
+
else signal.addEventListener("abort", onAborted, { once: true });
|
|
2149
2403
|
try {
|
|
2150
2404
|
await sendIterator(async ({ done, value, error }) => {
|
|
2151
2405
|
if (signal.aborted) return;
|
|
2152
2406
|
const chunk = {
|
|
2153
2407
|
done,
|
|
2154
|
-
value,
|
|
2408
|
+
value: done ? value : chunkBytes(value),
|
|
2155
2409
|
error: error === void 0 ? void 0 : serializeError(error)
|
|
2156
2410
|
};
|
|
2157
2411
|
log?.("[duplexOverPort] send", {
|
|
@@ -2159,48 +2413,34 @@
|
|
|
2159
2413
|
done,
|
|
2160
2414
|
size: value?.byteLength
|
|
2161
2415
|
});
|
|
2416
|
+
if (mayPipeline(port)) {
|
|
2417
|
+
port.postMessage({
|
|
2418
|
+
type: CHUNK,
|
|
2419
|
+
channelName,
|
|
2420
|
+
chunk
|
|
2421
|
+
});
|
|
2422
|
+
touch();
|
|
2423
|
+
if (done && !error) onSent();
|
|
2424
|
+
if (portBufferedAmount(port) > 0) await Promise.race([whenPortReady(port), aborted]);
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2162
2427
|
await callPort(port, chunk, {
|
|
2163
2428
|
channelName,
|
|
2164
2429
|
timeout: NO_TIMEOUT,
|
|
2165
2430
|
signal
|
|
2166
2431
|
});
|
|
2167
2432
|
touch();
|
|
2433
|
+
if (done && !error) onSent();
|
|
2168
2434
|
}, stream);
|
|
2169
2435
|
} catch (err) {
|
|
2170
2436
|
if (signal.aborted) return;
|
|
2171
2437
|
throw err;
|
|
2172
2438
|
} finally {
|
|
2173
2439
|
signal.removeEventListener("abort", returnProducer);
|
|
2440
|
+
signal.removeEventListener("abort", onAborted);
|
|
2174
2441
|
}
|
|
2175
2442
|
}
|
|
2176
2443
|
/**
|
|
2177
|
-
* Runs `fn` on a later task, never a microtask: a producer looping on
|
|
2178
|
-
* `whenPortReady` must let the transport's own events run. `setImmediate`
|
|
2179
|
-
* where it exists; elsewhere a `MessageChannel`, because nested `setTimeout`s
|
|
2180
|
-
* are clamped to 4 ms in browsers.
|
|
2181
|
-
*/
|
|
2182
|
-
const setImmediateFn = globalThis.setImmediate;
|
|
2183
|
-
/** @internal */
|
|
2184
|
-
const nextTask = setImmediateFn ? (fn) => {
|
|
2185
|
-
setImmediateFn(fn);
|
|
2186
|
-
} : (() => {
|
|
2187
|
-
let channel;
|
|
2188
|
-
const queue = [];
|
|
2189
|
-
return (fn) => {
|
|
2190
|
-
if (!channel) {
|
|
2191
|
-
channel = new MessageChannel();
|
|
2192
|
-
channel.port1.onmessage = () => queue.shift()?.();
|
|
2193
|
-
}
|
|
2194
|
-
queue.push(fn);
|
|
2195
|
-
channel.port2.postMessage(0);
|
|
2196
|
-
};
|
|
2197
|
-
})();
|
|
2198
|
-
const states = /* @__PURE__ */ new WeakMap();
|
|
2199
|
-
/** @internal Registered by the multiplexer for each port with a window. */
|
|
2200
|
-
function setPortSendState(port, state) {
|
|
2201
|
-
states.set(port, state);
|
|
2202
|
-
}
|
|
2203
|
-
/**
|
|
2204
2444
|
* One virtual port.
|
|
2205
2445
|
*
|
|
2206
2446
|
* `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
|
|
@@ -2278,6 +2518,21 @@
|
|
|
2278
2518
|
const KEEPALIVE_ID = Number.MAX_SAFE_INTEGER;
|
|
2279
2519
|
/** The longest delay a timer holds; past it `setInterval` fires at once. */
|
|
2280
2520
|
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
2521
|
+
/**
|
|
2522
|
+
* What a windowed port offers in its `open`: every capability this end
|
|
2523
|
+
* speaks. `end` only over a codec that reads it (`PortCodec.halfClose`).
|
|
2524
|
+
*/
|
|
2525
|
+
function ownCaps(codec) {
|
|
2526
|
+
return codec.halfClose ? ["pipeline", "end"] : ["pipeline"];
|
|
2527
|
+
}
|
|
2528
|
+
/**
|
|
2529
|
+
* The capabilities in a peer's `caps` that this end speaks too, or
|
|
2530
|
+
* `undefined` when the peer listed none (it is then treated as an older peer).
|
|
2531
|
+
*/
|
|
2532
|
+
function sharedCaps(own, listed) {
|
|
2533
|
+
if (!Array.isArray(listed)) return void 0;
|
|
2534
|
+
return new Set(own.filter((cap) => listed.includes(cap)));
|
|
2535
|
+
}
|
|
2281
2536
|
/** Ceiling on concurrently open virtual ports. Bounds the id table only. */
|
|
2282
2537
|
const DEFAULT_MAX_PORTS = 1024;
|
|
2283
2538
|
/**
|
|
@@ -2341,6 +2596,8 @@
|
|
|
2341
2596
|
let muxClosed = false;
|
|
2342
2597
|
/** This end counts encoded bytes and reassembles frames. */
|
|
2343
2598
|
const exactCodec = codec.encode != null && codec.decode != null;
|
|
2599
|
+
/** What this end offers on a windowed port. */
|
|
2600
|
+
const caps = ownCaps(codec);
|
|
2344
2601
|
const post = (envelope, transfer) => {
|
|
2345
2602
|
if (muxClosed) return;
|
|
2346
2603
|
codecPost(envelope, transfer);
|
|
@@ -2398,13 +2655,39 @@
|
|
|
2398
2655
|
port.postMessage(bytes);
|
|
2399
2656
|
} catch {}
|
|
2400
2657
|
};
|
|
2658
|
+
/**
|
|
2659
|
+
* Both directions have ended: free the id as a close would, with no `close`
|
|
2660
|
+
* on the wire (each end frees it on its own once it has seen both ends).
|
|
2661
|
+
* Aborting the close signal is how the port's consumer learns it is gone.
|
|
2662
|
+
*/
|
|
2663
|
+
const freeIfEnded = (id, c) => {
|
|
2664
|
+
const entry = open.get(id);
|
|
2665
|
+
if (!c.endSent || !c.peerEnded || entry?.credit !== c) return;
|
|
2666
|
+
freePartial(entry);
|
|
2667
|
+
open.delete(id);
|
|
2668
|
+
leaks?.unregister(entry.handle);
|
|
2669
|
+
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the port ended in both directions"));
|
|
2670
|
+
};
|
|
2671
|
+
/** A peer that broke the port's protocol: hold none of its traffic, and close. */
|
|
2672
|
+
const failPort = (id, entry, error, reason) => {
|
|
2673
|
+
freePartial(entry);
|
|
2674
|
+
dropOutgoing(id);
|
|
2675
|
+
open.delete(id);
|
|
2676
|
+
releaseWaiters(entry.credit);
|
|
2677
|
+
entry.handle.markClosed(error);
|
|
2678
|
+
post({
|
|
2679
|
+
type: "close",
|
|
2680
|
+
id,
|
|
2681
|
+
reason
|
|
2682
|
+
});
|
|
2683
|
+
};
|
|
2401
2684
|
const releaseWaiters = (c) => {
|
|
2402
2685
|
for (const w of c?.waiters.splice(0) ?? []) w.resolve();
|
|
2403
2686
|
};
|
|
2404
2687
|
const flush = (c) => {
|
|
2405
2688
|
while (c.queue.length) {
|
|
2406
2689
|
const next = c.queue[0];
|
|
2407
|
-
if (c.enforced && c.outstanding > 0 && c.outstanding + next.size > c.window) break;
|
|
2690
|
+
if (next.size > 0 && c.enforced && c.outstanding > 0 && c.outstanding + next.size > c.window) break;
|
|
2408
2691
|
c.queue.shift();
|
|
2409
2692
|
c.queued -= next.size;
|
|
2410
2693
|
c.outstanding += next.size;
|
|
@@ -2442,15 +2725,20 @@
|
|
|
2442
2725
|
enforced: false,
|
|
2443
2726
|
opener,
|
|
2444
2727
|
exact,
|
|
2728
|
+
caps: /* @__PURE__ */ new Set(),
|
|
2445
2729
|
outstanding: 0,
|
|
2446
2730
|
queue: [],
|
|
2447
2731
|
queued: 0,
|
|
2448
2732
|
unconsumed: 0,
|
|
2449
2733
|
waiters: [],
|
|
2450
2734
|
toGrant: 0,
|
|
2451
|
-
grantScheduled: false
|
|
2735
|
+
grantScheduled: false,
|
|
2736
|
+
ending: false,
|
|
2737
|
+
endSent: false,
|
|
2738
|
+
peerEnded: false
|
|
2452
2739
|
} : void 0;
|
|
2453
2740
|
const self = newVirtualPort((payload, transfer) => {
|
|
2741
|
+
if (c?.ending) return;
|
|
2454
2742
|
touch(id);
|
|
2455
2743
|
const envelope = {
|
|
2456
2744
|
type: "message",
|
|
@@ -2537,6 +2825,38 @@
|
|
|
2537
2825
|
});
|
|
2538
2826
|
})
|
|
2539
2827
|
});
|
|
2828
|
+
setPortEndState(self.port, {
|
|
2829
|
+
end: () => {
|
|
2830
|
+
if (isClosed() || !c.caps.has("end")) return false;
|
|
2831
|
+
if (c.ending) return true;
|
|
2832
|
+
c.ending = true;
|
|
2833
|
+
c.queue.push({
|
|
2834
|
+
size: 0,
|
|
2835
|
+
send: () => sendOnPort(id, () => {
|
|
2836
|
+
if (open.get(id)?.credit !== c) return;
|
|
2837
|
+
post({
|
|
2838
|
+
type: "end",
|
|
2839
|
+
id
|
|
2840
|
+
});
|
|
2841
|
+
c.endSent = true;
|
|
2842
|
+
freeIfEnded(id, c);
|
|
2843
|
+
})
|
|
2844
|
+
});
|
|
2845
|
+
flush(c);
|
|
2846
|
+
return true;
|
|
2847
|
+
},
|
|
2848
|
+
watchPeerEnd: (resolve) => {
|
|
2849
|
+
if (!c.caps.has("end")) return false;
|
|
2850
|
+
if (c.peerEnded) resolve();
|
|
2851
|
+
else c.onPeerEnd = new WeakRef(resolve);
|
|
2852
|
+
return true;
|
|
2853
|
+
}
|
|
2854
|
+
});
|
|
2855
|
+
setPortAgreement(self.port, () => ({
|
|
2856
|
+
window: c.window,
|
|
2857
|
+
caps: c.caps,
|
|
2858
|
+
enforced: c.enforced
|
|
2859
|
+
}));
|
|
2540
2860
|
}
|
|
2541
2861
|
const record = {
|
|
2542
2862
|
deliver: self.deliver,
|
|
@@ -2588,16 +2908,24 @@
|
|
|
2588
2908
|
}
|
|
2589
2909
|
const handle = attach(envelope.id, envelope.meta, envelope.window, false, envelope.exact === true && exactCodec);
|
|
2590
2910
|
const credit = open.get(envelope.id)?.credit;
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2911
|
+
const listed = sharedCaps(caps, envelope.caps);
|
|
2912
|
+
if (credit) {
|
|
2913
|
+
const ack = credit.exact ? {
|
|
2914
|
+
type: "credit",
|
|
2915
|
+
id: envelope.id,
|
|
2916
|
+
bytes: 0,
|
|
2917
|
+
exact: true
|
|
2918
|
+
} : {
|
|
2919
|
+
type: "credit",
|
|
2920
|
+
id: envelope.id,
|
|
2921
|
+
bytes: 0
|
|
2922
|
+
};
|
|
2923
|
+
if (listed) credit.caps = listed;
|
|
2924
|
+
post(listed ? {
|
|
2925
|
+
...ack,
|
|
2926
|
+
caps: [...listed]
|
|
2927
|
+
} : ack);
|
|
2928
|
+
}
|
|
2601
2929
|
let accepted = false;
|
|
2602
2930
|
if (onPort) try {
|
|
2603
2931
|
accepted = onPort(handle.port, envelope.meta) !== false;
|
|
@@ -2625,6 +2953,7 @@
|
|
|
2625
2953
|
c.exact = false;
|
|
2626
2954
|
c.outstanding = 0;
|
|
2627
2955
|
}
|
|
2956
|
+
c.caps = sharedCaps(caps, envelope.caps) ?? /* @__PURE__ */ new Set();
|
|
2628
2957
|
post({
|
|
2629
2958
|
type: "credit",
|
|
2630
2959
|
id: envelope.id,
|
|
@@ -2636,6 +2965,10 @@
|
|
|
2636
2965
|
flush(c);
|
|
2637
2966
|
return;
|
|
2638
2967
|
}
|
|
2968
|
+
if ((envelope.type === "message" || envelope.type === "frame") && entry?.credit?.peerEnded) {
|
|
2969
|
+
failPort(envelope.id, entry, /* @__PURE__ */ new Error("webrun-rpc: the peer sent on a port after its end"), "protocol");
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2639
2972
|
if (envelope.type === "message") {
|
|
2640
2973
|
const sizeOf = (c) => (c.exact ? byteLengthOf(event.data) : void 0) ?? estimateMessageSize(envelope.payload);
|
|
2641
2974
|
if (!entry) return;
|
|
@@ -2660,16 +2993,7 @@
|
|
|
2660
2993
|
const charged = partial.room == null ? Math.max(0, partial.bytes + size - c.window) : 0;
|
|
2661
2994
|
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
2995
|
if (overrun) {
|
|
2663
|
-
|
|
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
|
-
});
|
|
2996
|
+
failPort(envelope.id, entry, /* @__PURE__ */ new RangeError(`webrun-rpc: a framed message passed ${overrun}`), "max-reassembled-bytes");
|
|
2673
2997
|
return;
|
|
2674
2998
|
}
|
|
2675
2999
|
partial.bytes += size;
|
|
@@ -2689,6 +3013,19 @@
|
|
|
2689
3013
|
entry.handle.deliver(inner.payload, whole.byteLength);
|
|
2690
3014
|
return;
|
|
2691
3015
|
}
|
|
3016
|
+
if (envelope.type === "end") {
|
|
3017
|
+
if (!entry) return;
|
|
3018
|
+
const c = entry.credit;
|
|
3019
|
+
if (!c?.caps.has("end")) {
|
|
3020
|
+
failPort(envelope.id, entry, /* @__PURE__ */ new Error("webrun-rpc: the peer ended a port where end was not agreed"), "protocol");
|
|
3021
|
+
return;
|
|
3022
|
+
}
|
|
3023
|
+
touch(envelope.id);
|
|
3024
|
+
c.peerEnded = true;
|
|
3025
|
+
c.onPeerEnd?.deref()?.();
|
|
3026
|
+
freeIfEnded(envelope.id, c);
|
|
3027
|
+
return;
|
|
3028
|
+
}
|
|
2692
3029
|
dropOutgoing(envelope.id);
|
|
2693
3030
|
if (!entry) return;
|
|
2694
3031
|
freePartial(entry);
|
|
@@ -2770,12 +3107,14 @@
|
|
|
2770
3107
|
id,
|
|
2771
3108
|
meta,
|
|
2772
3109
|
window,
|
|
2773
|
-
exact: true
|
|
3110
|
+
exact: true,
|
|
3111
|
+
caps: [...caps]
|
|
2774
3112
|
} : {
|
|
2775
3113
|
type: "open",
|
|
2776
3114
|
id,
|
|
2777
3115
|
meta,
|
|
2778
|
-
window
|
|
3116
|
+
window,
|
|
3117
|
+
caps: [...caps]
|
|
2779
3118
|
} : {
|
|
2780
3119
|
type: "open",
|
|
2781
3120
|
id,
|
|
@@ -2882,7 +3221,7 @@
|
|
|
2882
3221
|
const candidate = value;
|
|
2883
3222
|
if (typeof candidate.id !== "number") return false;
|
|
2884
3223
|
if (!Number.isInteger(candidate.id) || candidate.id < 0) return false;
|
|
2885
|
-
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close" || candidate.type === "credit" || candidate.type === "frame" || candidate.type === "ping" || candidate.type === "pong";
|
|
3224
|
+
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close" || candidate.type === "credit" || candidate.type === "frame" || candidate.type === "ping" || candidate.type === "pong" || candidate.type === "end";
|
|
2886
3225
|
}
|
|
2887
3226
|
/**
|
|
2888
3227
|
* For ports whose messages are structured values — a real `MessagePort`, a
|
|
@@ -2894,6 +3233,7 @@
|
|
|
2894
3233
|
* not send anything a byte codec could not also carry.
|
|
2895
3234
|
*/
|
|
2896
3235
|
const structuredCodec = {
|
|
3236
|
+
halfClose: true,
|
|
2897
3237
|
post(port, envelope, transfer) {
|
|
2898
3238
|
if (transfer && transfer.length > 0) port.postMessage(envelope, transfer);
|
|
2899
3239
|
else port.postMessage(envelope);
|
|
@@ -2931,10 +3271,15 @@
|
|
|
2931
3271
|
* own `handleStreams` / `sendStream` (no backpressure); version 2 is
|
|
2932
3272
|
* webrun-rpc's `connect` / `serve` over `overPipe` with webrun-http-streams'
|
|
2933
3273
|
* `fetchOverDuplex` / `serveFetchOverDuplex`: one port per link, calls
|
|
2934
|
-
* multiplexed on it, each call a stream with
|
|
2935
|
-
* directions, and cancellation that reaches the handler.
|
|
3274
|
+
* multiplexed on it, each call a stream with backpressure in both
|
|
3275
|
+
* directions, and cancellation that reaches the handler. A call's port with a
|
|
3276
|
+
* window (`HttpLinkOptions.window`) pipelines its chunks where both ends speak
|
|
3277
|
+
* it, and goes one confirmed chunk at a time otherwise, so the window needs
|
|
3278
|
+
* no new version.
|
|
2936
3279
|
*/
|
|
2937
3280
|
const HTTP_LINK_PROTOCOL = "webrun-http-link/2";
|
|
3281
|
+
/** The calling end's default `HttpLinkOptions.window`: 256 KiB. */
|
|
3282
|
+
const DEFAULT_HTTP_LINK_WINDOW = 262144;
|
|
2938
3283
|
/**
|
|
2939
3284
|
* Why a request failed when its link went away under it: the far side (the
|
|
2940
3285
|
* handler, or the ServiceWorker) was lost. The request may or may not have
|
|
@@ -2953,12 +3298,13 @@
|
|
|
2953
3298
|
*/
|
|
2954
3299
|
const LINK_CLOSE = "webrun-http:link-close";
|
|
2955
3300
|
/** Opens the calling end of a link on `port` (one port per link). */
|
|
2956
|
-
async function connectHttpLink(port, { streamTimeout, maxPorts } = {}) {
|
|
3301
|
+
async function connectHttpLink(port, { streamTimeout, maxPorts, window = DEFAULT_HTTP_LINK_WINDOW } = {}) {
|
|
2957
3302
|
const { call, close } = await connect({
|
|
2958
3303
|
mux: overPipe(port, {
|
|
2959
3304
|
codec: structuredCodec,
|
|
2960
3305
|
side: "initiator",
|
|
2961
|
-
maxPorts
|
|
3306
|
+
maxPorts,
|
|
3307
|
+
window
|
|
2962
3308
|
}),
|
|
2963
3309
|
timeout: streamTimeout
|
|
2964
3310
|
});
|
|
@@ -3521,7 +3867,8 @@
|
|
|
3521
3867
|
* the client that registered `key`, and exposes REGISTER/UNREGISTER/CONNECT
|
|
3522
3868
|
* channel calls used by the page-side relay client.
|
|
3523
3869
|
*/
|
|
3524
|
-
function startRelayServiceWorker(
|
|
3870
|
+
function startRelayServiceWorker(scope, options = {}) {
|
|
3871
|
+
const self = scope;
|
|
3525
3872
|
const [register, clear] = newRegistry();
|
|
3526
3873
|
const mounts = newMountTable({ exclude: options.exclude });
|
|
3527
3874
|
const hostKeys = /* @__PURE__ */ new Set();
|
|
@@ -3620,7 +3967,10 @@
|
|
|
3620
3967
|
wire: HTTP_LINK_PROTOCOL
|
|
3621
3968
|
}, channel.port2);
|
|
3622
3969
|
if (!reply) throw HttpError.errorForbidden(params);
|
|
3623
|
-
const response = await sendOverConnection(channel.port1, reply, request, {
|
|
3970
|
+
const response = await sendOverConnection(channel.port1, reply, request, {
|
|
3971
|
+
streamTimeout: options.streamTimeout,
|
|
3972
|
+
window: options.window
|
|
3973
|
+
});
|
|
3624
3974
|
return options.decorateResponse?.(response, request) ?? response;
|
|
3625
3975
|
} catch (error) {
|
|
3626
3976
|
const httpError = HttpError.fromError(error);
|