BREAKING CHANGE(documentation): Update readme documentation to comprehensively describe the new unified route-based configuration system in v14.0.0
This commit is contained in:
parent
28022ebe87
commit
bb66b98f1d
@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
- Added detailed description of IRouteConfig, IRouteMatch, and IRouteAction interfaces
|
||||||
|
- Improved explanation of port, domain, path, client IP, and TLS version matching features
|
||||||
|
- Included examples of helper functions (createHttpRoute, createHttpsRoute, etc.) with usage of template variables
|
||||||
|
- Enhanced migration guide from legacy configurations to the new match/action pattern
|
||||||
|
- Updated examples and tests to reflect the new documentation structure
|
||||||
|
|
||||||
## 2025-05-09 - 13.1.3 - fix(documentation)
|
## 2025-05-09 - 13.1.3 - fix(documentation)
|
||||||
Update readme.md to provide a unified and comprehensive overview of SmartProxy, with reorganized sections, enhanced diagrams, and detailed usage examples for various proxy scenarios.
|
Update readme.md to provide a unified and comprehensive overview of SmartProxy, with reorganized sections, enhanced diagrams, and detailed usage examples for various proxy scenarios.
|
||||||
|
|
||||||
|
252
readme.md
252
readme.md
@ -202,60 +202,187 @@ await proxy.stop();
|
|||||||
|
|
||||||
## Route-Based Configuration System
|
## Route-Based Configuration System
|
||||||
|
|
||||||
The new route-based configuration in v14.0.0 follows a match/action pattern, making it more powerful and flexible:
|
SmartProxy v14.0.0 introduces a new unified route configuration system based on the `IRouteConfig` interface. This system follows a match/action pattern that makes routing more powerful, flexible, and declarative.
|
||||||
|
|
||||||
|
### IRouteConfig Interface
|
||||||
|
|
||||||
|
The `IRouteConfig` interface is the core building block of SmartProxy's configuration system. Each route definition consists of match criteria and an action to perform on matched traffic:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Basic structure of a route configuration
|
interface IRouteConfig {
|
||||||
{
|
// What traffic to match (required)
|
||||||
// What traffic to match
|
match: IRouteMatch;
|
||||||
match: {
|
|
||||||
ports: 443, // Required: port(s) to listen on
|
|
||||||
domains: 'example.com', // Optional: domain(s) to match
|
|
||||||
path: '/api', // Optional: URL path pattern
|
|
||||||
clientIp: ['10.0.0.*'], // Optional: client IP patterns to match
|
|
||||||
tlsVersion: ['TLSv1.2', 'TLSv1.3'] // Optional: TLS versions to match
|
|
||||||
},
|
|
||||||
|
|
||||||
// What to do with matched traffic
|
// What to do with matched traffic (required)
|
||||||
action: {
|
action: IRouteAction;
|
||||||
type: 'forward', // 'forward', 'redirect', or 'block'
|
|
||||||
|
// Metadata (all optional)
|
||||||
|
name?: string; // Human-readable name for this route
|
||||||
|
description?: string; // Description of the route's purpose
|
||||||
|
priority?: number; // Controls matching order (higher = matched first)
|
||||||
|
tags?: string[]; // Arbitrary tags for categorization
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Match Criteria (IRouteMatch)
|
||||||
|
|
||||||
|
The `match` property defines criteria for identifying which incoming traffic should be handled by this route:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IRouteMatch {
|
||||||
|
// Listen on these ports (required)
|
||||||
|
ports: TPortRange; // number | number[] | Array<{ from: number; to: number }>
|
||||||
|
|
||||||
|
// Optional domain patterns to match (default: all domains)
|
||||||
|
domains?: string | string[]; // Supports wildcards like '*.example.com'
|
||||||
|
|
||||||
|
// Advanced matching criteria (all optional)
|
||||||
|
path?: string; // Match specific URL paths, supports glob patterns
|
||||||
|
clientIp?: string[]; // Match specific client IPs, supports glob patterns
|
||||||
|
tlsVersion?: string[]; // Match specific TLS versions e.g. ['TLSv1.2', 'TLSv1.3']
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Port Specification:**
|
||||||
|
- **Single port:** `ports: 80`
|
||||||
|
- **Multiple ports:** `ports: [80, 443]`
|
||||||
|
- **Port ranges:** `ports: [{ from: 8000, to: 8100 }]`
|
||||||
|
- **Mixed format:** `ports: [80, 443, { from: 8000, to: 8100 }]`
|
||||||
|
|
||||||
|
**Domain Matching:**
|
||||||
|
- **Single domain:** `domains: 'example.com'`
|
||||||
|
- **Multiple domains:** `domains: ['example.com', 'api.example.com']`
|
||||||
|
- **Wildcard domains:** `domains: '*.example.com'` (matches any subdomain)
|
||||||
|
- **Root domain + subdomains:** `domains: ['example.com', '*.example.com']`
|
||||||
|
|
||||||
|
**Path Matching:**
|
||||||
|
- **Exact path:** `path: '/api'` (matches only /api exactly)
|
||||||
|
- **Prefix match:** `path: '/api/*'` (matches /api and any paths under it)
|
||||||
|
- **Multiple patterns:** Use multiple routes with different priorities
|
||||||
|
|
||||||
|
**Client IP Matching:**
|
||||||
|
- **Exact IP:** `clientIp: ['192.168.1.1']`
|
||||||
|
- **Subnet wildcards:** `clientIp: ['10.0.0.*', '192.168.1.*']`
|
||||||
|
- **CIDR notation:** `clientIp: ['10.0.0.0/24']`
|
||||||
|
|
||||||
|
**TLS Version Matching:**
|
||||||
|
- `tlsVersion: ['TLSv1.2', 'TLSv1.3']` (only match these TLS versions)
|
||||||
|
|
||||||
|
#### Action Configuration (IRouteAction)
|
||||||
|
|
||||||
|
The `action` property defines what to do with traffic that matches the criteria:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IRouteAction {
|
||||||
|
// Action type (required)
|
||||||
|
type: 'forward' | 'redirect' | 'block';
|
||||||
|
|
||||||
// For 'forward' actions
|
// For 'forward' actions
|
||||||
target: {
|
target?: IRouteTarget;
|
||||||
host: 'localhost', // Target host(s) for forwarding
|
|
||||||
port: 8080 // Target port
|
|
||||||
},
|
|
||||||
|
|
||||||
// TLS handling (for 'forward' actions)
|
// TLS handling for 'forward' actions
|
||||||
tls: {
|
tls?: IRouteTls;
|
||||||
mode: 'terminate', // 'passthrough', 'terminate', 'terminate-and-reencrypt'
|
|
||||||
certificate: 'auto' // 'auto' for Let's Encrypt or {key, cert}
|
|
||||||
},
|
|
||||||
|
|
||||||
// For 'redirect' actions
|
// For 'redirect' actions
|
||||||
redirect: {
|
redirect?: IRouteRedirect;
|
||||||
to: 'https://{domain}{path}', // URL pattern with variables
|
|
||||||
status: 301 // HTTP status code
|
|
||||||
},
|
|
||||||
|
|
||||||
// Security controls for any action
|
// Security options for any action
|
||||||
security: {
|
security?: IRouteSecurity;
|
||||||
allowedIps: ['10.0.0.*'], // IP allowlist
|
|
||||||
blockedIps: ['1.2.3.4'], // IP blocklist
|
|
||||||
maxConnections: 100 // Connection limits
|
|
||||||
},
|
|
||||||
|
|
||||||
// Advanced options
|
// Advanced options
|
||||||
advanced: {
|
advanced?: IRouteAdvanced;
|
||||||
timeout: 30000, // Connection timeout
|
|
||||||
headers: { // Custom headers
|
|
||||||
'X-Forwarded-For': '{clientIp}'
|
|
||||||
},
|
|
||||||
keepAlive: true // Connection pooling
|
|
||||||
}
|
}
|
||||||
},
|
```
|
||||||
|
|
||||||
// Metadata (optional)
|
**Forward Action:**
|
||||||
|
When `type: 'forward'`, the traffic is forwarded to the specified target:
|
||||||
|
```typescript
|
||||||
|
interface IRouteTarget {
|
||||||
|
host: string | string[]; // Target host(s) - string array enables round-robin
|
||||||
|
port: number; // Target port
|
||||||
|
preservePort?: boolean; // Use incoming port as target port (default: false)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**TLS Configuration:**
|
||||||
|
When forwarding with TLS, you can configure how TLS is handled:
|
||||||
|
```typescript
|
||||||
|
interface IRouteTls {
|
||||||
|
mode: 'passthrough' | 'terminate' | 'terminate-and-reencrypt';
|
||||||
|
certificate?: 'auto' | { // 'auto' = use ACME (Let's Encrypt)
|
||||||
|
key: string; // TLS private key content
|
||||||
|
cert: string; // TLS certificate content
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**TLS Modes:**
|
||||||
|
- **passthrough:** Forward raw encrypted TLS traffic without decryption
|
||||||
|
- **terminate:** Terminate TLS and forward as HTTP
|
||||||
|
- **terminate-and-reencrypt:** Terminate TLS and create a new TLS connection to the backend
|
||||||
|
|
||||||
|
**Redirect Action:**
|
||||||
|
When `type: 'redirect'`, the client is redirected:
|
||||||
|
```typescript
|
||||||
|
interface IRouteRedirect {
|
||||||
|
to: string; // URL or template with variables
|
||||||
|
status: 301 | 302 | 307 | 308; // HTTP status code
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Block Action:**
|
||||||
|
When `type: 'block'`, the connection is immediately closed.
|
||||||
|
|
||||||
|
**Security Options:**
|
||||||
|
For any action type, you can add security controls:
|
||||||
|
```typescript
|
||||||
|
interface IRouteSecurity {
|
||||||
|
allowedIps?: string[]; // IP allowlist with glob pattern support
|
||||||
|
blockedIps?: string[]; // IP blocklist with glob pattern support
|
||||||
|
maxConnections?: number; // Maximum concurrent connections
|
||||||
|
authentication?: { // Optional authentication (future support)
|
||||||
|
type: 'basic' | 'digest' | 'oauth';
|
||||||
|
// Auth-specific options
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Advanced Options:**
|
||||||
|
Additional advanced configurations:
|
||||||
|
```typescript
|
||||||
|
interface IRouteAdvanced {
|
||||||
|
timeout?: number; // Connection timeout in milliseconds
|
||||||
|
headers?: Record<string, string>; // Custom HTTP headers
|
||||||
|
keepAlive?: boolean; // Enable connection pooling
|
||||||
|
// Additional advanced options
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Template Variables
|
||||||
|
|
||||||
|
String values in redirect URLs and headers can include variables:
|
||||||
|
|
||||||
|
- `{domain}`: The requested domain name
|
||||||
|
- `{port}`: The incoming port number
|
||||||
|
- `{path}`: The requested URL path
|
||||||
|
- `{query}`: The query string
|
||||||
|
- `{clientIp}`: The client's IP address
|
||||||
|
- `{sni}`: The SNI hostname
|
||||||
|
|
||||||
|
Example with template variables:
|
||||||
|
```typescript
|
||||||
|
redirect: {
|
||||||
|
to: 'https://{domain}{path}?source=redirect',
|
||||||
|
status: 301
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Route Metadata and Prioritization
|
||||||
|
|
||||||
|
You can add metadata to routes to help with organization and control matching priority:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
name: 'API Server', // Human-readable name
|
name: 'API Server', // Human-readable name
|
||||||
description: 'Main API endpoints', // Description
|
description: 'Main API endpoints', // Description
|
||||||
priority: 100, // Matching priority (higher = matched first)
|
priority: 100, // Matching priority (higher = matched first)
|
||||||
@ -263,6 +390,49 @@ The new route-based configuration in v14.0.0 follows a match/action pattern, mak
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Routes with higher priority values are matched first, allowing you to create specialized routes that take precedence over more general ones.
|
||||||
|
|
||||||
|
#### Complete Route Configuration Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Example of a complete route configuration
|
||||||
|
{
|
||||||
|
match: {
|
||||||
|
ports: 443,
|
||||||
|
domains: ['api.example.com', 'api-v2.example.com'],
|
||||||
|
path: '/secure/*',
|
||||||
|
clientIp: ['10.0.0.*', '192.168.1.*']
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: 'forward',
|
||||||
|
target: {
|
||||||
|
host: ['10.0.0.1', '10.0.0.2'], // Round-robin between these hosts
|
||||||
|
port: 8080
|
||||||
|
},
|
||||||
|
tls: {
|
||||||
|
mode: 'terminate',
|
||||||
|
certificate: 'auto' // Use Let's Encrypt
|
||||||
|
},
|
||||||
|
security: {
|
||||||
|
allowedIps: ['10.0.0.*'],
|
||||||
|
maxConnections: 100
|
||||||
|
},
|
||||||
|
advanced: {
|
||||||
|
timeout: 30000,
|
||||||
|
headers: {
|
||||||
|
'X-Original-Host': '{domain}',
|
||||||
|
'X-Client-IP': '{clientIp}'
|
||||||
|
},
|
||||||
|
keepAlive: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
name: 'Secure API Route',
|
||||||
|
description: 'Route for secure API endpoints with authentication',
|
||||||
|
priority: 100,
|
||||||
|
tags: ['api', 'secure', 'internal']
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Using Helper Functions
|
### Using Helper Functions
|
||||||
|
|
||||||
While you can create route configurations manually, SmartProxy provides helper functions to make it easier:
|
While you can create route configurations manually, SmartProxy provides helper functions to make it easier:
|
||||||
|
@ -16,7 +16,7 @@ import {
|
|||||||
} from '../ts/proxies/smart-proxy/index.js';
|
} from '../ts/proxies/smart-proxy/index.js';
|
||||||
|
|
||||||
// Import test helpers
|
// Import test helpers
|
||||||
import { getCertificate } from './helpers/certificates.js';
|
import { loadTestCertificates } from './helpers/certificates.js';
|
||||||
|
|
||||||
tap.test('Routes: Should create basic HTTP route', async () => {
|
tap.test('Routes: Should create basic HTTP route', async () => {
|
||||||
// Create a simple HTTP route
|
// Create a simple HTTP route
|
||||||
@ -31,12 +31,12 @@ tap.test('Routes: Should create basic HTTP route', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Validate the route configuration
|
// Validate the route configuration
|
||||||
expect(httpRoute.match.ports).to.equal(8080);
|
expect(httpRoute.match.ports).toEqual(8080);
|
||||||
expect(httpRoute.match.domains).to.equal('example.com');
|
expect(httpRoute.match.domains).toEqual('example.com');
|
||||||
expect(httpRoute.action.type).to.equal('forward');
|
expect(httpRoute.action.type).toEqual('forward');
|
||||||
expect(httpRoute.action.target?.host).to.equal('localhost');
|
expect(httpRoute.action.target?.host).toEqual('localhost');
|
||||||
expect(httpRoute.action.target?.port).to.equal(3000);
|
expect(httpRoute.action.target?.port).toEqual(3000);
|
||||||
expect(httpRoute.name).to.equal('Basic HTTP Route');
|
expect(httpRoute.name).toEqual('Basic HTTP Route');
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Routes: Should create HTTPS route with TLS termination', async () => {
|
tap.test('Routes: Should create HTTPS route with TLS termination', async () => {
|
||||||
@ -52,14 +52,14 @@ tap.test('Routes: Should create HTTPS route with TLS termination', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Validate the route configuration
|
// Validate the route configuration
|
||||||
expect(httpsRoute.match.ports).to.equal(443); // Default HTTPS port
|
expect(httpsRoute.match.ports).toEqual(443); // Default HTTPS port
|
||||||
expect(httpsRoute.match.domains).to.equal('secure.example.com');
|
expect(httpsRoute.match.domains).toEqual('secure.example.com');
|
||||||
expect(httpsRoute.action.type).to.equal('forward');
|
expect(httpsRoute.action.type).toEqual('forward');
|
||||||
expect(httpsRoute.action.tls?.mode).to.equal('terminate');
|
expect(httpsRoute.action.tls?.mode).toEqual('terminate');
|
||||||
expect(httpsRoute.action.tls?.certificate).to.equal('auto');
|
expect(httpsRoute.action.tls?.certificate).toEqual('auto');
|
||||||
expect(httpsRoute.action.target?.host).to.equal('localhost');
|
expect(httpsRoute.action.target?.host).toEqual('localhost');
|
||||||
expect(httpsRoute.action.target?.port).to.equal(8080);
|
expect(httpsRoute.action.target?.port).toEqual(8080);
|
||||||
expect(httpsRoute.name).to.equal('HTTPS Route');
|
expect(httpsRoute.name).toEqual('HTTPS Route');
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Routes: Should create HTTP to HTTPS redirect', async () => {
|
tap.test('Routes: Should create HTTP to HTTPS redirect', async () => {
|
||||||
@ -70,11 +70,11 @@ tap.test('Routes: Should create HTTP to HTTPS redirect', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Validate the route configuration
|
// Validate the route configuration
|
||||||
expect(redirectRoute.match.ports).to.equal(80);
|
expect(redirectRoute.match.ports).toEqual(80);
|
||||||
expect(redirectRoute.match.domains).to.equal('example.com');
|
expect(redirectRoute.match.domains).toEqual('example.com');
|
||||||
expect(redirectRoute.action.type).to.equal('redirect');
|
expect(redirectRoute.action.type).toEqual('redirect');
|
||||||
expect(redirectRoute.action.redirect?.to).to.equal('https://{domain}{path}');
|
expect(redirectRoute.action.redirect?.to).toEqual('https://{domain}{path}');
|
||||||
expect(redirectRoute.action.redirect?.status).to.equal(301);
|
expect(redirectRoute.action.redirect?.status).toEqual(301);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Routes: Should create complete HTTPS server with redirects', async () => {
|
tap.test('Routes: Should create complete HTTPS server with redirects', async () => {
|
||||||
@ -90,20 +90,20 @@ tap.test('Routes: Should create complete HTTPS server with redirects', async ()
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Validate that we got two routes (HTTPS route and HTTP redirect)
|
// Validate that we got two routes (HTTPS route and HTTP redirect)
|
||||||
expect(routes.length).to.equal(2);
|
expect(routes.length).toEqual(2);
|
||||||
|
|
||||||
// Validate HTTPS route
|
// Validate HTTPS route
|
||||||
const httpsRoute = routes[0];
|
const httpsRoute = routes[0];
|
||||||
expect(httpsRoute.match.ports).to.equal(443);
|
expect(httpsRoute.match.ports).toEqual(443);
|
||||||
expect(httpsRoute.match.domains).to.equal('example.com');
|
expect(httpsRoute.match.domains).toEqual('example.com');
|
||||||
expect(httpsRoute.action.type).to.equal('forward');
|
expect(httpsRoute.action.type).toEqual('forward');
|
||||||
expect(httpsRoute.action.tls?.mode).to.equal('terminate');
|
expect(httpsRoute.action.tls?.mode).toEqual('terminate');
|
||||||
|
|
||||||
// Validate HTTP redirect route
|
// Validate HTTP redirect route
|
||||||
const redirectRoute = routes[1];
|
const redirectRoute = routes[1];
|
||||||
expect(redirectRoute.match.ports).to.equal(80);
|
expect(redirectRoute.match.ports).toEqual(80);
|
||||||
expect(redirectRoute.action.type).to.equal('redirect');
|
expect(redirectRoute.action.type).toEqual('redirect');
|
||||||
expect(redirectRoute.action.redirect?.to).to.equal('https://{domain}{path}');
|
expect(redirectRoute.action.redirect?.to).toEqual('https://{domain}{path}');
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Routes: Should create load balancer route', async () => {
|
tap.test('Routes: Should create load balancer route', async () => {
|
||||||
@ -118,18 +118,18 @@ tap.test('Routes: Should create load balancer route', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Validate the route configuration
|
// Validate the route configuration
|
||||||
expect(lbRoute.match.domains).to.equal('app.example.com');
|
expect(lbRoute.match.domains).toEqual('app.example.com');
|
||||||
expect(lbRoute.action.type).to.equal('forward');
|
expect(lbRoute.action.type).toEqual('forward');
|
||||||
expect(Array.isArray(lbRoute.action.target?.host)).to.equal(true);
|
expect(Array.isArray(lbRoute.action.target?.host)).toBeTrue();
|
||||||
expect((lbRoute.action.target?.host as string[]).length).to.equal(3);
|
expect((lbRoute.action.target?.host as string[]).length).toEqual(3);
|
||||||
expect((lbRoute.action.target?.host as string[])[0]).to.equal('10.0.0.1');
|
expect((lbRoute.action.target?.host as string[])[0]).toEqual('10.0.0.1');
|
||||||
expect(lbRoute.action.target?.port).to.equal(8080);
|
expect(lbRoute.action.target?.port).toEqual(8080);
|
||||||
expect(lbRoute.action.tls?.mode).to.equal('terminate');
|
expect(lbRoute.action.tls?.mode).toEqual('terminate');
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('SmartProxy: Should create instance with route-based config', async () => {
|
tap.test('SmartProxy: Should create instance with route-based config', async () => {
|
||||||
// Create TLS certificates for testing
|
// Create TLS certificates for testing
|
||||||
const cert = await getCertificate();
|
const certs = loadTestCertificates();
|
||||||
|
|
||||||
// Create a SmartProxy instance with route-based configuration
|
// Create a SmartProxy instance with route-based configuration
|
||||||
const proxy = new SmartProxy({
|
const proxy = new SmartProxy({
|
||||||
@ -150,8 +150,8 @@ tap.test('SmartProxy: Should create instance with route-based config', async ()
|
|||||||
port: 8443
|
port: 8443
|
||||||
},
|
},
|
||||||
certificate: {
|
certificate: {
|
||||||
key: cert.key,
|
key: certs.privateKey,
|
||||||
cert: cert.cert
|
cert: certs.publicKey
|
||||||
},
|
},
|
||||||
name: 'HTTPS Route'
|
name: 'HTTPS Route'
|
||||||
})
|
})
|
||||||
@ -173,9 +173,9 @@ tap.test('SmartProxy: Should create instance with route-based config', async ()
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Simply verify the instance was created successfully
|
// Simply verify the instance was created successfully
|
||||||
expect(proxy).to.be.an('object');
|
expect(typeof proxy).toEqual('object');
|
||||||
expect(proxy.start).to.be.a('function');
|
expect(typeof proxy.start).toEqual('function');
|
||||||
expect(proxy.stop).to.be.a('function');
|
expect(typeof proxy.stop).toEqual('function');
|
||||||
});
|
});
|
||||||
|
|
||||||
export default tap.start();
|
export default tap.start();
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
name: '@push.rocks/smartproxy',
|
||||||
version: '13.1.3',
|
version: '15.0.0',
|
||||||
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication 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.'
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user