@push.rocks/smartproxy 27.6.0 → 27.7.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.
@@ -1,23 +1,29 @@
1
1
  import * as plugins from '../../plugins.js';
2
2
  import { logger } from '../../core/utils/logger.js';
3
- import type { IRouteConfig } from './models/route-types.js';
3
+ import type {
4
+ IRustCertificateStatus,
5
+ IRustMetricsSnapshot,
6
+ IRustProxyOptions,
7
+ IRustRouteConfig,
8
+ IRustStatistics,
9
+ } from './models/rust-types.js';
4
10
 
5
11
  /**
6
12
  * Type-safe command definitions for the Rust proxy IPC protocol.
7
13
  */
8
14
  type TSmartProxyCommands = {
9
- start: { params: { config: any }; result: void };
10
- stop: { params: Record<string, never>; result: void };
11
- updateRoutes: { params: { routes: IRouteConfig[] }; result: void };
12
- getMetrics: { params: Record<string, never>; result: any };
13
- getStatistics: { params: Record<string, never>; result: any };
14
- provisionCertificate: { params: { routeName: string }; result: void };
15
- renewCertificate: { params: { routeName: string }; result: void };
16
- getCertificateStatus: { params: { routeName: string }; result: any };
17
- getListeningPorts: { params: Record<string, never>; result: { ports: number[] } };
18
- setSocketHandlerRelay: { params: { socketPath: string }; result: void };
19
- addListeningPort: { params: { port: number }; result: void };
20
- removeListeningPort: { params: { port: number }; result: void };
15
+ start: { params: { config: IRustProxyOptions }; result: void };
16
+ stop: { params: Record<string, never>; result: void };
17
+ updateRoutes: { params: { routes: IRustRouteConfig[] }; result: void };
18
+ getMetrics: { params: Record<string, never>; result: IRustMetricsSnapshot };
19
+ getStatistics: { params: Record<string, never>; result: IRustStatistics };
20
+ provisionCertificate: { params: { routeName: string }; result: void };
21
+ renewCertificate: { params: { routeName: string }; result: void };
22
+ getCertificateStatus: { params: { routeName: string }; result: IRustCertificateStatus | null };
23
+ getListeningPorts: { params: Record<string, never>; result: { ports: number[] } };
24
+ setSocketHandlerRelay: { params: { socketPath: string }; result: void };
25
+ addListeningPort: { params: { port: number }; result: void };
26
+ removeListeningPort: { params: { port: number }; result: void };
21
27
  loadCertificate: { params: { domain: string; cert: string; key: string; ca?: string }; result: void };
22
28
  setDatagramHandlerRelay: { params: { socketPath: string }; result: void };
23
29
  };
