BREAKING CHANGE(smartproxy): Remove legacy migration utilities and deprecated forwarding helpers; consolidate route utilities, streamline interface definitions, and normalize IPv6-mapped IPv4 addresses

This commit is contained in:
2025-05-15 08:56:27 +00:00
parent aa1194ba5d
commit ac3a888453
18 changed files with 459 additions and 458 deletions

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartproxy',
version: '16.0.4',
version: '17.0.0',
description: '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.'
}

View File

@ -21,9 +21,21 @@ export function convertToLegacyForwardConfig(
? forwardConfig.target.host[0] // Use the first host in the array
: forwardConfig.target.host;
// Extract port number, handling different port formats
let port: number;
if (typeof forwardConfig.target.port === 'function') {
// Use a default port for function-based ports in adapter context
port = 80;
} else if (forwardConfig.target.port === 'preserve') {
// For 'preserve', use the default port 80 in this adapter context
port = 80;
} else {
port = forwardConfig.target.port;
}
return {
ip: host,
port: forwardConfig.target.port
port: port
};
}
@ -75,11 +87,23 @@ export function createPort80HandlerOptions(
forwardConfig.type === 'https-terminate-to-https'));
if (supportsHttp) {
// Determine port value handling different formats
let port: number;
if (typeof forwardConfig.target.port === 'function') {
// Use a default port for function-based ports
port = 80;
} else if (forwardConfig.target.port === 'preserve') {
// For 'preserve', use 80 in this adapter context
port = 80;
} else {
port = forwardConfig.target.port;
}
options.forward = {
ip: Array.isArray(forwardConfig.target.host)
? forwardConfig.target.host[0]
: forwardConfig.target.host,
port: forwardConfig.target.port
port: port
};
}

View File

