@push.rocks/smartproxy 21.1.7 → 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 (155) hide show
  1. package/changelog.md +109 -0
  2. package/dist_rust/rustproxy +0 -0
  3. package/dist_ts/00_commitinfo_data.js +1 -1
  4. package/dist_ts/core/utils/shared-security-manager.d.ts +17 -0
  5. package/dist_ts/core/utils/shared-security-manager.js +66 -1
  6. package/dist_ts/index.d.ts +1 -5
  7. package/dist_ts/index.js +3 -9
  8. package/dist_ts/protocols/common/fragment-handler.js +5 -1
  9. package/dist_ts/proxies/http-proxy/default-certificates.d.ts +54 -0
  10. package/dist_ts/proxies/http-proxy/default-certificates.js +127 -0
  11. package/dist_ts/proxies/http-proxy/http-proxy.d.ts +1 -1
  12. package/dist_ts/proxies/http-proxy/http-proxy.js +9 -14
  13. package/dist_ts/proxies/http-proxy/index.d.ts +5 -1
  14. package/dist_ts/proxies/http-proxy/index.js +6 -2
  15. package/dist_ts/proxies/http-proxy/security-manager.d.ts +4 -12
  16. package/dist_ts/proxies/http-proxy/security-manager.js +66 -99
  17. package/dist_ts/proxies/index.d.ts +1 -5
  18. package/dist_ts/proxies/index.js +2 -6
  19. package/dist_ts/proxies/nftables-proxy/index.d.ts +1 -0
  20. package/dist_ts/proxies/nftables-proxy/index.js +2 -1
  21. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +4 -26
  22. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +84 -236
  23. package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +9 -0
  24. package/dist_ts/proxies/nftables-proxy/utils/index.js +12 -0
  25. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +66 -0
  26. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +131 -0
  27. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +39 -0
  28. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +112 -0
  29. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +59 -0
  30. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +130 -0
  31. package/dist_ts/proxies/smart-proxy/certificate-manager.js +4 -3
  32. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +13 -2
  33. package/dist_ts/proxies/smart-proxy/connection-manager.js +16 -6
  34. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +35 -10
  35. package/dist_ts/proxies/smart-proxy/index.d.ts +5 -10
  36. package/dist_ts/proxies/smart-proxy/index.js +7 -13
  37. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -3
  38. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +17 -0
  39. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +72 -9
  40. package/dist_ts/proxies/smart-proxy/route-preprocessor.d.ts +37 -0
  41. package/dist_ts/proxies/smart-proxy/route-preprocessor.js +103 -0
  42. package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +23 -0
  43. package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +104 -0
  44. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +74 -0
  45. package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +146 -0
  46. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +49 -0
  47. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +259 -0
  48. package/dist_ts/proxies/smart-proxy/security-manager.d.ts +14 -12
  49. package/dist_ts/proxies/smart-proxy/security-manager.js +80 -74
  50. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +39 -157
  51. package/dist_ts/proxies/smart-proxy/smart-proxy.js +224 -622
  52. package/dist_ts/proxies/smart-proxy/socket-handler-server.d.ts +45 -0
  53. package/dist_ts/proxies/smart-proxy/socket-handler-server.js +253 -0
  54. package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +2 -9
  55. package/dist_ts/proxies/smart-proxy/tls-manager.js +3 -26
  56. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -1
  57. package/dist_ts/proxies/smart-proxy/utils/index.js +3 -4
  58. package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.d.ts +49 -0
  59. package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.js +108 -0
  60. package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.d.ts +57 -0
  61. package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.js +89 -0
  62. package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.d.ts +17 -0
  63. package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.js +32 -0
  64. package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.d.ts +68 -0
  65. package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.js +117 -0
  66. package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.d.ts +17 -0
  67. package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.js +27 -0
  68. package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.d.ts +63 -0
  69. package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.js +105 -0
  70. package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.d.ts +83 -0
  71. package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.js +126 -0
  72. package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.d.ts +47 -0
  73. package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.js +66 -0
  74. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.d.ts +70 -0
  75. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.js +287 -0
  76. package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.d.ts +46 -0
  77. package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.js +67 -0
  78. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +4 -457
  79. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +6 -950
  80. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +2 -2
  81. package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +67 -1
  82. package/dist_ts/proxies/smart-proxy/utils/route-validator.js +251 -3
  83. package/dist_ts/routing/index.d.ts +1 -1
  84. package/dist_ts/routing/index.js +3 -3
  85. package/dist_ts/routing/models/http-types.d.ts +119 -4
  86. package/dist_ts/routing/models/http-types.js +93 -5
  87. package/npmextra.json +12 -6
  88. package/package.json +34 -24
  89. package/readme.hints.md +184 -1
  90. package/readme.md +580 -266
  91. package/ts/00_commitinfo_data.ts +1 -1
  92. package/ts/core/utils/shared-security-manager.ts +98 -13
  93. package/ts/index.ts +4 -12
  94. package/ts/protocols/common/fragment-handler.ts +4 -0
  95. package/ts/proxies/index.ts +1 -9
  96. package/ts/proxies/nftables-proxy/index.ts +1 -0
  97. package/ts/proxies/nftables-proxy/nftables-proxy.ts +116 -290
  98. package/ts/proxies/nftables-proxy/utils/index.ts +38 -0
  99. package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +162 -0
  100. package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +125 -0
  101. package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +156 -0
  102. package/ts/proxies/smart-proxy/index.ts +6 -13
  103. package/ts/proxies/smart-proxy/models/interfaces.ts +6 -5
  104. package/ts/proxies/smart-proxy/route-preprocessor.ts +122 -0
  105. package/ts/proxies/smart-proxy/rust-binary-locator.ts +112 -0
  106. package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +161 -0
  107. package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +310 -0
  108. package/ts/proxies/smart-proxy/smart-proxy.ts +282 -800
  109. package/ts/proxies/smart-proxy/socket-handler-server.ts +279 -0
  110. package/ts/proxies/smart-proxy/utils/index.ts +3 -5
  111. package/ts/proxies/smart-proxy/utils/route-helpers/api-helpers.ts +144 -0
  112. package/ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.ts +124 -0
  113. package/ts/proxies/smart-proxy/utils/route-helpers/http-helpers.ts +40 -0
  114. package/ts/proxies/smart-proxy/utils/route-helpers/https-helpers.ts +163 -0
  115. package/ts/proxies/smart-proxy/utils/route-helpers/index.ts +62 -0
  116. package/ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.ts +154 -0
  117. package/ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.ts +202 -0
  118. package/ts/proxies/smart-proxy/utils/route-helpers/security-helpers.ts +96 -0
  119. package/ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.ts +337 -0
  120. package/ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.ts +98 -0
  121. package/ts/proxies/smart-proxy/utils/route-helpers.ts +5 -1302
  122. package/ts/proxies/smart-proxy/utils/route-utils.ts +1 -1
  123. package/ts/proxies/smart-proxy/utils/route-validator.ts +274 -4
  124. package/ts/routing/index.ts +2 -2
  125. package/ts/routing/models/http-types.ts +147 -4
  126. package/ts/proxies/http-proxy/certificate-manager.ts +0 -244
  127. package/ts/proxies/http-proxy/connection-pool.ts +0 -228
  128. package/ts/proxies/http-proxy/context-creator.ts +0 -145
  129. package/ts/proxies/http-proxy/function-cache.ts +0 -279
  130. package/ts/proxies/http-proxy/handlers/index.ts +0 -5
  131. package/ts/proxies/http-proxy/http-proxy.ts +0 -675
  132. package/ts/proxies/http-proxy/http-request-handler.ts +0 -331
  133. package/ts/proxies/http-proxy/http2-request-handler.ts +0 -255
  134. package/ts/proxies/http-proxy/index.ts +0 -13
  135. package/ts/proxies/http-proxy/models/http-types.ts +0 -148
  136. package/ts/proxies/http-proxy/models/index.ts +0 -5
  137. package/ts/proxies/http-proxy/models/types.ts +0 -125
  138. package/ts/proxies/http-proxy/request-handler.ts +0 -878
  139. package/ts/proxies/http-proxy/security-manager.ts +0 -433
  140. package/ts/proxies/http-proxy/websocket-handler.ts +0 -581
  141. package/ts/proxies/smart-proxy/acme-state-manager.ts +0 -112
  142. package/ts/proxies/smart-proxy/cert-store.ts +0 -92
  143. package/ts/proxies/smart-proxy/certificate-manager.ts +0 -894
  144. package/ts/proxies/smart-proxy/connection-manager.ts +0 -796
  145. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +0 -187
  146. package/ts/proxies/smart-proxy/metrics-collector.ts +0 -453
  147. package/ts/proxies/smart-proxy/nftables-manager.ts +0 -271
  148. package/ts/proxies/smart-proxy/port-manager.ts +0 -358
  149. package/ts/proxies/smart-proxy/route-connection-handler.ts +0 -1640
  150. package/ts/proxies/smart-proxy/route-orchestrator.ts +0 -297
  151. package/ts/proxies/smart-proxy/security-manager.ts +0 -257
  152. package/ts/proxies/smart-proxy/throughput-tracker.ts +0 -138
  153. package/ts/proxies/smart-proxy/timeout-manager.ts +0 -196
  154. package/ts/proxies/smart-proxy/tls-manager.ts +0 -207
  155. package/ts/proxies/smart-proxy/utils/route-validators.ts +0 -283
