Compare commits

...

8 Commits

Author SHA1 Message Date
cf96ff8a47 18.0.1
Some checks failed
Default (tags) / security (push) Successful in 36s
Default (tags) / test (push) Failing after 1h10m20s
Default (tags) / release (push) Has been cancelled
Default (tags) / metadata (push) Has been cancelled
2025-05-15 09:56:33 +00:00
94e9eafa25 fix(smartproxy): Consolidate duplicate IRouteSecurity interfaces to use standardized property names (ipAllowList and ipBlockList), fix port preservation logic for preserve mode in forward actions, and update dependency versions in package.json. 2025-05-15 09:56:32 +00:00
3e411667e6 18.0.0
Some checks failed
Default (tags) / security (push) Successful in 43s
Default (tags) / test (push) Failing after 1h11m0s
Default (tags) / release (push) Has been cancelled
Default (tags) / metadata (push) Has been cancelled
2025-05-15 09:34:01 +00:00
35d7dfcedf BREAKING CHANGE(IRouteSecurity): Consolidate duplicated IRouteSecurity interfaces by unifying property names 2025-05-15 09:34:01 +00:00
1067177d82 17.0.0
Some checks failed
Default (tags) / security (push) Successful in 45s
Default (tags) / test (push) Failing after 1h11m2s
Default (tags) / release (push) Has been cancelled
Default (tags) / metadata (push) Has been cancelled
2025-05-15 08:56:27 +00:00
ac3a888453 BREAKING CHANGE(smartproxy): Remove legacy migration utilities and deprecated forwarding helpers; consolidate route utilities, streamline interface definitions, and normalize IPv6-mapped IPv4 addresses 2025-05-15 08:56:27 +00:00
aa1194ba5d 16.0.4
Some checks failed
Default (tags) / security (push) Successful in 47s
Default (tags) / test (push) Failing after 1h11m4s
Default (tags) / release (push) Has been cancelled
Default (tags) / metadata (push) Has been cancelled
2025-05-14 18:35:06 +00:00
340823296a fix(smartproxy): Update dynamic port mapping to support 2025-05-14 18:35:06 +00:00
29 changed files with 1251 additions and 582 deletions

View File

@ -1,5 +1,50 @@
# Changelog
## 2025-05-15 - 18.0.1 - fix(smartproxy)
Consolidate duplicate IRouteSecurity interfaces to use standardized property names (ipAllowList and ipBlockList), fix port preservation logic for 'preserve' mode in forward actions, and update dependency versions in package.json.
- Unified the duplicate IRouteSecurity interfaces into a single definition using ipAllowList and ipBlockList.
- Updated security checks (e.g. isClientIpAllowed) to use the new standardized property names.
- Fixed the resolvePort function to properly handle 'preserve' mode and function-based port mapping.
- Bumped dependency versions: @push.rocks/smartacme from 7.3.2 to 7.3.3 and @types/node to 22.15.18, and updated tsbuild from 2.3.2 to 2.4.1.
- Revised documentation and changelog to reflect the interface consolidation and bug fixes.
## 2025-05-15 - 18.0.0 - BREAKING CHANGE(IRouteSecurity)
Consolidate duplicated IRouteSecurity interfaces by unifying property names (using 'ipAllowList' and 'ipBlockList' exclusively) and removing legacy definitions, updating security checks throughout the codebase to handle IPv6-mapped IPv4 addresses and cleaning up deprecated forwarding helpers.
- Unified duplicate IRouteSecurity definitions into a single interface with consistent property names.
- Replaced 'allowedIps' and 'blockedIps' with 'ipAllowList' and 'ipBlockList' respectively.
- Updated references in security and route managers to use the new properties.
- Ensured consistent IPv6-mapped IPv4 normalization in IP security checks.
- Removed deprecated helpers and legacy code affecting port forwarding and route migration.
## 2025-05-15 - 17.0.0 - BREAKING CHANGE(smartproxy)
Remove legacy migration utilities and deprecated forwarding helpers; consolidate route utilities, streamline interface definitions, and normalize IPv6-mapped IPv4 addresses
- Deleted ts/proxies/smart-proxy/utils/route-migration-utils.ts and removed its re-exports
- Removed deprecated helper functions (httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, httpsPassthrough) from ts/forwarding/config/forwarding-types.ts
- Updated ts/common/port80-adapter.ts to consistently normalize IPv6-mapped IPv4 addresses in IP comparisons
- Cleaned up legacy connection handling code in route-connection-handler.ts by removing unused parameters and obsolete comments
- Consolidated route utilities by replacing imports from route-helpers.js with route-patterns.js in multiple modules
- Simplified interface definitions by removing legacy aliases and type checking functions from models/interfaces.ts
- Enhanced type safety by replacing any remaining 'any' types with specific types throughout the codebase
- Updated documentation comments and removed references to deprecated functionality
## 2025-05-14 - 16.0.4 - fix(smartproxy)
Update dynamic port mapping to support 'preserve' target port value
- Refactored NetworkProxy to use a default port for 'preserve' values, correctly falling back to the incoming port when target.port is set to 'preserve'.
- Updated RequestHandler and WebSocketHandler to check for 'preserve' target port instead of legacy preservePort flag.
- Modified IRouteTarget type definitions to allow 'preserve' as a valid target port value.
## 2025-05-14 - 16.0.4 - fix(smartproxy)
Fix dynamic port mapping: update target port resolution to properly handle 'preserve' values across route configurations. Now, when a route's target port is set to 'preserve', the incoming port is used consistently in NetworkProxy, RequestHandler, WebSocketHandler, and RouteConnectionHandler. Also update type definitions in IRouteTarget to support 'preserve'.
- Refactored port resolution in NetworkProxy to use a default port for 'preserve' and then correctly fall back to the incoming port when 'preserve' is specified.
- Updated RequestHandler and WebSocketHandler to check if target.port equals 'preserve' instead of using a legacy 'preservePort' flag.
- Modified RouteConnectionHandler to correctly resolve dynamic port mappings with 'preserve'.
- Updated route type definitions to allow 'preserve' as a valid target port value.
## 2025-05-14 - 16.0.3 - fix(network-proxy, route-utils, route-manager)
Normalize IPv6-mapped IPv4 addresses in IP matching functions and remove deprecated legacy configuration methods in NetworkProxy. Update route-utils and route-manager to compare both canonical and IPv6-mapped IP forms, adjust tests accordingly, and clean up legacy exports.

468
docs/porthandling.md Normal file
View File

