@push.rocks/smartproxy 19.6.0 → 19.6.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.
@@ -30,6 +30,9 @@ export class FunctionCache {
30
30
  // Logger
31
31
  private logger: ILogger;
32
32
 
33
+ // Cleanup interval timer
34
+ private cleanupInterval: NodeJS.Timeout | null = null;
35
+
33
36
  /**
34
37
  * Creates a new function cache
35
38
  *
@@ -48,7 +51,12 @@ export class FunctionCache {
48
51
  this.defaultTtl = options.defaultTtl || 5000; // 5 seconds default
49
52
 
50
53
  // Start the cache cleanup timer
51
- setInterval(() => this.cleanupCache(), 30000); // Cleanup every 30 seconds
54
+ this.cleanupInterval = setInterval(() => this.cleanupCache(), 30000); // Cleanup every 30 seconds
55
+
56
+ // Make sure the interval doesn't keep the process alive
57
+ if (this.cleanupInterval.unref) {
58
+ this.cleanupInterval.unref();
59
+ }
52
60
  }
53
61
 
54
62
  /**
@@ -256,4 +264,16 @@ export class FunctionCache {
256
264
  this.portCache.clear();
257
265
  this.logger.info('Function cache cleared');
258
266
  }
267
+
268
+ /**
269
+ * Destroy the cache and cleanup resources
270
+ */
271
+ public destroy(): void {
272
+ if (this.cleanupInterval) {
273
+ clearInterval(this.cleanupInterval);
274
+ this.cleanupInterval = null;
275
+ }
276
+ this.clearCache();
277
+ this.logger.debug('Function cache destroyed');
278
+ }
259
279
  }
