@push.rocks/smartproxy 16.0.2 → 16.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/models/index.d.ts +2 -0
- package/dist_ts/core/models/index.js +3 -1
- package/dist_ts/core/models/route-context.d.ts +62 -0
- package/dist_ts/core/models/route-context.js +43 -0
- package/dist_ts/core/models/socket-augmentation.d.ts +12 -0
- package/dist_ts/core/models/socket-augmentation.js +18 -0
- package/dist_ts/core/utils/event-system.d.ts +200 -0
- package/dist_ts/core/utils/event-system.js +224 -0
- package/dist_ts/core/utils/index.d.ts +7 -0
- package/dist_ts/core/utils/index.js +8 -1
- package/dist_ts/core/utils/route-manager.d.ts +118 -0
- package/dist_ts/core/utils/route-manager.js +383 -0
- package/dist_ts/core/utils/route-utils.d.ts +94 -0
- package/dist_ts/core/utils/route-utils.js +264 -0
- package/dist_ts/core/utils/security-utils.d.ts +111 -0
- package/dist_ts/core/utils/security-utils.js +212 -0
- package/dist_ts/core/utils/shared-security-manager.d.ts +110 -0
- package/dist_ts/core/utils/shared-security-manager.js +252 -0
- package/dist_ts/core/utils/template-utils.d.ts +37 -0
- package/dist_ts/core/utils/template-utils.js +104 -0
- package/dist_ts/core/utils/websocket-utils.d.ts +23 -0
- package/dist_ts/core/utils/websocket-utils.js +86 -0
- package/dist_ts/http/router/index.d.ts +5 -1
- package/dist_ts/http/router/index.js +4 -2
- package/dist_ts/http/router/route-router.d.ts +108 -0
- package/dist_ts/http/router/route-router.js +393 -0
- package/dist_ts/index.d.ts +8 -2
- package/dist_ts/index.js +10 -3
- package/dist_ts/proxies/index.d.ts +7 -2
- package/dist_ts/proxies/index.js +10 -4
- package/dist_ts/proxies/network-proxy/certificate-manager.d.ts +21 -0
- package/dist_ts/proxies/network-proxy/certificate-manager.js +92 -1
- package/dist_ts/proxies/network-proxy/context-creator.d.ts +34 -0
- package/dist_ts/proxies/network-proxy/context-creator.js +108 -0
- package/dist_ts/proxies/network-proxy/function-cache.d.ts +90 -0
- package/dist_ts/proxies/network-proxy/function-cache.js +198 -0
- package/dist_ts/proxies/network-proxy/http-request-handler.d.ts +40 -0
- package/dist_ts/proxies/network-proxy/http-request-handler.js +256 -0
- package/dist_ts/proxies/network-proxy/http2-request-handler.d.ts +24 -0
- package/dist_ts/proxies/network-proxy/http2-request-handler.js +201 -0
- package/dist_ts/proxies/network-proxy/models/types.d.ts +73 -1
- package/dist_ts/proxies/network-proxy/models/types.js +242 -1
- package/dist_ts/proxies/network-proxy/network-proxy.d.ts +23 -20
- package/dist_ts/proxies/network-proxy/network-proxy.js +149 -60
- package/dist_ts/proxies/network-proxy/request-handler.d.ts +38 -5
- package/dist_ts/proxies/network-proxy/request-handler.js +584 -198
- package/dist_ts/proxies/network-proxy/security-manager.d.ts +65 -0
- package/dist_ts/proxies/network-proxy/security-manager.js +255 -0
- package/dist_ts/proxies/network-proxy/websocket-handler.d.ts +13 -2
- package/dist_ts/proxies/network-proxy/websocket-handler.js +238 -20
- package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
- package/dist_ts/proxies/smart-proxy/index.js +3 -3
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +3 -5
- package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +56 -4
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +4 -57
- package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +19 -228
- package/dist_ts/proxies/smart-proxy/port-manager.d.ts +81 -0
- package/dist_ts/proxies/smart-proxy/port-manager.js +166 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +5 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +131 -15
- package/dist_ts/proxies/smart-proxy/route-helpers/index.d.ts +3 -1
- package/dist_ts/proxies/smart-proxy/route-helpers/index.js +5 -3
- package/dist_ts/proxies/smart-proxy/route-helpers.d.ts +5 -178
- package/dist_ts/proxies/smart-proxy/route-helpers.js +8 -296
- package/dist_ts/proxies/smart-proxy/route-manager.d.ts +11 -2
- package/dist_ts/proxies/smart-proxy/route-manager.js +79 -10
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +29 -2
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +48 -43
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +67 -1
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +120 -1
- package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +3 -3
- package/dist_ts/proxies/smart-proxy/utils/route-validators.js +27 -5
- package/package.json +1 -1
- package/readme.md +102 -14
- package/readme.plan.md +103 -168
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/models/index.ts +2 -0
- package/ts/core/models/route-context.ts +113 -0
- package/ts/core/models/socket-augmentation.ts +33 -0
- package/ts/core/utils/event-system.ts +376 -0
- package/ts/core/utils/index.ts +7 -0
- package/ts/core/utils/route-manager.ts +489 -0
- package/ts/core/utils/route-utils.ts +312 -0
- package/ts/core/utils/security-utils.ts +309 -0
- package/ts/core/utils/shared-security-manager.ts +333 -0
- package/ts/core/utils/template-utils.ts +124 -0
- package/ts/core/utils/websocket-utils.ts +81 -0
- package/ts/http/router/index.ts +8 -1
- package/ts/http/router/route-router.ts +482 -0
- package/ts/index.ts +14 -2
- package/ts/proxies/index.ts +12 -3
- package/ts/proxies/network-proxy/certificate-manager.ts +114 -10
- package/ts/proxies/network-proxy/context-creator.ts +145 -0
- package/ts/proxies/network-proxy/function-cache.ts +259 -0
- package/ts/proxies/network-proxy/http-request-handler.ts +330 -0
- package/ts/proxies/network-proxy/http2-request-handler.ts +255 -0
- package/ts/proxies/network-proxy/models/types.ts +312 -1
- package/ts/proxies/network-proxy/network-proxy.ts +197 -85
- package/ts/proxies/network-proxy/request-handler.ts +698 -246
- package/ts/proxies/network-proxy/security-manager.ts +298 -0
- package/ts/proxies/network-proxy/websocket-handler.ts +276 -33
- package/ts/proxies/smart-proxy/index.ts +2 -12
- package/ts/proxies/smart-proxy/models/interfaces.ts +7 -4
- package/ts/proxies/smart-proxy/models/route-types.ts +77 -10
- package/ts/proxies/smart-proxy/network-proxy-bridge.ts +20 -257
- package/ts/proxies/smart-proxy/port-manager.ts +195 -0
- package/ts/proxies/smart-proxy/route-connection-handler.ts +156 -21
- package/ts/proxies/smart-proxy/route-manager.ts +98 -14
- package/ts/proxies/smart-proxy/smart-proxy.ts +56 -55
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +167 -1
- package/ts/proxies/smart-proxy/utils/route-validators.ts +24 -5
- package/ts/proxies/smart-proxy/domain-config-manager.ts.bak +0 -441
- package/ts/proxies/smart-proxy/route-helpers/index.ts +0 -9
- package/ts/proxies/smart-proxy/route-helpers.ts +0 -498
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import * as plugins from '../../plugins.js';
|
|
2
|
+
import type { IRouteConfig, IRouteContext } from '../../proxies/smart-proxy/models/route-types.js';
|
|
3
|
+
import type {
|
|
4
|
+
IIpValidationResult,
|
|
5
|
+
IIpConnectionInfo,
|
|
6
|
+
ISecurityLogger,
|
|
7
|
+
IRateLimitInfo
|
|
8
|
+
} from './security-utils.js';
|
|
9
|
+
import {
|
|
10
|
+
isIPAuthorized,
|
|
11
|
+
checkMaxConnections,
|
|
12
|
+
checkConnectionRate,
|
|
13
|
+
trackConnection,
|
|
14
|
+
removeConnection,
|
|
15
|
+
cleanupExpiredRateLimits,
|
|
16
|
+
parseBasicAuthHeader
|
|
17
|
+
} from './security-utils.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Shared SecurityManager for use across proxy components
|
|
21
|
+
* Handles IP tracking, rate limiting, and authentication
|
|
22
|
+
*/
|
|
23
|
+
export class SharedSecurityManager {
|
|
24
|
+
// IP connection tracking
|
|
25
|
+
private connectionsByIP: Map<string, IIpConnectionInfo> = new Map();
|
|
26
|
+
|
|
27
|
+
// Route-specific rate limiting
|
|
28
|
+
private rateLimits: Map<string, Map<string, IRateLimitInfo>> = new Map();
|
|
29
|
+
|
|
30
|
+
// Cache IP filtering results to avoid constant regex matching
|
|
31
|
+
private ipFilterCache: Map<string, Map<string, boolean>> = new Map();
|
|
32
|
+
|
|
33
|
+
// Default limits
|
|
34
|
+
private maxConnectionsPerIP: number;
|
|
35
|
+
private connectionRateLimitPerMinute: number;
|
|
36
|
+
|
|
37
|
+
// Cache cleanup interval
|
|
38
|
+
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a new SharedSecurityManager
|
|
42
|
+
*
|
|
43
|
+
* @param options - Configuration options
|
|
44
|
+
* @param logger - Logger instance
|
|
45
|
+
*/
|
|
46
|
+
constructor(options: {
|
|
47
|
+
maxConnectionsPerIP?: number;
|
|
48
|
+
connectionRateLimitPerMinute?: number;
|
|
49
|
+
cleanupIntervalMs?: number;
|
|
50
|
+
routes?: IRouteConfig[];
|
|
51
|
+
}, private logger?: ISecurityLogger) {
|
|
52
|
+
this.maxConnectionsPerIP = options.maxConnectionsPerIP || 100;
|
|
53
|
+
this.connectionRateLimitPerMinute = options.connectionRateLimitPerMinute || 300;
|
|
54
|
+
|
|
55
|
+
// Set up logger with defaults if not provided
|
|
56
|
+
this.logger = logger || {
|
|
57
|
+
info: console.log,
|
|
58
|
+
warn: console.warn,
|
|
59
|
+
error: console.error
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Set up cache cleanup interval
|
|
63
|
+
const cleanupInterval = options.cleanupIntervalMs || 60000; // Default: 1 minute
|
|
64
|
+
this.cleanupInterval = setInterval(() => {
|
|
65
|
+
this.cleanupCaches();
|
|
66
|
+
}, cleanupInterval);
|
|
67
|
+
|
|
68
|
+
// Don't keep the process alive just for cleanup
|
|
69
|
+
if (this.cleanupInterval.unref) {
|
|
70
|
+
this.cleanupInterval.unref();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get connections count by IP
|
|
76
|
+
*
|
|
77
|
+
* @param ip - The IP address to check
|
|
78
|
+
* @returns Number of connections from this IP
|
|
79
|
+
*/
|
|
80
|
+
public getConnectionCountByIP(ip: string): number {
|
|
81
|
+
return this.connectionsByIP.get(ip)?.connections.size || 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Track connection by IP
|
|
86
|
+
*
|
|
87
|
+
* @param ip - The IP address to track
|
|
88
|
+
* @param connectionId - The connection ID to associate
|
|
89
|
+
*/
|
|
90
|
+
public trackConnectionByIP(ip: string, connectionId: string): void {
|
|
91
|
+
trackConnection(ip, connectionId, this.connectionsByIP);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Remove connection tracking for an IP
|
|
96
|
+
*
|
|
97
|
+
* @param ip - The IP address to update
|
|
98
|
+
* @param connectionId - The connection ID to remove
|
|
99
|
+
*/
|
|
100
|
+
public removeConnectionByIP(ip: string, connectionId: string): void {
|
|
101
|
+
removeConnection(ip, connectionId, this.connectionsByIP);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if IP is authorized based on route security settings
|
|
106
|
+
*
|
|
107
|
+
* @param ip - The IP address to check
|
|
108
|
+
* @param allowedIPs - List of allowed IP patterns
|
|
109
|
+
* @param blockedIPs - List of blocked IP patterns
|
|
110
|
+
* @returns Whether the IP is authorized
|
|
111
|
+
*/
|
|
112
|
+
public isIPAuthorized(
|
|
113
|
+
ip: string,
|
|
114
|
+
allowedIPs: string[] = ['*'],
|
|
115
|
+
blockedIPs: string[] = []
|
|
116
|
+
): boolean {
|
|
117
|
+
return isIPAuthorized(ip, allowedIPs, blockedIPs);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Validate IP against rate limits and connection limits
|
|
122
|
+
*
|
|
123
|
+
* @param ip - The IP address to validate
|
|
124
|
+
* @returns Result with allowed status and reason if blocked
|
|
125
|
+
*/
|
|
126
|
+
public validateIP(ip: string): IIpValidationResult {
|
|
127
|
+
// Check connection count limit
|
|
128
|
+
const connectionResult = checkMaxConnections(
|
|
129
|
+
ip,
|
|
130
|
+
this.connectionsByIP,
|
|
131
|
+
this.maxConnectionsPerIP
|
|
132
|
+
);
|
|
133
|
+
if (!connectionResult.allowed) {
|
|
134
|
+
return connectionResult;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check connection rate limit
|
|
138
|
+
const rateResult = checkConnectionRate(
|
|
139
|
+
ip,
|
|
140
|
+
this.connectionsByIP,
|
|
141
|
+
this.connectionRateLimitPerMinute
|
|
142
|
+
);
|
|
143
|
+
if (!rateResult.allowed) {
|
|
144
|
+
return rateResult;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { allowed: true };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Check if a client is allowed to access a specific route
|
|
152
|
+
*
|
|
153
|
+
* @param route - The route to check
|
|
154
|
+
* @param context - The request context
|
|
155
|
+
* @returns Whether access is allowed
|
|
156
|
+
*/
|
|
157
|
+
public isAllowed(route: IRouteConfig, context: IRouteContext): boolean {
|
|
158
|
+
if (!route.security) {
|
|
159
|
+
return true; // No security restrictions
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// --- IP filtering ---
|
|
163
|
+
if (!this.isClientIpAllowed(route, context.clientIp)) {
|
|
164
|
+
this.logger?.debug?.(`IP ${context.clientIp} is blocked for route ${route.name || 'unnamed'}`);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --- Rate limiting ---
|
|
169
|
+
if (route.security.rateLimit?.enabled && !this.isWithinRateLimit(route, context)) {
|
|
170
|
+
this.logger?.debug?.(`Rate limit exceeded for route ${route.name || 'unnamed'}`);
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Check if a client IP is allowed for a route
|
|
179
|
+
*
|
|
180
|
+
* @param route - The route to check
|
|
181
|
+
* @param clientIp - The client IP
|
|
182
|
+
* @returns Whether the IP is allowed
|
|
183
|
+
*/
|
|
184
|
+
private isClientIpAllowed(route: IRouteConfig, clientIp: string): boolean {
|
|
185
|
+
if (!route.security) {
|
|
186
|
+
return true; // No security restrictions
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const routeId = route.id || route.name || 'unnamed';
|
|
190
|
+
|
|
191
|
+
// Check cache first
|
|
192
|
+
if (!this.ipFilterCache.has(routeId)) {
|
|
193
|
+
this.ipFilterCache.set(routeId, new Map());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const routeCache = this.ipFilterCache.get(routeId)!;
|
|
197
|
+
if (routeCache.has(clientIp)) {
|
|
198
|
+
return routeCache.get(clientIp)!;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check IP against route security settings
|
|
202
|
+
const ipAllowList = route.security.ipAllowList || route.security.allowedIps;
|
|
203
|
+
const ipBlockList = route.security.ipBlockList || route.security.blockedIps;
|
|
204
|
+
|
|
205
|
+
const allowed = this.isIPAuthorized(clientIp, ipAllowList, ipBlockList);
|
|
206
|
+
|
|
207
|
+
// Cache the result
|
|
208
|
+
routeCache.set(clientIp, allowed);
|
|
209
|
+
|
|
210
|
+
return allowed;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Check if request is within rate limit
|
|
215
|
+
*
|
|
216
|
+
* @param route - The route to check
|
|
217
|
+
* @param context - The request context
|
|
218
|
+
* @returns Whether the request is within rate limit
|
|
219
|
+
*/
|
|
220
|
+
private isWithinRateLimit(route: IRouteConfig, context: IRouteContext): boolean {
|
|
221
|
+
if (!route.security?.rateLimit?.enabled) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const rateLimit = route.security.rateLimit;
|
|
226
|
+
const routeId = route.id || route.name || 'unnamed';
|
|
227
|
+
|
|
228
|
+
// Determine rate limit key (by IP, path, or header)
|
|
229
|
+
let key = context.clientIp; // Default to IP
|
|
230
|
+
|
|
231
|
+
if (rateLimit.keyBy === 'path' && context.path) {
|
|
232
|
+
key = `${context.clientIp}:${context.path}`;
|
|
233
|
+
} else if (rateLimit.keyBy === 'header' && rateLimit.headerName && context.headers) {
|
|
234
|
+
const headerValue = context.headers[rateLimit.headerName.toLowerCase()];
|
|
235
|
+
if (headerValue) {
|
|
236
|
+
key = `${context.clientIp}:${headerValue}`;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Get or create rate limit tracking for this route
|
|
241
|
+
if (!this.rateLimits.has(routeId)) {
|
|
242
|
+
this.rateLimits.set(routeId, new Map());
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const routeLimits = this.rateLimits.get(routeId)!;
|
|
246
|
+
const now = Date.now();
|
|
247
|
+
|
|
248
|
+
// Get or create rate limit tracking for this key
|
|
249
|
+
let limit = routeLimits.get(key);
|
|
250
|
+
if (!limit || limit.expiry < now) {
|
|
251
|
+
// Create new rate limit or reset expired one
|
|
252
|
+
limit = {
|
|
253
|
+
count: 1,
|
|
254
|
+
expiry: now + (rateLimit.window * 1000)
|
|
255
|
+
};
|
|
256
|
+
routeLimits.set(key, limit);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Increment the counter
|
|
261
|
+
limit.count++;
|
|
262
|
+
|
|
263
|
+
// Check if rate limit is exceeded
|
|
264
|
+
return limit.count <= rateLimit.maxRequests;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Validate HTTP Basic Authentication
|
|
269
|
+
*
|
|
270
|
+
* @param route - The route to check
|
|
271
|
+
* @param authHeader - The Authorization header
|
|
272
|
+
* @returns Whether authentication is valid
|
|
273
|
+
*/
|
|
274
|
+
public validateBasicAuth(route: IRouteConfig, authHeader?: string): boolean {
|
|
275
|
+
// Skip if basic auth not enabled for route
|
|
276
|
+
if (!route.security?.basicAuth?.enabled) {
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// No auth header means auth failed
|
|
281
|
+
if (!authHeader) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Parse auth header
|
|
286
|
+
const credentials = parseBasicAuthHeader(authHeader);
|
|
287
|
+
if (!credentials) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Check credentials against configured users
|
|
292
|
+
const { username, password } = credentials;
|
|
293
|
+
const users = route.security.basicAuth.users;
|
|
294
|
+
|
|
295
|
+
return users.some(user =>
|
|
296
|
+
user.username === username && user.password === password
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Clean up caches to prevent memory leaks
|
|
302
|
+
*/
|
|
303
|
+
private cleanupCaches(): void {
|
|
304
|
+
// Clean up rate limits
|
|
305
|
+
cleanupExpiredRateLimits(this.rateLimits, this.logger);
|
|
306
|
+
|
|
307
|
+
// IP filter cache doesn't need cleanup (tied to routes)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Clear all IP tracking data (for shutdown)
|
|
312
|
+
*/
|
|
313
|
+
public clearIPTracking(): void {
|
|
314
|
+
this.connectionsByIP.clear();
|
|
315
|
+
this.rateLimits.clear();
|
|
316
|
+
this.ipFilterCache.clear();
|
|
317
|
+
|
|
318
|
+
if (this.cleanupInterval) {
|
|
319
|
+
clearInterval(this.cleanupInterval);
|
|
320
|
+
this.cleanupInterval = null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Update routes for security checking
|
|
326
|
+
*
|
|
327
|
+
* @param routes - New routes to use
|
|
328
|
+
*/
|
|
329
|
+
public setRoutes(routes: IRouteConfig[]): void {
|
|
330
|
+
// Only clear the IP filter cache - route-specific
|
|
331
|
+
this.ipFilterCache.clear();
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { IRouteContext } from '../models/route-context.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Utility class for resolving template variables in strings
|
|
5
|
+
*/
|
|
6
|
+
export class TemplateUtils {
|
|
7
|
+
/**
|
|
8
|
+
* Resolve template variables in a string using the route context
|
|
9
|
+
* Supports variables like {domain}, {path}, {clientIp}, etc.
|
|
10
|
+
*
|
|
11
|
+
* @param template The template string with {variables}
|
|
12
|
+
* @param context The route context with values
|
|
13
|
+
* @returns The resolved string
|
|
14
|
+
*/
|
|
15
|
+
public static resolveTemplateVariables(template: string, context: IRouteContext): string {
|
|
16
|
+
if (!template) {
|
|
17
|
+
return template;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Replace variables with values from context
|
|
21
|
+
return template.replace(/\{([a-zA-Z0-9_\.]+)\}/g, (match, varName) => {
|
|
22
|
+
// Handle nested properties with dot notation (e.g., {headers.host})
|
|
23
|
+
if (varName.includes('.')) {
|
|
24
|
+
const parts = varName.split('.');
|
|
25
|
+
let current: any = context;
|
|
26
|
+
|
|
27
|
+
// Traverse nested object structure
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
if (current === undefined || current === null) {
|
|
30
|
+
return match; // Return original if path doesn't exist
|
|
31
|
+
}
|
|
32
|
+
current = current[part];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Return the resolved value if it exists
|
|
36
|
+
if (current !== undefined && current !== null) {
|
|
37
|
+
return TemplateUtils.convertToString(current);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return match;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Direct property access
|
|
44
|
+
const value = context[varName as keyof IRouteContext];
|
|
45
|
+
if (value === undefined) {
|
|
46
|
+
return match; // Keep the original {variable} if not found
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Convert value to string
|
|
50
|
+
return TemplateUtils.convertToString(value);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Safely convert a value to a string
|
|
56
|
+
*
|
|
57
|
+
* @param value Any value to convert to string
|
|
58
|
+
* @returns String representation or original match for complex objects
|
|
59
|
+
*/
|
|
60
|
+
private static convertToString(value: any): string {
|
|
61
|
+
if (value === null || value === undefined) {
|
|
62
|
+
return '';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (typeof value === 'string') {
|
|
66
|
+
return value;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
70
|
+
return value.toString();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (Array.isArray(value)) {
|
|
74
|
+
return value.join(',');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (typeof value === 'object') {
|
|
78
|
+
try {
|
|
79
|
+
return JSON.stringify(value);
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return '[Object]';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return String(value);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolve template variables in header values
|
|
90
|
+
*
|
|
91
|
+
* @param headers Header object with potential template variables
|
|
92
|
+
* @param context Route context for variable resolution
|
|
93
|
+
* @returns New header object with resolved values
|
|
94
|
+
*/
|
|
95
|
+
public static resolveHeaderTemplates(
|
|
96
|
+
headers: Record<string, string>,
|
|
97
|
+
context: IRouteContext
|
|
98
|
+
): Record<string, string> {
|
|
99
|
+
const result: Record<string, string> = {};
|
|
100
|
+
|
|
101
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
102
|
+
// Skip special directive headers (starting with !)
|
|
103
|
+
if (value.startsWith('!')) {
|
|
104
|
+
result[key] = value;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Resolve template variables in the header value
|
|
109
|
+
result[key] = TemplateUtils.resolveTemplateVariables(value, context);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check if a string contains template variables
|
|
117
|
+
*
|
|
118
|
+
* @param str String to check for template variables
|
|
119
|
+
* @returns True if string contains template variables
|
|
120
|
+
*/
|
|
121
|
+
public static containsTemplateVariables(str: string): boolean {
|
|
122
|
+
return !!str && /\{([a-zA-Z0-9_\.]+)\}/g.test(str);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket utility functions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Type for WebSocket RawData that can be different types in different environments
|
|
7
|
+
* This matches the ws library's type definition
|
|
8
|
+
*/
|
|
9
|
+
export type RawData = Buffer | ArrayBuffer | Buffer[] | any;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the length of a WebSocket message regardless of its type
|
|
13
|
+
* (handles all possible WebSocket message data types)
|
|
14
|
+
*
|
|
15
|
+
* @param data - The data message from WebSocket (could be any RawData type)
|
|
16
|
+
* @returns The length of the data in bytes
|
|
17
|
+
*/
|
|
18
|
+
export function getMessageSize(data: RawData): number {
|
|
19
|
+
if (typeof data === 'string') {
|
|
20
|
+
// For string data, get the byte length
|
|
21
|
+
return Buffer.from(data, 'utf8').length;
|
|
22
|
+
} else if (data instanceof Buffer) {
|
|
23
|
+
// For Node.js Buffer
|
|
24
|
+
return data.length;
|
|
25
|
+
} else if (data instanceof ArrayBuffer) {
|
|
26
|
+
// For ArrayBuffer
|
|
27
|
+
return data.byteLength;
|
|
28
|
+
} else if (Array.isArray(data)) {
|
|
29
|
+
// For array of buffers, sum their lengths
|
|
30
|
+
return data.reduce((sum, chunk) => {
|
|
31
|
+
if (chunk instanceof Buffer) {
|
|
32
|
+
return sum + chunk.length;
|
|
33
|
+
} else if (chunk instanceof ArrayBuffer) {
|
|
34
|
+
return sum + chunk.byteLength;
|
|
35
|
+
}
|
|
36
|
+
return sum;
|
|
37
|
+
}, 0);
|
|
38
|
+
} else {
|
|
39
|
+
// For other types, try to determine the size or return 0
|
|
40
|
+
try {
|
|
41
|
+
return Buffer.from(data).length;
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.warn('Could not determine message size', e);
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Convert any raw WebSocket data to Buffer for consistent handling
|
|
51
|
+
*
|
|
52
|
+
* @param data - The data message from WebSocket (could be any RawData type)
|
|
53
|
+
* @returns A Buffer containing the data
|
|
54
|
+
*/
|
|
55
|
+
export function toBuffer(data: RawData): Buffer {
|
|
56
|
+
if (typeof data === 'string') {
|
|
57
|
+
return Buffer.from(data, 'utf8');
|
|
58
|
+
} else if (data instanceof Buffer) {
|
|
59
|
+
return data;
|
|
60
|
+
} else if (data instanceof ArrayBuffer) {
|
|
61
|
+
return Buffer.from(data);
|
|
62
|
+
} else if (Array.isArray(data)) {
|
|
63
|
+
// For array of buffers, concatenate them
|
|
64
|
+
return Buffer.concat(data.map(chunk => {
|
|
65
|
+
if (chunk instanceof Buffer) {
|
|
66
|
+
return chunk;
|
|
67
|
+
} else if (chunk instanceof ArrayBuffer) {
|
|
68
|
+
return Buffer.from(chunk);
|
|
69
|
+
}
|
|
70
|
+
return Buffer.from(chunk);
|
|
71
|
+
}));
|
|
72
|
+
} else {
|
|
73
|
+
// For other types, try to convert to Buffer or return empty Buffer
|
|
74
|
+
try {
|
|
75
|
+
return Buffer.from(data);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.warn('Could not convert message to Buffer', e);
|
|
78
|
+
return Buffer.alloc(0);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
package/ts/http/router/index.ts
CHANGED
|
@@ -2,4 +2,11 @@
|
|
|
2
2
|
* HTTP routing
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Export selectively to avoid ambiguity between duplicate type names
|
|
6
|
+
export { ProxyRouter } from './proxy-router.js';
|
|
7
|
+
export type { IPathPatternConfig } from './proxy-router.js';
|
|
8
|
+
// Re-export the RouterResult and PathPatternConfig from proxy-router.js (legacy names maintained for compatibility)
|
|
9
|
+
export type { PathPatternConfig as ProxyPathPatternConfig, RouterResult as ProxyRouterResult } from './proxy-router.js';
|
|
10
|
+
|
|
11
|
+
export { RouteRouter } from './route-router.js';
|
|
12
|
+
export type { PathPatternConfig as RoutePathPatternConfig, RouterResult as RouteRouterResult } from './route-router.js';
|