feat(smartproxy): Update documentation and route helper functions; add createPortRange, createSecurityConfig, createStaticFileRoute, and createTestRoute helpers to the readme and tests. Refactor test examples to use the new helper API and remove legacy connection handling files (including the old connection handler and PortRangeManager) to fully embrace the unified route‐based configuration.

This commit is contained in:
Philipp Kunz 2025-05-10 07:34:35 +00:00
parent 36bea96ac7
commit b4a0e4be6b
14 changed files with 458 additions and 193 deletions

View File

@ -1,5 +1,12 @@
# Changelog # Changelog
## 2025-05-10 - 15.1.0 - feat(smartproxy)
Update documentation and route helper functions; add createPortRange, createSecurityConfig, createStaticFileRoute, and createTestRoute helpers to the readme and tests. Refactor test examples to use the new helper API and remove legacy connection handling files (including the old connection handler and PortRangeManager) to fully embrace the unified routebased configuration.
- Added new helper functions (createPortRange, createSecurityConfig, createStaticFileRoute, createTestRoute) in readme and route helpers.
- Refactored tests (test.forwarding.examples.ts, test.forwarding.unit.ts, etc.) to update references to the new API.
- Removed legacy connection handler and PortRangeManager files to simplify code and align with routebased configuration.
## 2025-05-10 - 15.0.0 - BREAKING CHANGE(documentation) ## 2025-05-10 - 15.0.0 - BREAKING CHANGE(documentation)
Update readme documentation to comprehensively describe the new unified route-based configuration system in v14.0.0 Update readme documentation to comprehensively describe the new unified route-based configuration system in v14.0.0

View File

@ -463,6 +463,10 @@ Available helper functions:
- `createBlockRoute()` - Create a route to block specific traffic - `createBlockRoute()` - Create a route to block specific traffic
- `createLoadBalancerRoute()` - Create a route for load balancing - `createLoadBalancerRoute()` - Create a route for load balancing
- `createHttpsServer()` - Create a complete HTTPS server setup with HTTP redirect - `createHttpsServer()` - Create a complete HTTPS server setup with HTTP redirect
- `createPortRange()` - Helper to create port range configurations from various formats
- `createSecurityConfig()` - Helper to create security configuration objects
- `createStaticFileRoute()` - Create a route for serving static files
- `createTestRoute()` - Create a test route for debugging and testing purposes
## What You Can Do with SmartProxy ## What You Can Do with SmartProxy

View File

