update
This commit is contained in:
		| @@ -153,44 +153,55 @@ This component has the cleanest design, so we'll start migration here: | |||||||
|  |  | ||||||
| ### Phase 6: Proxy Implementation Migration (Weeks 3-4) | ### Phase 6: Proxy Implementation Migration (Weeks 3-4) | ||||||
|  |  | ||||||
| - [ ] Migrate SmartProxy components | - [x] Migrate SmartProxy components | ||||||
|   - [x] First, migrate interfaces to `ts/proxies/smart-proxy/models/` |   - [x] First, migrate interfaces to `ts/proxies/smart-proxy/models/` | ||||||
|   - [ ] Move core class: `ts/smartproxy/classes.smartproxy.ts` → `ts/proxies/smart-proxy/smart-proxy.ts` |   - [x] Move core class: `ts/smartproxy/classes.smartproxy.ts` → `ts/proxies/smart-proxy/smart-proxy.ts` | ||||||
|   - [ ] Move supporting classes using consistent naming |   - [x] Move supporting classes using consistent naming | ||||||
|  |     - [x] Move ConnectionManager from classes.pp.connectionmanager.ts to connection-manager.ts | ||||||
|  |     - [x] Move SecurityManager from classes.pp.securitymanager.ts to security-manager.ts | ||||||
|  |     - [x] Move DomainConfigManager from classes.pp.domainconfigmanager.ts to domain-config-manager.ts | ||||||
|  |     - [x] Move TimeoutManager from classes.pp.timeoutmanager.ts to timeout-manager.ts | ||||||
|  |     - [x] Move TlsManager from classes.pp.tlsmanager.ts to tls-manager.ts | ||||||
|  |     - [x] Move NetworkProxyBridge from classes.pp.networkproxybridge.ts to network-proxy-bridge.ts | ||||||
|  |     - [x] Move PortRangeManager from classes.pp.portrangemanager.ts to port-range-manager.ts | ||||||
|  |     - [x] Move ConnectionHandler from classes.pp.connectionhandler.ts to connection-handler.ts | ||||||
|   - [x] Normalize interface names (SmartProxyOptions instead of IPortProxySettings) |   - [x] Normalize interface names (SmartProxyOptions instead of IPortProxySettings) | ||||||
|  |  | ||||||
| - [ ] Migrate NetworkProxy components | - [x] Migrate NetworkProxy components | ||||||
|   - [x] First, migrate interfaces to `ts/proxies/network-proxy/models/` |   - [x] First, migrate interfaces to `ts/proxies/network-proxy/models/` | ||||||
|   - [ ] Move core class: `ts/networkproxy/classes.np.networkproxy.ts` → `ts/proxies/network-proxy/network-proxy.ts` |   - [x] Move core class: `ts/networkproxy/classes.np.networkproxy.ts` → `ts/proxies/network-proxy/network-proxy.ts` | ||||||
|   - [ ] Move supporting classes using consistent naming |   - [x] Move supporting classes using consistent naming | ||||||
|  |  | ||||||
| - [ ] Migrate NfTablesProxy | - [x] Migrate NfTablesProxy | ||||||
|   - [ ] Move `ts/nfttablesproxy/classes.nftablesproxy.ts` → `ts/proxies/nftables-proxy/nftables-proxy.ts` |   - [x] Move `ts/nfttablesproxy/classes.nftablesproxy.ts` → `ts/proxies/nftables-proxy/nftables-proxy.ts` | ||||||
|  |   - [x] Extract interfaces to `ts/proxies/nftables-proxy/models/interfaces.ts` | ||||||
|  |   - [x] Extract error classes to `ts/proxies/nftables-proxy/models/errors.ts` | ||||||
|  |   - [x] Create proper barrel files for module exports | ||||||
|  |  | ||||||
| ### Phase 7: Integration & Main Module (Week 4-5) | ### Phase 7: Integration & Main Module (Week 4-5) | ||||||
|  |  | ||||||
| - [ ] Create main entry points | - [x] Create main entry points | ||||||
|   - [ ] Update `ts/index.ts` with all public exports |   - [x] Update `ts/index.ts` with all public exports | ||||||
|   - [ ] Ensure backward compatibility with type aliases |   - [x] Ensure backward compatibility with type aliases | ||||||
|   - [ ] Implement proper namespace exports |   - [x] Implement proper namespace exports | ||||||
|  |  | ||||||
| - [ ] Update module dependencies | - [x] Update module dependencies | ||||||
|   - [ ] Update relative import paths in all modules |   - [x] Update relative import paths in all modules | ||||||
|   - [ ] Resolve circular dependencies if found |   - [x] Resolve circular dependencies if found | ||||||
|   - [ ] Test cross-module integration |   - [x] Test cross-module integration | ||||||
|  |  | ||||||
| ### Phase 8: Interface Normalization (Week 5) | ### Phase 8: Interface Normalization (Week 5) | ||||||
|  |  | ||||||
| - [ ] Standardize interface naming | - [x] Standardize interface naming | ||||||
|   - [ ] Rename `IPortProxySettings` → `SmartProxyOptions` |   - [x] Rename `IPortProxySettings` → `SmartProxyOptions` | ||||||
|   - [ ] Rename `IDomainConfig` → `DomainConfig` |   - [x] Rename `IDomainConfig` → `DomainConfig` | ||||||
|   - [ ] Rename `IConnectionRecord` → `ConnectionRecord` |   - [x] Rename `IConnectionRecord` → `ConnectionRecord` | ||||||
|   - [ ] Rename `INetworkProxyOptions` → `NetworkProxyOptions` |   - [x] Rename `INetworkProxyOptions` → `NetworkProxyOptions` | ||||||
|   - [ ] Rename other interfaces for consistency |   - [x] Rename other interfaces for consistency | ||||||
|  |  | ||||||
| - [ ] Provide backward compatibility | - [x] Provide backward compatibility | ||||||
|   - [ ] Add type aliases for renamed interfaces |   - [x] Add type aliases for renamed interfaces | ||||||
|   - [ ] Ensure all exports are compatible with existing code |   - [x] Ensure all exports are compatible with existing code | ||||||
|  |  | ||||||
| ### Phase 9: Testing & Validation (Weeks 5-6) | ### Phase 9: Testing & Validation (Weeks 5-6) | ||||||
|  |  | ||||||
| @@ -265,28 +276,29 @@ This component has the cleanest design, so we'll start migration here: | |||||||
| | ts/redirect/classes.redirect.ts | ts/http/redirects/redirect-handler.ts | ✅ | | | ts/redirect/classes.redirect.ts | ts/http/redirects/redirect-handler.ts | ✅ | | ||||||
| | ts/classes.router.ts | ts/http/router/proxy-router.ts | ✅ | | | ts/classes.router.ts | ts/http/router/proxy-router.ts | ✅ | | ||||||
| | **SmartProxy Components** | | | | | **SmartProxy Components** | | | | ||||||
| | ts/smartproxy/classes.smartproxy.ts | ts/proxies/smart-proxy/smart-proxy.ts | ❌ | | | ts/smartproxy/classes.smartproxy.ts | ts/proxies/smart-proxy/smart-proxy.ts | ✅ | | ||||||
| | ts/smartproxy/classes.pp.interfaces.ts | ts/proxies/smart-proxy/models/interfaces.ts | ✅ | | | ts/smartproxy/classes.pp.interfaces.ts | ts/proxies/smart-proxy/models/interfaces.ts | ✅ | | ||||||
| | ts/smartproxy/classes.pp.connectionhandler.ts | ts/proxies/smart-proxy/connection-handler.ts | ❌ | | | ts/smartproxy/classes.pp.connectionhandler.ts | ts/proxies/smart-proxy/connection-handler.ts | ✅ | | ||||||
| | ts/smartproxy/classes.pp.connectionmanager.ts | ts/proxies/smart-proxy/connection-manager.ts | ❌ | | | ts/smartproxy/classes.pp.connectionmanager.ts | ts/proxies/smart-proxy/connection-manager.ts | ✅ | | ||||||
| | ts/smartproxy/classes.pp.domainconfigmanager.ts | ts/proxies/smart-proxy/domain-config-manager.ts | ❌ | | | ts/smartproxy/classes.pp.domainconfigmanager.ts | ts/proxies/smart-proxy/domain-config-manager.ts | ✅ | | ||||||
| | ts/smartproxy/classes.pp.portrangemanager.ts | ts/proxies/smart-proxy/port-range-manager.ts | ❌ | | | ts/smartproxy/classes.pp.portrangemanager.ts | ts/proxies/smart-proxy/port-range-manager.ts | ✅ | | ||||||
| | ts/smartproxy/classes.pp.securitymanager.ts | ts/proxies/smart-proxy/security-manager.ts | ❌ | | | ts/smartproxy/classes.pp.securitymanager.ts | ts/proxies/smart-proxy/security-manager.ts | ✅ | | ||||||
| | ts/smartproxy/classes.pp.timeoutmanager.ts | ts/proxies/smart-proxy/timeout-manager.ts | ❌ | | | ts/smartproxy/classes.pp.timeoutmanager.ts | ts/proxies/smart-proxy/timeout-manager.ts | ✅ | | ||||||
| | ts/smartproxy/classes.pp.networkproxybridge.ts | ts/proxies/smart-proxy/network-proxy-bridge.ts | ❌ | | | ts/smartproxy/classes.pp.networkproxybridge.ts | ts/proxies/smart-proxy/network-proxy-bridge.ts | ✅ | | ||||||
|  | | ts/smartproxy/classes.pp.tlsmanager.ts | ts/proxies/smart-proxy/tls-manager.ts | ✅ | | ||||||
| | (new) | ts/proxies/smart-proxy/models/index.ts | ✅ | | | (new) | ts/proxies/smart-proxy/models/index.ts | ✅ | | ||||||
| | (new) | ts/proxies/smart-proxy/index.ts | ✅ | | | (new) | ts/proxies/smart-proxy/index.ts | ✅ | | ||||||
| | **NetworkProxy Components** | | | | | **NetworkProxy Components** | | | | ||||||
| | ts/networkproxy/classes.np.networkproxy.ts | ts/proxies/network-proxy/network-proxy.ts | ❌ | | | ts/networkproxy/classes.np.networkproxy.ts | ts/proxies/network-proxy/network-proxy.ts | ✅ | | ||||||
| | ts/networkproxy/classes.np.certificatemanager.ts | ts/proxies/network-proxy/certificate-manager.ts | ❌ | | | ts/networkproxy/classes.np.certificatemanager.ts | ts/proxies/network-proxy/certificate-manager.ts | ✅ | | ||||||
| | ts/networkproxy/classes.np.connectionpool.ts | ts/proxies/network-proxy/connection-pool.ts | ❌ | | | ts/networkproxy/classes.np.connectionpool.ts | ts/proxies/network-proxy/connection-pool.ts | ✅ | | ||||||
| | ts/networkproxy/classes.np.requesthandler.ts | ts/proxies/network-proxy/request-handler.ts | ❌ | | | ts/networkproxy/classes.np.requesthandler.ts | ts/proxies/network-proxy/request-handler.ts | ✅ | | ||||||
| | ts/networkproxy/classes.np.websockethandler.ts | ts/proxies/network-proxy/websocket-handler.ts | ❌ | | | ts/networkproxy/classes.np.websockethandler.ts | ts/proxies/network-proxy/websocket-handler.ts | ✅ | | ||||||
| | ts/networkproxy/classes.np.types.ts | ts/proxies/network-proxy/models/types.ts | ✅ | | | ts/networkproxy/classes.np.types.ts | ts/proxies/network-proxy/models/types.ts | ✅ | | ||||||
| | (new) | ts/proxies/network-proxy/models/index.ts | ✅ | | | (new) | ts/proxies/network-proxy/models/index.ts | ✅ | | ||||||
| | (new) | ts/proxies/network-proxy/index.ts | ✅ | | | (new) | ts/proxies/network-proxy/index.ts | ✅ | | ||||||
| | **NFTablesProxy Components** | | | | | **NFTablesProxy Components** | | | | ||||||
| | ts/nfttablesproxy/classes.nftablesproxy.ts | ts/proxies/nftables-proxy/nftables-proxy.ts | ❌ | | | ts/nfttablesproxy/classes.nftablesproxy.ts | ts/proxies/nftables-proxy/nftables-proxy.ts | ✅ | | ||||||
| | (new) | ts/proxies/nftables-proxy/index.ts | ✅ | | | (new) | ts/proxies/nftables-proxy/index.ts | ✅ | | ||||||
| | (new) | ts/proxies/index.ts | ✅ | | | (new) | ts/proxies/index.ts | ✅ | | ||||||
| | **Forwarding System** | | | | | **Forwarding System** | | | | ||||||
|   | |||||||
| @@ -1,12 +1,12 @@ | |||||||
| import { tap, expect } from '@push.rocks/tapbundle'; | import { tap, expect } from '@push.rocks/tapbundle'; | ||||||
| import * as plugins from '../ts/plugins.js'; | import * as plugins from '../ts/plugins.js'; | ||||||
| import type { IForwardConfig } from '../ts/smartproxy/types/forwarding.types.js'; | import type { ForwardConfig } from '../ts/forwarding/config/forwarding-types.js'; | ||||||
|  |  | ||||||
| // First, import the components directly to avoid issues with compiled modules | // First, import the components directly to avoid issues with compiled modules | ||||||
| import { ForwardingHandlerFactory } from '../ts/smartproxy/forwarding/forwarding.factory.js'; | import { ForwardingHandlerFactory } from '../ts/forwarding/factory/forwarding-factory.js'; | ||||||
| import { createDomainConfig } from '../ts/smartproxy/forwarding/domain-config.js'; | import { createDomainConfig } from '../ts/forwarding/config/domain-config.js'; | ||||||
| import { DomainManager } from '../ts/smartproxy/forwarding/domain-manager.js'; | import { DomainManager } from '../ts/forwarding/config/domain-manager.js'; | ||||||
| import { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, httpsPassthrough } from '../ts/smartproxy/types/forwarding.types.js'; | import { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, httpsPassthrough } from '../ts/forwarding/config/forwarding-types.js'; | ||||||
|  |  | ||||||
| const helpers = { | const helpers = { | ||||||
|   httpOnly, |   httpOnly, | ||||||
| @@ -17,7 +17,7 @@ const helpers = { | |||||||
|  |  | ||||||
| tap.test('ForwardingHandlerFactory - apply defaults based on type', async () => { | tap.test('ForwardingHandlerFactory - apply defaults based on type', async () => { | ||||||
|       // HTTP-only defaults |       // HTTP-only defaults | ||||||
|       const httpConfig: IForwardConfig = { |       const httpConfig: ForwardConfig = { | ||||||
|         type: 'http-only', |         type: 'http-only', | ||||||
|         target: { host: 'localhost', port: 3000 } |         target: { host: 'localhost', port: 3000 } | ||||||
|       }; |       }; | ||||||
| @@ -26,7 +26,7 @@ tap.test('ForwardingHandlerFactory - apply defaults based on type', async () => | |||||||
|       expect(expandedHttpConfig.http?.enabled).toEqual(true); |       expect(expandedHttpConfig.http?.enabled).toEqual(true); | ||||||
|  |  | ||||||
|       // HTTPS-passthrough defaults |       // HTTPS-passthrough defaults | ||||||
|       const passthroughConfig: IForwardConfig = { |       const passthroughConfig: ForwardConfig = { | ||||||
|         type: 'https-passthrough', |         type: 'https-passthrough', | ||||||
|         target: { host: 'localhost', port: 443 } |         target: { host: 'localhost', port: 443 } | ||||||
|       }; |       }; | ||||||
| @@ -36,7 +36,7 @@ tap.test('ForwardingHandlerFactory - apply defaults based on type', async () => | |||||||
|       expect(expandedPassthroughConfig.http?.enabled).toEqual(false); |       expect(expandedPassthroughConfig.http?.enabled).toEqual(false); | ||||||
|  |  | ||||||
|       // HTTPS-terminate-to-http defaults |       // HTTPS-terminate-to-http defaults | ||||||
|       const terminateToHttpConfig: IForwardConfig = { |       const terminateToHttpConfig: ForwardConfig = { | ||||||
|         type: 'https-terminate-to-http', |         type: 'https-terminate-to-http', | ||||||
|         target: { host: 'localhost', port: 3000 } |         target: { host: 'localhost', port: 3000 } | ||||||
|       }; |       }; | ||||||
| @@ -48,7 +48,7 @@ tap.test('ForwardingHandlerFactory - apply defaults based on type', async () => | |||||||
|       expect(expandedTerminateToHttpConfig.acme?.maintenance).toEqual(true); |       expect(expandedTerminateToHttpConfig.acme?.maintenance).toEqual(true); | ||||||
|  |  | ||||||
|       // HTTPS-terminate-to-https defaults |       // HTTPS-terminate-to-https defaults | ||||||
|       const terminateToHttpsConfig: IForwardConfig = { |       const terminateToHttpsConfig: ForwardConfig = { | ||||||
|         type: 'https-terminate-to-https', |         type: 'https-terminate-to-https', | ||||||
|         target: { host: 'localhost', port: 8443 } |         target: { host: 'localhost', port: 8443 } | ||||||
|       }; |       }; | ||||||
| @@ -62,7 +62,7 @@ tap.test('ForwardingHandlerFactory - apply defaults based on type', async () => | |||||||
|      |      | ||||||
| tap.test('ForwardingHandlerFactory - validate configuration', async () => { | tap.test('ForwardingHandlerFactory - validate configuration', async () => { | ||||||
|       // Valid configuration |       // Valid configuration | ||||||
|       const validConfig: IForwardConfig = { |       const validConfig: ForwardConfig = { | ||||||
|         type: 'http-only', |         type: 'http-only', | ||||||
|         target: { host: 'localhost', port: 3000 } |         target: { host: 'localhost', port: 3000 } | ||||||
|       }; |       }; | ||||||
| @@ -77,7 +77,7 @@ tap.test('ForwardingHandlerFactory - validate configuration', async () => { | |||||||
|       expect(() => ForwardingHandlerFactory.validateConfig(invalidConfig1)).toThrow(); |       expect(() => ForwardingHandlerFactory.validateConfig(invalidConfig1)).toThrow(); | ||||||
|        |        | ||||||
|       // Invalid configuration - invalid port |       // Invalid configuration - invalid port | ||||||
|       const invalidConfig2: IForwardConfig = { |       const invalidConfig2: ForwardConfig = { | ||||||
|         type: 'http-only', |         type: 'http-only', | ||||||
|         target: { host: 'localhost', port: 0 } |         target: { host: 'localhost', port: 0 } | ||||||
|       }; |       }; | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { expect, tap } from '@push.rocks/tapbundle'; | import { expect, tap } from '@push.rocks/tapbundle'; | ||||||
| import * as net from 'net'; | import * as net from 'net'; | ||||||
| import { SmartProxy } from '../ts/smartproxy/classes.smartproxy.js'; | import { SmartProxy } from '../ts/proxies/smart-proxy/index.js'; | ||||||
|  |  | ||||||
| let testServer: net.Server; | let testServer: net.Server; | ||||||
| let smartProxy: SmartProxy; | let smartProxy: SmartProxy; | ||||||
|   | |||||||
| @@ -6,8 +6,8 @@ import type { | |||||||
| } from './types.js'; | } from './types.js'; | ||||||
|  |  | ||||||
| import type { | import type { | ||||||
|   IForwardConfig |   ForwardConfig as IForwardConfig | ||||||
| } from '../smartproxy/types/forwarding.types.js'; | } from '../forwarding/config/forwarding-types.js'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Converts a forwarding configuration target to the legacy format |  * Converts a forwarding configuration target to the legacy format | ||||||
|   | |||||||
| @@ -1,3 +1,5 @@ | |||||||
| /** | /** | ||||||
|  * HTTP routing |  * HTTP routing | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
|  | export * from './proxy-router.js'; | ||||||
|   | |||||||
| @@ -1,22 +1,29 @@ | |||||||
| import * as plugins from './plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
|  | import type { ReverseProxyConfig } from '../../proxies/network-proxy/models/types.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Optional path pattern configuration that can be added to proxy configs |  * Optional path pattern configuration that can be added to proxy configs | ||||||
|  */ |  */ | ||||||
| export interface IPathPatternConfig { | export interface PathPatternConfig { | ||||||
|   pathPattern?: string; |   pathPattern?: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Backward compatibility
 | ||||||
|  | export type IPathPatternConfig = PathPatternConfig; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Interface for router result with additional metadata |  * Interface for router result with additional metadata | ||||||
|  */ |  */ | ||||||
| export interface IRouterResult { | export interface RouterResult { | ||||||
|   config: plugins.tsclass.network.IReverseProxyConfig; |   config: ReverseProxyConfig; | ||||||
|   pathMatch?: string; |   pathMatch?: string; | ||||||
|   pathParams?: Record<string, string>; |   pathParams?: Record<string, string>; | ||||||
|   pathRemainder?: string; |   pathRemainder?: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Backward compatibility
 | ||||||
|  | export type IRouterResult = RouterResult; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Router for HTTP reverse proxy requests |  * Router for HTTP reverse proxy requests | ||||||
|  *  |  *  | ||||||
| @@ -34,13 +41,13 @@ export interface IRouterResult { | |||||||
|  */ |  */ | ||||||
| export class ProxyRouter { | export class ProxyRouter { | ||||||
|   // Store original configs for reference
 |   // Store original configs for reference
 | ||||||
|   private reverseProxyConfigs: plugins.tsclass.network.IReverseProxyConfig[] = []; |   private reverseProxyConfigs: ReverseProxyConfig[] = []; | ||||||
|   // Default config to use when no match is found (optional)
 |   // Default config to use when no match is found (optional)
 | ||||||
|   private defaultConfig?: plugins.tsclass.network.IReverseProxyConfig; |   private defaultConfig?: ReverseProxyConfig; | ||||||
|   // Store path patterns separately since they're not in the original interface
 |   // Store path patterns separately since they're not in the original interface
 | ||||||
|   private pathPatterns: Map<plugins.tsclass.network.IReverseProxyConfig, string> = new Map(); |   private pathPatterns: Map<ReverseProxyConfig, string> = new Map(); | ||||||
|   // Logger interface
 |   // Logger interface
 | ||||||
|   private logger: {  |   private logger: { | ||||||
|     error: (message: string, data?: any) => void; |     error: (message: string, data?: any) => void; | ||||||
|     warn: (message: string, data?: any) => void; |     warn: (message: string, data?: any) => void; | ||||||
|     info: (message: string, data?: any) => void; |     info: (message: string, data?: any) => void; | ||||||
| @@ -48,8 +55,8 @@ export class ProxyRouter { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     configs?: plugins.tsclass.network.IReverseProxyConfig[], |     configs?: ReverseProxyConfig[], | ||||||
|     logger?: {  |     logger?: { | ||||||
|       error: (message: string, data?: any) => void; |       error: (message: string, data?: any) => void; | ||||||
|       warn: (message: string, data?: any) => void; |       warn: (message: string, data?: any) => void; | ||||||
|       info: (message: string, data?: any) => void; |       info: (message: string, data?: any) => void; | ||||||
| @@ -66,12 +73,12 @@ export class ProxyRouter { | |||||||
|    * Sets a new set of reverse configs to be routed to |    * Sets a new set of reverse configs to be routed to | ||||||
|    * @param reverseCandidatesArg Array of reverse proxy configurations |    * @param reverseCandidatesArg Array of reverse proxy configurations | ||||||
|    */ |    */ | ||||||
|   public setNewProxyConfigs(reverseCandidatesArg: plugins.tsclass.network.IReverseProxyConfig[]): void { |   public setNewProxyConfigs(reverseCandidatesArg: ReverseProxyConfig[]): void { | ||||||
|     this.reverseProxyConfigs = [...reverseCandidatesArg]; |     this.reverseProxyConfigs = [...reverseCandidatesArg]; | ||||||
|      | 
 | ||||||
|     // Find default config if any (config with "*" as hostname)
 |     // Find default config if any (config with "*" as hostname)
 | ||||||
|     this.defaultConfig = this.reverseProxyConfigs.find(config => config.hostName === '*'); |     this.defaultConfig = this.reverseProxyConfigs.find(config => config.hostName === '*'); | ||||||
|      | 
 | ||||||
|     this.logger.info(`Router initialized with ${this.reverseProxyConfigs.length} configs (${this.getHostnames().length} unique hosts)`); |     this.logger.info(`Router initialized with ${this.reverseProxyConfigs.length} configs (${this.getHostnames().length} unique hosts)`); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @@ -80,17 +87,17 @@ export class ProxyRouter { | |||||||
|    * @param req The incoming HTTP request |    * @param req The incoming HTTP request | ||||||
|    * @returns The matching proxy config or undefined if no match found |    * @returns The matching proxy config or undefined if no match found | ||||||
|    */ |    */ | ||||||
|   public routeReq(req: plugins.http.IncomingMessage): plugins.tsclass.network.IReverseProxyConfig { |   public routeReq(req: plugins.http.IncomingMessage): ReverseProxyConfig { | ||||||
|     const result = this.routeReqWithDetails(req); |     const result = this.routeReqWithDetails(req); | ||||||
|     return result ? result.config : undefined; |     return result ? result.config : undefined; | ||||||
|   } |   } | ||||||
|    | 
 | ||||||
|   /** |   /** | ||||||
|    * Routes a request with detailed matching information |    * Routes a request with detailed matching information | ||||||
|    * @param req The incoming HTTP request |    * @param req The incoming HTTP request | ||||||
|    * @returns Detailed routing result including matched config and path information |    * @returns Detailed routing result including matched config and path information | ||||||
|    */ |    */ | ||||||
|   public routeReqWithDetails(req: plugins.http.IncomingMessage): IRouterResult | undefined { |   public routeReqWithDetails(req: plugins.http.IncomingMessage): RouterResult | undefined { | ||||||
|     // Extract and validate host header
 |     // Extract and validate host header
 | ||||||
|     const originalHost = req.headers.host; |     const originalHost = req.headers.host; | ||||||
|     if (!originalHost) { |     if (!originalHost) { | ||||||
| @@ -202,7 +209,7 @@ export class ProxyRouter { | |||||||
|   /** |   /** | ||||||
|    * Find a config for a specific host and path |    * Find a config for a specific host and path | ||||||
|    */ |    */ | ||||||
|   private findConfigForHost(hostname: string, path: string): IRouterResult | undefined { |   private findConfigForHost(hostname: string, path: string): RouterResult | undefined { | ||||||
|     // Find all configs for this hostname
 |     // Find all configs for this hostname
 | ||||||
|     const configs = this.reverseProxyConfigs.filter( |     const configs = this.reverseProxyConfigs.filter( | ||||||
|       config => config.hostName.toLowerCase() === hostname.toLowerCase() |       config => config.hostName.toLowerCase() === hostname.toLowerCase() | ||||||
| @@ -349,7 +356,7 @@ export class ProxyRouter { | |||||||
|    * Gets all currently active proxy configurations |    * Gets all currently active proxy configurations | ||||||
|    * @returns Array of all active configurations |    * @returns Array of all active configurations | ||||||
|    */ |    */ | ||||||
|   public getProxyConfigs(): plugins.tsclass.network.IReverseProxyConfig[] { |   public getProxyConfigs(): ReverseProxyConfig[] { | ||||||
|     return [...this.reverseProxyConfigs]; |     return [...this.reverseProxyConfigs]; | ||||||
|   } |   } | ||||||
|    |    | ||||||
| @@ -373,7 +380,7 @@ export class ProxyRouter { | |||||||
|    * @param pathPattern Optional path pattern for route matching |    * @param pathPattern Optional path pattern for route matching | ||||||
|    */ |    */ | ||||||
|   public addProxyConfig( |   public addProxyConfig( | ||||||
|     config: plugins.tsclass.network.IReverseProxyConfig,  |     config: ReverseProxyConfig, | ||||||
|     pathPattern?: string |     pathPattern?: string | ||||||
|   ): void { |   ): void { | ||||||
|     this.reverseProxyConfigs.push(config); |     this.reverseProxyConfigs.push(config); | ||||||
| @@ -391,7 +398,7 @@ export class ProxyRouter { | |||||||
|    * @returns Boolean indicating if the config was found and updated |    * @returns Boolean indicating if the config was found and updated | ||||||
|    */ |    */ | ||||||
|   public setPathPattern( |   public setPathPattern( | ||||||
|     config: plugins.tsclass.network.IReverseProxyConfig,  |     config: ReverseProxyConfig, | ||||||
|     pathPattern: string |     pathPattern: string | ||||||
|   ): boolean { |   ): boolean { | ||||||
|     const exists = this.reverseProxyConfigs.includes(config); |     const exists = this.reverseProxyConfigs.includes(config); | ||||||
| @@ -3,7 +3,8 @@ | |||||||
|  */ |  */ | ||||||
|  |  | ||||||
| // Legacy exports (to maintain backward compatibility) | // Legacy exports (to maintain backward compatibility) | ||||||
| export * from './nfttablesproxy/classes.nftablesproxy.js'; | // Migrated to the new proxies structure | ||||||
|  | export * from './proxies/nftables-proxy/index.js'; | ||||||
| export * from './networkproxy/index.js'; | export * from './networkproxy/index.js'; | ||||||
| // Export port80handler elements selectively to avoid conflicts | // Export port80handler elements selectively to avoid conflicts | ||||||
| export { | export { | ||||||
| @@ -17,11 +18,13 @@ export { | |||||||
| export { Port80HandlerEvents } from './certificate/events/certificate-events.js'; | export { Port80HandlerEvents } from './certificate/events/certificate-events.js'; | ||||||
|  |  | ||||||
| export * from './redirect/classes.redirect.js'; | export * from './redirect/classes.redirect.js'; | ||||||
| export * from './smartproxy/classes.smartproxy.js'; | export * from './proxies/smart-proxy/index.js'; | ||||||
| // Original: export * from './smartproxy/classes.pp.snihandler.js' | // Original: export * from './smartproxy/classes.pp.snihandler.js' | ||||||
| // Now we export from the new module | // Now we export from the new module | ||||||
| export { SniHandler } from './tls/sni/sni-handler.js'; | export { SniHandler } from './tls/sni/sni-handler.js'; | ||||||
| export * from './smartproxy/classes.pp.interfaces.js'; | // Original: export * from './smartproxy/classes.pp.interfaces.js' | ||||||
|  | // Now we export from the new module | ||||||
|  | export * from './proxies/smart-proxy/models/interfaces.js'; | ||||||
|  |  | ||||||
| // Core types and utilities | // Core types and utilities | ||||||
| export * from './core/models/common-types.js'; | export * from './core/models/common-types.js'; | ||||||
|   | |||||||
| @@ -1,7 +1,3 @@ | |||||||
| // Re-export all components for easier imports | // Re-export all components from the new location | ||||||
| export * from './classes.np.types.js'; | // This file is for backward compatibility only | ||||||
| export * from './classes.np.certificatemanager.js'; | export * from '../proxies/network-proxy/index.js'; | ||||||
| export * from './classes.np.connectionpool.js'; |  | ||||||
| export * from './classes.np.requesthandler.js'; |  | ||||||
| export * from './classes.np.websockethandler.js'; |  | ||||||
| export * from './classes.np.networkproxy.js'; |  | ||||||
|   | |||||||
| @@ -1,27 +1,27 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import * as fs from 'fs'; | import * as fs from 'fs'; | ||||||
| import * as path from 'path'; | import * as path from 'path'; | ||||||
| import { fileURLToPath } from 'url'; | import { fileURLToPath } from 'url'; | ||||||
| import { type INetworkProxyOptions, type ICertificateEntry, type ILogger, createLogger } from './classes.np.types.js'; | import { type NetworkProxyOptions, type CertificateEntry, type Logger, createLogger } from './models/types.js'; | ||||||
| import { Port80Handler } from '../port80handler/classes.port80handler.js'; | import { Port80Handler } from '../../http/port80/port80-handler.js'; | ||||||
| import { Port80HandlerEvents } from '../common/types.js'; | import { CertificateEvents } from '../../certificate/events/certificate-events.js'; | ||||||
| import { buildPort80Handler } from '../certificate/acme/acme-factory.js'; | import { buildPort80Handler } from '../../certificate/acme/acme-factory.js'; | ||||||
| import { subscribeToPort80Handler } from '../common/eventUtils.js'; | import { subscribeToPort80Handler } from '../../core/utils/event-utils.js'; | ||||||
| import type { IDomainOptions } from '../common/types.js'; | import type { DomainOptions } from '../../certificate/models/certificate-types.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Manages SSL certificates for NetworkProxy including ACME integration |  * Manages SSL certificates for NetworkProxy including ACME integration | ||||||
|  */ |  */ | ||||||
| export class CertificateManager { | export class CertificateManager { | ||||||
|   private defaultCertificates: { key: string; cert: string }; |   private defaultCertificates: { key: string; cert: string }; | ||||||
|   private certificateCache: Map<string, ICertificateEntry> = new Map(); |   private certificateCache: Map<string, CertificateEntry> = new Map(); | ||||||
|   private port80Handler: Port80Handler | null = null; |   private port80Handler: Port80Handler | null = null; | ||||||
|   private externalPort80Handler: boolean = false; |   private externalPort80Handler: boolean = false; | ||||||
|   private certificateStoreDir: string; |   private certificateStoreDir: string; | ||||||
|   private logger: ILogger; |   private logger: Logger; | ||||||
|   private httpsServer: plugins.https.Server | null = null; |   private httpsServer: plugins.https.Server | null = null; | ||||||
|    | 
 | ||||||
|   constructor(private options: INetworkProxyOptions) { |   constructor(private options: NetworkProxyOptions) { | ||||||
|     this.certificateStoreDir = path.resolve(options.acme?.certificateStore || './certs'); |     this.certificateStoreDir = path.resolve(options.acme?.certificateStore || './certs'); | ||||||
|     this.logger = createLogger(options.logLevel || 'info'); |     this.logger = createLogger(options.logLevel || 'info'); | ||||||
|      |      | ||||||
| @@ -94,10 +94,10 @@ export class CertificateManager { | |||||||
|       // Clean up existing handler if needed
 |       // Clean up existing handler if needed
 | ||||||
|       if (this.port80Handler !== handler) { |       if (this.port80Handler !== handler) { | ||||||
|         // Unregister event handlers to avoid memory leaks
 |         // Unregister event handlers to avoid memory leaks
 | ||||||
|         this.port80Handler.removeAllListeners(Port80HandlerEvents.CERTIFICATE_ISSUED); |         this.port80Handler.removeAllListeners(CertificateEvents.CERTIFICATE_ISSUED); | ||||||
|         this.port80Handler.removeAllListeners(Port80HandlerEvents.CERTIFICATE_RENEWED); |         this.port80Handler.removeAllListeners(CertificateEvents.CERTIFICATE_RENEWED); | ||||||
|         this.port80Handler.removeAllListeners(Port80HandlerEvents.CERTIFICATE_FAILED); |         this.port80Handler.removeAllListeners(CertificateEvents.CERTIFICATE_FAILED); | ||||||
|         this.port80Handler.removeAllListeners(Port80HandlerEvents.CERTIFICATE_EXPIRING); |         this.port80Handler.removeAllListeners(CertificateEvents.CERTIFICATE_EXPIRING); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|      |      | ||||||
| @@ -220,7 +220,7 @@ export class CertificateManager { | |||||||
|         this.logger.info(`No certificate found for ${domain}, registering for issuance`); |         this.logger.info(`No certificate found for ${domain}, registering for issuance`); | ||||||
|          |          | ||||||
|         // Register with new domain options format
 |         // Register with new domain options format
 | ||||||
|         const domainOptions: IDomainOptions = { |         const domainOptions: DomainOptions = { | ||||||
|           domainName: domain, |           domainName: domain, | ||||||
|           sslRedirect: true, |           sslRedirect: true, | ||||||
|           acmeMaintenance: true |           acmeMaintenance: true | ||||||
| @@ -273,7 +273,7 @@ export class CertificateManager { | |||||||
|   /** |   /** | ||||||
|    * Gets a certificate for a domain |    * Gets a certificate for a domain | ||||||
|    */ |    */ | ||||||
|   public getCertificate(domain: string): ICertificateEntry | undefined { |   public getCertificate(domain: string): CertificateEntry | undefined { | ||||||
|     return this.certificateCache.get(domain); |     return this.certificateCache.get(domain); | ||||||
|   } |   } | ||||||
|    |    | ||||||
| @@ -299,7 +299,7 @@ export class CertificateManager { | |||||||
|      |      | ||||||
|     try { |     try { | ||||||
|       // Use the new domain options format
 |       // Use the new domain options format
 | ||||||
|       const domainOptions: IDomainOptions = { |       const domainOptions: DomainOptions = { | ||||||
|         domainName: domain, |         domainName: domain, | ||||||
|         sslRedirect: true, |         sslRedirect: true, | ||||||
|         acmeMaintenance: true |         acmeMaintenance: true | ||||||
| @@ -340,7 +340,7 @@ export class CertificateManager { | |||||||
|       } |       } | ||||||
|        |        | ||||||
|       // Register the domain for certificate issuance with new domain options format
 |       // Register the domain for certificate issuance with new domain options format
 | ||||||
|       const domainOptions: IDomainOptions = { |       const domainOptions: DomainOptions = { | ||||||
|         domainName: domain, |         domainName: domain, | ||||||
|         sslRedirect: true, |         sslRedirect: true, | ||||||
|         acmeMaintenance: true |         acmeMaintenance: true | ||||||
| @@ -1,15 +1,15 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import { type INetworkProxyOptions, type IConnectionEntry, type ILogger, createLogger } from './classes.np.types.js'; | import { type NetworkProxyOptions, type ConnectionEntry, type Logger, createLogger } from './models/types.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Manages a pool of backend connections for efficient reuse |  * Manages a pool of backend connections for efficient reuse | ||||||
|  */ |  */ | ||||||
| export class ConnectionPool { | export class ConnectionPool { | ||||||
|   private connectionPool: Map<string, Array<IConnectionEntry>> = new Map(); |   private connectionPool: Map<string, Array<ConnectionEntry>> = new Map(); | ||||||
|   private roundRobinPositions: Map<string, number> = new Map(); |   private roundRobinPositions: Map<string, number> = new Map(); | ||||||
|   private logger: ILogger; |   private logger: Logger; | ||||||
|    | 
 | ||||||
|   constructor(private options: INetworkProxyOptions) { |   constructor(private options: NetworkProxyOptions) { | ||||||
|     this.logger = createLogger(options.logLevel || 'info'); |     this.logger = createLogger(options.logLevel || 'info'); | ||||||
|   } |   } | ||||||
|    |    | ||||||
| @@ -4,5 +4,10 @@ | |||||||
| // Re-export models | // Re-export models | ||||||
| export * from './models/index.js'; | export * from './models/index.js'; | ||||||
|  |  | ||||||
| // Core NetworkProxy will be added later: | // Export NetworkProxy and supporting classes | ||||||
| // export { NetworkProxy } from './network-proxy.js'; | export { NetworkProxy } from './network-proxy.js'; | ||||||
|  | export { CertificateManager } from './certificate-manager.js'; | ||||||
|  | export { ConnectionPool } from './connection-pool.js'; | ||||||
|  | export { RequestHandler } from './request-handler.js'; | ||||||
|  | export type { IMetricsTracker, MetricsTracker } from './request-handler.js'; | ||||||
|  | export { WebSocketHandler } from './websocket-handler.js'; | ||||||
|   | |||||||
| @@ -1,11 +1,18 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import { type INetworkProxyOptions, type ILogger, createLogger, type IReverseProxyConfig } from './classes.np.types.js'; | import { | ||||||
| import { CertificateManager } from './classes.np.certificatemanager.js'; |   createLogger | ||||||
| import { ConnectionPool } from './classes.np.connectionpool.js'; | } from './models/types.js'; | ||||||
| import { RequestHandler, type IMetricsTracker } from './classes.np.requesthandler.js'; | import type { | ||||||
| import { WebSocketHandler } from './classes.np.websockethandler.js'; |   NetworkProxyOptions, | ||||||
| import { ProxyRouter } from '../classes.router.js'; |   Logger, | ||||||
| import { Port80Handler } from '../port80handler/classes.port80handler.js'; |   ReverseProxyConfig | ||||||
|  | } from './models/types.js'; | ||||||
|  | import { CertificateManager } from './certificate-manager.js'; | ||||||
|  | import { ConnectionPool } from './connection-pool.js'; | ||||||
|  | import { RequestHandler, type IMetricsTracker } from './request-handler.js'; | ||||||
|  | import { WebSocketHandler } from './websocket-handler.js'; | ||||||
|  | import { ProxyRouter } from '../../http/router/index.js'; | ||||||
|  | import { Port80Handler } from '../../http/port80/port80-handler.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * NetworkProxy provides a reverse proxy with TLS termination, WebSocket support, |  * NetworkProxy provides a reverse proxy with TLS termination, WebSocket support, | ||||||
| @@ -17,8 +24,8 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|     return {}; |     return {}; | ||||||
|   } |   } | ||||||
|   // Configuration
 |   // Configuration
 | ||||||
|   public options: INetworkProxyOptions; |   public options: NetworkProxyOptions; | ||||||
|   public proxyConfigs: IReverseProxyConfig[] = []; |   public proxyConfigs: ReverseProxyConfig[] = []; | ||||||
|    |    | ||||||
|   // Server instances (HTTP/2 with HTTP/1 fallback)
 |   // Server instances (HTTP/2 with HTTP/1 fallback)
 | ||||||
|   public httpsServer: any; |   public httpsServer: any; | ||||||
| @@ -38,7 +45,7 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|   public requestsServed: number = 0; |   public requestsServed: number = 0; | ||||||
|   public failedRequests: number = 0; |   public failedRequests: number = 0; | ||||||
|    |    | ||||||
|   // Tracking for PortProxy integration
 |   // Tracking for SmartProxy integration
 | ||||||
|   private portProxyConnections: number = 0; |   private portProxyConnections: number = 0; | ||||||
|   private tlsTerminatedConnections: number = 0; |   private tlsTerminatedConnections: number = 0; | ||||||
|    |    | ||||||
| @@ -47,12 +54,12 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|   private connectionPoolCleanupInterval: NodeJS.Timeout; |   private connectionPoolCleanupInterval: NodeJS.Timeout; | ||||||
|    |    | ||||||
|   // Logger
 |   // Logger
 | ||||||
|   private logger: ILogger; |   private logger: Logger; | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Creates a new NetworkProxy instance |    * Creates a new NetworkProxy instance | ||||||
|    */ |    */ | ||||||
|   constructor(optionsArg: INetworkProxyOptions) { |   constructor(optionsArg: NetworkProxyOptions) { | ||||||
|     // Set default options
 |     // Set default options
 | ||||||
|     this.options = { |     this.options = { | ||||||
|       port: optionsArg.port, |       port: optionsArg.port, | ||||||
| @@ -66,7 +73,7 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|         allowHeaders: 'Content-Type, Authorization', |         allowHeaders: 'Content-Type, Authorization', | ||||||
|         maxAge: 86400 |         maxAge: 86400 | ||||||
|       }, |       }, | ||||||
|       // Defaults for PortProxy integration
 |       // Defaults for SmartProxy integration
 | ||||||
|       connectionPoolSize: optionsArg.connectionPoolSize || 50, |       connectionPoolSize: optionsArg.connectionPoolSize || 50, | ||||||
|       portProxyIntegration: optionsArg.portProxyIntegration || false, |       portProxyIntegration: optionsArg.portProxyIntegration || false, | ||||||
|       useExternalPort80Handler: optionsArg.useExternalPort80Handler || false, |       useExternalPort80Handler: optionsArg.useExternalPort80Handler || false, | ||||||
| @@ -114,7 +121,7 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Returns the port number this NetworkProxy is listening on |    * Returns the port number this NetworkProxy is listening on | ||||||
|    * Useful for PortProxy to determine where to forward connections |    * Useful for SmartProxy to determine where to forward connections | ||||||
|    */ |    */ | ||||||
|   public getListeningPort(): number { |   public getListeningPort(): number { | ||||||
|     return this.options.port; |     return this.options.port; | ||||||
| @@ -152,7 +159,7 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Returns current server metrics |    * Returns current server metrics | ||||||
|    * Useful for PortProxy to determine which NetworkProxy to use for load balancing |    * Useful for SmartProxy to determine which NetworkProxy to use for load balancing | ||||||
|    */ |    */ | ||||||
|   public getMetrics(): any { |   public getMetrics(): any { | ||||||
|     return { |     return { | ||||||
| @@ -247,14 +254,14 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|       this.socketMap.add(connection); |       this.socketMap.add(connection); | ||||||
|       this.connectedClients = this.socketMap.getArray().length; |       this.connectedClients = this.socketMap.getArray().length; | ||||||
|        |        | ||||||
|       // Check for connection from PortProxy by inspecting the source port
 |       // Check for connection from SmartProxy by inspecting the source port
 | ||||||
|       const localPort = connection.localPort || 0; |       const localPort = connection.localPort || 0; | ||||||
|       const remotePort = connection.remotePort || 0; |       const remotePort = connection.remotePort || 0; | ||||||
|        |        | ||||||
|       // If this connection is from a PortProxy (usually indicated by it coming from localhost)
 |       // If this connection is from a SmartProxy (usually indicated by it coming from localhost)
 | ||||||
|       if (this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1')) { |       if (this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1')) { | ||||||
|         this.portProxyConnections++; |         this.portProxyConnections++; | ||||||
|         this.logger.debug(`New connection from PortProxy (local: ${localPort}, remote: ${remotePort})`); |         this.logger.debug(`New connection from SmartProxy (local: ${localPort}, remote: ${remotePort})`); | ||||||
|       } else { |       } else { | ||||||
|         this.logger.debug(`New direct connection (local: ${localPort}, remote: ${remotePort})`); |         this.logger.debug(`New direct connection (local: ${localPort}, remote: ${remotePort})`); | ||||||
|       } |       } | ||||||
| @@ -265,7 +272,7 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|           this.socketMap.remove(connection); |           this.socketMap.remove(connection); | ||||||
|           this.connectedClients = this.socketMap.getArray().length; |           this.connectedClients = this.socketMap.getArray().length; | ||||||
|            |            | ||||||
|           // If this was a PortProxy connection, decrement the counter
 |           // If this was a SmartProxy connection, decrement the counter
 | ||||||
|           if (this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1')) { |           if (this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1')) { | ||||||
|             this.portProxyConnections--; |             this.portProxyConnections--; | ||||||
|           } |           } | ||||||
| @@ -321,7 +328,7 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|    * Updates proxy configurations |    * Updates proxy configurations | ||||||
|    */ |    */ | ||||||
|   public async updateProxyConfigs( |   public async updateProxyConfigs( | ||||||
|     proxyConfigsArg: plugins.tsclass.network.IReverseProxyConfig[] |     proxyConfigsArg: ReverseProxyConfig[] | ||||||
|   ): Promise<void> { |   ): Promise<void> { | ||||||
|     this.logger.info(`Updating proxy configurations (${proxyConfigsArg.length} configs)`); |     this.logger.info(`Updating proxy configurations (${proxyConfigsArg.length} configs)`); | ||||||
|      |      | ||||||
| @@ -366,20 +373,20 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Converts PortProxy domain configurations to NetworkProxy configs |    * Converts SmartProxy domain configurations to NetworkProxy configs | ||||||
|    * @param domainConfigs PortProxy domain configs |    * @param domainConfigs SmartProxy domain configs | ||||||
|    * @param sslKeyPair Default SSL key pair to use if not specified |    * @param sslKeyPair Default SSL key pair to use if not specified | ||||||
|    * @returns Array of NetworkProxy configs |    * @returns Array of NetworkProxy configs | ||||||
|    */ |    */ | ||||||
|   public convertPortProxyConfigs( |   public convertSmartProxyConfigs( | ||||||
|     domainConfigs: Array<{ |     domainConfigs: Array<{ | ||||||
|       domains: string[]; |       domains: string[]; | ||||||
|       targetIPs?: string[]; |       targetIPs?: string[]; | ||||||
|       allowedIPs?: string[]; |       allowedIPs?: string[]; | ||||||
|     }>, |     }>, | ||||||
|     sslKeyPair?: { key: string; cert: string } |     sslKeyPair?: { key: string; cert: string } | ||||||
|   ): plugins.tsclass.network.IReverseProxyConfig[] { |   ): ReverseProxyConfig[] { | ||||||
|     const proxyConfigs: plugins.tsclass.network.IReverseProxyConfig[] = []; |     const proxyConfigs: ReverseProxyConfig[] = []; | ||||||
|      |      | ||||||
|     // Use default certificates if not provided
 |     // Use default certificates if not provided
 | ||||||
|     const defaultCerts = this.certificateManager.getDefaultCertificates(); |     const defaultCerts = this.certificateManager.getDefaultCertificates(); | ||||||
| @@ -404,7 +411,7 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     this.logger.info(`Converted ${domainConfigs.length} PortProxy configs to ${proxyConfigs.length} NetworkProxy configs`); |     this.logger.info(`Converted ${domainConfigs.length} SmartProxy configs to ${proxyConfigs.length} NetworkProxy configs`); | ||||||
|     return proxyConfigs; |     return proxyConfigs; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @@ -471,7 +478,7 @@ export class NetworkProxy implements IMetricsTracker { | |||||||
|   /** |   /** | ||||||
|    * Gets all proxy configurations currently in use |    * Gets all proxy configurations currently in use | ||||||
|    */ |    */ | ||||||
|   public getProxyConfigs(): plugins.tsclass.network.IReverseProxyConfig[] { |   public getProxyConfigs(): ReverseProxyConfig[] { | ||||||
|     return [...this.proxyConfigs]; |     return [...this.proxyConfigs]; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import { type INetworkProxyOptions, type ILogger, createLogger, type IReverseProxyConfig } from './classes.np.types.js'; | import { type NetworkProxyOptions, type Logger, createLogger, type ReverseProxyConfig } from './models/types.js'; | ||||||
| import { ConnectionPool } from './classes.np.connectionpool.js'; | import { ConnectionPool } from './connection-pool.js'; | ||||||
| import { ProxyRouter } from '../classes.router.js'; | import { ProxyRouter } from '../../http/router/index.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Interface for tracking metrics |  * Interface for tracking metrics | ||||||
| @@ -11,18 +11,21 @@ export interface IMetricsTracker { | |||||||
|   incrementFailedRequests(): void; |   incrementFailedRequests(): void; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Backward compatibility
 | ||||||
|  | export type MetricsTracker = IMetricsTracker; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Handles HTTP request processing and proxying |  * Handles HTTP request processing and proxying | ||||||
|  */ |  */ | ||||||
| export class RequestHandler { | export class RequestHandler { | ||||||
|   private defaultHeaders: { [key: string]: string } = {}; |   private defaultHeaders: { [key: string]: string } = {}; | ||||||
|   private logger: ILogger; |   private logger: Logger; | ||||||
|   private metricsTracker: IMetricsTracker | null = null; |   private metricsTracker: IMetricsTracker | null = null; | ||||||
|   // HTTP/2 client sessions for backend proxying
 |   // HTTP/2 client sessions for backend proxying
 | ||||||
|   private h2Sessions: Map<string, plugins.http2.ClientHttp2Session> = new Map(); |   private h2Sessions: Map<string, plugins.http2.ClientHttp2Session> = new Map(); | ||||||
|    | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private options: INetworkProxyOptions, |     private options: NetworkProxyOptions, | ||||||
|     private connectionPool: ConnectionPool, |     private connectionPool: ConnectionPool, | ||||||
|     private router: ProxyRouter |     private router: ProxyRouter | ||||||
|   ) { |   ) { | ||||||
| @@ -134,7 +137,7 @@ export class RequestHandler { | |||||||
|     this.applyDefaultHeaders(res); |     this.applyDefaultHeaders(res); | ||||||
|      |      | ||||||
|     // Determine routing configuration
 |     // Determine routing configuration
 | ||||||
|     let proxyConfig: IReverseProxyConfig | undefined; |     let proxyConfig: ReverseProxyConfig | undefined; | ||||||
|     try { |     try { | ||||||
|       proxyConfig = this.router.routeReq(req); |       proxyConfig = this.router.routeReq(req); | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
| @@ -232,7 +235,7 @@ export class RequestHandler { | |||||||
|       // Remove host header to avoid issues with virtual hosts on target server
 |       // Remove host header to avoid issues with virtual hosts on target server
 | ||||||
|       // The host header should match the target server's expected hostname
 |       // The host header should match the target server's expected hostname
 | ||||||
|       if (options.headers && options.headers.host) { |       if (options.headers && options.headers.host) { | ||||||
|         if ((proxyConfig as IReverseProxyConfig).rewriteHostHeader) { |         if ((proxyConfig as ReverseProxyConfig).rewriteHostHeader) { | ||||||
|           options.headers.host = `${destination.host}:${destination.port}`; |           options.headers.host = `${destination.host}:${destination.port}`; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| @@ -433,7 +436,9 @@ export class RequestHandler { | |||||||
|           // Map status and headers back to HTTP/2
 |           // Map status and headers back to HTTP/2
 | ||||||
|           const responseHeaders: Record<string, number|string|string[]> = {}; |           const responseHeaders: Record<string, number|string|string[]> = {}; | ||||||
|           for (const [k, v] of Object.entries(proxyRes.headers)) { |           for (const [k, v] of Object.entries(proxyRes.headers)) { | ||||||
|             if (v !== undefined) responseHeaders[k] = v; |             if (v !== undefined) { | ||||||
|  |               responseHeaders[k] = v as string | string[]; | ||||||
|  |             } | ||||||
|           } |           } | ||||||
|           stream.respond({ ':status': proxyRes.statusCode || 500, ...responseHeaders }); |           stream.respond({ ':status': proxyRes.statusCode || 500, ...responseHeaders }); | ||||||
|           proxyRes.pipe(stream); |           proxyRes.pipe(stream); | ||||||
| @@ -1,7 +1,7 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import { type INetworkProxyOptions, type IWebSocketWithHeartbeat, type ILogger, createLogger, type IReverseProxyConfig } from './classes.np.types.js'; | import { type NetworkProxyOptions, type WebSocketWithHeartbeat, type Logger, createLogger, type ReverseProxyConfig } from './models/types.js'; | ||||||
| import { ConnectionPool } from './classes.np.connectionpool.js'; | import { ConnectionPool } from './connection-pool.js'; | ||||||
| import { ProxyRouter } from '../classes.router.js'; | import { ProxyRouter } from '../../http/router/index.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handles WebSocket connections and proxying |  * Handles WebSocket connections and proxying | ||||||
| @@ -9,10 +9,10 @@ import { ProxyRouter } from '../classes.router.js'; | |||||||
| export class WebSocketHandler { | export class WebSocketHandler { | ||||||
|   private heartbeatInterval: NodeJS.Timeout | null = null; |   private heartbeatInterval: NodeJS.Timeout | null = null; | ||||||
|   private wsServer: plugins.ws.WebSocketServer | null = null; |   private wsServer: plugins.ws.WebSocketServer | null = null; | ||||||
|   private logger: ILogger; |   private logger: Logger; | ||||||
|    | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private options: INetworkProxyOptions, |     private options: NetworkProxyOptions, | ||||||
|     private connectionPool: ConnectionPool, |     private connectionPool: ConnectionPool, | ||||||
|     private router: ProxyRouter |     private router: ProxyRouter | ||||||
|   ) { |   ) { | ||||||
| @@ -30,7 +30,7 @@ export class WebSocketHandler { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // Handle WebSocket connections
 |     // Handle WebSocket connections
 | ||||||
|     this.wsServer.on('connection', (wsIncoming: IWebSocketWithHeartbeat, req: plugins.http.IncomingMessage) => { |     this.wsServer.on('connection', (wsIncoming: WebSocketWithHeartbeat, req: plugins.http.IncomingMessage) => { | ||||||
|       this.handleWebSocketConnection(wsIncoming, req); |       this.handleWebSocketConnection(wsIncoming, req); | ||||||
|     }); |     }); | ||||||
|      |      | ||||||
| @@ -58,7 +58,7 @@ export class WebSocketHandler { | |||||||
|       this.logger.debug(`WebSocket heartbeat check for ${this.wsServer.clients.size} clients`); |       this.logger.debug(`WebSocket heartbeat check for ${this.wsServer.clients.size} clients`); | ||||||
|        |        | ||||||
|       this.wsServer.clients.forEach((ws: plugins.wsDefault) => { |       this.wsServer.clients.forEach((ws: plugins.wsDefault) => { | ||||||
|         const wsWithHeartbeat = ws as IWebSocketWithHeartbeat; |         const wsWithHeartbeat = ws as WebSocketWithHeartbeat; | ||||||
|          |          | ||||||
|         if (wsWithHeartbeat.isAlive === false) { |         if (wsWithHeartbeat.isAlive === false) { | ||||||
|           this.logger.debug('Terminating inactive WebSocket connection'); |           this.logger.debug('Terminating inactive WebSocket connection'); | ||||||
| @@ -79,7 +79,7 @@ export class WebSocketHandler { | |||||||
|   /** |   /** | ||||||
|    * Handle a new WebSocket connection |    * Handle a new WebSocket connection | ||||||
|    */ |    */ | ||||||
|   private handleWebSocketConnection(wsIncoming: IWebSocketWithHeartbeat, req: plugins.http.IncomingMessage): void { |   private handleWebSocketConnection(wsIncoming: WebSocketWithHeartbeat, req: plugins.http.IncomingMessage): void { | ||||||
|     try { |     try { | ||||||
|       // Initialize heartbeat tracking
 |       // Initialize heartbeat tracking
 | ||||||
|       wsIncoming.isAlive = true; |       wsIncoming.isAlive = true; | ||||||
| @@ -127,7 +127,7 @@ export class WebSocketHandler { | |||||||
|       } |       } | ||||||
|        |        | ||||||
|       // Override host header if needed
 |       // Override host header if needed
 | ||||||
|       if ((proxyConfig as IReverseProxyConfig).rewriteHostHeader) { |       if ((proxyConfig as ReverseProxyConfig).rewriteHostHeader) { | ||||||
|         headers['host'] = `${destination.host}:${destination.port}`; |         headers['host'] = `${destination.host}:${destination.port}`; | ||||||
|       } |       } | ||||||
|        |        | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| /** | /** | ||||||
|  * NfTablesProxy implementation |  * NfTablesProxy implementation | ||||||
|  */ |  */ | ||||||
| // Core NfTablesProxy will be added later: | export * from './nftables-proxy.js'; | ||||||
| // export { NfTablesProxy } from './nftables-proxy.js'; | export * from './models/index.js'; | ||||||
|   | |||||||
							
								
								
									
										30
									
								
								ts/proxies/nftables-proxy/models/errors.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								ts/proxies/nftables-proxy/models/errors.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | /** | ||||||
|  |  * Custom error classes for better error handling | ||||||
|  |  */ | ||||||
|  | export class NftBaseError extends Error { | ||||||
|  |   constructor(message: string) { | ||||||
|  |     super(message); | ||||||
|  |     this.name = 'NftBaseError'; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class NftValidationError extends NftBaseError { | ||||||
|  |   constructor(message: string) { | ||||||
|  |     super(message); | ||||||
|  |     this.name = 'NftValidationError'; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class NftExecutionError extends NftBaseError { | ||||||
|  |   constructor(message: string) { | ||||||
|  |     super(message); | ||||||
|  |     this.name = 'NftExecutionError'; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export class NftResourceError extends NftBaseError { | ||||||
|  |   constructor(message: string) { | ||||||
|  |     super(message); | ||||||
|  |     this.name = 'NftResourceError'; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								ts/proxies/nftables-proxy/models/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								ts/proxies/nftables-proxy/models/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | /** | ||||||
|  |  * Export all models | ||||||
|  |  */ | ||||||
|  | export * from './interfaces.js'; | ||||||
|  | export * from './errors.js'; | ||||||
							
								
								
									
										94
									
								
								ts/proxies/nftables-proxy/models/interfaces.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								ts/proxies/nftables-proxy/models/interfaces.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | /** | ||||||
|  |  * Interfaces for NfTablesProxy | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Represents a port range for forwarding | ||||||
|  |  */ | ||||||
|  | export interface PortRange { | ||||||
|  |   from: number; | ||||||
|  |   to: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Legacy interface name for backward compatibility | ||||||
|  | export type IPortRange = PortRange; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Settings for NfTablesProxy. | ||||||
|  |  */ | ||||||
|  | export interface NfTableProxyOptions { | ||||||
|  |   // Basic settings | ||||||
|  |   fromPort: number | PortRange | Array<number | PortRange>; // Support single port, port range, or multiple ports/ranges | ||||||
|  |   toPort: number | PortRange | Array<number | PortRange>; | ||||||
|  |   toHost?: string; // Target host for proxying; defaults to 'localhost' | ||||||
|  |    | ||||||
|  |   // Advanced settings | ||||||
|  |   preserveSourceIP?: boolean; // If true, the original source IP is preserved | ||||||
|  |   deleteOnExit?: boolean;     // If true, clean up rules before process exit | ||||||
|  |   protocol?: 'tcp' | 'udp' | 'all'; // Protocol to forward, defaults to 'tcp' | ||||||
|  |   enableLogging?: boolean;    // Enable detailed logging | ||||||
|  |   ipv6Support?: boolean;      // Enable IPv6 support | ||||||
|  |   logFormat?: 'plain' | 'json'; // Format for logs | ||||||
|  |    | ||||||
|  |   // Source filtering | ||||||
|  |   allowedSourceIPs?: string[]; // If provided, only these IPs are allowed | ||||||
|  |   bannedSourceIPs?: string[];  // If provided, these IPs are blocked | ||||||
|  |   useIPSets?: boolean;        // Use nftables sets for efficient IP management | ||||||
|  |    | ||||||
|  |   // Rule management | ||||||
|  |   forceCleanSlate?: boolean;   // Clear all NfTablesProxy rules before starting | ||||||
|  |   tableName?: string;          // Custom table name (defaults to 'portproxy') | ||||||
|  |    | ||||||
|  |   // Connection management | ||||||
|  |   maxRetries?: number;        // Maximum number of retries for failed commands | ||||||
|  |   retryDelayMs?: number;      // Delay between retries in milliseconds | ||||||
|  |   useAdvancedNAT?: boolean;   // Use connection tracking for stateful NAT | ||||||
|  |    | ||||||
|  |   // Quality of Service | ||||||
|  |   qos?: { | ||||||
|  |     enabled: boolean; | ||||||
|  |     maxRate?: string;         // e.g. "10mbps" | ||||||
|  |     priority?: number;        // 1 (highest) to 10 (lowest) | ||||||
|  |     markConnections?: boolean; // Mark connections for easier management | ||||||
|  |   }; | ||||||
|  |    | ||||||
|  |   // Integration with PortProxy/NetworkProxy | ||||||
|  |   netProxyIntegration?: { | ||||||
|  |     enabled: boolean; | ||||||
|  |     redirectLocalhost?: boolean; // Redirect localhost traffic to NetworkProxy | ||||||
|  |     sslTerminationPort?: number; // Port where NetworkProxy handles SSL termination | ||||||
|  |   }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Legacy interface name for backward compatibility | ||||||
|  | export type INfTableProxySettings = NfTableProxyOptions; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Interface for status reporting | ||||||
|  |  */ | ||||||
|  | export interface NfTablesStatus { | ||||||
|  |   active: boolean; | ||||||
|  |   ruleCount: { | ||||||
|  |     total: number; | ||||||
|  |     added: number; | ||||||
|  |     verified: number; | ||||||
|  |   }; | ||||||
|  |   tablesConfigured: { family: string; tableName: string }[]; | ||||||
|  |   metrics: { | ||||||
|  |     forwardedConnections?: number; | ||||||
|  |     activeConnections?: number; | ||||||
|  |     bytesForwarded?: { | ||||||
|  |       sent: number; | ||||||
|  |       received: number; | ||||||
|  |     }; | ||||||
|  |   }; | ||||||
|  |   qosEnabled?: boolean; | ||||||
|  |   ipSetsConfigured?: { | ||||||
|  |     name: string; | ||||||
|  |     elementCount: number; | ||||||
|  |     type: string; | ||||||
|  |   }[]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Legacy interface name for backward compatibility | ||||||
|  | export type INfTablesStatus = NfTablesStatus; | ||||||
| @@ -3,95 +3,20 @@ import { promisify } from 'util'; | |||||||
| import * as fs from 'fs'; | import * as fs from 'fs'; | ||||||
| import * as path from 'path'; | import * as path from 'path'; | ||||||
| import * as os from 'os'; | import * as os from 'os'; | ||||||
|  | import { | ||||||
|  |   NftBaseError, | ||||||
|  |   NftValidationError, | ||||||
|  |   NftExecutionError, | ||||||
|  |   NftResourceError | ||||||
|  | } from './models/index.js'; | ||||||
|  | import type { | ||||||
|  |   PortRange, | ||||||
|  |   NfTableProxyOptions, | ||||||
|  |   NfTablesStatus | ||||||
|  | } from './models/index.js'; | ||||||
| 
 | 
 | ||||||
| const execAsync = promisify(exec); | const execAsync = promisify(exec); | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * Custom error classes for better error handling |  | ||||||
|  */ |  | ||||||
| export class NftBaseError extends Error { |  | ||||||
|   constructor(message: string) { |  | ||||||
|     super(message); |  | ||||||
|     this.name = 'NftBaseError'; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export class NftValidationError extends NftBaseError { |  | ||||||
|   constructor(message: string) { |  | ||||||
|     super(message); |  | ||||||
|     this.name = 'NftValidationError'; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export class NftExecutionError extends NftBaseError { |  | ||||||
|   constructor(message: string) { |  | ||||||
|     super(message); |  | ||||||
|     this.name = 'NftExecutionError'; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export class NftResourceError extends NftBaseError { |  | ||||||
|   constructor(message: string) { |  | ||||||
|     super(message); |  | ||||||
|     this.name = 'NftResourceError'; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Represents a port range for forwarding |  | ||||||
|  */ |  | ||||||
| export interface IPortRange { |  | ||||||
|   from: number; |  | ||||||
|   to: number; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** |  | ||||||
|  * Settings for NfTablesProxy. |  | ||||||
|  */ |  | ||||||
| export interface INfTableProxySettings { |  | ||||||
|   // Basic settings
 |  | ||||||
|   fromPort: number | IPortRange | Array<number | IPortRange>; // Support single port, port range, or multiple ports/ranges
 |  | ||||||
|   toPort: number | IPortRange | Array<number | IPortRange>; |  | ||||||
|   toHost?: string; // Target host for proxying; defaults to 'localhost'
 |  | ||||||
|    |  | ||||||
|   // Advanced settings
 |  | ||||||
|   preserveSourceIP?: boolean; // If true, the original source IP is preserved
 |  | ||||||
|   deleteOnExit?: boolean;     // If true, clean up rules before process exit
 |  | ||||||
|   protocol?: 'tcp' | 'udp' | 'all'; // Protocol to forward, defaults to 'tcp'
 |  | ||||||
|   enableLogging?: boolean;    // Enable detailed logging
 |  | ||||||
|   ipv6Support?: boolean;      // Enable IPv6 support
 |  | ||||||
|   logFormat?: 'plain' | 'json'; // Format for logs
 |  | ||||||
|    |  | ||||||
|   // Source filtering
 |  | ||||||
|   allowedSourceIPs?: string[]; // If provided, only these IPs are allowed
 |  | ||||||
|   bannedSourceIPs?: string[];  // If provided, these IPs are blocked
 |  | ||||||
|   useIPSets?: boolean;        // Use nftables sets for efficient IP management
 |  | ||||||
|    |  | ||||||
|   // Rule management
 |  | ||||||
|   forceCleanSlate?: boolean;   // Clear all NfTablesProxy rules before starting
 |  | ||||||
|   tableName?: string;          // Custom table name (defaults to 'portproxy')
 |  | ||||||
|    |  | ||||||
|   // Connection management
 |  | ||||||
|   maxRetries?: number;        // Maximum number of retries for failed commands
 |  | ||||||
|   retryDelayMs?: number;      // Delay between retries in milliseconds
 |  | ||||||
|   useAdvancedNAT?: boolean;   // Use connection tracking for stateful NAT
 |  | ||||||
|    |  | ||||||
|   // Quality of Service
 |  | ||||||
|   qos?: { |  | ||||||
|     enabled: boolean; |  | ||||||
|     maxRate?: string;         // e.g. "10mbps"
 |  | ||||||
|     priority?: number;        // 1 (highest) to 10 (lowest)
 |  | ||||||
|     markConnections?: boolean; // Mark connections for easier management
 |  | ||||||
|   }; |  | ||||||
|    |  | ||||||
|   // Integration with PortProxy/NetworkProxy
 |  | ||||||
|   netProxyIntegration?: { |  | ||||||
|     enabled: boolean; |  | ||||||
|     redirectLocalhost?: boolean; // Redirect localhost traffic to NetworkProxy
 |  | ||||||
|     sslTerminationPort?: number; // Port where NetworkProxy handles SSL termination
 |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Represents a rule added to nftables |  * Represents a rule added to nftables | ||||||
|  */ |  */ | ||||||
| @@ -105,40 +30,13 @@ interface NfTablesRule { | |||||||
|   verified?: boolean;    // Whether the rule has been verified as applied
 |   verified?: boolean;    // Whether the rule has been verified as applied
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /** |  | ||||||
|  * Interface for status reporting |  | ||||||
|  */ |  | ||||||
| export interface INfTablesStatus { |  | ||||||
|   active: boolean; |  | ||||||
|   ruleCount: { |  | ||||||
|     total: number; |  | ||||||
|     added: number; |  | ||||||
|     verified: number; |  | ||||||
|   }; |  | ||||||
|   tablesConfigured: { family: string; tableName: string }[]; |  | ||||||
|   metrics: { |  | ||||||
|     forwardedConnections?: number; |  | ||||||
|     activeConnections?: number; |  | ||||||
|     bytesForwarded?: { |  | ||||||
|       sent: number; |  | ||||||
|       received: number; |  | ||||||
|     }; |  | ||||||
|   }; |  | ||||||
|   qosEnabled?: boolean; |  | ||||||
|   ipSetsConfigured?: { |  | ||||||
|     name: string; |  | ||||||
|     elementCount: number; |  | ||||||
|     type: string; |  | ||||||
|   }[]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * NfTablesProxy sets up nftables NAT rules to forward TCP traffic. |  * NfTablesProxy sets up nftables NAT rules to forward TCP traffic. | ||||||
|  * Enhanced with multi-port support, IPv6, connection tracking, metrics, |  * Enhanced with multi-port support, IPv6, connection tracking, metrics, | ||||||
|  * and more advanced features. |  * and more advanced features. | ||||||
|  */ |  */ | ||||||
| export class NfTablesProxy { | export class NfTablesProxy { | ||||||
|   public settings: INfTableProxySettings; |   public settings: NfTableProxyOptions; | ||||||
|   private rules: NfTablesRule[] = []; |   private rules: NfTablesRule[] = []; | ||||||
|   private ipSets: Map<string, string[]> = new Map(); // Store IP sets for tracking
 |   private ipSets: Map<string, string[]> = new Map(); // Store IP sets for tracking
 | ||||||
|   private ruleTag: string; |   private ruleTag: string; | ||||||
| @@ -146,7 +44,7 @@ export class NfTablesProxy { | |||||||
|   private tempFilePath: string; |   private tempFilePath: string; | ||||||
|   private static NFT_CMD = 'nft'; |   private static NFT_CMD = 'nft'; | ||||||
| 
 | 
 | ||||||
|   constructor(settings: INfTableProxySettings) { |   constructor(settings: NfTableProxyOptions) { | ||||||
|     // Validate inputs to prevent command injection
 |     // Validate inputs to prevent command injection
 | ||||||
|     this.validateSettings(settings); |     this.validateSettings(settings); | ||||||
|      |      | ||||||
| @@ -199,9 +97,9 @@ export class NfTablesProxy { | |||||||
|   /** |   /** | ||||||
|    * Validates settings to prevent command injection and ensure valid values |    * Validates settings to prevent command injection and ensure valid values | ||||||
|    */ |    */ | ||||||
|   private validateSettings(settings: INfTableProxySettings): void { |   private validateSettings(settings: NfTableProxyOptions): void { | ||||||
|     // Validate port numbers
 |     // Validate port numbers
 | ||||||
|     const validatePorts = (port: number | IPortRange | Array<number | IPortRange>) => { |     const validatePorts = (port: number | PortRange | Array<number | PortRange>) => { | ||||||
|       if (Array.isArray(port)) { |       if (Array.isArray(port)) { | ||||||
|         port.forEach(p => validatePorts(p)); |         port.forEach(p => validatePorts(p)); | ||||||
|         return; |         return; | ||||||
| @@ -275,8 +173,8 @@ export class NfTablesProxy { | |||||||
|   /** |   /** | ||||||
|    * Normalizes port specifications into an array of port ranges |    * Normalizes port specifications into an array of port ranges | ||||||
|    */ |    */ | ||||||
|   private normalizePortSpec(portSpec: number | IPortRange | Array<number | IPortRange>): IPortRange[] { |   private normalizePortSpec(portSpec: number | PortRange | Array<number | PortRange>): PortRange[] { | ||||||
|     const result: IPortRange[] = []; |     const result: PortRange[] = []; | ||||||
|      |      | ||||||
|     if (Array.isArray(portSpec)) { |     if (Array.isArray(portSpec)) { | ||||||
|       // If it's an array, process each element
 |       // If it's an array, process each element
 | ||||||
| @@ -687,7 +585,7 @@ export class NfTablesProxy { | |||||||
|   /** |   /** | ||||||
|    * Gets a comma-separated list of all ports from a port specification |    * Gets a comma-separated list of all ports from a port specification | ||||||
|    */ |    */ | ||||||
|   private getAllPorts(portSpec: number | IPortRange | Array<number | IPortRange>): string { |   private getAllPorts(portSpec: number | PortRange | Array<number | PortRange>): string { | ||||||
|     const portRanges = this.normalizePortSpec(portSpec); |     const portRanges = this.normalizePortSpec(portSpec); | ||||||
|     const ports: string[] = []; |     const ports: string[] = []; | ||||||
|      |      | ||||||
| @@ -842,8 +740,8 @@ export class NfTablesProxy { | |||||||
|     family: string, |     family: string, | ||||||
|     preroutingChain: string, |     preroutingChain: string, | ||||||
|     postroutingChain: string, |     postroutingChain: string, | ||||||
|     fromPortRanges: IPortRange[], |     fromPortRanges: PortRange[], | ||||||
|     toPortRange: IPortRange |     toPortRange: PortRange | ||||||
|   ): Promise<boolean> { |   ): Promise<boolean> { | ||||||
|     try { |     try { | ||||||
|       let rulesetContent = ''; |       let rulesetContent = ''; | ||||||
| @@ -958,8 +856,8 @@ export class NfTablesProxy { | |||||||
|     family: string, |     family: string, | ||||||
|     preroutingChain: string, |     preroutingChain: string, | ||||||
|     postroutingChain: string, |     postroutingChain: string, | ||||||
|     fromPortRanges: IPortRange[], |     fromPortRanges: PortRange[], | ||||||
|     toPortRanges: IPortRange[] |     toPortRanges: PortRange[] | ||||||
|   ): Promise<boolean> { |   ): Promise<boolean> { | ||||||
|     try { |     try { | ||||||
|       let rulesetContent = ''; |       let rulesetContent = ''; | ||||||
| @@ -1410,8 +1308,8 @@ export class NfTablesProxy { | |||||||
|   /** |   /** | ||||||
|    * Get detailed status about the current state of the proxy |    * Get detailed status about the current state of the proxy | ||||||
|    */ |    */ | ||||||
|   public async getStatus(): Promise<INfTablesStatus> { |   public async getStatus(): Promise<NfTablesStatus> { | ||||||
|     const result: INfTablesStatus = { |     const result: NfTablesStatus = { | ||||||
|       active: this.rules.some(r => r.added), |       active: this.rules.some(r => r.added), | ||||||
|       ruleCount: { |       ruleCount: { | ||||||
|         total: this.rules.length, |         total: this.rules.length, | ||||||
| @@ -1,25 +1,25 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import type { | import type { | ||||||
|   IConnectionRecord, |   ConnectionRecord, | ||||||
|   IDomainConfig, |   DomainConfig, | ||||||
|   ISmartProxyOptions, |   SmartProxyOptions, | ||||||
| } from './classes.pp.interfaces.js'; | } from './models/interfaces.js'; | ||||||
| import { ConnectionManager } from './classes.pp.connectionmanager.js'; | import { ConnectionManager } from './connection-manager.js'; | ||||||
| import { SecurityManager } from './classes.pp.securitymanager.js'; | import { SecurityManager } from './security-manager.js'; | ||||||
| import { DomainConfigManager } from './classes.pp.domainconfigmanager.js'; | import { DomainConfigManager } from './domain-config-manager.js'; | ||||||
| import { TlsManager } from './classes.pp.tlsmanager.js'; | import { TlsManager } from './tls-manager.js'; | ||||||
| import { NetworkProxyBridge } from './classes.pp.networkproxybridge.js'; | import { NetworkProxyBridge } from './network-proxy-bridge.js'; | ||||||
| import { TimeoutManager } from './classes.pp.timeoutmanager.js'; | import { TimeoutManager } from './timeout-manager.js'; | ||||||
| import { PortRangeManager } from './classes.pp.portrangemanager.js'; | import { PortRangeManager } from './port-range-manager.js'; | ||||||
| import type { IForwardingHandler } from './types/forwarding.types.js'; | import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.js'; | ||||||
| import type { ForwardingType } from './types/forwarding.types.js'; | import type { ForwardingType } from '../../forwarding/config/forwarding-types.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handles new connection processing and setup logic |  * Handles new connection processing and setup logic | ||||||
|  */ |  */ | ||||||
| export class ConnectionHandler { | export class ConnectionHandler { | ||||||
|   constructor( |   constructor( | ||||||
|     private settings: ISmartProxyOptions, |     private settings: SmartProxyOptions, | ||||||
|     private connectionManager: ConnectionManager, |     private connectionManager: ConnectionManager, | ||||||
|     private securityManager: SecurityManager, |     private securityManager: SecurityManager, | ||||||
|     private domainConfigManager: DomainConfigManager, |     private domainConfigManager: DomainConfigManager, | ||||||
| @@ -102,7 +102,7 @@ export class ConnectionHandler { | |||||||
|    */ |    */ | ||||||
|   private handleNetworkProxyConnection( |   private handleNetworkProxyConnection( | ||||||
|     socket: plugins.net.Socket, |     socket: plugins.net.Socket, | ||||||
|     record: IConnectionRecord |     record: ConnectionRecord | ||||||
|   ): void { |   ): void { | ||||||
|     const connectionId = record.id; |     const connectionId = record.id; | ||||||
|     let initialDataReceived = false; |     let initialDataReceived = false; | ||||||
| @@ -307,7 +307,7 @@ export class ConnectionHandler { | |||||||
|   /** |   /** | ||||||
|    * Handle a standard (non-NetworkProxy) connection |    * Handle a standard (non-NetworkProxy) connection | ||||||
|    */ |    */ | ||||||
|   private handleStandardConnection(socket: plugins.net.Socket, record: IConnectionRecord): void { |   private handleStandardConnection(socket: plugins.net.Socket, record: ConnectionRecord): void { | ||||||
|     const connectionId = record.id; |     const connectionId = record.id; | ||||||
|     const localPort = record.localPort; |     const localPort = record.localPort; | ||||||
| 
 | 
 | ||||||
| @@ -382,7 +382,7 @@ export class ConnectionHandler { | |||||||
|     const setupConnection = ( |     const setupConnection = ( | ||||||
|       serverName: string, |       serverName: string, | ||||||
|       initialChunk?: Buffer, |       initialChunk?: Buffer, | ||||||
|       forcedDomain?: IDomainConfig, |       forcedDomain?: DomainConfig, | ||||||
|       overridePort?: number |       overridePort?: number | ||||||
|     ) => { |     ) => { | ||||||
|       // Clear the initial timeout since we've received data
 |       // Clear the initial timeout since we've received data
 | ||||||
| @@ -730,8 +730,8 @@ export class ConnectionHandler { | |||||||
|    */ |    */ | ||||||
|   private setupDirectConnection( |   private setupDirectConnection( | ||||||
|     socket: plugins.net.Socket, |     socket: plugins.net.Socket, | ||||||
|     record: IConnectionRecord, |     record: ConnectionRecord, | ||||||
|     domainConfig?: IDomainConfig, |     domainConfig?: DomainConfig, | ||||||
|     serverName?: string, |     serverName?: string, | ||||||
|     initialChunk?: Buffer, |     initialChunk?: Buffer, | ||||||
|     overridePort?: number |     overridePort?: number | ||||||
| @@ -1,20 +1,20 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import type { IConnectionRecord, ISmartProxyOptions } from './classes.pp.interfaces.js'; | import type { ConnectionRecord, SmartProxyOptions } from './models/interfaces.js'; | ||||||
| import { SecurityManager } from './classes.pp.securitymanager.js'; | import { SecurityManager } from './security-manager.js'; | ||||||
| import { TimeoutManager } from './classes.pp.timeoutmanager.js'; | import { TimeoutManager } from './timeout-manager.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Manages connection lifecycle, tracking, and cleanup |  * Manages connection lifecycle, tracking, and cleanup | ||||||
|  */ |  */ | ||||||
| export class ConnectionManager { | export class ConnectionManager { | ||||||
|   private connectionRecords: Map<string, IConnectionRecord> = new Map(); |   private connectionRecords: Map<string, ConnectionRecord> = new Map(); | ||||||
|   private terminationStats: { |   private terminationStats: { | ||||||
|     incoming: Record<string, number>; |     incoming: Record<string, number>; | ||||||
|     outgoing: Record<string, number>; |     outgoing: Record<string, number>; | ||||||
|   } = { incoming: {}, outgoing: {} }; |   } = { incoming: {}, outgoing: {} }; | ||||||
|    | 
 | ||||||
|   constructor( |   constructor( | ||||||
|     private settings: ISmartProxyOptions, |     private settings: SmartProxyOptions, | ||||||
|     private securityManager: SecurityManager, |     private securityManager: SecurityManager, | ||||||
|     private timeoutManager: TimeoutManager |     private timeoutManager: TimeoutManager | ||||||
|   ) {} |   ) {} | ||||||
| @@ -30,12 +30,12 @@ export class ConnectionManager { | |||||||
|   /** |   /** | ||||||
|    * Create and track a new connection |    * Create and track a new connection | ||||||
|    */ |    */ | ||||||
|   public createConnection(socket: plugins.net.Socket): IConnectionRecord { |   public createConnection(socket: plugins.net.Socket): ConnectionRecord { | ||||||
|     const connectionId = this.generateConnectionId(); |     const connectionId = this.generateConnectionId(); | ||||||
|     const remoteIP = socket.remoteAddress || ''; |     const remoteIP = socket.remoteAddress || ''; | ||||||
|     const localPort = socket.localPort || 0; |     const localPort = socket.localPort || 0; | ||||||
| 
 | 
 | ||||||
|     const record: IConnectionRecord = { |     const record: ConnectionRecord = { | ||||||
|       id: connectionId, |       id: connectionId, | ||||||
|       incoming: socket, |       incoming: socket, | ||||||
|       outgoing: null, |       outgoing: null, | ||||||
| @@ -66,22 +66,22 @@ export class ConnectionManager { | |||||||
|   /** |   /** | ||||||
|    * Track an existing connection |    * Track an existing connection | ||||||
|    */ |    */ | ||||||
|   public trackConnection(connectionId: string, record: IConnectionRecord): void { |   public trackConnection(connectionId: string, record: ConnectionRecord): void { | ||||||
|     this.connectionRecords.set(connectionId, record); |     this.connectionRecords.set(connectionId, record); | ||||||
|     this.securityManager.trackConnectionByIP(record.remoteIP, connectionId); |     this.securityManager.trackConnectionByIP(record.remoteIP, connectionId); | ||||||
|   } |   } | ||||||
|    | 
 | ||||||
|   /** |   /** | ||||||
|    * Get a connection by ID |    * Get a connection by ID | ||||||
|    */ |    */ | ||||||
|   public getConnection(connectionId: string): IConnectionRecord | undefined { |   public getConnection(connectionId: string): ConnectionRecord | undefined { | ||||||
|     return this.connectionRecords.get(connectionId); |     return this.connectionRecords.get(connectionId); | ||||||
|   } |   } | ||||||
|    | 
 | ||||||
|   /** |   /** | ||||||
|    * Get all active connections |    * Get all active connections | ||||||
|    */ |    */ | ||||||
|   public getConnections(): Map<string, IConnectionRecord> { |   public getConnections(): Map<string, ConnectionRecord> { | ||||||
|     return this.connectionRecords; |     return this.connectionRecords; | ||||||
|   } |   } | ||||||
|    |    | ||||||
| @@ -95,7 +95,7 @@ export class ConnectionManager { | |||||||
|   /** |   /** | ||||||
|    * Initiates cleanup once for a connection |    * Initiates cleanup once for a connection | ||||||
|    */ |    */ | ||||||
|   public initiateCleanupOnce(record: IConnectionRecord, reason: string = 'normal'): void { |   public initiateCleanupOnce(record: ConnectionRecord, reason: string = 'normal'): void { | ||||||
|     if (this.settings.enableDetailedLogging) { |     if (this.settings.enableDetailedLogging) { | ||||||
|       console.log(`[${record.id}] Connection cleanup initiated for ${record.remoteIP} (${reason})`); |       console.log(`[${record.id}] Connection cleanup initiated for ${record.remoteIP} (${reason})`); | ||||||
|     } |     } | ||||||
| @@ -114,7 +114,7 @@ export class ConnectionManager { | |||||||
|   /** |   /** | ||||||
|    * Clean up a connection record |    * Clean up a connection record | ||||||
|    */ |    */ | ||||||
|   public cleanupConnection(record: IConnectionRecord, reason: string = 'normal'): void { |   public cleanupConnection(record: ConnectionRecord, reason: string = 'normal'): void { | ||||||
|     if (!record.connectionClosed) { |     if (!record.connectionClosed) { | ||||||
|       record.connectionClosed = true; |       record.connectionClosed = true; | ||||||
| 
 | 
 | ||||||
| @@ -178,7 +178,7 @@ export class ConnectionManager { | |||||||
|   /** |   /** | ||||||
|    * Helper method to clean up a socket |    * Helper method to clean up a socket | ||||||
|    */ |    */ | ||||||
|   private cleanupSocket(record: IConnectionRecord, side: 'incoming' | 'outgoing', socket: plugins.net.Socket): void { |   private cleanupSocket(record: ConnectionRecord, side: 'incoming' | 'outgoing', socket: plugins.net.Socket): void { | ||||||
|     try { |     try { | ||||||
|       if (!socket.destroyed) { |       if (!socket.destroyed) { | ||||||
|         // Try graceful shutdown first, then force destroy after a short timeout
 |         // Try graceful shutdown first, then force destroy after a short timeout
 | ||||||
| @@ -213,7 +213,7 @@ export class ConnectionManager { | |||||||
|   /** |   /** | ||||||
|    * Creates a generic error handler for incoming or outgoing sockets |    * Creates a generic error handler for incoming or outgoing sockets | ||||||
|    */ |    */ | ||||||
|   public handleError(side: 'incoming' | 'outgoing', record: IConnectionRecord) { |   public handleError(side: 'incoming' | 'outgoing', record: ConnectionRecord) { | ||||||
|     return (err: Error) => { |     return (err: Error) => { | ||||||
|       const code = (err as any).code; |       const code = (err as any).code; | ||||||
|       let reason = 'error'; |       let reason = 'error'; | ||||||
| @@ -256,7 +256,7 @@ export class ConnectionManager { | |||||||
|   /** |   /** | ||||||
|    * Creates a generic close handler for incoming or outgoing sockets |    * Creates a generic close handler for incoming or outgoing sockets | ||||||
|    */ |    */ | ||||||
|   public handleClose(side: 'incoming' | 'outgoing', record: IConnectionRecord) { |   public handleClose(side: 'incoming' | 'outgoing', record: ConnectionRecord) { | ||||||
|     return () => { |     return () => { | ||||||
|       if (this.settings.enableDetailedLogging) { |       if (this.settings.enableDetailedLogging) { | ||||||
|         console.log(`[${record.id}] Connection closed on ${side} side from ${record.remoteIP}`); |         console.log(`[${record.id}] Connection closed on ${side} side from ${record.remoteIP}`); | ||||||
| @@ -1,24 +1,25 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import type { IDomainConfig, ISmartProxyOptions } from './classes.pp.interfaces.js'; | import type { DomainConfig, SmartProxyOptions } from './models/interfaces.js'; | ||||||
| import type { ForwardingType, IForwardConfig, IForwardingHandler } from './types/forwarding.types.js'; | import type { ForwardingType, ForwardConfig } from '../../forwarding/config/forwarding-types.js'; | ||||||
| import { ForwardingHandlerFactory } from './forwarding/forwarding.factory.js'; | import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.js'; | ||||||
|  | import { ForwardingHandlerFactory } from '../../forwarding/factory/forwarding-factory.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Manages domain configurations and target selection |  * Manages domain configurations and target selection | ||||||
|  */ |  */ | ||||||
| export class DomainConfigManager { | export class DomainConfigManager { | ||||||
|   // Track round-robin indices for domain configs
 |   // Track round-robin indices for domain configs
 | ||||||
|   private domainTargetIndices: Map<IDomainConfig, number> = new Map(); |   private domainTargetIndices: Map<DomainConfig, number> = new Map(); | ||||||
| 
 | 
 | ||||||
|   // Cache forwarding handlers for each domain config
 |   // Cache forwarding handlers for each domain config
 | ||||||
|   private forwardingHandlers: Map<IDomainConfig, IForwardingHandler> = new Map(); |   private forwardingHandlers: Map<DomainConfig, ForwardingHandler> = new Map(); | ||||||
| 
 | 
 | ||||||
|   constructor(private settings: ISmartProxyOptions) {} |   constructor(private settings: SmartProxyOptions) {} | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Updates the domain configurations |    * Updates the domain configurations | ||||||
|    */ |    */ | ||||||
|   public updateDomainConfigs(newDomainConfigs: IDomainConfig[]): void { |   public updateDomainConfigs(newDomainConfigs: DomainConfig[]): void { | ||||||
|     this.settings.domainConfigs = newDomainConfigs; |     this.settings.domainConfigs = newDomainConfigs; | ||||||
| 
 | 
 | ||||||
|     // Reset target indices for removed configs
 |     // Reset target indices for removed configs
 | ||||||
| @@ -30,7 +31,7 @@ export class DomainConfigManager { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Clear handlers for removed configs and create handlers for new configs
 |     // Clear handlers for removed configs and create handlers for new configs
 | ||||||
|     const handlersToRemove: IDomainConfig[] = []; |     const handlersToRemove: DomainConfig[] = []; | ||||||
|     for (const [config] of this.forwardingHandlers) { |     for (const [config] of this.forwardingHandlers) { | ||||||
|       if (!currentConfigSet.has(config)) { |       if (!currentConfigSet.has(config)) { | ||||||
|         handlersToRemove.push(config); |         handlersToRemove.push(config); | ||||||
| @@ -58,14 +59,14 @@ export class DomainConfigManager { | |||||||
|   /** |   /** | ||||||
|    * Get all domain configurations |    * Get all domain configurations | ||||||
|    */ |    */ | ||||||
|   public getDomainConfigs(): IDomainConfig[] { |   public getDomainConfigs(): DomainConfig[] { | ||||||
|     return this.settings.domainConfigs; |     return this.settings.domainConfigs; | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Find domain config matching a server name |    * Find domain config matching a server name | ||||||
|    */ |    */ | ||||||
|   public findDomainConfig(serverName: string): IDomainConfig | undefined { |   public findDomainConfig(serverName: string): DomainConfig | undefined { | ||||||
|     if (!serverName) return undefined; |     if (!serverName) return undefined; | ||||||
|      |      | ||||||
|     return this.settings.domainConfigs.find((config) => |     return this.settings.domainConfigs.find((config) => | ||||||
| @@ -76,7 +77,7 @@ export class DomainConfigManager { | |||||||
|   /** |   /** | ||||||
|    * Find domain config for a specific port |    * Find domain config for a specific port | ||||||
|    */ |    */ | ||||||
|   public findDomainConfigForPort(port: number): IDomainConfig | undefined { |   public findDomainConfigForPort(port: number): DomainConfig | undefined { | ||||||
|     return this.settings.domainConfigs.find( |     return this.settings.domainConfigs.find( | ||||||
|       (domain) => { |       (domain) => { | ||||||
|         const portRanges = domain.forwarding?.advanced?.portRanges; |         const portRanges = domain.forwarding?.advanced?.portRanges; | ||||||
| @@ -97,7 +98,7 @@ export class DomainConfigManager { | |||||||
|   /** |   /** | ||||||
|    * Get target IP with round-robin support |    * Get target IP with round-robin support | ||||||
|    */ |    */ | ||||||
|   public getTargetIP(domainConfig: IDomainConfig): string { |   public getTargetIP(domainConfig: DomainConfig): string { | ||||||
|     const targetHosts = Array.isArray(domainConfig.forwarding.target.host) |     const targetHosts = Array.isArray(domainConfig.forwarding.target.host) | ||||||
|       ? domainConfig.forwarding.target.host |       ? domainConfig.forwarding.target.host | ||||||
|       : [domainConfig.forwarding.target.host]; |       : [domainConfig.forwarding.target.host]; | ||||||
| @@ -116,21 +117,21 @@ export class DomainConfigManager { | |||||||
|    * Get target host with round-robin support (for tests) |    * Get target host with round-robin support (for tests) | ||||||
|    * This is just an alias for getTargetIP for easier test compatibility |    * This is just an alias for getTargetIP for easier test compatibility | ||||||
|    */ |    */ | ||||||
|   public getTargetHost(domainConfig: IDomainConfig): string { |   public getTargetHost(domainConfig: DomainConfig): string { | ||||||
|     return this.getTargetIP(domainConfig); |     return this.getTargetIP(domainConfig); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** |   /** | ||||||
|    * Get target port from domain config |    * Get target port from domain config | ||||||
|    */ |    */ | ||||||
|   public getTargetPort(domainConfig: IDomainConfig, defaultPort: number): number { |   public getTargetPort(domainConfig: DomainConfig, defaultPort: number): number { | ||||||
|     return domainConfig.forwarding.target.port || defaultPort; |     return domainConfig.forwarding.target.port || defaultPort; | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Checks if a domain should use NetworkProxy |    * Checks if a domain should use NetworkProxy | ||||||
|    */ |    */ | ||||||
|   public shouldUseNetworkProxy(domainConfig: IDomainConfig): boolean { |   public shouldUseNetworkProxy(domainConfig: DomainConfig): boolean { | ||||||
|     const forwardingType = this.getForwardingType(domainConfig); |     const forwardingType = this.getForwardingType(domainConfig); | ||||||
|     return forwardingType === 'https-terminate-to-http' || |     return forwardingType === 'https-terminate-to-http' || | ||||||
|            forwardingType === 'https-terminate-to-https'; |            forwardingType === 'https-terminate-to-https'; | ||||||
| @@ -139,7 +140,7 @@ export class DomainConfigManager { | |||||||
|   /** |   /** | ||||||
|    * Gets the NetworkProxy port for a domain |    * Gets the NetworkProxy port for a domain | ||||||
|    */ |    */ | ||||||
|   public getNetworkProxyPort(domainConfig: IDomainConfig): number | undefined { |   public getNetworkProxyPort(domainConfig: DomainConfig): number | undefined { | ||||||
|     // First check if we should use NetworkProxy at all
 |     // First check if we should use NetworkProxy at all
 | ||||||
|     if (!this.shouldUseNetworkProxy(domainConfig)) { |     if (!this.shouldUseNetworkProxy(domainConfig)) { | ||||||
|       return undefined; |       return undefined; | ||||||
| @@ -154,7 +155,7 @@ export class DomainConfigManager { | |||||||
|    * This method combines domain-specific security rules from the forwarding configuration |    * This method combines domain-specific security rules from the forwarding configuration | ||||||
|    * with global security defaults when necessary. |    * with global security defaults when necessary. | ||||||
|    */ |    */ | ||||||
|   public getEffectiveIPRules(domainConfig: IDomainConfig): { |   public getEffectiveIPRules(domainConfig: DomainConfig): { | ||||||
|     allowedIPs: string[], |     allowedIPs: string[], | ||||||
|     blockedIPs: string[] |     blockedIPs: string[] | ||||||
|   } { |   } { | ||||||
| @@ -200,7 +201,7 @@ export class DomainConfigManager { | |||||||
|   /** |   /** | ||||||
|    * Get connection timeout for a domain |    * Get connection timeout for a domain | ||||||
|    */ |    */ | ||||||
|   public getConnectionTimeout(domainConfig?: IDomainConfig): number { |   public getConnectionTimeout(domainConfig?: DomainConfig): number { | ||||||
|     if (domainConfig?.forwarding.advanced?.timeout) { |     if (domainConfig?.forwarding.advanced?.timeout) { | ||||||
|       return domainConfig.forwarding.advanced.timeout; |       return domainConfig.forwarding.advanced.timeout; | ||||||
|     } |     } | ||||||
| @@ -211,7 +212,7 @@ export class DomainConfigManager { | |||||||
|   /** |   /** | ||||||
|    * Creates a forwarding handler for a domain configuration |    * Creates a forwarding handler for a domain configuration | ||||||
|    */ |    */ | ||||||
|   private createForwardingHandler(domainConfig: IDomainConfig): IForwardingHandler { |   private createForwardingHandler(domainConfig: DomainConfig): ForwardingHandler { | ||||||
|     // Create a new handler using the factory
 |     // Create a new handler using the factory
 | ||||||
|     const handler = ForwardingHandlerFactory.createHandler(domainConfig.forwarding); |     const handler = ForwardingHandlerFactory.createHandler(domainConfig.forwarding); | ||||||
| 
 | 
 | ||||||
| @@ -227,7 +228,7 @@ export class DomainConfigManager { | |||||||
|    * Gets a forwarding handler for a domain config |    * Gets a forwarding handler for a domain config | ||||||
|    * If no handler exists, creates one |    * If no handler exists, creates one | ||||||
|    */ |    */ | ||||||
|   public getForwardingHandler(domainConfig: IDomainConfig): IForwardingHandler { |   public getForwardingHandler(domainConfig: DomainConfig): ForwardingHandler { | ||||||
|     // If we already have a handler, return it
 |     // If we already have a handler, return it
 | ||||||
|     if (this.forwardingHandlers.has(domainConfig)) { |     if (this.forwardingHandlers.has(domainConfig)) { | ||||||
|       return this.forwardingHandlers.get(domainConfig)!; |       return this.forwardingHandlers.get(domainConfig)!; | ||||||
| @@ -243,7 +244,7 @@ export class DomainConfigManager { | |||||||
|   /** |   /** | ||||||
|    * Gets the forwarding type for a domain config |    * Gets the forwarding type for a domain config | ||||||
|    */ |    */ | ||||||
|   public getForwardingType(domainConfig?: IDomainConfig): ForwardingType | undefined { |   public getForwardingType(domainConfig?: DomainConfig): ForwardingType | undefined { | ||||||
|     if (!domainConfig?.forwarding) return undefined; |     if (!domainConfig?.forwarding) return undefined; | ||||||
|     return domainConfig.forwarding.type; |     return domainConfig.forwarding.type; | ||||||
|   } |   } | ||||||
| @@ -251,7 +252,7 @@ export class DomainConfigManager { | |||||||
|   /** |   /** | ||||||
|    * Checks if the forwarding type requires TLS termination |    * Checks if the forwarding type requires TLS termination | ||||||
|    */ |    */ | ||||||
|   public requiresTlsTermination(domainConfig?: IDomainConfig): boolean { |   public requiresTlsTermination(domainConfig?: DomainConfig): boolean { | ||||||
|     if (!domainConfig) return false; |     if (!domainConfig) return false; | ||||||
| 
 | 
 | ||||||
|     const forwardingType = this.getForwardingType(domainConfig); |     const forwardingType = this.getForwardingType(domainConfig); | ||||||
| @@ -262,7 +263,7 @@ export class DomainConfigManager { | |||||||
|   /** |   /** | ||||||
|    * Checks if the forwarding type supports HTTP |    * Checks if the forwarding type supports HTTP | ||||||
|    */ |    */ | ||||||
|   public supportsHttp(domainConfig?: IDomainConfig): boolean { |   public supportsHttp(domainConfig?: DomainConfig): boolean { | ||||||
|     if (!domainConfig) return false; |     if (!domainConfig) return false; | ||||||
| 
 | 
 | ||||||
|     const forwardingType = this.getForwardingType(domainConfig); |     const forwardingType = this.getForwardingType(domainConfig); | ||||||
| @@ -284,7 +285,7 @@ export class DomainConfigManager { | |||||||
|   /** |   /** | ||||||
|    * Checks if HTTP requests should be redirected to HTTPS |    * Checks if HTTP requests should be redirected to HTTPS | ||||||
|    */ |    */ | ||||||
|   public shouldRedirectToHttps(domainConfig?: IDomainConfig): boolean { |   public shouldRedirectToHttps(domainConfig?: DomainConfig): boolean { | ||||||
|     if (!domainConfig?.forwarding) return false; |     if (!domainConfig?.forwarding) return false; | ||||||
| 
 | 
 | ||||||
|     // Only check for redirect if HTTP is enabled
 |     // Only check for redirect if HTTP is enabled
 | ||||||
| @@ -4,5 +4,15 @@ | |||||||
| // Re-export models | // Re-export models | ||||||
| export * from './models/index.js'; | export * from './models/index.js'; | ||||||
|  |  | ||||||
| // Core SmartProxy will be added later: | // Export the main SmartProxy class | ||||||
| // export { SmartProxy } from './smart-proxy.js'; | export { SmartProxy } from './smart-proxy.js'; | ||||||
|  |  | ||||||
|  | // Export supporting classes | ||||||
|  | export { ConnectionManager } from './connection-manager.js'; | ||||||
|  | export { SecurityManager } from './security-manager.js'; | ||||||
|  | export { DomainConfigManager } from './domain-config-manager.js'; | ||||||
|  | export { TimeoutManager } from './timeout-manager.js'; | ||||||
|  | export { TlsManager } from './tls-manager.js'; | ||||||
|  | export { NetworkProxyBridge } from './network-proxy-bridge.js'; | ||||||
|  | export { PortRangeManager } from './port-range-manager.js'; | ||||||
|  | export { ConnectionHandler } from './connection-handler.js'; | ||||||
|   | |||||||
| @@ -1,10 +1,10 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import { NetworkProxy } from '../networkproxy/classes.np.networkproxy.js'; | import { NetworkProxy } from '../network-proxy/index.js'; | ||||||
| import { Port80Handler } from '../port80handler/classes.port80handler.js'; | import { Port80Handler } from '../../http/port80/port80-handler.js'; | ||||||
| import { Port80HandlerEvents } from '../common/types.js'; | import { Port80HandlerEvents } from '../../core/models/common-types.js'; | ||||||
| import { subscribeToPort80Handler } from '../common/eventUtils.js'; | import { subscribeToPort80Handler } from '../../core/utils/event-utils.js'; | ||||||
| import type { CertificateData } from '../certificate/models/certificate-types.js'; | import type { CertificateData } from '../../certificate/models/certificate-types.js'; | ||||||
| import type { IConnectionRecord, ISmartProxyOptions, IDomainConfig } from './classes.pp.interfaces.js'; | import type { ConnectionRecord, SmartProxyOptions, DomainConfig } from './models/interfaces.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Manages NetworkProxy integration for TLS termination |  * Manages NetworkProxy integration for TLS termination | ||||||
| @@ -12,8 +12,8 @@ import type { IConnectionRecord, ISmartProxyOptions, IDomainConfig } from './cla | |||||||
| export class NetworkProxyBridge { | export class NetworkProxyBridge { | ||||||
|   private networkProxy: NetworkProxy | null = null; |   private networkProxy: NetworkProxy | null = null; | ||||||
|   private port80Handler: Port80Handler | null = null; |   private port80Handler: Port80Handler | null = null; | ||||||
|    | 
 | ||||||
|   constructor(private settings: ISmartProxyOptions) {} |   constructor(private settings: SmartProxyOptions) {} | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Set the Port80Handler to use for certificate management |    * Set the Port80Handler to use for certificate management | ||||||
| @@ -183,7 +183,7 @@ export class NetworkProxyBridge { | |||||||
|   public forwardToNetworkProxy( |   public forwardToNetworkProxy( | ||||||
|     connectionId: string, |     connectionId: string, | ||||||
|     socket: plugins.net.Socket, |     socket: plugins.net.Socket, | ||||||
|     record: IConnectionRecord, |     record: ConnectionRecord, | ||||||
|     initialData: Buffer, |     initialData: Buffer, | ||||||
|     customProxyPort?: number, |     customProxyPort?: number, | ||||||
|     onError?: (reason: string) => void |     onError?: (reason: string) => void | ||||||
| @@ -283,7 +283,7 @@ export class NetworkProxyBridge { | |||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       // Convert domain configs to NetworkProxy configs
 |       // Convert domain configs to NetworkProxy configs
 | ||||||
|       const proxyConfigs = this.networkProxy.convertPortProxyConfigs( |       const proxyConfigs = this.networkProxy.convertSmartProxyConfigs( | ||||||
|         this.settings.domainConfigs, |         this.settings.domainConfigs, | ||||||
|         certPair |         certPair | ||||||
|       ); |       ); | ||||||
| @@ -1,10 +1,10 @@ | |||||||
| import type{ ISmartProxyOptions } from './classes.pp.interfaces.js'; | import type { SmartProxyOptions } from './models/interfaces.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Manages port ranges and port-based configuration |  * Manages port ranges and port-based configuration | ||||||
|  */ |  */ | ||||||
| export class PortRangeManager { | export class PortRangeManager { | ||||||
|   constructor(private settings: ISmartProxyOptions) {} |   constructor(private settings: SmartProxyOptions) {} | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Get all ports that should be listened on |    * Get all ports that should be listened on | ||||||
| @@ -1,5 +1,5 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import type { ISmartProxyOptions } from './classes.pp.interfaces.js'; | import type { SmartProxyOptions } from './models/interfaces.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Handles security aspects like IP tracking, rate limiting, and authorization |  * Handles security aspects like IP tracking, rate limiting, and authorization | ||||||
| @@ -7,8 +7,8 @@ import type { ISmartProxyOptions } from './classes.pp.interfaces.js'; | |||||||
| export class SecurityManager { | export class SecurityManager { | ||||||
|   private connectionsByIP: Map<string, Set<string>> = new Map(); |   private connectionsByIP: Map<string, Set<string>> = new Map(); | ||||||
|   private connectionRateByIP: Map<string, number[]> = new Map(); |   private connectionRateByIP: Map<string, number[]> = new Map(); | ||||||
|    | 
 | ||||||
|   constructor(private settings: ISmartProxyOptions) {} |   constructor(private settings: SmartProxyOptions) {} | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Get connections count by IP |    * Get connections count by IP | ||||||
| @@ -1,22 +1,27 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| 
 | 
 | ||||||
| import { ConnectionManager } from './classes.pp.connectionmanager.js'; | // Importing from the new structure
 | ||||||
| import { SecurityManager } from './classes.pp.securitymanager.js'; | import { ConnectionManager } from './connection-manager.js'; | ||||||
| import { DomainConfigManager } from './classes.pp.domainconfigmanager.js'; | import { SecurityManager } from './security-manager.js'; | ||||||
| import { TlsManager } from './classes.pp.tlsmanager.js'; | import { DomainConfigManager } from './domain-config-manager.js'; | ||||||
| import { NetworkProxyBridge } from './classes.pp.networkproxybridge.js'; | import { TlsManager } from './tls-manager.js'; | ||||||
| import { TimeoutManager } from './classes.pp.timeoutmanager.js'; | import { NetworkProxyBridge } from './network-proxy-bridge.js'; | ||||||
| import { PortRangeManager } from './classes.pp.portrangemanager.js'; | import { TimeoutManager } from './timeout-manager.js'; | ||||||
| import { ConnectionHandler } from './classes.pp.connectionhandler.js'; | import { PortRangeManager } from './port-range-manager.js'; | ||||||
| import { Port80Handler } from '../port80handler/classes.port80handler.js'; | import { ConnectionHandler } from './connection-handler.js'; | ||||||
| import { CertProvisioner } from '../certificate/providers/cert-provisioner.js'; |  | ||||||
| import type { CertificateData } from '../certificate/models/certificate-types.js'; |  | ||||||
| import { buildPort80Handler } from '../certificate/acme/acme-factory.js'; |  | ||||||
| import type { ForwardingType } from './types/forwarding.types.js'; |  | ||||||
| import { createPort80HandlerOptions } from '../common/port80-adapter.js'; |  | ||||||
| 
 | 
 | ||||||
| import type { ISmartProxyOptions, IDomainConfig } from './classes.pp.interfaces.js'; | // External dependencies from migrated modules
 | ||||||
| export type { ISmartProxyOptions as IPortProxySettings, IDomainConfig }; | import { Port80Handler } from '../../http/port80/port80-handler.js'; | ||||||
|  | import { CertProvisioner } from '../../certificate/providers/cert-provisioner.js'; | ||||||
|  | import type { CertificateData } from '../../certificate/models/certificate-types.js'; | ||||||
|  | import { buildPort80Handler } from '../../certificate/acme/acme-factory.js'; | ||||||
|  | import type { ForwardingType } from '../../forwarding/config/forwarding-types.js'; | ||||||
|  | import { createPort80HandlerOptions } from '../../common/port80-adapter.js'; | ||||||
|  | 
 | ||||||
|  | // Import types from models
 | ||||||
|  | import type { SmartProxyOptions, DomainConfig } from './models/interfaces.js'; | ||||||
|  | // Provide backward compatibility types
 | ||||||
|  | export type { SmartProxyOptions as IPortProxySettings, DomainConfig as IDomainConfig }; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * SmartProxy - Main class that coordinates all components |  * SmartProxy - Main class that coordinates all components | ||||||
| @@ -41,7 +46,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|   // CertProvisioner for unified certificate workflows
 |   // CertProvisioner for unified certificate workflows
 | ||||||
|   private certProvisioner?: CertProvisioner; |   private certProvisioner?: CertProvisioner; | ||||||
|    |    | ||||||
|   constructor(settingsArg: ISmartProxyOptions) { |   constructor(settingsArg: SmartProxyOptions) { | ||||||
|     super(); |     super(); | ||||||
|     // Set reasonable defaults for all settings
 |     // Set reasonable defaults for all settings
 | ||||||
|     this.settings = { |     this.settings = { | ||||||
| @@ -121,7 +126,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|   /** |   /** | ||||||
|    * The settings for the port proxy |    * The settings for the port proxy | ||||||
|    */ |    */ | ||||||
|   public settings: ISmartProxyOptions; |   public settings: SmartProxyOptions; | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Initialize the Port80Handler for ACME certificate management |    * Initialize the Port80Handler for ACME certificate management | ||||||
| @@ -154,7 +159,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|   public async start() { |   public async start() { | ||||||
|     // Don't start if already shutting down
 |     // Don't start if already shutting down
 | ||||||
|     if (this.isShuttingDown) { |     if (this.isShuttingDown) { | ||||||
|       console.log("Cannot start PortProxy while it's shutting down"); |       console.log("Cannot start SmartProxy while it's shutting down"); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -262,7 +267,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|       server.listen(port, () => { |       server.listen(port, () => { | ||||||
|         const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port); |         const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port); | ||||||
|         console.log( |         console.log( | ||||||
|           `PortProxy -> OK: Now listening on port ${port}${ |           `SmartProxy -> OK: Now listening on port ${port}${ | ||||||
|             this.settings.sniEnabled && !isNetworkProxyPort ? ' (SNI passthrough enabled)' : '' |             this.settings.sniEnabled && !isNetworkProxyPort ? ' (SNI passthrough enabled)' : '' | ||||||
|           }${isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''}` |           }${isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''}` | ||||||
|         ); |         ); | ||||||
| @@ -347,7 +352,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|    * Stop the proxy server |    * Stop the proxy server | ||||||
|    */ |    */ | ||||||
|   public async stop() { |   public async stop() { | ||||||
|     console.log('PortProxy shutting down...'); |     console.log('SmartProxy shutting down...'); | ||||||
|     this.isShuttingDown = true; |     this.isShuttingDown = true; | ||||||
|     // Stop CertProvisioner if active
 |     // Stop CertProvisioner if active
 | ||||||
|     if (this.certProvisioner) { |     if (this.certProvisioner) { | ||||||
| @@ -402,13 +407,13 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|     // Clear all servers
 |     // Clear all servers
 | ||||||
|     this.netServers = []; |     this.netServers = []; | ||||||
| 
 | 
 | ||||||
|     console.log('PortProxy shutdown complete.'); |     console.log('SmartProxy shutdown complete.'); | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Updates the domain configurations for the proxy |    * Updates the domain configurations for the proxy | ||||||
|    */ |    */ | ||||||
|   public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise<void> { |   public async updateDomainConfigs(newDomainConfigs: DomainConfig[]): Promise<void> { | ||||||
|     console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`); |     console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`); | ||||||
| 
 | 
 | ||||||
|     // Update domain configs in DomainConfigManager
 |     // Update domain configs in DomainConfigManager
 | ||||||
| @@ -1,10 +1,10 @@ | |||||||
| import type { IConnectionRecord, ISmartProxyOptions } from './classes.pp.interfaces.js'; | import type { ConnectionRecord, SmartProxyOptions } from './models/interfaces.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Manages timeouts and inactivity tracking for connections |  * Manages timeouts and inactivity tracking for connections | ||||||
|  */ |  */ | ||||||
| export class TimeoutManager { | export class TimeoutManager { | ||||||
|   constructor(private settings: ISmartProxyOptions) {} |   constructor(private settings: SmartProxyOptions) {} | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Ensure timeout values don't exceed Node.js max safe integer |    * Ensure timeout values don't exceed Node.js max safe integer | ||||||
| @@ -28,7 +28,7 @@ export class TimeoutManager { | |||||||
|   /** |   /** | ||||||
|    * Update connection activity timestamp |    * Update connection activity timestamp | ||||||
|    */ |    */ | ||||||
|   public updateActivity(record: IConnectionRecord): void { |   public updateActivity(record: ConnectionRecord): void { | ||||||
|     record.lastActivity = Date.now(); |     record.lastActivity = Date.now(); | ||||||
| 
 | 
 | ||||||
|     // Clear any inactivity warning
 |     // Clear any inactivity warning
 | ||||||
| @@ -40,7 +40,7 @@ export class TimeoutManager { | |||||||
|   /** |   /** | ||||||
|    * Calculate effective inactivity timeout based on connection type |    * Calculate effective inactivity timeout based on connection type | ||||||
|    */ |    */ | ||||||
|   public getEffectiveInactivityTimeout(record: IConnectionRecord): number { |   public getEffectiveInactivityTimeout(record: ConnectionRecord): number { | ||||||
|     let effectiveTimeout = this.settings.inactivityTimeout || 14400000; // 4 hours default
 |     let effectiveTimeout = this.settings.inactivityTimeout || 14400000; // 4 hours default
 | ||||||
|      |      | ||||||
|     // For immortal keep-alive connections, use an extremely long timeout
 |     // For immortal keep-alive connections, use an extremely long timeout
 | ||||||
| @@ -60,7 +60,7 @@ export class TimeoutManager { | |||||||
|   /** |   /** | ||||||
|    * Calculate effective max lifetime based on connection type |    * Calculate effective max lifetime based on connection type | ||||||
|    */ |    */ | ||||||
|   public getEffectiveMaxLifetime(record: IConnectionRecord): number { |   public getEffectiveMaxLifetime(record: ConnectionRecord): number { | ||||||
|     // Use domain-specific timeout from forwarding.advanced if available
 |     // Use domain-specific timeout from forwarding.advanced if available
 | ||||||
|     const baseTimeout = record.domainConfig?.forwarding?.advanced?.timeout || |     const baseTimeout = record.domainConfig?.forwarding?.advanced?.timeout || | ||||||
|                         this.settings.maxConnectionLifetime || |                         this.settings.maxConnectionLifetime || | ||||||
| @@ -91,8 +91,8 @@ export class TimeoutManager { | |||||||
|    * @returns The cleanup timer |    * @returns The cleanup timer | ||||||
|    */ |    */ | ||||||
|   public setupConnectionTimeout( |   public setupConnectionTimeout( | ||||||
|     record: IConnectionRecord,  |     record: ConnectionRecord, | ||||||
|     onTimeout: (record: IConnectionRecord, reason: string) => void |     onTimeout: (record: ConnectionRecord, reason: string) => void | ||||||
|   ): NodeJS.Timeout { |   ): NodeJS.Timeout { | ||||||
|     // Clear any existing timer
 |     // Clear any existing timer
 | ||||||
|     if (record.cleanupTimer) { |     if (record.cleanupTimer) { | ||||||
| @@ -120,7 +120,7 @@ export class TimeoutManager { | |||||||
|    * Check for inactivity on a connection |    * Check for inactivity on a connection | ||||||
|    * @returns Object with check results |    * @returns Object with check results | ||||||
|    */ |    */ | ||||||
|   public checkInactivity(record: IConnectionRecord): { |   public checkInactivity(record: ConnectionRecord): { | ||||||
|     isInactive: boolean; |     isInactive: boolean; | ||||||
|     shouldWarn: boolean; |     shouldWarn: boolean; | ||||||
|     inactivityTime: number; |     inactivityTime: number; | ||||||
| @@ -169,7 +169,7 @@ export class TimeoutManager { | |||||||
|   /** |   /** | ||||||
|    * Apply socket timeout settings |    * Apply socket timeout settings | ||||||
|    */ |    */ | ||||||
|   public applySocketTimeouts(record: IConnectionRecord): void { |   public applySocketTimeouts(record: ConnectionRecord): void { | ||||||
|     // Skip for immortal keep-alive connections
 |     // Skip for immortal keep-alive connections
 | ||||||
|     if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') { |     if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') { | ||||||
|       // Disable timeouts completely for immortal connections
 |       // Disable timeouts completely for immortal connections
 | ||||||
| @@ -1,6 +1,6 @@ | |||||||
| import * as plugins from '../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import type { ISmartProxyOptions } from './classes.pp.interfaces.js'; | import type { SmartProxyOptions } from './models/interfaces.js'; | ||||||
| import { SniHandler } from '../tls/sni/sni-handler.js'; | import { SniHandler } from '../../tls/sni/sni-handler.js'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Interface for connection information used for SNI extraction |  * Interface for connection information used for SNI extraction | ||||||
| @@ -16,7 +16,7 @@ interface IConnectionInfo { | |||||||
|  * Manages TLS-related operations including SNI extraction and validation |  * Manages TLS-related operations including SNI extraction and validation | ||||||
|  */ |  */ | ||||||
| export class TlsManager { | export class TlsManager { | ||||||
|   constructor(private settings: ISmartProxyOptions) {} |   constructor(private settings: SmartProxyOptions) {} | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|    * Check if a data chunk appears to be a TLS handshake |    * Check if a data chunk appears to be a TLS handshake | ||||||
							
								
								
									
										15
									
								
								ts/smartproxy/LEGACY_NOTICE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								ts/smartproxy/LEGACY_NOTICE.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | |||||||
|  | # Legacy Notice | ||||||
|  |  | ||||||
|  | These files have been migrated to the new directory structure as part of the project restructuring. The new locations are as follows: | ||||||
|  |  | ||||||
|  | - `classes.smartproxy.ts` -> `/ts/proxies/smart-proxy/smart-proxy.ts` | ||||||
|  | - `classes.pp.connectionmanager.ts` -> `/ts/proxies/smart-proxy/connection-manager.ts` | ||||||
|  | - `classes.pp.securitymanager.ts` -> `/ts/proxies/smart-proxy/security-manager.ts` | ||||||
|  | - `classes.pp.domainconfigmanager.ts` -> `/ts/proxies/smart-proxy/domain-config-manager.ts` | ||||||
|  | - `classes.pp.tlsmanager.ts` -> `/ts/proxies/smart-proxy/tls-manager.ts` | ||||||
|  | - `classes.pp.networkproxybridge.ts` -> `/ts/proxies/smart-proxy/network-proxy-bridge.ts` | ||||||
|  | - `classes.pp.timeoutmanager.ts` -> `/ts/proxies/smart-proxy/timeout-manager.ts` | ||||||
|  | - `classes.pp.portrangemanager.ts` -> `/ts/proxies/smart-proxy/port-range-manager.ts` | ||||||
|  | - `classes.pp.connectionhandler.ts` -> `/ts/proxies/smart-proxy/connection-handler.ts` | ||||||
|  |  | ||||||
|  | This directory is now considered legacy and should not be used for new code. | ||||||
| @@ -1,132 +0,0 @@ | |||||||
| import * as plugins from '../plugins.js'; |  | ||||||
| import type { IForwardConfig } from './forwarding/index.js'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Provision object for static or HTTP-01 certificate |  | ||||||
|  */ |  | ||||||
| export type ISmartProxyCertProvisionObject = plugins.tsclass.network.ICert | 'http01'; |  | ||||||
|  |  | ||||||
| /** Domain configuration with forwarding configuration */ |  | ||||||
| export interface IDomainConfig { |  | ||||||
|   domains: string[]; // Glob patterns for domain(s) |  | ||||||
|   forwarding: IForwardConfig; // Unified forwarding configuration |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** Port proxy settings including global allowed port ranges */ |  | ||||||
| import type { IAcmeOptions } from '../common/types.js'; |  | ||||||
| export interface ISmartProxyOptions { |  | ||||||
|   fromPort: number; |  | ||||||
|   toPort: number; |  | ||||||
|   targetIP?: string; // Global target host to proxy to, defaults to 'localhost' |  | ||||||
|   domainConfigs: IDomainConfig[]; |  | ||||||
|   sniEnabled?: boolean; |  | ||||||
|   defaultAllowedIPs?: string[]; |  | ||||||
|   defaultBlockedIPs?: string[]; |  | ||||||
|   preserveSourceIP?: boolean; |  | ||||||
|  |  | ||||||
|   // TLS options |  | ||||||
|   pfx?: Buffer; |  | ||||||
|   key?: string | Buffer | Array<Buffer | string>; |  | ||||||
|   passphrase?: string; |  | ||||||
|   cert?: string | Buffer | Array<string | Buffer>; |  | ||||||
|   ca?: string | Buffer | Array<string | Buffer>; |  | ||||||
|   ciphers?: string; |  | ||||||
|   honorCipherOrder?: boolean; |  | ||||||
|   rejectUnauthorized?: boolean; |  | ||||||
|   secureProtocol?: string; |  | ||||||
|   servername?: string; |  | ||||||
|   minVersion?: string; |  | ||||||
|   maxVersion?: string; |  | ||||||
|  |  | ||||||
|   // Timeout settings |  | ||||||
|   initialDataTimeout?: number; // Timeout for initial data/SNI (ms), default: 60000 (60s) |  | ||||||
|   socketTimeout?: number; // Socket inactivity timeout (ms), default: 3600000 (1h) |  | ||||||
|   inactivityCheckInterval?: number; // How often to check for inactive connections (ms), default: 60000 (60s) |  | ||||||
|   maxConnectionLifetime?: number; // Default max connection lifetime (ms), default: 86400000 (24h) |  | ||||||
|   inactivityTimeout?: number; // Inactivity timeout (ms), default: 14400000 (4h) |  | ||||||
|  |  | ||||||
|   gracefulShutdownTimeout?: number; // (ms) maximum time to wait for connections to close during shutdown |  | ||||||
|   globalPortRanges: Array<{ from: number; to: number }>; // Global allowed port ranges |  | ||||||
|   forwardAllGlobalRanges?: boolean; // When true, forwards all connections on global port ranges to the global targetIP |  | ||||||
|  |  | ||||||
|   // Socket optimization settings |  | ||||||
|   noDelay?: boolean; // Disable Nagle's algorithm (default: true) |  | ||||||
|   keepAlive?: boolean; // Enable TCP keepalive (default: true) |  | ||||||
|   keepAliveInitialDelay?: number; // Initial delay before sending keepalive probes (ms) |  | ||||||
|   maxPendingDataSize?: number; // Maximum bytes to buffer during connection setup |  | ||||||
|  |  | ||||||
|   // Enhanced features |  | ||||||
|   disableInactivityCheck?: boolean; // Disable inactivity checking entirely |  | ||||||
|   enableKeepAliveProbes?: boolean; // Enable TCP keep-alive probes |  | ||||||
|   enableDetailedLogging?: boolean; // Enable detailed connection logging |  | ||||||
|   enableTlsDebugLogging?: boolean; // Enable TLS handshake debug logging |  | ||||||
|   enableRandomizedTimeouts?: boolean; // Randomize timeouts slightly to prevent thundering herd |  | ||||||
|   allowSessionTicket?: boolean; // Allow TLS session ticket for reconnection (default: true) |  | ||||||
|  |  | ||||||
|   // Rate limiting and security |  | ||||||
|   maxConnectionsPerIP?: number; // Maximum simultaneous connections from a single IP |  | ||||||
|   connectionRateLimitPerMinute?: number; // Max new connections per minute from a single IP |  | ||||||
|  |  | ||||||
|   // Enhanced keep-alive settings |  | ||||||
|   keepAliveTreatment?: 'standard' | 'extended' | 'immortal'; // How to treat keep-alive connections |  | ||||||
|   keepAliveInactivityMultiplier?: number; // Multiplier for inactivity timeout for keep-alive connections |  | ||||||
|   extendedKeepAliveLifetime?: number; // Extended lifetime for keep-alive connections (ms) |  | ||||||
|  |  | ||||||
|   // NetworkProxy integration |  | ||||||
|   useNetworkProxy?: number[]; // Array of ports to forward to NetworkProxy |  | ||||||
|   networkProxyPort?: number; // Port where NetworkProxy is listening (default: 8443) |  | ||||||
|  |  | ||||||
|   // ACME configuration options for SmartProxy |  | ||||||
|   acme?: IAcmeOptions; |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Optional certificate provider callback. Return 'http01' to use HTTP-01 challenges, |  | ||||||
|    * or a static certificate object for immediate provisioning. |  | ||||||
|    */ |  | ||||||
|   certProvisionFunction?: (domain: string) => Promise<ISmartProxyCertProvisionObject>; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Enhanced connection record |  | ||||||
|  */ |  | ||||||
| export interface IConnectionRecord { |  | ||||||
|   id: string; // Unique connection identifier |  | ||||||
|   incoming: plugins.net.Socket; |  | ||||||
|   outgoing: plugins.net.Socket | null; |  | ||||||
|   incomingStartTime: number; |  | ||||||
|   outgoingStartTime?: number; |  | ||||||
|   outgoingClosedTime?: number; |  | ||||||
|   lockedDomain?: string; // Used to lock this connection to the initial SNI |  | ||||||
|   connectionClosed: boolean; // Flag to prevent multiple cleanup attempts |  | ||||||
|   cleanupTimer?: NodeJS.Timeout; // Timer for max lifetime/inactivity |  | ||||||
|   alertFallbackTimeout?: NodeJS.Timeout; // Timer for fallback after alert |  | ||||||
|   lastActivity: number; // Last activity timestamp for inactivity detection |  | ||||||
|   pendingData: Buffer[]; // Buffer to hold data during connection setup |  | ||||||
|   pendingDataSize: number; // Track total size of pending data |  | ||||||
|  |  | ||||||
|   // Enhanced tracking fields |  | ||||||
|   bytesReceived: number; // Total bytes received |  | ||||||
|   bytesSent: number; // Total bytes sent |  | ||||||
|   remoteIP: string; // Remote IP (cached for logging after socket close) |  | ||||||
|   localPort: number; // Local port (cached for logging) |  | ||||||
|   isTLS: boolean; // Whether this connection is a TLS connection |  | ||||||
|   tlsHandshakeComplete: boolean; // Whether the TLS handshake is complete |  | ||||||
|   hasReceivedInitialData: boolean; // Whether initial data has been received |  | ||||||
|   domainConfig?: IDomainConfig; // Associated domain config for this connection |  | ||||||
|  |  | ||||||
|   // Keep-alive tracking |  | ||||||
|   hasKeepAlive: boolean; // Whether keep-alive is enabled for this connection |  | ||||||
|   inactivityWarningIssued?: boolean; // Whether an inactivity warning has been issued |  | ||||||
|   incomingTerminationReason?: string | null; // Reason for incoming termination |  | ||||||
|   outgoingTerminationReason?: string | null; // Reason for outgoing termination |  | ||||||
|  |  | ||||||
|   // NetworkProxy tracking |  | ||||||
|   usingNetworkProxy?: boolean; // Whether this connection is using a NetworkProxy |  | ||||||
|  |  | ||||||
|   // Renegotiation handler |  | ||||||
|   renegotiationHandler?: (chunk: Buffer) => void; // Handler for renegotiation detection |  | ||||||
|  |  | ||||||
|   // Browser connection tracking |  | ||||||
|   isBrowserConnection?: boolean; // Whether this connection appears to be from a browser |  | ||||||
|   domainSwitches?: number; // Number of times the domain has been switched on this connection |  | ||||||
| } |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| import type { IForwardConfig } from '../types/forwarding.types.js'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Domain configuration with unified forwarding configuration |  | ||||||
|  */ |  | ||||||
| export interface IDomainConfig { |  | ||||||
|   // Core properties - domain patterns |  | ||||||
|   domains: string[]; |  | ||||||
|  |  | ||||||
|   // Unified forwarding configuration |  | ||||||
|   forwarding: IForwardConfig; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Helper function to create a domain configuration |  | ||||||
|  */ |  | ||||||
| export function createDomainConfig( |  | ||||||
|   domains: string | string[], |  | ||||||
|   forwarding: IForwardConfig |  | ||||||
| ): IDomainConfig { |  | ||||||
|   // Normalize domains to an array |  | ||||||
|   const domainArray = Array.isArray(domains) ? domains : [domains]; |  | ||||||
|  |  | ||||||
|   return { |  | ||||||
|     domains: domainArray, |  | ||||||
|     forwarding |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
| @@ -1,283 +0,0 @@ | |||||||
| import * as plugins from '../../plugins.js'; |  | ||||||
| import type { IDomainConfig } from './domain-config.js'; |  | ||||||
| import type { IForwardingHandler } from '../types/forwarding.types.js'; |  | ||||||
| import { ForwardingHandlerEvents } from '../types/forwarding.types.js'; |  | ||||||
| import { ForwardingHandlerFactory } from './forwarding.factory.js'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Events emitted by the DomainManager |  | ||||||
|  */ |  | ||||||
| export enum DomainManagerEvents { |  | ||||||
|   DOMAIN_ADDED = 'domain-added', |  | ||||||
|   DOMAIN_REMOVED = 'domain-removed', |  | ||||||
|   DOMAIN_MATCHED = 'domain-matched', |  | ||||||
|   DOMAIN_MATCH_FAILED = 'domain-match-failed', |  | ||||||
|   CERTIFICATE_NEEDED = 'certificate-needed', |  | ||||||
|   CERTIFICATE_LOADED = 'certificate-loaded', |  | ||||||
|   ERROR = 'error' |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Manages domains and their forwarding handlers |  | ||||||
|  */ |  | ||||||
| export class DomainManager extends plugins.EventEmitter { |  | ||||||
|   private domainConfigs: IDomainConfig[] = []; |  | ||||||
|   private domainHandlers: Map<string, IForwardingHandler> = new Map(); |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Create a new DomainManager |  | ||||||
|    * @param initialDomains Optional initial domain configurations |  | ||||||
|    */ |  | ||||||
|   constructor(initialDomains?: IDomainConfig[]) { |  | ||||||
|     super(); |  | ||||||
|      |  | ||||||
|     if (initialDomains) { |  | ||||||
|       this.setDomainConfigs(initialDomains); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Set or replace all domain configurations |  | ||||||
|    * @param configs Array of domain configurations |  | ||||||
|    */ |  | ||||||
|   public async setDomainConfigs(configs: IDomainConfig[]): Promise<void> { |  | ||||||
|     // Clear existing handlers |  | ||||||
|     this.domainHandlers.clear(); |  | ||||||
|      |  | ||||||
|     // Store new configurations |  | ||||||
|     this.domainConfigs = [...configs]; |  | ||||||
|      |  | ||||||
|     // Initialize handlers for each domain |  | ||||||
|     for (const config of this.domainConfigs) { |  | ||||||
|       await this.createHandlersForDomain(config); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Add a new domain configuration |  | ||||||
|    * @param config The domain configuration to add |  | ||||||
|    */ |  | ||||||
|   public async addDomainConfig(config: IDomainConfig): Promise<void> { |  | ||||||
|     // Check if any of these domains already exist |  | ||||||
|     for (const domain of config.domains) { |  | ||||||
|       if (this.domainHandlers.has(domain)) { |  | ||||||
|         // Remove existing handler for this domain |  | ||||||
|         this.domainHandlers.delete(domain); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Add the new configuration |  | ||||||
|     this.domainConfigs.push(config); |  | ||||||
|      |  | ||||||
|     // Create handlers for the new domain |  | ||||||
|     await this.createHandlersForDomain(config); |  | ||||||
|      |  | ||||||
|     this.emit(DomainManagerEvents.DOMAIN_ADDED, { |  | ||||||
|       domains: config.domains, |  | ||||||
|       forwardingType: config.forwarding.type |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Remove a domain configuration |  | ||||||
|    * @param domain The domain to remove |  | ||||||
|    * @returns True if the domain was found and removed |  | ||||||
|    */ |  | ||||||
|   public removeDomainConfig(domain: string): boolean { |  | ||||||
|     // Find the config that includes this domain |  | ||||||
|     const index = this.domainConfigs.findIndex(config =>  |  | ||||||
|       config.domains.includes(domain) |  | ||||||
|     ); |  | ||||||
|      |  | ||||||
|     if (index === -1) { |  | ||||||
|       return false; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Get the config |  | ||||||
|     const config = this.domainConfigs[index]; |  | ||||||
|      |  | ||||||
|     // Remove all handlers for this config |  | ||||||
|     for (const domainName of config.domains) { |  | ||||||
|       this.domainHandlers.delete(domainName); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Remove the config |  | ||||||
|     this.domainConfigs.splice(index, 1); |  | ||||||
|      |  | ||||||
|     this.emit(DomainManagerEvents.DOMAIN_REMOVED, { |  | ||||||
|       domains: config.domains |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Find the handler for a domain |  | ||||||
|    * @param domain The domain to find a handler for |  | ||||||
|    * @returns The handler or undefined if no match |  | ||||||
|    */ |  | ||||||
|   public findHandlerForDomain(domain: string): IForwardingHandler | undefined { |  | ||||||
|     // Try exact match |  | ||||||
|     if (this.domainHandlers.has(domain)) { |  | ||||||
|       return this.domainHandlers.get(domain); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Try wildcard matches |  | ||||||
|     const wildcardHandler = this.findWildcardHandler(domain); |  | ||||||
|     if (wildcardHandler) { |  | ||||||
|       return wildcardHandler; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // No match found |  | ||||||
|     return undefined; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Handle a connection for a domain |  | ||||||
|    * @param domain The domain |  | ||||||
|    * @param socket The client socket |  | ||||||
|    * @returns True if the connection was handled |  | ||||||
|    */ |  | ||||||
|   public handleConnection(domain: string, socket: plugins.net.Socket): boolean { |  | ||||||
|     const handler = this.findHandlerForDomain(domain); |  | ||||||
|      |  | ||||||
|     if (!handler) { |  | ||||||
|       this.emit(DomainManagerEvents.DOMAIN_MATCH_FAILED, { |  | ||||||
|         domain, |  | ||||||
|         remoteAddress: socket.remoteAddress |  | ||||||
|       }); |  | ||||||
|       return false; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     this.emit(DomainManagerEvents.DOMAIN_MATCHED, { |  | ||||||
|       domain, |  | ||||||
|       handlerType: handler.constructor.name, |  | ||||||
|       remoteAddress: socket.remoteAddress |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Handle the connection |  | ||||||
|     handler.handleConnection(socket); |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Handle an HTTP request for a domain |  | ||||||
|    * @param domain The domain |  | ||||||
|    * @param req The HTTP request |  | ||||||
|    * @param res The HTTP response |  | ||||||
|    * @returns True if the request was handled |  | ||||||
|    */ |  | ||||||
|   public handleHttpRequest(domain: string, req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): boolean { |  | ||||||
|     const handler = this.findHandlerForDomain(domain); |  | ||||||
|      |  | ||||||
|     if (!handler) { |  | ||||||
|       this.emit(DomainManagerEvents.DOMAIN_MATCH_FAILED, { |  | ||||||
|         domain, |  | ||||||
|         remoteAddress: req.socket.remoteAddress |  | ||||||
|       }); |  | ||||||
|       return false; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     this.emit(DomainManagerEvents.DOMAIN_MATCHED, { |  | ||||||
|       domain, |  | ||||||
|       handlerType: handler.constructor.name, |  | ||||||
|       remoteAddress: req.socket.remoteAddress |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Handle the request |  | ||||||
|     handler.handleHttpRequest(req, res); |  | ||||||
|     return true; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Create handlers for a domain configuration |  | ||||||
|    * @param config The domain configuration |  | ||||||
|    */ |  | ||||||
|   private async createHandlersForDomain(config: IDomainConfig): Promise<void> { |  | ||||||
|     try { |  | ||||||
|       // Create a handler for this forwarding configuration |  | ||||||
|       const handler = ForwardingHandlerFactory.createHandler(config.forwarding); |  | ||||||
|        |  | ||||||
|       // Initialize the handler |  | ||||||
|       await handler.initialize(); |  | ||||||
|        |  | ||||||
|       // Set up event forwarding |  | ||||||
|       this.setupHandlerEvents(handler, config); |  | ||||||
|        |  | ||||||
|       // Store the handler for each domain in the config |  | ||||||
|       for (const domain of config.domains) { |  | ||||||
|         this.domainHandlers.set(domain, handler); |  | ||||||
|       } |  | ||||||
|     } catch (error) { |  | ||||||
|       this.emit(DomainManagerEvents.ERROR, { |  | ||||||
|         domains: config.domains, |  | ||||||
|         error: error instanceof Error ? error.message : String(error) |  | ||||||
|       }); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Set up event forwarding from a handler |  | ||||||
|    * @param handler The handler |  | ||||||
|    * @param config The domain configuration for this handler |  | ||||||
|    */ |  | ||||||
|   private setupHandlerEvents(handler: IForwardingHandler, config: IDomainConfig): void { |  | ||||||
|     // Forward relevant events |  | ||||||
|     handler.on(ForwardingHandlerEvents.CERTIFICATE_NEEDED, (data) => { |  | ||||||
|       this.emit(DomainManagerEvents.CERTIFICATE_NEEDED, { |  | ||||||
|         ...data, |  | ||||||
|         domains: config.domains |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     handler.on(ForwardingHandlerEvents.CERTIFICATE_LOADED, (data) => { |  | ||||||
|       this.emit(DomainManagerEvents.CERTIFICATE_LOADED, { |  | ||||||
|         ...data, |  | ||||||
|         domains: config.domains |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     handler.on(ForwardingHandlerEvents.ERROR, (data) => { |  | ||||||
|       this.emit(DomainManagerEvents.ERROR, { |  | ||||||
|         ...data, |  | ||||||
|         domains: config.domains |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Find a handler for a domain using wildcard matching |  | ||||||
|    * @param domain The domain to find a handler for |  | ||||||
|    * @returns The handler or undefined if no match |  | ||||||
|    */ |  | ||||||
|   private findWildcardHandler(domain: string): IForwardingHandler | undefined { |  | ||||||
|     // Exact match already checked in findHandlerForDomain |  | ||||||
|      |  | ||||||
|     // Try subdomain wildcard (*.example.com) |  | ||||||
|     if (domain.includes('.')) { |  | ||||||
|       const parts = domain.split('.'); |  | ||||||
|       if (parts.length > 2) { |  | ||||||
|         const wildcardDomain = `*.${parts.slice(1).join('.')}`; |  | ||||||
|         if (this.domainHandlers.has(wildcardDomain)) { |  | ||||||
|           return this.domainHandlers.get(wildcardDomain); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Try full wildcard |  | ||||||
|     if (this.domainHandlers.has('*')) { |  | ||||||
|       return this.domainHandlers.get('*'); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // No match found |  | ||||||
|     return undefined; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Get all domain configurations |  | ||||||
|    * @returns Array of domain configurations |  | ||||||
|    */ |  | ||||||
|   public getDomainConfigs(): IDomainConfig[] { |  | ||||||
|     return [...this.domainConfigs]; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,155 +0,0 @@ | |||||||
| import type { IForwardConfig, IForwardingHandler } from '../types/forwarding.types.js'; |  | ||||||
| import { HttpForwardingHandler } from './http.handler.js'; |  | ||||||
| import { HttpsPassthroughHandler } from './https-passthrough.handler.js'; |  | ||||||
| import { HttpsTerminateToHttpHandler } from './https-terminate-to-http.handler.js'; |  | ||||||
| import { HttpsTerminateToHttpsHandler } from './https-terminate-to-https.handler.js'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Factory for creating forwarding handlers based on the configuration type |  | ||||||
|  */ |  | ||||||
| export class ForwardingHandlerFactory { |  | ||||||
|   /** |  | ||||||
|    * Create a forwarding handler based on the configuration |  | ||||||
|    * @param config The forwarding configuration |  | ||||||
|    * @returns The appropriate forwarding handler |  | ||||||
|    */ |  | ||||||
|   public static createHandler(config: IForwardConfig): IForwardingHandler { |  | ||||||
|     // Create the appropriate handler based on the forwarding type |  | ||||||
|     switch (config.type) { |  | ||||||
|       case 'http-only': |  | ||||||
|         return new HttpForwardingHandler(config); |  | ||||||
|          |  | ||||||
|       case 'https-passthrough': |  | ||||||
|         return new HttpsPassthroughHandler(config); |  | ||||||
|          |  | ||||||
|       case 'https-terminate-to-http': |  | ||||||
|         return new HttpsTerminateToHttpHandler(config); |  | ||||||
|          |  | ||||||
|       case 'https-terminate-to-https': |  | ||||||
|         return new HttpsTerminateToHttpsHandler(config); |  | ||||||
|          |  | ||||||
|       default: |  | ||||||
|         // Type system should prevent this, but just in case: |  | ||||||
|         throw new Error(`Unknown forwarding type: ${(config as any).type}`); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Apply default values to a forwarding configuration based on its type |  | ||||||
|    * @param config The original forwarding configuration |  | ||||||
|    * @returns A configuration with defaults applied |  | ||||||
|    */ |  | ||||||
|   public static applyDefaults(config: IForwardConfig): IForwardConfig { |  | ||||||
|     // Create a deep copy of the configuration |  | ||||||
|     const result: IForwardConfig = JSON.parse(JSON.stringify(config)); |  | ||||||
|      |  | ||||||
|     // Apply defaults based on forwarding type |  | ||||||
|     switch (config.type) { |  | ||||||
|       case 'http-only': |  | ||||||
|         // Set defaults for HTTP-only mode |  | ||||||
|         result.http = { |  | ||||||
|           enabled: true, |  | ||||||
|           ...config.http |  | ||||||
|         }; |  | ||||||
|         break; |  | ||||||
|          |  | ||||||
|       case 'https-passthrough': |  | ||||||
|         // Set defaults for HTTPS passthrough |  | ||||||
|         result.https = { |  | ||||||
|           forwardSni: true, |  | ||||||
|           ...config.https |  | ||||||
|         }; |  | ||||||
|         // SNI forwarding doesn't do HTTP |  | ||||||
|         result.http = { |  | ||||||
|           enabled: false, |  | ||||||
|           ...config.http |  | ||||||
|         }; |  | ||||||
|         break; |  | ||||||
|          |  | ||||||
|       case 'https-terminate-to-http': |  | ||||||
|         // Set defaults for HTTPS termination to HTTP |  | ||||||
|         result.https = { |  | ||||||
|           ...config.https |  | ||||||
|         }; |  | ||||||
|         // Support HTTP access by default in this mode |  | ||||||
|         result.http = { |  | ||||||
|           enabled: true, |  | ||||||
|           redirectToHttps: true, |  | ||||||
|           ...config.http |  | ||||||
|         }; |  | ||||||
|         // Enable ACME by default |  | ||||||
|         result.acme = { |  | ||||||
|           enabled: true, |  | ||||||
|           maintenance: true, |  | ||||||
|           ...config.acme |  | ||||||
|         }; |  | ||||||
|         break; |  | ||||||
|          |  | ||||||
|       case 'https-terminate-to-https': |  | ||||||
|         // Similar to terminate-to-http but with different target handling |  | ||||||
|         result.https = { |  | ||||||
|           ...config.https |  | ||||||
|         }; |  | ||||||
|         result.http = { |  | ||||||
|           enabled: true, |  | ||||||
|           redirectToHttps: true, |  | ||||||
|           ...config.http |  | ||||||
|         }; |  | ||||||
|         result.acme = { |  | ||||||
|           enabled: true, |  | ||||||
|           maintenance: true, |  | ||||||
|           ...config.acme |  | ||||||
|         }; |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     return result; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Validate a forwarding configuration |  | ||||||
|    * @param config The configuration to validate |  | ||||||
|    * @throws Error if the configuration is invalid |  | ||||||
|    */ |  | ||||||
|   public static validateConfig(config: IForwardConfig): void { |  | ||||||
|     // Validate common properties |  | ||||||
|     if (!config.target) { |  | ||||||
|       throw new Error('Forwarding configuration must include a target'); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     if (!config.target.host || (Array.isArray(config.target.host) && config.target.host.length === 0)) { |  | ||||||
|       throw new Error('Target must include a host or array of hosts'); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     if (!config.target.port || config.target.port <= 0 || config.target.port > 65535) { |  | ||||||
|       throw new Error('Target must include a valid port (1-65535)'); |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Type-specific validation |  | ||||||
|     switch (config.type) { |  | ||||||
|       case 'http-only': |  | ||||||
|         // HTTP-only needs http.enabled to be true |  | ||||||
|         if (config.http?.enabled === false) { |  | ||||||
|           throw new Error('HTTP-only forwarding must have HTTP enabled'); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|          |  | ||||||
|       case 'https-passthrough': |  | ||||||
|         // HTTPS passthrough doesn't support HTTP |  | ||||||
|         if (config.http?.enabled === true) { |  | ||||||
|           throw new Error('HTTPS passthrough does not support HTTP'); |  | ||||||
|         } |  | ||||||
|          |  | ||||||
|         // HTTPS passthrough doesn't work with ACME |  | ||||||
|         if (config.acme?.enabled === true) { |  | ||||||
|           throw new Error('HTTPS passthrough does not support ACME'); |  | ||||||
|         } |  | ||||||
|         break; |  | ||||||
|          |  | ||||||
|       case 'https-terminate-to-http': |  | ||||||
|       case 'https-terminate-to-https': |  | ||||||
|         // These modes support all options, nothing specific to validate |  | ||||||
|         break; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,127 +0,0 @@ | |||||||
| import * as plugins from '../../plugins.js'; |  | ||||||
| import type { |  | ||||||
|   IForwardConfig, |  | ||||||
|   IForwardingHandler |  | ||||||
| } from '../types/forwarding.types.js'; |  | ||||||
| import { ForwardingHandlerEvents } from '../types/forwarding.types.js'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Base class for all forwarding handlers |  | ||||||
|  */ |  | ||||||
| export abstract class ForwardingHandler extends plugins.EventEmitter implements IForwardingHandler { |  | ||||||
|   /** |  | ||||||
|    * Create a new ForwardingHandler |  | ||||||
|    * @param config The forwarding configuration |  | ||||||
|    */ |  | ||||||
|   constructor(protected config: IForwardConfig) { |  | ||||||
|     super(); |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Initialize the handler |  | ||||||
|    * Base implementation does nothing, subclasses should override as needed |  | ||||||
|    */ |  | ||||||
|   public async initialize(): Promise<void> { |  | ||||||
|     // Base implementation - no initialization needed |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * Handle a new socket connection |  | ||||||
|    * @param socket The incoming socket connection |  | ||||||
|    */ |  | ||||||
|   public abstract handleConnection(socket: plugins.net.Socket): void; |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Handle an HTTP request |  | ||||||
|    * @param req The HTTP request |  | ||||||
|    * @param res The HTTP response |  | ||||||
|    */ |  | ||||||
|   public abstract handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void; |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Get a target from the configuration, supporting round-robin selection |  | ||||||
|    * @returns A resolved target object with host and port |  | ||||||
|    */ |  | ||||||
|   protected getTargetFromConfig(): { host: string, port: number } { |  | ||||||
|     const { target } = this.config; |  | ||||||
|      |  | ||||||
|     // Handle round-robin host selection |  | ||||||
|     if (Array.isArray(target.host)) { |  | ||||||
|       if (target.host.length === 0) { |  | ||||||
|         throw new Error('No target hosts specified'); |  | ||||||
|       } |  | ||||||
|        |  | ||||||
|       // Simple round-robin selection |  | ||||||
|       const randomIndex = Math.floor(Math.random() * target.host.length); |  | ||||||
|       return { |  | ||||||
|         host: target.host[randomIndex], |  | ||||||
|         port: target.port |  | ||||||
|       }; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Single host |  | ||||||
|     return { |  | ||||||
|       host: target.host, |  | ||||||
|       port: target.port |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Redirect an HTTP request to HTTPS |  | ||||||
|    * @param req The HTTP request |  | ||||||
|    * @param res The HTTP response |  | ||||||
|    */ |  | ||||||
|   protected redirectToHttps(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void { |  | ||||||
|     const host = req.headers.host || ''; |  | ||||||
|     const path = req.url || '/'; |  | ||||||
|     const redirectUrl = `https://${host}${path}`; |  | ||||||
|      |  | ||||||
|     res.writeHead(301, { |  | ||||||
|       'Location': redirectUrl, |  | ||||||
|       'Cache-Control': 'no-cache' |  | ||||||
|     }); |  | ||||||
|     res.end(`Redirecting to ${redirectUrl}`); |  | ||||||
|      |  | ||||||
|     this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, { |  | ||||||
|       statusCode: 301, |  | ||||||
|       headers: { 'Location': redirectUrl }, |  | ||||||
|       size: 0 |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Apply custom headers from configuration |  | ||||||
|    * @param headers The original headers |  | ||||||
|    * @param variables Variables to replace in the headers |  | ||||||
|    * @returns The headers with custom values applied |  | ||||||
|    */ |  | ||||||
|   protected applyCustomHeaders( |  | ||||||
|     headers: Record<string, string | string[] | undefined>, |  | ||||||
|     variables: Record<string, string> |  | ||||||
|   ): Record<string, string | string[] | undefined> { |  | ||||||
|     const customHeaders = this.config.advanced?.headers || {}; |  | ||||||
|     const result = { ...headers }; |  | ||||||
|      |  | ||||||
|     // Apply custom headers with variable substitution |  | ||||||
|     for (const [key, value] of Object.entries(customHeaders)) { |  | ||||||
|       let processedValue = value; |  | ||||||
|        |  | ||||||
|       // Replace variables in the header value |  | ||||||
|       for (const [varName, varValue] of Object.entries(variables)) { |  | ||||||
|         processedValue = processedValue.replace(`{${varName}}`, varValue); |  | ||||||
|       } |  | ||||||
|        |  | ||||||
|       result[key] = processedValue; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     return result; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Get the timeout for this connection from configuration |  | ||||||
|    * @returns Timeout in milliseconds |  | ||||||
|    */ |  | ||||||
|   protected getTimeout(): number { |  | ||||||
|     return this.config.advanced?.timeout || 60000; // Default: 60 seconds |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,140 +0,0 @@ | |||||||
| import * as plugins from '../../plugins.js'; |  | ||||||
| import { ForwardingHandler } from './forwarding.handler.js'; |  | ||||||
| import type { IForwardConfig } from '../types/forwarding.types.js'; |  | ||||||
| import { ForwardingHandlerEvents } from '../types/forwarding.types.js'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Handler for HTTP-only forwarding |  | ||||||
|  */ |  | ||||||
| export class HttpForwardingHandler extends ForwardingHandler { |  | ||||||
|   /** |  | ||||||
|    * Create a new HTTP forwarding handler |  | ||||||
|    * @param config The forwarding configuration |  | ||||||
|    */ |  | ||||||
|   constructor(config: IForwardConfig) { |  | ||||||
|     super(config); |  | ||||||
|      |  | ||||||
|     // Validate that this is an HTTP-only configuration |  | ||||||
|     if (config.type !== 'http-only') { |  | ||||||
|       throw new Error(`Invalid configuration type for HttpForwardingHandler: ${config.type}`); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Handle a raw socket connection |  | ||||||
|    * HTTP handler doesn't do much with raw sockets as it mainly processes |  | ||||||
|    * parsed HTTP requests |  | ||||||
|    */ |  | ||||||
|   public handleConnection(socket: plugins.net.Socket): void { |  | ||||||
|     // For HTTP, we mainly handle parsed requests, but we can still set up |  | ||||||
|     // some basic connection tracking |  | ||||||
|     const remoteAddress = socket.remoteAddress || 'unknown'; |  | ||||||
|      |  | ||||||
|     socket.on('close', (hadError) => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.DISCONNECTED, { |  | ||||||
|         remoteAddress, |  | ||||||
|         hadError |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     socket.on('error', (error) => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress, |  | ||||||
|         error: error.message |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     this.emit(ForwardingHandlerEvents.CONNECTED, { |  | ||||||
|       remoteAddress |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Handle an HTTP request |  | ||||||
|    * @param req The HTTP request |  | ||||||
|    * @param res The HTTP response |  | ||||||
|    */ |  | ||||||
|   public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void { |  | ||||||
|     // Get the target from configuration |  | ||||||
|     const target = this.getTargetFromConfig(); |  | ||||||
|      |  | ||||||
|     // Create a custom headers object with variables for substitution |  | ||||||
|     const variables = { |  | ||||||
|       clientIp: req.socket.remoteAddress || 'unknown' |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     // Prepare headers, merging with any custom headers from config |  | ||||||
|     const headers = this.applyCustomHeaders(req.headers, variables); |  | ||||||
|      |  | ||||||
|     // Create the proxy request options |  | ||||||
|     const options = { |  | ||||||
|       hostname: target.host, |  | ||||||
|       port: target.port, |  | ||||||
|       path: req.url, |  | ||||||
|       method: req.method, |  | ||||||
|       headers |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     // Create the proxy request |  | ||||||
|     const proxyReq = plugins.http.request(options, (proxyRes) => { |  | ||||||
|       // Copy status code and headers from the proxied response |  | ||||||
|       res.writeHead(proxyRes.statusCode || 500, proxyRes.headers); |  | ||||||
|        |  | ||||||
|       // Pipe the proxy response to the client response |  | ||||||
|       proxyRes.pipe(res); |  | ||||||
|        |  | ||||||
|       // Track bytes for logging |  | ||||||
|       let responseSize = 0; |  | ||||||
|       proxyRes.on('data', (chunk) => { |  | ||||||
|         responseSize += chunk.length; |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       proxyRes.on('end', () => { |  | ||||||
|         this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, { |  | ||||||
|           statusCode: proxyRes.statusCode, |  | ||||||
|           headers: proxyRes.headers, |  | ||||||
|           size: responseSize |  | ||||||
|         }); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Handle errors in the proxy request |  | ||||||
|     proxyReq.on('error', (error) => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress: req.socket.remoteAddress, |  | ||||||
|         error: `Proxy request error: ${error.message}` |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       // Send an error response if headers haven't been sent yet |  | ||||||
|       if (!res.headersSent) { |  | ||||||
|         res.writeHead(502, { 'Content-Type': 'text/plain' }); |  | ||||||
|         res.end(`Error forwarding request: ${error.message}`); |  | ||||||
|       } else { |  | ||||||
|         // Just end the response if headers have already been sent |  | ||||||
|         res.end(); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Track request details for logging |  | ||||||
|     let requestSize = 0; |  | ||||||
|     req.on('data', (chunk) => { |  | ||||||
|       requestSize += chunk.length; |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Log the request |  | ||||||
|     this.emit(ForwardingHandlerEvents.HTTP_REQUEST, { |  | ||||||
|       method: req.method, |  | ||||||
|       url: req.url, |  | ||||||
|       headers: req.headers, |  | ||||||
|       remoteAddress: req.socket.remoteAddress, |  | ||||||
|       target: `${target.host}:${target.port}` |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Pipe the client request to the proxy request |  | ||||||
|     if (req.readable) { |  | ||||||
|       req.pipe(proxyReq); |  | ||||||
|     } else { |  | ||||||
|       proxyReq.end(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,182 +0,0 @@ | |||||||
| import * as plugins from '../../plugins.js'; |  | ||||||
| import { ForwardingHandler } from './forwarding.handler.js'; |  | ||||||
| import type { IForwardConfig } from '../types/forwarding.types.js'; |  | ||||||
| import { ForwardingHandlerEvents } from '../types/forwarding.types.js'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Handler for HTTPS passthrough (SNI forwarding without termination) |  | ||||||
|  */ |  | ||||||
| export class HttpsPassthroughHandler extends ForwardingHandler { |  | ||||||
|   /** |  | ||||||
|    * Create a new HTTPS passthrough handler |  | ||||||
|    * @param config The forwarding configuration |  | ||||||
|    */ |  | ||||||
|   constructor(config: IForwardConfig) { |  | ||||||
|     super(config); |  | ||||||
|      |  | ||||||
|     // Validate that this is an HTTPS passthrough configuration |  | ||||||
|     if (config.type !== 'https-passthrough') { |  | ||||||
|       throw new Error(`Invalid configuration type for HttpsPassthroughHandler: ${config.type}`); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Handle a TLS/SSL socket connection by forwarding it without termination |  | ||||||
|    * @param clientSocket The incoming socket from the client |  | ||||||
|    */ |  | ||||||
|   public handleConnection(clientSocket: plugins.net.Socket): void { |  | ||||||
|     // Get the target from configuration |  | ||||||
|     const target = this.getTargetFromConfig(); |  | ||||||
|      |  | ||||||
|     // Log the connection |  | ||||||
|     const remoteAddress = clientSocket.remoteAddress || 'unknown'; |  | ||||||
|     const remotePort = clientSocket.remotePort || 0; |  | ||||||
|      |  | ||||||
|     this.emit(ForwardingHandlerEvents.CONNECTED, { |  | ||||||
|       remoteAddress, |  | ||||||
|       remotePort, |  | ||||||
|       target: `${target.host}:${target.port}` |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Create a connection to the target server |  | ||||||
|     const serverSocket = plugins.net.connect(target.port, target.host); |  | ||||||
|      |  | ||||||
|     // Handle errors on the server socket |  | ||||||
|     serverSocket.on('error', (error) => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress, |  | ||||||
|         error: `Target connection error: ${error.message}` |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       // Close the client socket if it's still open |  | ||||||
|       if (!clientSocket.destroyed) { |  | ||||||
|         clientSocket.destroy(); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Handle errors on the client socket |  | ||||||
|     clientSocket.on('error', (error) => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress, |  | ||||||
|         error: `Client connection error: ${error.message}` |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       // Close the server socket if it's still open |  | ||||||
|       if (!serverSocket.destroyed) { |  | ||||||
|         serverSocket.destroy(); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Track data transfer for logging |  | ||||||
|     let bytesSent = 0; |  | ||||||
|     let bytesReceived = 0; |  | ||||||
|      |  | ||||||
|     // Forward data from client to server |  | ||||||
|     clientSocket.on('data', (data) => { |  | ||||||
|       bytesSent += data.length; |  | ||||||
|        |  | ||||||
|       // Check if server socket is writable |  | ||||||
|       if (serverSocket.writable) { |  | ||||||
|         const flushed = serverSocket.write(data); |  | ||||||
|          |  | ||||||
|         // Handle backpressure |  | ||||||
|         if (!flushed) { |  | ||||||
|           clientSocket.pause(); |  | ||||||
|           serverSocket.once('drain', () => { |  | ||||||
|             clientSocket.resume(); |  | ||||||
|           }); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|        |  | ||||||
|       this.emit(ForwardingHandlerEvents.DATA_FORWARDED, { |  | ||||||
|         direction: 'outbound', |  | ||||||
|         bytes: data.length, |  | ||||||
|         total: bytesSent |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Forward data from server to client |  | ||||||
|     serverSocket.on('data', (data) => { |  | ||||||
|       bytesReceived += data.length; |  | ||||||
|        |  | ||||||
|       // Check if client socket is writable |  | ||||||
|       if (clientSocket.writable) { |  | ||||||
|         const flushed = clientSocket.write(data); |  | ||||||
|          |  | ||||||
|         // Handle backpressure |  | ||||||
|         if (!flushed) { |  | ||||||
|           serverSocket.pause(); |  | ||||||
|           clientSocket.once('drain', () => { |  | ||||||
|             serverSocket.resume(); |  | ||||||
|           }); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|        |  | ||||||
|       this.emit(ForwardingHandlerEvents.DATA_FORWARDED, { |  | ||||||
|         direction: 'inbound', |  | ||||||
|         bytes: data.length, |  | ||||||
|         total: bytesReceived |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Handle connection close |  | ||||||
|     const handleClose = () => { |  | ||||||
|       if (!clientSocket.destroyed) { |  | ||||||
|         clientSocket.destroy(); |  | ||||||
|       } |  | ||||||
|        |  | ||||||
|       if (!serverSocket.destroyed) { |  | ||||||
|         serverSocket.destroy(); |  | ||||||
|       } |  | ||||||
|        |  | ||||||
|       this.emit(ForwardingHandlerEvents.DISCONNECTED, { |  | ||||||
|         remoteAddress, |  | ||||||
|         bytesSent, |  | ||||||
|         bytesReceived |  | ||||||
|       }); |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     // Set up close handlers |  | ||||||
|     clientSocket.on('close', handleClose); |  | ||||||
|     serverSocket.on('close', handleClose); |  | ||||||
|      |  | ||||||
|     // Set timeouts |  | ||||||
|     const timeout = this.getTimeout(); |  | ||||||
|     clientSocket.setTimeout(timeout); |  | ||||||
|     serverSocket.setTimeout(timeout); |  | ||||||
|      |  | ||||||
|     // Handle timeouts |  | ||||||
|     clientSocket.on('timeout', () => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress, |  | ||||||
|         error: 'Client connection timeout' |  | ||||||
|       }); |  | ||||||
|       handleClose(); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     serverSocket.on('timeout', () => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress, |  | ||||||
|         error: 'Server connection timeout' |  | ||||||
|       }); |  | ||||||
|       handleClose(); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Handle an HTTP request - HTTPS passthrough doesn't support HTTP |  | ||||||
|    * @param req The HTTP request |  | ||||||
|    * @param res The HTTP response |  | ||||||
|    */ |  | ||||||
|   public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void { |  | ||||||
|     // HTTPS passthrough doesn't support HTTP requests |  | ||||||
|     res.writeHead(404, { 'Content-Type': 'text/plain' }); |  | ||||||
|     res.end('HTTP not supported for this domain'); |  | ||||||
|      |  | ||||||
|     this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, { |  | ||||||
|       statusCode: 404, |  | ||||||
|       headers: { 'Content-Type': 'text/plain' }, |  | ||||||
|       size: 'HTTP not supported for this domain'.length |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,264 +0,0 @@ | |||||||
| import * as plugins from '../../plugins.js'; |  | ||||||
| import { ForwardingHandler } from './forwarding.handler.js'; |  | ||||||
| import type { IForwardConfig } from '../types/forwarding.types.js'; |  | ||||||
| import { ForwardingHandlerEvents } from '../types/forwarding.types.js'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Handler for HTTPS termination with HTTP backend |  | ||||||
|  */ |  | ||||||
| export class HttpsTerminateToHttpHandler extends ForwardingHandler { |  | ||||||
|   private tlsServer: plugins.tls.Server | null = null; |  | ||||||
|   private secureContext: plugins.tls.SecureContext | null = null; |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Create a new HTTPS termination with HTTP backend handler |  | ||||||
|    * @param config The forwarding configuration |  | ||||||
|    */ |  | ||||||
|   constructor(config: IForwardConfig) { |  | ||||||
|     super(config); |  | ||||||
|      |  | ||||||
|     // Validate that this is an HTTPS terminate to HTTP configuration |  | ||||||
|     if (config.type !== 'https-terminate-to-http') { |  | ||||||
|       throw new Error(`Invalid configuration type for HttpsTerminateToHttpHandler: ${config.type}`); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Initialize the handler, setting up TLS context |  | ||||||
|    */ |  | ||||||
|   public async initialize(): Promise<void> { |  | ||||||
|     // We need to load or create TLS certificates |  | ||||||
|     if (this.config.https?.customCert) { |  | ||||||
|       // Use custom certificate from configuration |  | ||||||
|       this.secureContext = plugins.tls.createSecureContext({ |  | ||||||
|         key: this.config.https.customCert.key, |  | ||||||
|         cert: this.config.https.customCert.cert |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       this.emit(ForwardingHandlerEvents.CERTIFICATE_LOADED, { |  | ||||||
|         source: 'config', |  | ||||||
|         domain: this.config.target.host |  | ||||||
|       }); |  | ||||||
|     } else if (this.config.acme?.enabled) { |  | ||||||
|       // Request certificate through ACME if needed |  | ||||||
|       this.emit(ForwardingHandlerEvents.CERTIFICATE_NEEDED, { |  | ||||||
|         domain: Array.isArray(this.config.target.host)  |  | ||||||
|           ? this.config.target.host[0]  |  | ||||||
|           : this.config.target.host, |  | ||||||
|         useProduction: this.config.acme.production || false |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       // In a real implementation, we would wait for the certificate to be issued |  | ||||||
|       // For now, we'll use a dummy context |  | ||||||
|       this.secureContext = plugins.tls.createSecureContext({ |  | ||||||
|         key: '-----BEGIN PRIVATE KEY-----\nDummy key\n-----END PRIVATE KEY-----', |  | ||||||
|         cert: '-----BEGIN CERTIFICATE-----\nDummy cert\n-----END CERTIFICATE-----' |  | ||||||
|       }); |  | ||||||
|     } else { |  | ||||||
|       throw new Error('HTTPS termination requires either a custom certificate or ACME enabled'); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Set the secure context for TLS termination |  | ||||||
|    * Called when a certificate is available |  | ||||||
|    * @param context The secure context |  | ||||||
|    */ |  | ||||||
|   public setSecureContext(context: plugins.tls.SecureContext): void { |  | ||||||
|     this.secureContext = context; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Handle a TLS/SSL socket connection by terminating TLS and forwarding to HTTP backend |  | ||||||
|    * @param clientSocket The incoming socket from the client |  | ||||||
|    */ |  | ||||||
|   public handleConnection(clientSocket: plugins.net.Socket): void { |  | ||||||
|     // Make sure we have a secure context |  | ||||||
|     if (!this.secureContext) { |  | ||||||
|       clientSocket.destroy(new Error('TLS secure context not initialized')); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     const remoteAddress = clientSocket.remoteAddress || 'unknown'; |  | ||||||
|     const remotePort = clientSocket.remotePort || 0; |  | ||||||
|      |  | ||||||
|     // Create a TLS socket using our secure context |  | ||||||
|     const tlsSocket = new plugins.tls.TLSSocket(clientSocket, { |  | ||||||
|       secureContext: this.secureContext, |  | ||||||
|       isServer: true, |  | ||||||
|       server: this.tlsServer || undefined |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     this.emit(ForwardingHandlerEvents.CONNECTED, { |  | ||||||
|       remoteAddress, |  | ||||||
|       remotePort, |  | ||||||
|       tls: true |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Handle TLS errors |  | ||||||
|     tlsSocket.on('error', (error) => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress, |  | ||||||
|         error: `TLS error: ${error.message}` |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       if (!tlsSocket.destroyed) { |  | ||||||
|         tlsSocket.destroy(); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // The TLS socket will now emit HTTP traffic that can be processed |  | ||||||
|     // In a real implementation, we would create an HTTP parser and handle |  | ||||||
|     // the requests here, but for simplicity, we'll just log the data |  | ||||||
|      |  | ||||||
|     let dataBuffer = Buffer.alloc(0); |  | ||||||
|      |  | ||||||
|     tlsSocket.on('data', (data) => { |  | ||||||
|       // Append to buffer |  | ||||||
|       dataBuffer = Buffer.concat([dataBuffer, data]); |  | ||||||
|        |  | ||||||
|       // Very basic HTTP parsing - in a real implementation, use http-parser |  | ||||||
|       if (dataBuffer.includes(Buffer.from('\r\n\r\n'))) { |  | ||||||
|         const target = this.getTargetFromConfig(); |  | ||||||
|          |  | ||||||
|         // Simple example: forward the data to an HTTP server |  | ||||||
|         const socket = plugins.net.connect(target.port, target.host, () => { |  | ||||||
|           socket.write(dataBuffer); |  | ||||||
|           dataBuffer = Buffer.alloc(0); |  | ||||||
|            |  | ||||||
|           // Set up bidirectional data flow |  | ||||||
|           tlsSocket.pipe(socket); |  | ||||||
|           socket.pipe(tlsSocket); |  | ||||||
|         }); |  | ||||||
|          |  | ||||||
|         socket.on('error', (error) => { |  | ||||||
|           this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|             remoteAddress, |  | ||||||
|             error: `Target connection error: ${error.message}` |  | ||||||
|           }); |  | ||||||
|            |  | ||||||
|           if (!tlsSocket.destroyed) { |  | ||||||
|             tlsSocket.destroy(); |  | ||||||
|           } |  | ||||||
|         }); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Handle close |  | ||||||
|     tlsSocket.on('close', () => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.DISCONNECTED, { |  | ||||||
|         remoteAddress |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Set timeout |  | ||||||
|     const timeout = this.getTimeout(); |  | ||||||
|     tlsSocket.setTimeout(timeout); |  | ||||||
|      |  | ||||||
|     tlsSocket.on('timeout', () => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress, |  | ||||||
|         error: 'TLS connection timeout' |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       if (!tlsSocket.destroyed) { |  | ||||||
|         tlsSocket.destroy(); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Handle an HTTP request by forwarding to the HTTP backend |  | ||||||
|    * @param req The HTTP request |  | ||||||
|    * @param res The HTTP response |  | ||||||
|    */ |  | ||||||
|   public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void { |  | ||||||
|     // Check if we should redirect to HTTPS |  | ||||||
|     if (this.config.http?.redirectToHttps) { |  | ||||||
|       this.redirectToHttps(req, res); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Get the target from configuration |  | ||||||
|     const target = this.getTargetFromConfig(); |  | ||||||
|      |  | ||||||
|     // Create custom headers with variable substitution |  | ||||||
|     const variables = { |  | ||||||
|       clientIp: req.socket.remoteAddress || 'unknown' |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     // Prepare headers, merging with any custom headers from config |  | ||||||
|     const headers = this.applyCustomHeaders(req.headers, variables); |  | ||||||
|      |  | ||||||
|     // Create the proxy request options |  | ||||||
|     const options = { |  | ||||||
|       hostname: target.host, |  | ||||||
|       port: target.port, |  | ||||||
|       path: req.url, |  | ||||||
|       method: req.method, |  | ||||||
|       headers |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     // Create the proxy request |  | ||||||
|     const proxyReq = plugins.http.request(options, (proxyRes) => { |  | ||||||
|       // Copy status code and headers from the proxied response |  | ||||||
|       res.writeHead(proxyRes.statusCode || 500, proxyRes.headers); |  | ||||||
|        |  | ||||||
|       // Pipe the proxy response to the client response |  | ||||||
|       proxyRes.pipe(res); |  | ||||||
|        |  | ||||||
|       // Track response size for logging |  | ||||||
|       let responseSize = 0; |  | ||||||
|       proxyRes.on('data', (chunk) => { |  | ||||||
|         responseSize += chunk.length; |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       proxyRes.on('end', () => { |  | ||||||
|         this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, { |  | ||||||
|           statusCode: proxyRes.statusCode, |  | ||||||
|           headers: proxyRes.headers, |  | ||||||
|           size: responseSize |  | ||||||
|         }); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Handle errors in the proxy request |  | ||||||
|     proxyReq.on('error', (error) => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress: req.socket.remoteAddress, |  | ||||||
|         error: `Proxy request error: ${error.message}` |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       // Send an error response if headers haven't been sent yet |  | ||||||
|       if (!res.headersSent) { |  | ||||||
|         res.writeHead(502, { 'Content-Type': 'text/plain' }); |  | ||||||
|         res.end(`Error forwarding request: ${error.message}`); |  | ||||||
|       } else { |  | ||||||
|         // Just end the response if headers have already been sent |  | ||||||
|         res.end(); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Track request details for logging |  | ||||||
|     let requestSize = 0; |  | ||||||
|     req.on('data', (chunk) => { |  | ||||||
|       requestSize += chunk.length; |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Log the request |  | ||||||
|     this.emit(ForwardingHandlerEvents.HTTP_REQUEST, { |  | ||||||
|       method: req.method, |  | ||||||
|       url: req.url, |  | ||||||
|       headers: req.headers, |  | ||||||
|       remoteAddress: req.socket.remoteAddress, |  | ||||||
|       target: `${target.host}:${target.port}` |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Pipe the client request to the proxy request |  | ||||||
|     if (req.readable) { |  | ||||||
|       req.pipe(proxyReq); |  | ||||||
|     } else { |  | ||||||
|       proxyReq.end(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,292 +0,0 @@ | |||||||
| import * as plugins from '../../plugins.js'; |  | ||||||
| import { ForwardingHandler } from './forwarding.handler.js'; |  | ||||||
| import type { IForwardConfig } from '../types/forwarding.types.js'; |  | ||||||
| import { ForwardingHandlerEvents } from '../types/forwarding.types.js'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Handler for HTTPS termination with HTTPS backend |  | ||||||
|  */ |  | ||||||
| export class HttpsTerminateToHttpsHandler extends ForwardingHandler { |  | ||||||
|   private secureContext: plugins.tls.SecureContext | null = null; |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Create a new HTTPS termination with HTTPS backend handler |  | ||||||
|    * @param config The forwarding configuration |  | ||||||
|    */ |  | ||||||
|   constructor(config: IForwardConfig) { |  | ||||||
|     super(config); |  | ||||||
|      |  | ||||||
|     // Validate that this is an HTTPS terminate to HTTPS configuration |  | ||||||
|     if (config.type !== 'https-terminate-to-https') { |  | ||||||
|       throw new Error(`Invalid configuration type for HttpsTerminateToHttpsHandler: ${config.type}`); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Initialize the handler, setting up TLS context |  | ||||||
|    */ |  | ||||||
|   public async initialize(): Promise<void> { |  | ||||||
|     // We need to load or create TLS certificates for termination |  | ||||||
|     if (this.config.https?.customCert) { |  | ||||||
|       // Use custom certificate from configuration |  | ||||||
|       this.secureContext = plugins.tls.createSecureContext({ |  | ||||||
|         key: this.config.https.customCert.key, |  | ||||||
|         cert: this.config.https.customCert.cert |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       this.emit(ForwardingHandlerEvents.CERTIFICATE_LOADED, { |  | ||||||
|         source: 'config', |  | ||||||
|         domain: this.config.target.host |  | ||||||
|       }); |  | ||||||
|     } else if (this.config.acme?.enabled) { |  | ||||||
|       // Request certificate through ACME if needed |  | ||||||
|       this.emit(ForwardingHandlerEvents.CERTIFICATE_NEEDED, { |  | ||||||
|         domain: Array.isArray(this.config.target.host)  |  | ||||||
|           ? this.config.target.host[0]  |  | ||||||
|           : this.config.target.host, |  | ||||||
|         useProduction: this.config.acme.production || false |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       // In a real implementation, we would wait for the certificate to be issued |  | ||||||
|       // For now, we'll use a dummy context |  | ||||||
|       this.secureContext = plugins.tls.createSecureContext({ |  | ||||||
|         key: '-----BEGIN PRIVATE KEY-----\nDummy key\n-----END PRIVATE KEY-----', |  | ||||||
|         cert: '-----BEGIN CERTIFICATE-----\nDummy cert\n-----END CERTIFICATE-----' |  | ||||||
|       }); |  | ||||||
|     } else { |  | ||||||
|       throw new Error('HTTPS termination requires either a custom certificate or ACME enabled'); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Set the secure context for TLS termination |  | ||||||
|    * Called when a certificate is available |  | ||||||
|    * @param context The secure context |  | ||||||
|    */ |  | ||||||
|   public setSecureContext(context: plugins.tls.SecureContext): void { |  | ||||||
|     this.secureContext = context; |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Handle a TLS/SSL socket connection by terminating TLS and creating a new TLS connection to backend |  | ||||||
|    * @param clientSocket The incoming socket from the client |  | ||||||
|    */ |  | ||||||
|   public handleConnection(clientSocket: plugins.net.Socket): void { |  | ||||||
|     // Make sure we have a secure context |  | ||||||
|     if (!this.secureContext) { |  | ||||||
|       clientSocket.destroy(new Error('TLS secure context not initialized')); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     const remoteAddress = clientSocket.remoteAddress || 'unknown'; |  | ||||||
|     const remotePort = clientSocket.remotePort || 0; |  | ||||||
|      |  | ||||||
|     // Create a TLS socket using our secure context |  | ||||||
|     const tlsSocket = new plugins.tls.TLSSocket(clientSocket, { |  | ||||||
|       secureContext: this.secureContext, |  | ||||||
|       isServer: true |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     this.emit(ForwardingHandlerEvents.CONNECTED, { |  | ||||||
|       remoteAddress, |  | ||||||
|       remotePort, |  | ||||||
|       tls: true |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Handle TLS errors |  | ||||||
|     tlsSocket.on('error', (error) => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress, |  | ||||||
|         error: `TLS error: ${error.message}` |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       if (!tlsSocket.destroyed) { |  | ||||||
|         tlsSocket.destroy(); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // The TLS socket will now emit HTTP traffic that can be processed |  | ||||||
|     // In a real implementation, we would create an HTTP parser and handle |  | ||||||
|     // the requests here, but for simplicity, we'll just forward the data |  | ||||||
|      |  | ||||||
|     // Get the target from configuration |  | ||||||
|     const target = this.getTargetFromConfig(); |  | ||||||
|      |  | ||||||
|     // Set up the connection to the HTTPS backend |  | ||||||
|     const connectToBackend = () => { |  | ||||||
|       const backendSocket = plugins.tls.connect({ |  | ||||||
|         host: target.host, |  | ||||||
|         port: target.port, |  | ||||||
|         // In a real implementation, we would configure TLS options |  | ||||||
|         rejectUnauthorized: false // For testing only, never use in production |  | ||||||
|       }, () => { |  | ||||||
|         this.emit(ForwardingHandlerEvents.DATA_FORWARDED, { |  | ||||||
|           direction: 'outbound', |  | ||||||
|           target: `${target.host}:${target.port}`, |  | ||||||
|           tls: true |  | ||||||
|         }); |  | ||||||
|          |  | ||||||
|         // Set up bidirectional data flow |  | ||||||
|         tlsSocket.pipe(backendSocket); |  | ||||||
|         backendSocket.pipe(tlsSocket); |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       backendSocket.on('error', (error) => { |  | ||||||
|         this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|           remoteAddress, |  | ||||||
|           error: `Backend connection error: ${error.message}` |  | ||||||
|         }); |  | ||||||
|          |  | ||||||
|         if (!tlsSocket.destroyed) { |  | ||||||
|           tlsSocket.destroy(); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       // Handle close |  | ||||||
|       backendSocket.on('close', () => { |  | ||||||
|         if (!tlsSocket.destroyed) { |  | ||||||
|           tlsSocket.destroy(); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       // Set timeout |  | ||||||
|       const timeout = this.getTimeout(); |  | ||||||
|       backendSocket.setTimeout(timeout); |  | ||||||
|        |  | ||||||
|       backendSocket.on('timeout', () => { |  | ||||||
|         this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|           remoteAddress, |  | ||||||
|           error: 'Backend connection timeout' |  | ||||||
|         }); |  | ||||||
|          |  | ||||||
|         if (!backendSocket.destroyed) { |  | ||||||
|           backendSocket.destroy(); |  | ||||||
|         } |  | ||||||
|       }); |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     // Wait for the TLS handshake to complete before connecting to backend |  | ||||||
|     tlsSocket.on('secure', () => { |  | ||||||
|       connectToBackend(); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Handle close |  | ||||||
|     tlsSocket.on('close', () => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.DISCONNECTED, { |  | ||||||
|         remoteAddress |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Set timeout |  | ||||||
|     const timeout = this.getTimeout(); |  | ||||||
|     tlsSocket.setTimeout(timeout); |  | ||||||
|      |  | ||||||
|     tlsSocket.on('timeout', () => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress, |  | ||||||
|         error: 'TLS connection timeout' |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       if (!tlsSocket.destroyed) { |  | ||||||
|         tlsSocket.destroy(); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|    |  | ||||||
|   /** |  | ||||||
|    * Handle an HTTP request by forwarding to the HTTPS backend |  | ||||||
|    * @param req The HTTP request |  | ||||||
|    * @param res The HTTP response |  | ||||||
|    */ |  | ||||||
|   public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void { |  | ||||||
|     // Check if we should redirect to HTTPS |  | ||||||
|     if (this.config.http?.redirectToHttps) { |  | ||||||
|       this.redirectToHttps(req, res); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
|     // Get the target from configuration |  | ||||||
|     const target = this.getTargetFromConfig(); |  | ||||||
|      |  | ||||||
|     // Create custom headers with variable substitution |  | ||||||
|     const variables = { |  | ||||||
|       clientIp: req.socket.remoteAddress || 'unknown' |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     // Prepare headers, merging with any custom headers from config |  | ||||||
|     const headers = this.applyCustomHeaders(req.headers, variables); |  | ||||||
|      |  | ||||||
|     // Create the proxy request options |  | ||||||
|     const options = { |  | ||||||
|       hostname: target.host, |  | ||||||
|       port: target.port, |  | ||||||
|       path: req.url, |  | ||||||
|       method: req.method, |  | ||||||
|       headers, |  | ||||||
|       // In a real implementation, we would configure TLS options |  | ||||||
|       rejectUnauthorized: false // For testing only, never use in production |  | ||||||
|     }; |  | ||||||
|      |  | ||||||
|     // Create the proxy request using HTTPS |  | ||||||
|     const proxyReq = plugins.https.request(options, (proxyRes) => { |  | ||||||
|       // Copy status code and headers from the proxied response |  | ||||||
|       res.writeHead(proxyRes.statusCode || 500, proxyRes.headers); |  | ||||||
|        |  | ||||||
|       // Pipe the proxy response to the client response |  | ||||||
|       proxyRes.pipe(res); |  | ||||||
|        |  | ||||||
|       // Track response size for logging |  | ||||||
|       let responseSize = 0; |  | ||||||
|       proxyRes.on('data', (chunk) => { |  | ||||||
|         responseSize += chunk.length; |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       proxyRes.on('end', () => { |  | ||||||
|         this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, { |  | ||||||
|           statusCode: proxyRes.statusCode, |  | ||||||
|           headers: proxyRes.headers, |  | ||||||
|           size: responseSize |  | ||||||
|         }); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Handle errors in the proxy request |  | ||||||
|     proxyReq.on('error', (error) => { |  | ||||||
|       this.emit(ForwardingHandlerEvents.ERROR, { |  | ||||||
|         remoteAddress: req.socket.remoteAddress, |  | ||||||
|         error: `Proxy request error: ${error.message}` |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       // Send an error response if headers haven't been sent yet |  | ||||||
|       if (!res.headersSent) { |  | ||||||
|         res.writeHead(502, { 'Content-Type': 'text/plain' }); |  | ||||||
|         res.end(`Error forwarding request: ${error.message}`); |  | ||||||
|       } else { |  | ||||||
|         // Just end the response if headers have already been sent |  | ||||||
|         res.end(); |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Track request details for logging |  | ||||||
|     let requestSize = 0; |  | ||||||
|     req.on('data', (chunk) => { |  | ||||||
|       requestSize += chunk.length; |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Log the request |  | ||||||
|     this.emit(ForwardingHandlerEvents.HTTP_REQUEST, { |  | ||||||
|       method: req.method, |  | ||||||
|       url: req.url, |  | ||||||
|       headers: req.headers, |  | ||||||
|       remoteAddress: req.socket.remoteAddress, |  | ||||||
|       target: `${target.host}:${target.port}` |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     // Pipe the client request to the proxy request |  | ||||||
|     if (req.readable) { |  | ||||||
|       req.pipe(proxyReq); |  | ||||||
|     } else { |  | ||||||
|       proxyReq.end(); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -1,52 +0,0 @@ | |||||||
| // Export types |  | ||||||
| export type { |  | ||||||
|   ForwardingType, |  | ||||||
|   IForwardConfig, |  | ||||||
|   IForwardingHandler, |  | ||||||
|   ITargetConfig, |  | ||||||
|   IHttpOptions, |  | ||||||
|   IHttpsOptions, |  | ||||||
|   IAcmeForwardingOptions, |  | ||||||
|   ISecurityOptions, |  | ||||||
|   IAdvancedOptions |  | ||||||
| } from '../types/forwarding.types.js'; |  | ||||||
|  |  | ||||||
| // Export values |  | ||||||
| export { |  | ||||||
|   ForwardingHandlerEvents, |  | ||||||
|   httpOnly, |  | ||||||
|   tlsTerminateToHttp, |  | ||||||
|   tlsTerminateToHttps, |  | ||||||
|   httpsPassthrough |  | ||||||
| } from '../types/forwarding.types.js'; |  | ||||||
|  |  | ||||||
| // Export domain configuration |  | ||||||
| export * from './domain-config.js'; |  | ||||||
|  |  | ||||||
| // Export handlers |  | ||||||
| export { ForwardingHandler } from './forwarding.handler.js'; |  | ||||||
| export { HttpForwardingHandler } from './http.handler.js'; |  | ||||||
| export { HttpsPassthroughHandler } from './https-passthrough.handler.js'; |  | ||||||
| export { HttpsTerminateToHttpHandler } from './https-terminate-to-http.handler.js'; |  | ||||||
| export { HttpsTerminateToHttpsHandler } from './https-terminate-to-https.handler.js'; |  | ||||||
|  |  | ||||||
| // Export factory |  | ||||||
| export { ForwardingHandlerFactory } from './forwarding.factory.js'; |  | ||||||
|  |  | ||||||
| // Export manager |  | ||||||
| export { DomainManager, DomainManagerEvents } from './domain-manager.js'; |  | ||||||
|  |  | ||||||
| // Helper functions as a convenience object |  | ||||||
| import { |  | ||||||
|   httpOnly, |  | ||||||
|   tlsTerminateToHttp, |  | ||||||
|   tlsTerminateToHttps, |  | ||||||
|   httpsPassthrough |  | ||||||
| } from '../types/forwarding.types.js'; |  | ||||||
|  |  | ||||||
| export const helpers = { |  | ||||||
|   httpOnly, |  | ||||||
|   tlsTerminateToHttp, |  | ||||||
|   tlsTerminateToHttps, |  | ||||||
|   httpsPassthrough |  | ||||||
| }; |  | ||||||
| @@ -1,162 +0,0 @@ | |||||||
| import type * as plugins from '../../plugins.js'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * The primary forwarding types supported by SmartProxy |  | ||||||
|  */ |  | ||||||
| export type ForwardingType = |  | ||||||
|   | 'http-only'                // HTTP forwarding only (no HTTPS) |  | ||||||
|   | 'https-passthrough'        // Pass-through TLS traffic (SNI forwarding) |  | ||||||
|   | 'https-terminate-to-http'  // Terminate TLS and forward to HTTP backend |  | ||||||
|   | 'https-terminate-to-https'; // Terminate TLS and forward to HTTPS backend |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Target configuration for forwarding |  | ||||||
|  */ |  | ||||||
| export interface ITargetConfig { |  | ||||||
|   host: string | string[];  // Support single host or round-robin |  | ||||||
|   port: number; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * HTTP-specific options for forwarding |  | ||||||
|  */ |  | ||||||
| export interface IHttpOptions { |  | ||||||
|   enabled?: boolean;                 // Whether HTTP is enabled |  | ||||||
|   redirectToHttps?: boolean;         // Redirect HTTP to HTTPS |  | ||||||
|   headers?: Record<string, string>;  // Custom headers for HTTP responses |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * HTTPS-specific options for forwarding |  | ||||||
|  */ |  | ||||||
| export interface IHttpsOptions { |  | ||||||
|   customCert?: {                    // Use custom cert instead of auto-provisioned |  | ||||||
|     key: string; |  | ||||||
|     cert: string; |  | ||||||
|   }; |  | ||||||
|   forwardSni?: boolean;             // Forward SNI info in passthrough mode |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * ACME certificate handling options |  | ||||||
|  */ |  | ||||||
| export interface IAcmeForwardingOptions { |  | ||||||
|   enabled?: boolean;                // Enable ACME certificate provisioning   |  | ||||||
|   maintenance?: boolean;            // Auto-renew certificates |  | ||||||
|   production?: boolean;             // Use production ACME servers |  | ||||||
|   forwardChallenges?: {             // Forward ACME challenges |  | ||||||
|     host: string; |  | ||||||
|     port: number; |  | ||||||
|     useTls?: boolean; |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Security options for forwarding |  | ||||||
|  */ |  | ||||||
| export interface ISecurityOptions { |  | ||||||
|   allowedIps?: string[];            // IPs allowed to connect |  | ||||||
|   blockedIps?: string[];            // IPs blocked from connecting |  | ||||||
|   maxConnections?: number;          // Max simultaneous connections |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Advanced options for forwarding |  | ||||||
|  */ |  | ||||||
| export interface IAdvancedOptions { |  | ||||||
|   portRanges?: Array<{ from: number; to: number }>; // Allowed port ranges |  | ||||||
|   networkProxyPort?: number;        // Custom NetworkProxy port if using terminate mode |  | ||||||
|   keepAlive?: boolean;              // Enable TCP keepalive |  | ||||||
|   timeout?: number;                 // Connection timeout in ms |  | ||||||
|   headers?: Record<string, string>; // Custom headers with support for variables like {sni} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Unified forwarding configuration interface |  | ||||||
|  */ |  | ||||||
| export interface IForwardConfig { |  | ||||||
|   // Define the primary forwarding type - use-case driven approach |  | ||||||
|   type: ForwardingType; |  | ||||||
|    |  | ||||||
|   // Target configuration |  | ||||||
|   target: ITargetConfig; |  | ||||||
|    |  | ||||||
|   // Protocol options |  | ||||||
|   http?: IHttpOptions; |  | ||||||
|   https?: IHttpsOptions; |  | ||||||
|   acme?: IAcmeForwardingOptions; |  | ||||||
|    |  | ||||||
|   // Security and advanced options |  | ||||||
|   security?: ISecurityOptions; |  | ||||||
|   advanced?: IAdvancedOptions; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Event types emitted by forwarding handlers |  | ||||||
|  */ |  | ||||||
| export enum ForwardingHandlerEvents { |  | ||||||
|   CONNECTED = 'connected', |  | ||||||
|   DISCONNECTED = 'disconnected', |  | ||||||
|   ERROR = 'error', |  | ||||||
|   DATA_FORWARDED = 'data-forwarded', |  | ||||||
|   HTTP_REQUEST = 'http-request', |  | ||||||
|   HTTP_RESPONSE = 'http-response', |  | ||||||
|   CERTIFICATE_NEEDED = 'certificate-needed', |  | ||||||
|   CERTIFICATE_LOADED = 'certificate-loaded' |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Base interface for forwarding handlers |  | ||||||
|  */ |  | ||||||
| export interface IForwardingHandler extends plugins.EventEmitter { |  | ||||||
|   initialize(): Promise<void>; |  | ||||||
|   handleConnection(socket: plugins.net.Socket): void; |  | ||||||
|   handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Helper function types for common forwarding patterns |  | ||||||
|  */ |  | ||||||
| export const httpOnly = ( |  | ||||||
|   partialConfig: Partial<IForwardConfig> & Pick<IForwardConfig, 'target'> |  | ||||||
| ): IForwardConfig => ({ |  | ||||||
|   type: 'http-only', |  | ||||||
|   target: partialConfig.target, |  | ||||||
|   http: { enabled: true, ...(partialConfig.http || {}) }, |  | ||||||
|   ...(partialConfig.security ? { security: partialConfig.security } : {}), |  | ||||||
|   ...(partialConfig.advanced ? { advanced: partialConfig.advanced } : {}) |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| export const tlsTerminateToHttp = ( |  | ||||||
|   partialConfig: Partial<IForwardConfig> & Pick<IForwardConfig, 'target'> |  | ||||||
| ): IForwardConfig => ({ |  | ||||||
|   type: 'https-terminate-to-http', |  | ||||||
|   target: partialConfig.target, |  | ||||||
|   https: { ...(partialConfig.https || {}) }, |  | ||||||
|   acme: { enabled: true, maintenance: true, ...(partialConfig.acme || {}) }, |  | ||||||
|   http: { enabled: true, redirectToHttps: true, ...(partialConfig.http || {}) }, |  | ||||||
|   ...(partialConfig.security ? { security: partialConfig.security } : {}), |  | ||||||
|   ...(partialConfig.advanced ? { advanced: partialConfig.advanced } : {}) |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| export const tlsTerminateToHttps = ( |  | ||||||
|   partialConfig: Partial<IForwardConfig> & Pick<IForwardConfig, 'target'> |  | ||||||
| ): IForwardConfig => ({ |  | ||||||
|   type: 'https-terminate-to-https', |  | ||||||
|   target: partialConfig.target, |  | ||||||
|   https: { ...(partialConfig.https || {}) }, |  | ||||||
|   acme: { enabled: true, maintenance: true, ...(partialConfig.acme || {}) }, |  | ||||||
|   http: { enabled: true, redirectToHttps: true, ...(partialConfig.http || {}) }, |  | ||||||
|   ...(partialConfig.security ? { security: partialConfig.security } : {}), |  | ||||||
|   ...(partialConfig.advanced ? { advanced: partialConfig.advanced } : {}) |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| export const httpsPassthrough = ( |  | ||||||
|   partialConfig: Partial<IForwardConfig> & Pick<IForwardConfig, 'target'> |  | ||||||
| ): IForwardConfig => ({ |  | ||||||
|   type: 'https-passthrough', |  | ||||||
|   target: partialConfig.target, |  | ||||||
|   https: { forwardSni: true, ...(partialConfig.https || {}) }, |  | ||||||
|   ...(partialConfig.security ? { security: partialConfig.security } : {}), |  | ||||||
|   ...(partialConfig.advanced ? { advanced: partialConfig.advanced } : {}) |  | ||||||
| }); |  | ||||||
		Reference in New Issue
	
	Block a user