@statewalker/webrun-http-browser 0.7.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +716 -84
- package/dist/relay-sw.js +647 -90
- package/dist/relay-worker.js +647 -90
- package/dist/sw/protocol.d.ts +2 -0
- package/dist/sw/protocol.d.ts.map +1 -1
- package/dist/sw/sw-http-adapter.d.ts +2 -0
- package/dist/sw/sw-http-adapter.d.ts.map +1 -1
- package/dist/sw/worker-service.d.ts.map +1 -1
- package/dist/sw-worker.js +695 -81
- package/dist/sw.js +709 -83
- package/dist/worker.js +704 -82
- package/package.json +3 -3
- package/src/link/http-link.ts +42 -6
- package/src/sw/protocol.ts +4 -3
- package/src/sw/sw-http-adapter.ts +7 -0
- package/src/sw/worker-service.ts +14 -1
package/dist/relay-sw.js
CHANGED
|
@@ -1368,26 +1368,6 @@
|
|
|
1368
1368
|
} catch {}
|
|
1369
1369
|
}
|
|
1370
1370
|
}
|
|
1371
|
-
function asyncIterableToReadable(iter) {
|
|
1372
|
-
const it = iter[Symbol.asyncIterator]();
|
|
1373
|
-
return new ReadableStream({
|
|
1374
|
-
async pull(controller) {
|
|
1375
|
-
try {
|
|
1376
|
-
const { done, value } = await it.next();
|
|
1377
|
-
if (done) {
|
|
1378
|
-
controller.close();
|
|
1379
|
-
return;
|
|
1380
|
-
}
|
|
1381
|
-
controller.enqueue(value);
|
|
1382
|
-
} catch (err) {
|
|
1383
|
-
controller.error(err);
|
|
1384
|
-
}
|
|
1385
|
-
},
|
|
1386
|
-
async cancel() {
|
|
1387
|
-
await it.return?.();
|
|
1388
|
-
}
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1391
1371
|
/**
|
|
1392
1372
|
* Run a `Request` through a `Duplex` call and reconstruct the `Response` on
|
|
1393
1373
|
* the other side. The request's `signal` is plumbed into the body iteration —
|
|
@@ -1420,17 +1400,80 @@
|
|
|
1420
1400
|
else await release?.();
|
|
1421
1401
|
return new Response(null, responseInit);
|
|
1422
1402
|
}
|
|
1423
|
-
return new Response(
|
|
1403
|
+
return new Response(responseBody(respBody, request.signal, release), responseInit);
|
|
1424
1404
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1405
|
+
/**
|
|
1406
|
+
* How long a cancelled body waits for the read in flight before it abandons the call anyway.
|
|
1407
|
+
* The read's answer tells a body that has ended (the call is finished: an upload the handler
|
|
1408
|
+
* reads after answering goes on) from one that has not; a peer that sends nothing more never
|
|
1409
|
+
* answers it. A body whose end comes later than this after the cancel is abandoned too, so a
|
|
1410
|
+
* handler still streaming its response and still reading the upload then loses the upload.
|
|
1411
|
+
*/
|
|
1412
|
+
const CANCEL_GRACE_MS = 1e3;
|
|
1413
|
+
/**
|
|
1414
|
+
* The response body as a stream that the request's signal can end at any time.
|
|
1415
|
+
*
|
|
1416
|
+
* An abort errors the stream with the abort's reason at once — a read waiting
|
|
1417
|
+
* for a peer that sends nothing more rejects then, not when a next chunk
|
|
1418
|
+
* arrives, which may be never — and abandons the call (`release`), so on a
|
|
1419
|
+
* multiplexed transport its port is given back whether or not anyone still
|
|
1420
|
+
* reads the body. The listener is removed once the body ends, however it ends:
|
|
1421
|
+
* read to its end, cancelled, failed or aborted. A body nobody reads or cancels
|
|
1422
|
+
* keeps it until the signal aborts (and keeps the call open until then anyway).
|
|
1423
|
+
* A cancel abandons the call too — unless the read in flight comes back with the
|
|
1424
|
+
* body's end, which finishes it instead (see `cancel`).
|
|
1425
|
+
*/
|
|
1426
|
+
function responseBody(body, signal, release) {
|
|
1427
|
+
const it = body[Symbol.asyncIterator]();
|
|
1428
|
+
let controller;
|
|
1429
|
+
let ended = false;
|
|
1430
|
+
let reading = false;
|
|
1431
|
+
const end = () => {
|
|
1432
|
+
ended = true;
|
|
1433
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1434
|
+
};
|
|
1435
|
+
const onAbort = () => {
|
|
1436
|
+
if (ended) return;
|
|
1437
|
+
end();
|
|
1438
|
+
try {
|
|
1439
|
+
controller.error(abortReason$1(signal));
|
|
1440
|
+
} catch {}
|
|
1441
|
+
release?.();
|
|
1442
|
+
};
|
|
1443
|
+
return new ReadableStream({
|
|
1444
|
+
start(c) {
|
|
1445
|
+
controller = c;
|
|
1446
|
+
if (signal?.aborted) onAbort();
|
|
1447
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
1448
|
+
},
|
|
1449
|
+
async pull(c) {
|
|
1450
|
+
try {
|
|
1451
|
+
reading = true;
|
|
1452
|
+
const { done, value } = await it.next().finally(() => {
|
|
1453
|
+
reading = false;
|
|
1454
|
+
});
|
|
1455
|
+
if (ended) return;
|
|
1456
|
+
if (done) {
|
|
1457
|
+
end();
|
|
1458
|
+
c.close();
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
c.enqueue(value);
|
|
1462
|
+
} catch (err) {
|
|
1463
|
+
if (ended) return;
|
|
1464
|
+
end();
|
|
1465
|
+
c.error(err);
|
|
1466
|
+
}
|
|
1467
|
+
},
|
|
1468
|
+
cancel() {
|
|
1469
|
+
end();
|
|
1470
|
+
const returned = Promise.resolve(it.return?.()).catch(() => {});
|
|
1471
|
+
if (!reading) return;
|
|
1472
|
+
const grace = setTimeout(() => void release?.(), CANCEL_GRACE_MS);
|
|
1473
|
+
grace.unref?.();
|
|
1474
|
+
returned.finally(() => clearTimeout(grace));
|
|
1475
|
+
}
|
|
1476
|
+
});
|
|
1434
1477
|
}
|
|
1435
1478
|
var HttpError = class HttpError extends Error {
|
|
1436
1479
|
status;
|
|
@@ -1775,8 +1818,11 @@
|
|
|
1775
1818
|
error
|
|
1776
1819
|
});
|
|
1777
1820
|
};
|
|
1778
|
-
|
|
1779
|
-
|
|
1821
|
+
const listener = (event) => {
|
|
1822
|
+
onMessage(event);
|
|
1823
|
+
};
|
|
1824
|
+
port.addEventListener("message", listener);
|
|
1825
|
+
return () => port.removeEventListener("message", listener);
|
|
1780
1826
|
}
|
|
1781
1827
|
/**
|
|
1782
1828
|
* Wraps an async iterable so that an `AbortSignal` firing causes the wrapper
|
|
@@ -2095,7 +2141,11 @@
|
|
|
2095
2141
|
* outstanding at a time (spec D11/D12), with no per-chunk deadline (spec D8).
|
|
2096
2142
|
*/
|
|
2097
2143
|
async function sendChunks(port, channelName, output, { maxMessageSize, log }, signal, touch) {
|
|
2098
|
-
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 });
|
|
2099
2149
|
try {
|
|
2100
2150
|
await sendIterator(async ({ done, value, error }) => {
|
|
2101
2151
|
if (signal.aborted) return;
|
|
@@ -2119,7 +2169,36 @@
|
|
|
2119
2169
|
} catch (err) {
|
|
2120
2170
|
if (signal.aborted) return;
|
|
2121
2171
|
throw err;
|
|
2122
|
-
}
|
|
2172
|
+
} finally {
|
|
2173
|
+
signal.removeEventListener("abort", returnProducer);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
/**
|
|
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);
|
|
2123
2202
|
}
|
|
2124
2203
|
/**
|
|
2125
2204
|
* One virtual port.
|
|
@@ -2136,6 +2215,14 @@
|
|
|
2136
2215
|
if (closing.signal.aborted) return;
|
|
2137
2216
|
closing.abort(reason ?? /* @__PURE__ */ new Error("webrun-rpc: the port was closed"));
|
|
2138
2217
|
};
|
|
2218
|
+
const close = (reason) => {
|
|
2219
|
+
if (closed) return;
|
|
2220
|
+
closed = true;
|
|
2221
|
+
const notify = requestClose;
|
|
2222
|
+
listeners.clear();
|
|
2223
|
+
notify(reason);
|
|
2224
|
+
abort(reason);
|
|
2225
|
+
};
|
|
2139
2226
|
const port = {
|
|
2140
2227
|
addEventListener(_type, listener) {
|
|
2141
2228
|
listeners.add(listener);
|
|
@@ -2147,24 +2234,28 @@
|
|
|
2147
2234
|
if (closed) return;
|
|
2148
2235
|
send(message, transfer);
|
|
2149
2236
|
},
|
|
2150
|
-
close
|
|
2151
|
-
if (closed) return;
|
|
2152
|
-
closed = true;
|
|
2153
|
-
const notify = requestClose;
|
|
2154
|
-
listeners.clear();
|
|
2155
|
-
notify();
|
|
2156
|
-
abort();
|
|
2157
|
-
}
|
|
2237
|
+
close
|
|
2158
2238
|
};
|
|
2159
2239
|
setPortCloseSignal(port, closing.signal);
|
|
2240
|
+
const dispose = Symbol.dispose;
|
|
2241
|
+
if (dispose) Object.defineProperty(port, dispose, {
|
|
2242
|
+
value: () => close(),
|
|
2243
|
+
configurable: true
|
|
2244
|
+
});
|
|
2160
2245
|
return {
|
|
2161
2246
|
port,
|
|
2162
2247
|
deliver(payload) {
|
|
2163
2248
|
if (closed) return;
|
|
2164
2249
|
const event = new MessageEvent("message", { data: payload });
|
|
2250
|
+
let pending;
|
|
2165
2251
|
for (const listener of [...listeners]) try {
|
|
2166
|
-
listener(event);
|
|
2252
|
+
const result = listener(event);
|
|
2253
|
+
if (result && typeof result.then === "function") {
|
|
2254
|
+
pending ??= [];
|
|
2255
|
+
pending.push(result);
|
|
2256
|
+
}
|
|
2167
2257
|
} catch {}
|
|
2258
|
+
return pending ? Promise.allSettled(pending) : void 0;
|
|
2168
2259
|
},
|
|
2169
2260
|
markClosed(reason) {
|
|
2170
2261
|
closed = true;
|
|
@@ -2173,53 +2264,318 @@
|
|
|
2173
2264
|
},
|
|
2174
2265
|
isClosed() {
|
|
2175
2266
|
return closed;
|
|
2267
|
+
},
|
|
2268
|
+
hasListeners() {
|
|
2269
|
+
return listeners.size > 0;
|
|
2176
2270
|
}
|
|
2177
2271
|
};
|
|
2178
2272
|
}
|
|
2179
2273
|
/**
|
|
2180
|
-
* The
|
|
2181
|
-
*
|
|
2274
|
+
* The id of keepalive's `ping` / `pong`: one no port ever takes. An older mux
|
|
2275
|
+
* whose codec passes unknown envelopes through reads them as a close of that
|
|
2276
|
+
* id, which must therefore not be a real port (0 is the initiator's first).
|
|
2182
2277
|
*/
|
|
2183
|
-
const
|
|
2278
|
+
const KEEPALIVE_ID = Number.MAX_SAFE_INTEGER;
|
|
2279
|
+
/** The longest delay a timer holds; past it `setInterval` fires at once. */
|
|
2280
|
+
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
2184
2281
|
/** Ceiling on concurrently open virtual ports. Bounds the id table only. */
|
|
2185
2282
|
const DEFAULT_MAX_PORTS = 1024;
|
|
2186
2283
|
/**
|
|
2284
|
+
* What a message costs a port's window when the codec cannot say
|
|
2285
|
+
* (`PortCodec.encode`): its bytes and string lengths, found in its values up
|
|
2286
|
+
* to 4 levels deep (at most 256 values), and at least 64. Both ends compute it
|
|
2287
|
+
* from the same structured value, so they agree.
|
|
2288
|
+
*/
|
|
2289
|
+
function estimateMessageSize(payload) {
|
|
2290
|
+
let budget = 256;
|
|
2291
|
+
const walk = (v, depth) => {
|
|
2292
|
+
if (budget-- <= 0) return 0;
|
|
2293
|
+
if (typeof v === "string") return v.length;
|
|
2294
|
+
if (v instanceof ArrayBuffer) return v.byteLength;
|
|
2295
|
+
if (ArrayBuffer.isView(v)) return v.byteLength;
|
|
2296
|
+
if (v === null || typeof v !== "object" || depth >= 4) return 8;
|
|
2297
|
+
let n = 16;
|
|
2298
|
+
if (Array.isArray(v)) for (const x of v) n += walk(x, depth + 1);
|
|
2299
|
+
else for (const k in v) n += k.length + walk(v[k], depth + 1);
|
|
2300
|
+
return n;
|
|
2301
|
+
};
|
|
2302
|
+
return Math.max(64, walk(payload, 0));
|
|
2303
|
+
}
|
|
2304
|
+
/**
|
|
2187
2305
|
* The default `PortMux`: emulates multiplexing over a single port.
|
|
2188
2306
|
*
|
|
2189
2307
|
* A transport that already multiplexes natively supplies its own `PortMux`
|
|
2190
2308
|
* instead — this implementation is for transports that offer one pipe.
|
|
2191
2309
|
*/
|
|
2192
2310
|
function multiplexPort(port, options) {
|
|
2193
|
-
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize } = options;
|
|
2311
|
+
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
2312
|
+
const maxReassembledBytes = options.maxReassembledBytes ?? 67108864;
|
|
2313
|
+
const misses = keepalive?.misses ?? 3;
|
|
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`);
|
|
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}`);
|
|
2194
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
|
+
};
|
|
2324
|
+
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
2325
|
+
const entry = open.get(id);
|
|
2326
|
+
if (entry?.handle !== record) return;
|
|
2327
|
+
const { handle: _, credit: __, partial: ___, ...info } = entry;
|
|
2328
|
+
if (!record.hasListeners()) {
|
|
2329
|
+
release("leaked");
|
|
2330
|
+
record.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the port was dropped without close()"));
|
|
2331
|
+
}
|
|
2332
|
+
try {
|
|
2333
|
+
onLeak?.(info);
|
|
2334
|
+
} catch {}
|
|
2335
|
+
}) : void 0;
|
|
2336
|
+
const touch = (id) => {
|
|
2337
|
+
const entry = open.get(id);
|
|
2338
|
+
if (entry) entry.lastActivity = Date.now();
|
|
2339
|
+
};
|
|
2195
2340
|
let nextId = side === "initiator" ? 0 : 1;
|
|
2196
2341
|
let muxClosed = false;
|
|
2342
|
+
/** This end counts encoded bytes and reassembles frames. */
|
|
2343
|
+
const exactCodec = codec.encode != null && codec.decode != null;
|
|
2197
2344
|
const post = (envelope, transfer) => {
|
|
2198
2345
|
if (muxClosed) return;
|
|
2346
|
+
codecPost(envelope, transfer);
|
|
2347
|
+
};
|
|
2348
|
+
/** Like `post`, also while closing: the mux's own close notices. */
|
|
2349
|
+
const codecPost = (envelope, transfer) => {
|
|
2199
2350
|
try {
|
|
2200
2351
|
codec.post(port, envelope, transfer);
|
|
2201
2352
|
} catch {}
|
|
2202
2353
|
};
|
|
2203
|
-
const
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2354
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
2355
|
+
const ring = [];
|
|
2356
|
+
let pumping = false;
|
|
2357
|
+
const pump = async () => {
|
|
2358
|
+
pumping = true;
|
|
2359
|
+
try {
|
|
2360
|
+
while (ring.length && !muxClosed) {
|
|
2361
|
+
if (port.whenDrained) await port.whenDrained();
|
|
2362
|
+
if (muxClosed) break;
|
|
2363
|
+
const id = ring.shift();
|
|
2364
|
+
const queue = outgoing.get(id);
|
|
2365
|
+
const item = queue?.shift();
|
|
2366
|
+
if (queue?.length) ring.push(id);
|
|
2367
|
+
else outgoing.delete(id);
|
|
2368
|
+
item?.();
|
|
2369
|
+
}
|
|
2370
|
+
} catch (err) {
|
|
2371
|
+
close(err).catch(() => {});
|
|
2372
|
+
} finally {
|
|
2373
|
+
pumping = false;
|
|
2374
|
+
}
|
|
2375
|
+
};
|
|
2376
|
+
const sendOnPort = (id, item) => {
|
|
2377
|
+
if (frameSize == null) {
|
|
2378
|
+
item();
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
let queue = outgoing.get(id);
|
|
2382
|
+
if (!queue) {
|
|
2383
|
+
queue = [];
|
|
2384
|
+
outgoing.set(id, queue);
|
|
2385
|
+
ring.push(id);
|
|
2386
|
+
}
|
|
2387
|
+
queue.push(item);
|
|
2388
|
+
if (!pumping) pump();
|
|
2389
|
+
};
|
|
2390
|
+
const dropOutgoing = (id) => {
|
|
2391
|
+
if (!outgoing.delete(id)) return;
|
|
2392
|
+
const at = ring.indexOf(id);
|
|
2393
|
+
if (at >= 0) ring.splice(at, 1);
|
|
2394
|
+
};
|
|
2395
|
+
const postBytes = (bytes) => {
|
|
2396
|
+
if (muxClosed) return;
|
|
2397
|
+
try {
|
|
2398
|
+
port.postMessage(bytes);
|
|
2399
|
+
} catch {}
|
|
2400
|
+
};
|
|
2401
|
+
const releaseWaiters = (c) => {
|
|
2402
|
+
for (const w of c?.waiters.splice(0) ?? []) w.resolve();
|
|
2403
|
+
};
|
|
2404
|
+
const flush = (c) => {
|
|
2405
|
+
while (c.queue.length) {
|
|
2406
|
+
const next = c.queue[0];
|
|
2407
|
+
if (c.enforced && c.outstanding > 0 && c.outstanding + next.size > c.window) break;
|
|
2408
|
+
c.queue.shift();
|
|
2409
|
+
c.queued -= next.size;
|
|
2410
|
+
c.outstanding += next.size;
|
|
2411
|
+
next.send();
|
|
2412
|
+
}
|
|
2413
|
+
for (const w of [...c.waiters]) if (c.queued <= w.below) {
|
|
2414
|
+
c.waiters.splice(c.waiters.indexOf(w), 1);
|
|
2415
|
+
w.resolve();
|
|
2416
|
+
}
|
|
2417
|
+
};
|
|
2418
|
+
const grant = (id, c, bytes) => {
|
|
2419
|
+
c.unconsumed -= bytes;
|
|
2420
|
+
c.toGrant += bytes;
|
|
2421
|
+
const send = () => {
|
|
2422
|
+
c.grantScheduled = false;
|
|
2423
|
+
if (!c.toGrant || !open.has(id)) return;
|
|
2210
2424
|
post({
|
|
2425
|
+
type: "credit",
|
|
2426
|
+
id,
|
|
2427
|
+
bytes: c.toGrant
|
|
2428
|
+
});
|
|
2429
|
+
c.toGrant = 0;
|
|
2430
|
+
};
|
|
2431
|
+
if (c.toGrant >= c.window / 4) {
|
|
2432
|
+
send();
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
if (c.grantScheduled) return;
|
|
2436
|
+
c.grantScheduled = true;
|
|
2437
|
+
setTimeout(send, 0);
|
|
2438
|
+
};
|
|
2439
|
+
const attach = (id, meta, portWindow, opener, exact) => {
|
|
2440
|
+
const c = typeof portWindow === "number" && Number.isSafeInteger(portWindow) && portWindow > 0 ? {
|
|
2441
|
+
window: portWindow,
|
|
2442
|
+
enforced: false,
|
|
2443
|
+
opener,
|
|
2444
|
+
exact,
|
|
2445
|
+
outstanding: 0,
|
|
2446
|
+
queue: [],
|
|
2447
|
+
queued: 0,
|
|
2448
|
+
unconsumed: 0,
|
|
2449
|
+
waiters: [],
|
|
2450
|
+
toGrant: 0,
|
|
2451
|
+
grantScheduled: false
|
|
2452
|
+
} : void 0;
|
|
2453
|
+
const self = newVirtualPort((payload, transfer) => {
|
|
2454
|
+
touch(id);
|
|
2455
|
+
const envelope = {
|
|
2456
|
+
type: "message",
|
|
2457
|
+
id,
|
|
2458
|
+
payload
|
|
2459
|
+
};
|
|
2460
|
+
if (!c) {
|
|
2461
|
+
post(envelope, transfer);
|
|
2462
|
+
return;
|
|
2463
|
+
}
|
|
2464
|
+
const bytes = c.exact ? codec.encode?.(envelope) : void 0;
|
|
2465
|
+
const size = bytes?.byteLength ?? estimateMessageSize(payload);
|
|
2466
|
+
const waits = frameSize != null || c.queue.length > 0 || c.enforced && c.outstanding > 0 && c.outstanding + size > c.window;
|
|
2467
|
+
const held = !bytes && waits ? {
|
|
2468
|
+
...envelope,
|
|
2469
|
+
payload: structuredClone(payload, { transfer })
|
|
2470
|
+
} : void 0;
|
|
2471
|
+
const send = (whole = false) => {
|
|
2472
|
+
if (!bytes) {
|
|
2473
|
+
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
if (!whole && frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2477
|
+
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
2478
|
+
const data = bytes.subarray(at, at + frameSize);
|
|
2479
|
+
const frame = at + frameSize >= bytes.byteLength ? {
|
|
2480
|
+
type: "frame",
|
|
2481
|
+
id,
|
|
2482
|
+
data,
|
|
2483
|
+
last: true
|
|
2484
|
+
} : {
|
|
2485
|
+
type: "frame",
|
|
2486
|
+
id,
|
|
2487
|
+
data
|
|
2488
|
+
};
|
|
2489
|
+
sendOnPort(id, () => post(frame));
|
|
2490
|
+
}
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
sendOnPort(id, () => postBytes(bytes));
|
|
2494
|
+
};
|
|
2495
|
+
c.queue.push({
|
|
2496
|
+
send,
|
|
2497
|
+
size
|
|
2498
|
+
});
|
|
2499
|
+
c.queued += size;
|
|
2500
|
+
flush(c);
|
|
2501
|
+
}, (reason) => {
|
|
2502
|
+
release(reason);
|
|
2503
|
+
record.markClosed(reason);
|
|
2504
|
+
});
|
|
2505
|
+
function release(reason) {
|
|
2506
|
+
freePartial(open.get(id));
|
|
2507
|
+
open.delete(id);
|
|
2508
|
+
leaks?.unregister(record);
|
|
2509
|
+
if (c?.queue.length) {
|
|
2510
|
+
for (const item of c.queue.splice(0)) item.send(true);
|
|
2511
|
+
c.queued = 0;
|
|
2512
|
+
}
|
|
2513
|
+
sendOnPort(id, () => post({
|
|
2211
2514
|
type: "close",
|
|
2212
2515
|
id,
|
|
2213
2516
|
reason
|
|
2517
|
+
}));
|
|
2518
|
+
releaseWaiters(c);
|
|
2519
|
+
}
|
|
2520
|
+
const { markClosed, isClosed, hasListeners } = self;
|
|
2521
|
+
if (c) {
|
|
2522
|
+
const deliver = self.deliver;
|
|
2523
|
+
self.deliver = (payload, size = estimateMessageSize(payload)) => {
|
|
2524
|
+
c.unconsumed += size;
|
|
2525
|
+
const done = deliver(payload);
|
|
2526
|
+
if (done) done.then(() => grant(id, c, size));
|
|
2527
|
+
else grant(id, c, size);
|
|
2528
|
+
return done;
|
|
2529
|
+
};
|
|
2530
|
+
setPortSendState(self.port, {
|
|
2531
|
+
bufferedAmount: () => c.queued,
|
|
2532
|
+
ready: (below = 0) => new Promise((resolve) => {
|
|
2533
|
+
if (c.queued <= below || isClosed()) nextTask(resolve);
|
|
2534
|
+
else c.waiters.push({
|
|
2535
|
+
below,
|
|
2536
|
+
resolve: () => nextTask(resolve)
|
|
2537
|
+
});
|
|
2538
|
+
})
|
|
2214
2539
|
});
|
|
2215
|
-
|
|
2216
|
-
|
|
2540
|
+
}
|
|
2541
|
+
const record = {
|
|
2542
|
+
deliver: self.deliver,
|
|
2543
|
+
markClosed,
|
|
2544
|
+
isClosed,
|
|
2545
|
+
hasListeners,
|
|
2546
|
+
close: (reason) => {
|
|
2547
|
+
if (isClosed()) return;
|
|
2548
|
+
release(reason);
|
|
2549
|
+
markClosed(reason);
|
|
2550
|
+
}
|
|
2551
|
+
};
|
|
2552
|
+
leaks?.register(self.port, {
|
|
2553
|
+
id,
|
|
2554
|
+
record,
|
|
2555
|
+
release
|
|
2556
|
+
}, record);
|
|
2557
|
+
const now = Date.now();
|
|
2558
|
+
open.set(id, {
|
|
2559
|
+
id,
|
|
2560
|
+
meta,
|
|
2561
|
+
openedAt: now,
|
|
2562
|
+
lastActivity: now,
|
|
2563
|
+
handle: record,
|
|
2564
|
+
credit: c
|
|
2217
2565
|
});
|
|
2218
|
-
open.set(id, self);
|
|
2219
2566
|
return self;
|
|
2220
2567
|
};
|
|
2221
|
-
const handleEnvelope = (envelope) => {
|
|
2568
|
+
const handleEnvelope = (envelope, event) => {
|
|
2222
2569
|
if (muxClosed) return;
|
|
2570
|
+
heardSincePing = true;
|
|
2571
|
+
if (envelope.type === "ping" || envelope.type === "pong") {
|
|
2572
|
+
peerKeepsAlive = true;
|
|
2573
|
+
if (envelope.type === "ping") post({
|
|
2574
|
+
type: "pong",
|
|
2575
|
+
id: KEEPALIVE_ID
|
|
2576
|
+
});
|
|
2577
|
+
return;
|
|
2578
|
+
}
|
|
2223
2579
|
if (envelope.type === "open") {
|
|
2224
2580
|
if (open.has(envelope.id)) return;
|
|
2225
2581
|
if (open.size >= maxPorts) {
|
|
@@ -2230,7 +2586,18 @@
|
|
|
2230
2586
|
});
|
|
2231
2587
|
return;
|
|
2232
2588
|
}
|
|
2233
|
-
const handle = attach(envelope.id);
|
|
2589
|
+
const handle = attach(envelope.id, envelope.meta, envelope.window, false, envelope.exact === true && exactCodec);
|
|
2590
|
+
const credit = open.get(envelope.id)?.credit;
|
|
2591
|
+
if (credit) post(credit.exact ? {
|
|
2592
|
+
type: "credit",
|
|
2593
|
+
id: envelope.id,
|
|
2594
|
+
bytes: 0,
|
|
2595
|
+
exact: true
|
|
2596
|
+
} : {
|
|
2597
|
+
type: "credit",
|
|
2598
|
+
id: envelope.id,
|
|
2599
|
+
bytes: 0
|
|
2600
|
+
});
|
|
2234
2601
|
let accepted = false;
|
|
2235
2602
|
if (onPort) try {
|
|
2236
2603
|
accepted = onPort(handle.port, envelope.meta) !== false;
|
|
@@ -2245,57 +2612,215 @@
|
|
|
2245
2612
|
id: envelope.id,
|
|
2246
2613
|
reason: "rejected"
|
|
2247
2614
|
});
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
return;
|
|
2618
|
+
}
|
|
2619
|
+
const entry = open.get(envelope.id);
|
|
2620
|
+
if (envelope.type === "credit") {
|
|
2621
|
+
const c = entry?.credit;
|
|
2622
|
+
if (!c || !(envelope.bytes >= 0)) return;
|
|
2623
|
+
if (!c.enforced && c.opener) {
|
|
2624
|
+
if (c.exact && envelope.exact !== true) {
|
|
2625
|
+
c.exact = false;
|
|
2626
|
+
c.outstanding = 0;
|
|
2627
|
+
}
|
|
2628
|
+
post({
|
|
2629
|
+
type: "credit",
|
|
2630
|
+
id: envelope.id,
|
|
2631
|
+
bytes: 0
|
|
2632
|
+
});
|
|
2248
2633
|
}
|
|
2634
|
+
c.enforced = true;
|
|
2635
|
+
c.outstanding = Math.max(0, c.outstanding - envelope.bytes);
|
|
2636
|
+
flush(c);
|
|
2249
2637
|
return;
|
|
2250
2638
|
}
|
|
2251
|
-
const handle = open.get(envelope.id);
|
|
2252
2639
|
if (envelope.type === "message") {
|
|
2253
|
-
|
|
2254
|
-
|
|
2640
|
+
const sizeOf = (c) => (c.exact ? byteLengthOf(event.data) : void 0) ?? estimateMessageSize(envelope.payload);
|
|
2641
|
+
if (!entry) return;
|
|
2642
|
+
touch(envelope.id);
|
|
2643
|
+
entry.handle.deliver(envelope.payload, entry.credit ? sizeOf(entry.credit) : void 0);
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
if (envelope.type === "frame") {
|
|
2647
|
+
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
2648
|
+
touch(envelope.id);
|
|
2649
|
+
const c = entry.credit;
|
|
2650
|
+
const owed = c.unconsumed + c.toGrant;
|
|
2651
|
+
const partial = entry.partial ?? {
|
|
2652
|
+
chunks: [],
|
|
2653
|
+
bytes: 0,
|
|
2654
|
+
owed,
|
|
2655
|
+
charged: 0,
|
|
2656
|
+
...owed > 0 ? { room: c.window - owed } : {}
|
|
2657
|
+
};
|
|
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;
|
|
2678
|
+
partial.chunks.push(envelope.data);
|
|
2679
|
+
if (!envelope.last) return;
|
|
2680
|
+
freePartial(entry);
|
|
2681
|
+
const whole = new Uint8Array(partial.bytes);
|
|
2682
|
+
let at = 0;
|
|
2683
|
+
for (const chunk of partial.chunks) {
|
|
2684
|
+
whole.set(chunk, at);
|
|
2685
|
+
at += chunk.byteLength;
|
|
2686
|
+
}
|
|
2687
|
+
const inner = codec.decode(whole);
|
|
2688
|
+
if (inner?.type !== "message" || inner.id !== envelope.id) return;
|
|
2689
|
+
entry.handle.deliver(inner.payload, whole.byteLength);
|
|
2255
2690
|
return;
|
|
2256
2691
|
}
|
|
2257
|
-
|
|
2692
|
+
dropOutgoing(envelope.id);
|
|
2693
|
+
if (!entry) return;
|
|
2694
|
+
freePartial(entry);
|
|
2258
2695
|
open.delete(envelope.id);
|
|
2259
|
-
|
|
2696
|
+
releaseWaiters(entry.credit);
|
|
2697
|
+
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
|
2260
2698
|
};
|
|
2261
2699
|
const listener = (event) => {
|
|
2262
2700
|
const envelope = codec.read(event);
|
|
2263
|
-
if (envelope) handleEnvelope(envelope);
|
|
2701
|
+
if (envelope) handleEnvelope(envelope, event);
|
|
2702
|
+
};
|
|
2703
|
+
let peerKeepsAlive = false;
|
|
2704
|
+
let heardSincePing = true;
|
|
2705
|
+
let unanswered = 0;
|
|
2706
|
+
let pinger;
|
|
2707
|
+
const tick = () => {
|
|
2708
|
+
if (!keepalive) return;
|
|
2709
|
+
const backlogged = (port.bufferedAmount ?? 0) > 0;
|
|
2710
|
+
if (heardSincePing) unanswered = 0;
|
|
2711
|
+
else if (peerKeepsAlive && !backlogged) unanswered++;
|
|
2712
|
+
if (unanswered >= misses) {
|
|
2713
|
+
close(/* @__PURE__ */ new Error(`webrun-rpc: the peer did not answer ${misses} keepalive pings, ${keepalive.intervalMs} ms apart`)).catch(() => {});
|
|
2714
|
+
return;
|
|
2715
|
+
}
|
|
2716
|
+
heardSincePing = false;
|
|
2717
|
+
post({
|
|
2718
|
+
type: "ping",
|
|
2719
|
+
id: KEEPALIVE_ID
|
|
2720
|
+
});
|
|
2721
|
+
};
|
|
2722
|
+
const close = async (reason) => {
|
|
2723
|
+
if (muxClosed) return;
|
|
2724
|
+
clearInterval(pinger);
|
|
2725
|
+
muxClosed = true;
|
|
2726
|
+
signal?.removeEventListener("abort", onTransportAbort);
|
|
2727
|
+
port.removeEventListener("message", listener);
|
|
2728
|
+
const closedNow = /* @__PURE__ */ new Set();
|
|
2729
|
+
for (const [id, { handle, credit }] of [...open]) {
|
|
2730
|
+
closedNow.add(id);
|
|
2731
|
+
codecPost({
|
|
2732
|
+
type: "close",
|
|
2733
|
+
id
|
|
2734
|
+
});
|
|
2735
|
+
open.delete(id);
|
|
2736
|
+
releaseWaiters(credit);
|
|
2737
|
+
handle.markClosed(reason ?? /* @__PURE__ */ new Error("webrun-rpc: the multiplexer was closed"));
|
|
2738
|
+
}
|
|
2739
|
+
for (const id of outgoing.keys()) if (!closedNow.has(id)) codecPost({
|
|
2740
|
+
type: "close",
|
|
2741
|
+
id
|
|
2742
|
+
});
|
|
2743
|
+
outgoing.clear();
|
|
2744
|
+
ring.length = 0;
|
|
2745
|
+
await port.close?.();
|
|
2746
|
+
};
|
|
2747
|
+
const onTransportAbort = () => {
|
|
2748
|
+
close(signal?.reason).catch(() => {});
|
|
2264
2749
|
};
|
|
2265
2750
|
port.addEventListener("message", listener);
|
|
2266
2751
|
port.start?.();
|
|
2752
|
+
if (keepalive) {
|
|
2753
|
+
pinger = setInterval(tick, keepalive.intervalMs);
|
|
2754
|
+
pinger.unref?.();
|
|
2755
|
+
}
|
|
2756
|
+
if (signal?.aborted) close(signal.reason).catch(() => {});
|
|
2757
|
+
else signal?.addEventListener("abort", onTransportAbort, { once: true });
|
|
2267
2758
|
return {
|
|
2268
2759
|
maxMessageSize,
|
|
2269
|
-
async openPort(meta) {
|
|
2760
|
+
async openPort(meta, { signal: portSignal } = {}) {
|
|
2270
2761
|
if (muxClosed) throw new Error("webrun-rpc: the multiplexer is closed");
|
|
2271
|
-
if (
|
|
2762
|
+
if (portSignal?.aborted) throw portSignal.reason;
|
|
2763
|
+
if (open.size >= maxPorts) throw new RangeError(`webrun-rpc: maxPorts (${maxPorts}) reached; open ports by kind: ${describeKinds(open.values())}`);
|
|
2272
2764
|
while (open.has(nextId)) nextId += 2;
|
|
2273
2765
|
const id = nextId;
|
|
2274
2766
|
nextId += 2;
|
|
2275
|
-
const handle = attach(id);
|
|
2276
|
-
post({
|
|
2767
|
+
const handle = attach(id, meta, window, true, exactCodec);
|
|
2768
|
+
post(window ? exactCodec ? {
|
|
2769
|
+
type: "open",
|
|
2770
|
+
id,
|
|
2771
|
+
meta,
|
|
2772
|
+
window,
|
|
2773
|
+
exact: true
|
|
2774
|
+
} : {
|
|
2775
|
+
type: "open",
|
|
2776
|
+
id,
|
|
2777
|
+
meta,
|
|
2778
|
+
window
|
|
2779
|
+
} : {
|
|
2277
2780
|
type: "open",
|
|
2278
2781
|
id,
|
|
2279
2782
|
meta
|
|
2280
2783
|
});
|
|
2784
|
+
if (portSignal) {
|
|
2785
|
+
const record = open.get(id)?.handle;
|
|
2786
|
+
const onAbort = () => {
|
|
2787
|
+
if (record && open.get(id)?.handle === record) record.close(portSignal.reason);
|
|
2788
|
+
};
|
|
2789
|
+
portSignal.addEventListener("abort", onAbort, { once: true });
|
|
2790
|
+
getPortCloseSignal(handle.port)?.addEventListener("abort", () => portSignal.removeEventListener("abort", onAbort), { once: true });
|
|
2791
|
+
}
|
|
2281
2792
|
return handle.port;
|
|
2282
2793
|
},
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
}
|
|
2293
|
-
muxClosed = true;
|
|
2294
|
-
port.removeEventListener("message", listener);
|
|
2295
|
-
await port.close?.();
|
|
2794
|
+
close,
|
|
2795
|
+
ports() {
|
|
2796
|
+
return [...open.values()].map(({ handle: _, credit, ...info }) => credit ? {
|
|
2797
|
+
...info,
|
|
2798
|
+
...credit.enforced ? { window: credit.window } : {},
|
|
2799
|
+
queued: credit.queued,
|
|
2800
|
+
outstanding: credit.outstanding,
|
|
2801
|
+
unconsumed: credit.unconsumed
|
|
2802
|
+
} : info);
|
|
2296
2803
|
}
|
|
2297
2804
|
};
|
|
2298
2805
|
}
|
|
2806
|
+
/** The byte length of a received message's data, or `undefined` if it is not bytes. */
|
|
2807
|
+
function byteLengthOf(data) {
|
|
2808
|
+
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) return data.byteLength;
|
|
2809
|
+
}
|
|
2810
|
+
/**
|
|
2811
|
+
* `"http: 1000, ws: 24"`: the three commonest `meta.kind`s among `entries`, so
|
|
2812
|
+
* a full table names who filled it. A port without a kind counts as `"?"`.
|
|
2813
|
+
*/
|
|
2814
|
+
function describeKinds(entries) {
|
|
2815
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2816
|
+
for (const { meta } of entries) {
|
|
2817
|
+
const kind = meta?.kind;
|
|
2818
|
+
const name = typeof kind === "string" ? kind : "?";
|
|
2819
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
2820
|
+
}
|
|
2821
|
+
if (!counts.size) return "none";
|
|
2822
|
+
return [...counts].sort((x, y) => y[1] - x[1]).slice(0, 3).map(([name, n]) => `${name}: ${n}`).join(", ");
|
|
2823
|
+
}
|
|
2299
2824
|
/**
|
|
2300
2825
|
* Announced with every port this pair opens. Layer 1 never reads `meta` — it is
|
|
2301
2826
|
* here so a peer running something else over the same multiplexer can tell a
|
|
@@ -2357,7 +2882,7 @@
|
|
|
2357
2882
|
const candidate = value;
|
|
2358
2883
|
if (typeof candidate.id !== "number") return false;
|
|
2359
2884
|
if (!Number.isInteger(candidate.id) || candidate.id < 0) return false;
|
|
2360
|
-
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close";
|
|
2885
|
+
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close" || candidate.type === "credit" || candidate.type === "frame" || candidate.type === "ping" || candidate.type === "pong";
|
|
2361
2886
|
}
|
|
2362
2887
|
/**
|
|
2363
2888
|
* For ports whose messages are structured values — a real `MessagePort`, a
|
|
@@ -2495,7 +3020,7 @@
|
|
|
2495
3020
|
finish();
|
|
2496
3021
|
return response;
|
|
2497
3022
|
}
|
|
2498
|
-
return new Response(guardBody(response.body, lost, () => lostReason, finish), {
|
|
3023
|
+
return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
|
|
2499
3024
|
status: response.status,
|
|
2500
3025
|
statusText: response.statusText,
|
|
2501
3026
|
headers: response.headers
|
|
@@ -2514,33 +3039,65 @@
|
|
|
2514
3039
|
return asyncDisposable(link, () => link.close());
|
|
2515
3040
|
}
|
|
2516
3041
|
/**
|
|
2517
|
-
* A response body that reports its end (done, cancelled or
|
|
2518
|
-
* with the link's reason when the link is lost mid-stream.
|
|
3042
|
+
* A response body that reports its end (done, cancelled, failed or aborted) and
|
|
3043
|
+
* fails with the link's reason when the link is lost mid-stream. An abort of the
|
|
3044
|
+
* request's signal ends it at once, read or not: the exchange is over (the layer
|
|
3045
|
+
* below has given its port back), so the request leaves `inflight` then.
|
|
2519
3046
|
*/
|
|
2520
|
-
function guardBody(body, lost, reason, finish) {
|
|
3047
|
+
function guardBody(body, lost, reason, finish, signal) {
|
|
2521
3048
|
const reader = body.getReader();
|
|
3049
|
+
let controller;
|
|
3050
|
+
let ended = false;
|
|
3051
|
+
const end = () => {
|
|
3052
|
+
ended = true;
|
|
3053
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3054
|
+
finish();
|
|
3055
|
+
};
|
|
3056
|
+
const onAbort = () => {
|
|
3057
|
+
if (ended) return;
|
|
3058
|
+
end();
|
|
3059
|
+
reader.cancel().catch(() => {});
|
|
3060
|
+
try {
|
|
3061
|
+
controller.error(abortError(signal));
|
|
3062
|
+
} catch {}
|
|
3063
|
+
};
|
|
2522
3064
|
return new ReadableStream({
|
|
3065
|
+
start(c) {
|
|
3066
|
+
controller = c;
|
|
3067
|
+
if (signal?.aborted) onAbort();
|
|
3068
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
3069
|
+
},
|
|
2523
3070
|
async pull(controller) {
|
|
2524
3071
|
try {
|
|
2525
3072
|
const { done, value } = await Promise.race([reader.read(), lost]);
|
|
3073
|
+
if (ended) return;
|
|
2526
3074
|
if (done) {
|
|
2527
|
-
|
|
3075
|
+
end();
|
|
2528
3076
|
controller.close();
|
|
2529
3077
|
return;
|
|
2530
3078
|
}
|
|
2531
3079
|
controller.enqueue(value);
|
|
2532
3080
|
} catch (error) {
|
|
2533
|
-
|
|
3081
|
+
if (ended) return;
|
|
3082
|
+
end();
|
|
2534
3083
|
reader.cancel().catch(() => {});
|
|
2535
3084
|
controller.error(reason() ?? error);
|
|
2536
3085
|
}
|
|
2537
3086
|
},
|
|
2538
3087
|
async cancel(why) {
|
|
2539
|
-
|
|
3088
|
+
end();
|
|
2540
3089
|
await reader.cancel(why).catch(() => {});
|
|
2541
3090
|
}
|
|
2542
3091
|
});
|
|
2543
3092
|
}
|
|
3093
|
+
/** The signal's reason as an `Error` (`AbortError` when it gave none). */
|
|
3094
|
+
function abortError(signal) {
|
|
3095
|
+
const why = signal.reason;
|
|
3096
|
+
if (why instanceof Error) return why;
|
|
3097
|
+
const err = new Error(why === void 0 ? "Aborted" : String(why));
|
|
3098
|
+
err.name = "AbortError";
|
|
3099
|
+
return err;
|
|
3100
|
+
}
|
|
2544
3101
|
//#endregion
|
|
2545
3102
|
//#region src/sw/protocol.ts
|
|
2546
3103
|
/** Page → ServiceWorker: open a WebSocket to a handler. */
|