@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.
262 lines • 19.4 kB
JavaScript
/**
* Route Utilities
*
* This file provides utility functions for working with route configurations,
* including merging, finding, and managing route collections.
*/
import { validateRouteConfig } from './route-validator.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';
/**
* Expand a port range specification into individual ports.
*/
export function expandPortRange(portRange) {
if (typeof portRange === 'number') {
return [portRange];
}
return portRange.flatMap((item) => {
if (typeof item === 'number') {
return [item];
}
if (item.from > item.to) {
return [];
}
const ports = [];
for (let port = item.from; port <= item.to; port++) {
ports.push(port);
}
return ports;
});
}
/**
* Check if a port range contains a port.
*/
export function portRangeIncludes(portRange, port) {
if (typeof portRange === 'number') {
return portRange === port;
}
return portRange.some((item) => {
if (typeof item === 'number') {
return item === port;
}
return port >= item.from && port <= item.to;
});
}
/**
* 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;
}
return portRangeIncludes(route.match.ports, port);
}
/**
* 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
}
for (const [headerName, expectedValue] of Object.entries(route.match.headers)) {
const actualKey = Object.keys(headers).find((key) => key.toLowerCase() === headerName.toLowerCase());
const actualValue = actualKey ? headers[actualKey] : undefined;
if (actualValue === undefined) {
return false;
}
if (expectedValue instanceof RegExp) {
if (!expectedValue.test(actualValue)) {
return false;
}
continue;
}
if (!HeaderMatcher.match(expectedValue, actualValue)) {
return false;
}
}
return true;
}
/**
* 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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUtdXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L3V0aWxzL3JvdXRlLXV0aWxzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7OztHQUtHO0FBR0gsT0FBTyxFQUFFLG1CQUFtQixFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFFM0Q7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUMvQixTQUF1QixFQUN2QixhQUFvQztJQUVwQyx5REFBeUQ7SUFDekQsTUFBTSxXQUFXLEdBQWlCLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0lBRXhFLG1DQUFtQztJQUNuQyxJQUFJLGFBQWEsQ0FBQyxFQUFFO1FBQUUsV0FBVyxDQUFDLEVBQUUsR0FBRyxhQUFhLENBQUMsRUFBRSxDQUFDO0lBQ3hELElBQUksYUFBYSxDQUFDLElBQUk7UUFBRSxXQUFXLENBQUMsSUFBSSxHQUFHLGFBQWEsQ0FBQyxJQUFJLENBQUM7SUFDOUQsSUFBSSxhQUFhLENBQUMsT0FBTyxLQUFLLFNBQVM7UUFBRSxXQUFXLENBQUMsT0FBTyxHQUFHLGFBQWEsQ0FBQyxPQUFPLENBQUM7SUFDckYsSUFBSSxhQUFhLENBQUMsUUFBUSxLQUFLLFNBQVM7UUFBRSxXQUFXLENBQUMsUUFBUSxHQUFHLGFBQWEsQ0FBQyxRQUFRLENBQUM7SUFFeEYsNEJBQTRCO0lBQzVCLElBQUksYUFBYSxDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ3hCLFdBQVcsQ0FBQyxLQUFLLEdBQUcsRUFBRSxHQUFHLFdBQVcsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUU3QyxJQUFJLGFBQWEsQ0FBQyxLQUFLLENBQUMsS0FBSyxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQzVDLFdBQVcsQ0FBQyxLQUFLLENBQUMsS0FBSyxHQUFHLGFBQWEsQ0FBQyxLQUFLLENBQUMs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