Skip to content

Commit 244e75c

Browse files
committed
Handle socket timeouts
1 parent 373ad30 commit 244e75c

File tree

1 file changed

+45
-13
lines changed

1 file changed

+45
-13
lines changed

packages/transport-node/src/transport.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,32 @@ export class TransportNode implements Types.Transport {
2020
Types.DeviceStatusEnum.DeviceDisconnected;
2121

2222
private closingByUser = false;
23+
private errored = false;
2324

2425
/**
2526
* Creates and connects a new TransportNode instance.
2627
* @param hostname - The IP address or hostname of the Meshtastic device.
2728
* @param port - The port number for the TCP connection (defaults to 4403).
29+
* @param timeout - TCP socket timeout in milliseconds (defaults to 60000).
2830
* @returns A promise that resolves with a connected TransportNode instance.
2931
*/
30-
public static create(hostname: string, port = 4403): Promise<TransportNode> {
32+
public static create(hostname: string, port = 4403, timeout = 60000): Promise<TransportNode> {
3133
return new Promise((resolve, reject) => {
3234
const socket = new Socket();
3335

3436
const onError = (err: Error) => {
3537
socket.destroy();
38+
socket.removeAllListeners();
3639
reject(err);
3740
};
3841

3942
socket.once("error", onError);
40-
41-
socket.connect(port, hostname, () => {
43+
socket.once("ready", () => {
4244
socket.removeListener("error", onError);
4345
resolve(new TransportNode(socket));
4446
});
47+
socket.setTimeout(timeout);
48+
socket.connect(port, hostname);
4549
});
4650
}
4751

@@ -52,8 +56,10 @@ export class TransportNode implements Types.Transport {
5256
constructor(connection: Socket) {
5357
this.socket = connection;
5458

55-
this.socket.on("error", (err) => {
56-
console.error("Socket connection error:", err);
59+
this.socket.on("error", () => {
60+
this.errored = true;
61+
this.socket?.removeAllListeners();
62+
this.socket?.destroy();
5763
if (!this.closingByUser) {
5864
this.emitStatus(
5965
Types.DeviceStatusEnum.DeviceDisconnected,
@@ -62,6 +68,27 @@ export class TransportNode implements Types.Transport {
6268
}
6369
});
6470

71+
this.socket.on("end", () => {
72+
if (this.closingByUser) {
73+
return; // suppress close-derived disconnect in user flow
74+
}
75+
this.emitStatus(
76+
Types.DeviceStatusEnum.DeviceDisconnected,
77+
"socket-end",
78+
);
79+
this.socket?.removeAllListeners();
80+
this.socket?.destroy();
81+
});
82+
83+
this.socket.on("timeout", () => {
84+
this.emitStatus(
85+
Types.DeviceStatusEnum.DeviceDisconnected,
86+
"socket-timeout",
87+
);
88+
this.socket?.removeAllListeners();
89+
this.socket?.destroy();
90+
});
91+
6592
this.socket.on("close", () => {
6693
if (this.closingByUser) {
6794
return; // suppress close-derived disconnect in user flow
@@ -98,7 +125,7 @@ export class TransportNode implements Types.Transport {
98125
}
99126
ctrl.close();
100127
} catch (error) {
101-
if (this.closingByUser) {
128+
if (this.closingByUser || this.errored) {
102129
ctrl.close();
103130
} else {
104131
this.emitStatus(
@@ -128,10 +155,9 @@ export class TransportNode implements Types.Transport {
128155
signal: abortController.signal,
129156
})
130157
.catch((err) => {
131-
if (abortController.signal.aborted) {
158+
if (abortController.signal.aborted || this.socket?.destroyed) {
132159
return;
133160
}
134-
console.error("Error piping data to socket:", err);
135161
const error = err instanceof Error ? err : new Error(String(err));
136162
this.socket?.destroy(error);
137163
});
@@ -161,10 +187,12 @@ export class TransportNode implements Types.Transport {
161187
await this.pipePromise;
162188
}
163189

164-
this.socket?.destroy();
190+
this.socket?.removeAllListeners();
191+
this.socket?.end();
165192
} finally {
166193
this.socket = undefined;
167194
this.closingByUser = false;
195+
this.errored = false;
168196
}
169197
}
170198

@@ -173,9 +201,13 @@ export class TransportNode implements Types.Transport {
173201
return;
174202
}
175203
this.lastStatus = next;
176-
this.fromDeviceController?.enqueue({
177-
type: "status",
178-
data: { status: next, reason },
179-
});
204+
try {
205+
this.fromDeviceController?.enqueue({
206+
type: "status",
207+
data: { status: next, reason },
208+
});
209+
} catch (e) {
210+
console.error('Enqueue fail', e);
211+
}
180212
}
181213
}

0 commit comments

Comments
 (0)