@ -0,0 +1,468 @@
# SmartProxy Port Handling
This document covers all the port handling capabilities in SmartProxy, including port range specification, dynamic port mapping, and runtime port management.
## Port Range Syntax
SmartProxy offers flexible port range specification through the `TPortRange` type, which can be defined in three different ways:
### 1. Single Port
```typescript
// Match a single port
{
match: {
ports: 443
}
}
```
### 2. Array of Specific Ports
```typescript
// Match multiple specific ports
{
match: {
ports: [80, 443, 8080]
}
}
```
### 3. Port Range
```typescript
// Match a range of ports
{
match: {
ports: [{ from: 8000, to: 8100 }]
}
}
```
### 4. Mixed Port Specifications
You can combine different port specification methods in a single rule:
```typescript
// Match both specific ports and port ranges
{
match: {
ports: [80, 443, { from: 8000, to: 8100 }]
}
}
```
## Port Forwarding Options
SmartProxy offers several ways to handle port forwarding from source to target:
### 1. Static Port Forwarding
Forward to a fixed target port:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: 8080
}
}
}
```
### 2. Preserve Source Port
Forward to the same port on the target:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: 'preserve'
}
}
}
```
### 3. Dynamic Port Mapping
Use a function to determine the target port based on connection context:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: (context) => {
// Calculate port based on request details
return 8000 + (context.port % 100);
}
}
}
}
```
## Port Selection Context
When using dynamic port mapping functions, you have access to a rich context object that provides details about the connection:
```typescript
interface IRouteContext {
// Connection information
port: number; // The matched incoming port
domain?: string; // The domain from SNI or Host header
clientIp: string; // The client's IP address
serverIp: string; // The server's IP address
path?: string; // URL path (for HTTP connections)
query?: string; // Query string (for HTTP connections)
headers?: Record<string, string>; // HTTP headers (for HTTP connections)
// TLS information
isTls: boolean; // Whether the connection is TLS
tlsVersion?: string; // TLS version if applicable
// Route information
routeName?: string; // The name of the matched route
routeId?: string; // The ID of the matched route
// Additional properties
timestamp: number; // The request timestamp
connectionId: string; // Unique connection identifier
}
```
## Common Port Mapping Patterns
### 1. Port Offset Mapping
Forward traffic to target ports with a fixed offset:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: (context) => context.port + 1000
}
}
}
```
### 2. Domain-Based Port Mapping
Forward to different backend ports based on the domain:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: (context) => {
switch (context.domain) {
case 'api.example.com': return 8001;
case 'admin.example.com': return 8002;
case 'staging.example.com': return 8003;
default: return 8000;
}
}
}
}
}
```
### 3. Load Balancing with Hash-Based Distribution
Distribute connections across a port range using a deterministic hash function:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: (context) => {
// Simple hash function to ensure consistent mapping
const hostname = context.domain || '';
const hash = hostname.split('').reduce((a, b) => a + b.charCodeAt(0), 0);
return 8000 + (hash % 10); // Map to ports 8000-8009
}
}
}
}
```
## IPv6-Mapped IPv4 Compatibility
SmartProxy automatically handles IPv6-mapped IPv4 addresses for optimal compatibility. When a connection from an IPv4 address (e.g., `192.168.1.1`) arrives as an IPv6-mapped address (`::ffff:192.168.1.1`), the system normalizes these addresses for consistent matching.
This is particularly important when:
1. Matching client IP restrictions in route configurations
2. Preserving source IP for outgoing connections
3. Tracking connections and rate limits
No special configuration is needed - the system handles this normalization automatically.
## Dynamic Port Management
SmartProxy allows for runtime port configuration changes without requiring a restart.
### Adding and Removing Ports
```typescript
// Get the SmartProxy instance
const proxy = new SmartProxy({ /* config */ });
// Add a new listening port
await proxy.addListeningPort(8081);
// Remove a listening port
await proxy.removeListeningPort(8082);
```
### Runtime Route Updates
```typescript
// Get current routes
const currentRoutes = proxy.getRoutes();
// Add new route for the new port
const newRoute = {
name: 'New Dynamic Route',
match: {
ports: 8081,
domains: ['dynamic.example.com']
},
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: 9000
}
}
};
// Update the route configuration
await proxy.updateRoutes([...currentRoutes, newRoute]);
// Remove routes for a specific port
const routesWithout8082 = currentRoutes.filter(route => {
const ports = proxy.routeManager.expandPortRange(route.match.ports);
return !ports.includes(8082);
});
await proxy.updateRoutes(routesWithout8082);
```
## Performance Considerations
### Port Range Expansion
When using large port ranges, SmartProxy uses internal caching to optimize performance. For example, a range like `{ from: 1000, to: 2000 }` is expanded only once and then cached for future use.
### Port Range Validation
The system automatically validates port ranges to ensure:
1. Port numbers are within the valid range (1-65535)
2. The "from" value is not greater than the "to" value in range specifications
3. Port ranges do not contain duplicate entries
Invalid port ranges will be logged as warnings and skipped during configuration.
## Configuration Recipes
### Global Port Range
Listen on a large range of ports and forward to the same ports on a backend:
```typescript
{
name: 'Global port range forwarding',
match: {
ports: [{ from: 8000, to: 9000 }]
},
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: 'preserve'
}
}
}
```
### Domain-Specific Port Ranges
Different port ranges for different domain groups:
```typescript
[
{
name: 'API port range',
match: {
ports: [{ from: 8000, to: 8099 }]
},
action: {
type: 'forward',
target: {
host: 'api.backend.example.com',
port: 'preserve'
}
}
},
{
name: 'Admin port range',
match: {
ports: [{ from: 9000, to: 9099 }]
},
action: {
type: 'forward',
target: {
host: 'admin.backend.example.com',
port: 'preserve'
}
}
}
]
```
### Mixed Internal/External Port Forwarding
Forward specific high-numbered ports to standard ports on internal servers:
```typescript
[
{
name: 'Web server forwarding',
match: {
ports: [8080, 8443]
},
action: {
type: 'forward',
target: {
host: 'web.internal',
port: (context) => context.port === 8080 ? 80 : 443
}
}
},
{
name: 'Database forwarding',
match: {
ports: [15432]
},
action: {
type: 'forward',
target: {
host: 'db.internal',
port: 5432
}
}
}
]
```
## Debugging Port Configurations
When troubleshooting port forwarding issues, enable detailed logging:
```typescript
const proxy = new SmartProxy({
routes: [ /* your routes */ ],
enableDetailedLogging: true
});
```
This will log:
- Port configuration during startup
- Port matching decisions during routing
- Dynamic port function results
- Connection details including source and target ports
## Port Security Considerations
### Restricting Ports
For security, you may want to restrict which ports can be accessed by specific clients:
```typescript
{
name: 'Restricted port range',
match: {
ports: [{ from: 8000, to: 9000 }],
clientIp: ['10.0.0.0/8'] // Only internal network can access these ports
},
action: {
type: 'forward',
target: {
host: 'internal.example.com',
port: 'preserve'
}
}
}
```
### Rate Limiting by Port
Apply different rate limits for different port ranges:
```typescript
{
name: 'API ports with rate limiting',
match: {
ports: [{ from: 8000, to: 8100 }]
},
action: {
type: 'forward',
target: {
host: 'api.example.com',
port: 'preserve'
},
security: {
rateLimit: {
enabled: true,
maxRequests: 100,
window: 60 // 60 seconds
}
}
}
}
```
## Best Practices
1. **Use Specific Port Ranges**: Instead of large ranges (e.g., 1-65535), use specific ranges for specific purposes
2. **Prioritize Routes**: When multiple routes could match, use the `priority` field to ensure the most specific route is matched first
3. **Name Your Routes**: Use descriptive names to make debugging easier, especially when using port ranges
4. **Use Preserve Port Where Possible**: Using `port: 'preserve'` is more efficient and easier to maintain than creating multiple specific mappings
5. **Limit Dynamic Port Functions**: While powerful, complex port functions can be harder to debug; prefer simple map or math-based functions
6. **Use Port Variables**: For complex setups, define your port ranges as variables for easier maintenance:
```typescript
const API_PORTS = [{ from: 8000, to: 8099 }];
const ADMIN_PORTS = [{ from: 9000, to: 9099 }];
const routes = [
{
name: 'API Routes',
match: { ports: API_PORTS, /* ... */ },
// ...
},
{
name: 'Admin Routes',
match: { ports: ADMIN_PORTS, /* ... */ },
// ...
}
];
```

View File