@@ -1,187 +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
- const proxySocket = new plugins.net.Socket();
114
-
115
- await new Promise<void>((resolve, reject) => {
116
- proxySocket.connect(httpProxyPort, 'localhost', () => {
117
- console.log(`[${connectionId}] Connected to HttpProxy for termination`);
118
- resolve();
119
- });
120
-
121
- proxySocket.on('error', reject);
122
- });
123
-
124
- // Send client IP information header first (custom protocol)
125
- // Format: "CLIENT_IP:<ip>\r\n"
126
- const clientIPHeader = Buffer.from(`CLIENT_IP:${record.remoteIP}\r\n`);
127
- proxySocket.write(clientIPHeader);
128
-
129
- // Send initial chunk if present
130
- if (initialChunk) {
131
- // Count the initial chunk bytes
132
- record.bytesReceived += initialChunk.length;
133
- if (this.smartProxy.metricsCollector) {
134
- this.smartProxy.metricsCollector.recordBytes(record.id, initialChunk.length, 0);
135
- }
136
- proxySocket.write(initialChunk);
137
- }
138
-
139
- // Use centralized bidirectional forwarding
140
- // Extract underlying socket if it's a WrappedSocket
141
- const underlyingSocket = socket instanceof WrappedSocket ? socket.socket : socket;
142
-
143
- setupBidirectionalForwarding(underlyingSocket, proxySocket, {
144
- onClientData: (chunk) => {
145
- // Update stats - this is the ONLY place bytes are counted for HttpProxy connections
146
- if (record) {
147
- record.bytesReceived += chunk.length;
148
- if (this.smartProxy.metricsCollector) {
149
- this.smartProxy.metricsCollector.recordBytes(record.id, chunk.length, 0);
150
- }
151
- }
152
- },
153
- onServerData: (chunk) => {
154
- // Update stats - this is the ONLY place bytes are counted for HttpProxy connections
155
- if (record) {
156
- record.bytesSent += chunk.length;
157
- if (this.smartProxy.metricsCollector) {
158
- this.smartProxy.metricsCollector.recordBytes(record.id, 0, chunk.length);
159
- }
160
- }
161
- },
162
- onCleanup: (reason) => {
163
- cleanupCallback(reason);
164
- },
165
- enableHalfOpen: false // Close both when one closes (required for proxy chains)
166
- });
167
- }
168
-
169
- /**
170
- * Start HttpProxy
171
- */
172
- public async start(): Promise<void> {
173
- if (this.httpProxy) {
174
- await this.httpProxy.start();
175
- }
176
- }
177
-
178
- /**
179
- * Stop HttpProxy
180
- */
181
- public async stop(): Promise<void> {
182
- if (this.httpProxy) {
183
- await this.httpProxy.stop();
184
- this.httpProxy = null;
185
- }
186
- }
187
- }
@@ -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
- }