@push.rocks/smartproxy 19.5.10 → 19.5.13

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartproxy",
3
- "version": "19.5.10",
3
+ "version": "19.5.13",
4
4
  "private": false,
5
5
  "description": "A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.",
6
6
  "main": "dist_ts/index.js",
package/readme.hints.md CHANGED
@@ -413,4 +413,55 @@ const routes: IRouteConfig[] = [{
413
413
  ### 7. Next Steps (Remaining Work)
414
414
  - **Phase 2 (cont)**: Migrate components to use LifecycleComponent
415
415
  - **Phase 3**: Add worker threads for CPU-intensive operations
416
- - **Phase 4**: Performance monitoring dashboard
416
+ - **Phase 4**: Performance monitoring dashboard
417
+
418
+ ## Socket Error Handling Fix (v19.5.11+)
419
+
420
+ ### Issue
421
+ Server crashed with unhandled 'error' event when backend connections failed (ECONNREFUSED). Also caused memory leak with rising active connection count as failed connections weren't cleaned up properly.
422
+
423
+ ### Root Cause
424
+ 1. **Race Condition**: In forwarding handlers, sockets were created with `net.connect()` but error handlers were attached later, creating a window where errors could crash the server
425
+ 2. **Incomplete Cleanup**: When server connections failed, client sockets weren't properly cleaned up, leaving connection records in memory
426
+
427
+ ### Solution
428
+ Created `createSocketWithErrorHandler()` utility that attaches error handlers immediately:
429
+ ```typescript
430
+ // Before (race condition):
431
+ const socket = net.connect(port, host);
432
+ // ... other code ...
433
+ socket.on('error', handler); // Too late!
434
+
435
+ // After (safe):
436
+ const socket = createSocketWithErrorHandler({
437
+ port, host,
438
+ onError: (error) => {
439
+ // Handle error immediately
440
+ clientSocket.destroy();
441
+ },
442
+ onConnect: () => {
443
+ // Set up forwarding
444
+ }
445
+ });
446
+ ```
447
+
448
+ ### Changes Made
449
+ 1. **New Utility**: `ts/core/utils/socket-utils.ts` - Added `createSocketWithErrorHandler()`
450
+ 2. **Updated Handlers**:
451
+ - `https-passthrough-handler.ts` - Uses safe socket creation
452
+ - `https-terminate-to-http-handler.ts` - Uses safe socket creation
453
+ 3. **Connection Cleanup**: Client sockets destroyed immediately on server connection failure
454
+
455
+ ### Test Coverage
456
+ - `test/test.socket-error-handling.node.ts` - Verifies server doesn't crash on ECONNREFUSED
457
+ - `test/test.forwarding-error-fix.node.ts` - Tests forwarding handlers handle errors gracefully
458
+
459
+ ### Configuration
460
+ No configuration changes needed. The fix is transparent to users.
461
+
462
+ ### Important Note
463
+ The fix was applied in two places:
464
+ 1. **ForwardingHandler classes** (`https-passthrough-handler.ts`, etc.) - These are standalone forwarding utilities
465
+ 2. **SmartProxy route-connection-handler** (`route-connection-handler.ts`) - This is where the actual SmartProxy connection handling happens
466
+
467
+ The critical fix for SmartProxy was in `setupDirectConnection()` method in route-connection-handler.ts, which now uses `createSocketWithErrorHandler()` to properly handle connection failures and clean up connection records.
package/readme.plan.md CHANGED
@@ -1,337 +1,165 @@
1
- # SmartProxy Socket Cleanup Fix Plan
1
+ # SmartProxy Socket Handling Fix Plan
2
2
 
3
- ## Problem Summary
3
+ Reread CLAUDE.md file for guidelines
4
4
 
5
- The current socket cleanup implementation is too aggressive and closes long-lived connections prematurely. This affects:
6
- - WebSocket connections in HTTPS passthrough
7
- - Long-lived HTTP connections (SSE, streaming)
8
- - Database connections
9
- - Any connection that should remain open for hours
5
+ ## Implementation Summary (COMPLETED)
10
6
 
11
- ## Root Causes
7
+ The critical socket handling issues have been fixed:
12
8
 
13
- ### 1. **Bilateral Socket Cleanup**
14
- When one socket closes, both sockets are immediately destroyed:
15
- ```typescript
16
- // In createSocketCleanupHandler
17
- cleanupSocket(clientSocket, 'client');
18
- cleanupSocket(serverSocket, 'server'); // Both destroyed together!
19
- ```
9
+ 1. **Prevented Server Crashes**: Created `createSocketWithErrorHandler()` utility that attaches error handlers immediately upon socket creation, preventing unhandled ECONNREFUSED errors from crashing the server.
20
10
 
21
- ### 2. **Aggressive Timeout Handling**
22
- Timeout events immediately trigger connection cleanup:
23
- ```typescript
24
- socket.on('timeout', () => {
25
- handleClose(`${prefix}_timeout`); // Destroys both sockets!
26
- });
27
- ```
11
+ 2. **Fixed Memory Leaks**: Updated forwarding handlers to properly clean up client sockets when server connections fail, ensuring connection records are removed from tracking.
28
12
 
29
- ### 3. **Parity Check Forces Closure**
30
- If one socket closes but the other remains open for >2 minutes, connection is forcefully terminated:
31
- ```typescript
32
- if (record.outgoingClosedTime &&
33
- !record.incoming.destroyed &&
34
- now - record.outgoingClosedTime > 120000) {
35
- this.cleanupConnection(record, 'parity_check');
36
- }
37
- ```
13
+ 3. **Key Changes Made**:
14
+ - Added `createSocketWithErrorHandler()` in `socket-utils.ts`
15
+ - Updated `https-passthrough-handler.ts` to use safe socket creation
16
+ - Updated `https-terminate-to-http-handler.ts` to use safe socket creation
17
+ - Ensured client sockets are destroyed when server connections fail
18
+ - Connection cleanup now triggered by socket close events
38
19
 
39
- ### 4. **No Half-Open Connection Support**
40
- The proxy doesn't support TCP half-open connections where one side closes while the other continues sending.
20
+ 4. **Test Results**: Server no longer crashes on ECONNREFUSED errors, and connections are properly cleaned up.
41
21
 
42
- ## Fix Implementation Plan
22
+ ## Problem Summary
43
23
 
44
- ### Phase 1: Fix Socket Cleanup (Prevent Premature Closure)
24
+ The SmartProxy server is experiencing critical issues:
25
+ 1. **Server crashes** due to unhandled socket connection errors (ECONNREFUSED)
26
+ 2. **Memory leak** with steadily rising active connection count
27
+ 3. **Race conditions** between socket creation and error handler attachment
28
+ 4. **Orphaned sockets** when server connections fail
45
29
 
46
- #### 1.1 Modify `cleanupSocket()` to support graceful shutdown
47
- ```typescript
48
- export interface CleanupOptions {
49
- immediate?: boolean; // Force immediate destruction
50
- allowDrain?: boolean; // Allow write buffer to drain
51
- gracePeriod?: number; // Ms to wait before force close
52
- }
30
+ ## Root Causes
53
31
 
54
- export function cleanupSocket(
55
- socket: Socket | TLSSocket | null,
56
- socketName?: string,
57
- options: CleanupOptions = {}
58
- ): Promise<void> {
59
- if (!socket || socket.destroyed) return Promise.resolve();
60
-
61
- return new Promise<void>((resolve) => {
62
- const cleanup = () => {
63
- socket.removeAllListeners();
64
- if (!socket.destroyed) {
65
- socket.destroy();
66
- }
67
- resolve();
68
- };
69
-
70
- if (options.immediate) {
71
- cleanup();
72
- } else if (options.allowDrain && socket.writable) {
73
- // Allow pending writes to complete
74
- socket.end(() => cleanup());
75
-
76
- // Force cleanup after grace period
77
- if (options.gracePeriod) {
78
- setTimeout(cleanup, options.gracePeriod);
79
- }
80
- } else {
81
- cleanup();
82
- }
83
- });
84
- }
85
- ```
32
+ ### 1. Delayed Error Handler Attachment
33
+ - Sockets created without immediate error handlers
34
+ - Error events can fire before handlers attached
35
+ - Causes uncaught exceptions and server crashes
86
36
 
87
- #### 1.2 Implement Independent Socket Tracking
88
- ```typescript
89
- export function createIndependentSocketHandlers(
90
- clientSocket: Socket,
91
- serverSocket: Socket,
92
- onBothClosed: (reason: string) => void
93
- ): { cleanupClient: () => void, cleanupServer: () => void } {
94
- let clientClosed = false;
95
- let serverClosed = false;
96
- let clientReason = '';
97
- let serverReason = '';
98
-
99
- const checkBothClosed = () => {
100
- if (clientClosed && serverClosed) {
101
- onBothClosed(`client: ${clientReason}, server: ${serverReason}`);
102
- }
103
- };
104
-
105
- const cleanupClient = async (reason: string) => {
106
- if (clientClosed) return;
107
- clientClosed = true;
108
- clientReason = reason;
109
-
110
- // Allow server to continue if still active
111
- if (!serverClosed && serverSocket.writable) {
112
- // Half-close: stop reading from client, let server finish
113
- clientSocket.pause();
114
- clientSocket.unpipe(serverSocket);
115
- await cleanupSocket(clientSocket, 'client', { allowDrain: true });
116
- } else {
117
- await cleanupSocket(clientSocket, 'client');
118
- }
119
-
120
- checkBothClosed();
121
- };
122
-
123
- const cleanupServer = async (reason: string) => {
124
- if (serverClosed) return;
125
- serverClosed = true;
126
- serverReason = reason;
127
-
128
- // Allow client to continue if still active
129
- if (!clientClosed && clientSocket.writable) {
130
- // Half-close: stop reading from server, let client finish
131
- serverSocket.pause();
132
- serverSocket.unpipe(clientSocket);
133
- await cleanupSocket(serverSocket, 'server', { allowDrain: true });
134
- } else {
135
- await cleanupSocket(serverSocket, 'server');
136
- }
137
-
138
- checkBothClosed();
139
- };
140
-
141
- return { cleanupClient, cleanupServer };
142
- }
143
- ```
37
+ ### 2. Incomplete Cleanup Logic
38
+ - Client sockets not cleaned up when server connection fails
39
+ - Connection counter only decrements after BOTH sockets close
40
+ - Failed server connections leave orphaned client sockets
144
41
 
145
- ### Phase 2: Fix Timeout Handling
42
+ ### 3. Missing Global Error Handlers
43
+ - No process-level uncaughtException handler
44
+ - No process-level unhandledRejection handler
45
+ - Any unhandled error crashes entire server
146
46
 
147
- #### 2.1 Separate timeout handling from connection closure
148
- ```typescript
149
- export function setupSocketHandlers(
150
- socket: Socket | TLSSocket,
151
- handleClose: (reason: string) => void,
152
- handleTimeout?: (socket: Socket) => void, // New optional handler
153
- errorPrefix?: string
154
- ): void {
155
- socket.on('error', (error) => {
156
- const prefix = errorPrefix || 'Socket';
157
- handleClose(`${prefix}_error: ${error.message}`);
158
- });
159
-
160
- socket.on('close', () => {
161
- const prefix = errorPrefix || 'socket';
162
- handleClose(`${prefix}_closed`);
163
- });
164
-
165
- socket.on('timeout', () => {
166
- if (handleTimeout) {
167
- handleTimeout(socket); // Custom timeout handling
168
- } else {
169
- // Default: just log, don't close
170
- console.warn(`Socket timeout: ${errorPrefix || 'socket'}`);
171
- }
172
- });
173
- }
174
- ```
47
+ ## Implementation Plan
175
48
 
176
- #### 2.2 Update HTTPS passthrough handler
177
- ```typescript
178
- // In https-passthrough-handler.ts
179
- const { cleanupClient, cleanupServer } = createIndependentSocketHandlers(
180
- clientSocket,
181
- serverSocket,
182
- (reason) => {
183
- this.emit(ForwardingHandlerEvents.DISCONNECTED, {
184
- remoteAddress,
185
- bytesSent,
186
- bytesReceived,
187
- reason
188
- });
189
- }
190
- );
191
-
192
- // Setup handlers with custom timeout handling
193
- setupSocketHandlers(clientSocket, cleanupClient, (socket) => {
194
- // Just reset timeout, don't close
195
- socket.setTimeout(timeout);
196
- }, 'client');
197
-
198
- setupSocketHandlers(serverSocket, cleanupServer, (socket) => {
199
- // Just reset timeout, don't close
200
- socket.setTimeout(timeout);
201
- }, 'server');
202
- ```
49
+ ### Phase 1: Prevent Server Crashes (Critical)
203
50
 
204
- ### Phase 3: Fix Connection Manager
51
+ #### 1.1 Add Global Error Handlers
52
+ - [x] ~~Add global error handlers in main entry point~~ (Removed per user request - no global handlers)
53
+ - [x] Log errors with context
54
+ - [x] ~~Implement graceful shutdown sequence~~ (Removed - handled locally)
205
55
 
206
- #### 3.1 Remove aggressive parity check
207
- ```typescript
208
- // Remove or significantly increase the parity check timeout
209
- // From 2 minutes to 30 minutes for long-lived connections
210
- if (record.outgoingClosedTime &&
211
- !record.incoming.destroyed &&
212
- !record.connectionClosed &&
213
- now - record.outgoingClosedTime > 1800000) { // 30 minutes
214
- // Only close if no data activity
215
- if (now - record.lastActivity > 600000) { // 10 minutes of inactivity
216
- this.cleanupConnection(record, 'parity_check');
217
- }
218
- }
219
- ```
56
+ #### 1.2 Fix Socket Creation Race Condition
57
+ - [x] Modify socket creation to attach error handlers immediately
58
+ - [x] Update all forwarding handlers (https-passthrough, http, etc.)
59
+ - [x] Ensure error handlers attached in same tick as socket creation
220
60
 
221
- #### 3.2 Update cleanupConnection to check socket states
222
- ```typescript
223
- public cleanupConnection(record: IConnectionRecord, reason: string = 'normal'): void {
224
- if (!record.connectionClosed) {
225
- record.connectionClosed = true;
226
-
227
- // Only cleanup sockets that are actually closed or inactive
228
- if (record.incoming && (!record.incoming.writable || record.incoming.destroyed)) {
229
- cleanupSocket(record.incoming, `${record.id}-incoming`, { immediate: true });
230
- }
231
-
232
- if (record.outgoing && (!record.outgoing.writable || record.outgoing.destroyed)) {
233
- cleanupSocket(record.outgoing, `${record.id}-outgoing`, { immediate: true });
234
- }
235
-
236
- // If either socket is still active, don't remove the record yet
237
- if ((record.incoming && record.incoming.writable) ||
238
- (record.outgoing && record.outgoing.writable)) {
239
- record.connectionClosed = false; // Reset flag
240
- return; // Don't finish cleanup
241
- }
242
-
243
- // Continue with full cleanup...
244
- }
245
- }
246
- ```
61
+ ### Phase 2: Fix Memory Leaks (High Priority)
247
62
 
248
- ### Phase 4: Testing and Validation
63
+ #### 2.1 Fix Connection Cleanup Logic
64
+ - [x] Clean up client socket immediately if server connection fails
65
+ - [x] Decrement connection counter on any socket failure (handled by socket close events)
66
+ - [x] Implement proper cleanup for half-open connections
249
67
 
250
- #### 4.1 Test Cases to Implement
251
- 1. WebSocket connection should stay open for >1 hour
252
- 2. HTTP streaming response should continue after request closes
253
- 3. Half-open connections should work correctly
254
- 4. Verify no socket leaks with long-running connections
255
- 5. Test graceful shutdown with pending data
68
+ #### 2.2 Improve Socket Utils
69
+ - [x] Create new utility function for safe socket creation with immediate error handling
70
+ - [x] Update createIndependentSocketHandlers to handle immediate failures
71
+ - [ ] Add connection tracking debug utilities
256
72
 
257
- #### 4.2 Socket Leak Prevention
258
- - Ensure all event listeners are tracked and removed
259
- - Use WeakMap for socket metadata to prevent memory leaks
260
- - Implement connection count monitoring
261
- - Add periodic health checks for orphaned sockets
73
+ ### Phase 3: Comprehensive Testing (Important)
262
74
 
263
- ## Implementation Order
75
+ #### 3.1 Create Test Cases
76
+ - [x] Test ECONNREFUSED scenario
77
+ - [ ] Test timeout handling
78
+ - [ ] Test half-open connections
79
+ - [ ] Test rapid connect/disconnect cycles
264
80
 
265
- 1. **Day 1**: Implement graceful `cleanupSocket()` and independent socket handlers
266
- 2. **Day 2**: Update all handlers to use new cleanup mechanism
267
- 3. **Day 3**: Fix timeout handling to not close connections
268
- 4. **Day 4**: Update connection manager parity check and cleanup logic
269
- 5. **Day 5**: Comprehensive testing and leak detection
81
+ #### 3.2 Add Monitoring
82
+ - [ ] Add connection leak detection
83
+ - [ ] Add metrics for connection lifecycle
84
+ - [ ] Add debug logging for socket state transitions
270
85
 
271
- ## Configuration Changes
86
+ ## Detailed Implementation Steps
272
87
 
273
- Add new options to SmartProxyOptions:
88
+ ### Step 1: Global Error Handlers (ts/proxies/smart-proxy/smart-proxy.ts)
274
89
  ```typescript
275
- interface ISmartProxyOptions {
276
- // Existing options...
277
-
278
- // New options for long-lived connections
279
- socketCleanupGracePeriod?: number; // Default: 5000ms
280
- allowHalfOpenConnections?: boolean; // Default: true
281
- parityCheckTimeout?: number; // Default: 1800000ms (30 min)
282
- timeoutBehavior?: 'close' | 'reset' | 'ignore'; // Default: 'reset'
283
- }
90
+ // Add in constructor or start method
91
+ process.on('uncaughtException', (error) => {
92
+ logger.log('error', 'Uncaught exception', { error });
93
+ // Graceful shutdown
94
+ });
95
+
96
+ process.on('unhandledRejection', (reason, promise) => {
97
+ logger.log('error', 'Unhandled rejection', { reason, promise });
98
+ });
284
99
  ```
285
100
 
286
- ## Success Metrics
101
+ ### Step 2: Safe Socket Creation Utility (ts/core/utils/socket-utils.ts)
102
+ ```typescript
103
+ export function createSocketWithErrorHandler(
104
+ options: net.NetConnectOpts,
105
+ onError: (err: Error) => void
106
+ ): net.Socket {
107
+ const socket = net.connect(options);
108
+ socket.on('error', onError);
109
+ return socket;
110
+ }
111
+ ```
287
112
 
288
- 1. WebSocket connections remain stable for 24+ hours
289
- 2. No premature connection closures reported
290
- 3. Memory usage remains stable (no socket leaks)
291
- 4. Half-open connections work correctly
292
- 5. Graceful shutdown completes within grace period
113
+ ### Step 3: Fix HttpsPassthroughHandler (ts/forwarding/handlers/https-passthrough-handler.ts)
114
+ - Replace direct socket creation with safe creation
115
+ - Handle server connection failures immediately
116
+ - Clean up client socket on server connection failure
293
117
 
294
- ## Implementation Status: COMPLETED
118
+ ### Step 4: Fix Connection Counting
119
+ - Decrement on ANY socket close, not just when both close
120
+ - Track failed connections separately
121
+ - Add connection state tracking
295
122
 
296
- ### Implemented Changes
123
+ ### Step 5: Update All Handlers
124
+ - [ ] https-passthrough-handler.ts
125
+ - [ ] http-handler.ts
126
+ - [ ] https-terminate-to-http-handler.ts
127
+ - [ ] https-terminate-to-https-handler.ts
128
+ - [ ] route-connection-handler.ts
297
129
 
298
- 1. **Modified `cleanupSocket()` in `socket-utils.ts`**
299
- - Added `CleanupOptions` interface with `immediate`, `allowDrain`, and `gracePeriod` options
300
- - Implemented graceful shutdown support with write buffer draining
130
+ ## Success Criteria
301
131
 
302
- 2. **Created `createIndependentSocketHandlers()` in `socket-utils.ts`**
303
- - Tracks socket states independently
304
- - Supports half-open connections where one side can close while the other remains open
305
- - Only triggers full cleanup when both sockets are closed
132
+ 1. **No server crashes** on ECONNREFUSED or other socket errors
133
+ 2. **Active connections** remain stable (no steady increase)
134
+ 3. **All sockets** properly cleaned up on errors
135
+ 4. **Memory usage** remains stable under load
136
+ 5. **Graceful handling** of all error scenarios
306
137
 
307
- 3. **Updated `setupSocketHandlers()` in `socket-utils.ts`**
308
- - Added optional `handleTimeout` parameter to customize timeout behavior
309
- - Prevents automatic connection closure on timeout events
138
+ ## Testing Plan
310
139
 
311
- 4. **Updated HTTPS Passthrough Handler**
312
- - Now uses `createIndependentSocketHandlers` for half-open support
313
- - Custom timeout handling that resets timer instead of closing connection
314
- - Manual data forwarding with backpressure handling
140
+ 1. Simulate ECONNREFUSED by targeting closed ports
141
+ 2. Monitor active connection count over time
142
+ 3. Stress test with rapid connections
143
+ 4. Test with unreachable hosts
144
+ 5. Test with slow/timing out connections
315
145
 
316
- 5. **Updated Connection Manager**
317
- - Extended parity check from 2 minutes to 30 minutes
318
- - Added activity check before closing (10 minutes of inactivity required)
319
- - Modified cleanup to check socket states before destroying
146
+ ## Rollback Plan
320
147
 
321
- 6. **Updated Basic Forwarding in Route Connection Handler**
322
- - Replaced simple `pipe()` with independent socket handlers
323
- - Added manual data forwarding with backpressure support
324
- - Removed bilateral close handlers to prevent premature cleanup
148
+ If issues arise:
149
+ 1. Revert socket creation changes
150
+ 2. Keep global error handlers (they add safety)
151
+ 3. Add more detailed logging for debugging
152
+ 4. Implement fixes incrementally
325
153
 
326
- ### Test Results
154
+ ## Timeline
327
155
 
328
- All tests passing:
329
- - Long-lived connection test: Connection stayed open for 61+ seconds with periodic keep-alive
330
- - Half-open connection test: One side closed while the other continued to send data
331
- - ✅ No socket leaks or premature closures
156
+ - Phase 1: Immediate (prevents crashes)
157
+ - Phase 2: Within 24 hours (fixes leaks)
158
+ - Phase 3: Within 48 hours (ensures stability)
332
159
 
333
- ### Notes
160
+ ## Notes
334
161
 
335
- - The fix maintains backward compatibility
336
- - No configuration changes required for existing deployments
337
- - Long-lived connections now work correctly in both HTTPS passthrough and basic forwarding modes
162
+ - The race condition is the most critical issue
163
+ - Connection counting logic needs complete overhaul
164
+ - Consider using a connection state machine for clarity
165
+ - Add connection lifecycle events for debugging
@@ -6,6 +6,14 @@ export interface CleanupOptions {
6
6
  gracePeriod?: number; // Ms to wait before force close
7
7
  }
8
8
 
9
+ export interface SafeSocketOptions {
10
+ port: number;
11
+ host: string;
12
+ onError?: (error: Error) => void;
13
+ onConnect?: () => void;
14
+ timeout?: number;
15
+ }
16
+
9
17
  /**
10
18
  * Safely cleanup a socket by removing all listeners and destroying it
11
19
  * @param socket The socket to cleanup
@@ -197,4 +205,39 @@ export function pipeSockets(
197
205
  ): void {
198
206
  socket1.pipe(socket2);
199
207
  socket2.pipe(socket1);
208
+ }
209
+
210
+ /**
211
+ * Create a socket with immediate error handling to prevent crashes
212
+ * @param options Socket creation options
213
+ * @returns The created socket
214
+ */
215
+ export function createSocketWithErrorHandler(options: SafeSocketOptions): plugins.net.Socket {
216
+ const { port, host, onError, onConnect, timeout } = options;
217
+
218
+ // Create socket with immediate error handler attachment
219
+ const socket = new plugins.net.Socket();
220
+
221
+ // Attach error handler BEFORE connecting to catch immediate errors
222
+ socket.on('error', (error) => {
223
+ console.error(`Socket connection error to ${host}:${port}: ${error.message}`);
224
+ if (onError) {
225
+ onError(error);
226
+ }
227
+ });
228
+
229
+ // Attach connect handler if provided
230
+ if (onConnect) {
231
+ socket.on('connect', onConnect);
232
+ }
233
+
234
+ // Set timeout if provided
235
+ if (timeout) {
236
+ socket.setTimeout(timeout);
237
+ }
238
+
239
+ // Now attempt to connect - any immediate errors will be caught
240
+ socket.connect(port, host);
241
+
242
+ return socket;
200
243
  }