@@ -121,7 +127,7 @@ export class RustProxyBridge extends plugins.EventEmitter {
121
127
 
122
128
  // --- Convenience methods for each management command ---
123
129
 
124
- public async startProxy(config: any): Promise<void> {
130
+ public async startProxy(config: IRustProxyOptions): Promise<void> {
125
131
  await this.bridge.sendCommand('start', { config });
126
132
  }
127
133
 
@@ -129,15 +135,15 @@ export class RustProxyBridge extends plugins.EventEmitter {
129
135
  await this.bridge.sendCommand('stop', {} as Record<string, never>);
130
136
  }
131
137
 
132
- public async updateRoutes(routes: IRouteConfig[]): Promise<void> {
138
+ public async updateRoutes(routes: IRustRouteConfig[]): Promise<void> {
133
139
  await this.bridge.sendCommand('updateRoutes', { routes });
134
140
  }
135
141
 
136
- public async getMetrics(): Promise<any> {
142
+ public async getMetrics(): Promise<IRustMetricsSnapshot> {
137
143
  return this.bridge.sendCommand('getMetrics', {} as Record<string, never>);
138
144
  }
139
145
 
140
- public async getStatistics(): Promise<any> {
146
+ public async getStatistics(): Promise<IRustStatistics> {
141
147
  return this.bridge.sendCommand('getStatistics', {} as Record<string, never>);
142
148
  }
143
149
 
@@ -149,7 +155,7 @@ export class RustProxyBridge extends plugins.EventEmitter {
149
155
  await this.bridge.sendCommand('renewCertificate', { routeName });
150
156
  }
151
157
 
152
- public async getCertificateStatus(routeName: string): Promise<any> {
158
+ public async getCertificateStatus(routeName: string): Promise<IRustCertificateStatus | null> {
153
159
  return this.bridge.sendCommand('getCertificateStatus', { routeName });
154
160
  }
155
161
 
@@ -11,6 +11,7 @@ import { RustMetricsAdapter } from './rust-metrics-adapter.js';
11
11
  // Route management
12
12
  import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
13
13
  import { RouteValidator } from './utils/route-validator.js';
14
+ import { buildRustProxyOptions } from './utils/rust-config.js';
14
15
  import { generateDefaultCertificate } from './utils/default-cert-generator.js';
15
16
  import { Mutex } from './utils/mutex.js';
16
17
  import { ConcurrencySemaphore } from './utils/concurrency-semaphore.js';
@@ -19,6 +20,7 @@ import { ConcurrencySemaphore } from './utils/concurrency-semaphore.js';
19
20
  import type { ISmartProxyOptions, TSmartProxyCertProvisionObject, IAcmeOptions, ICertProvisionEventComms, ICertificateIssuedEvent, ICertificateFailedEvent } from './models/interfaces.js';
20
21
  import type { IRouteConfig } from './models/route-types.js';
21
22
  import type { IMetrics } from './models/metrics-types.js';
23
+ import type { IRustCertificateStatus, IRustProxyOptions, IRustStatistics } from './models/rust-types.js';
22
24
 
23
25
  /**
24
26
  * SmartProxy - Rust-backed proxy engine with TypeScript configuration API.
@@ -365,7 +367,7 @@ export class SmartProxy extends plugins.EventEmitter {
365
367
  /**
366
368
  * Get certificate status for a route (async - calls Rust).
367
369
  */
368
- public async getCertificateStatus(routeName: string): Promise<any> {
370
+ public async getCertificateStatus(routeName: string): Promise<IRustCertificateStatus | null> {
369
371
  return this.bridge.getCertificateStatus(routeName);
370
372
  }
371
373
 
@@ -379,7 +381,7 @@ export class SmartProxy extends plugins.EventEmitter {
379
381
  /**
380
382
  * Get statistics (async - calls Rust).
381
383
  */
382
- public async getStatistics(): Promise<any> {
384
+ public async getStatistics(): Promise<IRustStatistics> {
383
385
  return this.bridge.getStatistics();
384
386
  }
385
387
 
@@ -484,37 +486,8 @@ export class SmartProxy extends plugins.EventEmitter {
484
486
  /**
485
487
  * Build the Rust configuration object from TS settings.
486
488
  */
487
- private buildRustConfig(routes: IRouteConfig[], acmeOverride?: IAcmeOptions): any {
488
- const acme = acmeOverride !== undefined ? acmeOverride : this.settings.acme;
489
- return {
490
- routes,
491
- defaults: this.settings.defaults,
492
- acme: acme
493
- ? {
494
- enabled: acme.enabled,
495
- email: acme.email,
496
- useProduction: acme.useProduction,
497
- port: acme.port,
498
- renewThresholdDays: acme.renewThresholdDays,
499
- autoRenew: acme.autoRenew,
500
- renewCheckIntervalHours: acme.renewCheckIntervalHours,
501
- }
502
- : undefined,
503
- connectionTimeout: this.settings.connectionTimeout,
504
- initialDataTimeout: this.settings.initialDataTimeout,
505
- socketTimeout: this.settings.socketTimeout,
506
- maxConnectionLifetime: this.settings.maxConnectionLifetime,
507
- gracefulShutdownTimeout: this.settings.gracefulShutdownTimeout,
508
- maxConnectionsPerIp: this.settings.maxConnectionsPerIP,
509
- connectionRateLimitPerMinute: this.settings.connectionRateLimitPerMinute,
510
- keepAliveTreatment: this.settings.keepAliveTreatment,
511
- keepAliveInactivityMultiplier: this.settings.keepAliveInactivityMultiplier,
512
- extendedKeepAliveLifetime: this.settings.extendedKeepAliveLifetime,
513
- proxyIps: this.settings.proxyIPs,
514
- acceptProxyProtocol: this.settings.acceptProxyProtocol,
515
- sendProxyProtocol: this.settings.sendProxyProtocol,
516
- metrics: this.settings.metrics,
517
- };
489
+ private buildRustConfig(routes: IRustProxyOptions['routes'], acmeOverride?: IAcmeOptions): IRustProxyOptions {
490
+ return buildRustProxyOptions(this.settings, routes, acmeOverride);
518
491
  }
519
492
 
520
493
  /**
@@ -168,14 +168,28 @@ export function routeMatchesHeaders(
168
168
  if (!route.match?.headers || Object.keys(route.match.headers).length === 0) {
169
169
  return true; // No headers specified means it matches any headers
170
170
  }
171
-
172
- // Convert RegExp patterns to strings for HeaderMatcher
173
- const stringHeaders: Record<string, string> = {};
174
- for (const [key, value] of Object.entries(route.match.headers)) {
175
- stringHeaders[key] = value instanceof RegExp ? value.source : value;
171
+
172
+ for (const [headerName, expectedValue] of Object.entries(route.match.headers)) {
173
+ const actualKey = Object.keys(headers).find((key) => key.toLowerCase() === headerName.toLowerCase());
174
+ const actualValue = actualKey ? headers[actualKey] : undefined;
175
+
176
+ if (actualValue === undefined) {
177
+ return false;
178
+ }
179
+
180
+ if (expectedValue instanceof RegExp) {
181
+ if (!expectedValue.test(actualValue)) {
182
+ return false;
183
+ }
184
+ continue;
185
+ }
186
+
187
+ if (!HeaderMatcher.match(expectedValue, actualValue)) {
188
+ return false;
189
+ }
176
190
  }
177
-
178
- return HeaderMatcher.matchAll(stringHeaders, headers);
191
+
192
+ return true;
179
193
  }
180
194
 
181
195
  /**
@@ -283,4 +297,4 @@ export function generateRouteId(route: IRouteConfig): string {
283
297
  */
284
298
  export function cloneRoute(route: IRouteConfig): IRouteConfig {
285
299
  return JSON.parse(JSON.stringify(route));
286
- }
300
+ }
@@ -0,0 +1,187 @@
1
+ import type { IAcmeOptions, ISmartProxyOptions } from '../models/interfaces.js';
2
+ import type { IRouteAction, IRouteConfig, IRouteMatch, IRouteTarget, ITargetMatch } from '../models/route-types.js';
3
+ import type {
4
+ IRustAcmeOptions,
5
+ IRustDefaultConfig,
6
+ IRustProxyOptions,
7
+ IRustRouteAction,
8
+ IRustRouteConfig,
9
+ IRustRouteMatch,
10
+ IRustRouteTarget,
11
+ IRustTargetMatch,
12
+ IRustRouteUdp,
13
+ TRustHeaderMatchers,
14
+ } from '../models/rust-types.js';
15
+
16
+ const SUPPORTED_REGEX_FLAGS = new Set(['i', 'm', 's', 'u', 'g']);
17
+
18
+ export function serializeHeaderMatchValue(value: string | RegExp): string {
19
+ if (typeof value === 'string') {
20
+ return value;
21
+ }
22
+
23
+ const unsupportedFlags = Array.from(new Set(value.flags)).filter((flag) => !SUPPORTED_REGEX_FLAGS.has(flag));
24
+ if (unsupportedFlags.length > 0) {
25
+ throw new Error(
26
+ `Header RegExp uses unsupported flags for Rust serialization: ${unsupportedFlags.join(', ')}`
27
+ );
28
+ }
29
+
30
+ return `/${value.source}/${value.flags}`;
31
+ }
32
+
33
+ export function serializeHeaderMatchers(headers?: Record<string, string | RegExp>): TRustHeaderMatchers | undefined {
34
+ if (!headers) {
35
+ return undefined;
36
+ }
37
+
38
+ return Object.fromEntries(
39
+ Object.entries(headers).map(([key, value]) => [key, serializeHeaderMatchValue(value)])
40
+ );
41
+ }
42
+
43
+ export function serializeTargetMatchForRust(match?: ITargetMatch): IRustTargetMatch | undefined {
44
+ if (!match) {
45
+ return undefined;
46
+ }
47
+
48
+ return {
49
+ ...match,
50
+ headers: serializeHeaderMatchers(match.headers),
51
+ };
52
+ }
53
+
54
+ export function serializeRouteMatchForRust(match: IRouteMatch): IRustRouteMatch {
55
+ return {
56
+ ...match,
57
+ headers: serializeHeaderMatchers(match.headers),
58
+ };
59
+ }
60
+
61
+ export function serializeRouteTargetForRust(target: IRouteTarget): IRustRouteTarget {
62
+ if (typeof target.host !== 'string' && !Array.isArray(target.host)) {
63
+ throw new Error('Route target host must be serialized before sending to Rust');
64
+ }
65
+
66
+ if (typeof target.port !== 'number' && target.port !== 'preserve') {
67
+ throw new Error('Route target port must be serialized before sending to Rust');
68
+ }
69
+
70
+ return {
71
+ ...target,
72
+ host: target.host,
73
+ port: target.port,
74
+ match: serializeTargetMatchForRust(target.match),
75
+ };
76
+ }
77
+
78
+ function serializeUdpForRust(udp?: IRouteAction['udp']): IRustRouteUdp | undefined {
79
+ if (!udp) {
80
+ return undefined;
81
+ }
82
+
83
+ const { maxSessionsPerIP, ...rest } = udp;
84
+
85
+ return {
86
+ ...rest,
87
+ maxSessionsPerIp: maxSessionsPerIP,
88
+ };
89
+ }
90
+
91
+ export function serializeRouteActionForRust(action: IRouteAction): IRustRouteAction {
92
+ const {
93
+ socketHandler: _socketHandler,
94
+ datagramHandler: _datagramHandler,
95
+ forwardingEngine: _forwardingEngine,
96
+ nftables: _nftables,
97
+ targets,
98
+ udp,
99
+ ...rest
100
+ } = action;
101
+
102
+ return {
103
+ ...rest,
104
+ targets: targets?.map((target) => serializeRouteTargetForRust(target)),
105
+ udp: serializeUdpForRust(udp),
106
+ };
107
+ }
108
+
109
+ export function serializeRouteForRust(route: IRouteConfig): IRustRouteConfig {
110
+ return {
111
+ ...route,
112
+ match: serializeRouteMatchForRust(route.match),
113
+ action: serializeRouteActionForRust(route.action),
114
+ };
115
+ }
116
+
117
+ function serializeAcmeForRust(acme?: IAcmeOptions): IRustAcmeOptions | undefined {
118
+ if (!acme) {
119
+ return undefined;
120
+ }
121
+
122
+ return {
123
+ enabled: acme.enabled,
124
+ email: acme.email,
125
+ environment: acme.environment,
126
+ accountEmail: acme.accountEmail,
127
+ port: acme.port,
128
+ useProduction: acme.useProduction,
129
+ renewThresholdDays: acme.renewThresholdDays,
130
+ autoRenew: acme.autoRenew,
131
+ skipConfiguredCerts: acme.skipConfiguredCerts,
132
+ renewCheckIntervalHours: acme.renewCheckIntervalHours,
133
+ };
134
+ }
135
+
136
+ function serializeDefaultsForRust(defaults?: ISmartProxyOptions['defaults']): IRustDefaultConfig | undefined {
137
+ if (!defaults) {
138
+ return undefined;
139
+ }
140
+
141
+ const { preserveSourceIP, ...rest } = defaults;
142
+
143
+ return {
144
+ ...rest,
145
+ preserveSourceIp: preserveSourceIP,
146
+ };
147
+ }
148
+
149
+ export function buildRustProxyOptions(
150
+ settings: ISmartProxyOptions,
151
+ routes: IRustRouteConfig[],
152
+ acmeOverride?: IAcmeOptions,
153
+ ): IRustProxyOptions {
154
+ const acme = acmeOverride !== undefined ? acmeOverride : settings.acme;
155
+
156
+ return {
157
+ routes,
158
+ preserveSourceIp: settings.preserveSourceIP,
159
+ proxyIps: settings.proxyIPs,
160
+ acceptProxyProtocol: settings.acceptProxyProtocol,
161
+ sendProxyProtocol: settings.sendProxyProtocol,
162
+ defaults: serializeDefaultsForRust(settings.defaults),
163
+ connectionTimeout: settings.connectionTimeout,
164
+ initialDataTimeout: settings.initialDataTimeout,
165
+ socketTimeout: settings.socketTimeout,
166
+ inactivityCheckInterval: settings.inactivityCheckInterval,
167
+ maxConnectionLifetime: settings.maxConnectionLifetime,
168
+ inactivityTimeout: settings.inactivityTimeout,
169
+ gracefulShutdownTimeout: settings.gracefulShutdownTimeout,
170
+ noDelay: settings.noDelay,
171
+ keepAlive: settings.keepAlive,
172
+ keepAliveInitialDelay: settings.keepAliveInitialDelay,
173
+ maxPendingDataSize: settings.maxPendingDataSize,
174
+ disableInactivityCheck: settings.disableInactivityCheck,
175
+ enableKeepAliveProbes: settings.enableKeepAliveProbes,
176
+ enableDetailedLogging: settings.enableDetailedLogging,
177
+ enableTlsDebugLogging: settings.enableTlsDebugLogging,
178
+ enableRandomizedTimeouts: settings.enableRandomizedTimeouts,
179
+ maxConnectionsPerIp: settings.maxConnectionsPerIP,
180
+ connectionRateLimitPerMinute: settings.connectionRateLimitPerMinute,
181
+ keepAliveTreatment: settings.keepAliveTreatment,
182
+ keepAliveInactivityMultiplier: settings.keepAliveInactivityMultiplier,
183
+ extendedKeepAliveLifetime: settings.extendedKeepAliveLifetime,
184
+ metrics: settings.metrics,
185
+ acme: serializeAcmeForRust(acme),
186
+ };
187
+ }