@statewalker/webrun-rpc 0.4.3 → 0.5.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/duplex-over-port.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +61 -24
- package/dist/multiplex-port.d.ts +5 -0
- package/dist/multiplex-port.d.ts.map +1 -1
- package/dist/virtual-port.d.ts +6 -2
- package/dist/virtual-port.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/duplex-over-port.ts +27 -0
- package/src/index.ts +1 -1
- package/src/multiplex-port.ts +9 -3
- package/src/virtual-port.ts +21 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"duplex-over-port.d.ts","sourceRoot":"","sources":["../src/duplex-over-port.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,MAAM,EAOZ,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"duplex-over-port.d.ts","sourceRoot":"","sources":["../src/duplex-over-port.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,MAAM,EAOZ,MAAM,6BAA6B,CAAC;AAIrC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGzD;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,4BAA4B,CAAC;AActD,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACpC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,GAAE,qBAA0B,GAAG,MAAM,CAE/F;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,qBAA0B,GAClC,MAAM,IAAI,CA8BZ"}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export * from "./io-send.js";
|
|
|
10
10
|
export * from "./listen-bidi.js";
|
|
11
11
|
export * from "./listen-port.js";
|
|
12
12
|
export * from "./message-target.js";
|
|
13
|
-
export { DEFAULT_MAX_PORTS, multiplexPort } from "./multiplex-port.js";
|
|
13
|
+
export { DEFAULT_MAX_PORTS, MUX_CLOSED_MESSAGE, multiplexPort } from "./multiplex-port.js";
|
|
14
14
|
export type { PortCodec, PortEnvelope, PortMux, PortMuxOptions } from "./port-types.js";
|
|
15
15
|
export * from "./recieve.js";
|
|
16
16
|
export * from "./send.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EACL,OAAO,EACP,KAAK,MAAM,EACX,KAAK,eAAe,EACpB,QAAQ,EACR,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,GACN,MAAM,oBAAoB,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAC/D,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAC3E,OAAO,EACL,OAAO,EACP,KAAK,MAAM,EACX,KAAK,eAAe,EACpB,QAAQ,EACR,SAAS,EACT,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,GACN,MAAM,oBAAoB,CAAC;AAC5B,cAAc,uBAAuB,CAAC;AACtC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC;AAC7B,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC3F,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACxF,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,cAAc,wBAAwB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -505,6 +505,7 @@ function duplexOverPort(port, options = {}) {
|
|
|
505
505
|
function serveDuplexOverPort(port, handler, options = {}) {
|
|
506
506
|
const controller = new AbortController();
|
|
507
507
|
const notice = installAbortNotice(port, controller);
|
|
508
|
+
abortOnPortClose(port, controller);
|
|
508
509
|
const clock = installStreamTimeout(controller, options.timeout);
|
|
509
510
|
const inbound = receiveChunks(port, CHANNEL_IN, controller, clock.touch);
|
|
510
511
|
let output;
|
|
@@ -559,6 +560,7 @@ function teardownOnce(controller, notice, clock, inbound, output) {
|
|
|
559
560
|
function runCallerSide(port, input, options) {
|
|
560
561
|
const controller = new AbortController();
|
|
561
562
|
const notice = installAbortNotice(port, controller);
|
|
563
|
+
abortOnPortClose(port, controller);
|
|
562
564
|
const clock = installStreamTimeout(controller, options.timeout);
|
|
563
565
|
const inbound = receiveChunks(port, CHANNEL_OUT, controller, clock.touch);
|
|
564
566
|
const pump = sendChunks(port, CHANNEL_IN, input, options, controller.signal, clock.touch);
|
|
@@ -607,6 +609,27 @@ function installAbortNotice(port, controller) {
|
|
|
607
609
|
};
|
|
608
610
|
}
|
|
609
611
|
/**
|
|
612
|
+
* Aborts the stream when its port closes underneath it — the multiplexer was
|
|
613
|
+
* closed, or the peer closed the port — for ports that report it (see
|
|
614
|
+
* `getPortCloseSignal`). Without this a stream waiting for the peer's next
|
|
615
|
+
* chunk would wait forever on a port that can no longer deliver one: the
|
|
616
|
+
* outbound half's `callPort` already fails on the close signal, but nothing
|
|
617
|
+
* failed the inbound half.
|
|
618
|
+
*/
|
|
619
|
+
function abortOnPortClose(port, controller) {
|
|
620
|
+
const closed = getPortCloseSignal(port);
|
|
621
|
+
if (!closed) return;
|
|
622
|
+
const onClose = () => {
|
|
623
|
+
if (!controller.signal.aborted) controller.abort(closed.reason);
|
|
624
|
+
};
|
|
625
|
+
if (closed.aborted) {
|
|
626
|
+
onClose();
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
closed.addEventListener("abort", onClose, { once: true });
|
|
630
|
+
controller.signal.addEventListener("abort", () => closed.removeEventListener("abort", onClose), { once: true });
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
610
633
|
* The per-stream inactivity timeout (spec D8). Reset by any chunk in either
|
|
611
634
|
* direction; elapsing aborts the stream. Unset, zero or non-finite installs no
|
|
612
635
|
* timer at all, which is the default: a slow consumer is throttled, not failed.
|
|
@@ -768,26 +791,34 @@ async function sendChunks(port, channelName, output, { maxMessageSize, log }, si
|
|
|
768
791
|
function newVirtualPort(send, requestClose) {
|
|
769
792
|
const listeners = /* @__PURE__ */ new Set();
|
|
770
793
|
let closed = false;
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
postMessage(message, transfer) {
|
|
780
|
-
if (closed) return;
|
|
781
|
-
send(message, transfer);
|
|
782
|
-
},
|
|
783
|
-
close() {
|
|
784
|
-
if (closed) return;
|
|
785
|
-
closed = true;
|
|
786
|
-
const notify = requestClose;
|
|
787
|
-
listeners.clear();
|
|
788
|
-
notify();
|
|
789
|
-
}
|
|
794
|
+
const closing = new AbortController();
|
|
795
|
+
const abort = (reason) => {
|
|
796
|
+
if (closing.signal.aborted) return;
|
|
797
|
+
closing.abort(reason ?? /* @__PURE__ */ new Error("webrun-rpc: the port was closed"));
|
|
798
|
+
};
|
|
799
|
+
const port = {
|
|
800
|
+
addEventListener(_type, listener) {
|
|
801
|
+
listeners.add(listener);
|
|
790
802
|
},
|
|
803
|
+
removeEventListener(_type, listener) {
|
|
804
|
+
listeners.delete(listener);
|
|
805
|
+
},
|
|
806
|
+
postMessage(message, transfer) {
|
|
807
|
+
if (closed) return;
|
|
808
|
+
send(message, transfer);
|
|
809
|
+
},
|
|
810
|
+
close() {
|
|
811
|
+
if (closed) return;
|
|
812
|
+
closed = true;
|
|
813
|
+
const notify = requestClose;
|
|
814
|
+
listeners.clear();
|
|
815
|
+
notify();
|
|
816
|
+
abort();
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
setPortCloseSignal(port, closing.signal);
|
|
820
|
+
return {
|
|
821
|
+
port,
|
|
791
822
|
deliver(payload) {
|
|
792
823
|
if (closed) return;
|
|
793
824
|
const event = new MessageEvent("message", { data: payload });
|
|
@@ -795,9 +826,10 @@ function newVirtualPort(send, requestClose) {
|
|
|
795
826
|
listener(event);
|
|
796
827
|
} catch {}
|
|
797
828
|
},
|
|
798
|
-
markClosed() {
|
|
829
|
+
markClosed(reason) {
|
|
799
830
|
closed = true;
|
|
800
831
|
listeners.clear();
|
|
832
|
+
abort(reason);
|
|
801
833
|
},
|
|
802
834
|
isClosed() {
|
|
803
835
|
return closed;
|
|
@@ -806,6 +838,11 @@ function newVirtualPort(send, requestClose) {
|
|
|
806
838
|
}
|
|
807
839
|
//#endregion
|
|
808
840
|
//#region src/multiplex-port.ts
|
|
841
|
+
/**
|
|
842
|
+
* The message of the error that fails the calls still in flight on a
|
|
843
|
+
* multiplexer when it is closed (`PortMux.close()`).
|
|
844
|
+
*/
|
|
845
|
+
const MUX_CLOSED_MESSAGE = "webrun-rpc: the multiplexer was closed";
|
|
809
846
|
/** Ceiling on concurrently open virtual ports. Bounds the id table only. */
|
|
810
847
|
const DEFAULT_MAX_PORTS = 1024;
|
|
811
848
|
/**
|
|
@@ -864,7 +901,7 @@ function multiplexPort(port, options) {
|
|
|
864
901
|
}
|
|
865
902
|
if (!accepted) {
|
|
866
903
|
open.delete(envelope.id);
|
|
867
|
-
handle.markClosed();
|
|
904
|
+
handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the port was rejected"));
|
|
868
905
|
post({
|
|
869
906
|
type: "close",
|
|
870
907
|
id: envelope.id,
|
|
@@ -881,7 +918,7 @@ function multiplexPort(port, options) {
|
|
|
881
918
|
}
|
|
882
919
|
if (!handle) return;
|
|
883
920
|
open.delete(envelope.id);
|
|
884
|
-
handle.markClosed();
|
|
921
|
+
handle.markClosed(/* @__PURE__ */ new Error("webrun-rpc: the peer closed the port"));
|
|
885
922
|
};
|
|
886
923
|
const listener = (event) => {
|
|
887
924
|
const envelope = codec.read(event);
|
|
@@ -913,7 +950,7 @@ function multiplexPort(port, options) {
|
|
|
913
950
|
id
|
|
914
951
|
});
|
|
915
952
|
open.delete(id);
|
|
916
|
-
handle.markClosed();
|
|
953
|
+
handle.markClosed(/* @__PURE__ */ new Error(MUX_CLOSED_MESSAGE));
|
|
917
954
|
}
|
|
918
955
|
muxClosed = true;
|
|
919
956
|
port.removeEventListener("message", listener);
|
|
@@ -1177,4 +1214,4 @@ function transferPortMux(target, options = {}) {
|
|
|
1177
1214
|
};
|
|
1178
1215
|
}
|
|
1179
1216
|
//#endregion
|
|
1180
|
-
export { CANCEL_CHANNEL_TYPE, DEFAULT_MAX_PORTS, NO_TIMEOUT, PORT_TRANSFER, STREAM_ABORT, byteChannelFromMessagePort, callBidi, callPort, connect, duplexOverPort, getPortCloseSignal, ioHandle, ioSend, listenBidi, listenCancelChannel, listenPort, multiplexPort, overPipe, overPorts, postCancelChannel, recieve, send, serve, serveDuplexOverPort, setPortCloseSignal, structuredCodec, transferPortMux };
|
|
1217
|
+
export { CANCEL_CHANNEL_TYPE, DEFAULT_MAX_PORTS, MUX_CLOSED_MESSAGE, NO_TIMEOUT, PORT_TRANSFER, STREAM_ABORT, byteChannelFromMessagePort, callBidi, callPort, connect, duplexOverPort, getPortCloseSignal, ioHandle, ioSend, listenBidi, listenCancelChannel, listenPort, multiplexPort, overPipe, overPorts, postCancelChannel, recieve, send, serve, serveDuplexOverPort, setPortCloseSignal, structuredCodec, transferPortMux };
|
package/dist/multiplex-port.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { MessageTarget } from "./message-target.js";
|
|
2
2
|
import type { PortMux, PortMuxOptions } from "./port-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* The message of the error that fails the calls still in flight on a
|
|
5
|
+
* multiplexer when it is closed (`PortMux.close()`).
|
|
6
|
+
*/
|
|
7
|
+
export declare const MUX_CLOSED_MESSAGE = "webrun-rpc: the multiplexer was closed";
|
|
3
8
|
/** Ceiling on concurrently open virtual ports. Bounds the id table only. */
|
|
4
9
|
export declare const DEFAULT_MAX_PORTS = 1024;
|
|
5
10
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multiplex-port.d.ts","sourceRoot":"","sources":["../src/multiplex-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAgB,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG7E,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAwHnF"}
|
|
1
|
+
{"version":3,"file":"multiplex-port.d.ts","sourceRoot":"","sources":["../src/multiplex-port.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,KAAK,EAAgB,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG7E;;;GAGG;AACH,eAAO,MAAM,kBAAkB,2CAA2C,CAAC;AAE3E,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB,OAAO,CAAC;AAEtC;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAwHnF"}
|
package/dist/virtual-port.d.ts
CHANGED
|
@@ -4,8 +4,12 @@ export interface VirtualPortHandle {
|
|
|
4
4
|
port: MessageTarget;
|
|
5
5
|
/** Multiplexer-only: hand an inbound payload to the consumer's listeners. */
|
|
6
6
|
deliver(payload: unknown): void;
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Multiplexer-only: the port is finished; drop listeners and go inert.
|
|
9
|
+
* `reason` becomes the reason of the port's close signal (see
|
|
10
|
+
* `getPortCloseSignal`), which is what fails the calls still waiting on it.
|
|
11
|
+
*/
|
|
12
|
+
markClosed(reason?: unknown): void;
|
|
9
13
|
isClosed(): boolean;
|
|
10
14
|
}
|
|
11
15
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"virtual-port.d.ts","sourceRoot":"","sources":["../src/virtual-port.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"virtual-port.d.ts","sourceRoot":"","sources":["../src/virtual-port.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAE1E,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,IAAI,EAAE,aAAa,CAAC;IACpB,6EAA6E;IAC7E,OAAO,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IAChC;;;;OAIG;IACH,UAAU,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACnC,QAAQ,IAAI,OAAO,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,KAAK,IAAI,EAC3D,YAAY,EAAE,CAAC,MAAM,CAAC,EAAE,OAAO,KAAK,IAAI,GACvC,iBAAiB,CA6DnB"}
|
package/package.json
CHANGED
package/src/duplex-over-port.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
toChunks,
|
|
10
10
|
} from "@statewalker/webrun-streams";
|
|
11
11
|
import { callPort, NO_TIMEOUT } from "./call-port.js";
|
|
12
|
+
import { getPortCloseSignal } from "./close-signal.js";
|
|
12
13
|
import { listenPort } from "./listen-port.js";
|
|
13
14
|
import type { MessageTarget } from "./message-target.js";
|
|
14
15
|
import { throughAbort } from "./through-abort.js";
|
|
@@ -82,6 +83,7 @@ export function serveDuplexOverPort(
|
|
|
82
83
|
): () => void {
|
|
83
84
|
const controller = new AbortController();
|
|
84
85
|
const notice = installAbortNotice(port, controller);
|
|
86
|
+
abortOnPortClose(port, controller);
|
|
85
87
|
const clock = installStreamTimeout(controller, options.timeout);
|
|
86
88
|
const inbound = receiveChunks(port, CHANNEL_IN, controller, clock.touch);
|
|
87
89
|
let output: AsyncGenerator<Uint8Array>;
|
|
@@ -166,6 +168,7 @@ function runCallerSide(
|
|
|
166
168
|
): AsyncGenerator<Uint8Array> {
|
|
167
169
|
const controller = new AbortController();
|
|
168
170
|
const notice = installAbortNotice(port, controller);
|
|
171
|
+
abortOnPortClose(port, controller);
|
|
169
172
|
const clock = installStreamTimeout(controller, options.timeout);
|
|
170
173
|
const inbound = receiveChunks(port, CHANNEL_OUT, controller, clock.touch);
|
|
171
174
|
const pump = sendChunks(port, CHANNEL_IN, input, options, controller.signal, clock.touch);
|
|
@@ -234,6 +237,30 @@ function installAbortNotice(
|
|
|
234
237
|
};
|
|
235
238
|
}
|
|
236
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Aborts the stream when its port closes underneath it — the multiplexer was
|
|
242
|
+
* closed, or the peer closed the port — for ports that report it (see
|
|
243
|
+
* `getPortCloseSignal`). Without this a stream waiting for the peer's next
|
|
244
|
+
* chunk would wait forever on a port that can no longer deliver one: the
|
|
245
|
+
* outbound half's `callPort` already fails on the close signal, but nothing
|
|
246
|
+
* failed the inbound half.
|
|
247
|
+
*/
|
|
248
|
+
function abortOnPortClose(port: MessageTarget, controller: AbortController): void {
|
|
249
|
+
const closed = getPortCloseSignal(port);
|
|
250
|
+
if (!closed) return;
|
|
251
|
+
const onClose = () => {
|
|
252
|
+
if (!controller.signal.aborted) controller.abort(closed.reason);
|
|
253
|
+
};
|
|
254
|
+
if (closed.aborted) {
|
|
255
|
+
onClose();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
closed.addEventListener("abort", onClose, { once: true });
|
|
259
|
+
controller.signal.addEventListener("abort", () => closed.removeEventListener("abort", onClose), {
|
|
260
|
+
once: true,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
237
264
|
/**
|
|
238
265
|
* The per-stream inactivity timeout (spec D8). Reset by any chunk in either
|
|
239
266
|
* direction; elapsing aborts the stream. Unset, zero or non-finite installs no
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ export * from "./io-send.js";
|
|
|
21
21
|
export * from "./listen-bidi.js";
|
|
22
22
|
export * from "./listen-port.js";
|
|
23
23
|
export * from "./message-target.js";
|
|
24
|
-
export { DEFAULT_MAX_PORTS, multiplexPort } from "./multiplex-port.js";
|
|
24
|
+
export { DEFAULT_MAX_PORTS, MUX_CLOSED_MESSAGE, multiplexPort } from "./multiplex-port.js";
|
|
25
25
|
export type { PortCodec, PortEnvelope, PortMux, PortMuxOptions } from "./port-types.js";
|
|
26
26
|
export * from "./recieve.js";
|
|
27
27
|
export * from "./send.js";
|
package/src/multiplex-port.ts
CHANGED
|
@@ -2,6 +2,12 @@ import type { MessageTarget } from "./message-target.js";
|
|
|
2
2
|
import type { PortEnvelope, PortMux, PortMuxOptions } from "./port-types.js";
|
|
3
3
|
import { newVirtualPort, type VirtualPortHandle } from "./virtual-port.js";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* The message of the error that fails the calls still in flight on a
|
|
7
|
+
* multiplexer when it is closed (`PortMux.close()`).
|
|
8
|
+
*/
|
|
9
|
+
export const MUX_CLOSED_MESSAGE = "webrun-rpc: the multiplexer was closed";
|
|
10
|
+
|
|
5
11
|
/** Ceiling on concurrently open virtual ports. Bounds the id table only. */
|
|
6
12
|
export const DEFAULT_MAX_PORTS = 1024;
|
|
7
13
|
|
|
@@ -70,7 +76,7 @@ export function multiplexPort(port: MessageTarget, options: PortMuxOptions): Por
|
|
|
70
76
|
}
|
|
71
77
|
if (!accepted) {
|
|
72
78
|
open.delete(envelope.id);
|
|
73
|
-
handle.markClosed();
|
|
79
|
+
handle.markClosed(new Error("webrun-rpc: the port was rejected"));
|
|
74
80
|
post({ type: "close", id: envelope.id, reason: "rejected" });
|
|
75
81
|
}
|
|
76
82
|
return;
|
|
@@ -89,7 +95,7 @@ export function multiplexPort(port: MessageTarget, options: PortMuxOptions): Por
|
|
|
89
95
|
|
|
90
96
|
if (!handle) return;
|
|
91
97
|
open.delete(envelope.id);
|
|
92
|
-
handle.markClosed();
|
|
98
|
+
handle.markClosed(new Error("webrun-rpc: the peer closed the port"));
|
|
93
99
|
};
|
|
94
100
|
|
|
95
101
|
const listener = (event: MessageEvent): void => {
|
|
@@ -124,7 +130,7 @@ export function multiplexPort(port: MessageTarget, options: PortMuxOptions): Por
|
|
|
124
130
|
for (const [id, handle] of [...open]) {
|
|
125
131
|
post({ type: "close", id });
|
|
126
132
|
open.delete(id);
|
|
127
|
-
handle.markClosed();
|
|
133
|
+
handle.markClosed(new Error(MUX_CLOSED_MESSAGE));
|
|
128
134
|
}
|
|
129
135
|
muxClosed = true;
|
|
130
136
|
port.removeEventListener("message", listener);
|
package/src/virtual-port.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setPortCloseSignal } from "./close-signal.js";
|
|
1
2
|
import type { MessageListener, MessageTarget } from "./message-target.js";
|
|
2
3
|
|
|
3
4
|
export interface VirtualPortHandle {
|
|
@@ -5,8 +6,12 @@ export interface VirtualPortHandle {
|
|
|
5
6
|
port: MessageTarget;
|
|
6
7
|
/** Multiplexer-only: hand an inbound payload to the consumer's listeners. */
|
|
7
8
|
deliver(payload: unknown): void;
|
|
8
|
-
/**
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Multiplexer-only: the port is finished; drop listeners and go inert.
|
|
11
|
+
* `reason` becomes the reason of the port's close signal (see
|
|
12
|
+
* `getPortCloseSignal`), which is what fails the calls still waiting on it.
|
|
13
|
+
*/
|
|
14
|
+
markClosed(reason?: unknown): void;
|
|
10
15
|
isClosed(): boolean;
|
|
11
16
|
}
|
|
12
17
|
|
|
@@ -23,6 +28,16 @@ export function newVirtualPort(
|
|
|
23
28
|
): VirtualPortHandle {
|
|
24
29
|
const listeners = new Set<MessageListener>();
|
|
25
30
|
let closed = false;
|
|
31
|
+
// A virtual port knows when it is finished, unlike a raw `MessagePort`. Its
|
|
32
|
+
// close signal is what lets the calls still waiting on it — `callPort`, and
|
|
33
|
+
// the streams of `duplexOverPort` — fail at once instead of hanging on a
|
|
34
|
+
// port that has gone inert. Without it, closing a multiplexer left every call
|
|
35
|
+
// in flight on it pending forever.
|
|
36
|
+
const closing = new AbortController();
|
|
37
|
+
const abort = (reason?: unknown) => {
|
|
38
|
+
if (closing.signal.aborted) return;
|
|
39
|
+
closing.abort(reason ?? new Error("webrun-rpc: the port was closed"));
|
|
40
|
+
};
|
|
26
41
|
|
|
27
42
|
const port: MessageTarget = {
|
|
28
43
|
addEventListener(_type, listener) {
|
|
@@ -41,8 +56,10 @@ export function newVirtualPort(
|
|
|
41
56
|
const notify = requestClose;
|
|
42
57
|
listeners.clear();
|
|
43
58
|
notify();
|
|
59
|
+
abort();
|
|
44
60
|
},
|
|
45
61
|
};
|
|
62
|
+
setPortCloseSignal(port, closing.signal);
|
|
46
63
|
|
|
47
64
|
return {
|
|
48
65
|
port,
|
|
@@ -60,9 +77,10 @@ export function newVirtualPort(
|
|
|
60
77
|
}
|
|
61
78
|
}
|
|
62
79
|
},
|
|
63
|
-
markClosed() {
|
|
80
|
+
markClosed(reason) {
|
|
64
81
|
closed = true;
|
|
65
82
|
listeners.clear();
|
|
83
|
+
abort(reason);
|
|
66
84
|
},
|
|
67
85
|
isClosed() {
|
|
68
86
|
return closed;
|