import * as plugins from '../../plugins.js'; import type { IForwardConfig, IForwardingHandler } from '../config/forwarding-types.js'; import { ForwardingHandlerEvents } from '../config/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 { // 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, variables: Record ): Record { 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 } }