@limrun/api 0.13.0 → 0.13.2

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/index.d.mts +1 -1
  3. package/index.d.mts.map +1 -1
  4. package/index.d.ts +1 -1
  5. package/index.d.ts.map +1 -1
  6. package/index.js +3 -3
  7. package/index.js.map +1 -1
  8. package/index.mjs +1 -1
  9. package/index.mjs.map +1 -1
  10. package/instance-client.d.mts +32 -0
  11. package/instance-client.d.mts.map +1 -1
  12. package/instance-client.d.ts +32 -0
  13. package/instance-client.d.ts.map +1 -1
  14. package/instance-client.js +193 -87
  15. package/instance-client.js.map +1 -1
  16. package/instance-client.mjs +193 -87
  17. package/instance-client.mjs.map +1 -1
  18. package/package.json +1 -1
  19. package/resources/android-instances.d.mts +9 -1
  20. package/resources/android-instances.d.mts.map +1 -1
  21. package/resources/android-instances.d.ts +9 -1
  22. package/resources/android-instances.d.ts.map +1 -1
  23. package/resources/android-instances.js +2 -2
  24. package/resources/android-instances.js.map +1 -1
  25. package/resources/android-instances.mjs +2 -2
  26. package/resources/android-instances.mjs.map +1 -1
  27. package/resources/ios-instances.d.mts +9 -1
  28. package/resources/ios-instances.d.mts.map +1 -1
  29. package/resources/ios-instances.d.ts +9 -1
  30. package/resources/ios-instances.d.ts.map +1 -1
  31. package/resources/ios-instances.js +2 -2
  32. package/resources/ios-instances.js.map +1 -1
  33. package/resources/ios-instances.mjs +2 -2
  34. package/resources/ios-instances.mjs.map +1 -1
  35. package/src/index.ts +1 -1
  36. package/src/instance-client.ts +269 -101
  37. package/src/resources/android-instances.ts +13 -3
  38. package/src/resources/ios-instances.ts +13 -3
  39. package/src/tunnel.ts +262 -43
  40. package/src/version.ts +1 -1
  41. package/tunnel.d.mts +50 -7
  42. package/tunnel.d.mts.map +1 -1
  43. package/tunnel.d.ts +50 -7
  44. package/tunnel.d.ts.map +1 -1
  45. package/tunnel.js +195 -44
  46. package/tunnel.js.map +1 -1
  47. package/tunnel.mjs +195 -44
  48. package/tunnel.mjs.map +1 -1
  49. package/version.d.mts +1 -1
  50. package/version.d.ts +1 -1
  51. package/version.js +1 -1
  52. package/version.mjs +1 -1
package/tunnel.js CHANGED
@@ -5,95 +5,246 @@ const tslib_1 = require("./internal/tslib.js");
5
5
  const net = tslib_1.__importStar(require("net"));
6
6
  const ws_1 = require("ws");
7
7
  /**
8
- * Starts a one-shot TCP → WebSocket proxy.
8
+ * Starts a persistent TCP → WebSocket proxy.
9
9
  *
10
10
  * The function creates a local TCP server that listens on an ephemeral port on
11
- * 127.0.0.1. As soon as the **first** TCP client connects the server stops
12
- * accepting further connections and forwards all traffic between that client
13
- * and `remoteURL` through an authenticated WebSocket. If you need to proxy
14
- * more than one TCP connection, call `startTcpTunnel` again to create a new
15
- * proxy instance.
11
+ * 127.0.0.1. When a TCP client connects, it forwards all traffic between that
12
+ * client and `remoteURL` through an authenticated WebSocket. The server remains
13
+ * active even after the client disconnects, allowing reconnection without
14
+ * recreating the tunnel.
16
15
  *
17
16
  * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance)
18
17
  * @param token Bearer token sent as `Authorization` header
19
18
  * @param hostname Optional IP address to listen on. Default is 127.0.0.1
20
19
  * @param port Optional port number to listen on. Default is to ask Node.js
21
20
  * to find an available non-privileged port.
21
+ * @param options Optional reconnection configuration
22
22
  */
