/** * Route Helper Functions * * This file provides utility functions for creating route configurations for common scenarios. * These functions aim to simplify the creation of route configurations for typical use cases. * * This module includes helper functions for creating: * - HTTP routes (createHttpRoute) * - HTTPS routes with TLS termination (createHttpsTerminateRoute) * - HTTP to HTTPS redirects (createHttpToHttpsRedirect) * - HTTPS passthrough routes (createHttpsPassthroughRoute) * - Complete HTTPS servers with redirects (createCompleteHttpsServer) * - Load balancer routes (createLoadBalancerRoute) * - Static file server routes (createStaticFileRoute) * - API routes (createApiRoute) * - WebSocket routes (createWebSocketRoute) */ import type { IRouteConfig, IRouteMatch, IRouteAction, IRouteTarget, TPortRange } from '../models/route-types.js'; /** * Create an HTTP-only route configuration * @param domains Domain(s) to match * @param target Target host and port * @param options Additional route options * @returns Route configuration object */ export function createHttpRoute( domains: string | string[], target: { host: string | string[]; port: number }, options: Partial = {} ): IRouteConfig { // Create route match const match: IRouteMatch = { ports: options.match?.ports || 80, domains }; // Create route action const action: IRouteAction = { type: 'forward', target }; // Create the route config return { match, action, name: options.name || `HTTP Route for ${Array.isArray(domains) ? domains.join(', ') : domains}`, ...options }; } /** * Create an HTTPS route with TLS termination (including HTTP redirect to HTTPS) * @param domains Domain(s) to match * @param target Target host and port * @param options Additional route options * @returns Route configuration object */ export function createHttpsTerminateRoute( domains: string | string[], target: { host: string | string[]; port: number }, options: { certificate?: 'auto' | { key: string; cert: string }; httpPort?: number | number[]; httpsPort?: number | number[]; reencrypt?: boolean; name?: string; [key: string]: any; } = {} ): IRouteConfig { // Create route match const match: IRouteMatch = { ports: options.httpsPort || 443, domains }; // Create route action const action: IRouteAction = { type: 'forward', target, tls: { mode: options.reencrypt ? 'terminate-and-reencrypt' : 'terminate', certificate: options.certificate || 'auto' } }; // Create the route config return { match, action, name: options.name || `HTTPS Route for ${Array.isArray(domains) ? domains.join(', ') : domains}`, ...options }; } /** * Create an HTTP to HTTPS redirect route * @param domains Domain(s) to match * @param httpsPort HTTPS port to redirect to (default: 443) * @param options Additional route options * @returns Route configuration object */ export function createHttpToHttpsRedirect( domains: string | string[], httpsPort: number = 443, options: Partial = {} ): IRouteConfig { // Create route match const match: IRouteMatch = { ports: options.match?.ports || 80, domains }; // Create route action const action: IRouteAction = { type: 'redirect', redirect: { to: `https://{domain}:${httpsPort}{path}`, status: 301 } }; // Create the route config return { match, action, name: options.name || `HTTP to HTTPS Redirect for ${Array.isArray(domains) ? domains.join(', ') : domains}`, ...options }; } /** * Create an HTTPS passthrough route (SNI-based forwarding without TLS termination) * @param domains Domain(s) to match * @param target Target host and port * @param options Additional route options * @returns Route configuration object */ export function createHttpsPassthroughRoute( domains: string | string[], target: { host: string | string[]; port: number }, options: Partial = {} ): IRouteConfig { // Create route match const match: IRouteMatch = { ports: options.match?.ports || 443, domains }; // Create route action const action: IRouteAction = { type: 'forward', target, tls: { mode: 'passthrough' } }; // Create the route config return { match, action, name: options.name || `HTTPS Passthrough for ${Array.isArray(domains) ? domains.join(', ') : domains}`, ...options }; } /** * Create a complete HTTPS server with HTTP to HTTPS redirects * @param domains Domain(s) to match * @param target Target host and port * @param options Additional configuration options * @returns Array of two route configurations (HTTPS and HTTP redirect) */ export function createCompleteHttpsServer( domains: string | string[], target: { host: string | string[]; port: number }, options: { certificate?: 'auto' | { key: string; cert: string }; httpPort?: number | number[]; httpsPort?: number | number[]; reencrypt?: boolean; name?: string; [key: string]: any; } = {} ): IRouteConfig[] { // Create the HTTPS route const httpsRoute = createHttpsTerminateRoute(domains, target, options); // Create the HTTP redirect route const httpRedirectRoute = createHttpToHttpsRedirect( domains, // Extract the HTTPS port from the HTTPS route - ensure it's a number typeof options.httpsPort === 'number' ? options.httpsPort : Array.isArray(options.httpsPort) ? options.httpsPort[0] : 443, { // Set the HTTP port match: { ports: options.httpPort || 80, domains }, name: `HTTP to HTTPS Redirect for ${Array.isArray(domains) ? domains.join(', ') : domains}` } ); return [httpsRoute, httpRedirectRoute]; } /** * Create a load balancer route (round-robin between multiple backend hosts) * @param domains Domain(s) to match * @param hosts Array of backend hosts to load balance between * @param port Backend port * @param options Additional route options * @returns Route configuration object */ export function createLoadBalancerRoute( domains: string | string[], hosts: string[], port: number, options: { tls?: { mode: 'passthrough' | 'terminate' | 'terminate-and-reencrypt'; certificate?: 'auto' | { key: string; cert: string }; }; [key: string]: any; } = {} ): IRouteConfig { // Create route match const match: IRouteMatch = { ports: options.match?.ports || (options.tls ? 443 : 80), domains }; // Create route target const target: IRouteTarget = { host: hosts, port }; // Create route action const action: IRouteAction = { type: 'forward', target }; // Add TLS configuration if provided if (options.tls) { action.tls = { mode: options.tls.mode, certificate: options.tls.certificate || 'auto' }; } // Create the route config return { match, action, name: options.name || `Load Balancer for ${Array.isArray(domains) ? domains.join(', ') : domains}`, ...options }; } /** * Create a static file server route * @param domains Domain(s) to match * @param rootDir Root directory path for static files * @param options Additional route options * @returns Route configuration object */ export function createStaticFileRoute( domains: string | string[], rootDir: string, options: { indexFiles?: string[]; serveOnHttps?: boolean; certificate?: 'auto' | { key: string; cert: string }; httpPort?: number | number[]; httpsPort?: number | number[]; name?: string; [key: string]: any; } = {} ): IRouteConfig { // Create route match const match: IRouteMatch = { ports: options.serveOnHttps ? (options.httpsPort || 443) : (options.httpPort || 80), domains }; // Create route action const action: IRouteAction = { type: 'static', static: { root: rootDir, index: options.indexFiles || ['index.html', 'index.htm'] } }; // Add TLS configuration if serving on HTTPS if (options.serveOnHttps) { action.tls = { mode: 'terminate', certificate: options.certificate || 'auto' }; } // Create the route config return { match, action, name: options.name || `Static Files for ${Array.isArray(domains) ? domains.join(', ') : domains}`, ...options }; } /** * Create an API route configuration * @param domains Domain(s) to match * @param apiPath API base path (e.g., "/api") * @param target Target host and port * @param options Additional route options * @returns Route configuration object */ export function createApiRoute( domains: string | string[], apiPath: string, target: { host: string | string[]; port: number }, options: { useTls?: boolean; certificate?: 'auto' | { key: string; cert: string }; addCorsHeaders?: boolean; httpPort?: number | number[]; httpsPort?: number | number[]; name?: string; [key: string]: any; } = {} ): IRouteConfig { // Normalize API path const normalizedPath = apiPath.startsWith('/') ? apiPath : `/${apiPath}`; const pathWithWildcard = normalizedPath.endsWith('/') ? `${normalizedPath}*` : `${normalizedPath}/*`; // Create route match const match: IRouteMatch = { ports: options.useTls ? (options.httpsPort || 443) : (options.httpPort || 80), domains, path: pathWithWildcard }; // Create route action const action: IRouteAction = { type: 'forward', target }; // Add TLS configuration if using HTTPS if (options.useTls) { action.tls = { mode: 'terminate', certificate: options.certificate || 'auto' }; } // Add CORS headers if requested const headers: Record> = {}; if (options.addCorsHeaders) { headers.response = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', 'Access-Control-Max-Age': '86400' }; } // Create the route config return { match, action, headers: Object.keys(headers).length > 0 ? headers : undefined, name: options.name || `API Route ${normalizedPath} for ${Array.isArray(domains) ? domains.join(', ') : domains}`, priority: options.priority || 100, // Higher priority for specific path matches ...options }; } /** * Create a WebSocket route configuration * @param domains Domain(s) to match * @param wsPath WebSocket path (e.g., "/ws") * @param target Target WebSocket server host and port * @param options Additional route options * @returns Route configuration object */ export function createWebSocketRoute( domains: string | string[], wsPath: string, target: { host: string | string[]; port: number }, options: { useTls?: boolean; certificate?: 'auto' | { key: string; cert: string }; httpPort?: number | number[]; httpsPort?: number | number[]; pingInterval?: number; pingTimeout?: number; name?: string; [key: string]: any; } = {} ): IRouteConfig { // Normalize WebSocket path const normalizedPath = wsPath.startsWith('/') ? wsPath : `/${wsPath}`; // Create route match const match: IRouteMatch = { ports: options.useTls ? (options.httpsPort || 443) : (options.httpPort || 80), domains, path: normalizedPath }; // Create route action const action: IRouteAction = { type: 'forward', target, websocket: { enabled: true, pingInterval: options.pingInterval || 30000, // 30 seconds pingTimeout: options.pingTimeout || 5000 // 5 seconds } }; // Add TLS configuration if using HTTPS if (options.useTls) { action.tls = { mode: 'terminate', certificate: options.certificate || 'auto' }; } // Create the route config return { match, action, name: options.name || `WebSocket Route ${normalizedPath} for ${Array.isArray(domains) ? domains.join(', ') : domains}`, priority: options.priority || 100, // Higher priority for WebSocket routes ...options }; }