UNPKG

@push.rocks/smartproxy

Version:

A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.

229 lines 17.7 kB
/** * Route Utilities * * This file provides utility functions for working with route configurations, * including merging, finding, and managing route collections. */ import { validateRouteConfig } from './route-validators.js'; /** * Merge two route configurations * The second route's properties will override the first route's properties where they exist * @param baseRoute The base route configuration * @param overrideRoute The route configuration with overriding properties * @returns A new merged route configuration */ export function mergeRouteConfigs(baseRoute, overrideRoute) { // Create deep copies to avoid modifying original objects const mergedRoute = JSON.parse(JSON.stringify(baseRoute)); // Apply overrides at the top level if (overrideRoute.id) mergedRoute.id = overrideRoute.id; if (overrideRoute.name) mergedRoute.name = overrideRoute.name; if (overrideRoute.enabled !== undefined) mergedRoute.enabled = overrideRoute.enabled; if (overrideRoute.priority !== undefined) mergedRoute.priority = overrideRoute.priority; // Merge match configuration if (overrideRoute.match) { mergedRoute.match = { ...mergedRoute.match }; if (overrideRoute.match.ports !== undefined) { mergedRoute.match.ports = overrideRoute.match.ports; } if (overrideRoute.match.domains !== undefined) { mergedRoute.match.domains = overrideRoute.match.domains; } if (overrideRoute.match.path !== undefined) { mergedRoute.match.path = overrideRoute.match.path; } if (overrideRoute.match.headers !== undefined) { mergedRoute.match.headers = overrideRoute.match.headers; } } // Merge action configuration if (overrideRoute.action) { // If action types are different, replace the entire action if (overrideRoute.action.type && overrideRoute.action.type !== mergedRoute.action.type) { // Handle socket handler specially since it's a function if (overrideRoute.action.type === 'socket-handler' && overrideRoute.action.socketHandler) { mergedRoute.action = { type: 'socket-handler', socketHandler: overrideRoute.action.socketHandler }; } else { mergedRoute.action = JSON.parse(JSON.stringify(overrideRoute.action)); } } else { // Otherwise merge the action properties mergedRoute.action = { ...mergedRoute.action }; // Merge targets if (overrideRoute.action.targets) { mergedRoute.action.targets = overrideRoute.action.targets; } // Merge TLS options if (overrideRoute.action.tls) { mergedRoute.action.tls = { ...mergedRoute.action.tls, ...overrideRoute.action.tls }; } // Handle socket handler update if (overrideRoute.action.socketHandler) { mergedRoute.action.socketHandler = overrideRoute.action.socketHandler; } } } return mergedRoute; } import { DomainMatcher, PathMatcher, HeaderMatcher } from '../../../core/routing/matchers/index.js'; /** * Check if a route matches a domain * @param route The route to check * @param domain The domain to match against * @returns True if the route matches the domain, false otherwise */ export function routeMatchesDomain(route, domain) { if (!route.match?.domains) { return false; } const domains = Array.isArray(route.match.domains) ? route.match.domains : [route.match.domains]; return domains.some(d => DomainMatcher.match(d, domain)); } /** * Check if a route matches a port * @param route The route to check * @param port The port to match against * @returns True if the route matches the port, false otherwise */ export function routeMatchesPort(route, port) { if (!route.match?.ports) { return false; } if (typeof route.match.ports === 'number') { return route.match.ports === port; } if (Array.isArray(route.match.ports)) { // Simple case - array of numbers if (typeof route.match.ports[0] === 'number') { return route.match.ports.includes(port); } // Complex case - array of port ranges if (typeof route.match.ports[0] === 'object') { return route.match.ports.some(range => port >= range.from && port <= range.to); } } return false; } /** * Check if a route matches a path * @param route The route to check * @param path The path to match against * @returns True if the route matches the path, false otherwise */ export function routeMatchesPath(route, path) { if (!route.match?.path) { return true; // No path specified means it matches any path } return PathMatcher.match(route.match.path, path).matches; } /** * Check if a route matches headers * @param route The route to check * @param headers The headers to match against * @returns True if the route matches the headers, false otherwise */ export function routeMatchesHeaders(route, headers) { if (!route.match?.headers || Object.keys(route.match.headers).length === 0) { return true; // No headers specified means it matches any headers } // Convert RegExp patterns to strings for HeaderMatcher const stringHeaders = {}; for (const [key, value] of Object.entries(route.match.headers)) { stringHeaders[key] = value instanceof RegExp ? value.source : value; } return HeaderMatcher.matchAll(stringHeaders, headers); } /** * Find all routes that match the given criteria * @param routes Array of routes to search * @param criteria Matching criteria * @returns Array of matching routes sorted by priority */ export function findMatchingRoutes(routes, criteria) { // Filter routes that are enabled and match all provided criteria const matchingRoutes = routes.filter(route => { // Skip disabled routes if (route.enabled === false) { return false; } // Check domain match if specified if (criteria.domain && !routeMatchesDomain(route, criteria.domain)) { return false; } // Check port match if specified if (criteria.port !== undefined && !routeMatchesPort(route, criteria.port)) { return false; } // Check path match if specified if (criteria.path && !routeMatchesPath(route, criteria.path)) { return false; } // Check headers match if specified if (criteria.headers && !routeMatchesHeaders(route, criteria.headers)) { return false; } return true; }); // Sort matching routes by priority (higher priority first) return matchingRoutes.sort((a, b) => { const priorityA = a.priority || 0; const priorityB = b.priority || 0; return priorityB - priorityA; // Higher priority first }); } /** * Find the best matching route for the given criteria * @param routes Array of routes to search * @param criteria Matching criteria * @returns The best matching route or undefined if no match */ export function findBestMatchingRoute(routes, criteria) { const matchingRoutes = findMatchingRoutes(routes, criteria); return matchingRoutes.length > 0 ? matchingRoutes[0] : undefined; } /** * Create a route ID based on route properties * @param route Route configuration * @returns Generated route ID */ export function generateRouteId(route) { // Create a deterministic ID based on route properties const domains = Array.isArray(route.match?.domains) ? route.match.domains.join('-') : route.match?.domains || 'any'; let portsStr = 'any'; if (route.match?.ports) { if (Array.isArray(route.match.ports)) { portsStr = route.match.ports.join('-'); } else if (typeof route.match.ports === 'number') { portsStr = route.match.ports.toString(); } } const path = route.match?.path || 'any'; const action = route.action?.type || 'unknown'; return `route-${domains}-${portsStr}-${path}-${action}`.replace(/[^a-zA-Z0-9-]/g, '-'); } /** * Clone a route configuration * @param route Route to clone * @returns Deep copy of the route */ export function cloneRoute(route) { return JSON.parse(JSON.stringify(route)); } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUtdXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L3V0aWxzL3JvdXRlLXV0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7OztHQUtHO0FBR0gsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFFNUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUMvQixTQUF1QixFQUN2QixhQUFvQztJQUVwQyx5REFBeUQ7SUFDekQsTUFBTSxXQUFXLEdBQWlCLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0lBRXhFLG1DQUFtQztJQUNuQyxJQUFJLGFBQWEsQ0FBQyxFQUFFO1FBQUUsV0FBVyxDQUFDLEVBQUUsR0FBRyxhQUFhLENBQUMsRUFBRSxDQUFDO0lBQ3hELElBQUksYUFBYSxDQUFDLElBQUk7UUFBRSxXQUFXLENBQUMsSUFBSSxHQUFHLGFBQWEsQ0FBQyxJQUFJLENBQUM7SUFDOUQsSUFBSSxhQUFhLENBQUMsT0FBTyxLQUFLLFNBQVM7UUFBRSxXQUFXLENBQUMsT0FBTyxHQUFHLGFBQWEsQ0FBQyxPQUFPLENBQUM7SUFDckYsSUFBSSxhQUFhLENBQUMsUUFBUSxLQUFLLFNBQVM7UUFBRSxXQUFXLENBQUMsUUFBUSxHQUFHLGFBQWEsQ0FBQyxRQUFRLENBQUM7SUFFeEYsNEJBQTRCO0lBQzVCLElBQUksYUFBYSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ3hCLFdBQVcsQ0FBQyxLQUFLLEdBQUcsRUFBRSxHQUFHLFdBQVcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUU3QyxJQUFJLGFBQWEsQ0FBQyxLQUFLLENBQUMsS0FBSyxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQzVDLFdBQVcsQ0FBQyxLQUFLLENBQUMsS0FBSyxHQUFHLGFBQWEsQ0FBQyxLQUFLLENBQUMs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