@push.rocks/smartproxy 5.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/classes.pp.interfaces.d.ts +23 -0
  3. package/dist_ts/classes.pp.networkproxybridge.d.ts +15 -1
  4. package/dist_ts/classes.pp.networkproxybridge.js +116 -21
  5. package/dist_ts/classes.pp.portproxy.d.ts +20 -4
  6. package/dist_ts/classes.pp.portproxy.js +321 -22
  7. package/dist_ts/index.d.ts +6 -6
  8. package/dist_ts/index.js +7 -7
  9. package/dist_ts/networkproxy/classes.np.certificatemanager.d.ts +77 -0
  10. package/dist_ts/networkproxy/classes.np.certificatemanager.js +354 -0
  11. package/dist_ts/networkproxy/classes.np.connectionpool.d.ts +47 -0
  12. package/dist_ts/networkproxy/classes.np.connectionpool.js +210 -0
  13. package/dist_ts/networkproxy/classes.np.networkproxy.d.ts +117 -0
  14. package/dist_ts/networkproxy/classes.np.networkproxy.js +375 -0
  15. package/dist_ts/networkproxy/classes.np.requesthandler.d.ts +51 -0
  16. package/dist_ts/networkproxy/classes.np.requesthandler.js +210 -0
  17. package/dist_ts/networkproxy/classes.np.types.d.ts +82 -0
  18. package/dist_ts/networkproxy/classes.np.types.js +35 -0
  19. package/dist_ts/networkproxy/classes.np.websockethandler.d.ts +38 -0
  20. package/dist_ts/networkproxy/classes.np.websockethandler.js +188 -0
  21. package/dist_ts/networkproxy/index.d.ts +6 -0
  22. package/dist_ts/networkproxy/index.js +8 -0
  23. package/dist_ts/nfttablesproxy/classes.nftablesproxy.d.ts +219 -0
  24. package/dist_ts/nfttablesproxy/classes.nftablesproxy.js +1542 -0
  25. package/dist_ts/port80handler/classes.port80handler.d.ts +260 -0
  26. package/dist_ts/port80handler/classes.port80handler.js +928 -0
  27. package/dist_ts/smartproxy/classes.pp.connectionhandler.d.ts +39 -0
  28. package/dist_ts/smartproxy/classes.pp.connectionhandler.js +754 -0
  29. package/dist_ts/smartproxy/classes.pp.connectionmanager.d.ts +78 -0
  30. package/dist_ts/smartproxy/classes.pp.connectionmanager.js +378 -0
  31. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.d.ts +55 -0
  32. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.js +103 -0
  33. package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +133 -0
  34. package/dist_ts/smartproxy/classes.pp.interfaces.js +2 -0
  35. package/dist_ts/smartproxy/classes.pp.networkproxybridge.d.ts +57 -0
  36. package/dist_ts/smartproxy/classes.pp.networkproxybridge.js +306 -0
  37. package/dist_ts/smartproxy/classes.pp.portrangemanager.d.ts +56 -0
  38. package/dist_ts/smartproxy/classes.pp.portrangemanager.js +179 -0
  39. package/dist_ts/smartproxy/classes.pp.securitymanager.d.ts +47 -0
  40. package/dist_ts/smartproxy/classes.pp.securitymanager.js +126 -0
  41. package/dist_ts/smartproxy/classes.pp.snihandler.d.ts +153 -0
  42. package/dist_ts/smartproxy/classes.pp.snihandler.js +1053 -0
  43. package/dist_ts/smartproxy/classes.pp.timeoutmanager.d.ts +47 -0
  44. package/dist_ts/smartproxy/classes.pp.timeoutmanager.js +154 -0
  45. package/dist_ts/smartproxy/classes.pp.tlsalert.d.ts +149 -0
  46. package/dist_ts/smartproxy/classes.pp.tlsalert.js +225 -0
  47. package/dist_ts/smartproxy/classes.pp.tlsmanager.d.ts +57 -0
  48. package/dist_ts/smartproxy/classes.pp.tlsmanager.js +132 -0
  49. package/dist_ts/smartproxy/classes.smartproxy.d.ts +64 -0
  50. package/dist_ts/smartproxy/classes.smartproxy.js +567 -0
  51. package/package.json +1 -1
  52. package/readme.md +77 -27
  53. package/ts/00_commitinfo_data.ts +1 -1
  54. package/ts/index.ts +6 -6
  55. package/ts/networkproxy/classes.np.certificatemanager.ts +398 -0
  56. package/ts/networkproxy/classes.np.connectionpool.ts +241 -0
  57. package/ts/networkproxy/classes.np.networkproxy.ts +469 -0
  58. package/ts/networkproxy/classes.np.requesthandler.ts +278 -0
  59. package/ts/networkproxy/classes.np.types.ts +123 -0
  60. package/ts/networkproxy/classes.np.websockethandler.ts +226 -0
  61. package/ts/networkproxy/index.ts +7 -0
  62. package/ts/{classes.port80handler.ts → port80handler/classes.port80handler.ts} +249 -1
  63. package/ts/{classes.pp.connectionhandler.ts → smartproxy/classes.pp.connectionhandler.ts} +1 -1
  64. package/ts/{classes.pp.connectionmanager.ts → smartproxy/classes.pp.connectionmanager.ts} +1 -1
  65. package/ts/{classes.pp.domainconfigmanager.ts → smartproxy/classes.pp.domainconfigmanager.ts} +1 -1
  66. package/ts/{classes.pp.interfaces.ts → smartproxy/classes.pp.interfaces.ts} +31 -5
  67. package/ts/{classes.pp.networkproxybridge.ts → smartproxy/classes.pp.networkproxybridge.ts} +129 -28
  68. package/ts/{classes.pp.securitymanager.ts → smartproxy/classes.pp.securitymanager.ts} +1 -1
  69. package/ts/{classes.pp.tlsmanager.ts → smartproxy/classes.pp.tlsmanager.ts} +1 -1
  70. package/ts/smartproxy/classes.smartproxy.ts +679 -0
  71. package/ts/classes.networkproxy.ts +0 -1730
  72. package/ts/classes.pp.acmemanager.ts +0 -149
  73. package/ts/classes.pp.portproxy.ts +0 -344
  74. /package/ts/{classes.nftablesproxy.ts → nfttablesproxy/classes.nftablesproxy.ts} +0 -0
  75. /package/ts/{classes.pp.portrangemanager.ts → smartproxy/classes.pp.portrangemanager.ts} +0 -0
  76. /package/ts/{classes.pp.snihandler.ts → smartproxy/classes.pp.snihandler.ts} +0 -0
  77. /package/ts/{classes.pp.timeoutmanager.ts → smartproxy/classes.pp.timeoutmanager.ts} +0 -0
  78. /package/ts/{classes.pp.tlsalert.ts → smartproxy/classes.pp.tlsalert.ts} +0 -0
