@@ -20,28 +20,32 @@ export class TransportNode implements Types.Transport {
20
20
Types . DeviceStatusEnum . DeviceDisconnected ;
21
21
22
22
private closingByUser = false ;
23
+ private errored = false ;
23
24
24
25
/**
25
26
* Creates and connects a new TransportNode instance.
26
27
* @param hostname - The IP address or hostname of the Meshtastic device.
27
28
* @param port - The port number for the TCP connection (defaults to 4403).
29
+ * @param timeout - TCP socket timeout in milliseconds (defaults to 60000).
28
30
* @returns A promise that resolves with a connected TransportNode instance.
29
31
*/
30
- public static create ( hostname : string , port = 4403 ) : Promise < TransportNode > {
32
+ public static create ( hostname : string , port = 4403 , timeout = 60000 ) : Promise < TransportNode > {
31
33
return new Promise ( ( resolve , reject ) => {
32
34
const socket = new Socket ( ) ;
33
35
34
36
const onError = ( err : Error ) => {
35
37
socket . destroy ( ) ;
38
+ socket . removeAllListeners ( ) ;
36
39
reject ( err ) ;
37
40
} ;
38
41
39
42
socket . once ( "error" , onError ) ;
40
-
41
- socket . connect ( port , hostname , ( ) => {
43
+ socket . once ( "ready" , ( ) => {
42
44
socket . removeListener ( "error" , onError ) ;
43
45
resolve ( new TransportNode ( socket ) ) ;
44
46
} ) ;
47
+ socket . setTimeout ( timeout ) ;
48
+ socket . connect ( port , hostname ) ;
45
49
} ) ;
46
50
}
47
51
@@ -52,8 +56,10 @@ export class TransportNode implements Types.Transport {
52
56
constructor ( connection : Socket ) {
53
57
this . socket = connection ;
54
58
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 ( ) ;
57
63
if ( ! this . closingByUser ) {
58
64
this . emitStatus (
59
65
Types . DeviceStatusEnum . DeviceDisconnected ,
@@ -62,6 +68,27 @@ export class TransportNode implements Types.Transport {
62
68
}
63
69
} ) ;
64
70
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
+
65
92
this . socket . on ( "close" , ( ) => {
66
93
if ( this . closingByUser ) {
67
94
return ; // suppress close-derived disconnect in user flow
@@ -98,7 +125,7 @@ export class TransportNode implements Types.Transport {
98
125
}
99
126
ctrl . close ( ) ;
100
127
} catch ( error ) {
101
- if ( this . closingByUser ) {
128
+ if ( this . closingByUser || this . errored ) {
102
129
ctrl . close ( ) ;
103
130
} else {
104
131
this . emitStatus (
@@ -128,10 +155,9 @@ export class TransportNode implements Types.Transport {
128
155
signal : abortController . signal ,
129
156
} )
130
157
. catch ( ( err ) => {
131
- if ( abortController . signal . aborted ) {
158
+ if ( abortController . signal . aborted || this . socket ?. destroyed ) {
132
159
return ;
133
160
}
134
- console . error ( "Error piping data to socket:" , err ) ;
135
161
const error = err instanceof Error ? err : new Error ( String ( err ) ) ;
136
162
this . socket ?. destroy ( error ) ;
137
163
} ) ;
@@ -161,10 +187,12 @@ export class TransportNode implements Types.Transport {
161
187
await this . pipePromise ;
162
188
}
163
189
164
- this . socket ?. destroy ( ) ;
190
+ this . socket ?. removeAllListeners ( ) ;
191
+ this . socket ?. end ( ) ;
165
192
} finally {
166
193
this . socket = undefined ;
167
194
this . closingByUser = false ;
195
+ this . errored = false ;
168
196
}
169
197
}
170
198
@@ -173,9 +201,13 @@ export class TransportNode implements Types.Transport {
173
201
return ;
174
202
}
175
203
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
+ }
180
212
}
181
213
}
0 commit comments