import type{ IPortProxySettings } from './classes.pp.interfaces.js'; /** * Manages port ranges and port-based configuration */ export class PortRangeManager { constructor(private settings: IPortProxySettings) {} /** * Get all ports that should be listened on */ public getListeningPorts(): Set { const listeningPorts = new Set(); // Always include the main fromPort listeningPorts.add(this.settings.fromPort); // Add ports from global port ranges if defined if (this.settings.globalPortRanges && this.settings.globalPortRanges.length > 0) { for (const range of this.settings.globalPortRanges) { for (let port = range.from; port <= range.to; port++) { listeningPorts.add(port); } } } return listeningPorts; } /** * Check if a port should use NetworkProxy for forwarding */ public shouldUseNetworkProxy(port: number): boolean { return !!this.settings.useNetworkProxy && this.settings.useNetworkProxy.includes(port); } /** * Check if port should use global forwarding */ public shouldUseGlobalForwarding(port: number): boolean { return ( !!this.settings.forwardAllGlobalRanges && this.isPortInGlobalRanges(port) ); } /** * Check if a port is in global ranges */ public isPortInGlobalRanges(port: number): boolean { return ( this.settings.globalPortRanges && this.isPortInRanges(port, this.settings.globalPortRanges) ); } /** * Check if a port falls within the specified ranges */ public isPortInRanges(port: number, ranges: Array<{ from: number; to: number }>): boolean { return ranges.some((range) => port >= range.from && port <= range.to); } /** * Get forwarding port for a specific listening port * This determines what port to connect to on the target */ public getForwardingPort(listeningPort: number): number { // If using global forwarding, forward to the original port if (this.settings.forwardAllGlobalRanges && this.isPortInGlobalRanges(listeningPort)) { return listeningPort; } // Otherwise use the configured toPort return this.settings.toPort; } /** * Find domain-specific port ranges that include a given port */ public findDomainPortRange(port: number): { domainIndex: number, range: { from: number, to: number } } | undefined { for (let i = 0; i < this.settings.domainConfigs.length; i++) { const domain = this.settings.domainConfigs[i]; if (domain.portRanges) { for (const range of domain.portRanges) { if (port >= range.from && port <= range.to) { return { domainIndex: i, range }; } } } } return undefined; } /** * Get a list of all configured ports * This includes the fromPort, NetworkProxy ports, and ports from all ranges */ public getAllConfiguredPorts(): number[] { const ports = new Set(); // Add main listening port ports.add(this.settings.fromPort); // Add NetworkProxy port if configured if (this.settings.networkProxyPort) { ports.add(this.settings.networkProxyPort); } // Add NetworkProxy ports if (this.settings.useNetworkProxy) { for (const port of this.settings.useNetworkProxy) { ports.add(port); } } // Add ACME HTTP challenge port if enabled if (this.settings.acme?.enabled && this.settings.acme.port) { ports.add(this.settings.acme.port); } // Add global port ranges if (this.settings.globalPortRanges) { for (const range of this.settings.globalPortRanges) { for (let port = range.from; port <= range.to; port++) { ports.add(port); } } } // Add domain-specific port ranges for (const domain of this.settings.domainConfigs) { if (domain.portRanges) { for (const range of domain.portRanges) { for (let port = range.from; port <= range.to; port++) { ports.add(port); } } } // Add domain-specific NetworkProxy port if configured if (domain.useNetworkProxy && domain.networkProxyPort) { ports.add(domain.networkProxyPort); } } return Array.from(ports); } /** * Validate port configuration * Returns array of warning messages */ public validateConfiguration(): string[] { const warnings: string[] = []; // Check for overlapping port ranges const portMappings = new Map(); // Track global port ranges if (this.settings.globalPortRanges) { for (const range of this.settings.globalPortRanges) { for (let port = range.from; port <= range.to; port++) { if (!portMappings.has(port)) { portMappings.set(port, []); } portMappings.get(port)!.push('Global Port Range'); } } } // Track domain-specific port ranges for (const domain of this.settings.domainConfigs) { if (domain.portRanges) { for (const range of domain.portRanges) { for (let port = range.from; port <= range.to; port++) { if (!portMappings.has(port)) { portMappings.set(port, []); } portMappings.get(port)!.push(`Domain: ${domain.domains.join(', ')}`); } } } } // Check for ports with multiple mappings for (const [port, mappings] of portMappings.entries()) { if (mappings.length > 1) { warnings.push(`Port ${port} has multiple mappings: ${mappings.join(', ')}`); } } // Check if main ports are used elsewhere if (portMappings.has(this.settings.fromPort) && portMappings.get(this.settings.fromPort)!.length > 0) { warnings.push(`Main listening port ${this.settings.fromPort} is also used in port ranges`); } if (this.settings.networkProxyPort && portMappings.has(this.settings.networkProxyPort)) { warnings.push(`NetworkProxy port ${this.settings.networkProxyPort} is also used in port ranges`); } // Check ACME port if (this.settings.acme?.enabled && this.settings.acme.port) { if (portMappings.has(this.settings.acme.port)) { warnings.push(`ACME HTTP challenge port ${this.settings.acme.port} is also used in port ranges`); } } return warnings; } }