@ -1,6 +1,6 @@
{
"name": "@push.rocks/smartproxy",
"version": "16.0.3",
"version": "18.0.1",
"private": false,
"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.",
"main": "dist_ts/index.js",
@ -15,16 +15,16 @@
"buildDocs": "tsdoc"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.3.2",
"@git.zone/tsbuild": "^2.4.1",
"@git.zone/tsrun": "^1.2.44",
"@git.zone/tstest": "^1.0.77",
"@push.rocks/tapbundle": "^6.0.3",
"@types/node": "^22.15.3",
"@types/node": "^22.15.18",
"typescript": "^5.8.3"
},
"dependencies": {
"@push.rocks/lik": "^6.2.2",
"@push.rocks/smartacme": "^7.3.2",
"@push.rocks/smartacme": "^7.3.3",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartnetwork": "^4.0.1",
"@push.rocks/smartpromise": "^4.2.3",

303
pnpm-lock.yaml generated
View File

@ -12,8 +12,8 @@ importers:
specifier: ^6.2.2
version: 6.2.2
'@push.rocks/smartacme':
specifier: ^7.3.2
version: 7.3.2(@aws-sdk/credential-providers@3.798.0)(socks@2.8.4)
specifier: ^7.3.3
version: 7.3.3(@aws-sdk/credential-providers@3.798.0)(socks@2.8.4)
'@push.rocks/smartdelay':
specifier: ^3.0.5
version: 3.0.5
@ -52,8 +52,8 @@ importers:
version: 8.18.2
devDependencies:
'@git.zone/tsbuild':
specifier: ^2.3.2
version: 2.3.2
specifier: ^2.4.1
version: 2.4.1
'@git.zone/tsrun':
specifier: ^1.2.44
version: 1.3.3
@ -64,8 +64,8 @@ importers:
specifier: ^6.0.3
version: 6.0.3(@aws-sdk/credential-providers@3.798.0)(socks@2.8.4)
'@types/node':
specifier: ^22.15.3
version: 22.15.3
specifier: ^22.15.18
version: 22.15.18
typescript:
specifier: ^5.8.3
version: 5.8.3
@ -355,8 +355,8 @@ packages:
'@cloudflare/workers-types@4.20250303.0':
resolution: {integrity: sha512-O7F7nRT4bbmwHf3gkRBLfJ7R6vHIJ/oZzWdby6obOiw2yavUfp/AIwS7aO2POu5Cv8+h3TXS3oHs3kKCZLraUA==}
'@cloudflare/workers-types@4.20250505.0':
resolution: {integrity: sha512-pLQ/UaCupEy3fTTfy7yCR7FuAbawvCohYAdadGHPUfzssksA9MhkqBLlzYWRwIoC34R8grVn4XOCknEg+NMr0Q==}
'@cloudflare/workers-types@4.20250515.0':
resolution: {integrity: sha512-KoHFMH04gOXp3KEI+wrFIU+3ZfoSXnwqZTpybNQjalHoN3pWjtWBb/030cCRAZ639YX+DAHAxNF7AvEYGz1oaA==}
'@colors/colors@1.6.0':
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
@ -680,8 +680,8 @@ packages:
'@esm-bundle/chai@4.3.4-fix.0':
resolution: {integrity: sha512-26SKdM4uvDWlY8/OOOxSB1AqQWeBosCX3wRYUZO7enTAj03CtVxIiCimYVG2WpULcyV51qapK4qTovwkUr5Mlw==}
'@git.zone/tsbuild@2.3.2':
resolution: {integrity: sha512-PG7N39/MkpIKGgRvT2MC7eyLHMcoofaQJQgUlJzicp62Wfk2W9qbnI8Xexb52uy7zvmndao/G4xZ391exJAj+A==}
'@git.zone/tsbuild@2.4.1':
resolution: {integrity: sha512-JfigTr1egTChGU49CZfl6LWtamZIhKDqHQfwa2XWPcMwX1XGT6retTdpbcs+MDX5vadFfkVSSglgxVsZH63QVw==}
hasBin: true
'@git.zone/tsbundle@2.2.5':
@ -872,8 +872,8 @@ packages:
'@push.rocks/qenv@6.1.0':
resolution: {integrity: sha512-1FUFMlSVwFSFg8LbqfkzJ2LLP4lMGApUtgOpsvrde6+AxBmB4gjoNgCUH7z3xXfDAtYqcrtSELXBNE0xVL1MqQ==}
'@push.rocks/smartacme@7.3.2':
resolution: {integrity: sha512-pfNd31wqvEn/2Bi9qZGCzvpV6/5V1jB9xOuWlsUTp4RihDVwQq2/se69pUeXDd1smWOM1yF4zq+45VO5DMDsCg==}
'@push.rocks/smartacme@7.3.3':
resolution: {integrity: sha512-48g9V4EpZI8B/YiPseIuB/balH22IMp/p26+DAE57jxvv1hh+PSV4I/UPWCPWP/z7OTtKF+EfEco9TEb5BF2Lw==}
'@push.rocks/smartarchive@3.0.8':
resolution: {integrity: sha512-1jPmR0b7hXmjYQoRiTlRXrIbZcdcFmSdGOfznufjcDpGPe86Km0d8TBnzqghTx4dTihzKC67IxAaz/DM3lvxpA==}
@ -896,6 +896,9 @@ packages:
'@push.rocks/smartcli@4.0.11':
resolution: {integrity: sha512-KDWfUqWBoUZsOEtsDx36d6qc8GG7Zo5E+HHamYY68KVDO8BMu6jbBucoUUPDksczLEmbXKLmroBP1mn/xozQOA==}
'@push.rocks/smartclickhouse@2.0.17':
resolution: {integrity: sha512-IYO8Obor/Ruam2KQ2B/+5uQ+rL0exU5KZoSgOc3jkkrfjn+zZenN2xoV8lVqavAtxZVfG7MfxFrcv6I7I9ZMmA==}
'@push.rocks/smartcrypto@2.0.4':
resolution: {integrity: sha512-1+/5bsjyataf5uUkUNnnVXGRAt+gHVk1KDzozjTqgqJxHvQk1d9fVDohL6CxUhUucTPtu5VR5xNBiV8YCDuGyw==}
@ -953,6 +956,9 @@ packages:
'@push.rocks/smartlog@3.0.7':
resolution: {integrity: sha512-WHOw0iHHjCEbYY4KGX40iFtLI11QJvvWIbC9yFn3Mt+nrdupMnry7Ztc5v/PqO8lu33Q6xDBMXiNQ9yNY0HVGw==}
'@push.rocks/smartlog@3.0.9':
resolution: {integrity: sha512-B/YIJrwXsbxPkAJly8+55yx3Eqm5bIaCZ/xD2oe6fD8Zp58VLF2P8hpoQZJOiSO+KI7wXVlTEFHsmt8fpRZIVA==}
'@push.rocks/smartmanifest@2.0.2':
resolution: {integrity: sha512-QGc5C9vunjfUbYsPGz5bynV/mVmPHkrQDkWp8ZO8VJtK1GZe+njgbrNyxn2SUHR0IhSAbSXl1j4JvBqYf5eTVg==}
@ -1742,11 +1748,11 @@ packages:
'@types/node-forge@1.3.11':
resolution: {integrity: sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==}
'@types/node@18.19.87':
resolution: {integrity: sha512-OIAAu6ypnVZHmsHCeJ+7CCSub38QNBS9uceMQeg7K5Ur0Jr+wG9wEOEvvMbhp09pxD5czIUy/jND7s7Tb6Nw7A==}
'@types/node@18.19.100':
resolution: {integrity: sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA==}
'@types/node@22.15.3':
resolution: {integrity: sha512-lX7HFZeHf4QG/J7tBZqrCAXwz9J5RD56Y6MpP0eJkka8p+K0RY/yBTW7CYFJ4VGCclxqOLKmiGP5juQc6MKgcw==}
'@types/node@22.15.18':
resolution: {integrity: sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==}
'@types/parse5@6.0.3':
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
@ -2118,6 +2124,10 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
chalk@5.4.1:
resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
character-entities-html4@2.1.0:
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
@ -2160,6 +2170,14 @@ packages:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
cli-cursor@5.0.0:
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
engines: {node: '>=18'}
cli-spinners@2.9.2:
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
engines: {node: '>=6'}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@ -2340,6 +2358,15 @@ packages:
supports-color:
optional: true
debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
decode-named-character-reference@1.1.0:
resolution: {integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==}
@ -2447,6 +2474,9 @@ packages:
elliptic@6.6.1:
resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==}
emoji-regex@10.4.0:
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@ -2762,6 +2792,10 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-east-asian-width@1.3.0:
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
engines: {node: '>=18'}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@ -3018,6 +3052,10 @@ packages:
resolution: {integrity: sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=}
engines: {node: '>=0.10.0'}
is-interactive@2.0.0:
resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
engines: {node: '>=12'}
is-ip@3.1.0:
resolution: {integrity: sha512-35vd5necO7IitFPjd/YBeqwWnyDWbuLH9ZXQdMfDA8TEo7pv5X8yfrvVO3xbJbLUlERCMvf6X0hTUamQxCYJ9Q==}
engines: {node: '>=8'}
@ -3066,6 +3104,10 @@ packages:
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
engines: {node: '>=18'}
is-unicode-supported@1.3.0:
resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
engines: {node: '>=12'}
is-unicode-supported@2.1.0:
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
engines: {node: '>=18'}
@ -3281,6 +3323,10 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
log-symbols@6.0.0:
resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
engines: {node: '>=18'}
log-update@4.0.0:
resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==}
engines: {node: '>=10'}
@ -3519,6 +3565,10 @@ packages:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
mimic-function@5.0.1:
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=18'}
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
@ -3716,6 +3766,10 @@ packages:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
onetime@7.0.0:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
only@0.0.2:
resolution: {integrity: sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=}
@ -3723,6 +3777,10 @@ packages:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'}
ora@8.2.0:
resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==}
engines: {node: '>=18'}
p-cancelable@3.0.0:
resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
engines: {node: '>=12.20'}
@ -4066,6 +4124,10 @@ packages:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
restore-cursor@5.1.0:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@ -4117,6 +4179,11 @@ packages:
engines: {node: '>=10'}
hasBin: true
semver@7.7.2:
resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==}
engines: {node: '>=10'}
hasBin: true
send@0.19.0:
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
engines: {node: '>= 0.8.0'}
@ -4243,6 +4310,10 @@ packages:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
stdin-discarder@0.2.2:
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
engines: {node: '>=18'}
stream-shift@1.0.3:
resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
@ -4261,6 +4332,10 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
string-width@7.2.0:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
@ -4434,6 +4509,10 @@ packages:
resolution: {integrity: sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA==}
engines: {node: '>=16'}
type-fest@4.41.0:
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
engines: {node: '>=16'}
type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
@ -4760,7 +4839,7 @@ snapshots:
'@api.global/typedrequest': 3.1.10
'@api.global/typedrequest-interfaces': 3.0.19
'@api.global/typedsocket': 3.0.1
'@cloudflare/workers-types': 4.20250505.0
'@cloudflare/workers-types': 4.20250515.0
'@design.estate/dees-comms': 1.0.27
'@push.rocks/lik': 6.2.2
'@push.rocks/smartchok': 1.0.34
@ -4769,7 +4848,7 @@ snapshots:
'@push.rocks/smartfeed': 1.0.11
'@push.rocks/smartfile': 11.2.0
'@push.rocks/smartjson': 5.0.20
'@push.rocks/smartlog': 3.0.7
'@push.rocks/smartlog': 3.0.9
'@push.rocks/smartlog-destination-devtools': 1.0.12
'@push.rocks/smartlog-interfaces': 3.0.2
'@push.rocks/smartmanifest': 2.0.2
@ -4823,7 +4902,7 @@ snapshots:
'@apiclient.xyz/cloudflare@6.4.1':
dependencies:
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartlog': 3.0.7
'@push.rocks/smartlog': 3.0.9
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrequest': 2.1.0
'@push.rocks/smartstring': 4.0.15
@ -5671,7 +5750,7 @@ snapshots:
'@cloudflare/workers-types@4.20250303.0': {}
'@cloudflare/workers-types@4.20250505.0': {}
'@cloudflare/workers-types@4.20250515.0': {}
'@colors/colors@1.6.0': {}
@ -5884,14 +5963,14 @@ snapshots:
dependencies:
'@types/chai': 4.3.20
'@git.zone/tsbuild@2.3.2':
'@git.zone/tsbuild@2.4.1':
dependencies:
'@git.zone/tspublish': 1.9.1
'@push.rocks/early': 4.0.4
'@push.rocks/smartcli': 4.0.11
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartfile': 11.2.0
'@push.rocks/smartlog': 3.0.7
'@push.rocks/smartlog': 3.0.9
'@push.rocks/smartpath': 5.0.18
'@push.rocks/smartpromise': 4.2.3
typescript: 5.7.3
@ -5921,7 +6000,7 @@ snapshots:
'@push.rocks/smartcli': 4.0.11
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartfile': 11.2.0
'@push.rocks/smartlog': 3.0.7
'@push.rocks/smartlog': 3.0.9
'@push.rocks/smartnpm': 2.0.4
'@push.rocks/smartpath': 5.0.18
'@push.rocks/smartrequest': 2.1.0
@ -5997,7 +6076,7 @@ snapshots:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/yargs': 17.0.33
chalk: 4.1.2
@ -6284,7 +6363,7 @@ snapshots:
'@push.rocks/smartlog': 3.0.7
'@push.rocks/smartpath': 5.0.18
'@push.rocks/smartacme@7.3.2(@aws-sdk/credential-providers@3.798.0)(socks@2.8.4)':
'@push.rocks/smartacme@7.3.3(@aws-sdk/credential-providers@3.798.0)(socks@2.8.4)':
dependencies:
'@api.global/typedserver': 3.0.74
'@apiclient.xyz/cloudflare': 6.4.1
@ -6293,7 +6372,7 @@ snapshots:
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartdns': 6.2.2
'@push.rocks/smartfile': 11.2.0
'@push.rocks/smartlog': 3.0.7
'@push.rocks/smartlog': 3.0.9
'@push.rocks/smartnetwork': 4.0.1
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrequest': 2.1.0
@ -6306,7 +6385,6 @@ snapshots:
- '@aws-sdk/credential-providers'
- '@mongodb-js/zstd'
- '@nuxt/kit'
- aws-crt
- bufferutil
- encoding
- gcp-metadata
@ -6389,6 +6467,15 @@ snapshots:
'@push.rocks/smartrx': 3.0.7
yargs-parser: 21.1.1
'@push.rocks/smartclickhouse@2.0.17':
dependencies:
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartobject': 1.0.12
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrx': 3.0.10
'@push.rocks/smarturl': 3.1.0
'@push.rocks/webrequest': 3.0.37
'@push.rocks/smartcrypto@2.0.4':
dependencies:
'@push.rocks/smartpromise': 4.2.3
@ -6548,6 +6635,20 @@ snapshots:
'@push.rocks/isounique': 1.0.5
'@push.rocks/smartlog-interfaces': 3.0.2
'@push.rocks/smartlog@3.0.9':
dependencies:
'@api.global/typedrequest-interfaces': 3.0.19
'@push.rocks/consolecolor': 2.0.2
'@push.rocks/isounique': 1.0.5
'@push.rocks/smartclickhouse': 2.0.17
'@push.rocks/smartfile': 11.2.0
'@push.rocks/smarthash': 3.0.4
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smarttime': 4.1.1
'@push.rocks/webrequest': 3.0.37
'@tsclass/tsclass': 9.2.0
ora: 8.2.0
'@push.rocks/smartmanifest@2.0.2': {}
'@push.rocks/smartmarkdown@3.0.3':
@ -6843,7 +6944,7 @@ snapshots:
'@push.rocks/smartversion@3.0.5':
dependencies:
'@types/semver': 7.7.0
semver: 7.7.1
semver: 7.7.2
'@push.rocks/smartxml@1.1.1':
dependencies:
@ -7732,7 +7833,7 @@ snapshots:
'@tsclass/tsclass@5.0.0':
dependencies:
type-fest: 4.40.1
type-fest: 4.41.0
'@tsclass/tsclass@8.2.1':
dependencies:
@ -7744,18 +7845,18 @@ snapshots:
'@types/accepts@1.3.7':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/babel__code-frame@7.0.6': {}
'@types/bn.js@5.1.6':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/body-parser@1.19.5':
dependencies:
'@types/connect': 3.4.38
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/buffer-json@2.0.3': {}
@ -7771,17 +7872,17 @@ snapshots:
'@types/clean-css@4.2.11':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
source-map: 0.6.1
'@types/co-body@6.1.3':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/qs': 6.9.18
'@types/connect@3.4.38':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/content-disposition@0.5.8': {}
@ -7794,11 +7895,11 @@ snapshots:
'@types/connect': 3.4.38
'@types/express': 5.0.0
'@types/keygrip': 1.0.6
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/cors@2.8.17':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/debounce@1.2.4': {}
@ -7814,7 +7915,7 @@ snapshots:
'@types/dns-packet@5.6.5':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/elliptic@6.4.18':
dependencies:
@ -7822,14 +7923,14 @@ snapshots:
'@types/express-serve-static-core@4.19.6':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/qs': 6.9.18
'@types/range-parser': 1.2.7
'@types/send': 0.17.4
'@types/express-serve-static-core@5.0.6':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/qs': 6.9.18
'@types/range-parser': 1.2.7
'@types/send': 0.17.4
@ -7860,30 +7961,30 @@ snapshots:
'@types/from2@2.3.5':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/fs-extra@11.0.4':
dependencies:
'@types/jsonfile': 6.1.4
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/fs-extra@9.0.13':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/glob@7.2.0':
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/glob@8.1.0':
dependencies:
'@types/minimatch': 5.1.2
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/gunzip-maybe@1.4.2':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/hast@3.0.4':
dependencies:
@ -7917,7 +8018,7 @@ snapshots:
'@types/jsonfile@6.1.4':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/keygrip@1.0.6': {}
@ -7934,7 +8035,7 @@ snapshots:
'@types/http-errors': 2.0.4
'@types/keygrip': 1.0.6
'@types/koa-compose': 3.2.8
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/mdast@4.0.4':
dependencies:
@ -7952,18 +8053,18 @@ snapshots:
'@types/node-fetch@2.6.12':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
form-data: 4.0.2
'@types/node-forge@1.3.11':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/node@18.19.87':
'@types/node@18.19.100':
dependencies:
undici-types: 5.26.5
'@types/node@22.15.3':
'@types/node@22.15.18':
dependencies:
undici-types: 6.21.0
@ -7981,19 +8082,19 @@ snapshots:
'@types/s3rver@3.7.4':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/semver@7.7.0': {}
'@types/send@0.17.4':
dependencies:
'@types/mime': 1.3.5
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/serve-static@1.15.7':
dependencies:
'@types/http-errors': 2.0.4
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/send': 0.17.4
'@types/sinon-chai@3.2.12':
@ -8013,11 +8114,11 @@ snapshots:
'@types/tar-stream@2.2.3':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/through2@2.0.41':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/triple-beam@1.3.5': {}
@ -8041,18 +8142,18 @@ snapshots:
'@types/whatwg-url@8.2.2':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/webidl-conversions': 7.0.3
'@types/which@3.0.4': {}
'@types/ws@7.4.7':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/ws@8.18.1':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
'@types/yargs-parser@21.0.3': {}
@ -8062,7 +8163,7 @@ snapshots:
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 22.15.3
'@types/node': 22.15.18
optional: true
'@ungap/structured-clone@1.3.0': {}
@ -8156,8 +8257,8 @@ snapshots:
dependencies:
'@peculiar/x509': 1.12.3
asn1js: 3.0.6
axios: 1.9.0(debug@4.4.0)
debug: 4.4.0
axios: 1.9.0(debug@4.4.1)
debug: 4.4.1
node-forge: 1.3.1
transitivePeerDependencies:
- supports-color
@ -8227,9 +8328,9 @@ snapshots:
axe-core@4.10.3: {}
axios@1.9.0(debug@4.4.0):
axios@1.9.0(debug@4.4.1):
dependencies:
follow-redirects: 1.15.9(debug@4.4.0)
follow-redirects: 1.15.9(debug@4.4.1)
form-data: 4.0.2
proxy-from-env: 1.1.0
transitivePeerDependencies:
@ -8409,6 +8510,8 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
chalk@5.4.1: {}
character-entities-html4@2.1.0: {}
character-entities-legacy@3.0.0: {}
@ -8443,6 +8546,12 @@ snapshots:
dependencies:
restore-cursor: 3.1.0
cli-cursor@5.0.0:
dependencies:
restore-cursor: 5.1.0
cli-spinners@2.9.2: {}
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@ -8457,7 +8566,7 @@ snapshots:
cloudflare@4.2.0:
dependencies:
'@types/node': 18.19.87
'@types/node': 18.19.100
'@types/node-fetch': 2.6.12
abort-controller: 3.0.0
agentkeepalive: 4.6.0
@ -8600,6 +8709,10 @@ snapshots:
dependencies:
ms: 2.1.3
debug@4.4.1:
dependencies:
ms: 2.1.3
decode-named-character-reference@1.1.0:
dependencies:
character-entities: 2.0.2
@ -8703,6 +8816,8 @@ snapshots:
minimalistic-assert: 1.0.1
minimalistic-crypto-utils: 1.0.1
emoji-regex@10.4.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@ -8735,7 +8850,7 @@ snapshots:
dependencies:
'@types/cookie': 0.4.1
'@types/cors': 2.8.17
'@types/node': 22.15.3
'@types/node': 22.15.18
accepts: 1.3.8
base64id: 2.0.0
cookie: 0.4.2
@ -9031,6 +9146,10 @@ snapshots:
optionalDependencies:
debug: 4.4.0
follow-redirects@1.15.9(debug@4.4.1):
optionalDependencies:
debug: 4.4.1
foreground-child@2.0.0:
dependencies:
cross-spawn: 7.0.6
@ -9101,6 +9220,8 @@ snapshots:
get-caller-file@2.0.5: {}
get-east-asian-width@1.3.0: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@ -9428,6 +9549,8 @@ snapshots:
is-gzip@1.0.0: {}
is-interactive@2.0.0: {}
is-ip@3.1.0:
dependencies:
ip-regex: 4.3.0
@ -9467,6 +9590,8 @@ snapshots:
is-stream@4.0.1: {}
is-unicode-supported@1.3.0: {}
is-unicode-supported@2.1.0: {}
is-windows@1.0.2: {}
@ -9539,7 +9664,7 @@ snapshots:
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 22.15.3
'@types/node': 22.15.18
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@ -9728,6 +9853,11 @@ snapshots:
lodash@4.17.21: {}
log-symbols@6.0.0:
dependencies:
chalk: 5.4.1
is-unicode-supported: 1.3.0
log-update@4.0.0:
dependencies:
ansi-escapes: 4.3.2
@ -10138,6 +10268,8 @@ snapshots:
mimic-fn@2.1.0: {}
mimic-function@5.0.1: {}
mimic-response@3.1.0: {}
mimic-response@4.0.0: {}
@ -10315,6 +10447,10 @@ snapshots:
dependencies:
mimic-fn: 2.1.0
onetime@7.0.0:
dependencies:
mimic-function: 5.0.1
only@0.0.2: {}
open@8.4.2:
@ -10323,6 +10459,18 @@ snapshots:
is-docker: 2.2.1
is-wsl: 2.2.0
ora@8.2.0:
dependencies:
chalk: 5.4.1
cli-cursor: 5.0.0
cli-spinners: 2.9.2
is-interactive: 2.0.0
is-unicode-supported: 2.1.0
log-symbols: 6.0.0
stdin-discarder: 0.2.2
string-width: 7.2.0
strip-ansi: 7.1.0
p-cancelable@3.0.0: {}
p-event@4.2.0:
@ -10375,7 +10523,7 @@ snapshots:
got: 12.6.1
registry-auth-token: 5.1.0
registry-url: 6.0.1
semver: 7.7.1
semver: 7.7.2
pako@0.2.9: {}
@ -10710,6 +10858,11 @@ snapshots:
onetime: 5.1.2
signal-exit: 3.0.7
restore-cursor@5.1.0:
dependencies:
onetime: 7.0.0
signal-exit: 4.1.0
reusify@1.1.0: {}
rimraf@3.0.2:
@ -10765,6 +10918,8 @@ snapshots:
semver@7.7.1: {}
semver@7.7.2: {}
send@0.19.0:
dependencies:
debug: 2.6.9
@ -10944,6 +11099,8 @@ snapshots:
statuses@2.0.1: {}
stdin-discarder@0.2.2: {}
stream-shift@1.0.3: {}
streamsearch@0.1.2: {}
@ -10967,6 +11124,12 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.1.0
string-width@7.2.0:
dependencies:
emoji-regex: 10.4.0
get-east-asian-width: 1.3.0
strip-ansi: 7.1.0
string_decoder@1.1.1:
dependencies:
safe-buffer: 5.1.2
@ -11144,6 +11307,8 @@ snapshots:
type-fest@4.40.1: {}
type-fest@4.41.0: {}
type-is@1.6.18:
dependencies:
media-typer: 0.3.0

