@push.rocks/smartproxy 22.4.2 → 23.0.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 (101) hide show
  1. package/changelog.md +36 -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 -6
  5. package/dist_ts/index.js +3 -11
  6. package/dist_ts/protocols/common/fragment-handler.js +5 -1
  7. package/dist_ts/proxies/index.d.ts +1 -6
  8. package/dist_ts/proxies/index.js +2 -8
  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/models/route-types.js +1 -1
  13. package/dist_ts/proxies/smart-proxy/route-preprocessor.d.ts +37 -0
  14. package/dist_ts/proxies/smart-proxy/route-preprocessor.js +103 -0
  15. package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +23 -0
  16. package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +104 -0
  17. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +74 -0
  18. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +146 -0
  19. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +49 -0
  20. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +259 -0
  21. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +39 -157
  22. package/dist_ts/proxies/smart-proxy/smart-proxy.js +224 -621
  23. package/dist_ts/proxies/smart-proxy/socket-handler-server.d.ts +45 -0
  24. package/dist_ts/proxies/smart-proxy/socket-handler-server.js +253 -0
  25. package/dist_ts/routing/index.d.ts +1 -1
  26. package/dist_ts/routing/index.js +3 -3
  27. package/dist_ts/routing/models/http-types.d.ts +119 -4
  28. package/dist_ts/routing/models/http-types.js +93 -5
  29. package/package.json +1 -1
  30. package/readme.md +444 -219
  31. package/ts/00_commitinfo_data.ts +1 -1
  32. package/ts/index.ts +4 -15
  33. package/ts/protocols/common/fragment-handler.ts +4 -0
  34. package/ts/proxies/index.ts +1 -12
  35. package/ts/proxies/smart-proxy/index.ts +6 -13
  36. package/ts/proxies/smart-proxy/models/interfaces.ts +6 -4
  37. package/ts/proxies/smart-proxy/models/route-types.ts +0 -2
  38. package/ts/proxies/smart-proxy/route-preprocessor.ts +122 -0
  39. package/ts/proxies/smart-proxy/rust-binary-locator.ts +112 -0
  40. package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +161 -0
  41. package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +310 -0
  42. package/ts/proxies/smart-proxy/smart-proxy.ts +282 -798
  43. package/ts/proxies/smart-proxy/socket-handler-server.ts +279 -0
  44. package/ts/routing/index.ts +2 -2
  45. package/ts/routing/models/http-types.ts +147 -4
  46. package/dist_ts/proxies/nftables-proxy/index.d.ts +0 -6
  47. package/dist_ts/proxies/nftables-proxy/index.js +0 -7
  48. package/dist_ts/proxies/nftables-proxy/models/errors.d.ts +0 -15
  49. package/dist_ts/proxies/nftables-proxy/models/errors.js +0 -28
  50. package/dist_ts/proxies/nftables-proxy/models/index.d.ts +0 -5
  51. package/dist_ts/proxies/nftables-proxy/models/index.js +0 -6
  52. package/dist_ts/proxies/nftables-proxy/models/interfaces.d.ts +0 -75
  53. package/dist_ts/proxies/nftables-proxy/models/interfaces.js +0 -5
  54. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +0 -124
  55. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +0 -1374
  56. package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +0 -9
  57. package/dist_ts/proxies/nftables-proxy/utils/index.js +0 -12
  58. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +0 -66
  59. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +0 -131
  60. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +0 -39
  61. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +0 -112
  62. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +0 -59
  63. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +0 -130
  64. package/ts/proxies/http-proxy/connection-pool.ts +0 -228
  65. package/ts/proxies/http-proxy/context-creator.ts +0 -145
  66. package/ts/proxies/http-proxy/default-certificates.ts +0 -150
  67. package/ts/proxies/http-proxy/function-cache.ts +0 -279
  68. package/ts/proxies/http-proxy/handlers/index.ts +0 -5
  69. package/ts/proxies/http-proxy/http-proxy.ts +0 -669
  70. package/ts/proxies/http-proxy/http-request-handler.ts +0 -331
  71. package/ts/proxies/http-proxy/http2-request-handler.ts +0 -255
  72. package/ts/proxies/http-proxy/index.ts +0 -18
  73. package/ts/proxies/http-proxy/models/http-types.ts +0 -148
  74. package/ts/proxies/http-proxy/models/index.ts +0 -5
  75. package/ts/proxies/http-proxy/models/types.ts +0 -125
  76. package/ts/proxies/http-proxy/request-handler.ts +0 -878
  77. package/ts/proxies/http-proxy/security-manager.ts +0 -413
  78. package/ts/proxies/http-proxy/websocket-handler.ts +0 -581
  79. package/ts/proxies/nftables-proxy/index.ts +0 -6
  80. package/ts/proxies/nftables-proxy/models/errors.ts +0 -30
  81. package/ts/proxies/nftables-proxy/models/index.ts +0 -5
  82. package/ts/proxies/nftables-proxy/models/interfaces.ts +0 -94
  83. package/ts/proxies/nftables-proxy/nftables-proxy.ts +0 -1754
  84. package/ts/proxies/nftables-proxy/utils/index.ts +0 -38
  85. package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +0 -162
  86. package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +0 -125
  87. package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +0 -156
  88. package/ts/proxies/smart-proxy/acme-state-manager.ts +0 -112
  89. package/ts/proxies/smart-proxy/cert-store.ts +0 -92
  90. package/ts/proxies/smart-proxy/certificate-manager.ts +0 -895
  91. package/ts/proxies/smart-proxy/connection-manager.ts +0 -809
  92. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +0 -213
  93. package/ts/proxies/smart-proxy/metrics-collector.ts +0 -453
  94. package/ts/proxies/smart-proxy/nftables-manager.ts +0 -271
  95. package/ts/proxies/smart-proxy/port-manager.ts +0 -358
  96. package/ts/proxies/smart-proxy/route-connection-handler.ts +0 -1712
  97. package/ts/proxies/smart-proxy/route-orchestrator.ts +0 -297
  98. package/ts/proxies/smart-proxy/security-manager.ts +0 -269
  99. package/ts/proxies/smart-proxy/throughput-tracker.ts +0 -138
  100. package/ts/proxies/smart-proxy/timeout-manager.ts +0 -196
  101. 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
- }