@ -1,112 +1,197 @@
import * as plugins from '../ts/plugins.js'; import * as path from 'path';
import { tap, expect } from '@push.rocks/tapbundle'; import { tap, expect } from '@push.rocks/tapbundle';
import { SmartProxy } from '../ts/proxies/smart-proxy/index.js'; import { SmartProxy } from '../ts/proxies/smart-proxy/index.js';
import type { TForwardingType } from '../ts/forwarding/config/forwarding-types.js';
import type { IDomainConfig } from '../ts/forwarding/config/domain-config.js';
import { import {
httpOnly, createHttpRoute,
httpsPassthrough, createHttpsRoute,
tlsTerminateToHttp, createPassthroughRoute,
tlsTerminateToHttps createRedirectRoute,
} from '../ts/forwarding/config/forwarding-types.js'; createHttpToHttpsRedirect,
createBlockRoute,
createLoadBalancerRoute,
createHttpsServer,
createPortRange,
createSecurityConfig,
createStaticFileRoute,
createTestRoute
} from '../ts/proxies/smart-proxy/route-helpers/index.js';
import type { IRouteConfig } from '../ts/proxies/smart-proxy/models/route-types.js';
// Test to demonstrate various forwarding configurations // Test to demonstrate various route configurations using the new helpers
tap.test('Forwarding configuration examples', async (tools) => { tap.test('Route-based configuration examples', async (tools) => {
// Example 1: HTTP-only configuration // Example 1: HTTP-only configuration
const httpOnlyConfig: IDomainConfig = { const httpOnlyRoute = createHttpRoute({
domains: ['http.example.com'], domains: 'http.example.com',
forwarding: httpOnly({ target: {
target: { host: 'localhost',
host: 'localhost', port: 3000
port: 3000 },
}, security: {
security: { allowedIps: ['*'] // Allow all
allowedIps: ['*'] // Allow all },
} name: 'Basic HTTP Route'
}) });
};
console.log(httpOnlyConfig.forwarding, 'HTTP-only configuration created successfully');
expect(httpOnlyConfig.forwarding.type).toEqual('http-only');
// Example 2: HTTPS Passthrough (SNI) console.log('HTTP-only route created successfully:', httpOnlyRoute.name);
const httpsPassthroughConfig: IDomainConfig = { expect(httpOnlyRoute.action.type).toEqual('forward');
domains: ['pass.example.com'], expect(httpOnlyRoute.match.domains).toEqual('http.example.com');
forwarding: httpsPassthrough({
target: { // Example 2: HTTPS Passthrough (SNI) configuration
host: ['10.0.0.1', '10.0.0.2'], // Round-robin target IPs const httpsPassthroughRoute = createPassthroughRoute({
port: 443 domains: 'pass.example.com',
}, target: {
security: { host: ['10.0.0.1', '10.0.0.2'], // Round-robin target IPs
allowedIps: ['*'] // Allow all port: 443
} },
}) security: {
}; allowedIps: ['*'] // Allow all
expect(httpsPassthroughConfig.forwarding).toBeTruthy(); },
expect(httpsPassthroughConfig.forwarding.type).toEqual('https-passthrough'); name: 'HTTPS Passthrough Route'
expect(Array.isArray(httpsPassthroughConfig.forwarding.target.host)).toBeTrue(); });
expect(httpsPassthroughRoute).toBeTruthy();
expect(httpsPassthroughRoute.action.tls?.mode).toEqual('passthrough');
expect(Array.isArray(httpsPassthroughRoute.action.target?.host)).toBeTrue();
// Example 3: HTTPS Termination to HTTP Backend // Example 3: HTTPS Termination to HTTP Backend
const terminateToHttpConfig: IDomainConfig = { const terminateToHttpRoute = createHttpsRoute({
domains: ['secure.example.com'], domains: 'secure.example.com',
forwarding: tlsTerminateToHttp({ target: {
target: { host: 'localhost',
host: 'localhost', port: 8080
port: 8080 },
}, tlsMode: 'terminate',
http: { certificate: 'auto',
redirectToHttps: true, // Redirect HTTP requests to HTTPS headers: {
headers: { 'X-Forwarded-Proto': 'https'
'X-Forwarded-Proto': 'https' },
} security: {
}, allowedIps: ['*'] // Allow all
acme: { },
enabled: true, name: 'HTTPS Termination to HTTP Backend'
maintenance: true, });
production: false // Use staging ACME server for testing
},
security: {
allowedIps: ['*'] // Allow all
}
})
};
expect(terminateToHttpConfig.forwarding).toBeTruthy();
expect(terminateToHttpConfig.forwarding.type).toEqual('https-terminate-to-http');
expect(terminateToHttpConfig.forwarding.http?.redirectToHttps).toBeTrue();
// Example 4: HTTPS Termination to HTTPS Backend // Create the HTTP to HTTPS redirect for this domain
const terminateToHttpsConfig: IDomainConfig = { const httpToHttpsRedirect = createHttpToHttpsRedirect({
domains: ['proxy.example.com'], domains: 'secure.example.com',
forwarding: tlsTerminateToHttps({ name: 'HTTP to HTTPS Redirect for secure.example.com'
target: { });
host: 'internal-api.local',
port: 8443
},
https: {
forwardSni: true // Forward original SNI info
},
security: {
allowedIps: ['10.0.0.0/24', '192.168.1.0/24'],
maxConnections: 1000
},
advanced: {
timeout: 3600000, // 1 hour in ms
headers: {
'X-Original-Host': '{sni}'
}
}
})
};
expect(terminateToHttpsConfig.forwarding).toBeTruthy();
expect(terminateToHttpsConfig.forwarding.type).toEqual('https-terminate-to-https');
expect(terminateToHttpsConfig.forwarding.https?.forwardSni).toBeTrue();
expect(terminateToHttpsConfig.forwarding.security?.allowedIps?.length).toEqual(2);
// Skip the SmartProxy integration test for now and just verify our configuration objects work expect(terminateToHttpRoute).toBeTruthy();
console.log('All forwarding configurations were created successfully'); expect(terminateToHttpRoute.action.tls?.mode).toEqual('terminate');
expect(terminateToHttpRoute.action.advanced?.headers?.['X-Forwarded-Proto']).toEqual('https');
expect(httpToHttpsRedirect.action.type).toEqual('redirect');
// This is just to verify that our test passes // Example 4: Load Balancer with HTTPS
expect(true).toBeTrue(); const loadBalancerRoute = createLoadBalancerRoute({
domains: 'proxy.example.com',
targets: ['internal-api-1.local', 'internal-api-2.local'],
targetPort: 8443,
tlsMode: 'terminate-and-reencrypt',
certificate: 'auto',
headers: {
'X-Original-Host': '{domain}'
},
security: {
allowedIps: ['10.0.0.0/24', '192.168.1.0/24'],
maxConnections: 1000
},
name: 'Load Balanced HTTPS Route'
});
expect(loadBalancerRoute).toBeTruthy();
expect(loadBalancerRoute.action.tls?.mode).toEqual('terminate-and-reencrypt');
expect(Array.isArray(loadBalancerRoute.action.target?.host)).toBeTrue();
expect(loadBalancerRoute.action.security?.allowedIps?.length).toEqual(2);
// Example 5: Block specific IPs
const blockRoute = createBlockRoute({
ports: [80, 443],
clientIp: ['192.168.5.0/24'],
name: 'Block Suspicious IPs',
priority: 1000 // High priority to ensure it's evaluated first
});
expect(blockRoute.action.type).toEqual('block');
expect(blockRoute.match.clientIp?.length).toEqual(1);
expect(blockRoute.priority).toEqual(1000);
// Example 6: Complete HTTPS Server with HTTP Redirect
const httpsServerRoutes = createHttpsServer({
domains: 'complete.example.com',
target: {
host: 'localhost',
port: 8080
},
certificate: 'auto',
name: 'Complete HTTPS Server'
});
expect(Array.isArray(httpsServerRoutes)).toBeTrue();
expect(httpsServerRoutes.length).toEqual(2); // HTTPS route and HTTP redirect
expect(httpsServerRoutes[0].action.tls?.mode).toEqual('terminate');
expect(httpsServerRoutes[1].action.type).toEqual('redirect');
// Example 7: Static File Server
const staticFileRoute = createStaticFileRoute({
domains: 'static.example.com',
targetDirectory: '/var/www/static',
tlsMode: 'terminate',
certificate: 'auto',
headers: {
'Cache-Control': 'public, max-age=86400'
},
name: 'Static File Server'
});
expect(staticFileRoute.action.advanced?.staticFiles?.directory).toEqual('/var/www/static');
expect(staticFileRoute.action.advanced?.headers?.['Cache-Control']).toEqual('public, max-age=86400');
// Example 8: Test Route for Debugging
const testRoute = createTestRoute({
ports: 8000,
domains: 'test.example.com',
response: {
status: 200,
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ status: 'ok', message: 'API is working!' })
}
});
expect(testRoute.match.ports).toEqual(8000);
expect(testRoute.action.advanced?.testResponse?.status).toEqual(200);
// Create a SmartProxy instance with all routes
const allRoutes: IRouteConfig[] = [
httpOnlyRoute,
httpsPassthroughRoute,
terminateToHttpRoute,
httpToHttpsRedirect,
loadBalancerRoute,
blockRoute,
...httpsServerRoutes,
staticFileRoute,
testRoute
];
// We're not actually starting the SmartProxy in this test,
// just verifying that the configuration is valid
const smartProxy = new SmartProxy({
routes: allRoutes,
acme: {
email: 'admin@example.com',
termsOfServiceAgreed: true,
directoryUrl: 'https://acme-staging-v02.api.letsencrypt.org/directory'
}
});
console.log(`Smart Proxy configured with ${allRoutes.length} routes`);
// Verify our example proxy was created correctly
expect(smartProxy).toBeTruthy();
}); });
export default tap.start(); export default tap.start();