View File

@ -1,103 +1,186 @@
# SmartProxy Configuration Troubleshooting
# SmartProxy Interface Consolidation Plan
## IPv6/IPv4 Mapping Issue
## Overview
### Problem Identified
The SmartProxy is failing to match connections for wildcard domains (like `*.lossless.digital`) when IP restrictions are in place. After extensive debugging, the root cause has been identified:
This document outlines a plan to consolidate duplicate and inconsistent interfaces in the SmartProxy codebase, specifically the `IRouteSecurity` interface which is defined twice with different properties. This inconsistency caused issues with security checks for port forwarding. The goal is to unify these interfaces, use consistent property naming, and improve code maintainability.
When a connection comes in from an IPv4 address (e.g., `212.95.99.130`), the Node.js server receives it as an IPv6-mapped IPv4 address with the format `::ffff:212.95.99.130`. However, the route configuration is expecting the exact string `212.95.99.130`, causing a mismatch.
## Problem Description (RESOLVED)
From the debug logs:
```
[DEBUG] Route rejected: clientIp mismatch. Request: ::ffff:212.95.99.130, Route patterns: ["212.95.99.130"]
```
We had two separate `IRouteSecurity` interfaces defined in `ts/proxies/smart-proxy/models/route-types.ts` which have now been consolidated into a single interface:
### Solution
1. **First definition** (previous lines 116-122) - Used in IRouteAction:
```typescript
export interface IRouteSecurity {
allowedIps?: string[];
blockedIps?: string[];
maxConnections?: number;
authentication?: IRouteAuthentication;
}
```
To fix this issue, update the route configurations to include both formats of the IP address. Here's how to modify the affected route:
2. **Second definition** (previous lines 253-272) - Used directly in IRouteConfig:
```typescript
export interface IRouteSecurity {
rateLimit?: IRouteRateLimit;
basicAuth?: {...};
jwtAuth?: {...};
ipAllowList?: string[];
ipBlockList?: string[];
}
```
This duplication with inconsistent naming (`allowedIps` vs `ipAllowList` and `blockedIps` vs `ipBlockList`) caused routing issues when IP security checks were used, particularly with port range configurations.
## Implementation Plan (COMPLETED)
### Phase 1: Interface Consolidation ✅
1. **Create a unified interface definition:** ✅
- Created one comprehensive `IRouteSecurity` interface that includes all properties
- Standardized on `ipAllowList` and `ipBlockList` property names
- Added proper documentation for each property
- Removed the duplicate interface definition
2. **Update references to use the unified interface:** ✅
- Updated all code that references the old interface properties
- Updated all configurations to use the new property names
- Ensured implementation in `route-manager.ts` uses the correct property names
### Phase 2: Code and Documentation Updates ✅
1. **Update type usages and documentation:** ✅
- Updated all code that creates or uses security configurations
- Updated documentation to reflect the new interface structure
- Added examples of the correct property usage
- Documented the changes in this plan
2. **Fix TypeScript errors:** ✅
- Fixed TypeScript errors in http-request-handler.ts
- Successfully built the project with `pnpm run build`
## Implementation Completed ✅
The interface consolidation has been successfully implemented with the following changes:
1. **Unified interface created:**
```typescript
// Wildcard domain route for *.lossless.digital
{
match: {
ports: 443,
domains: ['*.lossless.digital'],
clientIp: ['212.95.99.130', '::ffff:212.95.99.130'], // Include both formats
},
action: {
type: 'forward',
target: {
host: '212.95.99.130',
port: 443
},
tls: {
mode: 'passthrough'
},
security: {
allowedIps: ['212.95.99.130', '::ffff:212.95.99.130'] // Include both formats
}
},
name: 'Wildcard lossless.digital route (IP restricted)'
// Consolidated interface definition
export interface IRouteSecurity {
// Access control lists
ipAllowList?: string[]; // IP addresses that are allowed to connect
ipBlockList?: string[]; // IP addresses that are blocked from connecting
// Connection limits
maxConnections?: number; // Maximum concurrent connections
// Authentication
authentication?: IRouteAuthentication;
// Rate limiting
rateLimit?: IRouteRateLimit;
// Authentication methods
basicAuth?: {
enabled: boolean;
users: Array<{ username: string; password: string }>;
realm?: string;
excludePaths?: string[];
};
jwtAuth?: {
enabled: boolean;
secret: string;
algorithm?: string;
issuer?: string;
audience?: string;
expiresIn?: number;
excludePaths?: string[];
};
}
```
### Alternative Long-Term Fix
A more robust solution would be to modify the SmartProxy codebase to automatically handle IPv6-mapped IPv4 addresses by normalizing them before comparison. This would involve:
1. Modifying the `matchIpPattern` function in `route-manager.ts` to normalize IPv6-mapped IPv4 addresses:
2. **Updated isClientIpAllowed method:**
```typescript
private matchIpPattern(pattern: string, ip: string): boolean {
// Normalize IPv6-mapped IPv4 addresses
const normalizedIp = ip.startsWith('::ffff:') ? ip.substring(7) : ip;
const normalizedPattern = pattern.startsWith('::ffff:') ? pattern.substring(7) : pattern;
private isClientIpAllowed(route: IRouteConfig, clientIp: string): boolean {
const security = route.action.security;
// Handle exact match with normalized addresses
if (normalizedPattern === normalizedIp) {
return true;
if (!security) {
return true; // No security settings means allowed
}
// Rest of the existing function...
// Check blocked IPs first
if (security.ipBlockList && security.ipBlockList.length > 0) {
for (const pattern of security.ipBlockList) {
if (this.matchIpPattern(pattern, clientIp)) {
return false; // IP is blocked
}
}
}
// If there are allowed IPs, check them
if (security.ipAllowList && security.ipAllowList.length > 0) {
for (const pattern of security.ipAllowList) {
if (this.matchIpPattern(pattern, clientIp)) {
return true; // IP is allowed
}
}
return false; // IP not in allowed list
}
// No allowed IPs specified, so IP is allowed
return true;
}
```
2. Making similar modifications to other IP-related functions in the codebase.
## Wild Card Domain Matching Issue
### Explanation
The wildcard domain matching in SmartProxy works as follows:
1. When a pattern like `*.lossless.digital` is specified, it's converted to a regex: `/^.*\.lossless\.digital$/i`
2. This correctly matches any subdomain like `my.lossless.digital`, `api.lossless.digital`, etc.
3. However, it does NOT match the apex domain `lossless.digital` (without a subdomain)
If you need to match both the apex domain and subdomains, use a list:
3. **Fixed port preservation logic:**
```typescript
domains: ['lossless.digital', '*.lossless.digital']
// In base-handler.ts
protected resolvePort(
port: number | 'preserve' | ((ctx: any) => number),
incomingPort: number = 80
): number {
if (typeof port === 'function') {
try {
// Create a minimal context for the function that includes the incoming port
const ctx = { port: incomingPort };
return port(ctx);
} catch (err) {
console.error('Error resolving port function:', err);
return incomingPort; // Fall back to incoming port
}
} else if (port === 'preserve') {
return incomingPort; // Use the actual incoming port for 'preserve'
} else {
return port;
}
}
```
## Debugging SmartProxy
4. **Fixed TypeScript error in http-request-handler.ts:**
```typescript
// Safely check for host property existence
if (options.headers && 'host' in options.headers) {
// Only apply if host header rewrite is enabled or not explicitly disabled
const shouldRewriteHost = route?.action.options?.rewriteHostHeader !== false;
if (shouldRewriteHost) {
// Safely cast to OutgoingHttpHeaders to access host property
(options.headers as plugins.http.OutgoingHttpHeaders).host = `${destination.host}:${destination.port}`;
}
}
```
To debug routing issues in SmartProxy:
## Achieved Benefits ✅
1. Add detailed logging to the `route-manager.js` file in the `dist_ts` directory:
- `findMatchingRoute` method - to see what criteria are being checked
- `matchRouteDomain` method - to see domain matching logic
- `matchDomain` method - to see pattern matching
- `matchIpPattern` method - to see IP matching logic
- **Improved Consistency**: Single, unified interface with consistent property naming
- **Better Type Safety**: Eliminated confusing duplicate interface definitions
- **Reduced Errors**: Prevented misunderstandings about which property names to use
- **Forward Compatibility**: Clearer path for future security enhancements
- **Better Developer Experience**: Simplified interface with comprehensive documentation
- **Fixed Issues**: Port preservation with port ranges now works correctly with security checks
2. Run the proxy with debugging enabled:
```
pnpm run startNew
```
## Verification ✅
3. Monitor the logs for detailed information about the routing process and identify where matches are failing.
## Priority and Route Order
Remember that routes are evaluated in priority order (higher priority first). If multiple routes could match the same request, ensure that the more specific routes have higher priority.
When routes have the same priority (or none specified), they're evaluated in the order they're defined in the configuration.
- The project builds successfully with `pnpm run build`
- The unified interface works properly with all type checking
- The port range forwarding with `port: 'preserve'` now works correctly with IP security rules
- The security checks consistently use the standardized property names throughout the codebase

