@statewalker/webrun-streams-webrtc 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -333,7 +333,8 @@ const claimed = /* @__PURE__ */ new WeakSet();
333
333
  * ```
334
334
  *
335
335
  * Messages that arrive before a port's first listener are held for it and delivered after that
336
- * tick, to every listener added by then.
336
+ * tick, to every listener added by then. Messages posted before a port's channel opens are queued
337
+ * and sent, in order, when it opens.
337
338
  */
338
339
  async function webrtcPortMux(options) {
339
340
  const { pc, codec, onPort } = options;
@@ -376,9 +377,26 @@ async function webrtcPortMux(options) {
376
377
  if (held) held.push(data);
377
378
  else deliver(data);
378
379
  };
380
+ let queued = dc.readyState === "connecting" ? [] : void 0;
381
+ const send = (bytes) => {
382
+ try {
383
+ dc.send(bytes);
384
+ } catch (err) {
385
+ end(err instanceof Error ? err : new Error(String(err)));
386
+ }
387
+ };
388
+ const onOpen = () => {
389
+ const pending = queued;
390
+ queued = void 0;
391
+ for (const bytes of pending ?? []) {
392
+ if (closeSignal.aborted) return;
393
+ send(bytes);
394
+ }
395
+ };
379
396
  const onClose = () => end(/* @__PURE__ */ new Error("webrun-streams-webrtc: the data channel closed"));
380
397
  const onError = (ev) => end(/* @__PURE__ */ new Error(`webrun-streams-webrtc: the data channel failed: ${ev.error?.message ?? "unknown"}`));
381
398
  dc.addEventListener("message", onMessage);
399
+ dc.addEventListener("open", onOpen);
382
400
  dc.addEventListener("close", onClose);
383
401
  dc.addEventListener("error", onError);
384
402
  if (dc.readyState === "closing" || dc.readyState === "closed") onClose();
@@ -386,6 +404,8 @@ async function webrtcPortMux(options) {
386
404
  const cleanUp = () => {
387
405
  live.delete(port);
388
406
  dc.removeEventListener("message", onMessage);
407
+ dc.removeEventListener("open", onOpen);
408
+ queued = void 0;
389
409
  listeners.clear();
390
410
  held = void 0;
391
411
  if (dc.readyState !== "closed") dc.close();
@@ -408,18 +428,16 @@ async function webrtcPortMux(options) {
408
428
  });
409
429
  },
410
430
  postMessage(message) {
411
- if (closeSignal.aborted || dc.readyState !== "open") return;
431
+ if (closeSignal.aborted) return;
432
+ if (!queued && dc.readyState !== "open") return;
412
433
  const bytes = codec.encode(message);
413
434
  if (bytes.byteLength > maxFrameBytes) {
414
435
  const err = /* @__PURE__ */ new RangeError(`webrun-streams-webrtc: a ${bytes.byteLength}-byte message exceeds maxFrameBytes (${maxFrameBytes})`);
415
436
  end(err);
416
437
  throw err;
417
438
  }
418
- try {
419
- dc.send(bytes);
420
- } catch (err) {
421
- end(err instanceof Error ? err : new Error(String(err)));
422
- }
439
+ if (queued) queued.push(bytes);
440
+ else send(bytes);
423
441
  },
424
442
  addEventListener(_type, listener) {
425
443
  listeners.add(listener);
@@ -67,7 +67,8 @@ export interface WebRtcPortMux {
67
67
  * ```
68
68
  *
69
69
  * Messages that arrive before a port's first listener are held for it and delivered after that
70
- * tick, to every listener added by then.
70
+ * tick, to every listener added by then. Messages posted before a port's channel opens are queued
71
+ * and sent, in order, when it opens.
71
72
  */
72
73
  export declare function webrtcPortMux(options: WebRtcPortMuxOptions): Promise<WebRtcPortMux>;
