@push.rocks/smartproxy 22.4.2 → 22.6.0

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 (72) hide show
  1. package/changelog.md +28 -0
  2. package/dist_rust/rustproxy +0 -0
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/index.d.ts +1 -5
  5. package/dist_ts/index.js +3 -9
  6. package/dist_ts/protocols/common/fragment-handler.js +5 -1
  7. package/dist_ts/proxies/index.d.ts +1 -5
  8. package/dist_ts/proxies/index.js +2 -6
  9. package/dist_ts/proxies/smart-proxy/index.d.ts +5 -10
  10. package/dist_ts/proxies/smart-proxy/index.js +7 -13
  11. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -2
  12. package/dist_ts/proxies/smart-proxy/route-preprocessor.d.ts +37 -0
  13. package/dist_ts/proxies/smart-proxy/route-preprocessor.js +103 -0
  14. package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +23 -0
  15. package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +104 -0
  16. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +74 -0
  17. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +146 -0
  18. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +49 -0
  19. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +259 -0
  20. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +39 -157
  21. package/dist_ts/proxies/smart-proxy/smart-proxy.js +224 -621
  22. package/dist_ts/proxies/smart-proxy/socket-handler-server.d.ts +45 -0
  23. package/dist_ts/proxies/smart-proxy/socket-handler-server.js +253 -0
  24. package/dist_ts/routing/index.d.ts +1 -1
  25. package/dist_ts/routing/index.js +3 -3
  26. package/dist_ts/routing/models/http-types.d.ts +119 -4
  27. package/dist_ts/routing/models/http-types.js +93 -5
  28. package/package.json +1 -1
  29. package/readme.md +470 -219
  30. package/ts/00_commitinfo_data.ts +1 -1
  31. package/ts/index.ts +4 -12
  32. package/ts/protocols/common/fragment-handler.ts +4 -0
  33. package/ts/proxies/index.ts +1 -9
  34. package/ts/proxies/smart-proxy/index.ts +6 -13
  35. package/ts/proxies/smart-proxy/models/interfaces.ts +6 -4
  36. package/ts/proxies/smart-proxy/route-preprocessor.ts +122 -0
  37. package/ts/proxies/smart-proxy/rust-binary-locator.ts +112 -0
  38. package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +161 -0
  39. package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +310 -0
  40. package/ts/proxies/smart-proxy/smart-proxy.ts +282 -798
  41. package/ts/proxies/smart-proxy/socket-handler-server.ts +279 -0
  42. package/ts/routing/index.ts +2 -2
  43. package/ts/routing/models/http-types.ts +147 -4
  44. package/ts/proxies/http-proxy/connection-pool.ts +0 -228
  45. package/ts/proxies/http-proxy/context-creator.ts +0 -145
  46. package/ts/proxies/http-proxy/default-certificates.ts +0 -150
  47. package/ts/proxies/http-proxy/function-cache.ts +0 -279
  48. package/ts/proxies/http-proxy/handlers/index.ts +0 -5
  49. package/ts/proxies/http-proxy/http-proxy.ts +0 -669
  50. package/ts/proxies/http-proxy/http-request-handler.ts +0 -331
  51. package/ts/proxies/http-proxy/http2-request-handler.ts +0 -255
  52. package/ts/proxies/http-proxy/index.ts +0 -18
  53. package/ts/proxies/http-proxy/models/http-types.ts +0 -148
  54. package/ts/proxies/http-proxy/models/index.ts +0 -5
  55. package/ts/proxies/http-proxy/models/types.ts +0 -125
  56. package/ts/proxies/http-proxy/request-handler.ts +0 -878
  57. package/ts/proxies/http-proxy/security-manager.ts +0 -413
  58. package/ts/proxies/http-proxy/websocket-handler.ts +0 -581
  59. package/ts/proxies/smart-proxy/acme-state-manager.ts +0 -112
  60. package/ts/proxies/smart-proxy/cert-store.ts +0 -92
  61. package/ts/proxies/smart-proxy/certificate-manager.ts +0 -895
  62. package/ts/proxies/smart-proxy/connection-manager.ts +0 -809
  63. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +0 -213
  64. package/ts/proxies/smart-proxy/metrics-collector.ts +0 -453
  65. package/ts/proxies/smart-proxy/nftables-manager.ts +0 -271
  66. package/ts/proxies/smart-proxy/port-manager.ts +0 -358
  67. package/ts/proxies/smart-proxy/route-connection-handler.ts +0 -1712
  68. package/ts/proxies/smart-proxy/route-orchestrator.ts +0 -297
  69. package/ts/proxies/smart-proxy/security-manager.ts +0 -269
  70. package/ts/proxies/smart-proxy/throughput-tracker.ts +0 -138
  71. package/ts/proxies/smart-proxy/timeout-manager.ts +0 -196
  72. package/ts/proxies/smart-proxy/tls-manager.ts +0 -171
