@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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/common/port80-adapter.d.ts +11 -0
- package/dist_ts/common/port80-adapter.js +61 -0
- package/dist_ts/examples/forwarding-example.d.ts +1 -0
- package/dist_ts/examples/forwarding-example.js +96 -0
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +3 -1
- package/dist_ts/smartproxy/classes.pp.connectionhandler.js +179 -30
- package/dist_ts/smartproxy/classes.pp.domainconfigmanager.d.ts +39 -0
- package/dist_ts/smartproxy/classes.pp.domainconfigmanager.js +172 -20
- package/dist_ts/smartproxy/classes.pp.interfaces.d.ts +3 -11
- package/dist_ts/smartproxy/classes.pp.portrangemanager.js +17 -10
- package/dist_ts/smartproxy/classes.pp.securitymanager.d.ts +19 -2
- package/dist_ts/smartproxy/classes.pp.securitymanager.js +27 -4
- package/dist_ts/smartproxy/classes.pp.timeoutmanager.js +3 -3
- package/dist_ts/smartproxy/classes.smartproxy.js +45 -13
- package/dist_ts/smartproxy/forwarding/domain-config.d.ts +12 -0
- package/dist_ts/smartproxy/forwarding/domain-config.js +12 -0
- package/dist_ts/smartproxy/forwarding/domain-manager.d.ts +86 -0
- package/dist_ts/smartproxy/forwarding/domain-manager.js +241 -0
- package/dist_ts/smartproxy/forwarding/forwarding.factory.d.ts +24 -0
- package/dist_ts/smartproxy/forwarding/forwarding.factory.js +137 -0
- package/dist_ts/smartproxy/forwarding/forwarding.handler.d.ts +55 -0
- package/dist_ts/smartproxy/forwarding/forwarding.handler.js +94 -0
- package/dist_ts/smartproxy/forwarding/http.handler.d.ts +25 -0
- package/dist_ts/smartproxy/forwarding/http.handler.js +123 -0
- package/dist_ts/smartproxy/forwarding/https-passthrough.handler.d.ts +24 -0
- package/dist_ts/smartproxy/forwarding/https-passthrough.handler.js +154 -0
- package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.d.ts +36 -0
- package/dist_ts/smartproxy/forwarding/https-terminate-to-http.handler.js +229 -0
- package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.d.ts +35 -0
- package/dist_ts/smartproxy/forwarding/https-terminate-to-https.handler.js +254 -0
- package/dist_ts/smartproxy/forwarding/index.d.ts +16 -0
- package/dist_ts/smartproxy/forwarding/index.js +23 -0
- package/dist_ts/smartproxy/types/forwarding.types.d.ts +104 -0
- package/dist_ts/smartproxy/types/forwarding.types.js +50 -0
- package/package.json +2 -2
- package/readme.md +158 -8
- package/readme.plan.md +471 -42
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/common/port80-adapter.ts +87 -0
- package/ts/index.ts +3 -0
- package/ts/smartproxy/classes.pp.connectionhandler.ts +231 -44
- package/ts/smartproxy/classes.pp.domainconfigmanager.ts +198 -24
- package/ts/smartproxy/classes.pp.interfaces.ts +3 -11
- package/ts/smartproxy/classes.pp.portrangemanager.ts +17 -10
- package/ts/smartproxy/classes.pp.securitymanager.ts +29 -5
- package/ts/smartproxy/classes.pp.timeoutmanager.ts +3 -3
- package/ts/smartproxy/classes.smartproxy.ts +68 -15
- package/ts/smartproxy/forwarding/domain-config.ts +28 -0
- package/ts/smartproxy/forwarding/domain-manager.ts +283 -0
- package/ts/smartproxy/forwarding/forwarding.factory.ts +155 -0
- package/ts/smartproxy/forwarding/forwarding.handler.ts +127 -0
- package/ts/smartproxy/forwarding/http.handler.ts +140 -0
- package/ts/smartproxy/forwarding/https-passthrough.handler.ts +182 -0
- package/ts/smartproxy/forwarding/https-terminate-to-http.handler.ts +264 -0
- package/ts/smartproxy/forwarding/https-terminate-to-https.handler.ts +292 -0
- package/ts/smartproxy/forwarding/index.ts +52 -0
- 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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
133
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
174
|
-
|
|
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
|
|
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?.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
+
}
|