73
74
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"port-mux.d.ts","sourceRoot":"","sources":["../src/port-mux.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,QAAY,CAAC;AAEtD,sHAAsH;AACtH,eAAO,MAAM,6BAA6B,QAAqC,CAAC;AAEhF,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,QAAa,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAAC;IACnC,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC;CACpC;AAED,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9D,4FAA4F;AAC5F,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5D,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/D,+EAA+E;IAC/E,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,wFAAwF;IACxF,EAAE,EAAE,iBAAiB,CAAC;IACtB,kDAAkD;IAClD,KAAK,EAAE,UAAU,CAAC;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,OAAO,GAAG,SAAS,CAAC;IACxD,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACvF,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC,CA6MzF"}
1
+ {"version":3,"file":"port-mux.d.ts","sourceRoot":"","sources":["../src/port-mux.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,eAAO,MAAM,iBAAiB,iBAAiB,CAAC;AAEhD;;;GAGG;AACH,eAAO,MAAM,4BAA4B,QAAY,CAAC;AAEtD,sHAAsH;AACtH,eAAO,MAAM,6BAA6B,QAAqC,CAAC;AAEhF,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,QAAa,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAAC;IACnC,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC;CACpC;AAED,KAAK,QAAQ,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAE9D,4FAA4F;AAC5F,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC5D,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC/D,+EAA+E;IAC/E,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;CACnC;AAED,MAAM,WAAW,oBAAoB;IACnC,wFAAwF;IACxF,EAAE,EAAE,iBAAiB,CAAC;IACtB,kDAAkD;IAClD,KAAK,EAAE,UAAU,CAAC;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,eAAe,KAAK,OAAO,GAAG,SAAS,CAAC;IACxD,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC;IACvF,KAAK,CAAC,MAAM,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAMD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,aAAa,CAAC,CAkOzF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@statewalker/webrun-streams-webrtc",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "WebRTC DataChannel-backed Connect/Serve adapter (native multi-stream) in the webrun-streams-* family",
@@ -43,8 +43,8 @@
43
43
  "typescript": "^7.0.2",
44
44
  "vitest": "^5.0.5",
45
45
  "@statewalker/webrun-msgpack": "^0.11.0",
46
- "@statewalker/webrun-rpc": "^0.12.0",
47
- "@statewalker/webrun-streams-conformance": "^0.5.0"
46
+ "@statewalker/webrun-streams-conformance": "^0.5.0",
47
+ "@statewalker/webrun-rpc": "^0.12.0"
48
48
  },
49
49
  "sideEffects": false,
50
50
  "publishConfig": {
package/src/port-mux.ts CHANGED
@@ -78,7 +78,8 @@ const claimed = new WeakSet<RTCDataChannel>();
78
78
  * ```
79
79
  *
80
80
  * Messages that arrive before a port's first listener are held for it and delivered after that
81
- * tick, to every listener added by then.
81
+ * tick, to every listener added by then. Messages posted before a port's channel opens are queued
82
+ * and sent, in order, when it opens.
82
83
  */
83
84
  export async function webrtcPortMux(options: WebRtcPortMuxOptions): Promise<WebRtcPortMux> {
84
85
  const { pc, codec, onPort } = options;
@@ -132,6 +133,26 @@ export async function webrtcPortMux(options: WebRtcPortMuxOptions): Promise<WebR
132
133
  if (held) held.push(data);
133
134
  else deliver(data);
134
135
  };
136
+ // Posts made while the channel is still connecting, sent in order when it opens, as a
137
+ // MessagePort queues them (statewalker/statewalker-root#57). `undefined` once the channel has
138
+ // opened, or when it was open already.
139
+ let queued: Uint8Array[] | undefined = dc.readyState === "connecting" ? [] : undefined;
140
+ const send = (bytes: Uint8Array) => {
141
+ try {
142
+ dc.send(bytes as Uint8Array<ArrayBuffer>);
143
+ } catch (err) {
144
+ // a send queue full, or a channel closing meanwhile: the port is broken
145
+ end(err instanceof Error ? err : new Error(String(err)));
146
+ }
147
+ };
148
+ const onOpen = () => {
149
+ const pending = queued;
150
+ queued = undefined;
151
+ for (const bytes of pending ?? []) {
152
+ if (closeSignal.aborted) return;
153
+ send(bytes);
154
+ }
155
+ };
135
156
  const onClose = () => end(new Error("webrun-streams-webrtc: the data channel closed"));
136
157
  const onError = (ev: Event) =>
137
158
  end(
@@ -140,6 +161,7 @@ export async function webrtcPortMux(options: WebRtcPortMuxOptions): Promise<WebR
140
161
  ),
141
162
  );
142
163
  dc.addEventListener("message", onMessage);
164
+ dc.addEventListener("open", onOpen);
143
165
  dc.addEventListener("close", onClose);
144
166
  dc.addEventListener("error", onError);
145
167
  if (dc.readyState === "closing" || dc.readyState === "closed") onClose();
@@ -147,6 +169,8 @@ export async function webrtcPortMux(options: WebRtcPortMuxOptions): Promise<WebR
147
169
  const cleanUp = () => {
148
170
  live.delete(port);
149
171
  dc.removeEventListener("message", onMessage);
172
+ dc.removeEventListener("open", onOpen);
173
+ queued = undefined;
150
174
  listeners.clear();
151
175
  held = undefined;
152
176
  if (dc.readyState !== "closed") dc.close();
@@ -170,7 +194,9 @@ export async function webrtcPortMux(options: WebRtcPortMuxOptions): Promise<WebR
170
194
  });
171
195
  },
172
196
  postMessage(message) {
173
- if (closeSignal.aborted || dc.readyState !== "open") return; // `closeSignal` says so
197
+ if (closeSignal.aborted) return; // `closeSignal` says so
198
+ // a channel closing or closed: its close event aborts `closeSignal`
199
+ if (!queued && dc.readyState !== "open") return;
174
200
  const bytes = codec.encode(message);
175
201
  if (bytes.byteLength > maxFrameBytes) {
176
202
  const err = new RangeError(
@@ -179,12 +205,8 @@ export async function webrtcPortMux(options: WebRtcPortMuxOptions): Promise<WebR
179
205
  end(err);
180
206
  throw err;
181
207
  }
182
- try {
183
- dc.send(bytes as Uint8Array<ArrayBuffer>);
184
- } catch (err) {
185
- // a send queue full, or a channel closing meanwhile: the port is broken
186
- end(err instanceof Error ? err : new Error(String(err)));
187
- }
208
+ if (queued) queued.push(bytes);
209
+ else send(bytes);
188
210
  },
189
211
  addEventListener(_type, listener) {
190
212
  listeners.add(listener);