smartproxy/ts/proxies/smart-proxy/route-helpers.ts

497 lines
12 KiB
TypeScript

import type {
IRouteConfig,
IRouteMatch,
IRouteAction,
IRouteTarget,
IRouteTls,
IRouteRedirect,
IRouteSecurity,
IRouteAdvanced,
TPortRange
} from './models/route-types.js';
/**
* Basic helper function to create a route configuration
*/
export function createRoute(
match: IRouteMatch,
action: IRouteAction,
metadata?: {
name?: string;
description?: string;
priority?: number;
tags?: string[];
}
): IRouteConfig {
return {
match,
action,
...metadata
};
}
/**
* Create a basic HTTP route configuration
*/
export function createHttpRoute(
options: {
ports?: number | number[]; // Default: 80
domains?: string | string[];
path?: string;
target: IRouteTarget;
headers?: Record<string, string>;
security?: IRouteSecurity;
name?: string;
description?: string;
priority?: number;
tags?: string[];
}
): IRouteConfig {
return createRoute(
{
ports: options.ports || 80,
...(options.domains ? { domains: options.domains } : {}),
...(options.path ? { path: options.path } : {})
},
{
type: 'forward',
target: options.target,
...(options.headers || options.security ? {
advanced: {
...(options.headers ? { headers: options.headers } : {})
},
...(options.security ? { security: options.security } : {})
} : {})
},
{
name: options.name || 'HTTP Route',
description: options.description,
priority: options.priority,
tags: options.tags
}
);
}
/**
* Create an HTTPS route configuration with TLS termination
*/
export function createHttpsRoute(
options: {
ports?: number | number[]; // Default: 443
domains: string | string[];
path?: string;
target: IRouteTarget;
tlsMode?: 'terminate' | 'terminate-and-reencrypt';
certificate?: 'auto' | { key: string; cert: string };
headers?: Record<string, string>;
security?: IRouteSecurity;
name?: string;
description?: string;
priority?: number;
tags?: string[];
}
): IRouteConfig {
return createRoute(
{
ports: options.ports || 443,
domains: options.domains,
...(options.path ? { path: options.path } : {})
},
{
type: 'forward',
target: options.target,
tls: {
mode: options.tlsMode || 'terminate',
certificate: options.certificate || 'auto'
},
...(options.headers || options.security ? {
advanced: {
...(options.headers ? { headers: options.headers } : {})
},
...(options.security ? { security: options.security } : {})
} : {})
},
{
name: options.name || 'HTTPS Route',
description: options.description,
priority: options.priority,
tags: options.tags
}
);
}
/**
* Create an HTTPS passthrough route configuration
*/
export function createPassthroughRoute(
options: {
ports?: number | number[]; // Default: 443
domains?: string | string[];
target: IRouteTarget;
security?: IRouteSecurity;
name?: string;
description?: string;
priority?: number;
tags?: string[];
}
): IRouteConfig {
return createRoute(
{
ports: options.ports || 443,
...(options.domains ? { domains: options.domains } : {})
},
{
type: 'forward',
target: options.target,
tls: {
mode: 'passthrough'
},
...(options.security ? { security: options.security } : {})
},
{
name: options.name || 'HTTPS Passthrough Route',
description: options.description,
priority: options.priority,
tags: options.tags
}
);
}
/**
* Create a redirect route configuration
*/
export function createRedirectRoute(
options: {
ports?: number | number[]; // Default: 80
domains?: string | string[];
path?: string;
redirectTo: string;
statusCode?: 301 | 302 | 307 | 308;
name?: string;
description?: string;
priority?: number;
tags?: string[];
}
): IRouteConfig {
return createRoute(
{
ports: options.ports || 80,
...(options.domains ? { domains: options.domains } : {}),
...(options.path ? { path: options.path } : {})
},
{
type: 'redirect',
redirect: {
to: options.redirectTo,
status: options.statusCode || 301
}
},
{
name: options.name || 'Redirect Route',
description: options.description,
priority: options.priority,
tags: options.tags
}
);
}
/**
* Create an HTTP to HTTPS redirect route configuration
*/
export function createHttpToHttpsRedirect(
options: {
domains: string | string[];
statusCode?: 301 | 302 | 307 | 308;
name?: string;
priority?: number;
}
): IRouteConfig {
const domainArray = Array.isArray(options.domains) ? options.domains : [options.domains];
return createRedirectRoute({
ports: 80,
domains: options.domains,
redirectTo: 'https://{domain}{path}',
statusCode: options.statusCode || 301,
name: options.name || `HTTP to HTTPS Redirect for ${domainArray.join(', ')}`,
priority: options.priority || 100 // High priority for redirects
});
}
/**
* Create a block route configuration
*/
export function createBlockRoute(
options: {
ports: number | number[];
domains?: string | string[];
clientIp?: string[];
name?: string;
description?: string;
priority?: number;
tags?: string[];
}
): IRouteConfig {
return createRoute(
{
ports: options.ports,
...(options.domains ? { domains: options.domains } : {}),
...(options.clientIp ? { clientIp: options.clientIp } : {})
},
{
type: 'block'
},
{
name: options.name || 'Block Route',
description: options.description,
priority: options.priority || 1000, // Very high priority for blocks
tags: options.tags
}
);
}
/**
* Create a load balancer route configuration
*/
export function createLoadBalancerRoute(
options: {
ports?: number | number[]; // Default: 443
domains: string | string[];
path?: string;
targets: string[]; // Array of host names/IPs for load balancing
targetPort: number;
tlsMode?: 'passthrough' | 'terminate' | 'terminate-and-reencrypt';
certificate?: 'auto' | { key: string; cert: string };
headers?: Record<string, string>;
security?: IRouteSecurity;
name?: string;
description?: string;
tags?: string[];
}
): IRouteConfig {
const useTls = options.tlsMode !== undefined;
const defaultPort = useTls ? 443 : 80;
return createRoute(
{
ports: options.ports || defaultPort,
domains: options.domains,
...(options.path ? { path: options.path } : {})
},
{
type: 'forward',
target: {
host: options.targets,
port: options.targetPort
},
...(useTls ? {
tls: {
mode: options.tlsMode!,
...(options.tlsMode !== 'passthrough' && options.certificate ? {
certificate: options.certificate
} : {})
}
} : {}),
...(options.headers || options.security ? {
advanced: {
...(options.headers ? { headers: options.headers } : {})
},
...(options.security ? { security: options.security } : {})
} : {})
},
{
name: options.name || 'Load Balanced Route',
description: options.description || `Load balancing across ${options.targets.length} backends`,
tags: options.tags
}
);
}
/**
* Create a complete HTTPS server configuration with HTTP redirect
*/
export function createHttpsServer(
options: {
domains: string | string[];
target: IRouteTarget;
certificate?: 'auto' | { key: string; cert: string };
security?: IRouteSecurity;
addHttpRedirect?: boolean;
name?: string;
}
): IRouteConfig[] {
const routes: IRouteConfig[] = [];
const domainArray = Array.isArray(options.domains) ? options.domains : [options.domains];
// Add HTTPS route
routes.push(createHttpsRoute({
domains: options.domains,
target: options.target,
certificate: options.certificate || 'auto',
security: options.security,
name: options.name || `HTTPS Server for ${domainArray.join(', ')}`
}));
// Add HTTP to HTTPS redirect if requested
if (options.addHttpRedirect !== false) {
routes.push(createHttpToHttpsRedirect({
domains: options.domains,
name: `HTTP to HTTPS Redirect for ${domainArray.join(', ')}`,
priority: 100
}));
}
return routes;
}
/**
* Create a port range configuration from various input formats
*/
export function createPortRange(
ports: number | number[] | string | Array<{ from: number; to: number }>
): TPortRange {
// If it's a string like "80,443" or "8000-9000", parse it
if (typeof ports === 'string') {
if (ports.includes('-')) {
// Handle range like "8000-9000"
const [start, end] = ports.split('-').map(p => parseInt(p.trim(), 10));
return [{ from: start, to: end }];
} else if (ports.includes(',')) {
// Handle comma-separated list like "80,443,8080"
return ports.split(',').map(p => parseInt(p.trim(), 10));
} else {
// Handle single port as string
return parseInt(ports.trim(), 10);
}
}
// Otherwise return as is
return ports;
}
/**
* Create a security configuration object
*/
export function createSecurityConfig(
options: {
allowedIps?: string[];
blockedIps?: string[];
maxConnections?: number;
authentication?: {
type: 'basic' | 'digest' | 'oauth';
// Auth-specific options
[key: string]: any;
};
}
): IRouteSecurity {
return {
...(options.allowedIps ? { allowedIps: options.allowedIps } : {}),
...(options.blockedIps ? { blockedIps: options.blockedIps } : {}),
...(options.maxConnections ? { maxConnections: options.maxConnections } : {}),
...(options.authentication ? { authentication: options.authentication } : {})
};
}
/**
* Create a static file server route
*/
export function createStaticFileRoute(
options: {
ports?: number | number[]; // Default: 80
domains: string | string[];
path?: string;
targetDirectory: string;
tlsMode?: 'terminate' | 'terminate-and-reencrypt';
certificate?: 'auto' | { key: string; cert: string };
headers?: Record<string, string>;
security?: IRouteSecurity;
name?: string;
description?: string;
priority?: number;
tags?: string[];
}
): IRouteConfig {
const useTls = options.tlsMode !== undefined;
const defaultPort = useTls ? 443 : 80;
return createRoute(
{
ports: options.ports || defaultPort,
domains: options.domains,
...(options.path ? { path: options.path } : {})
},
{
type: 'forward',
target: {
host: 'localhost', // Static file serving is typically handled locally
port: 0, // Special value indicating a static file server
preservePort: false
},
...(useTls ? {
tls: {
mode: options.tlsMode!,
certificate: options.certificate || 'auto'
}
} : {}),
advanced: {
...(options.headers ? { headers: options.headers } : {}),
staticFiles: {
directory: options.targetDirectory,
indexFiles: ['index.html', 'index.htm']
}
},
...(options.security ? { security: options.security } : {})
},
{
name: options.name || 'Static File Server',
description: options.description || `Serving static files from ${options.targetDirectory}`,
priority: options.priority,
tags: options.tags
}
);
}
/**
* Create a test route for debugging purposes
*/
export function createTestRoute(
options: {
ports?: number | number[]; // Default: 8000
domains?: string | string[];
path?: string;
response?: {
status?: number;
headers?: Record<string, string>;
body?: string;
};
name?: string;
}
): IRouteConfig {
return createRoute(
{
ports: options.ports || 8000,
...(options.domains ? { domains: options.domains } : {}),
...(options.path ? { path: options.path } : {})
},
{
type: 'forward',
target: {
host: 'test', // Special value indicating a test route
port: 0
},
advanced: {
testResponse: {
status: options.response?.status || 200,
headers: options.response?.headers || { 'Content-Type': 'text/plain' },
body: options.response?.body || 'Test route is working!'
}
}
},
{
name: options.name || 'Test Route',
description: 'Route for testing and debugging',
priority: 500,
tags: ['test', 'debug']
}
);
}