@statewalker/webrun-http-browser 0.7.1 → 0.9.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 +620 -70
- package/dist/relay-sw.js +606 -88
- package/dist/relay-worker.js +606 -88
- 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 +606 -68
- package/dist/sw.js +620 -70
- package/dist/worker.js +615 -69
- package/package.json +3 -3
- package/src/link/http-link.ts +42 -6
- package/src/sw/protocol.ts +4 -3
- package/src/sw/sw-http-adapter.ts +7 -0
- package/src/sw/worker-service.ts +14 -1
package/dist/sw.js
CHANGED
|
@@ -1525,7 +1525,7 @@ async function fetchOverDuplex(call, request, options = {}) {
|
|
|
1525
1525
|
else await release?.();
|
|
1526
1526
|
return new Response(null, responseInit);
|
|
1527
1527
|
}
|
|
1528
|
-
return new Response(
|
|
1528
|
+
return new Response(responseBody(respBody, request.signal, release), responseInit);
|
|
1529
1529
|
}
|
|
1530
1530
|
/**
|
|
1531
1531
|
* Wrap a `(Request) => Promise<Response>` handler as a `Duplex` so it can be
|
|
@@ -1656,15 +1656,78 @@ function trackingEnd(source) {
|
|
|
1656
1656
|
settle
|
|
1657
1657
|
};
|
|
1658
1658
|
}
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1659
|
+
/**
|
|
1660
|
+
* How long a cancelled body waits for the read in flight before it abandons the call anyway.
|
|
1661
|
+
* The read's answer tells a body that has ended (the call is finished: an upload the handler
|
|
1662
|
+
* reads after answering goes on) from one that has not; a peer that sends nothing more never
|
|
1663
|
+
* answers it. A body whose end comes later than this after the cancel is abandoned too, so a
|
|
1664
|
+
* handler still streaming its response and still reading the upload then loses the upload.
|
|
1665
|
+
*/
|
|
1666
|
+
const CANCEL_GRACE_MS = 1e3;
|
|
1667
|
+
/**
|
|
1668
|
+
* The response body as a stream that the request's signal can end at any time.
|
|
1669
|
+
*
|
|
1670
|
+
* An abort errors the stream with the abort's reason at once — a read waiting
|
|
1671
|
+
* for a peer that sends nothing more rejects then, not when a next chunk
|
|
1672
|
+
* arrives, which may be never — and abandons the call (`release`), so on a
|
|
1673
|
+
* multiplexed transport its port is given back whether or not anyone still
|
|
1674
|
+
* reads the body. The listener is removed once the body ends, however it ends:
|
|
1675
|
+
* read to its end, cancelled, failed or aborted. A body nobody reads or cancels
|
|
1676
|
+
* keeps it until the signal aborts (and keeps the call open until then anyway).
|
|
1677
|
+
* A cancel abandons the call too — unless the read in flight comes back with the
|
|
1678
|
+
* body's end, which finishes it instead (see `cancel`).
|
|
1679
|
+
*/
|
|
1680
|
+
function responseBody(body, signal, release) {
|
|
1681
|
+
const it = body[Symbol.asyncIterator]();
|
|
1682
|
+
let controller;
|
|
1683
|
+
let ended = false;
|
|
1684
|
+
let reading = false;
|
|
1685
|
+
const end = () => {
|
|
1686
|
+
ended = true;
|
|
1687
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1688
|
+
};
|
|
1689
|
+
const onAbort = () => {
|
|
1690
|
+
if (ended) return;
|
|
1691
|
+
end();
|
|
1692
|
+
try {
|
|
1693
|
+
controller.error(abortReason$1(signal));
|
|
1694
|
+
} catch {}
|
|
1695
|
+
release?.();
|
|
1696
|
+
};
|
|
1697
|
+
return new ReadableStream({
|
|
1698
|
+
start(c) {
|
|
1699
|
+
controller = c;
|
|
1700
|
+
if (signal?.aborted) onAbort();
|
|
1701
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
1702
|
+
},
|
|
1703
|
+
async pull(c) {
|
|
1704
|
+
try {
|
|
1705
|
+
reading = true;
|
|
1706
|
+
const { done, value } = await it.next().finally(() => {
|
|
1707
|
+
reading = false;
|
|
1708
|
+
});
|
|
1709
|
+
if (ended) return;
|
|
1710
|
+
if (done) {
|
|
1711
|
+
end();
|
|
1712
|
+
c.close();
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
c.enqueue(value);
|
|
1716
|
+
} catch (err) {
|
|
1717
|
+
if (ended) return;
|
|
1718
|
+
end();
|
|
1719
|
+
c.error(err);
|
|
1720
|
+
}
|
|
1721
|
+
},
|
|
1722
|
+
cancel() {
|
|
1723
|
+
end();
|
|
1724
|
+
const returned = Promise.resolve(it.return?.()).catch(() => {});
|
|
1725
|
+
if (!reading) return;
|
|
1726
|
+
const grace = setTimeout(() => void release?.(), CANCEL_GRACE_MS);
|
|
1727
|
+
grace.unref?.();
|
|
1728
|
+
returned.finally(() => clearTimeout(grace));
|
|
1729
|
+
}
|
|
1730
|
+
});
|
|
1668
1731
|
}
|
|
1669
1732
|
//#endregion
|
|
1670
1733
|
//#region ../webrun-rpc/dist/index.js
|
|
@@ -1789,8 +1852,11 @@ function listenPort(port, handler, { channelName = "", log = () => {} } = {}) {
|
|
|
1789
1852
|
error
|
|
1790
1853
|
});
|
|
1791
1854
|
};
|
|
1792
|
-
|
|
1793
|
-
|
|
1855
|
+
const listener = (event) => {
|
|
1856
|
+
onMessage(event);
|
|
1857
|
+
};
|
|
1858
|
+
port.addEventListener("message", listener);
|
|
1859
|
+
return () => port.removeEventListener("message", listener);
|
|
1794
1860
|
}
|
|
1795
1861
|
/**
|
|
1796
1862
|
* Wraps an async iterable so that an `AbortSignal` firing causes the wrapper
|
|
@@ -2195,6 +2261,33 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
|
|
|
2195
2261
|
}
|
|
2196
2262
|
}
|
|
2197
2263
|
/**
|
|
2264
|
+
* Runs `fn` on a later task, never a microtask: a producer looping on
|
|
2265
|
+
* `whenPortReady` must let the transport's own events run. `setImmediate`
|
|
2266
|
+
* where it exists; elsewhere a `MessageChannel`, because nested `setTimeout`s
|
|
2267
|
+
* are clamped to 4 ms in browsers.
|
|
2268
|
+
*/
|
|
2269
|
+
const setImmediateFn = globalThis.setImmediate;
|
|
2270
|
+
/** @internal */
|
|
2271
|
+
const nextTask = setImmediateFn ? (fn) => {
|
|
2272
|
+
setImmediateFn(fn);
|
|
2273
|
+
} : (() => {
|
|
2274
|
+
let channel;
|
|
2275
|
+
const queue = [];
|
|
2276
|
+
return (fn) => {
|
|
2277
|
+
if (!channel) {
|
|
2278
|
+
channel = new MessageChannel();
|
|
2279
|
+
channel.port1.onmessage = () => queue.shift()?.();
|
|
2280
|
+
}
|
|
2281
|
+
queue.push(fn);
|
|
2282
|
+
channel.port2.postMessage(0);
|
|
2283
|
+
};
|
|
2284
|
+
})();
|
|
2285
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
2286
|
+
/** @internal Registered by the multiplexer for each port with a window. */
|
|
2287
|
+
function setPortSendState(port, state) {
|
|
2288
|
+
states.set(port, state);
|
|
2289
|
+
}
|
|
2290
|
+
/**
|
|
2198
2291
|
* One virtual port.
|
|
2199
2292
|
*
|
|
2200
2293
|
* `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
|
|
@@ -2209,6 +2302,14 @@ function newVirtualPort(send, requestClose) {
|
|
|
2209
2302
|
if (closing.signal.aborted) return;
|
|
2210
2303
|
closing.abort(reason ?? /* @__PURE__ */ new Error("webrun-rpc: the port was closed"));
|
|
2211
2304
|
};
|
|
2305
|
+
const close = (reason) => {
|
|
2306
|
+
if (closed) return;
|
|
2307
|
+
closed = true;
|
|
2308
|
+
const notify = requestClose;
|
|
2309
|
+
listeners.clear();
|
|
2310
|
+
notify(reason);
|
|
2311
|
+
abort(reason);
|
|
2312
|
+
};
|
|
2212
2313
|
const port = {
|
|
2213
2314
|
addEventListener(_type, listener) {
|
|
2214
2315
|
listeners.add(listener);
|
|
@@ -2220,24 +2321,28 @@ function newVirtualPort(send, requestClose) {
|
|
|
2220
2321
|
if (closed) return;
|
|
2221
2322
|
send(message, transfer);
|
|
2222
2323
|
},
|
|
2223
|
-
close
|
|
2224
|
-
if (closed) return;
|
|
2225
|
-
closed = true;
|
|
2226
|
-
const notify = requestClose;
|
|
2227
|
-
listeners.clear();
|
|
2228
|
-
notify();
|
|
2229
|
-
abort();
|
|
2230
|
-
}
|
|
2324
|
+
close
|
|
2231
2325
|
};
|
|
2232
2326
|
setPortCloseSignal(port, closing.signal);
|
|
2327
|
+
const dispose = Symbol.dispose;
|
|
2328
|
+
if (dispose) Object.defineProperty(port, dispose, {
|
|
2329
|
+
value: () => close(),
|
|
2330
|
+
configurable: true
|
|
2331
|
+
});
|
|
2233
2332
|
return {
|
|
2234
2333
|
port,
|
|
2235
2334
|
deliver(payload) {
|
|
2236
2335
|
if (closed) return;
|
|
2237
2336
|
const event = new MessageEvent("message", { data: payload });
|
|
2337
|
+
let pending;
|
|
2238
2338
|
for (const listener of [...listeners]) try {
|
|
2239
|
-
listener(event);
|
|
2339
|
+
const result = listener(event);
|
|
2340
|
+
if (result && typeof result.then === "function") {
|
|
2341
|
+
pending ??= [];
|
|
2342
|
+
pending.push(result);
|
|
2343
|
+
}
|
|
2240
2344
|
} catch {}
|
|
2345
|
+
return pending ? Promise.allSettled(pending) : void 0;
|
|
2241
2346
|
},
|
|
2242
2347
|
markClosed(reason) {
|
|
2243
2348
|
closed = true;
|
|
@@ -2246,53 +2351,309 @@ function newVirtualPort(send, requestClose) {
|
|
|
2246
2351
|
},
|
|
2247
2352
|
isClosed() {
|
|
2248
2353
|
return closed;
|
|
2354
|
+
},
|
|
2355
|
+
hasListeners() {
|
|
2356
|
+
return listeners.size > 0;
|
|
2249
2357
|
}
|
|
2250
2358
|
};
|
|
2251
2359
|
}
|
|
2252
2360
|
/**
|
|
2253
|
-
* The
|
|
2254
|
-
*
|
|
2361
|
+
* The id of keepalive's `ping` / `pong`: one no port ever takes. An older mux
|
|
2362
|
+
* whose codec passes unknown envelopes through reads them as a close of that
|
|
2363
|
+
* id, which must therefore not be a real port (0 is the initiator's first).
|
|
2255
2364
|
*/
|
|
2256
|
-
const
|
|
2365
|
+
const KEEPALIVE_ID = Number.MAX_SAFE_INTEGER;
|
|
2366
|
+
/** The longest delay a timer holds; past it `setInterval` fires at once. */
|
|
2367
|
+
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
2257
2368
|
/** Ceiling on concurrently open virtual ports. Bounds the id table only. */
|
|
2258
2369
|
const DEFAULT_MAX_PORTS = 1024;
|
|
2259
2370
|
/**
|
|
2371
|
+
* What a message costs a port's window when the codec cannot say
|
|
2372
|
+
* (`PortCodec.encode`): its bytes and string lengths, found in its values up
|
|
2373
|
+
* to 4 levels deep (at most 256 values), and at least 64. Both ends compute it
|
|
2374
|
+
* from the same structured value, so they agree.
|
|
2375
|
+
*/
|
|
2376
|
+
function estimateMessageSize(payload) {
|
|
2377
|
+
let budget = 256;
|
|
2378
|
+
const walk = (v, depth) => {
|
|
2379
|
+
if (budget-- <= 0) return 0;
|
|
2380
|
+
if (typeof v === "string") return v.length;
|
|
2381
|
+
if (v instanceof ArrayBuffer) return v.byteLength;
|
|
2382
|
+
if (ArrayBuffer.isView(v)) return v.byteLength;
|
|
2383
|
+
if (v === null || typeof v !== "object" || depth >= 4) return 8;
|
|
2384
|
+
let n = 16;
|
|
2385
|
+
if (Array.isArray(v)) for (const x of v) n += walk(x, depth + 1);
|
|
2386
|
+
else for (const k in v) n += k.length + walk(v[k], depth + 1);
|
|
2387
|
+
return n;
|
|
2388
|
+
};
|
|
2389
|
+
return Math.max(64, walk(payload, 0));
|
|
2390
|
+
}
|
|
2391
|
+
/**
|
|
2260
2392
|
* The default `PortMux`: emulates multiplexing over a single port.
|
|
2261
2393
|
*
|
|
2262
2394
|
* A transport that already multiplexes natively supplies its own `PortMux`
|
|
2263
2395
|
* instead — this implementation is for transports that offer one pipe.
|
|
2264
2396
|
*/
|
|
2265
2397
|
function multiplexPort(port, options) {
|
|
2266
|
-
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize } = options;
|
|
2398
|
+
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
2399
|
+
const misses = keepalive?.misses ?? 3;
|
|
2400
|
+
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
|
+
if (frameSize != null && !(Number.isSafeInteger(frameSize) && frameSize > 0)) throw new RangeError(`webrun-rpc: frameSize must be a positive integer, got ${frameSize}`);
|
|
2267
2402
|
const open = /* @__PURE__ */ new Map();
|
|
2403
|
+
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
2404
|
+
const entry = open.get(id);
|
|
2405
|
+
if (entry?.handle !== record) return;
|
|
2406
|
+
const { handle: _, credit: __, partial: ___, ...info } = entry;
|
|
2407
|
+
if (!record.hasListeners()) {
|
|
2408
|
+
release("leaked");
|
|
2409
|
+
record.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the port was dropped without close()"));
|
|
2410
|
+
}
|
|
2411
|
+
try {
|
|
2412
|
+
onLeak?.(info);
|
|
2413
|
+
} catch {}
|
|
2414
|
+
}) : void 0;
|
|
2415
|
+
const touch = (id) => {
|
|
2416
|
+
const entry = open.get(id);
|
|
2417
|
+
if (entry) entry.lastActivity = Date.now();
|
|
2418
|
+
};
|
|
2268
2419
|
let nextId = side === "initiator" ? 0 : 1;
|
|
2269
2420
|
let muxClosed = false;
|
|
2421
|
+
/** This end counts encoded bytes and reassembles frames. */
|
|
2422
|
+
const exactCodec = codec.encode != null && codec.decode != null;
|
|
2270
2423
|
const post = (envelope, transfer) => {
|
|
2271
2424
|
if (muxClosed) return;
|
|
2425
|
+
codecPost(envelope, transfer);
|
|
2426
|
+
};
|
|
2427
|
+
/** Like `post`, also while closing: the mux's own close notices. */
|
|
2428
|
+
const codecPost = (envelope, transfer) => {
|
|
2272
2429
|
try {
|
|
2273
2430
|
codec.post(port, envelope, transfer);
|
|
2274
2431
|
} catch {}
|
|
2275
2432
|
};
|
|
2276
|
-
const
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2433
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
2434
|
+
const ring = [];
|
|
2435
|
+
let pumping = false;
|
|
2436
|
+
const pump = async () => {
|
|
2437
|
+
pumping = true;
|
|
2438
|
+
try {
|
|
2439
|
+
while (ring.length && !muxClosed) {
|
|
2440
|
+
if (port.whenDrained) await port.whenDrained();
|
|
2441
|
+
if (muxClosed) break;
|
|
2442
|
+
const id = ring.shift();
|
|
2443
|
+
const queue = outgoing.get(id);
|
|
2444
|
+
const item = queue?.shift();
|
|
2445
|
+
if (queue?.length) ring.push(id);
|
|
2446
|
+
else outgoing.delete(id);
|
|
2447
|
+
item?.();
|
|
2448
|
+
}
|
|
2449
|
+
} catch (err) {
|
|
2450
|
+
close(err).catch(() => {});
|
|
2451
|
+
} finally {
|
|
2452
|
+
pumping = false;
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
const sendOnPort = (id, item) => {
|
|
2456
|
+
if (frameSize == null) {
|
|
2457
|
+
item();
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
let queue = outgoing.get(id);
|
|
2461
|
+
if (!queue) {
|
|
2462
|
+
queue = [];
|
|
2463
|
+
outgoing.set(id, queue);
|
|
2464
|
+
ring.push(id);
|
|
2465
|
+
}
|
|
2466
|
+
queue.push(item);
|
|
2467
|
+
if (!pumping) pump();
|
|
2468
|
+
};
|
|
2469
|
+
const dropOutgoing = (id) => {
|
|
2470
|
+
if (!outgoing.delete(id)) return;
|
|
2471
|
+
const at = ring.indexOf(id);
|
|
2472
|
+
if (at >= 0) ring.splice(at, 1);
|
|
2473
|
+
};
|
|
2474
|
+
const postBytes = (bytes) => {
|
|
2475
|
+
if (muxClosed) return;
|
|
2476
|
+
try {
|
|
2477
|
+
port.postMessage(bytes);
|
|
2478
|
+
} catch {}
|
|
2479
|
+
};
|
|
2480
|
+
const releaseWaiters = (c) => {
|
|
2481
|
+
for (const w of c?.waiters.splice(0) ?? []) w.resolve();
|
|
2482
|
+
};
|
|
2483
|
+
const flush = (c) => {
|
|
2484
|
+
while (c.queue.length) {
|
|
2485
|
+
const next = c.queue[0];
|
|
2486
|
+
if (c.enforced && c.outstanding > 0 && c.outstanding + next.size > c.window) break;
|
|
2487
|
+
c.queue.shift();
|
|
2488
|
+
c.queued -= next.size;
|
|
2489
|
+
c.outstanding += next.size;
|
|
2490
|
+
next.send();
|
|
2491
|
+
}
|
|
2492
|
+
for (const w of [...c.waiters]) if (c.queued <= w.below) {
|
|
2493
|
+
c.waiters.splice(c.waiters.indexOf(w), 1);
|
|
2494
|
+
w.resolve();
|
|
2495
|
+
}
|
|
2496
|
+
};
|
|
2497
|
+
const grant = (id, c, bytes) => {
|
|
2498
|
+
c.unconsumed -= bytes;
|
|
2499
|
+
c.toGrant += bytes;
|
|
2500
|
+
const send = () => {
|
|
2501
|
+
c.grantScheduled = false;
|
|
2502
|
+
if (!c.toGrant || !open.has(id)) return;
|
|
2283
2503
|
post({
|
|
2504
|
+
type: "credit",
|
|
2505
|
+
id,
|
|
2506
|
+
bytes: c.toGrant
|
|
2507
|
+
});
|
|
2508
|
+
c.toGrant = 0;
|
|
2509
|
+
};
|
|
2510
|
+
if (c.toGrant >= c.window / 4) {
|
|
2511
|
+
send();
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
if (c.grantScheduled) return;
|
|
2515
|
+
c.grantScheduled = true;
|
|
2516
|
+
setTimeout(send, 0);
|
|
2517
|
+
};
|
|
2518
|
+
const attach = (id, meta, portWindow, opener, exact) => {
|
|
2519
|
+
const c = typeof portWindow === "number" && Number.isSafeInteger(portWindow) && portWindow > 0 ? {
|
|
2520
|
+
window: portWindow,
|
|
2521
|
+
enforced: false,
|
|
2522
|
+
opener,
|
|
2523
|
+
exact,
|
|
2524
|
+
outstanding: 0,
|
|
2525
|
+
queue: [],
|
|
2526
|
+
queued: 0,
|
|
2527
|
+
unconsumed: 0,
|
|
2528
|
+
waiters: [],
|
|
2529
|
+
toGrant: 0,
|
|
2530
|
+
grantScheduled: false
|
|
2531
|
+
} : void 0;
|
|
2532
|
+
const self = newVirtualPort((payload, transfer) => {
|
|
2533
|
+
touch(id);
|
|
2534
|
+
const envelope = {
|
|
2535
|
+
type: "message",
|
|
2536
|
+
id,
|
|
2537
|
+
payload
|
|
2538
|
+
};
|
|
2539
|
+
if (!c) {
|
|
2540
|
+
post(envelope, transfer);
|
|
2541
|
+
return;
|
|
2542
|
+
}
|
|
2543
|
+
const bytes = c.exact ? codec.encode?.(envelope) : void 0;
|
|
2544
|
+
const size = bytes?.byteLength ?? estimateMessageSize(payload);
|
|
2545
|
+
const waits = frameSize != null || c.queue.length > 0 || c.enforced && c.outstanding > 0 && c.outstanding + size > c.window;
|
|
2546
|
+
const held = !bytes && waits ? {
|
|
2547
|
+
...envelope,
|
|
2548
|
+
payload: structuredClone(payload, { transfer })
|
|
2549
|
+
} : void 0;
|
|
2550
|
+
const send = () => {
|
|
2551
|
+
if (!bytes) {
|
|
2552
|
+
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
2553
|
+
return;
|
|
2554
|
+
}
|
|
2555
|
+
if (frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
2556
|
+
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
2557
|
+
const data = bytes.subarray(at, at + frameSize);
|
|
2558
|
+
const frame = at + frameSize >= bytes.byteLength ? {
|
|
2559
|
+
type: "frame",
|
|
2560
|
+
id,
|
|
2561
|
+
data,
|
|
2562
|
+
last: true
|
|
2563
|
+
} : {
|
|
2564
|
+
type: "frame",
|
|
2565
|
+
id,
|
|
2566
|
+
data
|
|
2567
|
+
};
|
|
2568
|
+
sendOnPort(id, () => post(frame));
|
|
2569
|
+
}
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
sendOnPort(id, () => postBytes(bytes));
|
|
2573
|
+
};
|
|
2574
|
+
c.queue.push({
|
|
2575
|
+
send,
|
|
2576
|
+
size
|
|
2577
|
+
});
|
|
2578
|
+
c.queued += size;
|
|
2579
|
+
flush(c);
|
|
2580
|
+
}, (reason) => {
|
|
2581
|
+
release(reason);
|
|
2582
|
+
record.markClosed(reason);
|
|
2583
|
+
});
|
|
2584
|
+
function release(reason) {
|
|
2585
|
+
open.delete(id);
|
|
2586
|
+
leaks?.unregister(record);
|
|
2587
|
+
if (c?.queue.length) {
|
|
2588
|
+
for (const item of c.queue.splice(0)) item.send();
|
|
2589
|
+
c.queued = 0;
|
|
2590
|
+
}
|
|
2591
|
+
sendOnPort(id, () => post({
|
|
2284
2592
|
type: "close",
|
|
2285
2593
|
id,
|
|
2286
2594
|
reason
|
|
2595
|
+
}));
|
|
2596
|
+
releaseWaiters(c);
|
|
2597
|
+
}
|
|
2598
|
+
const { markClosed, isClosed, hasListeners } = self;
|
|
2599
|
+
if (c) {
|
|
2600
|
+
const deliver = self.deliver;
|
|
2601
|
+
self.deliver = (payload, size = estimateMessageSize(payload)) => {
|
|
2602
|
+
c.unconsumed += size;
|
|
2603
|
+
const done = deliver(payload);
|
|
2604
|
+
if (done) done.then(() => grant(id, c, size));
|
|
2605
|
+
else grant(id, c, size);
|
|
2606
|
+
return done;
|
|
2607
|
+
};
|
|
2608
|
+
setPortSendState(self.port, {
|
|
2609
|
+
bufferedAmount: () => c.queued,
|
|
2610
|
+
ready: (below = 0) => new Promise((resolve) => {
|
|
2611
|
+
if (c.queued <= below || isClosed()) nextTask(resolve);
|
|
2612
|
+
else c.waiters.push({
|
|
2613
|
+
below,
|
|
2614
|
+
resolve: () => nextTask(resolve)
|
|
2615
|
+
});
|
|
2616
|
+
})
|
|
2287
2617
|
});
|
|
2288
|
-
|
|
2289
|
-
|
|
2618
|
+
}
|
|
2619
|
+
const record = {
|
|
2620
|
+
deliver: self.deliver,
|
|
2621
|
+
markClosed,
|
|
2622
|
+
isClosed,
|
|
2623
|
+
hasListeners,
|
|
2624
|
+
close: (reason) => {
|
|
2625
|
+
if (isClosed()) return;
|
|
2626
|
+
release(reason);
|
|
2627
|
+
markClosed(reason);
|
|
2628
|
+
}
|
|
2629
|
+
};
|
|
2630
|
+
leaks?.register(self.port, {
|
|
2631
|
+
id,
|
|
2632
|
+
record,
|
|
2633
|
+
release
|
|
2634
|
+
}, record);
|
|
2635
|
+
const now = Date.now();
|
|
2636
|
+
open.set(id, {
|
|
2637
|
+
id,
|
|
2638
|
+
meta,
|
|
2639
|
+
openedAt: now,
|
|
2640
|
+
lastActivity: now,
|
|
2641
|
+
handle: record,
|
|
2642
|
+
credit: c
|
|
2290
2643
|
});
|
|
2291
|
-
open.set(id, self);
|
|
2292
2644
|
return self;
|
|
2293
2645
|
};
|
|
2294
|
-
const handleEnvelope = (envelope) => {
|
|
2646
|
+
const handleEnvelope = (envelope, event) => {
|
|
2295
2647
|
if (muxClosed) return;
|
|
2648
|
+
heardSincePing = true;
|
|
2649
|
+
if (envelope.type === "ping" || envelope.type === "pong") {
|
|
2650
|
+
peerKeepsAlive = true;
|
|
2651
|
+
if (envelope.type === "ping") post({
|
|
2652
|
+
type: "pong",
|
|
2653
|
+
id: KEEPALIVE_ID
|
|
2654
|
+
});
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2296
2657
|
if (envelope.type === "open") {
|
|
2297
2658
|
if (open.has(envelope.id)) return;
|
|
2298
2659
|
if (open.size >= maxPorts) {
|
|
@@ -2303,7 +2664,18 @@ function multiplexPort(port, options) {
|
|
|
2303
2664
|
});
|
|
2304
2665
|
return;
|
|
2305
2666
|
}
|
|
2306
|
-
const handle = attach(envelope.id);
|
|
2667
|
+
const handle = attach(envelope.id, envelope.meta, envelope.window, false, envelope.exact === true && exactCodec);
|
|
2668
|
+
const credit = open.get(envelope.id)?.credit;
|
|
2669
|
+
if (credit) post(credit.exact ? {
|
|
2670
|
+
type: "credit",
|
|
2671
|
+
id: envelope.id,
|
|
2672
|
+
bytes: 0,
|
|
2673
|
+
exact: true
|
|
2674
|
+
} : {
|
|
2675
|
+
type: "credit",
|
|
2676
|
+
id: envelope.id,
|
|
2677
|
+
bytes: 0
|
|
2678
|
+
});
|
|
2307
2679
|
let accepted = false;
|
|
2308
2680
|
if (onPort) try {
|
|
2309
2681
|
accepted = onPort(handle.port, envelope.meta) !== false;
|
|
@@ -2318,57 +2690,191 @@ function multiplexPort(port, options) {
|
|
|
2318
2690
|
id: envelope.id,
|
|
2319
2691
|
reason: "rejected"
|
|
2320
2692
|
});
|
|
2693
|
+
return;
|
|
2694
|
+
}
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
const entry = open.get(envelope.id);
|
|
2698
|
+
if (envelope.type === "credit") {
|
|
2699
|
+
const c = entry?.credit;
|
|
2700
|
+
if (!c || !(envelope.bytes >= 0)) return;
|
|
2701
|
+
if (!c.enforced && c.opener) {
|
|
2702
|
+
if (c.exact && envelope.exact !== true) {
|
|
2703
|
+
c.exact = false;
|
|
2704
|
+
c.outstanding = 0;
|
|
2705
|
+
}
|
|
2706
|
+
post({
|
|
2707
|
+
type: "credit",
|
|
2708
|
+
id: envelope.id,
|
|
2709
|
+
bytes: 0
|
|
2710
|
+
});
|
|
2321
2711
|
}
|
|
2712
|
+
c.enforced = true;
|
|
2713
|
+
c.outstanding = Math.max(0, c.outstanding - envelope.bytes);
|
|
2714
|
+
flush(c);
|
|
2322
2715
|
return;
|
|
2323
2716
|
}
|
|
2324
|
-
const handle = open.get(envelope.id);
|
|
2325
2717
|
if (envelope.type === "message") {
|
|
2326
|
-
|
|
2327
|
-
|
|
2718
|
+
const sizeOf = (c) => (c.exact ? byteLengthOf(event.data) : void 0) ?? estimateMessageSize(envelope.payload);
|
|
2719
|
+
if (!entry) return;
|
|
2720
|
+
touch(envelope.id);
|
|
2721
|
+
entry.handle.deliver(envelope.payload, entry.credit ? sizeOf(entry.credit) : void 0);
|
|
2328
2722
|
return;
|
|
2329
2723
|
}
|
|
2330
|
-
if (
|
|
2724
|
+
if (envelope.type === "frame") {
|
|
2725
|
+
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
2726
|
+
touch(envelope.id);
|
|
2727
|
+
const partial = entry.partial ?? {
|
|
2728
|
+
chunks: [],
|
|
2729
|
+
bytes: 0
|
|
2730
|
+
};
|
|
2731
|
+
entry.partial = partial;
|
|
2732
|
+
partial.chunks.push(envelope.data);
|
|
2733
|
+
partial.bytes += envelope.data.byteLength;
|
|
2734
|
+
if (!envelope.last) return;
|
|
2735
|
+
entry.partial = void 0;
|
|
2736
|
+
const whole = new Uint8Array(partial.bytes);
|
|
2737
|
+
let at = 0;
|
|
2738
|
+
for (const chunk of partial.chunks) {
|
|
2739
|
+
whole.set(chunk, at);
|
|
2740
|
+
at += chunk.byteLength;
|
|
2741
|
+
}
|
|
2742
|
+
const inner = codec.decode(whole);
|
|
2743
|
+
if (inner?.type !== "message" || inner.id !== envelope.id) return;
|
|
2744
|
+
entry.handle.deliver(inner.payload, whole.byteLength);
|
|
2745
|
+
return;
|
|
2746
|
+
}
|
|
2747
|
+
dropOutgoing(envelope.id);
|
|
2748
|
+
if (!entry) return;
|
|
2331
2749
|
open.delete(envelope.id);
|
|
2332
|
-
|
|
2750
|
+
releaseWaiters(entry.credit);
|
|
2751
|
+
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
|
2333
2752
|
};
|
|
2334
2753
|
const listener = (event) => {
|
|
2335
2754
|
const envelope = codec.read(event);
|
|
2336
|
-
if (envelope) handleEnvelope(envelope);
|
|
2755
|
+
if (envelope) handleEnvelope(envelope, event);
|
|
2756
|
+
};
|
|
2757
|
+
let peerKeepsAlive = false;
|
|
2758
|
+
let heardSincePing = true;
|
|
2759
|
+
let unanswered = 0;
|
|
2760
|
+
let pinger;
|
|
2761
|
+
const tick = () => {
|
|
2762
|
+
if (!keepalive) return;
|
|
2763
|
+
const backlogged = (port.bufferedAmount ?? 0) > 0;
|
|
2764
|
+
if (heardSincePing) unanswered = 0;
|
|
2765
|
+
else if (peerKeepsAlive && !backlogged) unanswered++;
|
|
2766
|
+
if (unanswered >= misses) {
|
|
2767
|
+
close(/* @__PURE__ */ new Error(`webrun-rpc: the peer did not answer ${misses} keepalive pings, ${keepalive.intervalMs} ms apart`)).catch(() => {});
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
heardSincePing = false;
|
|
2771
|
+
post({
|
|
2772
|
+
type: "ping",
|
|
2773
|
+
id: KEEPALIVE_ID
|
|
2774
|
+
});
|
|
2775
|
+
};
|
|
2776
|
+
const close = async (reason) => {
|
|
2777
|
+
if (muxClosed) return;
|
|
2778
|
+
clearInterval(pinger);
|
|
2779
|
+
muxClosed = true;
|
|
2780
|
+
signal?.removeEventListener("abort", onTransportAbort);
|
|
2781
|
+
port.removeEventListener("message", listener);
|
|
2782
|
+
const closedNow = /* @__PURE__ */ new Set();
|
|
2783
|
+
for (const [id, { handle, credit }] of [...open]) {
|
|
2784
|
+
closedNow.add(id);
|
|
2785
|
+
codecPost({
|
|
2786
|
+
type: "close",
|
|
2787
|
+
id
|
|
2788
|
+
});
|
|
2789
|
+
open.delete(id);
|
|
2790
|
+
releaseWaiters(credit);
|
|
2791
|
+
handle.markClosed(reason ?? /* @__PURE__ */ new Error("webrun-rpc: the multiplexer was closed"));
|
|
2792
|
+
}
|
|
2793
|
+
for (const id of outgoing.keys()) if (!closedNow.has(id)) codecPost({
|
|
2794
|
+
type: "close",
|
|
2795
|
+
id
|
|
2796
|
+
});
|
|
2797
|
+
outgoing.clear();
|
|
2798
|
+
ring.length = 0;
|
|
2799
|
+
await port.close?.();
|
|
2800
|
+
};
|
|
2801
|
+
const onTransportAbort = () => {
|
|
2802
|
+
close(signal?.reason).catch(() => {});
|
|
2337
2803
|
};
|
|
2338
2804
|
port.addEventListener("message", listener);
|
|
2339
2805
|
port.start?.();
|
|
2806
|
+
if (keepalive) {
|
|
2807
|
+
pinger = setInterval(tick, keepalive.intervalMs);
|
|
2808
|
+
pinger.unref?.();
|
|
2809
|
+
}
|
|
2810
|
+
if (signal?.aborted) close(signal.reason).catch(() => {});
|
|
2811
|
+
else signal?.addEventListener("abort", onTransportAbort, { once: true });
|
|
2340
2812
|
return {
|
|
2341
2813
|
maxMessageSize,
|
|
2342
|
-
async openPort(meta) {
|
|
2814
|
+
async openPort(meta, { signal: portSignal } = {}) {
|
|
2343
2815
|
if (muxClosed) throw new Error("webrun-rpc: the multiplexer is closed");
|
|
2344
|
-
if (
|
|
2816
|
+
if (portSignal?.aborted) throw portSignal.reason;
|
|
2817
|
+
if (open.size >= maxPorts) throw new RangeError(`webrun-rpc: maxPorts (${maxPorts}) reached; open ports by kind: ${describeKinds(open.values())}`);
|
|
2345
2818
|
while (open.has(nextId)) nextId += 2;
|
|
2346
2819
|
const id = nextId;
|
|
2347
2820
|
nextId += 2;
|
|
2348
|
-
const handle = attach(id);
|
|
2349
|
-
post({
|
|
2821
|
+
const handle = attach(id, meta, window, true, exactCodec);
|
|
2822
|
+
post(window ? exactCodec ? {
|
|
2823
|
+
type: "open",
|
|
2824
|
+
id,
|
|
2825
|
+
meta,
|
|
2826
|
+
window,
|
|
2827
|
+
exact: true
|
|
2828
|
+
} : {
|
|
2829
|
+
type: "open",
|
|
2830
|
+
id,
|
|
2831
|
+
meta,
|
|
2832
|
+
window
|
|
2833
|
+
} : {
|
|
2350
2834
|
type: "open",
|
|
2351
2835
|
id,
|
|
2352
2836
|
meta
|
|
2353
2837
|
});
|
|
2838
|
+
if (portSignal) {
|
|
2839
|
+
const record = open.get(id)?.handle;
|
|
2840
|
+
const onAbort = () => {
|
|
2841
|
+
if (record && open.get(id)?.handle === record) record.close(portSignal.reason);
|
|
2842
|
+
};
|
|
2843
|
+
portSignal.addEventListener("abort", onAbort, { once: true });
|
|
2844
|
+
getPortCloseSignal(handle.port)?.addEventListener("abort", () => portSignal.removeEventListener("abort", onAbort), { once: true });
|
|
2845
|
+
}
|
|
2354
2846
|
return handle.port;
|
|
2355
2847
|
},
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
}
|
|
2366
|
-
muxClosed = true;
|
|
2367
|
-
port.removeEventListener("message", listener);
|
|
2368
|
-
await port.close?.();
|
|
2848
|
+
close,
|
|
2849
|
+
ports() {
|
|
2850
|
+
return [...open.values()].map(({ handle: _, credit, ...info }) => credit ? {
|
|
2851
|
+
...info,
|
|
2852
|
+
...credit.enforced ? { window: credit.window } : {},
|
|
2853
|
+
queued: credit.queued,
|
|
2854
|
+
outstanding: credit.outstanding,
|
|
2855
|
+
unconsumed: credit.unconsumed
|
|
2856
|
+
} : info);
|
|
2369
2857
|
}
|
|
2370
2858
|
};
|
|
2371
2859
|
}
|
|
2860
|
+
/** The byte length of a received message's data, or `undefined` if it is not bytes. */
|
|
2861
|
+
function byteLengthOf(data) {
|
|
2862
|
+
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) return data.byteLength;
|
|
2863
|
+
}
|
|
2864
|
+
/**
|
|
2865
|
+
* `"http: 1000, ws: 24"`: the three commonest `meta.kind`s among `entries`, so
|
|
2866
|
+
* a full table names who filled it. A port without a kind counts as `"?"`.
|
|
2867
|
+
*/
|
|
2868
|
+
function describeKinds(entries) {
|
|
2869
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2870
|
+
for (const { meta } of entries) {
|
|
2871
|
+
const kind = meta?.kind;
|
|
2872
|
+
const name = typeof kind === "string" ? kind : "?";
|
|
2873
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
2874
|
+
}
|
|
2875
|
+
if (!counts.size) return "none";
|
|
2876
|
+
return [...counts].sort((x, y) => y[1] - x[1]).slice(0, 3).map(([name, n]) => `${name}: ${n}`).join(", ");
|
|
2877
|
+
}
|
|
2372
2878
|
/**
|
|
2373
2879
|
* Announced with every port this pair opens. Layer 1 never reads `meta` — it is
|
|
2374
2880
|
* here so a peer running something else over the same multiplexer can tell a
|
|
@@ -2475,7 +2981,7 @@ function isEnvelope(value) {
|
|
|
2475
2981
|
const candidate = value;
|
|
2476
2982
|
if (typeof candidate.id !== "number") return false;
|
|
2477
2983
|
if (!Number.isInteger(candidate.id) || candidate.id < 0) return false;
|
|
2478
|
-
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close";
|
|
2984
|
+
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close" || candidate.type === "credit" || candidate.type === "frame" || candidate.type === "ping" || candidate.type === "pong";
|
|
2479
2985
|
}
|
|
2480
2986
|
/**
|
|
2481
2987
|
* For ports whose messages are structured values — a real `MessagePort`, a
|
|
@@ -2637,7 +3143,7 @@ function trackedLink(send, release) {
|
|
|
2637
3143
|
finish();
|
|
2638
3144
|
return response;
|
|
2639
3145
|
}
|
|
2640
|
-
return new Response(guardBody(response.body, lost, () => lostReason, finish), {
|
|
3146
|
+
return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
|
|
2641
3147
|
status: response.status,
|
|
2642
3148
|
statusText: response.statusText,
|
|
2643
3149
|
headers: response.headers
|
|
@@ -2656,33 +3162,65 @@ function trackedLink(send, release) {
|
|
|
2656
3162
|
return asyncDisposable(link, () => link.close());
|
|
2657
3163
|
}
|
|
2658
3164
|
/**
|
|
2659
|
-
* A response body that reports its end (done, cancelled or
|
|
2660
|
-
* with the link's reason when the link is lost mid-stream.
|
|
3165
|
+
* A response body that reports its end (done, cancelled, failed or aborted) and
|
|
3166
|
+
* fails with the link's reason when the link is lost mid-stream. An abort of the
|
|
3167
|
+
* request's signal ends it at once, read or not: the exchange is over (the layer
|
|
3168
|
+
* below has given its port back), so the request leaves `inflight` then.
|
|
2661
3169
|
*/
|
|
2662
|
-
function guardBody(body, lost, reason, finish) {
|
|
3170
|
+
function guardBody(body, lost, reason, finish, signal) {
|
|
2663
3171
|
const reader = body.getReader();
|
|
3172
|
+
let controller;
|
|
3173
|
+
let ended = false;
|
|
3174
|
+
const end = () => {
|
|
3175
|
+
ended = true;
|
|
3176
|
+
signal?.removeEventListener("abort", onAbort);
|
|
3177
|
+
finish();
|
|
3178
|
+
};
|
|
3179
|
+
const onAbort = () => {
|
|
3180
|
+
if (ended) return;
|
|
3181
|
+
end();
|
|
3182
|
+
reader.cancel().catch(() => {});
|
|
3183
|
+
try {
|
|
3184
|
+
controller.error(abortError(signal));
|
|
3185
|
+
} catch {}
|
|
3186
|
+
};
|
|
2664
3187
|
return new ReadableStream({
|
|
3188
|
+
start(c) {
|
|
3189
|
+
controller = c;
|
|
3190
|
+
if (signal?.aborted) onAbort();
|
|
3191
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
3192
|
+
},
|
|
2665
3193
|
async pull(controller) {
|
|
2666
3194
|
try {
|
|
2667
3195
|
const { done, value } = await Promise.race([reader.read(), lost]);
|
|
3196
|
+
if (ended) return;
|
|
2668
3197
|
if (done) {
|
|
2669
|
-
|
|
3198
|
+
end();
|
|
2670
3199
|
controller.close();
|
|
2671
3200
|
return;
|
|
2672
3201
|
}
|
|
2673
3202
|
controller.enqueue(value);
|
|
2674
3203
|
} catch (error) {
|
|
2675
|
-
|
|
3204
|
+
if (ended) return;
|
|
3205
|
+
end();
|
|
2676
3206
|
reader.cancel().catch(() => {});
|
|
2677
3207
|
controller.error(reason() ?? error);
|
|
2678
3208
|
}
|
|
2679
3209
|
},
|
|
2680
3210
|
async cancel(why) {
|
|
2681
|
-
|
|
3211
|
+
end();
|
|
2682
3212
|
await reader.cancel(why).catch(() => {});
|
|
2683
3213
|
}
|
|
2684
3214
|
});
|
|
2685
3215
|
}
|
|
3216
|
+
/** The signal's reason as an `Error` (`AbortError` when it gave none). */
|
|
3217
|
+
function abortError(signal) {
|
|
3218
|
+
const why = signal.reason;
|
|
3219
|
+
if (why instanceof Error) return why;
|
|
3220
|
+
const err = new Error(why === void 0 ? "Aborted" : String(why));
|
|
3221
|
+
err.name = "AbortError";
|
|
3222
|
+
return err;
|
|
3223
|
+
}
|
|
2686
3224
|
//#endregion
|
|
2687
3225
|
//#region src/link/locks.ts
|
|
2688
3226
|
function lockManager() {
|
|
@@ -5498,6 +6036,8 @@ var WorkerLinkImpl = class {
|
|
|
5498
6036
|
_ownerLock;
|
|
5499
6037
|
_attaching;
|
|
5500
6038
|
_detached = false;
|
|
6039
|
+
/** The `seq` of the last link sent to the worker. */
|
|
6040
|
+
_sent = 0;
|
|
5501
6041
|
constructor(_adapter, _control, init) {
|
|
5502
6042
|
this._adapter = _adapter;
|
|
5503
6043
|
this._control = _control;
|
|
@@ -5509,6 +6049,7 @@ var WorkerLinkImpl = class {
|
|
|
5509
6049
|
_control.onmessage = (event) => {
|
|
5510
6050
|
const message = event.data;
|
|
5511
6051
|
if (message?.type === "relink") {
|
|
6052
|
+
if (message.seen !== void 0 && message.seen < this._sent) return;
|
|
5512
6053
|
this._adapter._poke(WAKE_MESSAGE);
|
|
5513
6054
|
this.attach().catch(() => {});
|
|
5514
6055
|
} else if (message?.type === "keepalive") this._adapter._poke(KEEPALIVE_MESSAGE);
|
|
@@ -5555,7 +6096,8 @@ var WorkerLinkImpl = class {
|
|
|
5555
6096
|
this._control.postMessage({
|
|
5556
6097
|
type: "link",
|
|
5557
6098
|
key: this.key,
|
|
5558
|
-
routerLock: reply.routerLock
|
|
6099
|
+
routerLock: reply.routerLock,
|
|
6100
|
+
seq: ++this._sent
|
|
5559
6101
|
}, [
|
|
5560
6102
|
down.port2,
|
|
5561
6103
|
ctl.port2,
|
|
@@ -5656,12 +6198,18 @@ function serveHttpInWorker(handler, options = {}) {
|
|
|
5656
6198
|
const ownerLock = newLockName("webrun-http-owner");
|
|
5657
6199
|
const held = holdLock(ownerLock);
|
|
5658
6200
|
const controls = /* @__PURE__ */ new Set();
|
|
6201
|
+
const seen = /* @__PURE__ */ new Map();
|
|
5659
6202
|
const tellPages = (message) => {
|
|
5660
6203
|
for (const control of controls) control.postMessage(message);
|
|
5661
6204
|
};
|
|
5662
6205
|
const endpoint = new HandlerEndpoint(handler, {
|
|
5663
6206
|
...options,
|
|
5664
|
-
requestRelink: () =>
|
|
6207
|
+
requestRelink: () => {
|
|
6208
|
+
for (const control of controls) control.postMessage({
|
|
6209
|
+
type: "relink",
|
|
6210
|
+
seen: seen.get(control) ?? 0
|
|
6211
|
+
});
|
|
6212
|
+
},
|
|
5665
6213
|
keepAlive: () => tellPages({ type: "keepalive" })
|
|
5666
6214
|
});
|
|
5667
6215
|
const onMessage = (event) => {
|
|
@@ -5673,6 +6221,7 @@ function serveHttpInWorker(handler, options = {}) {
|
|
|
5673
6221
|
control.onmessage = (message) => {
|
|
5674
6222
|
const msg = message.data;
|
|
5675
6223
|
if (msg?.type === "link") {
|
|
6224
|
+
if (msg.seq !== void 0) seen.set(control, msg.seq);
|
|
5676
6225
|
const [down, ctl, up] = message.ports;
|
|
5677
6226
|
if (down) endpoint.link(msg.key, {
|
|
5678
6227
|
down,
|
|
@@ -5713,6 +6262,7 @@ function serveHttpInWorker(handler, options = {}) {
|
|
|
5713
6262
|
await endpoint.close();
|
|
5714
6263
|
for (const control of controls) control.close();
|
|
5715
6264
|
controls.clear();
|
|
6265
|
+
seen.clear();
|
|
5716
6266
|
(await held)();
|
|
5717
6267
|
}
|
|
5718
6268
|
};
|