@push.rocks/smartproxy 10.2.0 → 12.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 (59) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/common/port80-adapter.d.ts +11 -0
  3. package/dist_ts/common/port80-adapter.js +61 -0
  4. package/dist_ts/examples/forwarding-example.d.ts +1 -0
  5. package/dist_ts/examples/forwarding-example.js +96 -0
  6. package/dist_ts/index.d.ts +1 -0
  7. package/dist_ts/index.js +3 -1
  8. package/dist_ts/smartproxy/classes.pp.connectionhandler.js +179 -30
  9. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.d.ts +39 -0
  10. package/dist_ts/smartproxy/classes.pp.domainconfigmanager.js +172 -20
  11. package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +3 -11
  12. package/dist_ts/smartproxy/classes.pp.portrangemanager.js +17 -10
  13. package/dist_ts/smartproxy/classes.pp.securitymanager.d.ts +19 -2
  14. package/dist_ts/smartproxy/classes.pp.securitymanager.js +27 -4
  15. package/dist_ts/smartproxy/classes.pp.timeoutmanager.js +3 -3
  16. package/dist_ts/smartproxy/classes.smartproxy.js +45 -13
  17. package/dist_ts/smartproxy/forwarding/domain-config.d.ts +12 -0
  18. package/dist_ts/smartproxy/forwarding/domain-config.js +12 -0
  19. package/dist_ts/smartproxy/forwarding/domain-manager.d.ts +86 -0
  20. package/dist_ts/smartproxy/forwarding/domain-manager.js +241 -0
  21. package/dist_ts/smartproxy/forwarding/forwarding.factory.d.ts +24 -0
  22. package/dist_ts/smartproxy/forwarding/forwarding.factory.js +137 -0
  23. package/dist_ts/smartproxy/forwarding/forwarding.handler.d.ts +55 -0
  24. package/dist_ts/smartproxy/forwarding/forwarding.handler.js +94 -0
  25. package/dist_ts/smartproxy/forwarding/http.handler.d.ts +25 -0
  26. package/dist_ts/smartproxy/forwarding/http.handler.js +123 -0
  27. package/dist_ts/smartproxy/forwarding/https-passthrough.handler.d.ts +24 -0
  28. package/dist_ts/smartproxy/forwarding/https-passthrough.handler.js +154 -0
  29. package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.d.ts +36 -0
  30. package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.js +229 -0
  31. package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.d.ts +35 -0
  32. package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.js +254 -0
  33. package/dist_ts/smartproxy/forwarding/index.d.ts +16 -0
  34. package/dist_ts/smartproxy/forwarding/index.js +23 -0
  35. package/dist_ts/smartproxy/types/forwarding.types.d.ts +104 -0
  36. package/dist_ts/smartproxy/types/forwarding.types.js +50 -0
  37. package/package.json +2 -2
  38. package/readme.md +158 -8
  39. package/readme.plan.md +471 -42
  40. package/ts/00_commitinfo_data.ts +1 -1
  41. package/ts/common/port80-adapter.ts +87 -0
  42. package/ts/index.ts +3 -0
  43. package/ts/smartproxy/classes.pp.connectionhandler.ts +231 -44
  44. package/ts/smartproxy/classes.pp.domainconfigmanager.ts +198 -24
  45. package/ts/smartproxy/classes.pp.interfaces.ts +3 -11
  46. package/ts/smartproxy/classes.pp.portrangemanager.ts +17 -10
  47. package/ts/smartproxy/classes.pp.securitymanager.ts +29 -5
  48. package/ts/smartproxy/classes.pp.timeoutmanager.ts +3 -3
  49. package/ts/smartproxy/classes.smartproxy.ts +68 -15
  50. package/ts/smartproxy/forwarding/domain-config.ts +28 -0
  51. package/ts/smartproxy/forwarding/domain-manager.ts +283 -0
  52. package/ts/smartproxy/forwarding/forwarding.factory.ts +155 -0
  53. package/ts/smartproxy/forwarding/forwarding.handler.ts +127 -0
  54. package/ts/smartproxy/forwarding/http.handler.ts +140 -0
  55. package/ts/smartproxy/forwarding/https-passthrough.handler.ts +182 -0
  56. package/ts/smartproxy/forwarding/https-terminate-to-http.handler.ts +264 -0
  57. package/ts/smartproxy/forwarding/https-terminate-to-https.handler.ts +292 -0
  58. package/ts/smartproxy/forwarding/index.ts +52 -0
  59. package/ts/smartproxy/types/forwarding.types.ts +162 -0
