@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/index.js
CHANGED
|
@@ -2598,7 +2598,7 @@ async function fetchOverDuplex(call, request, options = {}) {
|
|
|
2598
2598
|
else await release?.();
|
|
2599
2599
|
return new Response(null, responseInit);
|
|
2600
2600
|
}
|
|
2601
|
-
return new Response(
|
|
2601
|
+
return new Response(responseBody(respBody, request.signal, release), responseInit);
|
|
2602
2602
|
}
|
|
2603
2603
|
/**
|
|
2604
2604
|
* Wrap a `(Request) => Promise<Response>` handler as a `Duplex` so it can be
|
|
@@ -2729,15 +2729,78 @@ function trackingEnd(source) {
|
|
|
2729
2729
|
settle
|
|
2730
2730
|
};
|
|
2731
2731
|
}
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2732
|
+
/**
|
|
2733
|
+
* How long a cancelled body waits for the read in flight before it abandons the call anyway.
|
|
2734
|
+
* The read's answer tells a body that has ended (the call is finished: an upload the handler
|
|
2735
|
+
* reads after answering goes on) from one that has not; a peer that sends nothing more never
|
|
2736
|
+
* answers it. A body whose end comes later than this after the cancel is abandoned too, so a
|
|
2737
|
+
* handler still streaming its response and still reading the upload then loses the upload.
|
|
2738
|
+
*/
|
|
2739
|
+
const CANCEL_GRACE_MS = 1e3;
|
|
2740
|
+
/**
|
|
2741
|
+
* The response body as a stream that the request's signal can end at any time.
|
|
2742
|
+
*
|
|
2743
|
+
* An abort errors the stream with the abort's reason at once — a read waiting
|
|
2744
|
+
* for a peer that sends nothing more rejects then, not when a next chunk
|
|
2745
|
+
* arrives, which may be never — and abandons the call (`release`), so on a
|
|
2746
|
+
* multiplexed transport its port is given back whether or not anyone still
|
|
2747
|
+
* reads the body. The listener is removed once the body ends, however it ends:
|
|
2748
|
+
* read to its end, cancelled, failed or aborted. A body nobody reads or cancels
|
|
2749
|
+
* keeps it until the signal aborts (and keeps the call open until then anyway).
|
|
2750
|
+
* A cancel abandons the call too — unless the read in flight comes back with the
|
|
2751
|
+
* body's end, which finishes it instead (see `cancel`).
|
|
2752
|
+
*/
|
|
2753
|
+
function responseBody(body, signal, release) {
|
|
2754
|
+
const it = body[Symbol.asyncIterator]();
|
|
2755
|
+
let controller;
|
|
2756
|
+
let ended = false;
|
|
2757
|
+
let reading = false;
|
|
2758
|
+
const end = () => {
|
|
2759
|
+
ended = true;
|
|
2760
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2761
|
+
};
|
|
2762
|
+
const onAbort = () => {
|
|
2763
|
+
if (ended) return;
|
|
2764
|
+
end();
|
|
2765
|
+
try {
|
|
2766
|
+
controller.error(abortReason$1(signal));
|
|
2767
|
+
} catch {}
|
|
2768
|
+
release?.();
|
|
2769
|
+
};
|
|
2770
|
+
return new ReadableStream({
|
|
2771
|
+
start(c) {
|
|
2772
|
+
controller = c;
|
|
2773
|
+
if (signal?.aborted) onAbort();
|
|
2774
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
2775
|
+
},
|
|
2776
|
+
async pull(c) {
|
|
2777
|
+
try {
|
|
2778
|
+
reading = true;
|
|
2779
|
+
const { done, value } = await it.next().finally(() => {
|
|
2780
|
+
reading = false;
|
|
2781
|
+
});
|
|
2782
|
+
if (ended) return;
|
|
2783
|
+
if (done) {
|
|
2784
|
+
end();
|
|
2785
|
+
c.close();
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2788
|
+
c.enqueue(value);
|
|
2789
|
+
} catch (err) {
|
|
2790
|
+
if (ended) return;
|
|
2791
|
+
end();
|
|
2792
|
+
c.error(err);
|
|
2793
|
+
}
|
|
2794
|
+
},
|
|
2795
|
+
cancel() {
|
|
2796
|
+
end();
|
|
2797
|
+
const returned = Promise.resolve(it.return?.()).catch(() => {});
|
|
2798
|
+
if (!reading) return;
|
|
2799
|
+
const grace = setTimeout(() => void release?.(), CANCEL_GRACE_MS);
|
|
2800
|
+
grace.unref?.();
|
|
2801
|
+
returned.finally(() => clearTimeout(grace));
|
|
2802
|
+
}
|
|
2803
|
+
});
|
|
2741
2804
|
}
|
|
2742
2805
|
/**
|
|
2743
2806
|
* Host a `SiteHandler` over a `Connect/Serve` pair. The `start(serve, params)`
|
|
@@ -3024,8 +3087,11 @@ function listenPort(port, handler, { channelName = "", log = () => {} } = {}) {
|
|
|
3024
3087
|
error
|
|
3025
3088
|
});
|
|
3026
3089
|
};
|
|
3027
|
-
|
|
3028
|
-
|
|
3090
|
+
const listener = (event) => {
|
|
3091
|
+
onMessage(event);
|
|
3092
|
+
};
|
|
3093
|
+
port.addEventListener("message", listener);
|
|
3094
|
+
return () => port.removeEventListener("message", listener);
|
|
3029
3095
|
}
|
|
3030
3096
|
/**
|
|
3031
3097
|
* Wraps an async iterable so that an `AbortSignal` firing causes the wrapper
|
|
@@ -3430,6 +3496,33 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
|
|
|
3430
3496
|
}
|
|
3431
3497
|
}
|
|
3432
3498
|
/**
|
|
3499
|
+
* Runs `fn` on a later task, never a microtask: a producer looping on
|
|
3500
|
+
* `whenPortReady` must let the transport's own events run. `setImmediate`
|
|
3501
|
+
* where it exists; elsewhere a `MessageChannel`, because nested `setTimeout`s
|
|
3502
|
+
* are clamped to 4 ms in browsers.
|
|
3503
|
+
*/
|
|
3504
|
+
const setImmediateFn = globalThis.setImmediate;
|
|
3505
|
+
/** @internal */
|
|
3506
|
+
const nextTask = setImmediateFn ? (fn) => {
|
|
3507
|
+
setImmediateFn(fn);
|
|
3508
|
+
} : (() => {
|
|
3509
|
+
let channel;
|
|
3510
|
+
const queue = [];
|
|
3511
|
+
return (fn) => {
|
|
3512
|
+
if (!channel) {
|
|
3513
|
+
channel = new MessageChannel();
|
|
3514
|
+
channel.port1.onmessage = () => queue.shift()?.();
|
|
3515
|
+
}
|
|
3516
|
+
queue.push(fn);
|
|
3517
|
+
channel.port2.postMessage(0);
|
|
3518
|
+
};
|
|
3519
|
+
})();
|
|
3520
|
+
const states = /* @__PURE__ */ new WeakMap();
|
|
3521
|
+
/** @internal Registered by the multiplexer for each port with a window. */
|
|
3522
|
+
function setPortSendState(port, state) {
|
|
3523
|
+
states.set(port, state);
|
|
3524
|
+
}
|
|
3525
|
+
/**
|
|
3433
3526
|
* One virtual port.
|
|
3434
3527
|
*
|
|
3435
3528
|
* `deliver` and `markClosed` are deliberately not on `port`: the consumer holds
|
|
@@ -3444,6 +3537,14 @@ function newVirtualPort(send, requestClose) {
|
|
|
3444
3537
|
if (closing.signal.aborted) return;
|
|
3445
3538
|
closing.abort(reason ?? /* @__PURE__ */ new Error("webrun-rpc: the port was closed"));
|
|
3446
3539
|
};
|
|
3540
|
+
const close = (reason) => {
|
|
3541
|
+
if (closed) return;
|
|
3542
|
+
closed = true;
|
|
3543
|
+
const notify = requestClose;
|
|
3544
|
+
listeners.clear();
|
|
3545
|
+
notify(reason);
|
|
3546
|
+
abort(reason);
|
|
3547
|
+
};
|
|
3447
3548
|
const port = {
|
|
3448
3549
|
addEventListener(_type, listener) {
|
|
3449
3550
|
listeners.add(listener);
|
|
@@ -3455,24 +3556,28 @@ function newVirtualPort(send, requestClose) {
|
|
|
3455
3556
|
if (closed) return;
|
|
3456
3557
|
send(message, transfer);
|
|
3457
3558
|
},
|
|
3458
|
-
close
|
|
3459
|
-
if (closed) return;
|
|
3460
|
-
closed = true;
|
|
3461
|
-
const notify = requestClose;
|
|
3462
|
-
listeners.clear();
|
|
3463
|
-
notify();
|
|
3464
|
-
abort();
|
|
3465
|
-
}
|
|
3559
|
+
close
|
|
3466
3560
|
};
|
|
3467
3561
|
setPortCloseSignal(port, closing.signal);
|
|
3562
|
+
const dispose = Symbol.dispose;
|
|
3563
|
+
if (dispose) Object.defineProperty(port, dispose, {
|
|
3564
|
+
value: () => close(),
|
|
3565
|
+
configurable: true
|
|
3566
|
+
});
|
|
3468
3567
|
return {
|
|
3469
3568
|
port,
|
|
3470
3569
|
deliver(payload) {
|
|
3471
3570
|
if (closed) return;
|
|
3472
3571
|
const event = new MessageEvent("message", { data: payload });
|
|
3572
|
+
let pending;
|
|
3473
3573
|
for (const listener of [...listeners]) try {
|
|
3474
|
-
listener(event);
|
|
3574
|
+
const result = listener(event);
|
|
3575
|
+
if (result && typeof result.then === "function") {
|
|
3576
|
+
pending ??= [];
|
|
3577
|
+
pending.push(result);
|
|
3578
|
+
}
|
|
3475
3579
|
} catch {}
|
|
3580
|
+
return pending ? Promise.allSettled(pending) : void 0;
|
|
3476
3581
|
},
|
|
3477
3582
|
markClosed(reason) {
|
|
3478
3583
|
closed = true;
|
|
@@ -3481,53 +3586,309 @@ function newVirtualPort(send, requestClose) {
|
|
|
3481
3586
|
},
|
|
3482
3587
|
isClosed() {
|
|
3483
3588
|
return closed;
|
|
3589
|
+
},
|
|
3590
|
+
hasListeners() {
|
|
3591
|
+
return listeners.size > 0;
|
|
3484
3592
|
}
|
|
3485
3593
|
};
|
|
3486
3594
|
}
|
|
3487
3595
|
/**
|
|
3488
|
-
* The
|
|
3489
|
-
*
|
|
3596
|
+
* The id of keepalive's `ping` / `pong`: one no port ever takes. An older mux
|
|
3597
|
+
* whose codec passes unknown envelopes through reads them as a close of that
|
|
3598
|
+
* id, which must therefore not be a real port (0 is the initiator's first).
|
|
3490
3599
|
*/
|
|
3491
|
-
const
|
|
3600
|
+
const KEEPALIVE_ID = Number.MAX_SAFE_INTEGER;
|
|
3601
|
+
/** The longest delay a timer holds; past it `setInterval` fires at once. */
|
|
3602
|
+
const MAX_TIMER_MS = 2 ** 31 - 1;
|
|
3492
3603
|
/** Ceiling on concurrently open virtual ports. Bounds the id table only. */
|
|
3493
3604
|
const DEFAULT_MAX_PORTS = 1024;
|
|
3494
3605
|
/**
|
|
3606
|
+
* What a message costs a port's window when the codec cannot say
|
|
3607
|
+
* (`PortCodec.encode`): its bytes and string lengths, found in its values up
|
|
3608
|
+
* to 4 levels deep (at most 256 values), and at least 64. Both ends compute it
|
|
3609
|
+
* from the same structured value, so they agree.
|
|
3610
|
+
*/
|
|
3611
|
+
function estimateMessageSize(payload) {
|
|
3612
|
+
let budget = 256;
|
|
3613
|
+
const walk = (v, depth) => {
|
|
3614
|
+
if (budget-- <= 0) return 0;
|
|
3615
|
+
if (typeof v === "string") return v.length;
|
|
3616
|
+
if (v instanceof ArrayBuffer) return v.byteLength;
|
|
3617
|
+
if (ArrayBuffer.isView(v)) return v.byteLength;
|
|
3618
|
+
if (v === null || typeof v !== "object" || depth >= 4) return 8;
|
|
3619
|
+
let n = 16;
|
|
3620
|
+
if (Array.isArray(v)) for (const x of v) n += walk(x, depth + 1);
|
|
3621
|
+
else for (const k in v) n += k.length + walk(v[k], depth + 1);
|
|
3622
|
+
return n;
|
|
3623
|
+
};
|
|
3624
|
+
return Math.max(64, walk(payload, 0));
|
|
3625
|
+
}
|
|
3626
|
+
/**
|
|
3495
3627
|
* The default `PortMux`: emulates multiplexing over a single port.
|
|
3496
3628
|
*
|
|
3497
3629
|
* A transport that already multiplexes natively supplies its own `PortMux`
|
|
3498
3630
|
* instead — this implementation is for transports that offer one pipe.
|
|
3499
3631
|
*/
|
|
3500
3632
|
function multiplexPort(port, options) {
|
|
3501
|
-
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize } = options;
|
|
3633
|
+
const { codec, onPort, side = "initiator", maxPorts = DEFAULT_MAX_PORTS, maxMessageSize, signal, window, frameSize, keepalive, onLeak } = options;
|
|
3634
|
+
const misses = keepalive?.misses ?? 3;
|
|
3635
|
+
if (keepalive && !(Number.isFinite(keepalive.intervalMs) && keepalive.intervalMs > 0 && keepalive.intervalMs <= MAX_TIMER_MS && Number.isSafeInteger(misses) && misses > 0)) throw new RangeError(`webrun-rpc: keepalive needs an intervalMs in (0, ${MAX_TIMER_MS}] and a positive integer misses`);
|
|
3636
|
+
if (frameSize != null && !(Number.isSafeInteger(frameSize) && frameSize > 0)) throw new RangeError(`webrun-rpc: frameSize must be a positive integer, got ${frameSize}`);
|
|
3502
3637
|
const open = /* @__PURE__ */ new Map();
|
|
3638
|
+
const leaks = onLeak ? new FinalizationRegistry(({ id, record, release }) => {
|
|
3639
|
+
const entry = open.get(id);
|
|
3640
|
+
if (entry?.handle !== record) return;
|
|
3641
|
+
const { handle: _, credit: __, partial: ___, ...info } = entry;
|
|
3642
|
+
if (!record.hasListeners()) {
|
|
3643
|
+
release("leaked");
|
|
3644
|
+
record.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the port was dropped without close()"));
|
|
3645
|
+
}
|
|
3646
|
+
try {
|
|
3647
|
+
onLeak?.(info);
|
|
3648
|
+
} catch {}
|
|
3649
|
+
}) : void 0;
|
|
3650
|
+
const touch = (id) => {
|
|
3651
|
+
const entry = open.get(id);
|
|
3652
|
+
if (entry) entry.lastActivity = Date.now();
|
|
3653
|
+
};
|
|
3503
3654
|
let nextId = side === "initiator" ? 0 : 1;
|
|
3504
3655
|
let muxClosed = false;
|
|
3656
|
+
/** This end counts encoded bytes and reassembles frames. */
|
|
3657
|
+
const exactCodec = codec.encode != null && codec.decode != null;
|
|
3505
3658
|
const post = (envelope, transfer) => {
|
|
3506
3659
|
if (muxClosed) return;
|
|
3660
|
+
codecPost(envelope, transfer);
|
|
3661
|
+
};
|
|
3662
|
+
/** Like `post`, also while closing: the mux's own close notices. */
|
|
3663
|
+
const codecPost = (envelope, transfer) => {
|
|
3507
3664
|
try {
|
|
3508
3665
|
codec.post(port, envelope, transfer);
|
|
3509
3666
|
} catch {}
|
|
3510
3667
|
};
|
|
3511
|
-
const
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3668
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
3669
|
+
const ring = [];
|
|
3670
|
+
let pumping = false;
|
|
3671
|
+
const pump = async () => {
|
|
3672
|
+
pumping = true;
|
|
3673
|
+
try {
|
|
3674
|
+
while (ring.length && !muxClosed) {
|
|
3675
|
+
if (port.whenDrained) await port.whenDrained();
|
|
3676
|
+
if (muxClosed) break;
|
|
3677
|
+
const id = ring.shift();
|
|
3678
|
+
const queue = outgoing.get(id);
|
|
3679
|
+
const item = queue?.shift();
|
|
3680
|
+
if (queue?.length) ring.push(id);
|
|
3681
|
+
else outgoing.delete(id);
|
|
3682
|
+
item?.();
|
|
3683
|
+
}
|
|
3684
|
+
} catch (err) {
|
|
3685
|
+
close(err).catch(() => {});
|
|
3686
|
+
} finally {
|
|
3687
|
+
pumping = false;
|
|
3688
|
+
}
|
|
3689
|
+
};
|
|
3690
|
+
const sendOnPort = (id, item) => {
|
|
3691
|
+
if (frameSize == null) {
|
|
3692
|
+
item();
|
|
3693
|
+
return;
|
|
3694
|
+
}
|
|
3695
|
+
let queue = outgoing.get(id);
|
|
3696
|
+
if (!queue) {
|
|
3697
|
+
queue = [];
|
|
3698
|
+
outgoing.set(id, queue);
|
|
3699
|
+
ring.push(id);
|
|
3700
|
+
}
|
|
3701
|
+
queue.push(item);
|
|
3702
|
+
if (!pumping) pump();
|
|
3703
|
+
};
|
|
3704
|
+
const dropOutgoing = (id) => {
|
|
3705
|
+
if (!outgoing.delete(id)) return;
|
|
3706
|
+
const at = ring.indexOf(id);
|
|
3707
|
+
if (at >= 0) ring.splice(at, 1);
|
|
3708
|
+
};
|
|
3709
|
+
const postBytes = (bytes) => {
|
|
3710
|
+
if (muxClosed) return;
|
|
3711
|
+
try {
|
|
3712
|
+
port.postMessage(bytes);
|
|
3713
|
+
} catch {}
|
|
3714
|
+
};
|
|
3715
|
+
const releaseWaiters = (c) => {
|
|
3716
|
+
for (const w of c?.waiters.splice(0) ?? []) w.resolve();
|
|
3717
|
+
};
|
|
3718
|
+
const flush = (c) => {
|
|
3719
|
+
while (c.queue.length) {
|
|
3720
|
+
const next = c.queue[0];
|
|
3721
|
+
if (c.enforced && c.outstanding > 0 && c.outstanding + next.size > c.window) break;
|
|
3722
|
+
c.queue.shift();
|
|
3723
|
+
c.queued -= next.size;
|
|
3724
|
+
c.outstanding += next.size;
|
|
3725
|
+
next.send();
|
|
3726
|
+
}
|
|
3727
|
+
for (const w of [...c.waiters]) if (c.queued <= w.below) {
|
|
3728
|
+
c.waiters.splice(c.waiters.indexOf(w), 1);
|
|
3729
|
+
w.resolve();
|
|
3730
|
+
}
|
|
3731
|
+
};
|
|
3732
|
+
const grant = (id, c, bytes) => {
|
|
3733
|
+
c.unconsumed -= bytes;
|
|
3734
|
+
c.toGrant += bytes;
|
|
3735
|
+
const send = () => {
|
|
3736
|
+
c.grantScheduled = false;
|
|
3737
|
+
if (!c.toGrant || !open.has(id)) return;
|
|
3518
3738
|
post({
|
|
3739
|
+
type: "credit",
|
|
3740
|
+
id,
|
|
3741
|
+
bytes: c.toGrant
|
|
3742
|
+
});
|
|
3743
|
+
c.toGrant = 0;
|
|
3744
|
+
};
|
|
3745
|
+
if (c.toGrant >= c.window / 4) {
|
|
3746
|
+
send();
|
|
3747
|
+
return;
|
|
3748
|
+
}
|
|
3749
|
+
if (c.grantScheduled) return;
|
|
3750
|
+
c.grantScheduled = true;
|
|
3751
|
+
setTimeout(send, 0);
|
|
3752
|
+
};
|
|
3753
|
+
const attach = (id, meta, portWindow, opener, exact) => {
|
|
3754
|
+
const c = typeof portWindow === "number" && Number.isSafeInteger(portWindow) && portWindow > 0 ? {
|
|
3755
|
+
window: portWindow,
|
|
3756
|
+
enforced: false,
|
|
3757
|
+
opener,
|
|
3758
|
+
exact,
|
|
3759
|
+
outstanding: 0,
|
|
3760
|
+
queue: [],
|
|
3761
|
+
queued: 0,
|
|
3762
|
+
unconsumed: 0,
|
|
3763
|
+
waiters: [],
|
|
3764
|
+
toGrant: 0,
|
|
3765
|
+
grantScheduled: false
|
|
3766
|
+
} : void 0;
|
|
3767
|
+
const self = newVirtualPort((payload, transfer) => {
|
|
3768
|
+
touch(id);
|
|
3769
|
+
const envelope = {
|
|
3770
|
+
type: "message",
|
|
3771
|
+
id,
|
|
3772
|
+
payload
|
|
3773
|
+
};
|
|
3774
|
+
if (!c) {
|
|
3775
|
+
post(envelope, transfer);
|
|
3776
|
+
return;
|
|
3777
|
+
}
|
|
3778
|
+
const bytes = c.exact ? codec.encode?.(envelope) : void 0;
|
|
3779
|
+
const size = bytes?.byteLength ?? estimateMessageSize(payload);
|
|
3780
|
+
const waits = frameSize != null || c.queue.length > 0 || c.enforced && c.outstanding > 0 && c.outstanding + size > c.window;
|
|
3781
|
+
const held = !bytes && waits ? {
|
|
3782
|
+
...envelope,
|
|
3783
|
+
payload: structuredClone(payload, { transfer })
|
|
3784
|
+
} : void 0;
|
|
3785
|
+
const send = () => {
|
|
3786
|
+
if (!bytes) {
|
|
3787
|
+
sendOnPort(id, held ? () => post(held) : () => post(envelope, transfer));
|
|
3788
|
+
return;
|
|
3789
|
+
}
|
|
3790
|
+
if (frameSize != null && c.enforced && c.exact && bytes.byteLength > frameSize) {
|
|
3791
|
+
for (let at = 0; at < bytes.byteLength; at += frameSize) {
|
|
3792
|
+
const data = bytes.subarray(at, at + frameSize);
|
|
3793
|
+
const frame = at + frameSize >= bytes.byteLength ? {
|
|
3794
|
+
type: "frame",
|
|
3795
|
+
id,
|
|
3796
|
+
data,
|
|
3797
|
+
last: true
|
|
3798
|
+
} : {
|
|
3799
|
+
type: "frame",
|
|
3800
|
+
id,
|
|
3801
|
+
data
|
|
3802
|
+
};
|
|
3803
|
+
sendOnPort(id, () => post(frame));
|
|
3804
|
+
}
|
|
3805
|
+
return;
|
|
3806
|
+
}
|
|
3807
|
+
sendOnPort(id, () => postBytes(bytes));
|
|
3808
|
+
};
|
|
3809
|
+
c.queue.push({
|
|
3810
|
+
send,
|
|
3811
|
+
size
|
|
3812
|
+
});
|
|
3813
|
+
c.queued += size;
|
|
3814
|
+
flush(c);
|
|
3815
|
+
}, (reason) => {
|
|
3816
|
+
release(reason);
|
|
3817
|
+
record.markClosed(reason);
|
|
3818
|
+
});
|
|
3819
|
+
function release(reason) {
|
|
3820
|
+
open.delete(id);
|
|
3821
|
+
leaks?.unregister(record);
|
|
3822
|
+
if (c?.queue.length) {
|
|
3823
|
+
for (const item of c.queue.splice(0)) item.send();
|
|
3824
|
+
c.queued = 0;
|
|
3825
|
+
}
|
|
3826
|
+
sendOnPort(id, () => post({
|
|
3519
3827
|
type: "close",
|
|
3520
3828
|
id,
|
|
3521
3829
|
reason
|
|
3830
|
+
}));
|
|
3831
|
+
releaseWaiters(c);
|
|
3832
|
+
}
|
|
3833
|
+
const { markClosed, isClosed, hasListeners } = self;
|
|
3834
|
+
if (c) {
|
|
3835
|
+
const deliver = self.deliver;
|
|
3836
|
+
self.deliver = (payload, size = estimateMessageSize(payload)) => {
|
|
3837
|
+
c.unconsumed += size;
|
|
3838
|
+
const done = deliver(payload);
|
|
3839
|
+
if (done) done.then(() => grant(id, c, size));
|
|
3840
|
+
else grant(id, c, size);
|
|
3841
|
+
return done;
|
|
3842
|
+
};
|
|
3843
|
+
setPortSendState(self.port, {
|
|
3844
|
+
bufferedAmount: () => c.queued,
|
|
3845
|
+
ready: (below = 0) => new Promise((resolve) => {
|
|
3846
|
+
if (c.queued <= below || isClosed()) nextTask(resolve);
|
|
3847
|
+
else c.waiters.push({
|
|
3848
|
+
below,
|
|
3849
|
+
resolve: () => nextTask(resolve)
|
|
3850
|
+
});
|
|
3851
|
+
})
|
|
3522
3852
|
});
|
|
3523
|
-
|
|
3524
|
-
|
|
3853
|
+
}
|
|
3854
|
+
const record = {
|
|
3855
|
+
deliver: self.deliver,
|
|
3856
|
+
markClosed,
|
|
3857
|
+
isClosed,
|
|
3858
|
+
hasListeners,
|
|
3859
|
+
close: (reason) => {
|
|
3860
|
+
if (isClosed()) return;
|
|
3861
|
+
release(reason);
|
|
3862
|
+
markClosed(reason);
|
|
3863
|
+
}
|
|
3864
|
+
};
|
|
3865
|
+
leaks?.register(self.port, {
|
|
3866
|
+
id,
|
|
3867
|
+
record,
|
|
3868
|
+
release
|
|
3869
|
+
}, record);
|
|
3870
|
+
const now = Date.now();
|
|
3871
|
+
open.set(id, {
|
|
3872
|
+
id,
|
|
3873
|
+
meta,
|
|
3874
|
+
openedAt: now,
|
|
3875
|
+
lastActivity: now,
|
|
3876
|
+
handle: record,
|
|
3877
|
+
credit: c
|
|
3525
3878
|
});
|
|
3526
|
-
open.set(id, self);
|
|
3527
3879
|
return self;
|
|
3528
3880
|
};
|
|
3529
|
-
const handleEnvelope = (envelope) => {
|
|
3881
|
+
const handleEnvelope = (envelope, event) => {
|
|
3530
3882
|
if (muxClosed) return;
|
|
3883
|
+
heardSincePing = true;
|
|
3884
|
+
if (envelope.type === "ping" || envelope.type === "pong") {
|
|
3885
|
+
peerKeepsAlive = true;
|
|
3886
|
+
if (envelope.type === "ping") post({
|
|
3887
|
+
type: "pong",
|
|
3888
|
+
id: KEEPALIVE_ID
|
|
3889
|
+
});
|
|
3890
|
+
return;
|
|
3891
|
+
}
|
|
3531
3892
|
if (envelope.type === "open") {
|
|
3532
3893
|
if (open.has(envelope.id)) return;
|
|
3533
3894
|
if (open.size >= maxPorts) {
|
|
@@ -3538,7 +3899,18 @@ function multiplexPort(port, options) {
|
|
|
3538
3899
|
});
|
|
3539
3900
|
return;
|
|
3540
3901
|
}
|
|
3541
|
-
const handle = attach(envelope.id);
|
|
3902
|
+
const handle = attach(envelope.id, envelope.meta, envelope.window, false, envelope.exact === true && exactCodec);
|
|
3903
|
+
const credit = open.get(envelope.id)?.credit;
|
|
3904
|
+
if (credit) post(credit.exact ? {
|
|
3905
|
+
type: "credit",
|
|
3906
|
+
id: envelope.id,
|
|
3907
|
+
bytes: 0,
|
|
3908
|
+
exact: true
|
|
3909
|
+
} : {
|
|
3910
|
+
type: "credit",
|
|
3911
|
+
id: envelope.id,
|
|
3912
|
+
bytes: 0
|
|
3913
|
+
});
|
|
3542
3914
|
let accepted = false;
|
|
3543
3915
|
if (onPort) try {
|
|
3544
3916
|
accepted = onPort(handle.port, envelope.meta) !== false;
|
|
@@ -3553,57 +3925,191 @@ function multiplexPort(port, options) {
|
|
|
3553
3925
|
id: envelope.id,
|
|
3554
3926
|
reason: "rejected"
|
|
3555
3927
|
});
|
|
3928
|
+
return;
|
|
3556
3929
|
}
|
|
3557
3930
|
return;
|
|
3558
3931
|
}
|
|
3559
|
-
const
|
|
3932
|
+
const entry = open.get(envelope.id);
|
|
3933
|
+
if (envelope.type === "credit") {
|
|
3934
|
+
const c = entry?.credit;
|
|
3935
|
+
if (!c || !(envelope.bytes >= 0)) return;
|
|
3936
|
+
if (!c.enforced && c.opener) {
|
|
3937
|
+
if (c.exact && envelope.exact !== true) {
|
|
3938
|
+
c.exact = false;
|
|
3939
|
+
c.outstanding = 0;
|
|
3940
|
+
}
|
|
3941
|
+
post({
|
|
3942
|
+
type: "credit",
|
|
3943
|
+
id: envelope.id,
|
|
3944
|
+
bytes: 0
|
|
3945
|
+
});
|
|
3946
|
+
}
|
|
3947
|
+
c.enforced = true;
|
|
3948
|
+
c.outstanding = Math.max(0, c.outstanding - envelope.bytes);
|
|
3949
|
+
flush(c);
|
|
3950
|
+
return;
|
|
3951
|
+
}
|
|
3560
3952
|
if (envelope.type === "message") {
|
|
3561
|
-
|
|
3562
|
-
|
|
3953
|
+
const sizeOf = (c) => (c.exact ? byteLengthOf(event.data) : void 0) ?? estimateMessageSize(envelope.payload);
|
|
3954
|
+
if (!entry) return;
|
|
3955
|
+
touch(envelope.id);
|
|
3956
|
+
entry.handle.deliver(envelope.payload, entry.credit ? sizeOf(entry.credit) : void 0);
|
|
3563
3957
|
return;
|
|
3564
3958
|
}
|
|
3565
|
-
if (
|
|
3959
|
+
if (envelope.type === "frame") {
|
|
3960
|
+
if (!entry?.credit?.exact || !codec.decode || !(envelope.data instanceof Uint8Array)) return;
|
|
3961
|
+
touch(envelope.id);
|
|
3962
|
+
const partial = entry.partial ?? {
|
|
3963
|
+
chunks: [],
|
|
3964
|
+
bytes: 0
|
|
3965
|
+
};
|
|
3966
|
+
entry.partial = partial;
|
|
3967
|
+
partial.chunks.push(envelope.data);
|
|
3968
|
+
partial.bytes += envelope.data.byteLength;
|
|
3969
|
+
if (!envelope.last) return;
|
|
3970
|
+
entry.partial = void 0;
|
|
3971
|
+
const whole = new Uint8Array(partial.bytes);
|
|
3972
|
+
let at = 0;
|
|
3973
|
+
for (const chunk of partial.chunks) {
|
|
3974
|
+
whole.set(chunk, at);
|
|
3975
|
+
at += chunk.byteLength;
|
|
3976
|
+
}
|
|
3977
|
+
const inner = codec.decode(whole);
|
|
3978
|
+
if (inner?.type !== "message" || inner.id !== envelope.id) return;
|
|
3979
|
+
entry.handle.deliver(inner.payload, whole.byteLength);
|
|
3980
|
+
return;
|
|
3981
|
+
}
|
|
3982
|
+
dropOutgoing(envelope.id);
|
|
3983
|
+
if (!entry) return;
|
|
3566
3984
|
open.delete(envelope.id);
|
|
3567
|
-
|
|
3985
|
+
releaseWaiters(entry.credit);
|
|
3986
|
+
entry.handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
|
3568
3987
|
};
|
|
3569
3988
|
const listener = (event) => {
|
|
3570
3989
|
const envelope = codec.read(event);
|
|
3571
|
-
if (envelope) handleEnvelope(envelope);
|
|
3990
|
+
if (envelope) handleEnvelope(envelope, event);
|
|
3991
|
+
};
|
|
3992
|
+
let peerKeepsAlive = false;
|
|
3993
|
+
let heardSincePing = true;
|
|
3994
|
+
let unanswered = 0;
|
|
3995
|
+
let pinger;
|
|
3996
|
+
const tick = () => {
|
|
3997
|
+
if (!keepalive) return;
|
|
3998
|
+
const backlogged = (port.bufferedAmount ?? 0) > 0;
|
|
3999
|
+
if (heardSincePing) unanswered = 0;
|
|
4000
|
+
else if (peerKeepsAlive && !backlogged) unanswered++;
|
|
4001
|
+
if (unanswered >= misses) {
|
|
4002
|
+
close(/* @__PURE__ */ new Error(`webrun-rpc: the peer did not answer ${misses} keepalive pings, ${keepalive.intervalMs} ms apart`)).catch(() => {});
|
|
4003
|
+
return;
|
|
4004
|
+
}
|
|
4005
|
+
heardSincePing = false;
|
|
4006
|
+
post({
|
|
4007
|
+
type: "ping",
|
|
4008
|
+
id: KEEPALIVE_ID
|
|
4009
|
+
});
|
|
4010
|
+
};
|
|
4011
|
+
const close = async (reason) => {
|
|
4012
|
+
if (muxClosed) return;
|
|
4013
|
+
clearInterval(pinger);
|
|
4014
|
+
muxClosed = true;
|
|
4015
|
+
signal?.removeEventListener("abort", onTransportAbort);
|
|
4016
|
+
port.removeEventListener("message", listener);
|
|
4017
|
+
const closedNow = /* @__PURE__ */ new Set();
|
|
4018
|
+
for (const [id, { handle, credit }] of [...open]) {
|
|
4019
|
+
closedNow.add(id);
|
|
4020
|
+
codecPost({
|
|
4021
|
+
type: "close",
|
|
4022
|
+
id
|
|
4023
|
+
});
|
|
4024
|
+
open.delete(id);
|
|
4025
|
+
releaseWaiters(credit);
|
|
4026
|
+
handle.markClosed(reason ?? /* @__PURE__ */ new Error("webrun-rpc: the multiplexer was closed"));
|
|
4027
|
+
}
|
|
4028
|
+
for (const id of outgoing.keys()) if (!closedNow.has(id)) codecPost({
|
|
4029
|
+
type: "close",
|
|
4030
|
+
id
|
|
4031
|
+
});
|
|
4032
|
+
outgoing.clear();
|
|
4033
|
+
ring.length = 0;
|
|
4034
|
+
await port.close?.();
|
|
4035
|
+
};
|
|
4036
|
+
const onTransportAbort = () => {
|
|
4037
|
+
close(signal?.reason).catch(() => {});
|
|
3572
4038
|
};
|
|
3573
4039
|
port.addEventListener("message", listener);
|
|
3574
4040
|
port.start?.();
|
|
4041
|
+
if (keepalive) {
|
|
4042
|
+
pinger = setInterval(tick, keepalive.intervalMs);
|
|
4043
|
+
pinger.unref?.();
|
|
4044
|
+
}
|
|
4045
|
+
if (signal?.aborted) close(signal.reason).catch(() => {});
|
|
4046
|
+
else signal?.addEventListener("abort", onTransportAbort, { once: true });
|
|
3575
4047
|
return {
|
|
3576
4048
|
maxMessageSize,
|
|
3577
|
-
async openPort(meta) {
|
|
4049
|
+
async openPort(meta, { signal: portSignal } = {}) {
|
|
3578
4050
|
if (muxClosed) throw new Error("webrun-rpc: the multiplexer is closed");
|
|
3579
|
-
if (
|
|
4051
|
+
if (portSignal?.aborted) throw portSignal.reason;
|
|
4052
|
+
if (open.size >= maxPorts) throw new RangeError(`webrun-rpc: maxPorts (${maxPorts}) reached; open ports by kind: ${describeKinds(open.values())}`);
|
|
3580
4053
|
while (open.has(nextId)) nextId += 2;
|
|
3581
4054
|
const id = nextId;
|
|
3582
4055
|
nextId += 2;
|
|
3583
|
-
const handle = attach(id);
|
|
3584
|
-
post({
|
|
4056
|
+
const handle = attach(id, meta, window, true, exactCodec);
|
|
4057
|
+
post(window ? exactCodec ? {
|
|
4058
|
+
type: "open",
|
|
4059
|
+
id,
|
|
4060
|
+
meta,
|
|
4061
|
+
window,
|
|
4062
|
+
exact: true
|
|
4063
|
+
} : {
|
|
4064
|
+
type: "open",
|
|
4065
|
+
id,
|
|
4066
|
+
meta,
|
|
4067
|
+
window
|
|
4068
|
+
} : {
|
|
3585
4069
|
type: "open",
|
|
3586
4070
|
id,
|
|
3587
4071
|
meta
|
|
3588
4072
|
});
|
|
4073
|
+
if (portSignal) {
|
|
4074
|
+
const record = open.get(id)?.handle;
|
|
4075
|
+
const onAbort = () => {
|
|
4076
|
+
if (record && open.get(id)?.handle === record) record.close(portSignal.reason);
|
|
4077
|
+
};
|
|
4078
|
+
portSignal.addEventListener("abort", onAbort, { once: true });
|
|
4079
|
+
getPortCloseSignal(handle.port)?.addEventListener("abort", () => portSignal.removeEventListener("abort", onAbort), { once: true });
|
|
4080
|
+
}
|
|
3589
4081
|
return handle.port;
|
|
3590
4082
|
},
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
}
|
|
3601
|
-
muxClosed = true;
|
|
3602
|
-
port.removeEventListener("message", listener);
|
|
3603
|
-
await port.close?.();
|
|
4083
|
+
close,
|
|
4084
|
+
ports() {
|
|
4085
|
+
return [...open.values()].map(({ handle: _, credit, ...info }) => credit ? {
|
|
4086
|
+
...info,
|
|
4087
|
+
...credit.enforced ? { window: credit.window } : {},
|
|
4088
|
+
queued: credit.queued,
|
|
4089
|
+
outstanding: credit.outstanding,
|
|
4090
|
+
unconsumed: credit.unconsumed
|
|
4091
|
+
} : info);
|
|
3604
4092
|
}
|
|
3605
4093
|
};
|
|
3606
4094
|
}
|
|
4095
|
+
/** The byte length of a received message's data, or `undefined` if it is not bytes. */
|
|
4096
|
+
function byteLengthOf(data) {
|
|
4097
|
+
if (data instanceof ArrayBuffer || ArrayBuffer.isView(data)) return data.byteLength;
|
|
4098
|
+
}
|
|
4099
|
+
/**
|
|
4100
|
+
* `"http: 1000, ws: 24"`: the three commonest `meta.kind`s among `entries`, so
|
|
4101
|
+
* a full table names who filled it. A port without a kind counts as `"?"`.
|
|
4102
|
+
*/
|
|
4103
|
+
function describeKinds(entries) {
|
|
4104
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4105
|
+
for (const { meta } of entries) {
|
|
4106
|
+
const kind = meta?.kind;
|
|
4107
|
+
const name = typeof kind === "string" ? kind : "?";
|
|
4108
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
4109
|
+
}
|
|
4110
|
+
if (!counts.size) return "none";
|
|
4111
|
+
return [...counts].sort((x, y) => y[1] - x[1]).slice(0, 3).map(([name, n]) => `${name}: ${n}`).join(", ");
|
|
4112
|
+
}
|
|
3607
4113
|
/**
|
|
3608
4114
|
* Announced with every port this pair opens. Layer 1 never reads `meta` — it is
|
|
3609
4115
|
* here so a peer running something else over the same multiplexer can tell a
|
|
@@ -3710,7 +4216,7 @@ function isEnvelope(value) {
|
|
|
3710
4216
|
const candidate = value;
|
|
3711
4217
|
if (typeof candidate.id !== "number") return false;
|
|
3712
4218
|
if (!Number.isInteger(candidate.id) || candidate.id < 0) return false;
|
|
3713
|
-
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close";
|
|
4219
|
+
return candidate.type === "open" || candidate.type === "message" || candidate.type === "close" || candidate.type === "credit" || candidate.type === "frame" || candidate.type === "ping" || candidate.type === "pong";
|
|
3714
4220
|
}
|
|
3715
4221
|
/**
|
|
3716
4222
|
* For ports whose messages are structured values — a real `MessagePort`, a
|
|
@@ -3872,7 +4378,7 @@ function trackedLink(send, release) {
|
|
|
3872
4378
|
finish();
|
|
3873
4379
|
return response;
|
|
3874
4380
|
}
|
|
3875
|
-
return new Response(guardBody(response.body, lost, () => lostReason, finish), {
|
|
4381
|
+
return new Response(guardBody(response.body, lost, () => lostReason, finish, request.signal), {
|
|
3876
4382
|
status: response.status,
|
|
3877
4383
|
statusText: response.statusText,
|
|
3878
4384
|
headers: response.headers
|
|
@@ -3891,33 +4397,65 @@ function trackedLink(send, release) {
|
|
|
3891
4397
|
return asyncDisposable(link, () => link.close());
|
|
3892
4398
|
}
|
|
3893
4399
|
/**
|
|
3894
|
-
* A response body that reports its end (done, cancelled or
|
|
3895
|
-
* with the link's reason when the link is lost mid-stream.
|
|
4400
|
+
* A response body that reports its end (done, cancelled, failed or aborted) and
|
|
4401
|
+
* fails with the link's reason when the link is lost mid-stream. An abort of the
|
|
4402
|
+
* request's signal ends it at once, read or not: the exchange is over (the layer
|
|
4403
|
+
* below has given its port back), so the request leaves `inflight` then.
|
|
3896
4404
|
*/
|
|
3897
|
-
function guardBody(body, lost, reason, finish) {
|
|
4405
|
+
function guardBody(body, lost, reason, finish, signal) {
|
|
3898
4406
|
const reader = body.getReader();
|
|
4407
|
+
let controller;
|
|
4408
|
+
let ended = false;
|
|
4409
|
+
const end = () => {
|
|
4410
|
+
ended = true;
|
|
4411
|
+
signal?.removeEventListener("abort", onAbort);
|
|
4412
|
+
finish();
|
|
4413
|
+
};
|
|
4414
|
+
const onAbort = () => {
|
|
4415
|
+
if (ended) return;
|
|
4416
|
+
end();
|
|
4417
|
+
reader.cancel().catch(() => {});
|
|
4418
|
+
try {
|
|
4419
|
+
controller.error(abortError(signal));
|
|
4420
|
+
} catch {}
|
|
4421
|
+
};
|
|
3899
4422
|
return new ReadableStream({
|
|
4423
|
+
start(c) {
|
|
4424
|
+
controller = c;
|
|
4425
|
+
if (signal?.aborted) onAbort();
|
|
4426
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
4427
|
+
},
|
|
3900
4428
|
async pull(controller) {
|
|
3901
4429
|
try {
|
|
3902
4430
|
const { done, value } = await Promise.race([reader.read(), lost]);
|
|
4431
|
+
if (ended) return;
|
|
3903
4432
|
if (done) {
|
|
3904
|
-
|
|
4433
|
+
end();
|
|
3905
4434
|
controller.close();
|
|
3906
4435
|
return;
|
|
3907
4436
|
}
|
|
3908
4437
|
controller.enqueue(value);
|
|
3909
4438
|
} catch (error) {
|
|
3910
|
-
|
|
4439
|
+
if (ended) return;
|
|
4440
|
+
end();
|
|
3911
4441
|
reader.cancel().catch(() => {});
|
|
3912
4442
|
controller.error(reason() ?? error);
|
|
3913
4443
|
}
|
|
3914
4444
|
},
|
|
3915
4445
|
async cancel(why) {
|
|
3916
|
-
|
|
4446
|
+
end();
|
|
3917
4447
|
await reader.cancel(why).catch(() => {});
|
|
3918
4448
|
}
|
|
3919
4449
|
});
|
|
3920
4450
|
}
|
|
4451
|
+
/** The signal's reason as an `Error` (`AbortError` when it gave none). */
|
|
4452
|
+
function abortError(signal) {
|
|
4453
|
+
const why = signal.reason;
|
|
4454
|
+
if (why instanceof Error) return why;
|
|
4455
|
+
const err = new Error(why === void 0 ? "Aborted" : String(why));
|
|
4456
|
+
err.name = "AbortError";
|
|
4457
|
+
return err;
|
|
4458
|
+
}
|
|
3921
4459
|
//#endregion
|
|
3922
4460
|
//#region src/link/locks.ts
|
|
3923
4461
|
function lockManager() {
|
|
@@ -6638,6 +7176,8 @@ var WorkerLinkImpl = class {
|
|
|
6638
7176
|
_ownerLock;
|
|
6639
7177
|
_attaching;
|
|
6640
7178
|
_detached = false;
|
|
7179
|
+
/** The `seq` of the last link sent to the worker. */
|
|
7180
|
+
_sent = 0;
|
|
6641
7181
|
constructor(_adapter, _control, init) {
|
|
6642
7182
|
this._adapter = _adapter;
|
|
6643
7183
|
this._control = _control;
|
|
@@ -6649,6 +7189,7 @@ var WorkerLinkImpl = class {
|
|
|
6649
7189
|
_control.onmessage = (event) => {
|
|
6650
7190
|
const message = event.data;
|
|
6651
7191
|
if (message?.type === "relink") {
|
|
7192
|
+
if (message.seen !== void 0 && message.seen < this._sent) return;
|
|
6652
7193
|
this._adapter._poke(WAKE_MESSAGE);
|
|
6653
7194
|
this.attach().catch(() => {});
|
|
6654
7195
|
} else if (message?.type === "keepalive") this._adapter._poke(KEEPALIVE_MESSAGE);
|
|
@@ -6695,7 +7236,8 @@ var WorkerLinkImpl = class {
|
|
|
6695
7236
|
this._control.postMessage({
|
|
6696
7237
|
type: "link",
|
|
6697
7238
|
key: this.key,
|
|
6698
|
-
routerLock: reply.routerLock
|
|
7239
|
+
routerLock: reply.routerLock,
|
|
7240
|
+
seq: ++this._sent
|
|
6699
7241
|
}, [
|
|
6700
7242
|
down.port2,
|
|
6701
7243
|
ctl.port2,
|
|
@@ -6796,12 +7338,18 @@ function serveHttpInWorker(handler, options = {}) {
|
|
|
6796
7338
|
const ownerLock = newLockName("webrun-http-owner");
|
|
6797
7339
|
const held = holdLock(ownerLock);
|
|
6798
7340
|
const controls = /* @__PURE__ */ new Set();
|
|
7341
|
+
const seen = /* @__PURE__ */ new Map();
|
|
6799
7342
|
const tellPages = (message) => {
|
|
6800
7343
|
for (const control of controls) control.postMessage(message);
|
|
6801
7344
|
};
|
|
6802
7345
|
const endpoint = new HandlerEndpoint(handler, {
|
|
6803
7346
|
...options,
|
|
6804
|
-
requestRelink: () =>
|
|
7347
|
+
requestRelink: () => {
|
|
7348
|
+
for (const control of controls) control.postMessage({
|
|
7349
|
+
type: "relink",
|
|
7350
|
+
seen: seen.get(control) ?? 0
|
|
7351
|
+
});
|
|
7352
|
+
},
|
|
6805
7353
|
keepAlive: () => tellPages({ type: "keepalive" })
|
|
6806
7354
|
});
|
|
6807
7355
|
const onMessage = (event) => {
|
|
@@ -6813,6 +7361,7 @@ function serveHttpInWorker(handler, options = {}) {
|
|
|
6813
7361
|
control.onmessage = (message) => {
|
|
6814
7362
|
const msg = message.data;
|
|
6815
7363
|
if (msg?.type === "link") {
|
|
7364
|
+
if (msg.seq !== void 0) seen.set(control, msg.seq);
|
|
6816
7365
|
const [down, ctl, up] = message.ports;
|
|
6817
7366
|
if (down) endpoint.link(msg.key, {
|
|
6818
7367
|
down,
|
|
@@ -6853,6 +7402,7 @@ function serveHttpInWorker(handler, options = {}) {
|
|
|
6853
7402
|
await endpoint.close();
|
|
6854
7403
|
for (const control of controls) control.close();
|
|
6855
7404
|
controls.clear();
|
|
7405
|
+
seen.clear();
|
|
6856
7406
|
(await held)();
|
|
6857
7407
|
}
|
|
6858
7408
|
};
|