@push.rocks/smartproxy 19.5.5 → 19.5.6

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 (28) hide show
  1. package/dist_ts/core/utils/index.d.ts +1 -0
  2. package/dist_ts/core/utils/index.js +2 -1
  3. package/dist_ts/core/utils/socket-utils.d.ts +28 -0
  4. package/dist_ts/core/utils/socket-utils.js +77 -0
  5. package/dist_ts/forwarding/handlers/http-handler.js +7 -4
  6. package/dist_ts/forwarding/handlers/https-passthrough-handler.js +14 -55
  7. package/dist_ts/forwarding/handlers/https-terminate-to-http-handler.js +52 -40
  8. package/dist_ts/forwarding/handlers/https-terminate-to-https-handler.js +31 -43
  9. package/dist_ts/proxies/http-proxy/connection-pool.js +4 -19
  10. package/dist_ts/proxies/http-proxy/http-proxy.js +3 -7
  11. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +0 -4
  12. package/dist_ts/proxies/smart-proxy/connection-manager.js +7 -30
  13. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +13 -2
  14. package/dist_ts/proxies/smart-proxy/port-manager.js +3 -3
  15. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +35 -4
  16. package/package.json +1 -1
  17. package/ts/core/utils/index.ts +1 -0
  18. package/ts/core/utils/socket-utils.ts +96 -0
  19. package/ts/forwarding/handlers/http-handler.ts +7 -3
  20. package/ts/forwarding/handlers/https-passthrough-handler.ts +13 -62
  21. package/ts/forwarding/handlers/https-terminate-to-http-handler.ts +58 -46
  22. package/ts/forwarding/handlers/https-terminate-to-https-handler.ts +38 -53
  23. package/ts/proxies/http-proxy/connection-pool.ts +3 -16
  24. package/ts/proxies/http-proxy/http-proxy.ts +2 -5
  25. package/ts/proxies/smart-proxy/connection-manager.ts +6 -28
  26. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +15 -1
  27. package/ts/proxies/smart-proxy/port-manager.ts +2 -2
  28. package/ts/proxies/smart-proxy/route-connection-handler.ts +39 -4
@@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
2
2
  import { ForwardingHandler } from './base-handler.js';
3
3
  import type { IForwardConfig } from '../config/forwarding-types.js';
4
4
  import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
5
+ import { createSocketCleanupHandler, setupSocketHandlers } from '../../core/utils/socket-utils.js';
5
6
 
