From fb8dac14c9d08fad6bfee61f21fe941a10696c70 Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Fri, 29 Aug 2025 13:57:02 -0400 Subject: [PATCH 01/12] Clear heartbeat and queue when disconnected --- packages/core/src/meshDevice.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core/src/meshDevice.ts b/packages/core/src/meshDevice.ts index 1ba7a13c..8f8017f4 100755 --- a/packages/core/src/meshDevice.ts +++ b/packages/core/src/meshDevice.ts @@ -64,6 +64,11 @@ export class MeshDevice { this.isConfigured = true; } else if (status === DeviceStatusEnum.DeviceConfiguring) { this.isConfigured = false; + } else if (status === DeviceStatusEnum.DeviceDisconnected) { + if (this._heartbeatIntervalId !== undefined) { + clearInterval(this._heartbeatIntervalId); + } + this.complete(); } }); From 0bf35558ce763b4d8c7d45be628a8a3bdbeff93d Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Fri, 29 Aug 2025 13:58:02 -0400 Subject: [PATCH 02/12] Give clearer error in case configure fails due to a lost connection. Used to throw 'Packet does not exist' --- packages/core/src/meshDevice.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/core/src/meshDevice.ts b/packages/core/src/meshDevice.ts index 8f8017f4..1076cd18 100755 --- a/packages/core/src/meshDevice.ts +++ b/packages/core/src/meshDevice.ts @@ -775,7 +775,13 @@ export class MeshDevice { }, }); - return this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio)); + return this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio)) + .catch((e) => { + if (this.deviceStatus === DeviceStatusEnum.DeviceDisconnected) { + throw new Error('Device connection lost'); + } + throw e; + }); } /** From 5b503e141a7b36408a24669bcb48d87105daeac2 Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Fri, 29 Aug 2025 14:22:02 -0400 Subject: [PATCH 03/12] If the queue processing error is due to a lost connection, throw it instead of looping endlessly --- packages/core/src/utils/queue.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core/src/utils/queue.ts b/packages/core/src/utils/queue.ts index 55f23282..8a814385 100644 --- a/packages/core/src/utils/queue.ts +++ b/packages/core/src/utils/queue.ts @@ -117,6 +117,11 @@ export class Queue { await writer.write(item.data); item.sent = true; } catch (error) { + if (error?.code === 'ECONNRESET') { + writer.releaseLock(); + this.lock = false; + throw error; + } console.error(`Error sending packet ${item.id}`, error); } } From 226dcce60f4b9a680e8276b67a7146c3f1f8df5a Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Fri, 29 Aug 2025 15:17:03 -0400 Subject: [PATCH 04/12] In case we send a disconnection event we don't need to also throw --- packages/transport-http/src/transport.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/transport-http/src/transport.ts b/packages/transport-http/src/transport.ts index dae3baaa..3bdf57db 100644 --- a/packages/transport-http/src/transport.ts +++ b/packages/transport-http/src/transport.ts @@ -76,6 +76,7 @@ export class TransportHTTP implements Types.Transport { Types.DeviceStatusEnum.DeviceDisconnected, this.isTimeoutOrAbort(error) ? "write-timeout" : "write-error", ); + return; } throw error; } @@ -165,6 +166,7 @@ export class TransportHTTP implements Types.Transport { Types.DeviceStatusEnum.DeviceDisconnected, this.isTimeoutOrAbort(error) ? "write-timeout" : "write-error", ); + return; } throw error; } From cf488d90e7b7ac4a2f27b45d1c068e27ec15bc0e Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Fri, 29 Aug 2025 15:35:01 -0400 Subject: [PATCH 05/12] Catch heartbeat errors --- packages/core/src/meshDevice.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/meshDevice.ts b/packages/core/src/meshDevice.ts index 1076cd18..31fe02b4 100755 --- a/packages/core/src/meshDevice.ts +++ b/packages/core/src/meshDevice.ts @@ -808,7 +808,7 @@ export class MeshDevice { clearInterval(this._heartbeatIntervalId); } this._heartbeatIntervalId = setInterval(() => { - this.heartbeat(); + this.heartbeat().catch(() => {}); }, interval); } From dadcf13691a534c6bf96bf20465f0c694b20a110 Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Fri, 29 Aug 2025 16:47:03 -0400 Subject: [PATCH 06/12] Also handle invalid state errors --- packages/core/src/utils/queue.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/utils/queue.ts b/packages/core/src/utils/queue.ts index 8a814385..8c248702 100644 --- a/packages/core/src/utils/queue.ts +++ b/packages/core/src/utils/queue.ts @@ -117,7 +117,7 @@ export class Queue { await writer.write(item.data); item.sent = true; } catch (error) { - if (error?.code === 'ECONNRESET') { + if (error?.code === 'ECONNRESET' || error?.code === 'ERR_INVALID_STATE') { writer.releaseLock(); this.lock = false; throw error; From f3089a4c225235b8bb727aaf6f25847ffca5ead2 Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Fri, 29 Aug 2025 17:17:36 -0400 Subject: [PATCH 07/12] Handle socket timeouts --- packages/transport-node/src/transport.ts | 58 ++++++++++++++++++------ 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/packages/transport-node/src/transport.ts b/packages/transport-node/src/transport.ts index 03713d0b..6730f57d 100644 --- a/packages/transport-node/src/transport.ts +++ b/packages/transport-node/src/transport.ts @@ -20,28 +20,32 @@ export class TransportNode implements Types.Transport { Types.DeviceStatusEnum.DeviceDisconnected; private closingByUser = false; + private errored = false; /** * Creates and connects a new TransportNode instance. * @param hostname - The IP address or hostname of the Meshtastic device. * @param port - The port number for the TCP connection (defaults to 4403). + * @param timeout - TCP socket timeout in milliseconds (defaults to 60000). * @returns A promise that resolves with a connected TransportNode instance. */ - public static create(hostname: string, port = 4403): Promise { + public static create(hostname: string, port = 4403, timeout = 60000): Promise { return new Promise((resolve, reject) => { const socket = new Socket(); const onError = (err: Error) => { socket.destroy(); + socket.removeAllListeners(); reject(err); }; socket.once("error", onError); - - socket.connect(port, hostname, () => { + socket.once("ready", () => { socket.removeListener("error", onError); resolve(new TransportNode(socket)); }); + socket.setTimeout(timeout); + socket.connect(port, hostname); }); } @@ -52,8 +56,10 @@ export class TransportNode implements Types.Transport { constructor(connection: Socket) { this.socket = connection; - this.socket.on("error", (err) => { - console.error("Socket connection error:", err); + this.socket.on("error", () => { + this.errored = true; + this.socket?.removeAllListeners(); + this.socket?.destroy(); if (!this.closingByUser) { this.emitStatus( Types.DeviceStatusEnum.DeviceDisconnected, @@ -62,6 +68,27 @@ export class TransportNode implements Types.Transport { } }); + this.socket.on("end", () => { + if (this.closingByUser) { + return; // suppress close-derived disconnect in user flow + } + this.emitStatus( + Types.DeviceStatusEnum.DeviceDisconnected, + "socket-end", + ); + this.socket?.removeAllListeners(); + this.socket?.destroy(); + }); + + this.socket.on("timeout", () => { + this.emitStatus( + Types.DeviceStatusEnum.DeviceDisconnected, + "socket-timeout", + ); + this.socket?.removeAllListeners(); + this.socket?.destroy(); + }); + this.socket.on("close", () => { if (this.closingByUser) { return; // suppress close-derived disconnect in user flow @@ -98,7 +125,7 @@ export class TransportNode implements Types.Transport { } ctrl.close(); } catch (error) { - if (this.closingByUser) { + if (this.closingByUser || this.errored) { ctrl.close(); } else { this.emitStatus( @@ -128,10 +155,9 @@ export class TransportNode implements Types.Transport { signal: abortController.signal, }) .catch((err) => { - if (abortController.signal.aborted) { + if (abortController.signal.aborted || this.socket?.destroyed) { return; } - console.error("Error piping data to socket:", err); const error = err instanceof Error ? err : new Error(String(err)); this.socket?.destroy(error); }); @@ -161,10 +187,12 @@ export class TransportNode implements Types.Transport { await this.pipePromise; } - this.socket?.destroy(); + this.socket?.removeAllListeners(); + this.socket?.end(); } finally { this.socket = undefined; this.closingByUser = false; + this.errored = false; } } @@ -173,9 +201,13 @@ export class TransportNode implements Types.Transport { return; } this.lastStatus = next; - this.fromDeviceController?.enqueue({ - type: "status", - data: { status: next, reason }, - }); + try { + this.fromDeviceController?.enqueue({ + type: "status", + data: { status: next, reason }, + }); + } catch (e) { + console.error('Enqueue fail', e); + } } } From d3b171d46b927c07e86b647c494ef84af03e0edc Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Sat, 30 Aug 2025 12:21:46 -0400 Subject: [PATCH 08/12] Log heartbeat failures --- packages/core/src/meshDevice.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/meshDevice.ts b/packages/core/src/meshDevice.ts index 31fe02b4..a8246192 100755 --- a/packages/core/src/meshDevice.ts +++ b/packages/core/src/meshDevice.ts @@ -808,7 +808,12 @@ export class MeshDevice { clearInterval(this._heartbeatIntervalId); } this._heartbeatIntervalId = setInterval(() => { - this.heartbeat().catch(() => {}); + this.heartbeat().catch((err) => { + this.log.error( + Emitter[Emitter.Ping], + `⚠️ Unable to send heartbeat: ${err.message}` + ); + }); }, interval); } From d6cfd14dfe7400d3f6fed6303c4f264713e19b9a Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Thu, 4 Sep 2025 09:11:15 -0400 Subject: [PATCH 09/12] Make linter happy --- packages/core/src/meshDevice.ts | 13 +++++++------ packages/core/src/utils/queue.ts | 5 ++++- packages/transport-node/src/transport.ts | 13 +++++++------ 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/packages/core/src/meshDevice.ts b/packages/core/src/meshDevice.ts index a8246192..0be2b224 100755 --- a/packages/core/src/meshDevice.ts +++ b/packages/core/src/meshDevice.ts @@ -775,13 +775,14 @@ export class MeshDevice { }, }); - return this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio)) - .catch((e) => { + return this.sendRaw(toBinary(Protobuf.Mesh.ToRadioSchema, toRadio)).catch( + (e) => { if (this.deviceStatus === DeviceStatusEnum.DeviceDisconnected) { - throw new Error('Device connection lost'); + throw new Error("Device connection lost"); } throw e; - }); + }, + ); } /** @@ -810,8 +811,8 @@ export class MeshDevice { this._heartbeatIntervalId = setInterval(() => { this.heartbeat().catch((err) => { this.log.error( - Emitter[Emitter.Ping], - `⚠️ Unable to send heartbeat: ${err.message}` + Emitter[Emitter.Ping], + `⚠️ Unable to send heartbeat: ${err.message}`, ); }); }, interval); diff --git a/packages/core/src/utils/queue.ts b/packages/core/src/utils/queue.ts index 8c248702..65bff569 100644 --- a/packages/core/src/utils/queue.ts +++ b/packages/core/src/utils/queue.ts @@ -117,7 +117,10 @@ export class Queue { await writer.write(item.data); item.sent = true; } catch (error) { - if (error?.code === 'ECONNRESET' || error?.code === 'ERR_INVALID_STATE') { + if ( + error?.code === "ECONNRESET" || + error?.code === "ERR_INVALID_STATE" + ) { writer.releaseLock(); this.lock = false; throw error; diff --git a/packages/transport-node/src/transport.ts b/packages/transport-node/src/transport.ts index 6730f57d..50ec0307 100644 --- a/packages/transport-node/src/transport.ts +++ b/packages/transport-node/src/transport.ts @@ -29,7 +29,11 @@ export class TransportNode implements Types.Transport { * @param timeout - TCP socket timeout in milliseconds (defaults to 60000). * @returns A promise that resolves with a connected TransportNode instance. */ - public static create(hostname: string, port = 4403, timeout = 60000): Promise { + public static create( + hostname: string, + port = 4403, + timeout = 60000, + ): Promise { return new Promise((resolve, reject) => { const socket = new Socket(); @@ -72,10 +76,7 @@ export class TransportNode implements Types.Transport { if (this.closingByUser) { return; // suppress close-derived disconnect in user flow } - this.emitStatus( - Types.DeviceStatusEnum.DeviceDisconnected, - "socket-end", - ); + this.emitStatus(Types.DeviceStatusEnum.DeviceDisconnected, "socket-end"); this.socket?.removeAllListeners(); this.socket?.destroy(); }); @@ -207,7 +208,7 @@ export class TransportNode implements Types.Transport { data: { status: next, reason }, }); } catch (e) { - console.error('Enqueue fail', e); + console.error("Enqueue fail", e); } } } From bdfb8c5827b00284f0ece47aefec331a89f1952d Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Thu, 4 Sep 2025 11:40:58 -0400 Subject: [PATCH 10/12] Transform stream being a singleton prevented reconnection attempts --- packages/core/src/utils/transform/toDevice.ts | 28 ++++++++++--------- packages/transport-deno/src/transport.ts | 5 ++-- .../src/transport.test.ts | 3 +- .../transport-node-serial/src/transport.ts | 5 ++-- packages/transport-node/src/transport.test.ts | 3 +- packages/transport-node/src/transport.ts | 2 +- .../src/transport.test.ts | 3 +- .../transport-web-serial/src/transport.ts | 8 ++++-- 8 files changed, 33 insertions(+), 24 deletions(-) diff --git a/packages/core/src/utils/transform/toDevice.ts b/packages/core/src/utils/transform/toDevice.ts index 05350413..7f6a3932 100644 --- a/packages/core/src/utils/transform/toDevice.ts +++ b/packages/core/src/utils/transform/toDevice.ts @@ -1,16 +1,18 @@ /** * Pads packets with appropriate framing information before writing to the output stream. */ -export const toDeviceStream: TransformStream = - new TransformStream({ - transform(chunk: Uint8Array, controller): void { - const bufLen = chunk.length; - const header = new Uint8Array([ - 0x94, - 0xc3, - (bufLen >> 8) & 0xff, - bufLen & 0xff, - ]); - controller.enqueue(new Uint8Array([...header, ...chunk])); - }, - }); +export const toDeviceStream: () => TransformStream = + () => { + return new TransformStream({ + transform(chunk: Uint8Array, controller): void { + const bufLen = chunk.length; + const header = new Uint8Array([ + 0x94, + 0xc3, + (bufLen >> 8) & 0xff, + bufLen & 0xff, + ]); + controller.enqueue(new Uint8Array([...header, ...chunk])); + }, + }); + }; diff --git a/packages/transport-deno/src/transport.ts b/packages/transport-deno/src/transport.ts index d5e41aa6..eec90695 100644 --- a/packages/transport-deno/src/transport.ts +++ b/packages/transport-deno/src/transport.ts @@ -16,9 +16,10 @@ export class TransportDeno implements Types.Transport { constructor(connection: Deno.Conn) { this.connection = connection; - Utils.toDeviceStream.readable.pipeTo(this.connection.writable); + const toDeviceStream = Utils.toDeviceStream(); + toDeviceStream.readable.pipeTo(this.connection.writable); - this._toDevice = Utils.toDeviceStream.writable; + this._toDevice = toDeviceStream.writable; this._fromDevice = this.connection.readable.pipeThrough( Utils.fromDeviceStream(), ); diff --git a/packages/transport-node-serial/src/transport.test.ts b/packages/transport-node-serial/src/transport.test.ts index 3345bbd0..9fdc3e17 100644 --- a/packages/transport-node-serial/src/transport.test.ts +++ b/packages/transport-node-serial/src/transport.test.ts @@ -67,8 +67,9 @@ function stubCoreTransforms() { }); // Utils.toDeviceStream is a getter + const transform = Utils.toDeviceStream(); vi.spyOn(Utils, "toDeviceStream", "get").mockReturnValue( - toDevice as unknown as typeof Utils.toDeviceStream, + toDevice as unknown as typeof transform, ); vi.spyOn(Utils, "fromDeviceStream").mockImplementation( diff --git a/packages/transport-node-serial/src/transport.ts b/packages/transport-node-serial/src/transport.ts index fc5df1a7..8bc0dd52 100644 --- a/packages/transport-node-serial/src/transport.ts +++ b/packages/transport-node-serial/src/transport.ts @@ -112,9 +112,10 @@ export class TransportNodeSerial implements Types.Transport { }); // Stream for data going FROM the application TO the Meshtastic device. - this._toDevice = Utils.toDeviceStream.writable; + const toDeviceTransform = Utils.toDeviceStream(); + this._toDevice = toDeviceTransform.writable; - this.pipePromise = Utils.toDeviceStream.readable + this.pipePromise = toDeviceTransform.readable .pipeTo(Writable.toWeb(port) as WritableStream, { signal: controller.signal, }) diff --git a/packages/transport-node/src/transport.test.ts b/packages/transport-node/src/transport.test.ts index ae9d5feb..abb785f1 100644 --- a/packages/transport-node/src/transport.test.ts +++ b/packages/transport-node/src/transport.test.ts @@ -67,8 +67,9 @@ function stubCoreTransforms() { }, }); + const transform = Utils.toDeviceStream(); vi.spyOn(Utils, "toDeviceStream", "get").mockReturnValue( - toDevice as unknown as typeof Utils.toDeviceStream, + toDevice as unknown as typeof transform, ); vi.spyOn(Utils, "fromDeviceStream").mockImplementation( diff --git a/packages/transport-node/src/transport.ts b/packages/transport-node/src/transport.ts index 50ec0307..f5b8e8bf 100644 --- a/packages/transport-node/src/transport.ts +++ b/packages/transport-node/src/transport.ts @@ -148,7 +148,7 @@ export class TransportNode implements Types.Transport { }); // Stream for data going FROM the application TO the Meshtastic device. - const toDeviceTransform = Utils.toDeviceStream; + const toDeviceTransform = Utils.toDeviceStream(); this._toDevice = toDeviceTransform.writable; this.pipePromise = toDeviceTransform.readable diff --git a/packages/transport-web-serial/src/transport.test.ts b/packages/transport-web-serial/src/transport.test.ts index dbf4ef71..c3d25c98 100644 --- a/packages/transport-web-serial/src/transport.test.ts +++ b/packages/transport-web-serial/src/transport.test.ts @@ -18,9 +18,10 @@ function stubCoreTransforms() { }, }); + const transform = Utils.toDeviceStream(); const restoreTo = vi .spyOn(Utils, "toDeviceStream", "get") - .mockReturnValue(toDevice as unknown as typeof Utils.toDeviceStream); + .mockReturnValue(toDevice as unknown as typeof transform); const restoreFrom = vi .spyOn(Utils, "fromDeviceStream") diff --git a/packages/transport-web-serial/src/transport.ts b/packages/transport-web-serial/src/transport.ts index 949e4d5c..ff81aee1 100644 --- a/packages/transport-web-serial/src/transport.ts +++ b/packages/transport-web-serial/src/transport.ts @@ -58,7 +58,8 @@ export class TransportWebSerial implements Types.Transport { const abortController = this.abortController; // Set up the pipe with abort signal for clean cancellation - this.pipePromise = Utils.toDeviceStream.readable + const toDeviceTransform = Utils.toDeviceStream(); + this.pipePromise = toDeviceTransform.readable .pipeTo(connection.writable, { signal: this.abortController.signal }) .catch((err) => { // Ignore expected rejection when we cancel it via the AbortController. @@ -73,7 +74,7 @@ export class TransportWebSerial implements Types.Transport { ); }); - this._toDevice = Utils.toDeviceStream.writable; + this._toDevice = toDeviceTransform.writable; // Wrap + capture controller to inject status packets this._fromDevice = new ReadableStream({ @@ -199,7 +200,8 @@ export class TransportWebSerial implements Types.Transport { const abortController = this.abortController; // Re-establish the pipe connection - this.pipePromise = Utils.toDeviceStream.readable + const toDeviceTransform = Utils.toDeviceStream(); + this.pipePromise = toDeviceTransform.readable .pipeTo(this.connection.writable, { signal: this.abortController.signal, }) From 4eb7dc74c5aead9d40e401b16a9017d68d465b59 Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Thu, 4 Sep 2025 12:32:19 -0400 Subject: [PATCH 11/12] Adapt tests to not using singleton --- .../transport-node-serial/src/transport.test.ts | 13 +++++++------ packages/transport-node/src/transport.test.ts | 13 +++++++------ packages/transport-web-serial/src/transport.test.ts | 13 +++++++------ 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/packages/transport-node-serial/src/transport.test.ts b/packages/transport-node-serial/src/transport.test.ts index 9fdc3e17..d6290853 100644 --- a/packages/transport-node-serial/src/transport.test.ts +++ b/packages/transport-node-serial/src/transport.test.ts @@ -53,11 +53,12 @@ class FakeSerialPort extends Duplex { } function stubCoreTransforms() { - const toDevice = new TransformStream({ - transform(chunk, controller) { - controller.enqueue(chunk); - }, - }); + const toDevice = () => + new TransformStream({ + transform(chunk, controller) { + controller.enqueue(chunk); + }, + }); const fromDeviceFactory = () => new TransformStream({ @@ -67,7 +68,7 @@ function stubCoreTransforms() { }); // Utils.toDeviceStream is a getter - const transform = Utils.toDeviceStream(); + const transform = Utils.toDeviceStream; vi.spyOn(Utils, "toDeviceStream", "get").mockReturnValue( toDevice as unknown as typeof transform, ); diff --git a/packages/transport-node/src/transport.test.ts b/packages/transport-node/src/transport.test.ts index abb785f1..03ab4a49 100644 --- a/packages/transport-node/src/transport.test.ts +++ b/packages/transport-node/src/transport.test.ts @@ -54,11 +54,12 @@ class FakeSocket extends Duplex { } function stubCoreTransforms() { - const toDevice = new TransformStream({ - transform(chunk, controller) { - controller.enqueue(chunk); - }, - }); + const toDevice = () => + new TransformStream({ + transform(chunk, controller) { + controller.enqueue(chunk); + }, + }); const fromDeviceFactory = () => new TransformStream({ @@ -67,7 +68,7 @@ function stubCoreTransforms() { }, }); - const transform = Utils.toDeviceStream(); + const transform = Utils.toDeviceStream; vi.spyOn(Utils, "toDeviceStream", "get").mockReturnValue( toDevice as unknown as typeof transform, ); diff --git a/packages/transport-web-serial/src/transport.test.ts b/packages/transport-web-serial/src/transport.test.ts index c3d25c98..403a3f38 100644 --- a/packages/transport-web-serial/src/transport.test.ts +++ b/packages/transport-web-serial/src/transport.test.ts @@ -4,11 +4,12 @@ import { runTransportContract } from "../../../tests/utils/transportContract.ts" import { TransportWebSerial } from "./transport.ts"; function stubCoreTransforms() { - const toDevice = new TransformStream({ - transform(chunk, controller) { - controller.enqueue(chunk); - }, - }); + const toDevice = () => + new TransformStream({ + transform(chunk, controller) { + controller.enqueue(chunk); + }, + }); // maps raw bytes -> DeviceOutput.packet const fromDeviceFactory = () => @@ -18,7 +19,7 @@ function stubCoreTransforms() { }, }); - const transform = Utils.toDeviceStream(); + const transform = Utils.toDeviceStream; const restoreTo = vi .spyOn(Utils, "toDeviceStream", "get") .mockReturnValue(toDevice as unknown as typeof transform); From c99bade10b5d1791f94611f3d4515f8384ff298c Mon Sep 17 00:00:00 2001 From: Henri Bergius Date: Thu, 4 Sep 2025 12:55:59 -0400 Subject: [PATCH 12/12] Aborting already ends the connection --- packages/transport-node/src/transport.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/transport-node/src/transport.ts b/packages/transport-node/src/transport.ts index f5b8e8bf..9d41167c 100644 --- a/packages/transport-node/src/transport.ts +++ b/packages/transport-node/src/transport.ts @@ -187,9 +187,7 @@ export class TransportNode implements Types.Transport { if (this.pipePromise) { await this.pipePromise; } - - this.socket?.removeAllListeners(); - this.socket?.end(); + this.socket?.destroy(); } finally { this.socket = undefined; this.closingByUser = false;