@push.rocks/smartproxy 15.0.2 → 16.0.2

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.
Files changed (80) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/certificate/index.d.ts +10 -4
  3. package/dist_ts/certificate/index.js +5 -7
  4. package/dist_ts/certificate/models/certificate-types.d.ts +35 -15
  5. package/dist_ts/certificate/providers/cert-provisioner.d.ts +41 -15
  6. package/dist_ts/certificate/providers/cert-provisioner.js +201 -41
  7. package/dist_ts/forwarding/config/forwarding-types.d.ts +40 -76
  8. package/dist_ts/forwarding/config/forwarding-types.js +19 -18
  9. package/dist_ts/forwarding/config/index.d.ts +4 -2
  10. package/dist_ts/forwarding/config/index.js +5 -3
  11. package/dist_ts/forwarding/handlers/base-handler.js +3 -1
  12. package/dist_ts/forwarding/index.d.ts +5 -6
  13. package/dist_ts/forwarding/index.js +3 -3
  14. package/dist_ts/http/models/http-types.js +1 -1
  15. package/dist_ts/http/port80/acme-interfaces.d.ts +30 -0
  16. package/dist_ts/http/port80/acme-interfaces.js +46 -1
  17. package/dist_ts/http/port80/port80-handler.d.ts +17 -2
  18. package/dist_ts/http/port80/port80-handler.js +49 -11
  19. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +2 -61
  20. package/dist_ts/proxies/smart-proxy/models/interfaces.js +5 -4
  21. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +118 -4
  22. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +70 -4
  23. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +193 -43
  24. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +2 -5
  25. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +25 -146
  26. package/dist_ts/proxies/smart-proxy/route-helpers/index.d.ts +7 -0
  27. package/dist_ts/proxies/smart-proxy/route-helpers/index.js +9 -0
  28. package/dist_ts/proxies/smart-proxy/route-helpers.d.ts +54 -1
  29. package/dist_ts/proxies/smart-proxy/route-helpers.js +102 -1
  30. package/dist_ts/proxies/smart-proxy/route-manager.d.ts +3 -9
  31. package/dist_ts/proxies/smart-proxy/route-manager.js +3 -115
  32. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +72 -10
  33. package/dist_ts/proxies/smart-proxy/smart-proxy.js +135 -268
  34. package/dist_ts/proxies/smart-proxy/timeout-manager.js +3 -3
  35. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +12 -0
  36. package/dist_ts/proxies/smart-proxy/utils/index.js +19 -0
  37. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +174 -0
  38. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +332 -0
  39. package/dist_ts/proxies/smart-proxy/utils/route-migration-utils.d.ts +51 -0
  40. package/dist_ts/proxies/smart-proxy/utils/route-migration-utils.js +124 -0
  41. package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +131 -0
  42. package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +217 -0
  43. package/dist_ts/proxies/smart-proxy/utils/route-utils.d.ts +79 -0
  44. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +266 -0
  45. package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +73 -0
  46. package/dist_ts/proxies/smart-proxy/utils/route-validators.js +242 -0
  47. package/package.json +1 -1
  48. package/readme.md +139 -111
  49. package/readme.plan.md +164 -312
  50. package/ts/00_commitinfo_data.ts +1 -1
  51. package/ts/certificate/index.ts +17 -9
  52. package/ts/certificate/models/certificate-types.ts +37 -16
  53. package/ts/certificate/providers/cert-provisioner.ts +247 -54
  54. package/ts/forwarding/config/forwarding-types.ts +79 -107
  55. package/ts/forwarding/config/index.ts +4 -2
  56. package/ts/forwarding/handlers/base-handler.ts +4 -2
  57. package/ts/forwarding/index.ts +3 -2
  58. package/ts/http/models/http-types.ts +0 -1
  59. package/ts/http/port80/acme-interfaces.ts +84 -0
  60. package/ts/http/port80/port80-handler.ts +61 -15
  61. package/ts/proxies/smart-proxy/models/interfaces.ts +7 -64
  62. package/ts/proxies/smart-proxy/models/route-types.ts +152 -22
  63. package/ts/proxies/smart-proxy/network-proxy-bridge.ts +226 -55
  64. package/ts/proxies/smart-proxy/route-connection-handler.ts +36 -205
  65. package/ts/proxies/smart-proxy/route-helpers/index.ts +9 -0
  66. package/ts/proxies/smart-proxy/route-helpers.ts +165 -11
  67. package/ts/proxies/smart-proxy/route-manager.ts +3 -130
  68. package/ts/proxies/smart-proxy/smart-proxy.ts +157 -329
  69. package/ts/proxies/smart-proxy/timeout-manager.ts +2 -2
  70. package/ts/proxies/smart-proxy/utils/index.ts +40 -0
  71. package/ts/proxies/smart-proxy/utils/route-helpers.ts +455 -0
  72. package/ts/proxies/smart-proxy/utils/route-migration-utils.ts +165 -0
  73. package/ts/proxies/smart-proxy/utils/route-patterns.ts +309 -0
  74. package/ts/proxies/smart-proxy/utils/route-utils.ts +330 -0
  75. package/ts/proxies/smart-proxy/utils/route-validators.ts +269 -0
  76. package/ts/forwarding/config/domain-config.ts +0 -28
  77. package/ts/forwarding/config/domain-manager.ts +0 -283
  78. package/ts/proxies/smart-proxy/connection-handler.ts +0 -1240
  79. package/ts/proxies/smart-proxy/port-range-manager.ts +0 -211
  80. /package/ts/proxies/smart-proxy/{domain-config-manager.ts → domain-config-manager.ts.bak} +0 -0
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Route Patterns
3
+ *
4
+ * This file provides pre-defined route patterns for common use cases.
5
+ * These patterns can be used as templates for creating route configurations.
6
+ */
7
+
8
+ import type { IRouteConfig } from '../models/route-types.js';
9
+ import { createHttpRoute, createHttpsTerminateRoute, createHttpsPassthroughRoute, createCompleteHttpsServer } from './route-helpers.js';
10
+ import { mergeRouteConfigs } from './route-utils.js';
11
+
12
+ /**
13
+ * Create an API Gateway route pattern
14
+ * @param domains Domain(s) to match
15
+ * @param apiBasePath Base path for API endpoints (e.g., '/api')
16
+ * @param target Target host and port
17
+ * @param options Additional route options
18
+ * @returns API route configuration
19
+ */
20
+ export function createApiGatewayRoute(
21
+ domains: string | string[],
22
+ apiBasePath: string,
23
+ target: { host: string | string[]; port: number },
24
+ options: {
25
+ useTls?: boolean;
26
+ certificate?: 'auto' | { key: string; cert: string };
27
+ addCorsHeaders?: boolean;
28
+ [key: string]: any;
29
+ } = {}
30
+ ): IRouteConfig {
31
+ // Normalize apiBasePath to ensure it starts with / and doesn't end with /
32
+ const normalizedPath = apiBasePath.startsWith('/')
33
+ ? apiBasePath
34
+ : `/${apiBasePath}`;
35
+
36
+ // Add wildcard to path to match all API endpoints
37
+ const apiPath = normalizedPath.endsWith('/')
38
+ ? `${normalizedPath}*`
39
+ : `${normalizedPath}/*`;
40
+
41
+ // Create base route
42
+ const baseRoute = options.useTls
43
+ ? createHttpsTerminateRoute(domains, target, {
44
+ certificate: options.certificate || 'auto'
45
+ })
46
+ : createHttpRoute(domains, target);
47
+
48
+ // Add API-specific configurations
49
+ const apiRoute: Partial<IRouteConfig> = {
50
+ match: {
51
+ ...baseRoute.match,
52
+ path: apiPath
53
+ },
54
+ name: options.name || `API Gateway: ${apiPath} -> ${Array.isArray(target.host) ? target.host.join(', ') : target.host}:${target.port}`,
55
+ priority: options.priority || 100 // Higher priority for specific path matching
56
+ };
57
+
58
+ // Add CORS headers if requested
59
+ if (options.addCorsHeaders) {
60
+ apiRoute.headers = {
61
+ response: {
62
+ 'Access-Control-Allow-Origin': '*',
63
+ 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
64
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
65
+ 'Access-Control-Max-Age': '86400'
66
+ }
67
+ };
68
+ }
69
+
70
+ return mergeRouteConfigs(baseRoute, apiRoute);
71
+ }
72
+
73
+ /**
74
+ * Create a static file server route pattern
75
+ * @param domains Domain(s) to match
76
+ * @param rootDirectory Root directory for static files
77
+ * @param options Additional route options
78
+ * @returns Static file server route configuration
79
+ */
80
+ export function createStaticFileServerRoute(
81
+ domains: string | string[],
82
+ rootDirectory: string,
83
+ options: {
84
+ useTls?: boolean;
85
+ certificate?: 'auto' | { key: string; cert: string };
86
+ indexFiles?: string[];
87
+ cacheControl?: string;
88
+ path?: string;
89
+ [key: string]: any;
90
+ } = {}
91
+ ): IRouteConfig {
92
+ // Create base route with static action
93
+ const baseRoute: IRouteConfig = {
94
+ match: {
95
+ domains,
96
+ ports: options.useTls ? 443 : 80,
97
+ path: options.path || '/'
98
+ },
99
+ action: {
100
+ type: 'static',
101
+ static: {
102
+ root: rootDirectory,
103
+ index: options.indexFiles || ['index.html', 'index.htm'],
104
+ headers: {
105
+ 'Cache-Control': options.cacheControl || 'public, max-age=3600'
106
+ }
107
+ }
108
+ },
109
+ name: options.name || `Static Server: ${Array.isArray(domains) ? domains.join(', ') : domains}`,
110
+ priority: options.priority || 50
111
+ };
112
+
113
+ // Add TLS configuration if requested
114
+ if (options.useTls) {
115
+ baseRoute.action.tls = {
116
+ mode: 'terminate',
117
+ certificate: options.certificate || 'auto'
118
+ };
119
+ }
120
+
121
+ return baseRoute;
122
+ }
123
+
124
+ /**
125
+ * Create a WebSocket route pattern
126
+ * @param domains Domain(s) to match
127
+ * @param target WebSocket server host and port
128
+ * @param options Additional route options
129
+ * @returns WebSocket route configuration
130
+ */
131
+ export function createWebSocketRoute(
132
+ domains: string | string[],
133
+ target: { host: string | string[]; port: number },
134
+ options: {
135
+ useTls?: boolean;
136
+ certificate?: 'auto' | { key: string; cert: string };
137
+ path?: string;
138
+ [key: string]: any;
139
+ } = {}
140
+ ): IRouteConfig {
141
+ // Create base route
142
+ const baseRoute = options.useTls
143
+ ? createHttpsTerminateRoute(domains, target, {
144
+ certificate: options.certificate || 'auto'
145
+ })
146
+ : createHttpRoute(domains, target);
147
+
148
+ // Add WebSocket-specific configurations
149
+ const wsRoute: Partial<IRouteConfig> = {
150
+ match: {
151
+ ...baseRoute.match,
152
+ path: options.path || '/ws',
153
+ headers: {
154
+ 'Upgrade': 'websocket'
155
+ }
156
+ },
157
+ action: {
158
+ ...baseRoute.action,
159
+ websocket: {
160
+ enabled: true,
161
+ pingInterval: options.pingInterval || 30000, // 30 seconds
162
+ pingTimeout: options.pingTimeout || 5000 // 5 seconds
163
+ }
164
+ },
165
+ name: options.name || `WebSocket: ${Array.isArray(domains) ? domains.join(', ') : domains} -> ${Array.isArray(target.host) ? target.host.join(', ') : target.host}:${target.port}`,
166
+ priority: options.priority || 100 // Higher priority for WebSocket routes
167
+ };
168
+
169
+ return mergeRouteConfigs(baseRoute, wsRoute);
170
+ }
171
+
172
+ /**
173
+ * Create a load balancer route pattern
174
+ * @param domains Domain(s) to match
175
+ * @param backends Array of backend servers
176
+ * @param options Additional route options
177
+ * @returns Load balancer route configuration
178
+ */
179
+ export function createLoadBalancerRoute(
180
+ domains: string | string[],
181
+ backends: Array<{ host: string; port: number }>,
182
+ options: {
183
+ useTls?: boolean;
184
+ certificate?: 'auto' | { key: string; cert: string };
185
+ algorithm?: 'round-robin' | 'least-connections' | 'ip-hash';
186
+ healthCheck?: {
187
+ path: string;
188
+ interval: number;
189
+ timeout: number;
190
+ unhealthyThreshold: number;
191
+ healthyThreshold: number;
192
+ };
193
+ [key: string]: any;
194
+ } = {}
195
+ ): IRouteConfig {
196
+ // Extract hosts and ensure all backends use the same port
197
+ const port = backends[0].port;
198
+ const hosts = backends.map(backend => backend.host);
199
+
200
+ // Create route with multiple hosts for load balancing
201
+ const baseRoute = options.useTls
202
+ ? createHttpsTerminateRoute(domains, { host: hosts, port }, {
203
+ certificate: options.certificate || 'auto'
204
+ })
205
+ : createHttpRoute(domains, { host: hosts, port });
206
+
207
+ // Add load balancing specific configurations
208
+ const lbRoute: Partial<IRouteConfig> = {
209
+ action: {
210
+ ...baseRoute.action,
211
+ loadBalancing: {
212
+ algorithm: options.algorithm || 'round-robin',
213
+ healthCheck: options.healthCheck
214
+ }
215
+ },
216
+ name: options.name || `Load Balancer: ${Array.isArray(domains) ? domains.join(', ') : domains}`,
217
+ priority: options.priority || 50
218
+ };
219
+
220
+ return mergeRouteConfigs(baseRoute, lbRoute);
221
+ }
222
+
223
+ /**
224
+ * Create a rate limiting route pattern
225
+ * @param baseRoute Base route to add rate limiting to
226
+ * @param rateLimit Rate limiting configuration
227
+ * @returns Route with rate limiting
228
+ */
229
+ export function addRateLimiting(
230
+ baseRoute: IRouteConfig,
231
+ rateLimit: {
232
+ maxRequests: number;
233
+ window: number; // Time window in seconds
234
+ keyBy?: 'ip' | 'path' | 'header';
235
+ headerName?: string; // Required if keyBy is 'header'
236
+ errorMessage?: string;
237
+ }
238
+ ): IRouteConfig {
239
+ return mergeRouteConfigs(baseRoute, {
240
+ security: {
241
+ rateLimit: {
242
+ enabled: true,
243
+ maxRequests: rateLimit.maxRequests,
244
+ window: rateLimit.window,
245
+ keyBy: rateLimit.keyBy || 'ip',
246
+ headerName: rateLimit.headerName,
247
+ errorMessage: rateLimit.errorMessage || 'Rate limit exceeded. Please try again later.'
248
+ }
249
+ }
250
+ });
251
+ }
252
+
253
+ /**
254
+ * Create a basic authentication route pattern
255
+ * @param baseRoute Base route to add authentication to
256
+ * @param auth Authentication configuration
257
+ * @returns Route with basic authentication
258
+ */
259
+ export function addBasicAuth(
260
+ baseRoute: IRouteConfig,
261
+ auth: {
262
+ users: Array<{ username: string; password: string }>;
263
+ realm?: string;
264
+ excludePaths?: string[];
265
+ }
266
+ ): IRouteConfig {
267
+ return mergeRouteConfigs(baseRoute, {
268
+ security: {
269
+ basicAuth: {
270
+ enabled: true,
271
+ users: auth.users,
272
+ realm: auth.realm || 'Restricted Area',
273
+ excludePaths: auth.excludePaths || []
274
+ }
275
+ }
276
+ });
277
+ }
278
+
279
+ /**
280
+ * Create a JWT authentication route pattern
281
+ * @param baseRoute Base route to add JWT authentication to
282
+ * @param jwt JWT authentication configuration
283
+ * @returns Route with JWT authentication
284
+ */
285
+ export function addJwtAuth(
286
+ baseRoute: IRouteConfig,
287
+ jwt: {
288
+ secret: string;
289
+ algorithm?: string;
290
+ issuer?: string;
291
+ audience?: string;
292
+ expiresIn?: number; // Time in seconds
293
+ excludePaths?: string[];
294
+ }
295
+ ): IRouteConfig {
296
+ return mergeRouteConfigs(baseRoute, {
297
+ security: {
298
+ jwtAuth: {
299
+ enabled: true,
300
+ secret: jwt.secret,
301
+ algorithm: jwt.algorithm || 'HS256',
302
+ issuer: jwt.issuer,
303
+ audience: jwt.audience,
304
+ expiresIn: jwt.expiresIn,
305
+ excludePaths: jwt.excludePaths || []
306
+ }
307
+ }
308
+ });
309
+ }
@@ -0,0 +1,330 @@
1
+ /**
2
+ * Route Utilities
3
+ *
4
+ * This file provides utility functions for working with route configurations,
5
+ * including merging, finding, and managing route collections.
6
+ */
7
+
8
+ import type { IRouteConfig, IRouteMatch } from '../models/route-types.js';
9
+ import { validateRouteConfig } from './route-validators.js';
10
+
11
+ /**
12
+ * Merge two route configurations
13
+ * The second route's properties will override the first route's properties where they exist
14
+ * @param baseRoute The base route configuration
15
+ * @param overrideRoute The route configuration with overriding properties
16
+ * @returns A new merged route configuration
17
+ */
18
+ export function mergeRouteConfigs(
19
+ baseRoute: IRouteConfig,
20
+ overrideRoute: Partial<IRouteConfig>
21
+ ): IRouteConfig {
22
+ // Create deep copies to avoid modifying original objects
23
+ const mergedRoute: IRouteConfig = JSON.parse(JSON.stringify(baseRoute));
24
+
25
+ // Apply overrides at the top level
26
+ if (overrideRoute.id) mergedRoute.id = overrideRoute.id;
27
+ if (overrideRoute.name) mergedRoute.name = overrideRoute.name;
28
+ if (overrideRoute.enabled !== undefined) mergedRoute.enabled = overrideRoute.enabled;
29
+ if (overrideRoute.priority !== undefined) mergedRoute.priority = overrideRoute.priority;
30
+
31
+ // Merge match configuration
32
+ if (overrideRoute.match) {
33
+ mergedRoute.match = { ...mergedRoute.match };
34
+
35
+ if (overrideRoute.match.ports !== undefined) {
36
+ mergedRoute.match.ports = overrideRoute.match.ports;
37
+ }
38
+
39
+ if (overrideRoute.match.domains !== undefined) {
40
+ mergedRoute.match.domains = overrideRoute.match.domains;
41
+ }
42
+
43
+ if (overrideRoute.match.path !== undefined) {
44
+ mergedRoute.match.path = overrideRoute.match.path;
45
+ }
46
+
47
+ if (overrideRoute.match.headers !== undefined) {
48
+ mergedRoute.match.headers = overrideRoute.match.headers;
49
+ }
50
+ }
51
+
52
+ // Merge action configuration
53
+ if (overrideRoute.action) {
54
+ // If action types are different, replace the entire action
55
+ if (overrideRoute.action.type && overrideRoute.action.type !== mergedRoute.action.type) {
56
+ mergedRoute.action = JSON.parse(JSON.stringify(overrideRoute.action));
57
+ } else {
58
+ // Otherwise merge the action properties
59
+ mergedRoute.action = { ...mergedRoute.action };
60
+
61
+ // Merge target
62
+ if (overrideRoute.action.target) {
63
+ mergedRoute.action.target = {
64
+ ...mergedRoute.action.target,
65
+ ...overrideRoute.action.target
66
+ };
67
+ }
68
+
69
+ // Merge TLS options
70
+ if (overrideRoute.action.tls) {
71
+ mergedRoute.action.tls = {
72
+ ...mergedRoute.action.tls,
73
+ ...overrideRoute.action.tls
74
+ };
75
+ }
76
+
77
+ // Merge redirect options
78
+ if (overrideRoute.action.redirect) {
79
+ mergedRoute.action.redirect = {
80
+ ...mergedRoute.action.redirect,
81
+ ...overrideRoute.action.redirect
82
+ };
83
+ }
84
+
85
+ // Merge static options
86
+ if (overrideRoute.action.static) {
87
+ mergedRoute.action.static = {
88
+ ...mergedRoute.action.static,
89
+ ...overrideRoute.action.static
90
+ };
91
+ }
92
+ }
93
+ }
94
+
95
+ return mergedRoute;
96
+ }
97
+
98
+ /**
99
+ * Check if a route matches a domain
100
+ * @param route The route to check
101
+ * @param domain The domain to match against
102
+ * @returns True if the route matches the domain, false otherwise
103
+ */
104
+ export function routeMatchesDomain(route: IRouteConfig, domain: string): boolean {
105
+ if (!route.match?.domains) {
106
+ return false;
107
+ }
108
+
109
+ const domains = Array.isArray(route.match.domains)
110
+ ? route.match.domains
111
+ : [route.match.domains];
112
+
113
+ return domains.some(d => {
114
+ // Handle wildcard domains
115
+ if (d.startsWith('*.')) {
116
+ const suffix = d.substring(2);
117
+ return domain.endsWith(suffix) && domain.split('.').length > suffix.split('.').length;
118
+ }
119
+ return d.toLowerCase() === domain.toLowerCase();
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Check if a route matches a port
125
+ * @param route The route to check
126
+ * @param port The port to match against
127
+ * @returns True if the route matches the port, false otherwise
128
+ */
129
+ export function routeMatchesPort(route: IRouteConfig, port: number): boolean {
130
+ if (!route.match?.ports) {
131
+ return false;
132
+ }
133
+
134
+ if (typeof route.match.ports === 'number') {
135
+ return route.match.ports === port;
136
+ }
137
+
138
+ if (Array.isArray(route.match.ports)) {
139
+ // Simple case - array of numbers
140
+ if (typeof route.match.ports[0] === 'number') {
141
+ return (route.match.ports as number[]).includes(port);
142
+ }
143
+
144
+ // Complex case - array of port ranges
145
+ if (typeof route.match.ports[0] === 'object') {
146
+ return (route.match.ports as Array<{ from: number; to: number }>).some(
147
+ range => port >= range.from && port <= range.to
148
+ );
149
+ }
150
+ }
151
+
152
+ return false;
153
+ }
154
+
155
+ /**
156
+ * Check if a route matches a path
157
+ * @param route The route to check
158
+ * @param path The path to match against
159
+ * @returns True if the route matches the path, false otherwise
160
+ */
161
+ export function routeMatchesPath(route: IRouteConfig, path: string): boolean {
162
+ if (!route.match?.path) {
163
+ return true; // No path specified means it matches any path
164
+ }
165
+
166
+ // Handle exact path
167
+ if (route.match.path === path) {
168
+ return true;
169
+ }
170
+
171
+ // Handle path prefix with trailing slash (e.g., /api/)
172
+ if (route.match.path.endsWith('/') && path.startsWith(route.match.path)) {
173
+ return true;
174
+ }
175
+
176
+ // Handle exact path match without trailing slash
177
+ if (!route.match.path.endsWith('/') && path === route.match.path) {
178
+ return true;
179
+ }
180
+
181
+ // Handle wildcard paths (e.g., /api/*)
182
+ if (route.match.path.endsWith('*')) {
183
+ const prefix = route.match.path.slice(0, -1);
184
+ return path.startsWith(prefix);
185
+ }
186
+
187
+ return false;
188
+ }
189
+
190
+ /**
191
+ * Check if a route matches headers
192
+ * @param route The route to check
193
+ * @param headers The headers to match against
194
+ * @returns True if the route matches the headers, false otherwise
195
+ */
196
+ export function routeMatchesHeaders(
197
+ route: IRouteConfig,
198
+ headers: Record<string, string>
199
+ ): boolean {
200
+ if (!route.match?.headers || Object.keys(route.match.headers).length === 0) {
201
+ return true; // No headers specified means it matches any headers
202
+ }
203
+
204
+ // Check each header in the route's match criteria
205
+ return Object.entries(route.match.headers).every(([key, value]) => {
206
+ // If the header isn't present in the request, it doesn't match
207
+ if (!headers[key]) {
208
+ return false;
209
+ }
210
+
211
+ // Handle exact match
212
+ if (typeof value === 'string') {
213
+ return headers[key] === value;
214
+ }
215
+
216
+ // Handle regex match
217
+ if (value instanceof RegExp) {
218
+ return value.test(headers[key]);
219
+ }
220
+
221
+ return false;
222
+ });
223
+ }
224
+
225
+ /**
226
+ * Find all routes that match the given criteria
227
+ * @param routes Array of routes to search
228
+ * @param criteria Matching criteria
229
+ * @returns Array of matching routes sorted by priority
230
+ */
231
+ export function findMatchingRoutes(
232
+ routes: IRouteConfig[],
233
+ criteria: {
234
+ domain?: string;
235
+ port?: number;
236
+ path?: string;
237
+ headers?: Record<string, string>;
238
+ }
239
+ ): IRouteConfig[] {
240
+ // Filter routes that are enabled and match all provided criteria
241
+ const matchingRoutes = routes.filter(route => {
242
+ // Skip disabled routes
243
+ if (route.enabled === false) {
244
+ return false;
245
+ }
246
+
247
+ // Check domain match if specified
248
+ if (criteria.domain && !routeMatchesDomain(route, criteria.domain)) {
249
+ return false;
250
+ }
251
+
252
+ // Check port match if specified
253
+ if (criteria.port !== undefined && !routeMatchesPort(route, criteria.port)) {
254
+ return false;
255
+ }
256
+
257
+ // Check path match if specified
258
+ if (criteria.path && !routeMatchesPath(route, criteria.path)) {
259
+ return false;
260
+ }
261
+
262
+ // Check headers match if specified
263
+ if (criteria.headers && !routeMatchesHeaders(route, criteria.headers)) {
264
+ return false;
265
+ }
266
+
267
+ return true;
268
+ });
269
+
270
+ // Sort matching routes by priority (higher priority first)
271
+ return matchingRoutes.sort((a, b) => {
272
+ const priorityA = a.priority || 0;
273
+ const priorityB = b.priority || 0;
274
+ return priorityB - priorityA; // Higher priority first
275
+ });
276
+ }
277
+
278
+ /**
279
+ * Find the best matching route for the given criteria
280
+ * @param routes Array of routes to search
281
+ * @param criteria Matching criteria
282
+ * @returns The best matching route or undefined if no match
283
+ */
284
+ export function findBestMatchingRoute(
285
+ routes: IRouteConfig[],
286
+ criteria: {
287
+ domain?: string;
288
+ port?: number;
289
+ path?: string;
290
+ headers?: Record<string, string>;
291
+ }
292
+ ): IRouteConfig | undefined {
293
+ const matchingRoutes = findMatchingRoutes(routes, criteria);
294
+ return matchingRoutes.length > 0 ? matchingRoutes[0] : undefined;
295
+ }
296
+
297
+ /**
298
+ * Create a route ID based on route properties
299
+ * @param route Route configuration
300
+ * @returns Generated route ID
301
+ */
302
+ export function generateRouteId(route: IRouteConfig): string {
303
+ // Create a deterministic ID based on route properties
304
+ const domains = Array.isArray(route.match?.domains)
305
+ ? route.match.domains.join('-')
306
+ : route.match?.domains || 'any';
307
+
308
+ let portsStr = 'any';
309
+ if (route.match?.ports) {
310
+ if (Array.isArray(route.match.ports)) {
311
+ portsStr = route.match.ports.join('-');
312
+ } else if (typeof route.match.ports === 'number') {
313
+ portsStr = route.match.ports.toString();
314
+ }
315
+ }
316
+
317
+ const path = route.match?.path || 'any';
318
+ const action = route.action?.type || 'unknown';
319
+
320
+ return `route-${domains}-${portsStr}-${path}-${action}`.replace(/[^a-zA-Z0-9-]/g, '-');
321
+ }
322
+
323
+ /**
324
+ * Clone a route configuration
325
+ * @param route Route to clone
326
+ * @returns Deep copy of the route
327
+ */
328
+ export function cloneRoute(route: IRouteConfig): IRouteConfig {
329
+ return JSON.parse(JSON.stringify(route));
330
+ }