@@ -1,213 +0,0 @@
1
- import * as plugins from '../../plugins.js';
2
- import { HttpProxy } from '../http-proxy/index.js';
3
- import { setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
4
- import type { IConnectionRecord } from './models/interfaces.js';
5
- import type { IRouteConfig } from './models/route-types.js';
6
- import { WrappedSocket } from '../../core/models/wrapped-socket.js';
7
- import type { SmartProxy } from './smart-proxy.js';
8
-
9
- export class HttpProxyBridge {
10
- private httpProxy: HttpProxy | null = null;
11
-
12
- constructor(private smartProxy: SmartProxy) {}
13
-
14
- /**
15
- * Get the HttpProxy instance
16
- */
17
- public getHttpProxy(): HttpProxy | null {
18
- return this.httpProxy;
19
- }
20
-
21
- /**
22
- * Initialize HttpProxy instance
23
- */
24
- public async initialize(): Promise<void> {
25
- if (!this.httpProxy && this.smartProxy.settings.useHttpProxy && this.smartProxy.settings.useHttpProxy.length > 0) {
26
- const httpProxyOptions: any = {
27
- port: this.smartProxy.settings.httpProxyPort!,
28
- portProxyIntegration: true,
29
- logLevel: this.smartProxy.settings.enableDetailedLogging ? 'debug' : 'info'
30
- };
31
-
32
- this.httpProxy = new HttpProxy(httpProxyOptions);
33
- console.log(`Initialized HttpProxy on port ${this.smartProxy.settings.httpProxyPort}`);
34
-
35
- // Apply route configurations to HttpProxy
36
- await this.syncRoutesToHttpProxy(this.smartProxy.settings.routes || []);
37
- }
38
- }
39
-
40
- /**
41
- * Sync routes to HttpProxy
42
- */
43
- public async syncRoutesToHttpProxy(routes: IRouteConfig[]): Promise<void> {
44
- if (!this.httpProxy) return;
45
-
46
- // Convert routes to HttpProxy format
47
- const httpProxyConfigs = routes
48
- .filter(route => {
49
- // Check if this route matches any of the specified network proxy ports
50
- const routePorts = Array.isArray(route.match.ports)
51
- ? route.match.ports
52
- : [route.match.ports];
53
-
54
- return routePorts.some(port =>
55
- this.smartProxy.settings.useHttpProxy?.includes(port)
56
- );
57
- })
58
- .map(route => this.routeToHttpProxyConfig(route));
59
-
60
- // Apply configurations to HttpProxy
61
- await this.httpProxy.updateRouteConfigs(httpProxyConfigs);
62
- }
63
-
64
- /**
65
- * Convert route to HttpProxy configuration
66
- */
67
- private routeToHttpProxyConfig(route: IRouteConfig): any {
68
- // Convert route to HttpProxy domain config format
69
- let domain = '*';
70
- if (route.match.domains) {
71
- if (Array.isArray(route.match.domains)) {
72
- domain = route.match.domains[0] || '*';
73
- } else {
74
- domain = route.match.domains;
75
- }
76
- }
77
-
78
- return {
79
- ...route, // Keep the original route structure
80
- match: {
81
- ...route.match,
82
- domains: domain // Ensure domains is always set for HttpProxy
83
- }
84
- };
85
- }
86
-
87
- /**
88
- * Check if connection should use HttpProxy
89
- */
90
- public shouldUseHttpProxy(connection: IConnectionRecord, routeMatch: any): boolean {
91
- // Only use HttpProxy for TLS termination
92
- return (
93
- routeMatch.route.action.tls?.mode === 'terminate' ||
94
- routeMatch.route.action.tls?.mode === 'terminate-and-reencrypt'
95
- ) && this.httpProxy !== null;
96
- }
97
-
98
- /**
99
- * Forward connection to HttpProxy
100
- */
101
- public async forwardToHttpProxy(
102
- connectionId: string,
103
- socket: plugins.net.Socket | WrappedSocket,
104
- record: IConnectionRecord,
105
- initialChunk: Buffer,
106
- httpProxyPort: number,
107
- cleanupCallback: (reason: string) => void
108
- ): Promise<void> {
109
- if (!this.httpProxy) {
110
- throw new Error('HttpProxy not initialized');
111
- }
112
-
113
- // Check if client socket is already destroyed before proceeding
114
- const underlyingSocket = socket instanceof WrappedSocket ? socket.socket : socket;
115
- if (underlyingSocket.destroyed) {
116
- console.log(`[${connectionId}] Client socket already destroyed, skipping HttpProxy forwarding`);
117
- cleanupCallback('client_disconnected_before_proxy');
118
- return;
119
- }
120
-
121
- const proxySocket = new plugins.net.Socket();
122
-
123
- // Handle client disconnect during proxy connection setup
124
- const clientDisconnectHandler = () => {
125
- console.log(`[${connectionId}] Client disconnected during HttpProxy connection setup`);
126
- proxySocket.destroy();
127
- cleanupCallback('client_disconnected_during_setup');
128
- };
129
- underlyingSocket.once('close', clientDisconnectHandler);
130
-
131
- try {
132
- await new Promise<void>((resolve, reject) => {
133
- proxySocket.connect(httpProxyPort, 'localhost', () => {
134
- console.log(`[${connectionId}] Connected to HttpProxy for termination`);
135
- resolve();
136
- });
137
-
138
- proxySocket.on('error', reject);
139
- });
140
- } finally {
141
- // Remove the disconnect handler after connection attempt
142
- underlyingSocket.removeListener('close', clientDisconnectHandler);
143
- }
144
-
145
- // Double-check client socket is still connected after async operation
146
- if (underlyingSocket.destroyed) {
147
- console.log(`[${connectionId}] Client disconnected while connecting to HttpProxy`);
148
- proxySocket.destroy();
149
- cleanupCallback('client_disconnected_after_proxy_connect');
150
- return;
151
- }
152
-
153
- // Send client IP information header first (custom protocol)
154
- // Format: "CLIENT_IP:<ip>\r\n"
155
- const clientIPHeader = Buffer.from(`CLIENT_IP:${record.remoteIP}\r\n`);
156
- proxySocket.write(clientIPHeader);
157
-
158
- // Send initial chunk if present
159
- if (initialChunk) {
160
- // Count the initial chunk bytes
161
- record.bytesReceived += initialChunk.length;
162
- if (this.smartProxy.metricsCollector) {
163
- this.smartProxy.metricsCollector.recordBytes(record.id, initialChunk.length, 0);
164
- }
165
- proxySocket.write(initialChunk);
166
- }
167
-
168
- // Use centralized bidirectional forwarding (underlyingSocket already extracted above)
169
- setupBidirectionalForwarding(underlyingSocket, proxySocket, {
170
- onClientData: (chunk) => {
171
- // Update stats - this is the ONLY place bytes are counted for HttpProxy connections
172
- if (record) {
173
- record.bytesReceived += chunk.length;
174
- if (this.smartProxy.metricsCollector) {
175
- this.smartProxy.metricsCollector.recordBytes(record.id, chunk.length, 0);
176
- }
177
- }
178
- },
179
- onServerData: (chunk) => {
180
- // Update stats - this is the ONLY place bytes are counted for HttpProxy connections
181
- if (record) {
182
- record.bytesSent += chunk.length;
183
- if (this.smartProxy.metricsCollector) {
184
- this.smartProxy.metricsCollector.recordBytes(record.id, 0, chunk.length);
185
- }
186
- }
187
- },
188
- onCleanup: (reason) => {
189
- cleanupCallback(reason);
190
- },
191
- enableHalfOpen: false // Close both when one closes (required for proxy chains)
192
- });
193
- }
194
-
195
- /**
196
- * Start HttpProxy
197
- */
198
- public async start(): Promise<void> {
199
- if (this.httpProxy) {
200
- await this.httpProxy.start();
201
- }
202
- }
203
-
204
- /**
205
- * Stop HttpProxy
206
- */
207
- public async stop(): Promise<void> {
208
- if (this.httpProxy) {
209
- await this.httpProxy.stop();
210
- this.httpProxy = null;
211
- }
212
- }
213
- }
@@ -1,453 +0,0 @@
1
- import * as plugins from '../../plugins.js';
2
- import type { SmartProxy } from './smart-proxy.js';
3
- import type {
4
- IMetrics,
5
- IThroughputData,
6
- IThroughputHistoryPoint,
7
- IByteTracker
8
- } from './models/metrics-types.js';
9
- import { ThroughputTracker } from './throughput-tracker.js';
10
- import { logger } from '../../core/utils/logger.js';
11
-
12
- /**
13
- * Collects and provides metrics for SmartProxy with clean API
14
- */
15
- export class MetricsCollector implements IMetrics {
16
- // Throughput tracking
17
- private throughputTracker: ThroughputTracker;
18
- private routeThroughputTrackers = new Map<string, ThroughputTracker>();
19
- private ipThroughputTrackers = new Map<string, ThroughputTracker>();
20
-
21
- // Request tracking
22
- private requestTimestamps: number[] = [];
23
- private totalRequests: number = 0;
24
-
25
- // Connection byte tracking for per-route/IP metrics
26
- private connectionByteTrackers = new Map<string, IByteTracker>();
27
-
28
- // Subscriptions
29
- private samplingInterval?: NodeJS.Timeout;
30
- private connectionSubscription?: plugins.smartrx.rxjs.Subscription;
31
-
32
- // Configuration
33
- private readonly sampleIntervalMs: number;
34
- private readonly retentionSeconds: number;
35
-
36
- // Track connection durations for percentile calculations
37
- private connectionDurations: number[] = [];
38
- private bytesInArray: number[] = [];
39
- private bytesOutArray: number[] = [];
40
-
41
- constructor(
42
- private smartProxy: SmartProxy,
43
- config?: {
44
- sampleIntervalMs?: number;
45
- retentionSeconds?: number;
46
- }
47
- ) {
48
- this.sampleIntervalMs = config?.sampleIntervalMs || 1000;
49
- this.retentionSeconds = config?.retentionSeconds || 3600;
50
- this.throughputTracker = new ThroughputTracker(this.retentionSeconds);
51
- }
52
-
53
- // Connection metrics implementation
54
- public connections = {
55
- active: (): number => {
56
- return this.smartProxy.connectionManager.getConnectionCount();
57
- },
58
-
59
- total: (): number => {
60
- const stats = this.smartProxy.connectionManager.getTerminationStats();
61
- let total = this.smartProxy.connectionManager.getConnectionCount();
62
-
63
- for (const reason in stats.incoming) {
64
- total += stats.incoming[reason];
65
- }
66
-
67
- return total;
68
- },
69
-
70
- byRoute: (): Map<string, number> => {
71
- const routeCounts = new Map<string, number>();
72
- const connections = this.smartProxy.connectionManager.getConnections();
73
-
74
- for (const [_, record] of connections) {
75
- const routeName = (record as any).routeName ||
76
- record.routeConfig?.name ||
77
- 'unknown';
78
-
79
- const current = routeCounts.get(routeName) || 0;
80
- routeCounts.set(routeName, current + 1);
81
- }
82
-
83
- return routeCounts;
84
- },
85
-
86
- byIP: (): Map<string, number> => {
87
- const ipCounts = new Map<string, number>();
88
-
89
- for (const [_, record] of this.smartProxy.connectionManager.getConnections()) {
90
- const ip = record.remoteIP;
91
- const current = ipCounts.get(ip) || 0;
92
- ipCounts.set(ip, current + 1);
93
- }
94
-
95
- return ipCounts;
96
- },
97
-
98
- topIPs: (limit: number = 10): Array<{ ip: string; count: number }> => {
99
- const ipCounts = this.connections.byIP();
100
- return Array.from(ipCounts.entries())
101
- .sort((a, b) => b[1] - a[1])
102
- .slice(0, limit)
103
- .map(([ip, count]) => ({ ip, count }));
104
- }
105
- };
106
-
107
- // Throughput metrics implementation
108
- public throughput = {
109
- instant: (): IThroughputData => {
110
- return this.throughputTracker.getRate(1);
111
- },
112
-
113
- recent: (): IThroughputData => {
114
- return this.throughputTracker.getRate(10);
115
- },
116
-
117
- average: (): IThroughputData => {
118
- return this.throughputTracker.getRate(60);
119
- },
120
-
121
- custom: (seconds: number): IThroughputData => {
122
- return this.throughputTracker.getRate(seconds);
123
- },
124
-
125
- history: (seconds: number): Array<IThroughputHistoryPoint> => {
126
- return this.throughputTracker.getHistory(seconds);
127
- },
128
-
129
- byRoute: (windowSeconds: number = 1): Map<string, IThroughputData> => {
130
- const routeThroughput = new Map<string, IThroughputData>();
131
-
132
- // Get throughput from each route's dedicated tracker
133
- for (const [route, tracker] of this.routeThroughputTrackers) {
134
- const rate = tracker.getRate(windowSeconds);
135
- if (rate.in > 0 || rate.out > 0) {
136
- routeThroughput.set(route, rate);
137
- }
138
- }
139
-
140
- return routeThroughput;
141
- },
142
-
143
- byIP: (windowSeconds: number = 1): Map<string, IThroughputData> => {
144
- const ipThroughput = new Map<string, IThroughputData>();
145
-
146
- // Get throughput from each IP's dedicated tracker
147
- for (const [ip, tracker] of this.ipThroughputTrackers) {
148
- const rate = tracker.getRate(windowSeconds);
149
- if (rate.in > 0 || rate.out > 0) {
150
- ipThroughput.set(ip, rate);
151
- }
152
- }
153
-
154
- return ipThroughput;
155
- }
156
- };
157
-
158
- // Request metrics implementation
159
- public requests = {
160
- perSecond: (): number => {
161
- const now = Date.now();
162
- const oneSecondAgo = now - 1000;
163
-
164
- // Clean old timestamps
165
- this.requestTimestamps = this.requestTimestamps.filter(ts => ts > now - 60000);
166
-
167
- // Count requests in last second
168
- const recentRequests = this.requestTimestamps.filter(ts => ts > oneSecondAgo);
169
- return recentRequests.length;
170
- },
171
-
172
- perMinute: (): number => {
173
- const now = Date.now();
174
- const oneMinuteAgo = now - 60000;
175
-
176
- // Count requests in last minute
177
- const recentRequests = this.requestTimestamps.filter(ts => ts > oneMinuteAgo);
178
- return recentRequests.length;
179
- },
180
-
181
- total: (): number => {
182
- return this.totalRequests;
183
- }
184
- };
185
-
186
- // Totals implementation
187
- public totals = {
188
- bytesIn: (): number => {
189
- let total = 0;
190
-
191
- // Sum from all active connections
192
- for (const [_, record] of this.smartProxy.connectionManager.getConnections()) {
193
- total += record.bytesReceived;
194
- }
195
-
196
- // TODO: Add historical data from terminated connections
197
-
198
- return total;
199
- },
200
-
201
- bytesOut: (): number => {
202
- let total = 0;
203
-
204
- // Sum from all active connections
205
- for (const [_, record] of this.smartProxy.connectionManager.getConnections()) {
206
- total += record.bytesSent;
207
- }
208
-
209
- // TODO: Add historical data from terminated connections
210
-
211
- return total;
212
- },
213
-
214
- connections: (): number => {
215
- return this.connections.total();
216
- }
217
- };
218
-
219
- // Helper to calculate percentiles from an array
220
- private calculatePercentile(arr: number[], percentile: number): number {
221
- if (arr.length === 0) return 0;
222
- const sorted = [...arr].sort((a, b) => a - b);
223
- const index = Math.floor((sorted.length - 1) * percentile);
224
- return sorted[index];
225
- }
226
-
227
- // Percentiles implementation
228
- public percentiles = {
229
- connectionDuration: (): { p50: number; p95: number; p99: number } => {
230
- return {
231
- p50: this.calculatePercentile(this.connectionDurations, 0.5),
232
- p95: this.calculatePercentile(this.connectionDurations, 0.95),
233
- p99: this.calculatePercentile(this.connectionDurations, 0.99)
234
- };
235
- },
236
-
237
- bytesTransferred: (): {
238
- in: { p50: number; p95: number; p99: number };
239
- out: { p50: number; p95: number; p99: number };
240
- } => {
241
- return {
242
- in: {
243
- p50: this.calculatePercentile(this.bytesInArray, 0.5),
244
- p95: this.calculatePercentile(this.bytesInArray, 0.95),
245
- p99: this.calculatePercentile(this.bytesInArray, 0.99)
246
- },
247
- out: {
248
- p50: this.calculatePercentile(this.bytesOutArray, 0.5),
249
- p95: this.calculatePercentile(this.bytesOutArray, 0.95),
250
- p99: this.calculatePercentile(this.bytesOutArray, 0.99)
251
- }
252
- };
253
- }
254
- };
255
-
256
- /**
257
- * Record a new request
258
- */
259
- public recordRequest(connectionId: string, routeName: string, remoteIP: string): void {
260
- const now = Date.now();
261
- this.requestTimestamps.push(now);
262
- this.totalRequests++;
263
-
264
- // Initialize byte tracker for this connection
265
- this.connectionByteTrackers.set(connectionId, {
266
- connectionId,
267
- routeName,
268
- remoteIP,
269
- bytesIn: 0,
270
- bytesOut: 0,
271
- startTime: now,
272
- lastUpdate: now
273
- });
274
-
275
- // Cleanup old request timestamps
276
- if (this.requestTimestamps.length > 5000) {
277
- // First try to clean up old timestamps (older than 1 minute)
278
- const cutoff = now - 60000;
279
- this.requestTimestamps = this.requestTimestamps.filter(ts => ts > cutoff);
280
-
281
- // If still too many, enforce hard cap of 5000 most recent
282
- if (this.requestTimestamps.length > 5000) {
283
- this.requestTimestamps = this.requestTimestamps.slice(-5000);
284
- }
285
- }
286
- }
287
-
288
- /**
289
- * Record bytes transferred for a connection
290
- */
291
- public recordBytes(connectionId: string, bytesIn: number, bytesOut: number): void {
292
- // Update global throughput tracker
293
- this.throughputTracker.recordBytes(bytesIn, bytesOut);
294
-
295
- // Update connection-specific tracker
296
- const tracker = this.connectionByteTrackers.get(connectionId);
297
- if (tracker) {
298
- tracker.bytesIn += bytesIn;
299
- tracker.bytesOut += bytesOut;
300
- tracker.lastUpdate = Date.now();
301
-
302
- // Update per-route throughput tracker
303
- let routeTracker = this.routeThroughputTrackers.get(tracker.routeName);
304
- if (!routeTracker) {
305
- routeTracker = new ThroughputTracker(this.retentionSeconds);
306
- this.routeThroughputTrackers.set(tracker.routeName, routeTracker);
307
- }
308
- routeTracker.recordBytes(bytesIn, bytesOut);
309
-
310
- // Update per-IP throughput tracker
311
- let ipTracker = this.ipThroughputTrackers.get(tracker.remoteIP);
312
- if (!ipTracker) {
313
- ipTracker = new ThroughputTracker(this.retentionSeconds);
314
- this.ipThroughputTrackers.set(tracker.remoteIP, ipTracker);
315
- }
316
- ipTracker.recordBytes(bytesIn, bytesOut);
317
- }
318
- }
319
-
320
- /**
321
- * Clean up tracking for a closed connection
322
- */
323
- public removeConnection(connectionId: string): void {
324
- const tracker = this.connectionByteTrackers.get(connectionId);
325
- if (tracker) {
326
- // Calculate connection duration
327
- const duration = Date.now() - tracker.startTime;
328
-
329
- // Add to arrays for percentile calculations (bounded to prevent memory growth)
330
- const MAX_SAMPLES = 5000;
331
-
332
- this.connectionDurations.push(duration);
333
- if (this.connectionDurations.length > MAX_SAMPLES) {
334
- this.connectionDurations.shift();
335
- }
336
-
337
- this.bytesInArray.push(tracker.bytesIn);
338
- if (this.bytesInArray.length > MAX_SAMPLES) {
339
- this.bytesInArray.shift();
340
- }
341
-
342
- this.bytesOutArray.push(tracker.bytesOut);
343
- if (this.bytesOutArray.length > MAX_SAMPLES) {
344
- this.bytesOutArray.shift();
345
- }
346
- }
347
-
348
- this.connectionByteTrackers.delete(connectionId);
349
- }
350
-
351
- /**
352
- * Start the metrics collector
353
- */
354
- public start(): void {
355
- if (!this.smartProxy.routeConnectionHandler) {
356
- throw new Error('MetricsCollector: RouteConnectionHandler not available');
357
- }
358
-
359
- // Start periodic sampling
360
- this.samplingInterval = setInterval(() => {
361
- // Sample global throughput
362
- this.throughputTracker.takeSample();
363
-
364
- // Sample per-route throughput
365
- for (const [_, tracker] of this.routeThroughputTrackers) {
366
- tracker.takeSample();
367
- }
368
-
369
- // Sample per-IP throughput
370
- for (const [_, tracker] of this.ipThroughputTrackers) {
371
- tracker.takeSample();
372
- }
373
-
374
- // Clean up old connection trackers (connections closed more than 5 minutes ago)
375
- const cutoff = Date.now() - 300000;
376
- for (const [id, tracker] of this.connectionByteTrackers) {
377
- if (tracker.lastUpdate < cutoff) {
378
- this.connectionByteTrackers.delete(id);
379
- }
380
- }
381
-
382
- // Clean up unused route trackers
383
- const activeRoutes = new Set(Array.from(this.connectionByteTrackers.values()).map(t => t.routeName));
384
- for (const [route, _] of this.routeThroughputTrackers) {
385
- if (!activeRoutes.has(route)) {
386
- this.routeThroughputTrackers.delete(route);
387
- }
388
- }
389
-
390
- // Clean up unused IP trackers
391
- const activeIPs = new Set(Array.from(this.connectionByteTrackers.values()).map(t => t.remoteIP));
392
- for (const [ip, _] of this.ipThroughputTrackers) {
393
- if (!activeIPs.has(ip)) {
394
- this.ipThroughputTrackers.delete(ip);
395
- }
396
- }
397
- }, this.sampleIntervalMs);
398
-
399
- // Unref the interval so it doesn't keep the process alive
400
- if (this.samplingInterval.unref) {
401
- this.samplingInterval.unref();
402
- }
403
-
404
- // Subscribe to new connections
405
- this.connectionSubscription = this.smartProxy.routeConnectionHandler.newConnectionSubject.subscribe({
406
- next: (record) => {
407
- const routeName = record.routeConfig?.name || 'unknown';
408
- this.recordRequest(record.id, routeName, record.remoteIP);
409
-
410
- if (this.smartProxy.settings?.enableDetailedLogging) {
411
- logger.log('debug', `MetricsCollector: New connection recorded`, {
412
- connectionId: record.id,
413
- remoteIP: record.remoteIP,
414
- routeName,
415
- component: 'metrics'
416
- });
417
- }
418
- },
419
- error: (err) => {
420
- logger.log('error', `MetricsCollector: Error in connection subscription`, {
421
- error: err.message,
422
- component: 'metrics'
423
- });
424
- }
425
- });
426
-
427
- logger.log('debug', 'MetricsCollector started', { component: 'metrics' });
428
- }
429
-
430
- /**
431
- * Stop the metrics collector
432
- */
433
- public stop(): void {
434
- if (this.samplingInterval) {
435
- clearInterval(this.samplingInterval);
436
- this.samplingInterval = undefined;
437
- }
438
-
439
- if (this.connectionSubscription) {
440
- this.connectionSubscription.unsubscribe();
441
- this.connectionSubscription = undefined;
442
- }
443
-
444
- logger.log('debug', 'MetricsCollector stopped', { component: 'metrics' });
445
- }
446
-
447
- /**
448
- * Alias for stop() for compatibility
449
- */
450
- public destroy(): void {
451
- this.stop();
452
- }
453
- }