View File

@ -235,7 +235,7 @@ tap.test('SmartProxy: Should create instance with route-based config', async ()
port: 8080
},
security: {
allowedIps: ['127.0.0.1', '192.168.0.*'],
ipAllowList: ['127.0.0.1', '192.168.0.*'],
maxConnections: 100
}
},

View File

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

@ -199,8 +199,8 @@ export class SharedSecurityManager {
}
// Check IP against route security settings
const ipAllowList = route.security.ipAllowList || route.security.allowedIps;
const ipBlockList = route.security.ipBlockList || route.security.blockedIps;
const ipAllowList = route.security.ipAllowList;
const ipBlockList = route.security.ipBlockList;
const allowed = this.isIPAuthorized(clientIp, ipAllowList, ipBlockList);

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

@ -40,9 +40,10 @@ export abstract class ForwardingHandler extends plugins.EventEmitter implements
/**
* Get a target from the configuration, supporting round-robin selection
* @param incomingPort Optional incoming port for 'preserve' mode
* @returns A resolved target object with host and port
*/
protected getTargetFromConfig(): { host: string, port: number } {
protected getTargetFromConfig(incomingPort: number = 80): { host: string, port: number } {
const { target } = this.config;
// Handle round-robin host selection
@ -55,17 +56,42 @@ 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, incomingPort)
};
}
// Single host
return {
host: target.host,
port: target.port
port: this.resolvePort(target.port, incomingPort)
};
}
/**
* Resolves a port value, handling 'preserve' and function ports
* @param port The port value to resolve
* @param incomingPort Optional incoming port to use for 'preserve' mode
*/
protected resolvePort(
port: number | 'preserve' | ((ctx: any) => number),
incomingPort: number = 80
): number {
if (typeof port === 'function') {
try {
// Create a minimal context for the function that includes the incoming port
const ctx = { port: incomingPort };
return port(ctx);
} catch (err) {
console.error('Error resolving port function:', err);
return incomingPort; // Fall back to incoming port
}
} else if (port === 'preserve') {
return incomingPort; // Use the actual incoming port for 'preserve'
} else {
return port;
}
}
/**
* Redirect an HTTP request to HTTPS
* @param req The HTTP request

View File

@ -38,6 +38,7 @@ export class HttpForwardingHandler extends ForwardingHandler {
// For HTTP, we mainly handle parsed requests, but we can still set up
// some basic connection tracking
const remoteAddress = socket.remoteAddress || 'unknown';
const localPort = socket.localPort || 80;
socket.on('close', (hadError) => {
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
@ -54,7 +55,8 @@ export class HttpForwardingHandler extends ForwardingHandler {
});
this.emit(ForwardingHandlerEvents.CONNECTED, {
remoteAddress
remoteAddress,
localPort
});
}
@ -64,8 +66,11 @@ export class HttpForwardingHandler extends ForwardingHandler {
* @param res The HTTP response
*/
public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
// Get the target from configuration
const target = this.getTargetFromConfig();
// Get the local port from the request (for 'preserve' port handling)
const localPort = req.socket.localPort || 80;
// Get the target from configuration, passing the incoming port
const target = this.getTargetFromConfig(localPort);
// Create a custom headers object with variables for substitution
const variables = {

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

@ -41,11 +41,12 @@ export class HttpRequestHandler {
};
// Optionally rewrite host header to match target
if (options.headers && options.headers.host) {
if (options.headers && 'host' in options.headers) {
// Only apply if host header rewrite is enabled or not explicitly disabled
const shouldRewriteHost = route?.action.options?.rewriteHostHeader !== false;
if (shouldRewriteHost) {
options.headers.host = `${destination.host}:${destination.port}`;
// Safely cast to OutgoingHttpHeaders to access host property
(options.headers as plugins.http.OutgoingHttpHeaders).host = `${destination.host}:${destination.port}`;
}
}

View File

@ -447,6 +447,8 @@ export class NetworkProxy implements IMetricsTracker {
// Create legacy proxy configs for the router
// This is only needed for backward compatibility with ProxyRouter
const defaultPort = 443; // Default port for HTTPS when using 'preserve'
// and will be removed in the future
const legacyConfigs: IReverseProxyConfig[] = [];
@ -472,7 +474,8 @@ export class NetworkProxy implements IMetricsTracker {
? route.action.target.host
: [route.action.target.host];
const targetPort = route.action.target.port;
// Handle 'preserve' port value
const targetPort = route.action.target.port === 'preserve' ? defaultPort : route.action.target.port;
// Get certificate information
const certData = certificateUpdates.get(domain);

View File

@ -540,7 +540,7 @@ export class RequestHandler {
this.logger.debug(`Resolved function-based port to: ${resolvedPort}`);
}
} else {
targetPort = matchingRoute.action.target.port;
targetPort = matchingRoute.action.target.port === 'preserve' ? routeContext.port : matchingRoute.action.target.port as number;
}
// Select a single host if an array was provided
@ -760,7 +760,7 @@ export class RequestHandler {
this.logger.debug(`Resolved HTTP/2 function-based port to: ${resolvedPort}`);
}
} else {
targetPort = matchingRoute.action.target.port;
targetPort = matchingRoute.action.target.port === 'preserve' ? routeContext.port : matchingRoute.action.target.port as number;
}
// Select a single host if an array was provided

View File

@ -204,7 +204,7 @@ export class WebSocketHandler {
targetPort = route.action.target.port(toBaseContext(routeContext));
this.logger.debug(`Resolved function-based port for WebSocket: ${targetPort}`);
} else {
targetPort = route.action.target.port;
targetPort = route.action.target.port === 'preserve' ? routeContext.port : route.action.target.port as number;
}
// Select a single host if an array was provided

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
@ -43,8 +27,8 @@ export interface ISmartProxyOptions {
port: number; // Default port to use when not specified in routes
};
security?: {
allowedIps?: string[]; // Default allowed IPs
blockedIps?: string[]; // Default blocked IPs
ipAllowList?: string[]; // Default allowed IPs
ipBlockList?: string[]; // Default blocked IPs
maxConnections?: number; // Default max connections
};
preserveSourceIP?: boolean; // Default source IP preservation

View File

@ -69,8 +69,7 @@ export interface IRouteContext {
*/
export interface IRouteTarget {
host: string | string[] | ((context: IRouteContext) => string | string[]); // Host or hosts with optional function for dynamic resolution
port: number | ((context: IRouteContext) => number); // Port with optional function for dynamic mapping
preservePort?: boolean; // Use incoming port as target port (ignored if port is a function)
port: number | 'preserve' | ((context: IRouteContext) => number); // Port with optional function for dynamic mapping (use 'preserve' to keep the incoming port)
}
/**
@ -113,13 +112,39 @@ export interface IRouteAuthentication {
}
/**
* Security options for route actions
* Security options for routes
*/
export interface IRouteSecurity {
allowedIps?: string[];
blockedIps?: string[];
maxConnections?: number;
// Access control lists
ipAllowList?: string[]; // IP addresses that are allowed to connect
ipBlockList?: string[]; // IP addresses that are blocked from connecting
// Connection limits
maxConnections?: number; // Maximum concurrent connections
// Authentication
authentication?: IRouteAuthentication;
// Rate limiting
rateLimit?: IRouteRateLimit;
// Authentication methods
basicAuth?: {
enabled: boolean;
users: Array<{ username: string; password: string }>;
realm?: string;
excludePaths?: string[];
};
jwtAuth?: {
enabled: boolean;
secret: string;
algorithm?: string;
issuer?: string;
audience?: string;
expiresIn?: number;
excludePaths?: string[];
};
}
/**
@ -248,29 +273,7 @@ export interface IRouteRateLimit {
errorMessage?: string;
}
/**
* Security features for routes
*/
export interface IRouteSecurity {
rateLimit?: IRouteRateLimit;
basicAuth?: {
enabled: boolean;
users: Array<{ username: string; password: string }>;
realm?: string;
excludePaths?: string[];
};
jwtAuth?: {
enabled: boolean;
secret: string;
algorithm?: string;
issuer?: string;
audience?: string;
expiresIn?: number;
excludePaths?: string[];
};
ipAllowList?: string[];
ipBlockList?: string[];
}
// IRouteSecurity is defined above - unified definition is used for all routes
/**
* CORS configuration for a route
@ -322,61 +325,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,
@ -291,11 +289,11 @@ export class RouteConnectionHandler {
// Check default security settings
const defaultSecuritySettings = this.settings.defaults?.security;
if (defaultSecuritySettings) {
if (defaultSecuritySettings.allowedIps && defaultSecuritySettings.allowedIps.length > 0) {
if (defaultSecuritySettings.ipAllowList && defaultSecuritySettings.ipAllowList.length > 0) {
const isAllowed = this.securityManager.isIPAuthorized(
remoteIP,
defaultSecuritySettings.allowedIps,
defaultSecuritySettings.blockedIps || []
defaultSecuritySettings.ipAllowList,
defaultSecuritySettings.ipBlockList || []
);
if (!isAllowed) {
@ -316,7 +314,6 @@ export class RouteConnectionHandler {
return this.setupDirectConnection(
socket,
record,
undefined,
serverName,
initialChunk,
undefined,
@ -434,8 +431,8 @@ export class RouteConnectionHandler {
this.connectionManager.cleanupConnection(record, 'port_mapping_error');
return;
}
} else if (action.target.preservePort) {
// Use incoming port if preservePort is true
} else if (action.target.port === 'preserve') {
// Use incoming port if port is 'preserve'
targetPort = record.localPort;
} else {
// Use static port from configuration
@ -457,7 +454,6 @@ export class RouteConnectionHandler {
return this.setupDirectConnection(
socket,
record,
undefined,
record.lockedDomain,
initialChunk,
undefined,
@ -525,7 +521,7 @@ export class RouteConnectionHandler {
let targetPort: number;
if (typeof action.target.port === 'function') {
targetPort = action.target.port(routeContext);
} else if (action.target.preservePort) {
} else if (action.target.port === 'preserve') {
targetPort = record.localPort;
} else {
targetPort = action.target.port;
@ -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
@ -218,8 +213,8 @@ export class RouteManager extends plugins.EventEmitter {
}
// Check blocked IPs first
if (security.blockedIps && security.blockedIps.length > 0) {
for (const pattern of security.blockedIps) {
if (security.ipBlockList && security.ipBlockList.length > 0) {
for (const pattern of security.ipBlockList) {
if (this.matchIpPattern(pattern, clientIp)) {
return false; // IP is blocked
}
@ -227,8 +222,8 @@ export class RouteManager extends plugins.EventEmitter {
}
// If there are allowed IPs, check them
if (security.allowedIps && security.allowedIps.length > 0) {
for (const pattern of security.allowedIps) {
if (security.ipAllowList && security.ipAllowList.length > 0) {
for (const pattern of security.ipAllowList) {
if (this.matchIpPattern(pattern, clientIp)) {
return true; // IP is allowed
}

View File

@ -63,16 +63,15 @@ export class SecurityManager {
}
/**
* Check if an IP is authorized using forwarding security rules
* Check if an IP is authorized using security rules
*
* This method is used to determine if an IP is allowed to connect, based on security
* rules configured in the forwarding configuration. The allowed and blocked IPs are
* typically derived from domain.forwarding.security.allowedIps and blockedIps through
* DomainConfigManager.getEffectiveIPRules().
* rules configured in the route configuration. The allowed and blocked IPs are
* typically derived from route.security.ipAllowList and ipBlockList.
*
* @param ip - The IP address to check
* @param allowedIPs - Array of allowed IP patterns from forwarding.security.allowedIps
* @param blockedIPs - Array of blocked IP patterns from forwarding.security.blockedIps
* @param allowedIPs - Array of allowed IP patterns from security.ipAllowList
* @param blockedIPs - Array of blocked IP patterns from security.ipBlockList
* @returns true if IP is authorized, false if blocked
*/
public isIPAuthorized(ip: string, allowedIPs: string[], blockedIPs: string[] = []): boolean {
@ -94,10 +93,10 @@ export class SecurityManager {
* Check if the IP matches any of the glob patterns from security configuration
*
* This method checks IP addresses against glob patterns and handles IPv4/IPv6 normalization.
* It's used to implement IP filtering based on the forwarding.security configuration.
* It's used to implement IP filtering based on the route.security configuration.
*
* @param ip - The IP address to check
* @param patterns - Array of glob patterns from forwarding.security.allowedIps or blockedIps
* @param patterns - Array of glob patterns from security.ipAllowList or ipBlockList
* @returns true if IP matches any pattern, false otherwise
*/
private isGlobIPMatch(ip: string, patterns: string[]): boolean {

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