6
7
  /**
7
8
  * Handler for HTTPS termination with HTTP backend
@@ -95,76 +96,87 @@ export class HttpsTerminateToHttpHandler extends ForwardingHandler {
95
96
  tls: true
96
97
  });
97
98
 
98
- // Handle TLS errors
99
- tlsSocket.on('error', (error) => {
100
- this.emit(ForwardingHandlerEvents.ERROR, {
99
+ // Variables to track connections
100
+ let backendSocket: plugins.net.Socket | null = null;
101
+ let dataBuffer = Buffer.alloc(0);
102
+ let connectionEstablished = false;
103
+
104
+ // Create cleanup handler for all sockets
105
+ const handleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
106
+ this.emit(ForwardingHandlerEvents.DISCONNECTED, {
101
107
  remoteAddress,
102
- error: `TLS error: ${error.message}`
108
+ reason
103
109
  });
104
-
105
- if (!tlsSocket.destroyed) {
106
- tlsSocket.destroy();
107
- }
110
+ dataBuffer = Buffer.alloc(0);
111
+ connectionEstablished = false;
108
112
  });
109
113
 
110
- // The TLS socket will now emit HTTP traffic that can be processed
111
- // In a real implementation, we would create an HTTP parser and handle
112
- // the requests here, but for simplicity, we'll just log the data
114
+ // Set up error handling with our cleanup utility
115
+ setupSocketHandlers(tlsSocket, handleClose, 'tls');
113
116
 
114
- let dataBuffer = Buffer.alloc(0);
117
+ // Set timeout
118
+ const timeout = this.getTimeout();
119
+ tlsSocket.setTimeout(timeout);
120
+
121
+ tlsSocket.on('timeout', () => {
122
+ this.emit(ForwardingHandlerEvents.ERROR, {
123
+ remoteAddress,
124
+ error: 'TLS connection timeout'
125
+ });
126
+ handleClose('timeout');
127
+ });
115
128
 
129
+ // Handle TLS data
116
130
  tlsSocket.on('data', (data) => {
131
+ // If backend connection already established, just forward the data
132
+ if (connectionEstablished && backendSocket && !backendSocket.destroyed) {
133
+ backendSocket.write(data);
134
+ return;
135
+ }
136
+
117
137
  // Append to buffer
118
138
  dataBuffer = Buffer.concat([dataBuffer, data]);
119
139
 
120
140
  // Very basic HTTP parsing - in a real implementation, use http-parser
121
- if (dataBuffer.includes(Buffer.from('\r\n\r\n'))) {
141
+ if (dataBuffer.includes(Buffer.from('\r\n\r\n')) && !connectionEstablished) {
122
142
  const target = this.getTargetFromConfig();
123
143
 
124
- // Simple example: forward the data to an HTTP server
125
- const socket = plugins.net.connect(target.port, target.host, () => {
126
- socket.write(dataBuffer);
127
- dataBuffer = Buffer.alloc(0);
144
+ // Create backend connection
145
+ backendSocket = plugins.net.connect(target.port, target.host, () => {
146
+ connectionEstablished = true;
147
+
148
+ // Send buffered data
149
+ if (dataBuffer.length > 0) {
150
+ backendSocket!.write(dataBuffer);
151
+ dataBuffer = Buffer.alloc(0);
152
+ }
128
153
 
129
154
  // Set up bidirectional data flow
130
- tlsSocket.pipe(socket);
131
- socket.pipe(tlsSocket);
155
+ tlsSocket.pipe(backendSocket!);
156
+ backendSocket!.pipe(tlsSocket);
132
157
  });
133
158
 
134
- socket.on('error', (error) => {
159
+ // Update the cleanup handler with the backend socket
160
+ const newHandleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
161
+ this.emit(ForwardingHandlerEvents.DISCONNECTED, {
162
+ remoteAddress,
163
+ reason
164
+ });
165
+ dataBuffer = Buffer.alloc(0);
166
+ connectionEstablished = false;
167
+ });
168
+
169
+ // Set up handlers for backend socket
170
+ setupSocketHandlers(backendSocket, newHandleClose, 'backend');
171
+
172
+ backendSocket.on('error', (error) => {
135
173
  this.emit(ForwardingHandlerEvents.ERROR, {
136
174
  remoteAddress,
137
175
  error: `Target connection error: ${error.message}`
138
176
  });
139
-
140
- if (!tlsSocket.destroyed) {
141
- tlsSocket.destroy();
142
- }
143
177
  });
144
178
  }
145
179
  });
146
-
147
- // Handle close
148
- tlsSocket.on('close', () => {
149
- this.emit(ForwardingHandlerEvents.DISCONNECTED, {
150
- remoteAddress
151
- });
152
- });
153
-
154
- // Set timeout
155
- const timeout = this.getTimeout();
156
- tlsSocket.setTimeout(timeout);
157
-
158
- tlsSocket.on('timeout', () => {
159
- this.emit(ForwardingHandlerEvents.ERROR, {
160
- remoteAddress,
161
- error: 'TLS connection timeout'
162
- });
163
-
164
- if (!tlsSocket.destroyed) {
165
- tlsSocket.destroy();
166
- }
167
- });
168
180
  }
169
181
 
170
182
  /**
@@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
2
2
  import { ForwardingHandler } from './base-handler.js';
3
3
  import type { IForwardConfig } from '../config/forwarding-types.js';
4
4
  import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
5
+ import { createSocketCleanupHandler, setupSocketHandlers } from '../../core/utils/socket-utils.js';
5
6
 
6
7
  /**
7
8
  * Handler for HTTPS termination with HTTPS backend
@@ -93,28 +94,38 @@ export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
93
94
  tls: true
94
95
  });
95
96
 
96
- // Handle TLS errors
97
- tlsSocket.on('error', (error) => {
98
- this.emit(ForwardingHandlerEvents.ERROR, {
97
+ // Variable to track backend socket
98
+ let backendSocket: plugins.tls.TLSSocket | null = null;
99
+
100
+ // Create cleanup handler for both sockets
101
+ const handleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
102
+ this.emit(ForwardingHandlerEvents.DISCONNECTED, {
99
103
  remoteAddress,
100
- error: `TLS error: ${error.message}`
104
+ reason
101
105
  });
102
-
103
- if (!tlsSocket.destroyed) {
104
- tlsSocket.destroy();
105
- }
106
106
  });
107
107
 
108
- // The TLS socket will now emit HTTP traffic that can be processed
109
- // In a real implementation, we would create an HTTP parser and handle
110
- // the requests here, but for simplicity, we'll just forward the data
108
+ // Set up error handling with our cleanup utility
109
+ setupSocketHandlers(tlsSocket, handleClose, 'tls');
110
+
111
+ // Set timeout
112
+ const timeout = this.getTimeout();
113
+ tlsSocket.setTimeout(timeout);
114
+
115
+ tlsSocket.on('timeout', () => {
116
+ this.emit(ForwardingHandlerEvents.ERROR, {
117
+ remoteAddress,
118
+ error: 'TLS connection timeout'
119
+ });
120
+ handleClose('timeout');
121
+ });
111
122
 
112
123
  // Get the target from configuration
113
124
  const target = this.getTargetFromConfig();
114
125
 
115
126
  // Set up the connection to the HTTPS backend
116
127
  const connectToBackend = () => {
117
- const backendSocket = plugins.tls.connect({
128
+ backendSocket = plugins.tls.connect({
118
129
  host: target.host,
119
130
  port: target.port,
120
131
  // In a real implementation, we would configure TLS options
@@ -127,30 +138,29 @@ export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
127
138
  });
128
139
 
129
140
  // Set up bidirectional data flow
130
- tlsSocket.pipe(backendSocket);
131
- backendSocket.pipe(tlsSocket);
141
+ tlsSocket.pipe(backendSocket!);
142
+ backendSocket!.pipe(tlsSocket);
132
143
  });
133
144
 
145
+ // Update the cleanup handler with the backend socket
146
+ const newHandleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
147
+ this.emit(ForwardingHandlerEvents.DISCONNECTED, {
148
+ remoteAddress,
149
+ reason
150
+ });
151
+ });
152
+
153
+ // Set up handlers for backend socket
154
+ setupSocketHandlers(backendSocket, newHandleClose, 'backend');
155
+
134
156
  backendSocket.on('error', (error) => {
135
157
  this.emit(ForwardingHandlerEvents.ERROR, {
136
158
  remoteAddress,
137
159
  error: `Backend connection error: ${error.message}`
138
160
  });
139
-
140
- if (!tlsSocket.destroyed) {
141
- tlsSocket.destroy();
142
- }
143
- });
144
-
145
- // Handle close
146
- backendSocket.on('close', () => {
147
- if (!tlsSocket.destroyed) {
148
- tlsSocket.destroy();
149
- }
150
161
  });
151
162
 
152
- // Set timeout
153
- const timeout = this.getTimeout();
163
+ // Set timeout for backend socket
154
164
  backendSocket.setTimeout(timeout);
155
165
 
156
166
  backendSocket.on('timeout', () => {
@@ -158,10 +168,7 @@ export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
158
168
  remoteAddress,
159
169
  error: 'Backend connection timeout'
160
170
  });
161
-
162
- if (!backendSocket.destroyed) {
163
- backendSocket.destroy();
164
- }
171
+ newHandleClose('backend_timeout');
165
172
  });
166
173
  };
167
174
 
@@ -169,28 +176,6 @@ export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
169
176
  tlsSocket.on('secure', () => {
170
177
  connectToBackend();
171
178
  });
172
-
173
- // Handle close
174
- tlsSocket.on('close', () => {
175
- this.emit(ForwardingHandlerEvents.DISCONNECTED, {
176
- remoteAddress
177
- });
178
- });
179
-
180
- // Set timeout
181
- const timeout = this.getTimeout();
182
- tlsSocket.setTimeout(timeout);
183
-
184
- tlsSocket.on('timeout', () => {
185
- this.emit(ForwardingHandlerEvents.ERROR, {
186
- remoteAddress,
187
- error: 'TLS connection timeout'
188
- });
189
-
190
- if (!tlsSocket.destroyed) {
191
- tlsSocket.destroy();
192
- }
193
- });
194
179
  }
195
180
 
196
181
  /**
@@ -1,5 +1,6 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { type IHttpProxyOptions, type IConnectionEntry, type ILogger, createLogger } from './models/types.js';
3
+ import { cleanupSocket } from '../../core/utils/socket-utils.js';
3
4
 
4
5
  /**
5
6
  * Manages a pool of backend connections for efficient reuse
@@ -133,14 +134,7 @@ export class ConnectionPool {
133
134
  if ((connection.isIdle && now - connection.lastUsed > idleTimeout) ||
134
135
  connections.length > (this.options.connectionPoolSize || 50)) {
135
136
 
136
- try {
137
- if (!connection.socket.destroyed) {
138
- connection.socket.end();
139
- connection.socket.destroy();
140
- }
141
- } catch (err) {
142
- this.logger.error(`Error destroying pooled connection to ${host}`, err);
143
- }
137
+ cleanupSocket(connection.socket, `pool-${host}-idle`);
144
138
 
145
139
  connections.shift(); // Remove from pool
146
140
  removed++;
@@ -170,14 +164,7 @@ export class ConnectionPool {
170
164
  this.logger.debug(`Closing ${connections.length} connections to ${host}`);
171
165
 
172
166
  for (const connection of connections) {
173
- try {
174
- if (!connection.socket.destroyed) {
175
- connection.socket.end();
176
- connection.socket.destroy();
177
- }
178
- } catch (error) {
179
- this.logger.error(`Error closing connection to ${host}:`, error);
180
- }
167
+ cleanupSocket(connection.socket, `pool-${host}-close`);
181
168
  }
182
169
  }
183
170
 
@@ -18,6 +18,7 @@ import { RequestHandler, type IMetricsTracker } from './request-handler.js';
18
18
  import { WebSocketHandler } from './websocket-handler.js';
19
19
  import { ProxyRouter } from '../../routing/router/index.js';
20
20
  import { RouteRouter } from '../../routing/router/route-router.js';
21
+ import { cleanupSocket } from '../../core/utils/socket-utils.js';
21
22
  import { FunctionCache } from './function-cache.js';
22
23
 
23
24
  /**
@@ -520,11 +521,7 @@ export class HttpProxy implements IMetricsTracker {
520
521
 
521
522
  // Close all tracked sockets
522
523
  for (const socket of this.socketMap.getArray()) {
523
- try {
524
- socket.destroy();
525
- } catch (error) {
526
- this.logger.error('Error destroying socket', error);
527
- }
524
+ cleanupSocket(socket, 'http-proxy-stop');
528
525
  }
529
526
 
530
527
  // Close all connection pool connections
@@ -4,6 +4,7 @@ import { SecurityManager } from './security-manager.js';
4
4
  import { TimeoutManager } from './timeout-manager.js';
5
5
  import { logger } from '../../core/utils/logger.js';
6
6
  import { LifecycleComponent } from '../../core/utils/lifecycle-component.js';
7
+ import { cleanupSocket } from '../../core/utils/socket-utils.js';
7
8
 
8
9
  /**
9
10
  * Manages connection lifecycle, tracking, and cleanup with performance optimizations
@@ -278,10 +279,10 @@ export class ConnectionManager extends LifecycleComponent {
278
279
  }
279
280
 
280
281
  // Handle socket cleanup without delay
281
- this.cleanupSocketImmediate(record, 'incoming', record.incoming);
282
+ cleanupSocket(record.incoming, `${record.id}-incoming`);
282
283
 
283
284
  if (record.outgoing) {
284
- this.cleanupSocketImmediate(record, 'outgoing', record.outgoing);
285
+ cleanupSocket(record.outgoing, `${record.id}-outgoing`);
285
286
  }
286
287
 
287
288
  // Clear pendingData to avoid memory leaks
@@ -313,23 +314,6 @@ export class ConnectionManager extends LifecycleComponent {
313
314
  }
314
315
  }
315
316
 
316
- /**
317
- * Helper method to clean up a socket immediately
318
- */
319
- private cleanupSocketImmediate(record: IConnectionRecord, side: 'incoming' | 'outgoing', socket: plugins.net.Socket): void {
320
- try {
321
- if (!socket.destroyed) {
322
- socket.destroy();
323
- }
324
- } catch (err) {
325
- logger.log('error', `Error destroying ${side} socket: ${err}`, {
326
- connectionId: record.id,
327
- side,
328
- error: err,
329
- component: 'connection-manager'
330
- });
331
- }
332
- }
333
317
 