@@ -464,6 +464,11 @@ export class HttpProxy implements IMetricsTracker {
464
464
  // Stop WebSocket handler
465
465
  this.webSocketHandler.shutdown();
466
466
 
467
+ // Destroy request handler (cleans up intervals and caches)
468
+ if (this.requestHandler && typeof this.requestHandler.destroy === 'function') {
469
+ this.requestHandler.destroy();
470
+ }
471
+
467
472
  // Close all tracked sockets
468
473
  const socketCleanupPromises = this.socketMap.getArray().map(socket =>
469
474
  cleanupSocket(socket, 'http-proxy-stop', { immediate: true })
@@ -42,6 +42,9 @@ export class RequestHandler {
42
42
 
43
43
  // Security manager for IP filtering, rate limiting, etc.
44
44
  public securityManager: SecurityManager;
45
+
46
+ // Rate limit cleanup interval
47
+ private rateLimitCleanupInterval: NodeJS.Timeout | null = null;
45
48
 
46
49
  constructor(
47
50
  private options: IHttpProxyOptions,
@@ -54,9 +57,14 @@ export class RequestHandler {
54
57
  this.securityManager = new SecurityManager(this.logger);
55
58
 
56
59
  // Schedule rate limit cleanup every minute
57
- setInterval(() => {
60
+ this.rateLimitCleanupInterval = setInterval(() => {
58
61
  this.securityManager.cleanupExpiredRateLimits();
59
62
  }, 60000);
63
+
64
+ // Make sure the interval doesn't keep the process alive
65
+ if (this.rateLimitCleanupInterval.unref) {
66
+ this.rateLimitCleanupInterval.unref();
67
+ }
60
68
  }
61
69
 
62
70
  /**
@@ -741,4 +749,27 @@ export class RequestHandler {
741
749
  stream.end('Not Found: No route configuration for this request');
742
750
  if (this.metricsTracker) this.metricsTracker.incrementFailedRequests();
743
751
  }
752
+
753
+ /**
754
+ * Cleanup resources and stop intervals
755
+ */
756
+ public destroy(): void {
757
+ if (this.rateLimitCleanupInterval) {
758
+ clearInterval(this.rateLimitCleanupInterval);
759
+ this.rateLimitCleanupInterval = null;
760
+ }
761
+
762
+ // Close all HTTP/2 sessions
763
+ for (const [key, session] of this.h2Sessions) {
764
+ session.close();
765
+ }
766
+ this.h2Sessions.clear();
767
+
768
+ // Clear function cache if it has a destroy method
769
+ if (this.functionCache && typeof this.functionCache.destroy === 'function') {
770
+ this.functionCache.destroy();
771
+ }
772
+
773
+ this.logger.debug('RequestHandler destroyed');
774
+ }
744
775
  }
@@ -488,14 +488,19 @@ export class ConnectionManager extends LifecycleComponent {
488
488
  // Check for half-zombie: one socket destroyed
489
489
  if (incomingDestroyed || outgoingDestroyed) {
490
490
  const age = now - record.incomingStartTime;
491
- // Give it 30 seconds grace period for normal cleanup
492
- if (age > 30000) {
491
+ // Use longer grace period for encrypted connections (5 minutes vs 30 seconds)
492
+ const gracePeriod = record.isTLS ? 300000 : 30000;
493
+
494
+ // Also ensure connection is old enough to avoid premature cleanup
495
+ if (age > gracePeriod && age > 10000) {
493
496
  logger.log('warn', `Half-zombie connection detected: ${connectionId} - ${incomingDestroyed ? 'incoming' : 'outgoing'} destroyed`, {
494
497
  connectionId,
495
498
  remoteIP: record.remoteIP,
496
499
  age: plugins.prettyMs(age),
497
500
  incomingDestroyed,
498
501
  outgoingDestroyed,
502
+ isTLS: record.isTLS,
503
+ gracePeriod: plugins.prettyMs(gracePeriod),
499
504
  component: 'connection-manager'
500
505
  });
501
506
 
@@ -507,8 +512,11 @@ export class ConnectionManager extends LifecycleComponent {
507
512
  // Check for stuck connections: no data sent back to client
508
513
  if (!record.connectionClosed && record.outgoing && record.bytesReceived > 0 && record.bytesSent === 0) {
509
514
  const age = now - record.incomingStartTime;
510
- // If connection is older than 60 seconds and no data sent back, likely stuck
511
- if (age > 60000) {
515
+ // Use longer grace period for encrypted connections (5 minutes vs 60 seconds)
516
+ const stuckThreshold = record.isTLS ? 300000 : 60000;
517
+
518
+ // If connection is older than threshold and no data sent back, likely stuck
519
+ if (age > stuckThreshold) {
512
520
  logger.log('warn', `Stuck connection detected: ${connectionId} - received ${record.bytesReceived} bytes but sent 0 bytes`, {
513
521
  connectionId,
514
522
  remoteIP: record.remoteIP,
@@ -516,6 +524,8 @@ export class ConnectionManager extends LifecycleComponent {
516
524
  bytesReceived: record.bytesReceived,
517
525
  targetHost: record.targetHost,
518
526
  targetPort: record.targetPort,
527
+ isTLS: record.isTLS,
528
+ threshold: plugins.prettyMs(stuckThreshold),
519
529
  component: 'connection-manager'
520
530
  });
521
531
 
@@ -10,6 +10,7 @@ export class MetricsCollector implements IProxyStatsExtended {
10
10
  // RPS tracking (the only state we need to maintain)
11
11
  private requestTimestamps: number[] = [];
12
12
  private readonly RPS_WINDOW_SIZE = 60000; // 1 minute window
13
+ private readonly MAX_TIMESTAMPS = 5000; // Maximum timestamps to keep
13
14
 
14
15
  // Optional caching for performance
15
16
  private cachedMetrics: {
@@ -148,11 +149,14 @@ export class MetricsCollector implements IProxyStatsExtended {
148
149
  * Record a new request for RPS tracking
149
150
  */
150
151
  public recordRequest(): void {
151
- this.requestTimestamps.push(Date.now());
152
+ const now = Date.now();
153
+ this.requestTimestamps.push(now);
152
154
 
153
- // Prevent unbounded growth
154
- if (this.requestTimestamps.length > 10000) {
155
- this.cleanupOldRequests();
155
+ // Prevent unbounded growth - clean up more aggressively
156
+ if (this.requestTimestamps.length > this.MAX_TIMESTAMPS) {
157
+ // Keep only timestamps within the window
158
+ const cutoff = now - this.RPS_WINDOW_SIZE;
159
+ this.requestTimestamps = this.requestTimestamps.filter(ts => ts > cutoff);
156
160
  }
157
161
  }
158
162
 
@@ -10,7 +10,7 @@ import { TlsManager } from './tls-manager.js';
10
10
  import { HttpProxyBridge } from './http-proxy-bridge.js';
11
11
  import { TimeoutManager } from './timeout-manager.js';
12
12
  import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
13
- import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
13
+ import { cleanupSocket, setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
14
14
  import { WrappedSocket } from '../../core/models/wrapped-socket.js';
15
15
  import { getUnderlyingSocket } from '../../core/models/socket-types.js';
16
16
  import { ProxyProtocolParser } from '../../core/utils/proxy-protocol.js';
@@ -21,8 +21,9 @@ import { ProxyProtocolParser } from '../../core/utils/proxy-protocol.js';
21
21
  export class RouteConnectionHandler {
22
22
  private settings: ISmartProxyOptions;
23
23
 
24
- // Cache for route contexts to avoid recreation
25
- private routeContextCache: Map<string, IRouteContext> = new Map();
24
+ // Note: Route context caching was considered but not implemented
25
+ // as route contexts are lightweight and should be created fresh
26
+ // for each connection to ensure accurate context data
26
27
 
27
28
  // RxJS Subject for new connections
28
29
  public newConnectionSubject = new plugins.smartrx.rxjs.Subject<IConnectionRecord>();
@@ -730,8 +731,7 @@ export class RouteConnectionHandler {
730
731
  routeId: route.id,
731
732
  });
732
733
 
733
- // Cache the context for potential reuse
734
- this.routeContextCache.set(connectionId, routeContext);
734
+ // Note: Route contexts are not cached to ensure fresh data for each connection
735
735
 
736
736
  // Determine host using function or static value
737
737
  let targetHost: string | string[];