@ -1,10 +1,8 @@
import type * as plugins from '../../plugins.js';
/**
* @deprecated The legacy forwarding types are being replaced by the route-based configuration system.
* See /ts/proxies/smart-proxy/models/route-types.ts for the new route-based configuration.
*
* The primary forwarding types supported by SmartProxy
* Used for configuration compatibility
*/
export type TForwardingType =
| 'http-only' // HTTP forwarding only (no HTTPS)
@ -35,7 +33,7 @@ export interface IForwardingHandler extends plugins.EventEmitter {
handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void;
}
// Import and re-export the route-based helpers for seamless transition
// Route-based helpers are now available directly from route-patterns.ts
import {
createHttpRoute,
createHttpsTerminateRoute,
@ -43,7 +41,7 @@ import {
createHttpToHttpsRedirect,
createCompleteHttpsServer,
createLoadBalancerRoute
} from '../../proxies/smart-proxy/utils/route-helpers.js';
} from '../../proxies/smart-proxy/utils/route-patterns.js';
export {
createHttpRoute,
@ -54,23 +52,20 @@ export {
createLoadBalancerRoute
};
/**
* @deprecated These helper functions are maintained for backward compatibility.
* Please use the route-based helpers instead:
* - createHttpRoute
* - createHttpsTerminateRoute
* - createHttpsPassthroughRoute
* - createHttpToHttpsRedirect
*/
// Note: Legacy helper functions have been removed
// Please use the route-based helpers instead:
// - createHttpRoute
// - createHttpsTerminateRoute
// - createHttpsPassthroughRoute
// - createHttpToHttpsRedirect
import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js';
import { domainConfigToRouteConfig } from '../../proxies/smart-proxy/utils/route-migration-utils.js';
// For backward compatibility
// For backward compatibility, kept only the basic configuration interface
export interface IForwardConfig {
type: TForwardingType;
target: {
host: string | string[];
port: number;
port: number | 'preserve' | ((ctx: any) => number);
};
http?: any;
https?: any;
@ -78,57 +73,4 @@ export interface IForwardConfig {
security?: any;
advanced?: any;
[key: string]: any;
}
export interface IDeprecatedForwardConfig {
type: TForwardingType;
target: {
host: string | string[];
port: number;
};
[key: string]: any;
}
/**
* @deprecated Use createHttpRoute instead
*/
export const httpOnly = (
partialConfig: Partial<IDeprecatedForwardConfig> & Pick<IDeprecatedForwardConfig, 'target'>
): IDeprecatedForwardConfig => ({
type: 'http-only',
target: partialConfig.target,
...(partialConfig)
});
/**
* @deprecated Use createHttpsTerminateRoute instead
*/
export const tlsTerminateToHttp = (
partialConfig: Partial<IDeprecatedForwardConfig> & Pick<IDeprecatedForwardConfig, 'target'>
): IDeprecatedForwardConfig => ({
type: 'https-terminate-to-http',
target: partialConfig.target,
...(partialConfig)
});
/**
* @deprecated Use createHttpsTerminateRoute with reencrypt option instead
*/
export const tlsTerminateToHttps = (
partialConfig: Partial<IDeprecatedForwardConfig> & Pick<IDeprecatedForwardConfig, 'target'>
): IDeprecatedForwardConfig => ({
type: 'https-terminate-to-https',
target: partialConfig.target,
...(partialConfig)
});
/**
* @deprecated Use createHttpsPassthroughRoute instead
*/
export const httpsPassthrough = (
partialConfig: Partial<IDeprecatedForwardConfig> & Pick<IDeprecatedForwardConfig, 'target'>
): IDeprecatedForwardConfig => ({
type: 'https-passthrough',
target: partialConfig.target,
...(partialConfig)
});
}

View File

@ -5,5 +5,22 @@
* See /ts/proxies/smart-proxy/models/route-types.ts for the new route-based configuration.
*/
export * from './forwarding-types.js';
export * from '../../proxies/smart-proxy/utils/route-helpers.js';
export type {
TForwardingType,
IForwardConfig,
IForwardingHandler
} from './forwarding-types.js';
export {
ForwardingHandlerEvents
} from './forwarding-types.js';
// Import route helpers from route-patterns instead of deleted route-helpers
export {
createHttpRoute,
createHttpsTerminateRoute,
createHttpsPassthroughRoute,
createHttpToHttpsRedirect,
createCompleteHttpsServer,
createLoadBalancerRoute
} from '../../proxies/smart-proxy/utils/route-patterns.js';

View File

@ -122,8 +122,13 @@ export class ForwardingHandlerFactory {
throw new Error('Target must include a host or array of hosts');
}
if (!config.target.port || config.target.port <= 0 || config.target.port > 65535) {
throw new Error('Target must include a valid port (1-65535)');
// Validate port if it's a number
if (typeof config.target.port === 'number') {
if (config.target.port <= 0 || config.target.port > 65535) {
throw new Error('Target must include a valid port (1-65535)');
}
} else if (config.target.port !== 'preserve' && typeof config.target.port !== 'function') {
throw new Error('Target port must be a number, "preserve", or a function');
}
// Type-specific validation

View File

@ -55,17 +55,37 @@ export abstract class ForwardingHandler extends plugins.EventEmitter implements
const randomIndex = Math.floor(Math.random() * target.host.length);
return {
host: target.host[randomIndex],
port: target.port
port: this.resolvePort(target.port)
};
}
// Single host
return {
host: target.host,
port: target.port
port: this.resolvePort(target.port)
};
}
/**
* Resolves a port value, handling 'preserve' and function ports
*/
protected resolvePort(port: number | 'preserve' | ((ctx: any) => number)): number {
if (typeof port === 'function') {
try {
// Create a minimal context for the function
const ctx = { port: 80 }; // Default port for minimal context
return port(ctx);
} catch (err) {
console.error('Error resolving port function:', err);
return 80; // Default fallback port
}
} else if (port === 'preserve') {
return 80; // Default port for 'preserve' in base handler
} else {
return port;
}
}
/**
* Redirect an HTTP request to HTTPS
* @param req The HTTP request

View File

@ -3,9 +3,6 @@
* Provides a flexible and type-safe way to configure and manage various forwarding strategies
*/
// Export types and configuration
export * from './config/forwarding-types.js';
// Export handlers
export { ForwardingHandler } from './handlers/base-handler.js';
export * from './handlers/http-handler.js';
@ -16,20 +13,23 @@ export * from './handlers/https-terminate-to-https-handler.js';
// Export factory
export * from './factory/forwarding-factory.js';
// Helper functions as a convenience object
import {
httpOnly,
tlsTerminateToHttp,
tlsTerminateToHttps,
httpsPassthrough
// Export types - these include TForwardingType and IForwardConfig
export type {
TForwardingType,
IForwardConfig,
IForwardingHandler
} from './config/forwarding-types.js';
// Export route-based helpers from smart-proxy
export * from '../proxies/smart-proxy/utils/route-helpers.js';
export {
ForwardingHandlerEvents
} from './config/forwarding-types.js';
export const helpers = {
httpOnly,
tlsTerminateToHttp,
tlsTerminateToHttps,
httpsPassthrough
};
// Export route helpers directly from route-patterns
export {
createHttpRoute,
createHttpsTerminateRoute,
createHttpsPassthroughRoute,
createHttpToHttpsRedirect,
createCompleteHttpsServer,
createLoadBalancerRoute
} from '../proxies/smart-proxy/utils/route-patterns.js';

View File

@ -3,6 +3,3 @@
*/
export * from './interfaces.js';
export * from './route-types.js';
// Re-export IRoutedSmartProxyOptions explicitly to avoid ambiguity
export type { ISmartProxyOptions as IRoutedSmartProxyOptions } from './interfaces.js';

View File

@ -8,23 +8,7 @@ import type { TForwardingType } from '../../../forwarding/config/forwarding-type
*/
export type TSmartProxyCertProvisionObject = plugins.tsclass.network.ICert | 'http01';
/**
* Alias for backward compatibility with code that uses IRoutedSmartProxyOptions
*/
export type IRoutedSmartProxyOptions = ISmartProxyOptions;
/**
* Helper functions for type checking configuration types
*/
export function isLegacyOptions(options: any): boolean {
// Legacy options are no longer supported
return false;
}
export function isRoutedOptions(options: any): boolean {
// All configurations are now route-based
return true;
}
// Legacy options and type checking functions have been removed
/**
* SmartProxy configuration options

View File

@ -321,61 +321,4 @@ export interface IRouteConfig {
enabled?: boolean; // Whether the route is active (default: true)
}
/**
* Unified SmartProxy options with routes-based configuration
*/
export interface IRoutedSmartProxyOptions {
// The unified configuration array (required)
routes: IRouteConfig[];
// Global/default settings
defaults?: {
target?: {
host: string;
port: number;
};
security?: IRouteSecurity;
tls?: IRouteTls;
// ...other defaults
};
// Other global settings remain (acme, etc.)
acme?: IAcmeOptions;
// Connection timeouts and other global settings
initialDataTimeout?: number;
socketTimeout?: number;
inactivityCheckInterval?: number;
maxConnectionLifetime?: number;
inactivityTimeout?: number;
gracefulShutdownTimeout?: number;
// Socket optimization settings
noDelay?: boolean;
keepAlive?: boolean;
keepAliveInitialDelay?: number;
maxPendingDataSize?: number;
// Enhanced features
disableInactivityCheck?: boolean;
enableKeepAliveProbes?: boolean;
enableDetailedLogging?: boolean;
enableTlsDebugLogging?: boolean;
enableRandomizedTimeouts?: boolean;
allowSessionTicket?: boolean;
// Rate limiting and security
maxConnectionsPerIP?: number;
connectionRateLimitPerMinute?: number;
// Enhanced keep-alive settings
keepAliveTreatment?: 'standard' | 'extended' | 'immortal';
keepAliveInactivityMultiplier?: number;
extendedKeepAliveLifetime?: number;
/**
* Optional certificate provider callback. Return 'http01' to use HTTP-01 challenges,
* or a static certificate object for immediate provisioning.
*/
certProvisionFunction?: (domain: string) => Promise<any>;
}
// Configuration moved to models/interfaces.ts as ISmartProxyOptions

View File

@ -3,9 +3,7 @@ import type {
IConnectionRecord,
ISmartProxyOptions
} from './models/interfaces.js';
import {
isRoutedOptions
} from './models/interfaces.js';
// Route checking functions have been removed
import type {
IRouteConfig,
IRouteAction,
@ -316,7 +314,6 @@ export class RouteConnectionHandler {
return this.setupDirectConnection(
socket,
record,
undefined,
serverName,
initialChunk,
undefined,
@ -457,7 +454,6 @@ export class RouteConnectionHandler {
return this.setupDirectConnection(
socket,
record,
undefined,
record.lockedDomain,
initialChunk,
undefined,
@ -538,7 +534,6 @@ export class RouteConnectionHandler {
return this.setupDirectConnection(
socket,
record,
undefined,
record.lockedDomain,
initialChunk,
undefined,
@ -656,17 +651,12 @@ export class RouteConnectionHandler {
this.connectionManager.initiateCleanupOnce(record, 'route_blocked');
}
/**
* Legacy connection handling has been removed in favor of pure route-based approach
*/
/**
* Sets up a direct connection to the target
*/
private setupDirectConnection(
socket: plugins.net.Socket,
record: IConnectionRecord,
_unused?: any, // kept for backward compatibility
serverName?: string,
initialChunk?: Buffer,
overridePort?: number,

View File

@ -6,12 +6,7 @@ import type {
TPortRange
} from './models/route-types.js';
import type {
ISmartProxyOptions,
IRoutedSmartProxyOptions
} from './models/interfaces.js';
import {
isRoutedOptions,
isLegacyOptions
ISmartProxyOptions
} from './models/interfaces.js';
/**
@ -29,12 +24,12 @@ export interface IRouteMatchResult {
export class RouteManager extends plugins.EventEmitter {
private routes: IRouteConfig[] = [];
private portMap: Map<number, IRouteConfig[]> = new Map();
private options: IRoutedSmartProxyOptions;
private options: ISmartProxyOptions;
constructor(options: ISmartProxyOptions) {
super();
// We no longer support legacy options, always use provided options
// Store options
this.options = options;
// Initialize routes from either source

View File

@ -19,10 +19,8 @@ import { createPort80HandlerOptions } from '../../common/port80-adapter.js';
// Import types and utilities
import type {
ISmartProxyOptions,
IRoutedSmartProxyOptions
ISmartProxyOptions
} from './models/interfaces.js';
import { isRoutedOptions, isLegacyOptions } from './models/interfaces.js';
import type { IRouteConfig } from './models/route-types.js';
/**
@ -650,7 +648,7 @@ export class SmartProxy extends plugins.EventEmitter {
const domains: string[] = [];
// Get domains from routes
const routes = isRoutedOptions(this.settings) ? this.settings.routes : [];
const routes = this.settings.routes || [];
for (const route of routes) {
if (!route.match.domains) continue;

View File

@ -5,8 +5,7 @@
* including helpers, validators, utilities, and patterns for working with routes.
*/
// Export route helpers for creating routes
export * from './route-helpers.js';
// Route helpers have been consolidated in route-patterns.js
// Export route validators for validating route configurations
export * from './route-validators.js';
@ -35,6 +34,4 @@ export {
addJwtAuth
};
// Export migration utilities for transitioning from domain-based to route-based configs
// Note: These will be removed in a future version once migration is complete
export * from './route-migration-utils.js';
// Migration utilities have been removed as they are no longer needed

View File

@ -1,165 +0,0 @@
/**
* Route Migration Utilities
*
* This file provides utility functions for migrating from legacy domain-based
* configuration to the new route-based configuration system. These functions
* are temporary and will be removed after the migration is complete.
*/
import type { TForwardingType } from '../../../forwarding/config/forwarding-types.js';
import type { IRouteConfig, IRouteMatch, IRouteAction, IRouteTarget } from '../models/route-types.js';
/**
* Legacy domain config interface (for migration only)
* @deprecated This interface will be removed in a future version
*/
export interface ILegacyDomainConfig {
domains: string[];
forwarding: {
type: TForwardingType;
target: {
host: string | string[];
port: number;
};
[key: string]: any;
};
}
/**
* Convert a legacy domain config to a route-based config
* @param domainConfig Legacy domain configuration
* @param additionalOptions Additional options to add to the route
* @returns Route configuration
* @deprecated This function will be removed in a future version
*/
export function domainConfigToRouteConfig(
domainConfig: ILegacyDomainConfig,
additionalOptions: Partial<IRouteConfig> = {}
): IRouteConfig {
// Default port based on forwarding type
let defaultPort = 80;
let tlsMode: 'passthrough' | 'terminate' | 'terminate-and-reencrypt' | undefined;
switch (domainConfig.forwarding.type) {
case 'http-only':
defaultPort = 80;
break;
case 'https-passthrough':
defaultPort = 443;
tlsMode = 'passthrough';
break;
case 'https-terminate-to-http':
defaultPort = 443;
tlsMode = 'terminate';
break;
case 'https-terminate-to-https':
defaultPort = 443;
tlsMode = 'terminate-and-reencrypt';
break;
}
// Create route match criteria
const match: IRouteMatch = {
ports: additionalOptions.match?.ports || defaultPort,
domains: domainConfig.domains
};
// Create route target
const target: IRouteTarget = {
host: domainConfig.forwarding.target.host,
port: domainConfig.forwarding.target.port
};
// Create route action
const action: IRouteAction = {
type: 'forward',
target
};
// Add TLS configuration if needed
if (tlsMode) {
action.tls = {
mode: tlsMode,
certificate: 'auto'
};
// If the legacy config has custom certificates, use them
if (domainConfig.forwarding.https?.customCert) {
action.tls.certificate = {
key: domainConfig.forwarding.https.customCert.key,
cert: domainConfig.forwarding.https.customCert.cert
};
}
}
// Add security options if present
if (domainConfig.forwarding.security) {
action.security = domainConfig.forwarding.security;
}
// Create the route config
const routeConfig: IRouteConfig = {
match,
action,
// Include a name based on domains if not provided
name: additionalOptions.name || `Legacy route for ${domainConfig.domains.join(', ')}`,
// Include a note that this was converted from a legacy config
description: additionalOptions.description || 'Converted from legacy domain configuration'
};
// Add optional properties if provided
if (additionalOptions.priority !== undefined) {
routeConfig.priority = additionalOptions.priority;
}
if (additionalOptions.tags) {
routeConfig.tags = additionalOptions.tags;
}
return routeConfig;
}
/**
* Convert an array of legacy domain configs to route configurations
* @param domainConfigs Array of legacy domain configurations
* @returns Array of route configurations
* @deprecated This function will be removed in a future version
*/
export function domainConfigsToRouteConfigs(
domainConfigs: ILegacyDomainConfig[]
): IRouteConfig[] {
return domainConfigs.map(config => domainConfigToRouteConfig(config));
}
/**
* Extract domains from a route configuration
* @param route Route configuration
* @returns Array of domains
*/
export function extractDomainsFromRoute(route: IRouteConfig): string[] {
if (!route.match.domains) {
return [];
}
return Array.isArray(route.match.domains)
? route.match.domains
: [route.match.domains];
}
/**
* Extract domains from an array of route configurations
* @param routes Array of route configurations
* @returns Array of unique domains
*/
export function extractDomainsFromRoutes(routes: IRouteConfig[]): string[] {
const domains = new Set<string>();
for (const route of routes) {
const routeDomains = extractDomainsFromRoute(route);
for (const domain of routeDomains) {
domains.add(domain);
}
}
return Array.from(domains);
}

View File

@ -5,10 +5,154 @@
* These patterns can be used as templates for creating route configurations.
*/
import type { IRouteConfig } from '../models/route-types.js';
import { createHttpRoute, createHttpsTerminateRoute, createHttpsPassthroughRoute, createCompleteHttpsServer } from './route-helpers.js';
import type { IRouteConfig, IRouteMatch, IRouteAction, IRouteTarget } from '../models/route-types.js';
import { mergeRouteConfigs } from './route-utils.js';
/**
* Create a basic HTTP route configuration
*/
export function createHttpRoute(
domains: string | string[],
target: { host: string | string[]; port: number | 'preserve' | ((ctx: any) => number) },
options: Partial<IRouteConfig> = {}
): IRouteConfig {
const route: IRouteConfig = {
match: {
domains,
ports: 80
},
action: {
type: 'forward',
target: {
host: target.host,
port: target.port
}
},
name: options.name || `HTTP: ${Array.isArray(domains) ? domains.join(', ') : domains}`
};
return mergeRouteConfigs(route, options);
}
/**
* Create an HTTPS route with TLS termination
*/
export function createHttpsTerminateRoute(
domains: string | string[],
target: { host: string | string[]; port: number | 'preserve' | ((ctx: any) => number) },
options: Partial<IRouteConfig> & {
certificate?: 'auto' | { key: string; cert: string };
reencrypt?: boolean;
} = {}
): IRouteConfig {
const route: IRouteConfig = {
match: {
domains,
ports: 443
},
action: {
type: 'forward',
target: {
host: target.host,
port: target.port
},
tls: {
mode: options.reencrypt ? 'terminate-and-reencrypt' : 'terminate',
certificate: options.certificate || 'auto'
}
},
name: options.name || `HTTPS (terminate): ${Array.isArray(domains) ? domains.join(', ') : domains}`
};
return mergeRouteConfigs(route, options);
}
/**
* Create an HTTPS route with TLS passthrough
*/
export function createHttpsPassthroughRoute(
domains: string | string[],
target: { host: string | string[]; port: number | 'preserve' | ((ctx: any) => number) },
options: Partial<IRouteConfig> = {}
): IRouteConfig {
const route: IRouteConfig = {
match: {
domains,
ports: 443
},
action: {
type: 'forward',
target: {
host: target.host,
port: target.port
},
tls: {
mode: 'passthrough'
}
},
name: options.name || `HTTPS (passthrough): ${Array.isArray(domains) ? domains.join(', ') : domains}`
};
return mergeRouteConfigs(route, options);
}
/**
* Create an HTTP to HTTPS redirect route
*/
export function createHttpToHttpsRedirect(
domains: string | string[],
options: Partial<IRouteConfig> & {
redirectCode?: 301 | 302 | 307 | 308;
preservePath?: boolean;
} = {}
): IRouteConfig {
const route: IRouteConfig = {
match: {
domains,
ports: 80
},
action: {
type: 'redirect',
redirect: {
to: options.preservePath ? 'https://{domain}{path}' : 'https://{domain}',
status: options.redirectCode || 301
}
},
name: options.name || `HTTP to HTTPS redirect: ${Array.isArray(domains) ? domains.join(', ') : domains}`
};
return mergeRouteConfigs(route, options);
}
/**
* Create a complete HTTPS server with redirect from HTTP
*/
export function createCompleteHttpsServer(
domains: string | string[],
target: { host: string | string[]; port: number | 'preserve' | ((ctx: any) => number) },
options: Partial<IRouteConfig> & {
certificate?: 'auto' | { key: string; cert: string };
tlsMode?: 'terminate' | 'passthrough' | 'terminate-and-reencrypt';
redirectCode?: 301 | 302 | 307 | 308;
} = {}
): IRouteConfig[] {
// Create the TLS route based on the selected mode
const tlsRoute = options.tlsMode === 'passthrough'
? createHttpsPassthroughRoute(domains, target, options)
: createHttpsTerminateRoute(domains, target, {
...options,
reencrypt: options.tlsMode === 'terminate-and-reencrypt'
});
// Create the HTTP to HTTPS redirect route
const redirectRoute = createHttpToHttpsRedirect(domains, {
redirectCode: options.redirectCode,
preservePath: true
});
return [tlsRoute, redirectRoute];
}
/**
* Create an API Gateway route pattern
* @param domains Domain(s) to match