@@ -0,0 +1,679 @@
1
+ import * as plugins from '../plugins.js';
2
+ import type { IPortProxySettings, IDomainConfig } from './classes.pp.interfaces.js';
3
+ import { ConnectionManager } from './classes.pp.connectionmanager.js';
4
+ import { SecurityManager } from './classes.pp.securitymanager.js';
5
+ import { DomainConfigManager } from './classes.pp.domainconfigmanager.js';
6
+ import { TlsManager } from './classes.pp.tlsmanager.js';
7
+ import { NetworkProxyBridge } from './classes.pp.networkproxybridge.js';
8
+ import { TimeoutManager } from './classes.pp.timeoutmanager.js';
9
+ import { PortRangeManager } from './classes.pp.portrangemanager.js';
10
+ import { ConnectionHandler } from './classes.pp.connectionhandler.js';
11
+ import { Port80Handler, Port80HandlerEvents } from '../port80handler/classes.port80handler.js';
12
+ import * as path from 'path';
13
+ import * as fs from 'fs';
14
+
15
+ /**
16
+ * SmartProxy - Main class that coordinates all components
17
+ */
18
+ export class SmartProxy {
19
+ private netServers: plugins.net.Server[] = [];
20
+ private connectionLogger: NodeJS.Timeout | null = null;
21
+ private isShuttingDown: boolean = false;
22
+
23
+ // Component managers
24
+ private connectionManager: ConnectionManager;
25
+ private securityManager: SecurityManager;
26
+ public domainConfigManager: DomainConfigManager;
27
+ private tlsManager: TlsManager;
28
+ private networkProxyBridge: NetworkProxyBridge;
29
+ private timeoutManager: TimeoutManager;
30
+ private portRangeManager: PortRangeManager;
31
+ private connectionHandler: ConnectionHandler;
32
+
33
+ // Port80Handler for ACME certificate management
34
+ private port80Handler: Port80Handler | null = null;
35
+
36
+ constructor(settingsArg: IPortProxySettings) {
37
+ // Set reasonable defaults for all settings
38
+ this.settings = {
39
+ ...settingsArg,
40
+ targetIP: settingsArg.targetIP || 'localhost',
41
+ initialDataTimeout: settingsArg.initialDataTimeout || 120000,
42
+ socketTimeout: settingsArg.socketTimeout || 3600000,
43
+ inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000,
44
+ maxConnectionLifetime: settingsArg.maxConnectionLifetime || 86400000,
45
+ inactivityTimeout: settingsArg.inactivityTimeout || 14400000,
46
+ gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000,
47
+ noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
48
+ keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
49
+ keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000,
50
+ maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024,
51
+ disableInactivityCheck: settingsArg.disableInactivityCheck || false,
52
+ enableKeepAliveProbes:
53
+ settingsArg.enableKeepAliveProbes !== undefined ? settingsArg.enableKeepAliveProbes : true,
54
+ enableDetailedLogging: settingsArg.enableDetailedLogging || false,
55
+ enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
56
+ enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
57
+ allowSessionTicket:
58
+ settingsArg.allowSessionTicket !== undefined ? settingsArg.allowSessionTicket : true,
59
+ maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100,
60
+ connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300,
61
+ keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
62
+ keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
63
+ extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000,
64
+ networkProxyPort: settingsArg.networkProxyPort || 8443,
65
+ port80HandlerConfig: settingsArg.port80HandlerConfig || {},
66
+ globalPortRanges: settingsArg.globalPortRanges || [],
67
+ };
68
+
69
+ // Set port80HandlerConfig defaults, using legacy acme config if available
70
+ if (!this.settings.port80HandlerConfig || Object.keys(this.settings.port80HandlerConfig).length === 0) {
71
+ if (this.settings.acme) {
72
+ // Migrate from legacy acme config
73
+ this.settings.port80HandlerConfig = {
74
+ enabled: this.settings.acme.enabled,
75
+ port: this.settings.acme.port || 80,
76
+ contactEmail: this.settings.acme.contactEmail || 'admin@example.com',
77
+ useProduction: this.settings.acme.useProduction || false,
78
+ renewThresholdDays: this.settings.acme.renewThresholdDays || 30,
79
+ autoRenew: this.settings.acme.autoRenew !== false, // Default to true
80
+ certificateStore: this.settings.acme.certificateStore || './certs',
81
+ skipConfiguredCerts: this.settings.acme.skipConfiguredCerts || false,
82
+ httpsRedirectPort: this.settings.fromPort,
83
+ renewCheckIntervalHours: 24
84
+ };
85
+ } else {
86
+ // Set defaults if no config provided
87
+ this.settings.port80HandlerConfig = {
88
+ enabled: false,
89
+ port: 80,
90
+ contactEmail: 'admin@example.com',
91
+ useProduction: false,
92
+ renewThresholdDays: 30,
93
+ autoRenew: true,
94
+ certificateStore: './certs',
95
+ skipConfiguredCerts: false,
96
+ httpsRedirectPort: this.settings.fromPort,
97
+ renewCheckIntervalHours: 24
98
+ };
99
+ }
100
+ }
101
+
102
+ // Initialize component managers
103
+ this.timeoutManager = new TimeoutManager(this.settings);
104
+ this.securityManager = new SecurityManager(this.settings);
105
+ this.connectionManager = new ConnectionManager(
106
+ this.settings,
107
+ this.securityManager,
108
+ this.timeoutManager
109
+ );
110
+ this.domainConfigManager = new DomainConfigManager(this.settings);
111
+ this.tlsManager = new TlsManager(this.settings);
112
+ this.networkProxyBridge = new NetworkProxyBridge(this.settings);
113
+ this.portRangeManager = new PortRangeManager(this.settings);
114
+
115
+ // Initialize connection handler
116
+ this.connectionHandler = new ConnectionHandler(
117
+ this.settings,
118
+ this.connectionManager,
119
+ this.securityManager,
120
+ this.domainConfigManager,
121
+ this.tlsManager,
122
+ this.networkProxyBridge,
123
+ this.timeoutManager,
124
+ this.portRangeManager
125
+ );
126
+ }
127
+
128
+ /**
129
+ * The settings for the port proxy
130
+ */
131
+ public settings: IPortProxySettings;
132
+
133
+ /**
134
+ * Initialize the Port80Handler for ACME certificate management
135
+ */
136
+ private async initializePort80Handler(): Promise<void> {
137
+ const config = this.settings.port80HandlerConfig;
138
+
139
+ if (!config || !config.enabled) {
140
+ console.log('Port80Handler is disabled in configuration');
141
+ return;
142
+ }
143
+
144
+ try {
145
+ // Ensure the certificate store directory exists
146
+ if (config.certificateStore) {
147
+ const certStorePath = path.resolve(config.certificateStore);
148
+ if (!fs.existsSync(certStorePath)) {
149
+ fs.mkdirSync(certStorePath, { recursive: true });
150
+ console.log(`Created certificate store directory: ${certStorePath}`);
151
+ }
152
+ }
153
+
154
+ // Create Port80Handler with options from config
155
+ this.port80Handler = new Port80Handler({
156
+ port: config.port,
157
+ contactEmail: config.contactEmail,
158
+ useProduction: config.useProduction,
159
+ renewThresholdDays: config.renewThresholdDays,
160
+ httpsRedirectPort: config.httpsRedirectPort || this.settings.fromPort,
161
+ renewCheckIntervalHours: config.renewCheckIntervalHours,
162
+ enabled: config.enabled,
163
+ autoRenew: config.autoRenew,
164
+ certificateStore: config.certificateStore,
165
+ skipConfiguredCerts: config.skipConfiguredCerts
166
+ });
167
+
168
+ // Register domain forwarding configurations
169
+ if (config.domainForwards) {
170
+ for (const forward of config.domainForwards) {
171
+ this.port80Handler.addDomain({
172
+ domainName: forward.domain,
173
+ sslRedirect: true,
174
+ acmeMaintenance: true,
175
+ forward: forward.forwardConfig,
176
+ acmeForward: forward.acmeForwardConfig
177
+ });
178
+
179
+ console.log(`Registered domain forwarding for ${forward.domain}`);
180
+ }
181
+ }
182
+
183
+ // Register all non-wildcard domains from domain configs
184
+ for (const domainConfig of this.settings.domainConfigs) {
185
+ for (const domain of domainConfig.domains) {
186
+ // Skip wildcards
187
+ if (domain.includes('*')) continue;
188
+
189
+ this.port80Handler.addDomain({
190
+ domainName: domain,
191
+ sslRedirect: true,
192
+ acmeMaintenance: true
193
+ });
194
+
195
+ console.log(`Registered domain ${domain} with Port80Handler`);
196
+ }
197
+ }
198
+
199
+ // Set up event listeners
200
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, (certData) => {
201
+ console.log(`Certificate issued for ${certData.domain}, valid until ${certData.expiryDate.toISOString()}`);
202
+ });
203
+
204
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, (certData) => {
205
+ console.log(`Certificate renewed for ${certData.domain}, valid until ${certData.expiryDate.toISOString()}`);
206
+ });
207
+
208
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, (failureData) => {
209
+ console.log(`Certificate ${failureData.isRenewal ? 'renewal' : 'issuance'} failed for ${failureData.domain}: ${failureData.error}`);
210
+ });
211
+
212
+ this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_EXPIRING, (expiryData) => {
213
+ console.log(`Certificate for ${expiryData.domain} is expiring in ${expiryData.daysRemaining} days`);
214
+ });
215
+
216
+ // Share Port80Handler with NetworkProxyBridge
217
+ this.networkProxyBridge.setPort80Handler(this.port80Handler);
218
+
219
+ // Start Port80Handler
220
+ await this.port80Handler.start();
221
+ console.log(`Port80Handler started on port ${config.port}`);
222
+ } catch (err) {
223
+ console.log(`Error initializing Port80Handler: ${err}`);
224
+ }
225
+ }
226
+
227
+ /**
228
+ * Start the proxy server
229
+ */
230
+ public async start() {
231
+ // Don't start if already shutting down
232
+ if (this.isShuttingDown) {
233
+ console.log("Cannot start PortProxy while it's shutting down");
234
+ return;
235
+ }
236
+
237
+ // Initialize Port80Handler if enabled
238
+ await this.initializePort80Handler();
239
+
240
+ // Initialize and start NetworkProxy if needed
241
+ if (
242
+ this.settings.useNetworkProxy &&
243
+ this.settings.useNetworkProxy.length > 0
244
+ ) {
245
+ await this.networkProxyBridge.initialize();
246
+ await this.networkProxyBridge.start();
247
+ }
248
+
249
+ // Validate port configuration
250
+ const configWarnings = this.portRangeManager.validateConfiguration();
251
+ if (configWarnings.length > 0) {
252
+ console.log("Port configuration warnings:");
253
+ for (const warning of configWarnings) {
254
+ console.log(` - ${warning}`);
255
+ }
256
+ }
257
+
258
+ // Get listening ports from PortRangeManager
259
+ const listeningPorts = this.portRangeManager.getListeningPorts();
260
+
261
+ // Create servers for each port
262
+ for (const port of listeningPorts) {
263
+ const server = plugins.net.createServer((socket) => {
264
+ // Check if shutting down
265
+ if (this.isShuttingDown) {
266
+ socket.end();
267
+ socket.destroy();
268
+ return;
269
+ }
270
+
271
+ // Delegate to connection handler
272
+ this.connectionHandler.handleConnection(socket);
273
+ }).on('error', (err: Error) => {
274
+ console.log(`Server Error on port ${port}: ${err.message}`);
275
+ });
276
+
277
+ server.listen(port, () => {
278
+ const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
279
+ console.log(
280
+ `PortProxy -> OK: Now listening on port ${port}${
281
+ this.settings.sniEnabled && !isNetworkProxyPort ? ' (SNI passthrough enabled)' : ''
282
+ }${isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''}`
283
+ );
284
+ });
285
+
286
+ this.netServers.push(server);
287
+ }
288
+
289
+ // Set up periodic connection logging and inactivity checks
290
+ this.connectionLogger = setInterval(() => {
291
+ // Immediately return if shutting down
292
+ if (this.isShuttingDown) return;
293
+
294
+ // Perform inactivity check
295
+ this.connectionManager.performInactivityCheck();
296
+
297
+ // Log connection statistics
298
+ const now = Date.now();
299
+ let maxIncoming = 0;
300
+ let maxOutgoing = 0;
301
+ let tlsConnections = 0;
302
+ let nonTlsConnections = 0;
303
+ let completedTlsHandshakes = 0;
304
+ let pendingTlsHandshakes = 0;
305
+ let keepAliveConnections = 0;
306
+ let networkProxyConnections = 0;
307
+
308
+ // Get connection records for analysis
309
+ const connectionRecords = this.connectionManager.getConnections();
310
+
311
+ // Analyze active connections
312
+ for (const record of connectionRecords.values()) {
313
+ // Track connection stats
314
+ if (record.isTLS) {
315
+ tlsConnections++;
316
+ if (record.tlsHandshakeComplete) {
317
+ completedTlsHandshakes++;
318
+ } else {
319
+ pendingTlsHandshakes++;
320
+ }
321
+ } else {
322
+ nonTlsConnections++;
323
+ }
324
+
325
+ if (record.hasKeepAlive) {
326
+ keepAliveConnections++;
327
+ }
328
+
329
+ if (record.usingNetworkProxy) {
330
+ networkProxyConnections++;
331
+ }
332
+
333
+ maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
334
+ if (record.outgoingStartTime) {
335
+ maxOutgoing = Math.max(maxOutgoing, now - record.outgoingStartTime);
336
+ }
337
+ }
338
+
339
+ // Get termination stats
340
+ const terminationStats = this.connectionManager.getTerminationStats();
341
+
342
+ // Log detailed stats
343
+ console.log(
344
+ `Active connections: ${connectionRecords.size}. ` +
345
+ `Types: TLS=${tlsConnections} (Completed=${completedTlsHandshakes}, Pending=${pendingTlsHandshakes}), ` +
346
+ `Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}, NetworkProxy=${networkProxyConnections}. ` +
347
+ `Longest running: IN=${plugins.prettyMs(maxIncoming)}, OUT=${plugins.prettyMs(maxOutgoing)}. ` +
348
+ `Termination stats: ${JSON.stringify({
349
+ IN: terminationStats.incoming,
350
+ OUT: terminationStats.outgoing,
351
+ })}`
352
+ );
353
+ }, this.settings.inactivityCheckInterval || 60000);
354
+
355
+ // Make sure the interval doesn't keep the process alive
356
+ if (this.connectionLogger.unref) {
357
+ this.connectionLogger.unref();
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Stop the proxy server
363
+ */
364
+ public async stop() {
365
+ console.log('PortProxy shutting down...');
366
+ this.isShuttingDown = true;
367
+
368
+ // Stop the Port80Handler if running
369
+ if (this.port80Handler) {
370
+ try {
371
+ await this.port80Handler.stop();
372
+ console.log('Port80Handler stopped');
373
+ this.port80Handler = null;
374
+ } catch (err) {
375
+ console.log(`Error stopping Port80Handler: ${err}`);
376
+ }
377
+ }
378
+
379
+ // Stop accepting new connections
380
+ const closeServerPromises: Promise<void>[] = this.netServers.map(
381
+ (server) =>
382
+ new Promise<void>((resolve) => {
383
+ if (!server.listening) {
384
+ resolve();
385
+ return;
386
+ }
387
+ server.close((err) => {
388
+ if (err) {
389
+ console.log(`Error closing server: ${err.message}`);
390
+ }
391
+ resolve();
392
+ });
393
+ })
394
+ );
395
+
396
+ // Stop the connection logger
397
+ if (this.connectionLogger) {
398
+ clearInterval(this.connectionLogger);
399
+ this.connectionLogger = null;
400
+ }
401
+
402
+ // Wait for servers to close
403
+ await Promise.all(closeServerPromises);
404
+ console.log('All servers closed. Cleaning up active connections...');
405
+
406
+ // Clean up all active connections
407
+ this.connectionManager.clearConnections();
408
+
409
+ // Stop NetworkProxy
410
+ await this.networkProxyBridge.stop();
411
+
412
+ // Clear all servers
413
+ this.netServers = [];
414
+
415
+ console.log('PortProxy shutdown complete.');
416
+ }
417
+
418
+ /**
419
+ * Updates the domain configurations for the proxy
420
+ */
421
+ public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise<void> {
422
+ console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`);
423
+
424
+ // Update domain configs in DomainConfigManager
425
+ this.domainConfigManager.updateDomainConfigs(newDomainConfigs);
426
+
427
+ // If NetworkProxy is initialized, resync the configurations
428
+ if (this.networkProxyBridge.getNetworkProxy()) {
429
+ await this.networkProxyBridge.syncDomainConfigsToNetworkProxy();
430
+ }
431
+
432
+ // If Port80Handler is running, register non-wildcard domains
433
+ if (this.port80Handler && this.settings.port80HandlerConfig?.enabled) {
434
+ for (const domainConfig of newDomainConfigs) {
435
+ for (const domain of domainConfig.domains) {
436
+ // Skip wildcards
437
+ if (domain.includes('*')) continue;
438
+
439
+ this.port80Handler.addDomain({
440
+ domainName: domain,
441
+ sslRedirect: true,
442
+ acmeMaintenance: true
443
+ });
444
+ }
445
+ }
446
+
447
+ console.log('Registered non-wildcard domains with Port80Handler');
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Updates the Port80Handler configuration
453
+ */
454
+ public async updatePort80HandlerConfig(config: IPortProxySettings['port80HandlerConfig']): Promise<void> {
455
+ if (!config) return;
456
+
457
+ console.log('Updating Port80Handler configuration');
458
+
459
+ // Update the settings
460
+ this.settings.port80HandlerConfig = {
461
+ ...this.settings.port80HandlerConfig,
462
+ ...config
463
+ };
464
+
465
+ // Check if we need to restart Port80Handler
466
+ let needsRestart = false;
467
+
468
+ // Restart if enabled state changed
469
+ if (this.port80Handler && config.enabled === false) {
470
+ needsRestart = true;
471
+ } else if (!this.port80Handler && config.enabled === true) {
472
+ needsRestart = true;
473
+ } else if (this.port80Handler && (
474
+ config.port !== undefined ||
475
+ config.contactEmail !== undefined ||
476
+ config.useProduction !== undefined ||
477
+ config.renewThresholdDays !== undefined ||
478
+ config.renewCheckIntervalHours !== undefined
479
+ )) {
480
+ // Restart if critical settings changed
481
+ needsRestart = true;
482
+ }
483
+
484
+ if (needsRestart) {
485
+ // Stop if running
486
+ if (this.port80Handler) {
487
+ try {
488
+ await this.port80Handler.stop();
489
+ this.port80Handler = null;
490
+ console.log('Stopped Port80Handler for configuration update');
491
+ } catch (err) {
492
+ console.log(`Error stopping Port80Handler: ${err}`);
493
+ }
494
+ }
495
+
496
+ // Start with new config if enabled
497
+ if (this.settings.port80HandlerConfig.enabled) {
498
+ await this.initializePort80Handler();
499
+ console.log('Restarted Port80Handler with new configuration');
500
+ }
501
+ } else if (this.port80Handler) {
502
+ // Just update domain forwards if they changed
503
+ if (config.domainForwards) {
504
+ for (const forward of config.domainForwards) {
505
+ this.port80Handler.addDomain({
506
+ domainName: forward.domain,
507
+ sslRedirect: true,
508
+ acmeMaintenance: true,
509
+ forward: forward.forwardConfig,
510
+ acmeForward: forward.acmeForwardConfig
511
+ });
512
+ }
513
+ console.log('Updated domain forwards in Port80Handler');
514
+ }
515
+ }
516
+ }
517
+
518
+ /**
519
+ * Request a certificate for a specific domain
520
+ */
521
+ public async requestCertificate(domain: string): Promise<boolean> {
522
+ // Validate domain format
523
+ if (!this.isValidDomain(domain)) {
524
+ console.log(`Invalid domain format: ${domain}`);
525
+ return false;
526
+ }
527
+
528
+ // Use Port80Handler if available
529
+ if (this.port80Handler) {
530
+ try {
531
+ // Check if we already have a certificate
532
+ const cert = this.port80Handler.getCertificate(domain);
533
+ if (cert) {
534
+ console.log(`Certificate already exists for ${domain}, valid until ${cert.expiryDate.toISOString()}`);
535
+ return true;
536
+ }
537
+
538
+ // Register domain for certificate issuance
539
+ this.port80Handler.addDomain({
540
+ domainName: domain,
541
+ sslRedirect: true,
542
+ acmeMaintenance: true
543
+ });
544
+
545
+ console.log(`Domain ${domain} registered for certificate issuance`);
546
+ return true;
547
+ } catch (err) {
548
+ console.log(`Error registering domain with Port80Handler: ${err}`);
549
+ return false;
550
+ }
551
+ }
552
+
553
+ // Fall back to NetworkProxyBridge
554
+ return this.networkProxyBridge.requestCertificate(domain);
555
+ }
556
+
557
+ /**
558
+ * Validates if a domain name is valid for certificate issuance
559
+ */
560
+ private isValidDomain(domain: string): boolean {
561
+ // Very basic domain validation
562
+ if (!domain || domain.length === 0) {
563
+ return false;
564
+ }
565
+
566
+ // Check for wildcard domains (they can't get ACME certs)
567
+ if (domain.includes('*')) {
568
+ console.log(`Wildcard domains like "${domain}" are not supported for ACME certificates`);
569
+ return false;
570
+ }
571
+
572
+ // Check if domain has at least one dot and no invalid characters
573
+ 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])?)*$/;
574
+ if (!validDomainRegex.test(domain)) {
575
+ console.log(`Domain "${domain}" has invalid format`);
576
+ return false;
577
+ }
578
+
579
+ return true;
580
+ }
581
+
582
+ /**
583
+ * Get statistics about current connections
584
+ */
585
+ public getStatistics(): any {
586
+ const connectionRecords = this.connectionManager.getConnections();
587
+ const terminationStats = this.connectionManager.getTerminationStats();
588
+
589
+ let tlsConnections = 0;
590
+ let nonTlsConnections = 0;
591
+ let keepAliveConnections = 0;
592
+ let networkProxyConnections = 0;
593
+
594
+ // Analyze active connections
595
+ for (const record of connectionRecords.values()) {
596
+ if (record.isTLS) tlsConnections++;
597
+ else nonTlsConnections++;
598
+ if (record.hasKeepAlive) keepAliveConnections++;
599
+ if (record.usingNetworkProxy) networkProxyConnections++;
600
+ }
601
+
602
+ return {
603
+ activeConnections: connectionRecords.size,
604
+ tlsConnections,
605
+ nonTlsConnections,
606
+ keepAliveConnections,
607
+ networkProxyConnections,
608
+ terminationStats,
609
+ acmeEnabled: !!this.port80Handler,
610
+ port80HandlerPort: this.port80Handler ? this.settings.port80HandlerConfig?.port : null
611
+ };
612
+ }
613
+
614
+ /**
615
+ * Get a list of eligible domains for ACME certificates
616
+ */
617
+ public getEligibleDomainsForCertificates(): string[] {
618
+ // Collect all non-wildcard domains from domain configs
619
+ const domains: string[] = [];
620
+
621
+ for (const config of this.settings.domainConfigs) {
622
+ // Skip domains that can't be used with ACME
623
+ const eligibleDomains = config.domains.filter(domain =>
624
+ !domain.includes('*') && this.isValidDomain(domain)
625
+ );
626
+
627
+ domains.push(...eligibleDomains);
628
+ }
629
+
630
+ return domains;
631
+ }
632
+
633
+ /**
634
+ * Get status of certificates managed by Port80Handler
635
+ */
636
+ public getCertificateStatus(): any {
637
+ if (!this.port80Handler) {
638
+ return {
639
+ enabled: false,
640
+ message: 'Port80Handler is not enabled'
641
+ };
642
+ }
643
+
644
+ // Get eligible domains
645
+ const eligibleDomains = this.getEligibleDomainsForCertificates();
646
+ const certificateStatus: Record<string, any> = {};
647
+
648
+ // Check each domain
649
+ for (const domain of eligibleDomains) {
650
+ const cert = this.port80Handler.getCertificate(domain);
651
+
652
+ if (cert) {
653
+ const now = new Date();
654
+ const expiryDate = cert.expiryDate;
655
+ const daysRemaining = Math.floor((expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000));
656
+
657
+ certificateStatus[domain] = {
658
+ status: 'valid',
659
+ expiryDate: expiryDate.toISOString(),
660
+ daysRemaining,
661
+ renewalNeeded: daysRemaining <= this.settings.port80HandlerConfig.renewThresholdDays
662
+ };
663
+ } else {
664
+ certificateStatus[domain] = {
665
+ status: 'missing',
666
+ message: 'No certificate found'
667
+ };
668
+ }
669
+ }
670
+
671
+ return {
672
+ enabled: true,
673
+ port: this.settings.port80HandlerConfig.port,
674
+ useProduction: this.settings.port80HandlerConfig.useProduction,
675
+ autoRenew: this.settings.port80HandlerConfig.autoRenew,
676
+ certificates: certificateStatus
677
+ };
678
+ }
679
+ }