23
- async function startTcpTunnel(remoteURL, token, hostname, port) {
23
+ async function startTcpTunnel(remoteURL, token, hostname, port, options) {
24
+ const maxReconnectAttempts = options?.maxReconnectAttempts ?? 6;
25
+ const reconnectDelay = options?.reconnectDelay ?? 1000;
26
+ const maxReconnectDelay = options?.maxReconnectDelay ?? 30000;
27
+ const logLevel = options?.logLevel ?? 'info';
28
+ // Logger functions
29
+ const logger = {
30
+ debug: (...args) => {
31
+ if (logLevel === 'debug')
32
+ console.log('[Tunnel]', ...args);
33
+ },
34
+ info: (...args) => {
35
+ if (logLevel === 'info' || logLevel === 'debug')
36
+ console.log('[Tunnel]', ...args);
37
+ },
38
+ warn: (...args) => {
39
+ if (logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug')
40
+ console.warn('[Tunnel]', ...args);
41
+ },
42
+ error: (...args) => {
43
+ if (logLevel !== 'none')
44
+ console.error('[Tunnel]', ...args);
45
+ },
46
+ };
24
47
  return new Promise((resolve, reject) => {
25
48
  const server = net.createServer();
26
49
  let ws;
27
50
  let pingInterval;
28
- // close helper
29
- const close = () => {
51
+ let reconnectTimeout;
52
+ let reconnectAttempts = 0;
53
+ let intentionalDisconnect = false;
54
+ let tcpSocket;
55
+ let connectionState = 'connecting';
56
+ const stateChangeCallbacks = new Set();
57
+ const updateConnectionState = (newState) => {
58
+ if (connectionState !== newState) {
59
+ connectionState = newState;
60
+ logger.debug(`Connection state changed to: ${newState}`);
61
+ stateChangeCallbacks.forEach((callback) => {
62
+ try {
63
+ callback(newState);
64
+ }
65
+ catch (err) {
66
+ logger.error('Error in connection state callback:', err);
67
+ }
68
+ });
69
+ }
70
+ };
71
+ const cleanup = () => {
72
+ if (reconnectTimeout) {
73
+ clearTimeout(reconnectTimeout);
74
+ reconnectTimeout = undefined;
75
+ }
30
76
  if (pingInterval) {
31
77
  clearInterval(pingInterval);
32
78
  pingInterval = undefined;
33
79
  }
34
- if (ws && ws.readyState === ws_1.WebSocket.OPEN) {
35
- ws.close(1000, 'close');
80
+ if (ws) {
81
+ ws.removeAllListeners();
82
+ if (ws.readyState === ws_1.WebSocket.OPEN || ws.readyState === ws_1.WebSocket.CONNECTING) {
83
+ ws.close(1000, 'close');
84
+ }
85
+ ws = undefined;
86
+ }
87
+ };
88
+ const close = () => {
89
+ intentionalDisconnect = true;
90
+ cleanup();
91
+ updateConnectionState('disconnected');
92
+ if (tcpSocket && !tcpSocket.destroyed) {
93
+ tcpSocket.destroy();
36
94
  }
37
95
  if (server.listening) {
38
96
  server.close();
39
97
  }
40
98
  };
41
- // No AbortController support proxy can be closed via the returned handle
42
- // TCP server error
43
- server.once('error', (err) => {
44
- close();
45
- reject(new Error(`TCP server error: ${err.message}`));
46
- });
47
- // Listening
48
- server.once('listening', () => {
49
- const address = server.address();
50
- if (!address || typeof address === 'string') {
99
+ const scheduleReconnect = () => {
100
+ if (intentionalDisconnect) {
101
+ logger.debug('Skipping reconnection (intentional disconnect)');
102
+ return;
103
+ }
104
+ if (!tcpSocket || tcpSocket.destroyed) {
105
+ logger.debug('Skipping reconnection (TCP socket closed)');
106
+ return;
107
+ }
108
+ if (reconnectAttempts >= maxReconnectAttempts) {
109
+ logger.error(`Max reconnection attempts (${maxReconnectAttempts}) reached. Closing tunnel.`);
51
110
  close();
52
- return reject(new Error('Failed to obtain listening address'));
111
+ return;
53
112
  }
54
- resolve({ address, close });
55
- });
56
- // On first TCP connection
57
- server.on('connection', (tcpSocket) => {
58
- // Single-connection proxy
59
- server.close();
113
+ const currentDelay = Math.min(reconnectDelay * Math.pow(2, reconnectAttempts), maxReconnectDelay);
114
+ reconnectAttempts++;
115
+ logger.debug(`Scheduling reconnection attempt ${reconnectAttempts} in ${currentDelay}ms...`);
116
+ updateConnectionState('reconnecting');
117
+ reconnectTimeout = setTimeout(() => {
118
+ logger.debug(`Attempting to reconnect (attempt ${reconnectAttempts})...`);
119
+ setupWebSocket();
120
+ }, currentDelay);
121
+ };
122
+ const setupWebSocket = () => {
123
+ if (!tcpSocket || tcpSocket.destroyed) {
124
+ logger.error('Cannot setup WebSocket: TCP socket is closed');
125
+ return;
126
+ }
127
+ cleanup();
128
+ updateConnectionState('connecting');
60
129
  ws = new ws_1.WebSocket(remoteURL, {
61
130
  headers: { Authorization: `Bearer ${token}` },
62
131
  perMessageDeflate: false,
63
132
  });
64
- // WebSocket error
65
- ws.once('error', (err) => {
66
- console.error('WebSocket error:', err);
67
- tcpSocket.destroy();
68
- close();
133
+ ws.on('error', (err) => {
134
+ logger.error('WebSocket error:', err.message || err);
135
+ });
136
+ ws.on('close', () => {
137
+ if (pingInterval) {
138
+ clearInterval(pingInterval);
139
+ pingInterval = undefined;
140
+ }
141
+ const shouldReconnect = !intentionalDisconnect && connectionState !== 'disconnected';
142
+ updateConnectionState('disconnected');
143
+ logger.debug('WebSocket disconnected');
144
+ // Pause TCP socket to apply backpressure - TCP will handle buffering
145
+ if (tcpSocket && !tcpSocket.destroyed && !tcpSocket.isPaused()) {
146
+ logger.debug('Pausing TCP socket (applying backpressure)');
147
+ tcpSocket.pause();
148
+ }
149
+ if (shouldReconnect && tcpSocket && !tcpSocket.destroyed) {
150
+ scheduleReconnect();
151
+ }
69
152
  });
70
- ws.once('open', () => {
71
- const socket = ws; // non-undefined after open
153
+ ws.on('open', () => {
154
+ const socket = ws;
155
+ logger.debug('WebSocket connected');
156
+ reconnectAttempts = 0;
157
+ updateConnectionState('connected');
72
158
  pingInterval = setInterval(() => {
73
159
  if (socket.readyState === ws_1.WebSocket.OPEN) {
74
160
  socket.ping();
75
161
  }
76
162
  }, 30000);
77
- // TCP WS
78
- tcpSocket.on('data', (chunk) => {
163
+ // Resume TCP socket - queued data will flow through
164
+ if (tcpSocket && tcpSocket.isPaused()) {
165
+ logger.debug('Resuming TCP socket (releasing backpressure)');
166
+ tcpSocket.resume();
167
+ }
168
+ // TCP → WS: Forward data directly
169
+ const onTcpData = (chunk) => {
79
170
  if (socket.readyState === ws_1.WebSocket.OPEN) {
80
171
  socket.send(chunk);
81
172
  }
82
- });
173
+ // If WebSocket is not ready, data will queue in TCP buffers (backpressure)
174
+ };
175
+ // Remove old listener if exists and add new one
176
+ tcpSocket.removeListener('data', onTcpData);
177
+ tcpSocket.on('data', onTcpData);
83
178
  // WS → TCP
84
179
  socket.on('message', (data) => {
85
- if (!tcpSocket.destroyed) {
180
+ if (tcpSocket && !tcpSocket.destroyed) {
86
181
  tcpSocket.write(data);
87
182
  }
88
183
  });
89
184
  });
90
- // Mutual close
91
- tcpSocket.on('close', close);
92
- tcpSocket.on('error', (err) => {
93
- console.error('TCP socket error:', err);
185
+ };
186
+ // TCP server error
187
+ server.once('error', (err) => {
188
+ close();
189
+ reject(new Error(`TCP server error: ${err.message}`));
190
+ });
191
+ const getConnectionState = () => {
192
+ return connectionState;
193
+ };
194
+ const onConnectionStateChange = (callback) => {
195
+ stateChangeCallbacks.add(callback);
196
+ return () => {
197
+ stateChangeCallbacks.delete(callback);
198
+ };
199
+ };
200
+ // Listening
201
+ server.once('listening', () => {
202
+ const address = server.address();
203
+ if (!address || typeof address === 'string') {
94
204
  close();
205
+ return reject(new Error('Failed to obtain listening address'));
206
+ }
207
+ resolve({
208
+ address,
209
+ close,
210
+ getConnectionState,
211
+ onConnectionStateChange,
212
+ });
213
+ });
214
+ // Helper to clean up current connection but keep server alive
215
+ const cleanupConnection = () => {
216
+ cleanup();
217
+ updateConnectionState('disconnected');
218
+ if (tcpSocket && !tcpSocket.destroyed) {
219
+ tcpSocket.destroy();
220
+ tcpSocket = undefined;
221
+ }
222
+ // Reset reconnection state for next connection
223
+ reconnectAttempts = 0;
224
+ intentionalDisconnect = false;
225
+ logger.debug('Connection cleaned up, ready for new TCP connection');
226
+ };
227
+ // On TCP connection (can happen multiple times)
228
+ server.on('connection', (socket) => {
229
+ // If there's already an active connection, reject the new one
230
+ if (tcpSocket && !tcpSocket.destroyed) {
231
+ logger.debug('Rejecting new TCP connection - already have an active connection');
232
+ socket.destroy();
233
+ return;
234
+ }
235
+ logger.debug('TCP client connected');
236
+ tcpSocket = socket;
237
+ // TCP socket handlers
238
+ tcpSocket.on('close', () => {
239
+ logger.debug('TCP socket closed by client');
240
+ cleanupConnection();
241
+ });
242
+ tcpSocket.on('error', (err) => {
243
+ logger.error('TCP socket error:', err);
244
+ cleanupConnection();
95
245
  });
96
- ws.on('close', () => tcpSocket.destroy());
246
+ // Start WebSocket connection
247
+ setupWebSocket();
97
248
  });
98
249
  // Start listening
99
250
  server.listen(port, hostname);
package/tunnel.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tunnel.js","sourceRoot":"","sources":["src/tunnel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tunnel.js","sourceRoot":"","sources":["src/tunnel.ts"],"names":[],"mappings":"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"}
package/tunnel.mjs CHANGED
@@ -1,95 +1,246 @@
1
1
  import * as net from 'net';
2
2
  import { WebSocket } from 'ws';
3
3
  /**
4
- * Starts a one-shot TCP → WebSocket proxy.
4
+ * Starts a persistent TCP → WebSocket proxy.
5
5
  *
6
6
  * The function creates a local TCP server that listens on an ephemeral port on
7
- * 127.0.0.1. As soon as the **first** TCP client connects the server stops
8
- * accepting further connections and forwards all traffic between that client
9
- * and `remoteURL` through an authenticated WebSocket. If you need to proxy
10
- * more than one TCP connection, call `startTcpTunnel` again to create a new
11
- * proxy instance.
7
+ * 127.0.0.1. When a TCP client connects, it forwards all traffic between that
8
+ * client and `remoteURL` through an authenticated WebSocket. The server remains
9
+ * active even after the client disconnects, allowing reconnection without
10
+ * recreating the tunnel.
12
11
  *
13
12
  * @param remoteURL Remote WebSocket endpoint (e.g. wss://example.com/instance)
14
13
  * @param token Bearer token sent as `Authorization` header
15
14
  * @param hostname Optional IP address to listen on. Default is 127.0.0.1
16
15
  * @param port Optional port number to listen on. Default is to ask Node.js
17
16
  * to find an available non-privileged port.
17
+ * @param options Optional reconnection configuration
18
18
  */
19
- export async function startTcpTunnel(remoteURL, token, hostname, port) {
19
+ export async function startTcpTunnel(remoteURL, token, hostname, port, options) {
20
+ const maxReconnectAttempts = options?.maxReconnectAttempts ?? 6;
21
+ const reconnectDelay = options?.reconnectDelay ?? 1000;
22
+ const maxReconnectDelay = options?.maxReconnectDelay ?? 30000;
23
+ const logLevel = options?.logLevel ?? 'info';
24
+ // Logger functions
25
+ const logger = {
26
+ debug: (...args) => {
27
+ if (logLevel === 'debug')
28
+ console.log('[Tunnel]', ...args);
29
+ },
30
+ info: (...args) => {
31
+ if (logLevel === 'info' || logLevel === 'debug')
32
+ console.log('[Tunnel]', ...args);
33
+ },
34
+ warn: (...args) => {
35
+ if (logLevel === 'warn' || logLevel === 'info' || logLevel === 'debug')
36
+ console.warn('[Tunnel]', ...args);
37
+ },
38
+ error: (...args) => {
39
+ if (logLevel !== 'none')
40
+ console.error('[Tunnel]', ...args);
41
+ },
42
+ };
20
43
  return new Promise((resolve, reject) => {
21
44
  const server = net.createServer();
22
45
  let ws;
23
46
  let pingInterval;
24
- // close helper
25
- const close = () => {
47
+ let reconnectTimeout;
48
+ let reconnectAttempts = 0;
49
+ let intentionalDisconnect = false;
50
+ let tcpSocket;
51
+ let connectionState = 'connecting';
52
+ const stateChangeCallbacks = new Set();
53
+ const updateConnectionState = (newState) => {
54
+ if (connectionState !== newState) {
55
+ connectionState = newState;
56
+ logger.debug(`Connection state changed to: ${newState}`);
57
+ stateChangeCallbacks.forEach((callback) => {
58
+ try {
59
+ callback(newState);
60
+ }
61
+ catch (err) {
62
+ logger.error('Error in connection state callback:', err);
63
+ }
64
+ });
65
+ }
66
+ };
67
+ const cleanup = () => {
68
+ if (reconnectTimeout) {
69
+ clearTimeout(reconnectTimeout);
70
+ reconnectTimeout = undefined;
71
+ }
26
72
  if (pingInterval) {
27
73
  clearInterval(pingInterval);
28
74
  pingInterval = undefined;
29
75
  }
30
- if (ws && ws.readyState === WebSocket.OPEN) {
31
- ws.close(1000, 'close');
76
+ if (ws) {
77
+ ws.removeAllListeners();
78
+ if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
79
+ ws.close(1000, 'close');
80
+ }
81
+ ws = undefined;
82
+ }
83
+ };
84
+ const close = () => {
85
+ intentionalDisconnect = true;
86
+ cleanup();
87
+ updateConnectionState('disconnected');
88
+ if (tcpSocket && !tcpSocket.destroyed) {
89
+ tcpSocket.destroy();
32
90
  }
33
91
  if (server.listening) {
34
92
  server.close();
35
93
  }
36
94
  };
37
- // No AbortController support proxy can be closed via the returned handle
38
- // TCP server error
39
- server.once('error', (err) => {
40
- close();
41
- reject(new Error(`TCP server error: ${err.message}`));
42
- });
43
- // Listening
44
- server.once('listening', () => {
45
- const address = server.address();
46
- if (!address || typeof address === 'string') {
95
+ const scheduleReconnect = () => {
96
+ if (intentionalDisconnect) {
97
+ logger.debug('Skipping reconnection (intentional disconnect)');
98
+ return;
99
+ }
100
+ if (!tcpSocket || tcpSocket.destroyed) {
101
+ logger.debug('Skipping reconnection (TCP socket closed)');
102
+ return;
103
+ }
104
+ if (reconnectAttempts >= maxReconnectAttempts) {
105
+ logger.error(`Max reconnection attempts (${maxReconnectAttempts}) reached. Closing tunnel.`);
47
106
  close();
48
- return reject(new Error('Failed to obtain listening address'));
107
+ return;
49
108
  }
50
- resolve({ address, close });
51
- });
52
- // On first TCP connection
53
- server.on('connection', (tcpSocket) => {
54
- // Single-connection proxy
55
- server.close();
109
+ const currentDelay = Math.min(reconnectDelay * Math.pow(2, reconnectAttempts), maxReconnectDelay);
110
+ reconnectAttempts++;
111
+ logger.debug(`Scheduling reconnection attempt ${reconnectAttempts} in ${currentDelay}ms...`);
112
+ updateConnectionState('reconnecting');
113
+ reconnectTimeout = setTimeout(() => {
114
+ logger.debug(`Attempting to reconnect (attempt ${reconnectAttempts})...`);
115
+ setupWebSocket();
116
+ }, currentDelay);
117
+ };
118
+ const setupWebSocket = () => {
119
+ if (!tcpSocket || tcpSocket.destroyed) {
120
+ logger.error('Cannot setup WebSocket: TCP socket is closed');
121
+ return;
122
+ }
123
+ cleanup();
124
+ updateConnectionState('connecting');
56
125
  ws = new WebSocket(remoteURL, {
57
126
  headers: { Authorization: `Bearer ${token}` },
58
127
  perMessageDeflate: false,
59
128
  });
60
- // WebSocket error
61
- ws.once('error', (err) => {
62
- console.error('WebSocket error:', err);
63
- tcpSocket.destroy();
64
- close();
129
+ ws.on('error', (err) => {
130
+ logger.error('WebSocket error:', err.message || err);
131
+ });
132
+ ws.on('close', () => {
133
+ if (pingInterval) {
134
+ clearInterval(pingInterval);
135
+ pingInterval = undefined;
136
+ }
137
+ const shouldReconnect = !intentionalDisconnect && connectionState !== 'disconnected';
138
+ updateConnectionState('disconnected');
139
+ logger.debug('WebSocket disconnected');
140
+ // Pause TCP socket to apply backpressure - TCP will handle buffering
141
+ if (tcpSocket && !tcpSocket.destroyed && !tcpSocket.isPaused()) {
142
+ logger.debug('Pausing TCP socket (applying backpressure)');
143
+ tcpSocket.pause();
144
+ }
145
+ if (shouldReconnect && tcpSocket && !tcpSocket.destroyed) {
146
+ scheduleReconnect();
147
+ }
65
148
  });
66
- ws.once('open', () => {
67
- const socket = ws; // non-undefined after open
149
+ ws.on('open', () => {
150
+ const socket = ws;
151
+ logger.debug('WebSocket connected');
152
+ reconnectAttempts = 0;
153
+ updateConnectionState('connected');
68
154
  pingInterval = setInterval(() => {
69
155
  if (socket.readyState === WebSocket.OPEN) {
70
156
  socket.ping();
71
157
  }
72
158
  }, 30000);
73
- // TCP WS
74
- tcpSocket.on('data', (chunk) => {
159
+ // Resume TCP socket - queued data will flow through
160
+ if (tcpSocket && tcpSocket.isPaused()) {
161
+ logger.debug('Resuming TCP socket (releasing backpressure)');
162
+ tcpSocket.resume();
163
+ }
164
+ // TCP → WS: Forward data directly
165
+ const onTcpData = (chunk) => {
75
166
  if (socket.readyState === WebSocket.OPEN) {
76
167
  socket.send(chunk);
77
168
  }
78
- });
169
+ // If WebSocket is not ready, data will queue in TCP buffers (backpressure)
170
+ };
171
+ // Remove old listener if exists and add new one
172
+ tcpSocket.removeListener('data', onTcpData);
173
+ tcpSocket.on('data', onTcpData);
79
174
  // WS → TCP
80
175
  socket.on('message', (data) => {
81
- if (!tcpSocket.destroyed) {
176
+ if (tcpSocket && !tcpSocket.destroyed) {
82
177
  tcpSocket.write(data);
83
178
  }
84
179
  });
85
180
  });
86
- // Mutual close
87
- tcpSocket.on('close', close);
88
- tcpSocket.on('error', (err) => {
89
- console.error('TCP socket error:', err);
181
+ };
182
+ // TCP server error
183
+ server.once('error', (err) => {
184
+ close();
185
+ reject(new Error(`TCP server error: ${err.message}`));
186
+ });
187
+ const getConnectionState = () => {
188
+ return connectionState;
189
+ };
190
+ const onConnectionStateChange = (callback) => {
191
+ stateChangeCallbacks.add(callback);
192
+ return () => {
193
+ stateChangeCallbacks.delete(callback);
194
+ };
195
+ };
196
+ // Listening
197
+ server.once('listening', () => {
198
+ const address = server.address();
199
+ if (!address || typeof address === 'string') {
90
200
  close();
201
+ return reject(new Error('Failed to obtain listening address'));
202
+ }
203
+ resolve({
204
+ address,
205
+ close,
206
+ getConnectionState,
207
+ onConnectionStateChange,
208
+ });
209
+ });
210
+ // Helper to clean up current connection but keep server alive
211
+ const cleanupConnection = () => {
212
+ cleanup();
213
+ updateConnectionState('disconnected');
214
+ if (tcpSocket && !tcpSocket.destroyed) {
215
+ tcpSocket.destroy();
216
+ tcpSocket = undefined;
217
+ }
218
+ // Reset reconnection state for next connection
219
+ reconnectAttempts = 0;
220
+ intentionalDisconnect = false;
221
+ logger.debug('Connection cleaned up, ready for new TCP connection');
222
+ };
223
+ // On TCP connection (can happen multiple times)
224
+ server.on('connection', (socket) => {
225
+ // If there's already an active connection, reject the new one
226
+ if (tcpSocket && !tcpSocket.destroyed) {
227
+ logger.debug('Rejecting new TCP connection - already have an active connection');
228
+ socket.destroy();
229
+ return;
230
+ }
231
+ logger.debug('TCP client connected');
232
+ tcpSocket = socket;
233
+ // TCP socket handlers
234
+ tcpSocket.on('close', () => {
235
+ logger.debug('TCP socket closed by client');
236
+ cleanupConnection();
237
+ });
238
+ tcpSocket.on('error', (err) => {
239
+ logger.error('TCP socket error:', err);
240
+ cleanupConnection();
91
241
  });
92
- ws.on('close', () => tcpSocket.destroy());
242
+ // Start WebSocket connection
243
+ setupWebSocket();
93
244
  });
94
245
  // Start listening
95
246
  server.listen(port, hostname);
package/tunnel.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tunnel.mjs","sourceRoot":"","sources":["src/tunnel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tunnel.mjs","sourceRoot":"","sources":["src/tunnel.ts"],"names":[],"mappings":"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"}