@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.
- package/changelog.md +109 -0
- package/dist_rust/rustproxy +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/utils/shared-security-manager.d.ts +17 -0
- package/dist_ts/core/utils/shared-security-manager.js +66 -1
- package/dist_ts/index.d.ts +1 -5
- package/dist_ts/index.js +3 -9
- package/dist_ts/protocols/common/fragment-handler.js +5 -1
- package/dist_ts/proxies/http-proxy/default-certificates.d.ts +54 -0
- package/dist_ts/proxies/http-proxy/default-certificates.js +127 -0
- package/dist_ts/proxies/http-proxy/http-proxy.d.ts +1 -1
- package/dist_ts/proxies/http-proxy/http-proxy.js +9 -14
- package/dist_ts/proxies/http-proxy/index.d.ts +5 -1
- package/dist_ts/proxies/http-proxy/index.js +6 -2
- package/dist_ts/proxies/http-proxy/security-manager.d.ts +4 -12
- package/dist_ts/proxies/http-proxy/security-manager.js +66 -99
- package/dist_ts/proxies/index.d.ts +1 -5
- package/dist_ts/proxies/index.js +2 -6
- package/dist_ts/proxies/nftables-proxy/index.d.ts +1 -0
- package/dist_ts/proxies/nftables-proxy/index.js +2 -1
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +4 -26
- package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +84 -236
- package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +9 -0
- package/dist_ts/proxies/nftables-proxy/utils/index.js +12 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +66 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +131 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +39 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +112 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +59 -0
- package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +130 -0
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +4 -3
- package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +13 -2
- package/dist_ts/proxies/smart-proxy/connection-manager.js +16 -6
- package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +35 -10
- package/dist_ts/proxies/smart-proxy/index.d.ts +5 -10
- package/dist_ts/proxies/smart-proxy/index.js +7 -13
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -3
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +72 -9
- package/dist_ts/proxies/smart-proxy/route-preprocessor.d.ts +37 -0
- package/dist_ts/proxies/smart-proxy/route-preprocessor.js +103 -0
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.d.ts +23 -0
- package/dist_ts/proxies/smart-proxy/rust-binary-locator.js +104 -0
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.d.ts +74 -0
- package/dist_ts/proxies/smart-proxy/rust-metrics-adapter.js +146 -0
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +49 -0
- package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +259 -0
- package/dist_ts/proxies/smart-proxy/security-manager.d.ts +14 -12
- package/dist_ts/proxies/smart-proxy/security-manager.js +80 -74
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +39 -157
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +224 -622
- package/dist_ts/proxies/smart-proxy/socket-handler-server.d.ts +45 -0
- package/dist_ts/proxies/smart-proxy/socket-handler-server.js +253 -0
- package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +2 -9
- package/dist_ts/proxies/smart-proxy/tls-manager.js +3 -26
- package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/utils/index.js +3 -4
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.d.ts +49 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.js +108 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.d.ts +57 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.js +89 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.js +32 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.d.ts +68 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.js +117 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.d.ts +17 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.js +27 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.d.ts +63 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.js +105 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.d.ts +83 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.js +126 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.d.ts +47 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.js +66 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.d.ts +70 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.js +287 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.d.ts +46 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.js +67 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +4 -457
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +6 -950
- package/dist_ts/proxies/smart-proxy/utils/route-utils.js +2 -2
- package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +67 -1
- package/dist_ts/proxies/smart-proxy/utils/route-validator.js +251 -3
- package/dist_ts/routing/index.d.ts +1 -1
- package/dist_ts/routing/index.js +3 -3
- package/dist_ts/routing/models/http-types.d.ts +119 -4
- package/dist_ts/routing/models/http-types.js +93 -5
- package/npmextra.json +12 -6
- package/package.json +34 -24
- package/readme.hints.md +184 -1
- package/readme.md +580 -266
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/utils/shared-security-manager.ts +98 -13
- package/ts/index.ts +4 -12
- package/ts/protocols/common/fragment-handler.ts +4 -0
- package/ts/proxies/index.ts +1 -9
- package/ts/proxies/nftables-proxy/index.ts +1 -0
- package/ts/proxies/nftables-proxy/nftables-proxy.ts +116 -290
- package/ts/proxies/nftables-proxy/utils/index.ts +38 -0
- package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +162 -0
- package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +125 -0
- package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +156 -0
- package/ts/proxies/smart-proxy/index.ts +6 -13
- package/ts/proxies/smart-proxy/models/interfaces.ts +6 -5
- package/ts/proxies/smart-proxy/route-preprocessor.ts +122 -0
- package/ts/proxies/smart-proxy/rust-binary-locator.ts +112 -0
- package/ts/proxies/smart-proxy/rust-metrics-adapter.ts +161 -0
- package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +310 -0
- package/ts/proxies/smart-proxy/smart-proxy.ts +282 -800
- package/ts/proxies/smart-proxy/socket-handler-server.ts +279 -0
- package/ts/proxies/smart-proxy/utils/index.ts +3 -5
- package/ts/proxies/smart-proxy/utils/route-helpers/api-helpers.ts +144 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.ts +124 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/http-helpers.ts +40 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/https-helpers.ts +163 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/index.ts +62 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.ts +154 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.ts +202 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/security-helpers.ts +96 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.ts +337 -0
- package/ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.ts +98 -0
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +5 -1302
- package/ts/proxies/smart-proxy/utils/route-utils.ts +1 -1
- package/ts/proxies/smart-proxy/utils/route-validator.ts +274 -4
- package/ts/routing/index.ts +2 -2
- package/ts/routing/models/http-types.ts +147 -4
- package/ts/proxies/http-proxy/certificate-manager.ts +0 -244
- package/ts/proxies/http-proxy/connection-pool.ts +0 -228
- package/ts/proxies/http-proxy/context-creator.ts +0 -145
- package/ts/proxies/http-proxy/function-cache.ts +0 -279
- package/ts/proxies/http-proxy/handlers/index.ts +0 -5
- package/ts/proxies/http-proxy/http-proxy.ts +0 -675
- package/ts/proxies/http-proxy/http-request-handler.ts +0 -331
- package/ts/proxies/http-proxy/http2-request-handler.ts +0 -255
- package/ts/proxies/http-proxy/index.ts +0 -13
- package/ts/proxies/http-proxy/models/http-types.ts +0 -148
- package/ts/proxies/http-proxy/models/index.ts +0 -5
- package/ts/proxies/http-proxy/models/types.ts +0 -125
- package/ts/proxies/http-proxy/request-handler.ts +0 -878
- package/ts/proxies/http-proxy/security-manager.ts +0 -433
- package/ts/proxies/http-proxy/websocket-handler.ts +0 -581
- package/ts/proxies/smart-proxy/acme-state-manager.ts +0 -112
- package/ts/proxies/smart-proxy/cert-store.ts +0 -92
- package/ts/proxies/smart-proxy/certificate-manager.ts +0 -894
- package/ts/proxies/smart-proxy/connection-manager.ts +0 -796
- package/ts/proxies/smart-proxy/http-proxy-bridge.ts +0 -187
- package/ts/proxies/smart-proxy/metrics-collector.ts +0 -453
- package/ts/proxies/smart-proxy/nftables-manager.ts +0 -271
- package/ts/proxies/smart-proxy/port-manager.ts +0 -358
- package/ts/proxies/smart-proxy/route-connection-handler.ts +0 -1640
- package/ts/proxies/smart-proxy/route-orchestrator.ts +0 -297
- package/ts/proxies/smart-proxy/security-manager.ts +0 -257
- package/ts/proxies/smart-proxy/throughput-tracker.ts +0 -138
- package/ts/proxies/smart-proxy/timeout-manager.ts +0 -196
- package/ts/proxies/smart-proxy/tls-manager.ts +0 -207
- package/ts/proxies/smart-proxy/utils/route-validators.ts +0 -283
|
@@ -1,942 +1,424 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import { logger } from '../../core/utils/logger.js';
|
|
3
|
-
import { connectionLogDeduplicator } from '../../core/utils/log-deduplicator.js';
|
|
4
|
-
|
|
5
|
-
// Importing required components
|
|
6
|
-
import { ConnectionManager } from './connection-manager.js';
|
|
7
|
-
import { SecurityManager } from './security-manager.js';
|
|
8
|
-
import { TlsManager } from './tls-manager.js';
|
|
9
|
-
import { HttpProxyBridge } from './http-proxy-bridge.js';
|
|
10
|
-
import { TimeoutManager } from './timeout-manager.js';
|
|
11
|
-
import { PortManager } from './port-manager.js';
|
|
12
|
-
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
13
|
-
import { RouteConnectionHandler } from './route-connection-handler.js';
|
|
14
|
-
import { NFTablesManager } from './nftables-manager.js';
|
|
15
|
-
|
|
16
|
-
// Certificate manager
|
|
17
|
-
import { SmartCertManager, type ICertStatus } from './certificate-manager.js';
|
|
18
|
-
|
|
19
|
-
// Import types and utilities
|
|
20
|
-
import type {
|
|
21
|
-
ISmartProxyOptions
|
|
22
|
-
} from './models/interfaces.js';
|
|
23
|
-
import type { IRouteConfig } from './models/route-types.js';
|
|
24
3
|
|
|
25
|
-
//
|
|
26
|
-
import {
|
|
4
|
+
// Rust bridge and helpers
|
|
5
|
+
import { RustProxyBridge } from './rust-proxy-bridge.js';
|
|
6
|
+
import { RustBinaryLocator } from './rust-binary-locator.js';
|
|
7
|
+
import { RoutePreprocessor } from './route-preprocessor.js';
|
|
8
|
+
import { SocketHandlerServer } from './socket-handler-server.js';
|
|
9
|
+
import { RustMetricsAdapter } from './rust-metrics-adapter.js';
|
|
27
10
|
|
|
28
|
-
//
|
|
11
|
+
// Route management
|
|
12
|
+
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
|
|
29
13
|
import { RouteValidator } from './utils/route-validator.js';
|
|
14
|
+
import { Mutex } from './utils/mutex.js';
|
|
30
15
|
|
|
31
|
-
//
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
// Import ACME state manager
|
|
35
|
-
import { AcmeStateManager } from './acme-state-manager.js';
|
|
36
|
-
|
|
37
|
-
// Import metrics collector
|
|
38
|
-
import { MetricsCollector } from './metrics-collector.js';
|
|
16
|
+
// Types
|
|
17
|
+
import type { ISmartProxyOptions, TSmartProxyCertProvisionObject } from './models/interfaces.js';
|
|
18
|
+
import type { IRouteConfig } from './models/route-types.js';
|
|
39
19
|
import type { IMetrics } from './models/metrics-types.js';
|
|
40
20
|
|
|
41
21
|
/**
|
|
42
|
-
* SmartProxy -
|
|
43
|
-
*
|
|
44
|
-
* SmartProxy is a unified proxy system that works with routes to define connection handling behavior.
|
|
45
|
-
* Each route contains matching criteria (ports, domains, etc.) and an action to take (forward, redirect, block).
|
|
22
|
+
* SmartProxy - Rust-backed proxy engine with TypeScript configuration API.
|
|
46
23
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* -
|
|
50
|
-
* -
|
|
51
|
-
* -
|
|
52
|
-
* -
|
|
24
|
+
* All networking (TCP, TLS, HTTP reverse proxy, connection management, security,
|
|
25
|
+
* NFTables) is handled by the Rust binary. TypeScript is only:
|
|
26
|
+
* - The npm module interface (types, route helpers)
|
|
27
|
+
* - The thin IPC wrapper (this class)
|
|
28
|
+
* - Socket-handler callback relay (for JS-defined handlers)
|
|
29
|
+
* - Certificate provisioning callbacks (certProvisionFunction)
|
|
53
30
|
*/
|
|
54
31
|
export class SmartProxy extends plugins.EventEmitter {
|
|
55
|
-
|
|
56
|
-
private portManager: PortManager;
|
|
57
|
-
private connectionLogger: NodeJS.Timeout | null = null;
|
|
58
|
-
private isShuttingDown: boolean = false;
|
|
59
|
-
|
|
60
|
-
// Component managers
|
|
61
|
-
public connectionManager: ConnectionManager;
|
|
62
|
-
public securityManager: SecurityManager;
|
|
63
|
-
public tlsManager: TlsManager;
|
|
64
|
-
public httpProxyBridge: HttpProxyBridge;
|
|
65
|
-
public timeoutManager: TimeoutManager;
|
|
32
|
+
public settings: ISmartProxyOptions;
|
|
66
33
|
public routeManager: RouteManager;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Global challenge route tracking
|
|
74
|
-
private globalChallengeRouteActive: boolean = false;
|
|
34
|
+
|
|
35
|
+
private bridge: RustProxyBridge;
|
|
36
|
+
private preprocessor: RoutePreprocessor;
|
|
37
|
+
private socketHandlerServer: SocketHandlerServer | null = null;
|
|
38
|
+
private metricsAdapter: RustMetricsAdapter;
|
|
75
39
|
private routeUpdateLock: Mutex;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Metrics collector
|
|
79
|
-
public metricsCollector: MetricsCollector;
|
|
80
|
-
|
|
81
|
-
// Route orchestrator for managing route updates
|
|
82
|
-
private routeOrchestrator: RouteOrchestrator;
|
|
83
|
-
|
|
84
|
-
// Track port usage across route updates
|
|
85
|
-
private portUsageMap: Map<number, Set<string>> = new Map();
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Constructor for SmartProxy
|
|
89
|
-
*
|
|
90
|
-
* @param settingsArg Configuration options containing routes and other settings
|
|
91
|
-
* Routes define how traffic is matched and handled, with each route having:
|
|
92
|
-
* - match: criteria for matching traffic (ports, domains, paths, IPs)
|
|
93
|
-
* - action: what to do with matched traffic (forward, redirect, block)
|
|
94
|
-
*
|
|
95
|
-
* Example:
|
|
96
|
-
* ```ts
|
|
97
|
-
* const proxy = new SmartProxy({
|
|
98
|
-
* routes: [
|
|
99
|
-
* {
|
|
100
|
-
* match: {
|
|
101
|
-
* ports: 443,
|
|
102
|
-
* domains: ['example.com', '*.example.com']
|
|
103
|
-
* },
|
|
104
|
-
* action: {
|
|
105
|
-
* type: 'forward',
|
|
106
|
-
* target: { host: '10.0.0.1', port: 8443 },
|
|
107
|
-
* tls: { mode: 'passthrough' }
|
|
108
|
-
* }
|
|
109
|
-
* }
|
|
110
|
-
* ],
|
|
111
|
-
* defaults: {
|
|
112
|
-
* target: { host: 'localhost', port: 8080 },
|
|
113
|
-
* security: { ipAllowList: ['*'] }
|
|
114
|
-
* }
|
|
115
|
-
* });
|
|
116
|
-
* ```
|
|
117
|
-
*/
|
|
40
|
+
private stopping = false;
|
|
41
|
+
|
|
118
42
|
constructor(settingsArg: ISmartProxyOptions) {
|
|
119
43
|
super();
|
|
120
|
-
|
|
121
|
-
//
|
|
44
|
+
|
|
45
|
+
// Apply defaults
|
|
122
46
|
this.settings = {
|
|
123
47
|
...settingsArg,
|
|
124
48
|
initialDataTimeout: settingsArg.initialDataTimeout || 120000,
|
|
125
49
|
socketTimeout: settingsArg.socketTimeout || 3600000,
|
|
126
|
-
inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000,
|
|
127
50
|
maxConnectionLifetime: settingsArg.maxConnectionLifetime || 86400000,
|
|
128
51
|
inactivityTimeout: settingsArg.inactivityTimeout || 14400000,
|
|
129
52
|
gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000,
|
|
130
|
-
noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
|
|
131
|
-
keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
|
|
132
|
-
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000,
|
|
133
|
-
maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024,
|
|
134
|
-
disableInactivityCheck: settingsArg.disableInactivityCheck || false,
|
|
135
|
-
enableKeepAliveProbes:
|
|
136
|
-
settingsArg.enableKeepAliveProbes !== undefined ? settingsArg.enableKeepAliveProbes : true,
|
|
137
|
-
enableDetailedLogging: settingsArg.enableDetailedLogging || false,
|
|
138
|
-
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
|
|
139
|
-
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
|
|
140
|
-
allowSessionTicket:
|
|
141
|
-
settingsArg.allowSessionTicket !== undefined ? settingsArg.allowSessionTicket : true,
|
|
142
53
|
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100,
|
|
143
54
|
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300,
|
|
144
55
|
keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
|
|
145
56
|
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
|
|
146
57
|
extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000,
|
|
147
|
-
httpProxyPort: settingsArg.httpProxyPort || 8443,
|
|
148
58
|
};
|
|
149
|
-
|
|
150
|
-
// Normalize ACME options
|
|
59
|
+
|
|
60
|
+
// Normalize ACME options
|
|
151
61
|
if (this.settings.acme) {
|
|
152
|
-
// Support both 'email' and 'accountEmail' fields
|
|
153
62
|
if (this.settings.acme.accountEmail && !this.settings.acme.email) {
|
|
154
63
|
this.settings.acme.email = this.settings.acme.accountEmail;
|
|
155
64
|
}
|
|
156
|
-
|
|
157
|
-
// Set reasonable defaults for commonly used fields
|
|
158
65
|
this.settings.acme = {
|
|
159
|
-
enabled: this.settings.acme.enabled !== false,
|
|
66
|
+
enabled: this.settings.acme.enabled !== false,
|
|
160
67
|
port: this.settings.acme.port || 80,
|
|
161
68
|
email: this.settings.acme.email,
|
|
162
69
|
useProduction: this.settings.acme.useProduction || false,
|
|
163
70
|
renewThresholdDays: this.settings.acme.renewThresholdDays || 30,
|
|
164
|
-
autoRenew: this.settings.acme.autoRenew !== false,
|
|
71
|
+
autoRenew: this.settings.acme.autoRenew !== false,
|
|
165
72
|
certificateStore: this.settings.acme.certificateStore || './certs',
|
|
166
73
|
skipConfiguredCerts: this.settings.acme.skipConfiguredCerts || false,
|
|
167
74
|
renewCheckIntervalHours: this.settings.acme.renewCheckIntervalHours || 24,
|
|
168
75
|
routeForwards: this.settings.acme.routeForwards || [],
|
|
169
|
-
...this.settings.acme
|
|
76
|
+
...this.settings.acme,
|
|
170
77
|
};
|
|
171
78
|
}
|
|
172
|
-
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
this.securityManager = new SecurityManager(this);
|
|
176
|
-
this.connectionManager = new ConnectionManager(this);
|
|
177
|
-
|
|
178
|
-
// Create the route manager with SharedRouteManager API
|
|
179
|
-
// Create a logger adapter to match ILogger interface
|
|
180
|
-
const loggerAdapter = {
|
|
181
|
-
debug: (message: string, data?: any) => logger.log('debug', message, data),
|
|
182
|
-
info: (message: string, data?: any) => logger.log('info', message, data),
|
|
183
|
-
warn: (message: string, data?: any) => logger.log('warn', message, data),
|
|
184
|
-
error: (message: string, data?: any) => logger.log('error', message, data)
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
// Validate initial routes
|
|
188
|
-
if (this.settings.routes && this.settings.routes.length > 0) {
|
|
79
|
+
|
|
80
|
+
// Validate routes
|
|
81
|
+
if (this.settings.routes?.length) {
|
|
189
82
|
const validation = RouteValidator.validateRoutes(this.settings.routes);
|
|
190
83
|
if (!validation.valid) {
|
|
191
84
|
RouteValidator.logValidationErrors(validation.errors);
|
|
192
85
|
throw new Error(`Initial route validation failed: ${validation.errors.size} route(s) have errors`);
|
|
193
86
|
}
|
|
194
87
|
}
|
|
195
|
-
|
|
88
|
+
|
|
89
|
+
// Create logger adapter
|
|
90
|
+
const loggerAdapter = {
|
|
91
|
+
debug: (message: string, data?: any) => logger.log('debug', message, data),
|
|
92
|
+
info: (message: string, data?: any) => logger.log('info', message, data),
|
|
93
|
+
warn: (message: string, data?: any) => logger.log('warn', message, data),
|
|
94
|
+
error: (message: string, data?: any) => logger.log('error', message, data),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Initialize components
|
|
196
98
|
this.routeManager = new RouteManager({
|
|
197
99
|
logger: loggerAdapter,
|
|
198
100
|
enableDetailedLogging: this.settings.enableDetailedLogging,
|
|
199
|
-
routes: this.settings.routes
|
|
101
|
+
routes: this.settings.routes,
|
|
200
102
|
});
|
|
201
103
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
this.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// Initialize connection handler with route support
|
|
208
|
-
this.routeConnectionHandler = new RouteConnectionHandler(this);
|
|
209
|
-
|
|
210
|
-
// Initialize port manager
|
|
211
|
-
this.portManager = new PortManager(this);
|
|
212
|
-
|
|
213
|
-
// Initialize NFTablesManager
|
|
214
|
-
this.nftablesManager = new NFTablesManager(this);
|
|
215
|
-
|
|
216
|
-
// Initialize route update mutex for synchronization
|
|
217
|
-
this.routeUpdateLock = new Mutex();
|
|
218
|
-
|
|
219
|
-
// Initialize ACME state manager
|
|
220
|
-
this.acmeStateManager = new AcmeStateManager();
|
|
221
|
-
|
|
222
|
-
// Initialize metrics collector with reference to this SmartProxy instance
|
|
223
|
-
this.metricsCollector = new MetricsCollector(this, {
|
|
224
|
-
sampleIntervalMs: this.settings.metrics?.sampleIntervalMs,
|
|
225
|
-
retentionSeconds: this.settings.metrics?.retentionSeconds
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// Initialize route orchestrator for managing route updates
|
|
229
|
-
this.routeOrchestrator = new RouteOrchestrator(
|
|
230
|
-
this.portManager,
|
|
231
|
-
this.routeManager,
|
|
232
|
-
this.httpProxyBridge,
|
|
233
|
-
this.nftablesManager,
|
|
234
|
-
null, // certManager will be set later
|
|
235
|
-
loggerAdapter
|
|
104
|
+
this.bridge = new RustProxyBridge();
|
|
105
|
+
this.preprocessor = new RoutePreprocessor();
|
|
106
|
+
this.metricsAdapter = new RustMetricsAdapter(
|
|
107
|
+
this.bridge,
|
|
108
|
+
this.settings.metrics?.sampleIntervalMs ?? 1000
|
|
236
109
|
);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* The settings for the SmartProxy
|
|
241
|
-
*/
|
|
242
|
-
public settings: ISmartProxyOptions;
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Helper method to create and configure certificate manager
|
|
246
|
-
* This ensures consistent setup including the required ACME callback
|
|
247
|
-
*/
|
|
248
|
-
private async createCertificateManager(
|
|
249
|
-
routes: IRouteConfig[],
|
|
250
|
-
certStore: string = './certs',
|
|
251
|
-
acmeOptions?: any,
|
|
252
|
-
initialState?: { challengeRouteActive?: boolean }
|
|
253
|
-
): Promise<SmartCertManager> {
|
|
254
|
-
const certManager = new SmartCertManager(routes, certStore, acmeOptions, initialState);
|
|
255
|
-
|
|
256
|
-
// Always set up the route update callback for ACME challenges
|
|
257
|
-
certManager.setUpdateRoutesCallback(async (routes) => {
|
|
258
|
-
await this.updateRoutes(routes);
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
// Connect with HttpProxy if available
|
|
262
|
-
if (this.httpProxyBridge.getHttpProxy()) {
|
|
263
|
-
certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Set the ACME state manager
|
|
267
|
-
certManager.setAcmeStateManager(this.acmeStateManager);
|
|
268
|
-
|
|
269
|
-
// Pass down the global ACME config if available
|
|
270
|
-
if (this.settings.acme) {
|
|
271
|
-
certManager.setGlobalAcmeDefaults(this.settings.acme);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Pass down the custom certificate provision function if available
|
|
275
|
-
if (this.settings.certProvisionFunction) {
|
|
276
|
-
certManager.setCertProvisionFunction(this.settings.certProvisionFunction);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Pass down the fallback to ACME setting
|
|
280
|
-
if (this.settings.certProvisionFallbackToAcme !== undefined) {
|
|
281
|
-
certManager.setCertProvisionFallbackToAcme(this.settings.certProvisionFallbackToAcme);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
await certManager.initialize();
|
|
285
|
-
return certManager;
|
|
110
|
+
this.routeUpdateLock = new Mutex();
|
|
286
111
|
}
|
|
287
112
|
|
|
288
113
|
/**
|
|
289
|
-
*
|
|
114
|
+
* Start the proxy.
|
|
115
|
+
* Spawns the Rust binary, configures socket relay if needed, sends routes, handles cert provisioning.
|
|
290
116
|
*/
|
|
291
|
-
|
|
292
|
-
//
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
if (autoRoutes.length === 0 && !this.hasStaticCertRoutes()) {
|
|
298
|
-
logger.log('info', 'No routes require certificate management', { component: 'certificate-manager' });
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Prepare ACME options with priority:
|
|
303
|
-
// 1. Use top-level ACME config if available
|
|
304
|
-
// 2. Fall back to first auto route's ACME config
|
|
305
|
-
// 3. Otherwise use undefined
|
|
306
|
-
let acmeOptions: { email?: string; useProduction?: boolean; port?: number } | undefined;
|
|
307
|
-
|
|
308
|
-
if (this.settings.acme?.email) {
|
|
309
|
-
// Use top-level ACME config
|
|
310
|
-
acmeOptions = {
|
|
311
|
-
email: this.settings.acme.email,
|
|
312
|
-
useProduction: this.settings.acme.useProduction || false,
|
|
313
|
-
port: this.settings.acme.port || 80
|
|
314
|
-
};
|
|
315
|
-
logger.log('info', `Using top-level ACME configuration with email: ${acmeOptions.email}`, { component: 'certificate-manager' });
|
|
316
|
-
} else if (autoRoutes.length > 0) {
|
|
317
|
-
// Check for route-level ACME config
|
|
318
|
-
const routeWithAcme = autoRoutes.find(r => r.action.tls?.acme?.email);
|
|
319
|
-
if (routeWithAcme?.action.tls?.acme) {
|
|
320
|
-
const routeAcme = routeWithAcme.action.tls.acme;
|
|
321
|
-
acmeOptions = {
|
|
322
|
-
email: routeAcme.email,
|
|
323
|
-
useProduction: routeAcme.useProduction || false,
|
|
324
|
-
port: routeAcme.challengePort || 80
|
|
325
|
-
};
|
|
326
|
-
logger.log('info', `Using route-level ACME configuration from route '${routeWithAcme.name}' with email: ${acmeOptions.email}`, { component: 'certificate-manager' });
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Validate we have required configuration
|
|
331
|
-
if (autoRoutes.length > 0 && !acmeOptions?.email) {
|
|
117
|
+
public async start(): Promise<void> {
|
|
118
|
+
// Spawn Rust binary
|
|
119
|
+
const spawned = await this.bridge.spawn();
|
|
120
|
+
if (!spawned) {
|
|
332
121
|
throw new Error(
|
|
333
|
-
'
|
|
334
|
-
'
|
|
335
|
-
'1. Top-level "acme" configuration\n' +
|
|
336
|
-
'2. Individual route\'s "tls.acme" configuration'
|
|
122
|
+
'RustProxy binary not found. Set SMARTPROXY_RUST_BINARY env var, install the platform package, ' +
|
|
123
|
+
'or build locally with: cd rust && cargo build --release'
|
|
337
124
|
);
|
|
338
125
|
}
|
|
339
|
-
|
|
340
|
-
// Use the helper method to create and configure the certificate manager
|
|
341
|
-
this.certManager = await this.createCertificateManager(
|
|
342
|
-
this.settings.routes,
|
|
343
|
-
this.settings.acme?.certificateStore || './certs',
|
|
344
|
-
acmeOptions
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Check if we have routes with static certificates
|
|
350
|
-
*/
|
|
351
|
-
private hasStaticCertRoutes(): boolean {
|
|
352
|
-
return this.settings.routes.some(r =>
|
|
353
|
-
r.action.tls?.certificate &&
|
|
354
|
-
r.action.tls.certificate !== 'auto'
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Start the proxy server with support for both configuration types
|
|
360
|
-
*/
|
|
361
|
-
public async start() {
|
|
362
|
-
// Don't start if already shutting down
|
|
363
|
-
if (this.isShuttingDown) {
|
|
364
|
-
logger.log('warn', "Cannot start SmartProxy while it's in the shutdown process");
|
|
365
|
-
return;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Validate the route configuration
|
|
369
|
-
const configWarnings = this.routeManager.validateConfiguration();
|
|
370
|
-
|
|
371
|
-
// Also validate ACME configuration
|
|
372
|
-
const acmeWarnings = this.validateAcmeConfiguration();
|
|
373
|
-
const allWarnings = [...configWarnings, ...acmeWarnings];
|
|
374
|
-
|
|
375
|
-
if (allWarnings.length > 0) {
|
|
376
|
-
logger.log('warn', `${allWarnings.length} configuration warnings found`, { count: allWarnings.length });
|
|
377
|
-
for (const warning of allWarnings) {
|
|
378
|
-
logger.log('warn', `${warning}`);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
126
|
|
|
382
|
-
//
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
// Log port usage for startup
|
|
389
|
-
logger.log('info', `SmartProxy starting with ${listeningPorts.length} ports: ${listeningPorts.join(', ')}`, {
|
|
390
|
-
portCount: listeningPorts.length,
|
|
391
|
-
ports: listeningPorts,
|
|
392
|
-
component: 'smart-proxy'
|
|
127
|
+
// Handle unexpected exit (only emits error if not intentionally stopping)
|
|
128
|
+
this.bridge.on('exit', (code: number | null, signal: string | null) => {
|
|
129
|
+
if (this.stopping) return;
|
|
130
|
+
logger.log('error', `RustProxy exited unexpectedly (code=${code}, signal=${signal})`, { component: 'smart-proxy' });
|
|
131
|
+
this.emit('error', new Error(`RustProxy exited (code=${code}, signal=${signal})`));
|
|
393
132
|
});
|
|
394
133
|
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
134
|
+
// Check if any routes need TS-side handling (socket handlers, dynamic functions)
|
|
135
|
+
const hasHandlerRoutes = this.settings.routes.some(
|
|
136
|
+
(r) =>
|
|
137
|
+
(r.action.type === 'socket-handler' && r.action.socketHandler) ||
|
|
138
|
+
r.action.targets?.some((t) => typeof t.host === 'function' || typeof t.port === 'function')
|
|
139
|
+
);
|
|
401
140
|
|
|
402
|
-
//
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
await this.
|
|
141
|
+
// Start socket handler relay server (but don't tell Rust yet - proxy not started)
|
|
142
|
+
if (hasHandlerRoutes) {
|
|
143
|
+
this.socketHandlerServer = new SocketHandlerServer(this.preprocessor);
|
|
144
|
+
await this.socketHandlerServer.start();
|
|
406
145
|
}
|
|
407
146
|
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
await this.portManager.addPorts(listeningPorts);
|
|
411
|
-
|
|
412
|
-
// Initialize certificate manager AFTER port binding is complete
|
|
413
|
-
// This ensures the ACME challenge port is already bound and ready when needed
|
|
414
|
-
await this.initializeCertificateManager();
|
|
415
|
-
|
|
416
|
-
// Connect certificate manager with HttpProxy if both are available
|
|
417
|
-
if (this.certManager && this.httpProxyBridge.getHttpProxy()) {
|
|
418
|
-
this.certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
|
|
419
|
-
}
|
|
147
|
+
// Preprocess routes (strip JS functions, convert socket-handler routes)
|
|
148
|
+
const rustRoutes = this.preprocessor.preprocessForRust(this.settings.routes);
|
|
420
149
|
|
|
421
|
-
//
|
|
422
|
-
|
|
423
|
-
logger.log('info', 'Starting certificate provisioning now that ports are ready', { component: 'certificate-manager' });
|
|
424
|
-
await this.certManager.provisionAllCertificates();
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// Start the metrics collector now that all components are initialized
|
|
428
|
-
this.metricsCollector.start();
|
|
429
|
-
|
|
430
|
-
// Set up periodic connection logging and inactivity checks
|
|
431
|
-
this.connectionLogger = setInterval(() => {
|
|
432
|
-
// Immediately return if shutting down
|
|
433
|
-
if (this.isShuttingDown) return;
|
|
434
|
-
|
|
435
|
-
// Perform inactivity check
|
|
436
|
-
this.connectionManager.performInactivityCheck();
|
|
437
|
-
|
|
438
|
-
// Log connection statistics
|
|
439
|
-
const now = Date.now();
|
|
440
|
-
let maxIncoming = 0;
|
|
441
|
-
let maxOutgoing = 0;
|
|
442
|
-
let tlsConnections = 0;
|
|
443
|
-
let nonTlsConnections = 0;
|
|
444
|
-
let completedTlsHandshakes = 0;
|
|
445
|
-
let pendingTlsHandshakes = 0;
|
|
446
|
-
let keepAliveConnections = 0;
|
|
447
|
-
let httpProxyConnections = 0;
|
|
448
|
-
|
|
449
|
-
// Get connection records for analysis
|
|
450
|
-
const connectionRecords = this.connectionManager.getConnections();
|
|
451
|
-
|
|
452
|
-
// Analyze active connections
|
|
453
|
-
for (const record of connectionRecords.values()) {
|
|
454
|
-
// Track connection stats
|
|
455
|
-
if (record.isTLS) {
|
|
456
|
-
tlsConnections++;
|
|
457
|
-
if (record.tlsHandshakeComplete) {
|
|
458
|
-
completedTlsHandshakes++;
|
|
459
|
-
} else {
|
|
460
|
-
pendingTlsHandshakes++;
|
|
461
|
-
}
|
|
462
|
-
} else {
|
|
463
|
-
nonTlsConnections++;
|
|
464
|
-
}
|
|
150
|
+
// Build Rust config
|
|
151
|
+
const config = this.buildRustConfig(rustRoutes);
|
|
465
152
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
153
|
+
// Start the Rust proxy
|
|
154
|
+
await this.bridge.startProxy(config);
|
|
469
155
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
156
|
+
// Now that Rust proxy is running, configure socket handler relay
|
|
157
|
+
if (this.socketHandlerServer) {
|
|
158
|
+
await this.bridge.setSocketHandlerRelay(this.socketHandlerServer.getSocketPath());
|
|
159
|
+
}
|
|
473
160
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
maxOutgoing = Math.max(maxOutgoing, now - record.outgoingStartTime);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
161
|
+
// Handle certProvisionFunction
|
|
162
|
+
await this.provisionCertificatesViaCallback();
|
|
479
163
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
// Log detailed stats
|
|
484
|
-
logger.log('info', 'Connection statistics', {
|
|
485
|
-
activeConnections: connectionRecords.size,
|
|
486
|
-
tls: {
|
|
487
|
-
total: tlsConnections,
|
|
488
|
-
completed: completedTlsHandshakes,
|
|
489
|
-
pending: pendingTlsHandshakes
|
|
490
|
-
},
|
|
491
|
-
nonTls: nonTlsConnections,
|
|
492
|
-
keepAlive: keepAliveConnections,
|
|
493
|
-
httpProxy: httpProxyConnections,
|
|
494
|
-
longestRunning: {
|
|
495
|
-
incoming: plugins.prettyMs(maxIncoming),
|
|
496
|
-
outgoing: plugins.prettyMs(maxOutgoing)
|
|
497
|
-
},
|
|
498
|
-
terminationStats: {
|
|
499
|
-
incoming: terminationStats.incoming,
|
|
500
|
-
outgoing: terminationStats.outgoing
|
|
501
|
-
},
|
|
502
|
-
component: 'connection-manager'
|
|
503
|
-
});
|
|
504
|
-
}, this.settings.inactivityCheckInterval || 60000);
|
|
505
|
-
|
|
506
|
-
// Make sure the interval doesn't keep the process alive
|
|
507
|
-
if (this.connectionLogger.unref) {
|
|
508
|
-
this.connectionLogger.unref();
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
/**
|
|
513
|
-
* Extract domain configurations from routes for certificate provisioning
|
|
514
|
-
*
|
|
515
|
-
* Note: This method has been removed as we now work directly with routes
|
|
516
|
-
*/
|
|
517
|
-
|
|
518
|
-
/**
|
|
519
|
-
* Stop the proxy server
|
|
520
|
-
*/
|
|
521
|
-
public async stop() {
|
|
522
|
-
logger.log('info', 'SmartProxy shutting down...');
|
|
523
|
-
this.isShuttingDown = true;
|
|
524
|
-
this.portManager.setShuttingDown(true);
|
|
525
|
-
|
|
526
|
-
// Stop certificate manager
|
|
527
|
-
if (this.certManager) {
|
|
528
|
-
await this.certManager.stop();
|
|
529
|
-
logger.log('info', 'Certificate manager stopped');
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// Stop NFTablesManager
|
|
533
|
-
await this.nftablesManager.stop();
|
|
534
|
-
logger.log('info', 'NFTablesManager stopped');
|
|
535
|
-
|
|
536
|
-
// Stop the connection logger
|
|
537
|
-
if (this.connectionLogger) {
|
|
538
|
-
clearInterval(this.connectionLogger);
|
|
539
|
-
this.connectionLogger = null;
|
|
540
|
-
}
|
|
164
|
+
// Start metrics polling
|
|
165
|
+
this.metricsAdapter.startPolling();
|
|
541
166
|
|
|
542
|
-
|
|
543
|
-
await this.portManager.closeAll();
|
|
544
|
-
logger.log('info', 'All servers closed. Cleaning up active connections...');
|
|
545
|
-
|
|
546
|
-
// Clean up all active connections
|
|
547
|
-
await this.connectionManager.clearConnections();
|
|
548
|
-
|
|
549
|
-
// Stop HttpProxy
|
|
550
|
-
await this.httpProxyBridge.stop();
|
|
551
|
-
|
|
552
|
-
// Clear ACME state manager
|
|
553
|
-
this.acmeStateManager.clear();
|
|
554
|
-
|
|
555
|
-
// Stop metrics collector
|
|
556
|
-
this.metricsCollector.stop();
|
|
557
|
-
|
|
558
|
-
// Clean up ProtocolDetector singleton
|
|
559
|
-
const detection = await import('../../detection/index.js');
|
|
560
|
-
detection.ProtocolDetector.destroy();
|
|
561
|
-
|
|
562
|
-
// Flush any pending deduplicated logs
|
|
563
|
-
connectionLogDeduplicator.flushAll();
|
|
564
|
-
|
|
565
|
-
logger.log('info', 'SmartProxy shutdown complete.');
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
/**
|
|
569
|
-
* Updates the domain configurations for the proxy
|
|
570
|
-
*
|
|
571
|
-
* Note: This legacy method has been removed. Use updateRoutes instead.
|
|
572
|
-
*/
|
|
573
|
-
public async updateDomainConfigs(): Promise<void> {
|
|
574
|
-
logger.log('warn', 'Method updateDomainConfigs() is deprecated. Use updateRoutes() instead.');
|
|
575
|
-
throw new Error('updateDomainConfigs() is deprecated - use updateRoutes() instead');
|
|
167
|
+
logger.log('info', 'SmartProxy started (Rust engine)', { component: 'smart-proxy' });
|
|
576
168
|
}
|
|
577
|
-
|
|
169
|
+
|
|
578
170
|
/**
|
|
579
|
-
*
|
|
171
|
+
* Stop the proxy.
|
|
580
172
|
*/
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
} catch (error) {
|
|
593
|
-
// Silently handle logging errors
|
|
594
|
-
console.log('[INFO] Challenge route successfully removed from routes');
|
|
595
|
-
}
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// Wait before retrying
|
|
600
|
-
await plugins.smartdelay.delayFor(retryDelay);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
const error = `Failed to verify challenge route removal after ${maxRetries} attempts`;
|
|
173
|
+
public async stop(): Promise<void> {
|
|
174
|
+
logger.log('info', 'SmartProxy shutting down...', { component: 'smart-proxy' });
|
|
175
|
+
this.stopping = true;
|
|
176
|
+
|
|
177
|
+
// Stop metrics polling
|
|
178
|
+
this.metricsAdapter.stopPolling();
|
|
179
|
+
|
|
180
|
+
// Remove exit listener before killing to avoid spurious error events
|
|
181
|
+
this.bridge.removeAllListeners('exit');
|
|
182
|
+
|
|
183
|
+
// Stop Rust proxy
|
|
604
184
|
try {
|
|
605
|
-
|
|
606
|
-
} catch
|
|
607
|
-
//
|
|
608
|
-
|
|
185
|
+
await this.bridge.stopProxy();
|
|
186
|
+
} catch {
|
|
187
|
+
// Ignore if already stopped
|
|
188
|
+
}
|
|
189
|
+
this.bridge.kill();
|
|
190
|
+
|
|
191
|
+
// Stop socket handler relay
|
|
192
|
+
if (this.socketHandlerServer) {
|
|
193
|
+
await this.socketHandlerServer.stop();
|
|
194
|
+
this.socketHandlerServer = null;
|
|
609
195
|
}
|
|
610
|
-
|
|
196
|
+
|
|
197
|
+
logger.log('info', 'SmartProxy shutdown complete.', { component: 'smart-proxy' });
|
|
611
198
|
}
|
|
612
|
-
|
|
199
|
+
|
|
613
200
|
/**
|
|
614
|
-
* Update routes
|
|
615
|
-
*
|
|
616
|
-
* This method replaces the current route configuration with the provided routes.
|
|
617
|
-
* It also provisions certificates for routes that require TLS termination and have
|
|
618
|
-
* `certificate: 'auto'` set in their TLS configuration.
|
|
619
|
-
*
|
|
620
|
-
* @param newRoutes Array of route configurations to use
|
|
621
|
-
*
|
|
622
|
-
* Example:
|
|
623
|
-
* ```ts
|
|
624
|
-
* proxy.updateRoutes([
|
|
625
|
-
* {
|
|
626
|
-
* match: { ports: 443, domains: 'secure.example.com' },
|
|
627
|
-
* action: {
|
|
628
|
-
* type: 'forward',
|
|
629
|
-
* target: { host: '10.0.0.1', port: 8443 },
|
|
630
|
-
* tls: { mode: 'terminate', certificate: 'auto' }
|
|
631
|
-
* }
|
|
632
|
-
* }
|
|
633
|
-
* ]);
|
|
634
|
-
* ```
|
|
201
|
+
* Update routes atomically.
|
|
635
202
|
*/
|
|
636
203
|
public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> {
|
|
637
204
|
return this.routeUpdateLock.runExclusive(async () => {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
});
|
|
643
|
-
} catch (error) {
|
|
644
|
-
// Silently handle logging errors
|
|
645
|
-
console.log(`[INFO] Updating routes (${newRoutes.length} routes)`);
|
|
205
|
+
// Validate
|
|
206
|
+
const validation = RouteValidator.validateRoutes(newRoutes);
|
|
207
|
+
if (!validation.valid) {
|
|
208
|
+
RouteValidator.logValidationErrors(validation.errors);
|
|
209
|
+
throw new Error(`Route validation failed: ${validation.errors.size} route(s) have errors`);
|
|
646
210
|
}
|
|
647
211
|
|
|
648
|
-
//
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
createCertificateManager: this.createCertificateManager.bind(this),
|
|
663
|
-
verifyChallengeRouteRemoved: this.verifyChallengeRouteRemoved.bind(this)
|
|
664
|
-
}
|
|
212
|
+
// Preprocess for Rust
|
|
213
|
+
const rustRoutes = this.preprocessor.preprocessForRust(newRoutes);
|
|
214
|
+
|
|
215
|
+
// Send to Rust
|
|
216
|
+
await this.bridge.updateRoutes(rustRoutes);
|
|
217
|
+
|
|
218
|
+
// Update local route manager
|
|
219
|
+
this.routeManager.updateRoutes(newRoutes);
|
|
220
|
+
|
|
221
|
+
// Update socket handler relay if handler routes changed
|
|
222
|
+
const hasHandlerRoutes = newRoutes.some(
|
|
223
|
+
(r) =>
|
|
224
|
+
(r.action.type === 'socket-handler' && r.action.socketHandler) ||
|
|
225
|
+
r.action.targets?.some((t) => typeof t.host === 'function' || typeof t.port === 'function')
|
|
665
226
|
);
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
this.
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
this.portUsageMap = updateResult.portUsageMap;
|
|
675
|
-
|
|
676
|
-
// If certificate manager was recreated, update our reference
|
|
677
|
-
if (updateResult.newCertManager) {
|
|
678
|
-
this.certManager = updateResult.newCertManager;
|
|
679
|
-
// Update the orchestrator's reference too
|
|
680
|
-
this.routeOrchestrator.setCertManager(this.certManager);
|
|
227
|
+
|
|
228
|
+
if (hasHandlerRoutes && !this.socketHandlerServer) {
|
|
229
|
+
this.socketHandlerServer = new SocketHandlerServer(this.preprocessor);
|
|
230
|
+
await this.socketHandlerServer.start();
|
|
231
|
+
await this.bridge.setSocketHandlerRelay(this.socketHandlerServer.getSocketPath());
|
|
232
|
+
} else if (!hasHandlerRoutes && this.socketHandlerServer) {
|
|
233
|
+
await this.socketHandlerServer.stop();
|
|
234
|
+
this.socketHandlerServer = null;
|
|
681
235
|
}
|
|
236
|
+
|
|
237
|
+
// Update stored routes
|
|
238
|
+
this.settings.routes = newRoutes;
|
|
239
|
+
|
|
240
|
+
// Handle cert provisioning for new routes
|
|
241
|
+
await this.provisionCertificatesViaCallback();
|
|
242
|
+
|
|
243
|
+
logger.log('info', `Routes updated (${newRoutes.length} routes)`, { component: 'smart-proxy' });
|
|
682
244
|
});
|
|
683
245
|
}
|
|
684
|
-
|
|
246
|
+
|
|
685
247
|
/**
|
|
686
|
-
*
|
|
248
|
+
* Provision a certificate for a named route.
|
|
687
249
|
*/
|
|
688
250
|
public async provisionCertificate(routeName: string): Promise<void> {
|
|
689
|
-
|
|
690
|
-
throw new Error('Certificate manager not initialized');
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
const route = this.settings.routes.find(r => r.name === routeName);
|
|
694
|
-
if (!route) {
|
|
695
|
-
throw new Error(`Route ${routeName} not found`);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
await this.certManager.provisionCertificate(route);
|
|
251
|
+
await this.bridge.provisionCertificate(routeName);
|
|
699
252
|
}
|
|
700
253
|
|
|
701
|
-
// Port usage tracking methods moved to RouteOrchestrator
|
|
702
|
-
|
|
703
254
|
/**
|
|
704
|
-
* Force renewal of a certificate
|
|
255
|
+
* Force renewal of a certificate.
|
|
705
256
|
*/
|
|
706
257
|
public async renewCertificate(routeName: string): Promise<void> {
|
|
707
|
-
|
|
708
|
-
throw new Error('Certificate manager not initialized');
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
await this.certManager.renewCertificate(routeName);
|
|
258
|
+
await this.bridge.renewCertificate(routeName);
|
|
712
259
|
}
|
|
713
|
-
|
|
260
|
+
|
|
714
261
|
/**
|
|
715
|
-
* Get certificate status for a route
|
|
262
|
+
* Get certificate status for a route (async - calls Rust).
|
|
716
263
|
*/
|
|
717
|
-
public getCertificateStatus(routeName: string):
|
|
718
|
-
|
|
719
|
-
return undefined;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
return this.certManager.getCertificateStatus(routeName);
|
|
264
|
+
public async getCertificateStatus(routeName: string): Promise<any> {
|
|
265
|
+
return this.bridge.getCertificateStatus(routeName);
|
|
723
266
|
}
|
|
724
|
-
|
|
267
|
+
|
|
725
268
|
/**
|
|
726
|
-
* Get
|
|
727
|
-
*
|
|
728
|
-
* @returns IMetrics interface with grouped metrics methods
|
|
269
|
+
* Get the metrics interface.
|
|
729
270
|
*/
|
|
730
271
|
public getMetrics(): IMetrics {
|
|
731
|
-
return this.
|
|
272
|
+
return this.metricsAdapter;
|
|
732
273
|
}
|
|
733
|
-
|
|
274
|
+
|
|
734
275
|
/**
|
|
735
|
-
*
|
|
276
|
+
* Get statistics (async - calls Rust).
|
|
736
277
|
*/
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
if (!domain || domain.length === 0) {
|
|
740
|
-
return false;
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
// Check for wildcard domains (they can't get ACME certs)
|
|
744
|
-
if (domain.includes('*')) {
|
|
745
|
-
logger.log('warn', `Wildcard domains like "${domain}" are not supported for automatic ACME certificates`, { domain, component: 'certificate-manager' });
|
|
746
|
-
return false;
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
// Check if domain has at least one dot and no invalid characters
|
|
750
|
-
const validDomainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
751
|
-
if (!validDomainRegex.test(domain)) {
|
|
752
|
-
logger.log('warn', `Domain "${domain}" has invalid format for certificate issuance`, { domain, component: 'certificate-manager' });
|
|
753
|
-
return false;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
return true;
|
|
278
|
+
public async getStatistics(): Promise<any> {
|
|
279
|
+
return this.bridge.getStatistics();
|
|
757
280
|
}
|
|
758
|
-
|
|
281
|
+
|
|
759
282
|
/**
|
|
760
|
-
* Add a
|
|
761
|
-
*
|
|
762
|
-
* This allows you to add a port listener without updating routes.
|
|
763
|
-
* Useful for preparing to listen on a port before adding routes for it.
|
|
764
|
-
*
|
|
765
|
-
* @param port The port to start listening on
|
|
766
|
-
* @returns Promise that resolves when the port is listening
|
|
283
|
+
* Add a listening port at runtime.
|
|
767
284
|
*/
|
|
768
285
|
public async addListeningPort(port: number): Promise<void> {
|
|
769
|
-
|
|
286
|
+
await this.bridge.addListeningPort(port);
|
|
770
287
|
}
|
|
771
288
|
|
|
772
289
|
/**
|
|
773
|
-
*
|
|
774
|
-
*
|
|
775
|
-
* This allows you to stop a port listener without updating routes.
|
|
776
|
-
* Useful for temporary maintenance or port changes.
|
|
777
|
-
*
|
|
778
|
-
* @param port The port to stop listening on
|
|
779
|
-
* @returns Promise that resolves when the port is closed
|
|
290
|
+
* Remove a listening port at runtime.
|
|
780
291
|
*/
|
|
781
292
|
public async removeListeningPort(port: number): Promise<void> {
|
|
782
|
-
|
|
293
|
+
await this.bridge.removeListeningPort(port);
|
|
783
294
|
}
|
|
784
295
|
|
|
785
296
|
/**
|
|
786
|
-
* Get
|
|
787
|
-
*
|
|
788
|
-
* @returns Array of port numbers
|
|
297
|
+
* Get all currently listening ports (async - calls Rust).
|
|
789
298
|
*/
|
|
790
|
-
public getListeningPorts(): number[] {
|
|
791
|
-
|
|
299
|
+
public async getListeningPorts(): Promise<number[]> {
|
|
300
|
+
if (!this.bridge.running) return [];
|
|
301
|
+
return this.bridge.getListeningPorts();
|
|
792
302
|
}
|
|
793
303
|
|
|
794
304
|
/**
|
|
795
|
-
* Get
|
|
796
|
-
*/
|
|
797
|
-
public getStatistics(): any {
|
|
798
|
-
const connectionRecords = this.connectionManager.getConnections();
|
|
799
|
-
const terminationStats = this.connectionManager.getTerminationStats();
|
|
800
|
-
|
|
801
|
-
let tlsConnections = 0;
|
|
802
|
-
let nonTlsConnections = 0;
|
|
803
|
-
let keepAliveConnections = 0;
|
|
804
|
-
let httpProxyConnections = 0;
|
|
805
|
-
|
|
806
|
-
// Analyze active connections
|
|
807
|
-
for (const record of connectionRecords.values()) {
|
|
808
|
-
if (record.isTLS) tlsConnections++;
|
|
809
|
-
else nonTlsConnections++;
|
|
810
|
-
if (record.hasKeepAlive) keepAliveConnections++;
|
|
811
|
-
if (record.usingNetworkProxy) httpProxyConnections++;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
return {
|
|
815
|
-
activeConnections: connectionRecords.size,
|
|
816
|
-
tlsConnections,
|
|
817
|
-
nonTlsConnections,
|
|
818
|
-
keepAliveConnections,
|
|
819
|
-
httpProxyConnections,
|
|
820
|
-
terminationStats,
|
|
821
|
-
acmeEnabled: !!this.certManager,
|
|
822
|
-
port80HandlerPort: this.certManager ? 80 : null,
|
|
823
|
-
routeCount: this.settings.routes.length,
|
|
824
|
-
activePorts: this.portManager.getListeningPorts().length,
|
|
825
|
-
listeningPorts: this.portManager.getListeningPorts()
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
/**
|
|
830
|
-
* Get a list of eligible domains for ACME certificates
|
|
305
|
+
* Get eligible domains for ACME certificates (sync - reads local routes).
|
|
831
306
|
*/
|
|
832
307
|
public getEligibleDomainsForCertificates(): string[] {
|
|
833
308
|
const domains: string[] = [];
|
|
834
|
-
|
|
835
|
-
// Get domains from routes
|
|
836
|
-
const routes = this.settings.routes || [];
|
|
837
|
-
|
|
838
|
-
for (const route of routes) {
|
|
309
|
+
for (const route of this.settings.routes || []) {
|
|
839
310
|
if (!route.match.domains) continue;
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
// Skip domains that can't be used with ACME
|
|
852
|
-
const eligibleDomains = routeDomains.filter(domain =>
|
|
853
|
-
!domain.includes('*') && this.isValidDomain(domain)
|
|
854
|
-
);
|
|
855
|
-
|
|
856
|
-
domains.push(...eligibleDomains);
|
|
311
|
+
if (
|
|
312
|
+
route.action.type !== 'forward' ||
|
|
313
|
+
!route.action.tls ||
|
|
314
|
+
route.action.tls.mode === 'passthrough' ||
|
|
315
|
+
route.action.tls.certificate !== 'auto'
|
|
316
|
+
)
|
|
317
|
+
continue;
|
|
318
|
+
|
|
319
|
+
const routeDomains = Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains];
|
|
320
|
+
const eligible = routeDomains.filter((d) => !d.includes('*') && this.isValidDomain(d));
|
|
321
|
+
domains.push(...eligible);
|
|
857
322
|
}
|
|
858
|
-
|
|
859
|
-
// Legacy mode is no longer supported
|
|
860
|
-
|
|
861
323
|
return domains;
|
|
862
324
|
}
|
|
863
|
-
|
|
325
|
+
|
|
864
326
|
/**
|
|
865
|
-
* Get NFTables status
|
|
327
|
+
* Get NFTables status (async - calls Rust).
|
|
866
328
|
*/
|
|
867
329
|
public async getNfTablesStatus(): Promise<Record<string, any>> {
|
|
868
|
-
return this.
|
|
330
|
+
return this.bridge.getNftablesStatus();
|
|
869
331
|
}
|
|
870
|
-
|
|
332
|
+
|
|
333
|
+
// --- Private helpers ---
|
|
334
|
+
|
|
871
335
|
/**
|
|
872
|
-
*
|
|
336
|
+
* Build the Rust configuration object from TS settings.
|
|
873
337
|
*/
|
|
874
|
-
private
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
if (
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
338
|
+
private buildRustConfig(routes: IRouteConfig[]): any {
|
|
339
|
+
return {
|
|
340
|
+
routes,
|
|
341
|
+
defaults: this.settings.defaults,
|
|
342
|
+
acme: this.settings.acme
|
|
343
|
+
? {
|
|
344
|
+
enabled: this.settings.acme.enabled,
|
|
345
|
+
email: this.settings.acme.email,
|
|
346
|
+
useProduction: this.settings.acme.useProduction,
|
|
347
|
+
port: this.settings.acme.port,
|
|
348
|
+
renewThresholdDays: this.settings.acme.renewThresholdDays,
|
|
349
|
+
autoRenew: this.settings.acme.autoRenew,
|
|
350
|
+
certificateStore: this.settings.acme.certificateStore,
|
|
351
|
+
renewCheckIntervalHours: this.settings.acme.renewCheckIntervalHours,
|
|
352
|
+
}
|
|
353
|
+
: undefined,
|
|
354
|
+
connectionTimeout: this.settings.connectionTimeout,
|
|
355
|
+
initialDataTimeout: this.settings.initialDataTimeout,
|
|
356
|
+
socketTimeout: this.settings.socketTimeout,
|
|
357
|
+
maxConnectionLifetime: this.settings.maxConnectionLifetime,
|
|
358
|
+
gracefulShutdownTimeout: this.settings.gracefulShutdownTimeout,
|
|
359
|
+
maxConnectionsPerIp: this.settings.maxConnectionsPerIP,
|
|
360
|
+
connectionRateLimitPerMinute: this.settings.connectionRateLimitPerMinute,
|
|
361
|
+
keepAliveTreatment: this.settings.keepAliveTreatment,
|
|
362
|
+
keepAliveInactivityMultiplier: this.settings.keepAliveInactivityMultiplier,
|
|
363
|
+
extendedKeepAliveLifetime: this.settings.extendedKeepAliveLifetime,
|
|
364
|
+
acceptProxyProtocol: this.settings.acceptProxyProtocol,
|
|
365
|
+
sendProxyProtocol: this.settings.sendProxyProtocol,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* For routes with certificate: 'auto', call certProvisionFunction if set.
|
|
371
|
+
* If the callback returns a cert object, load it into Rust.
|
|
372
|
+
* If it returns 'http01', let Rust handle ACME.
|
|
373
|
+
*/
|
|
374
|
+
private async provisionCertificatesViaCallback(): Promise<void> {
|
|
375
|
+
const provisionFn = this.settings.certProvisionFunction;
|
|
376
|
+
if (!provisionFn) return;
|
|
377
|
+
|
|
378
|
+
for (const route of this.settings.routes) {
|
|
379
|
+
if (route.action.tls?.certificate !== 'auto') continue;
|
|
380
|
+
if (!route.match.domains) continue;
|
|
381
|
+
|
|
382
|
+
const domains = Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains];
|
|
383
|
+
|
|
384
|
+
for (const domain of domains) {
|
|
385
|
+
if (domain.includes('*')) continue;
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
const result: TSmartProxyCertProvisionObject = await provisionFn(domain);
|
|
389
|
+
|
|
390
|
+
if (result === 'http01') {
|
|
391
|
+
// Rust handles ACME for this domain
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Got a static cert object - load it into Rust
|
|
396
|
+
if (result && typeof result === 'object') {
|
|
397
|
+
const certObj = result as plugins.tsclass.network.ICert;
|
|
398
|
+
await this.bridge.loadCertificate(
|
|
399
|
+
domain,
|
|
400
|
+
certObj.publicKey,
|
|
401
|
+
certObj.privateKey,
|
|
402
|
+
);
|
|
403
|
+
logger.log('info', `Certificate loaded via provision function for ${domain}`, { component: 'smart-proxy' });
|
|
404
|
+
}
|
|
405
|
+
} catch (err: any) {
|
|
406
|
+
logger.log('warn', `certProvisionFunction failed for ${domain}: ${err.message}`, { component: 'smart-proxy' });
|
|
407
|
+
|
|
408
|
+
// Fallback to ACME if enabled
|
|
409
|
+
if (this.settings.certProvisionFallbackToAcme !== false) {
|
|
410
|
+
logger.log('info', `Falling back to ACME for ${domain}`, { component: 'smart-proxy' });
|
|
411
|
+
}
|
|
412
|
+
}
|
|
936
413
|
}
|
|
937
414
|
}
|
|
938
|
-
|
|
939
|
-
return warnings;
|
|
940
415
|
}
|
|
941
416
|
|
|
942
|
-
|
|
417
|
+
private isValidDomain(domain: string): boolean {
|
|
418
|
+
if (!domain || domain.length === 0) return false;
|
|
419
|
+
if (domain.includes('*')) return false;
|
|
420
|
+
const validDomainRegex =
|
|
421
|
+
/^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
422
|
+
return validDomainRegex.test(domain);
|
|
423
|
+
}
|
|
424
|
+
}
|