View File

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

@ -23,10 +23,10 @@ export type TPortRange = number | number[] | Array<{ from: number; to: number }>
export interface IRouteMatch { export interface IRouteMatch {
// Listen on these ports (required) // Listen on these ports (required)
ports: TPortRange; ports: TPortRange;
// Optional domain patterns to match (default: all domains) // Optional domain patterns to match (default: all domains)
domains?: string | string[]; domains?: string | string[];
// Advanced matching criteria // Advanced matching criteria
path?: string; // Match specific paths path?: string; // Match specific paths
clientIp?: string[]; // Match specific client IPs clientIp?: string[]; // Match specific client IPs
@ -61,6 +61,25 @@ export interface IRouteRedirect {
status: 301 | 302 | 307 | 308; status: 301 | 302 | 307 | 308;
} }
/**
* Authentication options
*/
export interface IRouteAuthentication {
type: 'basic' | 'digest' | 'oauth' | 'jwt';
credentials?: {
username: string;
password: string;
}[];
realm?: string;
jwtSecret?: string;
jwtIssuer?: string;
oauthProvider?: string;
oauthClientId?: string;
oauthClientSecret?: string;
oauthRedirectUri?: string;
[key: string]: any; // Allow additional auth-specific options
}
/** /**
* Security options for route actions * Security options for route actions
*/ */
@ -68,10 +87,28 @@ export interface IRouteSecurity {
allowedIps?: string[]; allowedIps?: string[];
blockedIps?: string[]; blockedIps?: string[];
maxConnections?: number; maxConnections?: number;
authentication?: { authentication?: IRouteAuthentication;
type: 'basic' | 'digest' | 'oauth'; }
// Auth-specific options would go here
}; /**
* Static file server configuration
*/
export interface IRouteStaticFiles {
directory: string;
indexFiles?: string[];
cacheControl?: string;
expires?: number;
followSymlinks?: boolean;
disableDirectoryListing?: boolean;
}
/**
* Test route response configuration
*/
export interface IRouteTestResponse {
status: number;
headers: Record<string, string>;
body: string;
} }
/** /**
@ -81,6 +118,8 @@ export interface IRouteAdvanced {
timeout?: number; timeout?: number;
headers?: Record<string, string>; headers?: Record<string, string>;
keepAlive?: boolean; keepAlive?: boolean;
staticFiles?: IRouteStaticFiles;
testResponse?: IRouteTestResponse;
// Additional advanced options would go here // Additional advanced options would go here
} }
@ -90,19 +129,19 @@ export interface IRouteAdvanced {
export interface IRouteAction { export interface IRouteAction {
// Basic routing // Basic routing
type: TRouteActionType; type: TRouteActionType;
// Target for forwarding // Target for forwarding
target?: IRouteTarget; target?: IRouteTarget;
// TLS handling // TLS handling
tls?: IRouteTls; tls?: IRouteTls;
// For redirects // For redirects
redirect?: IRouteRedirect; redirect?: IRouteRedirect;
// Security options // Security options
security?: IRouteSecurity; security?: IRouteSecurity;
// Advanced options // Advanced options
advanced?: IRouteAdvanced; advanced?: IRouteAdvanced;
} }
@ -113,10 +152,10 @@ export interface IRouteAction {
export interface IRouteConfig { export interface IRouteConfig {
// What to match // What to match
match: IRouteMatch; match: IRouteMatch;
// What to do with matched traffic // What to do with matched traffic
action: IRouteAction; action: IRouteAction;
// Optional metadata // Optional metadata
name?: string; // Human-readable name for this route name?: string; // Human-readable name for this route
description?: string; // Description of the route's purpose description?: string; // Description of the route's purpose
@ -130,7 +169,7 @@ export interface IRouteConfig {
export interface IRoutedSmartProxyOptions { export interface IRoutedSmartProxyOptions {
// The unified configuration array (required) // The unified configuration array (required)
routes: IRouteConfig[]; routes: IRouteConfig[];
// Global/default settings // Global/default settings
defaults?: { defaults?: {
target?: { target?: {
@ -141,10 +180,10 @@ export interface IRoutedSmartProxyOptions {
tls?: IRouteTls; tls?: IRouteTls;
// ...other defaults // ...other defaults
}; };
// Other global settings remain (acme, etc.) // Other global settings remain (acme, etc.)
acme?: IAcmeOptions; acme?: IAcmeOptions;
// Connection timeouts and other global settings // Connection timeouts and other global settings
initialDataTimeout?: number; initialDataTimeout?: number;
socketTimeout?: number; socketTimeout?: number;
@ -152,13 +191,13 @@ export interface IRoutedSmartProxyOptions {
maxConnectionLifetime?: number; maxConnectionLifetime?: number;
inactivityTimeout?: number; inactivityTimeout?: number;
gracefulShutdownTimeout?: number; gracefulShutdownTimeout?: number;
// Socket optimization settings // Socket optimization settings
noDelay?: boolean; noDelay?: boolean;
keepAlive?: boolean; keepAlive?: boolean;
keepAliveInitialDelay?: number; keepAliveInitialDelay?: number;
maxPendingDataSize?: number; maxPendingDataSize?: number;
// Enhanced features // Enhanced features
disableInactivityCheck?: boolean; disableInactivityCheck?: boolean;
enableKeepAliveProbes?: boolean; enableKeepAliveProbes?: boolean;
@ -166,16 +205,16 @@ export interface IRoutedSmartProxyOptions {
enableTlsDebugLogging?: boolean; enableTlsDebugLogging?: boolean;
enableRandomizedTimeouts?: boolean; enableRandomizedTimeouts?: boolean;
allowSessionTicket?: boolean; allowSessionTicket?: boolean;
// Rate limiting and security // Rate limiting and security
maxConnectionsPerIP?: number; maxConnectionsPerIP?: number;
connectionRateLimitPerMinute?: number; connectionRateLimitPerMinute?: number;
// Enhanced keep-alive settings // Enhanced keep-alive settings
keepAliveTreatment?: 'standard' | 'extended' | 'immortal'; keepAliveTreatment?: 'standard' | 'extended' | 'immortal';
keepAliveInactivityMultiplier?: number; keepAliveInactivityMultiplier?: number;
extendedKeepAliveLifetime?: number; extendedKeepAliveLifetime?: number;
/** /**
* Optional certificate provider callback. Return 'http01' to use HTTP-01 challenges, * Optional certificate provider callback. Return 'http01' to use HTTP-01 challenges,
* or a static certificate object for immediate provisioning. * or a static certificate object for immediate provisioning.

View File

@ -377,16 +377,10 @@ export class NetworkProxyBridge {
publicKey: certCert, publicKey: certCert,
destinationIps: targetHosts, destinationIps: targetHosts,
destinationPorts: [targetPort], destinationPorts: [targetPort],
proxyConfig: { // Use backendProtocol for TLS re-encryption:
targetIsTls: route.action.tls.mode === 'terminate-and-reencrypt', backendProtocol: route.action.tls.mode === 'terminate-and-reencrypt' ? 'http2' : 'http1',
allowHTTP1: true, // Add rewriteHostHeader for host header handling:
// Apply any other NetworkProxy-specific settings rewriteHostHeader: route.action.advanced?.headers ? true : false
...(route.action.advanced ? {
preserveHost: true,
timeout: route.action.advanced.timeout,
headers: route.action.advanced.headers
} : {})
}
}; };
configs.push(config); configs.push(config);

View File

@ -741,20 +741,7 @@ export class RouteConnectionHandler {
this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed'); this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed');
} }
// If we have a forwarding handler for this domain, let it handle the error // Route-based configuration doesn't use domain handlers
if (domainConfig) {
try {
const forwardingHandler = this.domainConfigManager.getForwardingHandler(domainConfig);
forwardingHandler.emit('connection_error', {
socket,
error: err,
connectionId
});
} catch (handlerErr) {
// If getting the handler fails, just log and continue with normal cleanup
console.log(`Error getting forwarding handler for error handling: ${handlerErr}`);
}
}
// Clean up the connection // Clean up the connection
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`); this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`);
@ -887,8 +874,8 @@ export class RouteConnectionHandler {
`${ `${
serverName serverName
? ` (SNI: ${serverName})` ? ` (SNI: ${serverName})`
: domainConfig : record.lockedDomain
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})` ? ` (Domain: ${record.lockedDomain})`
: '' : ''
}` + }` +
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${ ` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
@ -901,8 +888,8 @@ export class RouteConnectionHandler {
`${ `${
serverName serverName
? ` (SNI: ${serverName})` ? ` (SNI: ${serverName})`
: domainConfig : record.lockedDomain
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})` ? ` (Domain: ${record.lockedDomain})`
: '' : ''
}` }`
); );

View File

@ -1,12 +1,13 @@
import type { import type {
IRouteConfig, IRouteConfig,
IRouteMatch, IRouteMatch,
IRouteAction, IRouteAction,
IRouteTarget, IRouteTarget,
IRouteTls, IRouteTls,
IRouteRedirect, IRouteRedirect,
IRouteSecurity, IRouteSecurity,
IRouteAdvanced IRouteAdvanced,
TPortRange
} from './models/route-types.js'; } from './models/route-types.js';
/** /**
@ -30,7 +31,7 @@ export function createRoute(
} }
/** /**
* Create a basic HTTP route configuration * Create a basic HTTP route configuration
*/ */
export function createHttpRoute( export function createHttpRoute(
options: { options: {
@ -206,7 +207,7 @@ export function createHttpToHttpsRedirect(
} }
): IRouteConfig { ): IRouteConfig {
const domainArray = Array.isArray(options.domains) ? options.domains : [options.domains]; const domainArray = Array.isArray(options.domains) ? options.domains : [options.domains];
return createRedirectRoute({ return createRedirectRoute({
ports: 80, ports: 80,
domains: options.domains, domains: options.domains,
@ -270,7 +271,7 @@ export function createLoadBalancerRoute(
): IRouteConfig { ): IRouteConfig {
const useTls = options.tlsMode !== undefined; const useTls = options.tlsMode !== undefined;
const defaultPort = useTls ? 443 : 80; const defaultPort = useTls ? 443 : 80;
return createRoute( return createRoute(
{ {
ports: options.ports || defaultPort, ports: options.ports || defaultPort,
@ -321,7 +322,7 @@ export function createHttpsServer(
): IRouteConfig[] { ): IRouteConfig[] {
const routes: IRouteConfig[] = []; const routes: IRouteConfig[] = [];
const domainArray = Array.isArray(options.domains) ? options.domains : [options.domains]; const domainArray = Array.isArray(options.domains) ? options.domains : [options.domains];
// Add HTTPS route // Add HTTPS route
routes.push(createHttpsRoute({ routes.push(createHttpsRoute({
domains: options.domains, domains: options.domains,
@ -330,7 +331,7 @@ export function createHttpsServer(
security: options.security, security: options.security,
name: options.name || `HTTPS Server for ${domainArray.join(', ')}` name: options.name || `HTTPS Server for ${domainArray.join(', ')}`
})); }));
// Add HTTP to HTTPS redirect if requested // Add HTTP to HTTPS redirect if requested
if (options.addHttpRedirect !== false) { if (options.addHttpRedirect !== false) {
routes.push(createHttpToHttpsRedirect({ routes.push(createHttpToHttpsRedirect({
@ -339,6 +340,158 @@ export function createHttpsServer(
priority: 100 priority: 100
})); }));
} }
return routes; 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']
}
);
} }

View File

@ -0,0 +1,9 @@
/**
* Route helpers for SmartProxy
*
* This module provides helper functions for creating various types of route configurations
* to be used with the SmartProxy system.
*/
// Re-export all functions from the route-helpers.ts file
export * from '../route-helpers.js';

View File

@ -7,8 +7,7 @@ import type {
} from './models/route-types.js'; } from './models/route-types.js';
import type { import type {
ISmartProxyOptions, ISmartProxyOptions,
IRoutedSmartProxyOptions, IRoutedSmartProxyOptions
IDomainConfig
} from './models/interfaces.js'; } from './models/interfaces.js';
import { import {
isRoutedOptions, isRoutedOptions,

View File

@ -6,7 +6,7 @@ import { SecurityManager } from './security-manager.js';
import { TlsManager } from './tls-manager.js'; import { TlsManager } from './tls-manager.js';
import { NetworkProxyBridge } from './network-proxy-bridge.js'; import { NetworkProxyBridge } from './network-proxy-bridge.js';
import { TimeoutManager } from './timeout-manager.js'; import { TimeoutManager } from './timeout-manager.js';
import { PortRangeManager } from './port-range-manager.js'; // import { PortRangeManager } from './port-range-manager.js';
import { RouteManager } from './route-manager.js'; import { RouteManager } from './route-manager.js';
import { RouteConnectionHandler } from './route-connection-handler.js'; import { RouteConnectionHandler } from './route-connection-handler.js';
@ -22,7 +22,7 @@ import type {
ISmartProxyOptions, ISmartProxyOptions,
IRoutedSmartProxyOptions IRoutedSmartProxyOptions
} from './models/interfaces.js'; } from './models/interfaces.js';
import { isRoutedOptions } from './models/interfaces.js'; import { isRoutedOptions, isLegacyOptions } from './models/interfaces.js';
import type { IRouteConfig } from './models/route-types.js'; import type { IRouteConfig } from './models/route-types.js';
/** /**
@ -49,7 +49,7 @@ export class SmartProxy extends plugins.EventEmitter {
private tlsManager: TlsManager; private tlsManager: TlsManager;
private networkProxyBridge: NetworkProxyBridge; private networkProxyBridge: NetworkProxyBridge;
private timeoutManager: TimeoutManager; private timeoutManager: TimeoutManager;
private portRangeManager: PortRangeManager; // private portRangeManager: PortRangeManager;
private routeManager: RouteManager; private routeManager: RouteManager;
private routeConnectionHandler: RouteConnectionHandler; private routeConnectionHandler: RouteConnectionHandler;
@ -133,7 +133,7 @@ export class SmartProxy extends plugins.EventEmitter {
autoRenew: true, autoRenew: true,
certificateStore: './certs', certificateStore: './certs',
skipConfiguredCerts: false, skipConfiguredCerts: false,
httpsRedirectPort: this.settings.fromPort || 443, httpsRedirectPort: 443,
renewCheckIntervalHours: 24, renewCheckIntervalHours: 24,
domainForwards: [] domainForwards: []
}; };
@ -152,7 +152,7 @@ export class SmartProxy extends plugins.EventEmitter {
this.routeManager = new RouteManager(this.settings); this.routeManager = new RouteManager(this.settings);
// Create port range manager // Create port range manager
this.portRangeManager = new PortRangeManager(this.settings); // this.portRangeManager = new PortRangeManager(this.settings);
// Create other required components // Create other required components
this.tlsManager = new TlsManager(this.settings); this.tlsManager = new TlsManager(this.settings);
@ -220,22 +220,22 @@ export class SmartProxy extends plugins.EventEmitter {
if (this.port80Handler) { if (this.port80Handler) {
const acme = this.settings.acme!; const acme = this.settings.acme!;
// Setup domain forwards based on configuration type // Setup domain forwards
const domainForwards = acme.domainForwards?.map(f => { const domainForwards = acme.domainForwards?.map(f => {
if (isLegacyOptions(this.settings)) { // Check if a matching route exists
// If using legacy mode, check if domain config exists const matchingRoute = this.settings.routes.find(
const domainConfig = this.settings.domainConfigs.find( route => Array.isArray(route.match.domains)
dc => dc.domains.some(d => d === f.domain) ? route.match.domains.some(d => d === f.domain)
); : route.match.domains === f.domain
);
if (domainConfig?.forwarding) { if (matchingRoute) {
return { return {
domain: f.domain, domain: f.domain,
forwardConfig: f.forwardConfig, forwardConfig: f.forwardConfig,
acmeForwardConfig: f.acmeForwardConfig, acmeForwardConfig: f.acmeForwardConfig,
sslRedirect: f.sslRedirect || domainConfig.forwarding.http?.redirectToHttps || false sslRedirect: f.sslRedirect || false
}; };
}
} else { } else {
// In route mode, look for matching route // In route mode, look for matching route
const route = this.routeManager.findMatchingRoute({ const route = this.routeManager.findMatchingRoute({
@ -332,10 +332,8 @@ export class SmartProxy extends plugins.EventEmitter {
const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port); const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
console.log( console.log(
`SmartProxy -> OK: Now listening on port ${port}${ `SmartProxy -> OK: Now listening on port ${port}${
isLegacyOptions(this.settings) && this.settings.sniEnabled && !isNetworkProxyPort ? isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''
' (SNI passthrough enabled)' : }`
''
}${isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''}`
); );
}); });
@ -723,17 +721,7 @@ export class SmartProxy extends plugins.EventEmitter {
domains.push(...eligibleDomains); domains.push(...eligibleDomains);
} }
// For legacy mode, also get domains from domain configs // Legacy mode is no longer supported
if (isLegacyOptions(this.settings)) {
for (const config of this.settings.domainConfigs) {
// Skip domains that can't be used with ACME
const eligibleDomains = config.domains.filter(domain =>
!domain.includes('*') && this.isValidDomain(domain)
);
domains.push(...eligibleDomains);
}
}
return domains; return domains;
} }