@@ -84,8 +84,10 @@ export class PortRangeManager {
84
84
  } | undefined {
85
85
  for (let i = 0; i < this.settings.domainConfigs.length; i++) {
86
86
  const domain = this.settings.domainConfigs[i];
87
- if (domain.portRanges) {
88
- for (const range of domain.portRanges) {
87
+ // Get port ranges from forwarding.advanced if available
88
+ const portRanges = domain.forwarding?.advanced?.portRanges;
89
+ if (portRanges && portRanges.length > 0) {
90
+ for (const range of portRanges) {
89
91
  if (port >= range.from && port <= range.to) {
90
92
  return { domainIndex: i, range };
91
93
  }
@@ -129,17 +131,20 @@ export class PortRangeManager {
129
131
 
130
132
  // Add domain-specific port ranges
131
133
  for (const domain of this.settings.domainConfigs) {
132
- if (domain.portRanges) {
133
- for (const range of domain.portRanges) {
134
+ // Get port ranges from forwarding.advanced
135
+ const portRanges = domain.forwarding?.advanced?.portRanges;
136
+ if (portRanges && portRanges.length > 0) {
137
+ for (const range of portRanges) {
134
138
  for (let port = range.from; port <= range.to; port++) {
135
139
  ports.add(port);
136
140
  }
137
141
  }
138
142
  }
139
-
140
- // Add domain-specific NetworkProxy port if configured
141
- if (domain.useNetworkProxy && domain.networkProxyPort) {
142
- ports.add(domain.networkProxyPort);
143
+
144
+ // Add domain-specific NetworkProxy port if configured in forwarding.advanced
145
+ const networkProxyPort = domain.forwarding?.advanced?.networkProxyPort;
146
+ if (networkProxyPort) {
147
+ ports.add(networkProxyPort);
143
148
  }
144
149
  }
145
150
 
@@ -170,8 +175,10 @@ export class PortRangeManager {
170
175
 
171
176
  // Track domain-specific port ranges
172
177
  for (const domain of this.settings.domainConfigs) {
173
- if (domain.portRanges) {
174
- for (const range of domain.portRanges) {
178
+ // Get port ranges from forwarding.advanced
179
+ const portRanges = domain.forwarding?.advanced?.portRanges;
180
+ if (portRanges && portRanges.length > 0) {
181
+ for (const range of portRanges) {
175
182
  for (let port = range.from; port <= range.to; port++) {
176
183
  if (!portMappings.has(port)) {
177
184
  portMappings.set(port, []);
@@ -63,45 +63,69 @@ export class SecurityManager {
63
63
  }
64
64
 
65
65
  /**
66
- * Check if an IP is allowed using glob patterns
66
+ * Check if an IP is authorized using forwarding security rules
67
+ *
68
+ * This method is used to determine if an IP is allowed to connect, based on security
69
+ * rules configured in the forwarding configuration. The allowed and blocked IPs are
70
+ * typically derived from domain.forwarding.security.allowedIps and blockedIps through
71
+ * DomainConfigManager.getEffectiveIPRules().
72
+ *
73
+ * @param ip - The IP address to check
74
+ * @param allowedIPs - Array of allowed IP patterns from forwarding.security.allowedIps
75
+ * @param blockedIPs - Array of blocked IP patterns from forwarding.security.blockedIps
76
+ * @returns true if IP is authorized, false if blocked
67
77
  */
68
78
  public isIPAuthorized(ip: string, allowedIPs: string[], blockedIPs: string[] = []): boolean {
69
79
  // Skip IP validation if allowedIPs is empty
70
80
  if (!ip || (allowedIPs.length === 0 && blockedIPs.length === 0)) {
71
81
  return true;
72
82
  }
73
-
74
- // First check if IP is blocked
83
+
84
+ // First check if IP is blocked - blocked IPs take precedence
75
85
  if (blockedIPs.length > 0 && this.isGlobIPMatch(ip, blockedIPs)) {
76
86
  return false;
77
87
  }
78
-
88
+
79
89
  // Then check if IP is allowed
80
90
  return this.isGlobIPMatch(ip, allowedIPs);
81
91
  }
82
92
 
83
93
  /**
84
- * Check if the IP matches any of the glob patterns
94
+ * Check if the IP matches any of the glob patterns from security configuration
95
+ *
96
+ * This method checks IP addresses against glob patterns and handles IPv4/IPv6 normalization.
97
+ * It's used to implement IP filtering based on the forwarding.security configuration.
98
+ *
99
+ * @param ip - The IP address to check
100
+ * @param patterns - Array of glob patterns from forwarding.security.allowedIps or blockedIps
101
+ * @returns true if IP matches any pattern, false otherwise
85
102
  */
86
103
  private isGlobIPMatch(ip: string, patterns: string[]): boolean {
87
104
  if (!ip || !patterns || patterns.length === 0) return false;
88
105
 
106
+ // Handle IPv4/IPv6 normalization for proper matching
89
107
  const normalizeIP = (ip: string): string[] => {
90
108
  if (!ip) return [];
109
+ // Handle IPv4-mapped IPv6 addresses (::ffff:127.0.0.1)
91
110
  if (ip.startsWith('::ffff:')) {
92
111
  const ipv4 = ip.slice(7);
93
112
  return [ip, ipv4];
94
113
  }
114
+ // Handle IPv4 addresses by also checking IPv4-mapped form
95
115
  if (/^\d{1,3}(\.\d{1,3}){3}$/.test(ip)) {
96
116
  return [ip, `::ffff:${ip}`];
97
117
  }
98
118
  return [ip];
99
119
  };
100
120
 
121
+ // Normalize the IP being checked
101
122
  const normalizedIPVariants = normalizeIP(ip);
102
123
  if (normalizedIPVariants.length === 0) return false;
103
124
 
125
+ // Normalize the pattern IPs for consistent comparison
104
126
  const expandedPatterns = patterns.flatMap(normalizeIP);
127
+
128
+ // Check for any match between normalized IP variants and patterns
105
129
  return normalizedIPVariants.some((ipVariant) =>
106
130
  expandedPatterns.some((pattern) => plugins.minimatch(ipVariant, pattern))
107
131
  );
@@ -61,9 +61,9 @@ export class TimeoutManager {
61
61
  * Calculate effective max lifetime based on connection type
62
62
  */
63
63
  public getEffectiveMaxLifetime(record: IConnectionRecord): number {
64
- // Use domain-specific timeout if available
65
- const baseTimeout = record.domainConfig?.connectionTimeout ||
66
- this.settings.maxConnectionLifetime ||
64
+ // Use domain-specific timeout from forwarding.advanced if available
65
+ const baseTimeout = record.domainConfig?.forwarding?.advanced?.timeout ||
66
+ this.settings.maxConnectionLifetime ||
67
67
  86400000; // 24 hours default
68
68
 
69
69
  // For immortal keep-alive connections, use an extremely long lifetime
@@ -12,6 +12,8 @@ import { Port80Handler } from '../port80handler/classes.port80handler.js';
12
12
  import { CertProvisioner } from './classes.pp.certprovisioner.js';
13
13
  import type { ICertificateData } from '../common/types.js';
14
14
  import { buildPort80Handler } from '../common/acmeFactory.js';
15
+ import type { ForwardingType } from './types/forwarding.types.js';
16
+ import { createPort80HandlerOptions } from '../common/port80-adapter.js';
15
17
 
16
18
  import type { ISmartProxyOptions, IDomainConfig } from './classes.pp.interfaces.js';
17
19
  export type { ISmartProxyOptions as IPortProxySettings, IDomainConfig };
@@ -156,11 +158,44 @@ export class SmartProxy extends plugins.EventEmitter {
156
158
  return;
157
159
  }
158
160
 
161
+ // Process domain configs
162
+ // Note: ensureForwardingConfig is no longer needed since forwarding is now required
163
+
164
+ // Initialize domain config manager with the processed configs
165
+ this.domainConfigManager.updateDomainConfigs(this.settings.domainConfigs);
166
+
159
167
  // Initialize Port80Handler if enabled
160
168
  await this.initializePort80Handler();
169
+
161
170
  // Initialize CertProvisioner for unified certificate workflows
162
171
  if (this.port80Handler) {
163
172
  const acme = this.settings.acme!;
173
+
174
+ // Convert domain forwards to use the new forwarding system if possible
175
+ const domainForwards = acme.domainForwards?.map(f => {
176
+ // If the domain has a forwarding config in domainConfigs, use that
177
+ const domainConfig = this.settings.domainConfigs.find(
178
+ dc => dc.domains.some(d => d === f.domain)
179
+ );
180
+
181
+ if (domainConfig?.forwarding) {
182
+ return {
183
+ domain: f.domain,
184
+ forwardConfig: f.forwardConfig,
185
+ acmeForwardConfig: f.acmeForwardConfig,
186
+ sslRedirect: f.sslRedirect || domainConfig.forwarding.http?.redirectToHttps || false
187
+ };
188
+ }
189
+
190
+ // Otherwise use the existing configuration
191
+ return {
192
+ domain: f.domain,
193
+ forwardConfig: f.forwardConfig,
194
+ acmeForwardConfig: f.acmeForwardConfig,
195
+ sslRedirect: f.sslRedirect || false
196
+ };
197
+ }) || [];
198
+
164
199
  this.certProvisioner = new CertProvisioner(
165
200
  this.settings.domainConfigs,
166
201
  this.port80Handler,
@@ -169,13 +204,9 @@ export class SmartProxy extends plugins.EventEmitter {
169
204
  acme.renewThresholdDays!,
170
205
  acme.renewCheckIntervalHours!,
171
206
  acme.autoRenew!,
172
- acme.domainForwards?.map(f => ({
173
- domain: f.domain,
174
- forwardConfig: f.forwardConfig,
175
- acmeForwardConfig: f.acmeForwardConfig,
176
- sslRedirect: f.sslRedirect || false
177
- })) || []
207
+ domainForwards
178
208
  );
209
+
179
210
  this.certProvisioner.on('certificate', (certData) => {
180
211
  this.emit('certificate', {
181
212
  domain: certData.domain,
@@ -186,6 +217,7 @@ export class SmartProxy extends plugins.EventEmitter {
186
217
  isRenewal: certData.isRenewal
187
218
  });
188
219
  });
220
+
189
221
  await this.certProvisioner.start();
190
222
  console.log('CertProvisioner started');
191
223
  }
@@ -378,21 +410,41 @@ export class SmartProxy extends plugins.EventEmitter {
378
410
  */
379
411
  public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise<void> {
380
412
  console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`);
381
-
413
+
382
414
  // Update domain configs in DomainConfigManager
383
415
  this.domainConfigManager.updateDomainConfigs(newDomainConfigs);
384
-
416
+
385
417
  // If NetworkProxy is initialized, resync the configurations
386
418
  if (this.networkProxyBridge.getNetworkProxy()) {
387
419
  await this.networkProxyBridge.syncDomainConfigsToNetworkProxy();
388
420
  }
389
-
390
- // If Port80Handler is running, provision certificates per new domain
421
+
422
+ // If Port80Handler is running, provision certificates based on forwarding type
391
423
  if (this.port80Handler && this.settings.acme?.enabled) {
392
424
  for (const domainConfig of newDomainConfigs) {
425
+ // Skip certificate provisioning for http-only or passthrough configs that don't need certs
426
+ const forwardingType = domainConfig.forwarding.type;
427
+ const needsCertificate =
428
+ forwardingType === 'https-terminate-to-http' ||
429
+ forwardingType === 'https-terminate-to-https';
430
+
431
+ // Skip certificate provisioning if ACME is explicitly disabled for this domain
432
+ const acmeDisabled = domainConfig.forwarding.acme?.enabled === false;
433
+
434
+ if (!needsCertificate || acmeDisabled) {
435
+ if (this.settings.enableDetailedLogging) {
436
+ console.log(`Skipping certificate provisioning for ${domainConfig.domains.join(', ')} (${forwardingType})`);
437
+ }
438
+ continue;
439
+ }
440
+
393
441
  for (const domain of domainConfig.domains) {
394
442
  const isWildcard = domain.includes('*');
395
443
  let provision: string | plugins.tsclass.network.ICert = 'http01';
444
+
445
+ // Check for ACME forwarding configuration in the domain
446
+ const forwardAcmeChallenges = domainConfig.forwarding.acme?.forwardChallenges;
447
+
396
448
  if (this.settings.certProvisionFunction) {
397
449
  try {
398
450
  provision = await this.settings.certProvisionFunction(domain);
@@ -403,16 +455,17 @@ export class SmartProxy extends plugins.EventEmitter {
403
455
  console.warn(`Skipping wildcard domain without certProvisionFunction: ${domain}`);
404
456
  continue;
405
457
  }
458
+
406
459
  if (provision === 'http01') {
407
460
  if (isWildcard) {
408
461
  console.warn(`Skipping HTTP-01 for wildcard domain: ${domain}`);
409
462
  continue;
410
463
  }
411
- this.port80Handler.addDomain({
412
- domainName: domain,
413
- sslRedirect: true,
414
- acmeMaintenance: true
415
- });
464
+
465
+ // Create Port80Handler options from the forwarding configuration
466
+ const port80Config = createPort80HandlerOptions(domain, domainConfig.forwarding);
467
+
468
+ this.port80Handler.addDomain(port80Config);
416
469
  console.log(`Registered domain ${domain} with Port80Handler for HTTP-01`);
417
470
  } else {
418
471
  // Static certificate (e.g., DNS-01 provisioned) supports wildcards
@@ -0,0 +1,28 @@
1
+ import type { IForwardConfig } from '../types/forwarding.types.js';
2
+
3
+ /**
4
+ * Domain configuration with unified forwarding configuration
5
+ */
6
+ export interface IDomainConfig {
7
+ // Core properties - domain patterns
8
+ domains: string[];
9
+
10
+ // Unified forwarding configuration
11
+ forwarding: IForwardConfig;
12
+ }
13
+
14
+ /**
15
+ * Helper function to create a domain configuration
16
+ */
17
+ export function createDomainConfig(
18
+ domains: string | string[],
19
+ forwarding: IForwardConfig
20
+ ): IDomainConfig {
21
+ // Normalize domains to an array
22
+ const domainArray = Array.isArray(domains) ? domains : [domains];
23
+
24
+ return {
25
+ domains: domainArray,
26
+ forwarding
27
+ };
28
+ }
@@ -0,0 +1,283 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import type { IDomainConfig } from './domain-config.js';
3
+ import type { IForwardingHandler } from '../types/forwarding.types.js';
4
+ import { ForwardingHandlerEvents } from '../types/forwarding.types.js';
5
+ import { ForwardingHandlerFactory } from './forwarding.factory.js';
6
+
7
+ /**
8
+ * Events emitted by the DomainManager
9
+ */
10
+ export enum DomainManagerEvents {
11
+ DOMAIN_ADDED = 'domain-added',
12
+ DOMAIN_REMOVED = 'domain-removed',
13
+ DOMAIN_MATCHED = 'domain-matched',
14
+ DOMAIN_MATCH_FAILED = 'domain-match-failed',
15
+ CERTIFICATE_NEEDED = 'certificate-needed',
16
+ CERTIFICATE_LOADED = 'certificate-loaded',
17
+ ERROR = 'error'
18
+ }
19
+
20
+ /**
21
+ * Manages domains and their forwarding handlers
22
+ */
23
+ export class DomainManager extends plugins.EventEmitter {
24
+ private domainConfigs: IDomainConfig[] = [];
25
+ private domainHandlers: Map<string, IForwardingHandler> = new Map();
26
+
27
+ /**
28
+ * Create a new DomainManager
29
+ * @param initialDomains Optional initial domain configurations
30
+ */
31
+ constructor(initialDomains?: IDomainConfig[]) {
32
+ super();
33
+
34
+ if (initialDomains) {
35
+ this.setDomainConfigs(initialDomains);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Set or replace all domain configurations
41
+ * @param configs Array of domain configurations
42
+ */
43
+ public async setDomainConfigs(configs: IDomainConfig[]): Promise<void> {
44
+ // Clear existing handlers
45
+ this.domainHandlers.clear();
46
+
47
+ // Store new configurations
48
+ this.domainConfigs = [...configs];
49
+
50
+ // Initialize handlers for each domain
51
+ for (const config of this.domainConfigs) {
52
+ await this.createHandlersForDomain(config);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Add a new domain configuration
58
+ * @param config The domain configuration to add
59
+ */
60
+ public async addDomainConfig(config: IDomainConfig): Promise<void> {
61
+ // Check if any of these domains already exist
62
+ for (const domain of config.domains) {
63
+ if (this.domainHandlers.has(domain)) {
64
+ // Remove existing handler for this domain
65
+ this.domainHandlers.delete(domain);
66
+ }
67
+ }
68
+
69
+ // Add the new configuration
70
+ this.domainConfigs.push(config);
71
+
72
+ // Create handlers for the new domain
73
+ await this.createHandlersForDomain(config);
74
+
75
+ this.emit(DomainManagerEvents.DOMAIN_ADDED, {
76
+ domains: config.domains,
77
+ forwardingType: config.forwarding.type
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Remove a domain configuration
83
+ * @param domain The domain to remove
84
+ * @returns True if the domain was found and removed
85
+ */
86
+ public removeDomainConfig(domain: string): boolean {
87
+ // Find the config that includes this domain
88
+ const index = this.domainConfigs.findIndex(config =>
89
+ config.domains.includes(domain)
90
+ );
91
+
92
+ if (index === -1) {
93
+ return false;
94
+ }
95
+
96
+ // Get the config
97
+ const config = this.domainConfigs[index];
98
+
99
+ // Remove all handlers for this config
100
+ for (const domainName of config.domains) {
101
+ this.domainHandlers.delete(domainName);
102
+ }
103
+
104
+ // Remove the config
105
+ this.domainConfigs.splice(index, 1);
106
+
107
+ this.emit(DomainManagerEvents.DOMAIN_REMOVED, {
108
+ domains: config.domains
109
+ });
110
+
111
+ return true;
112
+ }
113
+
114
+ /**
115
+ * Find the handler for a domain
116
+ * @param domain The domain to find a handler for
117
+ * @returns The handler or undefined if no match
118
+ */
119
+ public findHandlerForDomain(domain: string): IForwardingHandler | undefined {
120
+ // Try exact match
121
+ if (this.domainHandlers.has(domain)) {
122
+ return this.domainHandlers.get(domain);
123
+ }
124
+
125
+ // Try wildcard matches
126
+ const wildcardHandler = this.findWildcardHandler(domain);
127
+ if (wildcardHandler) {
128
+ return wildcardHandler;
129
+ }
130
+
131
+ // No match found
132
+ return undefined;
133
+ }
134
+
135
+ /**
136
+ * Handle a connection for a domain
137
+ * @param domain The domain
138
+ * @param socket The client socket
139
+ * @returns True if the connection was handled
140
+ */
141
+ public handleConnection(domain: string, socket: plugins.net.Socket): boolean {
142
+ const handler = this.findHandlerForDomain(domain);
143
+
144
+ if (!handler) {
145
+ this.emit(DomainManagerEvents.DOMAIN_MATCH_FAILED, {
146
+ domain,
147
+ remoteAddress: socket.remoteAddress
148
+ });
149
+ return false;
150
+ }
151
+
152
+ this.emit(DomainManagerEvents.DOMAIN_MATCHED, {
153
+ domain,
154
+ handlerType: handler.constructor.name,
155
+ remoteAddress: socket.remoteAddress
156
+ });
157
+
158
+ // Handle the connection
159
+ handler.handleConnection(socket);
160
+ return true;
161
+ }
162
+
163
+ /**
164
+ * Handle an HTTP request for a domain
165
+ * @param domain The domain
166
+ * @param req The HTTP request
167
+ * @param res The HTTP response
168
+ * @returns True if the request was handled
169
+ */
170
+ public handleHttpRequest(domain: string, req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): boolean {
171
+ const handler = this.findHandlerForDomain(domain);
172
+
173
+ if (!handler) {
174
+ this.emit(DomainManagerEvents.DOMAIN_MATCH_FAILED, {
175
+ domain,
176
+ remoteAddress: req.socket.remoteAddress
177
+ });
178
+ return false;
179
+ }
180
+
181
+ this.emit(DomainManagerEvents.DOMAIN_MATCHED, {
182
+ domain,
183
+ handlerType: handler.constructor.name,
184
+ remoteAddress: req.socket.remoteAddress
185
+ });
186
+
187
+ // Handle the request
188
+ handler.handleHttpRequest(req, res);
189
+ return true;
190
+ }
191
+
192
+ /**
193
+ * Create handlers for a domain configuration
194
+ * @param config The domain configuration
195
+ */
196
+ private async createHandlersForDomain(config: IDomainConfig): Promise<void> {
197
+ try {
198
+ // Create a handler for this forwarding configuration
199
+ const handler = ForwardingHandlerFactory.createHandler(config.forwarding);
200
+
201
+ // Initialize the handler
202
+ await handler.initialize();
203
+
204
+ // Set up event forwarding
205
+ this.setupHandlerEvents(handler, config);
206
+
207
+ // Store the handler for each domain in the config
208
+ for (const domain of config.domains) {
209
+ this.domainHandlers.set(domain, handler);
210
+ }
211
+ } catch (error) {
212
+ this.emit(DomainManagerEvents.ERROR, {
213
+ domains: config.domains,
214
+ error: error instanceof Error ? error.message : String(error)
215
+ });
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Set up event forwarding from a handler
221
+ * @param handler The handler
222
+ * @param config The domain configuration for this handler
223
+ */
224
+ private setupHandlerEvents(handler: IForwardingHandler, config: IDomainConfig): void {
225
+ // Forward relevant events
226
+ handler.on(ForwardingHandlerEvents.CERTIFICATE_NEEDED, (data) => {
227
+ this.emit(DomainManagerEvents.CERTIFICATE_NEEDED, {
228
+ ...data,
229
+ domains: config.domains
230
+ });
231
+ });
232
+
233
+ handler.on(ForwardingHandlerEvents.CERTIFICATE_LOADED, (data) => {
234
+ this.emit(DomainManagerEvents.CERTIFICATE_LOADED, {
235
+ ...data,
236
+ domains: config.domains
237
+ });
238
+ });
239
+
240
+ handler.on(ForwardingHandlerEvents.ERROR, (data) => {
241
+ this.emit(DomainManagerEvents.ERROR, {
242
+ ...data,
243
+ domains: config.domains
244
+ });
245
+ });
246
+ }
247
+
248
+ /**
249
+ * Find a handler for a domain using wildcard matching
250
+ * @param domain The domain to find a handler for
251
+ * @returns The handler or undefined if no match
252
+ */
253
+ private findWildcardHandler(domain: string): IForwardingHandler | undefined {
254
+ // Exact match already checked in findHandlerForDomain
255
+
256
+ // Try subdomain wildcard (*.example.com)
257
+ if (domain.includes('.')) {
258
+ const parts = domain.split('.');
259
+ if (parts.length > 2) {
260
+ const wildcardDomain = `*.${parts.slice(1).join('.')}`;
261
+ if (this.domainHandlers.has(wildcardDomain)) {
262
+ return this.domainHandlers.get(wildcardDomain);
263
+ }
264
+ }
265
+ }
266
+
267
+ // Try full wildcard
268
+ if (this.domainHandlers.has('*')) {
269
+ return this.domainHandlers.get('*');
270
+ }
271
+
272
+ // No match found
273
+ return undefined;
274
+ }
275
+
276
+ /**
277
+ * Get all domain configurations
278
+ * @returns Array of domain configurations
279
+ */
280
+ public getDomainConfigs(): IDomainConfig[] {
281
+ return [...this.domainConfigs];
282
+ }
283
+ }