334
318
  /**
335
319
  * Creates a generic error handler for incoming or outgoing sockets
@@ -552,19 +536,13 @@ export class ConnectionManager extends LifecycleComponent {
552
536
  record.cleanupTimer = undefined;
553
537
  }
554
538
 
555
- // Immediate destruction
539
+ // Immediate destruction using socket-utils
556
540
  if (record.incoming) {
557
- record.incoming.removeAllListeners();
558
- if (!record.incoming.destroyed) {
559
- record.incoming.destroy();
560
- }
541
+ cleanupSocket(record.incoming, `${record.id}-incoming-shutdown`);
561
542
  }
562
543
 
563
544
  if (record.outgoing) {
564
- record.outgoing.removeAllListeners();
565
- if (!record.outgoing.destroyed) {
566
- record.outgoing.destroy();
567
- }
545
+ cleanupSocket(record.outgoing, `${record.id}-outgoing-shutdown`);
568
546
  }
569
547
  } catch (err) {
570
548
  logger.log('error', `Error during connection cleanup: ${err}`, {
@@ -128,10 +128,24 @@ export class HttpProxyBridge {
128
128
  proxySocket.pipe(socket);
129
129
 
130
130
  // Handle cleanup
131
+ let cleanedUp = false;
131
132
  const cleanup = (reason: string) => {
133
+ if (cleanedUp) return;
134
+ cleanedUp = true;
135
+
136
+ // Remove all event listeners to prevent memory leaks
137
+ socket.removeAllListeners('end');
138
+ socket.removeAllListeners('error');
139
+ proxySocket.removeAllListeners('end');
140
+ proxySocket.removeAllListeners('error');
141
+
132
142
  socket.unpipe(proxySocket);
133
143
  proxySocket.unpipe(socket);
134
- proxySocket.destroy();
144
+
145
+ if (!proxySocket.destroyed) {
146
+ proxySocket.destroy();
147
+ }
148
+
135
149
  cleanupCallback(reason);
136
150
  };
137
151
 
@@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js';
2
2
  import type { ISmartProxyOptions } from './models/interfaces.js';
3
3
  import { RouteConnectionHandler } from './route-connection-handler.js';
4
4
  import { logger } from '../../core/utils/logger.js';
5
+ import { cleanupSocket } from '../../core/utils/socket-utils.js';
5
6
 
6
7
  /**
7
8
  * PortManager handles the dynamic creation and removal of port listeners
@@ -64,8 +65,7 @@ export class PortManager {
64
65
  const server = plugins.net.createServer((socket) => {
65
66
  // Check if shutting down
66
67
  if (this.isShuttingDown) {
67
- socket.end();
68
- socket.destroy();
68
+ cleanupSocket(socket, 'port-manager-shutdown');
69
69
  return;
70
70
  }
71
71
 
@@ -9,7 +9,7 @@ import { TlsManager } from './tls-manager.js';
9
9
  import { HttpProxyBridge } from './http-proxy-bridge.js';
10
10
  import { TimeoutManager } from './timeout-manager.js';
11
11
  import { RouteManager } from './route-manager.js';
12
- import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.js';
12
+ import { cleanupSocket } from '../../core/utils/socket-utils.js';
13
13
 
14
14
  /**
15
15
  * Handles new connection processing and setup logic with support for route-based configuration
@@ -84,8 +84,7 @@ export class RouteConnectionHandler {
84
84
  const ipValidation = this.securityManager.validateIP(remoteIP);
85
85
  if (!ipValidation.allowed) {
86
86
  logger.log('warn', `Connection rejected`, { remoteIP, reason: ipValidation.reason, component: 'route-handler' });
87
- socket.end();
88
- socket.destroy();
87
+ cleanupSocket(socket, `rejected-${ipValidation.reason}`);
89
88
  return;
90
89
  }
91
90
 
@@ -822,6 +821,38 @@ export class RouteConnectionHandler {
822
821
  return;
823
822
  }
824
823
 
824
+ // Track event listeners added by the handler so we can clean them up
825
+ const originalOn = socket.on.bind(socket);
826
+ const originalOnce = socket.once.bind(socket);
827
+ const trackedListeners: Array<{event: string; listener: (...args: any[]) => void}> = [];
828
+
829
+ // Override socket.on to track listeners
830
+ socket.on = function(event: string, listener: (...args: any[]) => void) {
831
+ trackedListeners.push({event, listener});
832
+ return originalOn(event, listener);
833
+ } as any;
834
+
835
+ // Override socket.once to track listeners
836
+ socket.once = function(event: string, listener: (...args: any[]) => void) {
837
+ trackedListeners.push({event, listener});
838
+ return originalOnce(event, listener);
839
+ } as any;
840
+
841
+ // Set up automatic cleanup when socket closes
842
+ const cleanupHandler = () => {
843
+ // Remove all tracked listeners
844
+ for (const {event, listener} of trackedListeners) {
845
+ socket.removeListener(event, listener);
846
+ }
847
+ // Restore original methods
848
+ socket.on = originalOn;
849
+ socket.once = originalOnce;
850
+ };
851
+
852
+ // Listen for socket close to trigger cleanup
853
+ originalOnce('close', cleanupHandler);
854
+ originalOnce('error', cleanupHandler);
855
+
825
856
  // Create route context for the handler
826
857
  const routeContext = this.createRouteContext({
827
858
  connectionId: record.id,
@@ -855,6 +886,8 @@ export class RouteConnectionHandler {
855
886
  error: error.message,
856
887
  component: 'route-handler'
857
888
  });
889
+ // Remove all event listeners before destroying to prevent memory leaks
890
+ socket.removeAllListeners();
858
891
  if (!socket.destroyed) {
859
892
  socket.destroy();
860
893
  }
@@ -875,6 +908,8 @@ export class RouteConnectionHandler {
875
908
  error: error.message,
876
909
  component: 'route-handler'
877
910
  });
911
+ // Remove all event listeners before destroying to prevent memory leaks
912
+ socket.removeAllListeners();
878
913
  if (!socket.destroyed) {
879
914
  socket.destroy();
880
915
  }
@@ -1229,7 +1264,7 @@ export class RouteConnectionHandler {
1229
1264
  connectionId,
1230
1265
  serverName,
1231
1266
  connInfo,
1232
- (connectionId, reason) => this.connectionManager.initiateCleanupOnce(record, reason)
1267
+ (_connectionId, reason) => this.connectionManager.initiateCleanupOnce(record, reason)
1233
1268
  );
1234
1269
 
1235
1270
  // Store the handler in the connection record so we can remove it during cleanup