@push.rocks/smartproxy 15.0.2 → 16.0.3

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 (160) 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/core/models/index.d.ts +2 -0
  8. package/dist_ts/core/models/index.js +3 -1
  9. package/dist_ts/core/models/route-context.d.ts +62 -0
  10. package/dist_ts/core/models/route-context.js +43 -0
  11. package/dist_ts/core/models/socket-augmentation.d.ts +12 -0
  12. package/dist_ts/core/models/socket-augmentation.js +18 -0
  13. package/dist_ts/core/utils/event-system.d.ts +200 -0
  14. package/dist_ts/core/utils/event-system.js +224 -0
  15. package/dist_ts/core/utils/index.d.ts +7 -0
  16. package/dist_ts/core/utils/index.js +8 -1
  17. package/dist_ts/core/utils/route-manager.d.ts +118 -0
  18. package/dist_ts/core/utils/route-manager.js +383 -0
  19. package/dist_ts/core/utils/route-utils.d.ts +94 -0
  20. package/dist_ts/core/utils/route-utils.js +264 -0
  21. package/dist_ts/core/utils/security-utils.d.ts +111 -0
  22. package/dist_ts/core/utils/security-utils.js +212 -0
  23. package/dist_ts/core/utils/shared-security-manager.d.ts +110 -0
  24. package/dist_ts/core/utils/shared-security-manager.js +252 -0
  25. package/dist_ts/core/utils/template-utils.d.ts +37 -0
  26. package/dist_ts/core/utils/template-utils.js +104 -0
  27. package/dist_ts/core/utils/websocket-utils.d.ts +23 -0
  28. package/dist_ts/core/utils/websocket-utils.js +86 -0
  29. package/dist_ts/forwarding/config/forwarding-types.d.ts +40 -76
  30. package/dist_ts/forwarding/config/forwarding-types.js +19 -18
  31. package/dist_ts/forwarding/config/index.d.ts +4 -2
  32. package/dist_ts/forwarding/config/index.js +5 -3
  33. package/dist_ts/forwarding/handlers/base-handler.js +3 -1
  34. package/dist_ts/forwarding/index.d.ts +5 -6
  35. package/dist_ts/forwarding/index.js +3 -3
  36. package/dist_ts/http/models/http-types.js +1 -1
  37. package/dist_ts/http/port80/acme-interfaces.d.ts +30 -0
  38. package/dist_ts/http/port80/acme-interfaces.js +46 -1
  39. package/dist_ts/http/port80/port80-handler.d.ts +17 -2
  40. package/dist_ts/http/port80/port80-handler.js +49 -11
  41. package/dist_ts/http/router/index.d.ts +5 -1
  42. package/dist_ts/http/router/index.js +4 -2
  43. package/dist_ts/http/router/route-router.d.ts +108 -0
  44. package/dist_ts/http/router/route-router.js +393 -0
  45. package/dist_ts/index.d.ts +8 -2
  46. package/dist_ts/index.js +10 -3
  47. package/dist_ts/proxies/index.d.ts +7 -2
  48. package/dist_ts/proxies/index.js +10 -4
  49. package/dist_ts/proxies/network-proxy/certificate-manager.d.ts +21 -0
  50. package/dist_ts/proxies/network-proxy/certificate-manager.js +92 -1
  51. package/dist_ts/proxies/network-proxy/context-creator.d.ts +34 -0
  52. package/dist_ts/proxies/network-proxy/context-creator.js +108 -0
  53. package/dist_ts/proxies/network-proxy/function-cache.d.ts +90 -0
  54. package/dist_ts/proxies/network-proxy/function-cache.js +198 -0
  55. package/dist_ts/proxies/network-proxy/http-request-handler.d.ts +40 -0
  56. package/dist_ts/proxies/network-proxy/http-request-handler.js +256 -0
  57. package/dist_ts/proxies/network-proxy/http2-request-handler.d.ts +24 -0
  58. package/dist_ts/proxies/network-proxy/http2-request-handler.js +201 -0
  59. package/dist_ts/proxies/network-proxy/models/types.d.ts +73 -1
  60. package/dist_ts/proxies/network-proxy/models/types.js +242 -1
  61. package/dist_ts/proxies/network-proxy/network-proxy.d.ts +23 -20
  62. package/dist_ts/proxies/network-proxy/network-proxy.js +147 -60
  63. package/dist_ts/proxies/network-proxy/request-handler.d.ts +38 -5
  64. package/dist_ts/proxies/network-proxy/request-handler.js +584 -198
  65. package/dist_ts/proxies/network-proxy/security-manager.d.ts +65 -0
  66. package/dist_ts/proxies/network-proxy/security-manager.js +255 -0
  67. package/dist_ts/proxies/network-proxy/websocket-handler.d.ts +13 -2
  68. package/dist_ts/proxies/network-proxy/websocket-handler.js +238 -20
  69. package/dist_ts/proxies/smart-proxy/index.d.ts +1 -1
  70. package/dist_ts/proxies/smart-proxy/index.js +3 -3
  71. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -66
  72. package/dist_ts/proxies/smart-proxy/models/interfaces.js +5 -4
  73. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +173 -6
  74. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.d.ts +20 -7
  75. package/dist_ts/proxies/smart-proxy/network-proxy-bridge.js +49 -108
  76. package/dist_ts/proxies/smart-proxy/port-manager.d.ts +81 -0
  77. package/dist_ts/proxies/smart-proxy/port-manager.js +166 -0
  78. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +7 -5
  79. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +155 -160
  80. package/dist_ts/proxies/smart-proxy/route-helpers/index.d.ts +9 -0
  81. package/dist_ts/proxies/smart-proxy/route-helpers/index.js +11 -0
  82. package/dist_ts/proxies/smart-proxy/route-helpers.d.ts +5 -125
  83. package/dist_ts/proxies/smart-proxy/route-helpers.js +8 -195
  84. package/dist_ts/proxies/smart-proxy/route-manager.d.ts +14 -11
  85. package/dist_ts/proxies/smart-proxy/route-manager.js +81 -124
  86. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +101 -12
  87. package/dist_ts/proxies/smart-proxy/smart-proxy.js +178 -306
  88. package/dist_ts/proxies/smart-proxy/timeout-manager.js +3 -3
  89. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +12 -0
  90. package/dist_ts/proxies/smart-proxy/utils/index.js +19 -0
  91. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +240 -0
  92. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +451 -0
  93. package/dist_ts/proxies/smart-proxy/utils/route-migration-utils.d.ts +51 -0
  94. package/dist_ts/proxies/smart-proxy/utils/route-migration-utils.js +124 -0
  95. package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +131 -0
  96. package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +217 -0
  97. package/dist_ts/proxies/smart-proxy/utils/route-utils.d.ts +79 -0
  98. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +266 -0
  99. package/dist_ts/proxies/smart-proxy/utils/route-validators.d.ts +73 -0
  100. package/dist_ts/proxies/smart-proxy/utils/route-validators.js +264 -0
  101. package/package.json +1 -1
  102. package/readme.md +241 -125
  103. package/readme.plan.md +73 -286
  104. package/ts/00_commitinfo_data.ts +1 -1
  105. package/ts/certificate/index.ts +17 -9
  106. package/ts/certificate/models/certificate-types.ts +37 -16
  107. package/ts/certificate/providers/cert-provisioner.ts +247 -54
  108. package/ts/core/models/index.ts +2 -0
  109. package/ts/core/models/route-context.ts +113 -0
  110. package/ts/core/models/socket-augmentation.ts +33 -0
  111. package/ts/core/utils/event-system.ts +376 -0
  112. package/ts/core/utils/index.ts +7 -0
  113. package/ts/core/utils/route-manager.ts +489 -0
  114. package/ts/core/utils/route-utils.ts +312 -0
  115. package/ts/core/utils/security-utils.ts +309 -0
  116. package/ts/core/utils/shared-security-manager.ts +333 -0
  117. package/ts/core/utils/template-utils.ts +124 -0
  118. package/ts/core/utils/websocket-utils.ts +81 -0
  119. package/ts/forwarding/config/forwarding-types.ts +79 -107
  120. package/ts/forwarding/config/index.ts +4 -2
  121. package/ts/forwarding/handlers/base-handler.ts +4 -2
  122. package/ts/forwarding/index.ts +3 -2
  123. package/ts/http/models/http-types.ts +0 -1
  124. package/ts/http/port80/acme-interfaces.ts +84 -0
  125. package/ts/http/port80/port80-handler.ts +61 -15
  126. package/ts/http/router/index.ts +8 -1
  127. package/ts/http/router/route-router.ts +482 -0
  128. package/ts/index.ts +14 -2
  129. package/ts/proxies/index.ts +12 -3
  130. package/ts/proxies/network-proxy/certificate-manager.ts +114 -10
  131. package/ts/proxies/network-proxy/context-creator.ts +145 -0
  132. package/ts/proxies/network-proxy/function-cache.ts +259 -0
  133. package/ts/proxies/network-proxy/http-request-handler.ts +330 -0
  134. package/ts/proxies/network-proxy/http2-request-handler.ts +255 -0
  135. package/ts/proxies/network-proxy/models/types.ts +312 -1
  136. package/ts/proxies/network-proxy/network-proxy.ts +195 -86
  137. package/ts/proxies/network-proxy/request-handler.ts +698 -246
  138. package/ts/proxies/network-proxy/security-manager.ts +298 -0
  139. package/ts/proxies/network-proxy/websocket-handler.ts +276 -33
  140. package/ts/proxies/smart-proxy/index.ts +2 -12
  141. package/ts/proxies/smart-proxy/models/interfaces.ts +13 -67
  142. package/ts/proxies/smart-proxy/models/route-types.ts +223 -25
  143. package/ts/proxies/smart-proxy/network-proxy-bridge.ts +57 -123
  144. package/ts/proxies/smart-proxy/port-manager.ts +195 -0
  145. package/ts/proxies/smart-proxy/route-connection-handler.ts +191 -225
  146. package/ts/proxies/smart-proxy/route-manager.ts +101 -144
  147. package/ts/proxies/smart-proxy/smart-proxy.ts +206 -377
  148. package/ts/proxies/smart-proxy/timeout-manager.ts +2 -2
  149. package/ts/proxies/smart-proxy/utils/index.ts +40 -0
  150. package/ts/proxies/smart-proxy/utils/route-helpers.ts +621 -0
  151. package/ts/proxies/smart-proxy/utils/route-migration-utils.ts +165 -0
  152. package/ts/proxies/smart-proxy/utils/route-patterns.ts +309 -0
  153. package/ts/proxies/smart-proxy/utils/route-utils.ts +330 -0
  154. package/ts/proxies/smart-proxy/utils/route-validators.ts +288 -0
  155. package/ts/forwarding/config/domain-config.ts +0 -28
  156. package/ts/forwarding/config/domain-manager.ts +0 -283
  157. package/ts/proxies/smart-proxy/connection-handler.ts +0 -1240
  158. package/ts/proxies/smart-proxy/domain-config-manager.ts +0 -441
  159. package/ts/proxies/smart-proxy/port-range-manager.ts +0 -211
  160. package/ts/proxies/smart-proxy/route-helpers.ts +0 -344
@@ -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
+ }
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Route Validators
3
+ *
4
+ * This file provides utility functions for validating route configurations.
5
+ * These validators help ensure that route configurations are valid and correctly structured.
6
+ */
7
+
8
+ import type { IRouteConfig, IRouteMatch, IRouteAction, TPortRange } from '../models/route-types.js';
9
+
10
+ /**
11
+ * Validates a port range or port number
12
+ * @param port Port number, port range, or port function
13
+ * @returns True if valid, false otherwise
14
+ */
15
+ export function isValidPort(port: any): boolean {
16
+ if (typeof port === 'number') {
17
+ return port > 0 && port < 65536; // Valid port range is 1-65535
18
+ } else if (Array.isArray(port)) {
19
+ return port.every(p =>
20
+ (typeof p === 'number' && p > 0 && p < 65536) ||
21
+ (typeof p === 'object' && 'from' in p && 'to' in p &&
22
+ p.from > 0 && p.from < 65536 && p.to > 0 && p.to < 65536)
23
+ );
24
+ } else if (typeof port === 'function') {
25
+ // For function-based ports, we can't validate the result at config time
26
+ // so we just check that it's a function
27
+ return true;
28
+ } else if (typeof port === 'object' && 'from' in port && 'to' in port) {
29
+ return port.from > 0 && port.from < 65536 && port.to > 0 && port.to < 65536;
30
+ }
31
+ return false;
32
+ }
33
+
34
+ /**
35
+ * Validates a domain string
36
+ * @param domain Domain string to validate
37
+ * @returns True if valid, false otherwise
38
+ */
39
+ export function isValidDomain(domain: string): boolean {
40
+ // Basic domain validation regex - allows wildcards (*.example.com)
41
+ const domainRegex = /^(\*\.)?([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
42
+ return domainRegex.test(domain);
43
+ }
44
+
45
+ /**
46
+ * Validates a route match configuration
47
+ * @param match Route match configuration to validate
48
+ * @returns { valid: boolean, errors: string[] } Validation result
49
+ */
50
+ export function validateRouteMatch(match: IRouteMatch): { valid: boolean; errors: string[] } {
51
+ const errors: string[] = [];
52
+
53
+ // Validate ports
54
+ if (match.ports !== undefined) {
55
+ if (!isValidPort(match.ports)) {
56
+ errors.push('Invalid port number or port range in match.ports');
57
+ }
58
+ }
59
+
60
+ // Validate domains
61
+ if (match.domains !== undefined) {
62
+ if (typeof match.domains === 'string') {
63
+ if (!isValidDomain(match.domains)) {
64
+ errors.push(`Invalid domain format: ${match.domains}`);
65
+ }
66
+ } else if (Array.isArray(match.domains)) {
67
+ for (const domain of match.domains) {
68
+ if (!isValidDomain(domain)) {
69
+ errors.push(`Invalid domain format: ${domain}`);
70
+ }
71
+ }
72
+ } else {
73
+ errors.push('Domains must be a string or an array of strings');
74
+ }
75
+ }
76
+
77
+ // Validate path
78
+ if (match.path !== undefined) {
79
+ if (typeof match.path !== 'string' || !match.path.startsWith('/')) {
80
+ errors.push('Path must be a string starting with /');
81
+ }
82
+ }
83
+
84
+ return {
85
+ valid: errors.length === 0,
86
+ errors
87
+ };
88
+ }
89
+
90
+ /**
91
+ * Validates a route action configuration
92
+ * @param action Route action configuration to validate
93
+ * @returns { valid: boolean, errors: string[] } Validation result
94
+ */
95
+ export function validateRouteAction(action: IRouteAction): { valid: boolean; errors: string[] } {
96
+ const errors: string[] = [];
97
+
98
+ // Validate action type
99
+ if (!action.type) {
100
+ errors.push('Action type is required');
101
+ } else if (!['forward', 'redirect', 'static', 'block'].includes(action.type)) {
102
+ errors.push(`Invalid action type: ${action.type}`);
103
+ }
104
+
105
+ // Validate target for 'forward' action
106
+ if (action.type === 'forward') {
107
+ if (!action.target) {
108
+ errors.push('Target is required for forward action');
109
+ } else {
110
+ // Validate target host
111
+ if (!action.target.host) {
112
+ errors.push('Target host is required');
113
+ } else if (typeof action.target.host !== 'string' &&
114
+ !Array.isArray(action.target.host) &&
115
+ typeof action.target.host !== 'function') {
116
+ errors.push('Target host must be a string, array of strings, or function');
117
+ }
118
+
119
+ // Validate target port
120
+ if (action.target.port === undefined) {
121
+ errors.push('Target port is required');
122
+ } else if (typeof action.target.port !== 'number' &&
123
+ typeof action.target.port !== 'function') {
124
+ errors.push('Target port must be a number or a function');
125
+ } else if (typeof action.target.port === 'number' && !isValidPort(action.target.port)) {
126
+ errors.push('Target port must be between 1 and 65535');
127
+ }
128
+ }
129
+
130
+ // Validate TLS options for forward actions
131
+ if (action.tls) {
132
+ if (!['passthrough', 'terminate', 'terminate-and-reencrypt'].includes(action.tls.mode)) {
133
+ errors.push(`Invalid TLS mode: ${action.tls.mode}`);
134
+ }
135
+
136
+ // For termination modes, validate certificate
137
+ if (['terminate', 'terminate-and-reencrypt'].includes(action.tls.mode)) {
138
+ if (action.tls.certificate !== 'auto' &&
139
+ (!action.tls.certificate || !action.tls.certificate.key || !action.tls.certificate.cert)) {
140
+ errors.push('Certificate must be "auto" or an object with key and cert properties');
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ // Validate redirect for 'redirect' action
147
+ if (action.type === 'redirect') {
148
+ if (!action.redirect) {
149
+ errors.push('Redirect configuration is required for redirect action');
150
+ } else {
151
+ if (!action.redirect.to) {
152
+ errors.push('Redirect target (to) is required');
153
+ }
154
+
155
+ if (action.redirect.status &&
156
+ ![301, 302, 303, 307, 308].includes(action.redirect.status)) {
157
+ errors.push('Invalid redirect status code');
158
+ }
159
+ }
160
+ }
161
+
162
+ // Validate static file config for 'static' action
163
+ if (action.type === 'static') {
164
+ if (!action.static) {
165
+ errors.push('Static file configuration is required for static action');
166
+ } else {
167
+ if (!action.static.root) {
168
+ errors.push('Static file root directory is required');
169
+ }
170
+ }
171
+ }
172
+
173
+ return {
174
+ valid: errors.length === 0,
175
+ errors
176
+ };
177
+ }
178
+
179
+ /**
180
+ * Validates a complete route configuration
181
+ * @param route Route configuration to validate
182
+ * @returns { valid: boolean, errors: string[] } Validation result
183
+ */
184
+ export function validateRouteConfig(route: IRouteConfig): { valid: boolean; errors: string[] } {
185
+ const errors: string[] = [];
186
+
187
+ // Check for required properties
188
+ if (!route.match) {
189
+ errors.push('Route match configuration is required');
190
+ }
191
+
192
+ if (!route.action) {
193
+ errors.push('Route action configuration is required');
194
+ }
195
+
196
+ // Validate match configuration
197
+ if (route.match) {
198
+ const matchValidation = validateRouteMatch(route.match);
199
+ if (!matchValidation.valid) {
200
+ errors.push(...matchValidation.errors.map(err => `Match: ${err}`));
201
+ }
202
+ }
203
+
204
+ // Validate action configuration
205
+ if (route.action) {
206
+ const actionValidation = validateRouteAction(route.action);
207
+ if (!actionValidation.valid) {
208
+ errors.push(...actionValidation.errors.map(err => `Action: ${err}`));
209
+ }
210
+ }
211
+
212
+ // Ensure the route has a unique identifier
213
+ if (!route.id && !route.name) {
214
+ errors.push('Route should have either an id or a name for identification');
215
+ }
216
+
217
+ return {
218
+ valid: errors.length === 0,
219
+ errors
220
+ };
221
+ }
222
+
223
+ /**
224
+ * Validate an array of route configurations
225
+ * @param routes Array of route configurations to validate
226
+ * @returns { valid: boolean, errors: { index: number, errors: string[] }[] } Validation result
227
+ */
228
+ export function validateRoutes(routes: IRouteConfig[]): {
229
+ valid: boolean;
230
+ errors: { index: number; errors: string[] }[]
231
+ } {
232
+ const results: { index: number; errors: string[] }[] = [];
233
+
234
+ routes.forEach((route, index) => {
235
+ const validation = validateRouteConfig(route);
236
+ if (!validation.valid) {
237
+ results.push({
238
+ index,
239
+ errors: validation.errors
240
+ });
241
+ }
242
+ });
243
+
244
+ return {
245
+ valid: results.length === 0,
246
+ errors: results
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Check if a route configuration has the required properties for a specific action type
252
+ * @param route Route configuration to check
253
+ * @param actionType Expected action type
254
+ * @returns True if the route has the necessary properties, false otherwise
255
+ */
256
+ export function hasRequiredPropertiesForAction(route: IRouteConfig, actionType: string): boolean {
257
+ if (!route.action || route.action.type !== actionType) {
258
+ return false;
259
+ }
260
+
261
+ switch (actionType) {
262
+ case 'forward':
263
+ return !!route.action.target && !!route.action.target.host && !!route.action.target.port;
264
+ case 'redirect':
265
+ return !!route.action.redirect && !!route.action.redirect.to;
266
+ case 'static':
267
+ return !!route.action.static && !!route.action.static.root;
268
+ case 'block':
269
+ return true; // Block action doesn't require additional properties
270
+ default:
271
+ return false;
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Throws an error if the route config is invalid, returns the config if valid
277
+ * Useful for immediate validation when creating routes
278
+ * @param route Route configuration to validate
279
+ * @returns The validated route configuration
280
+ * @throws Error if the route configuration is invalid
281
+ */
282
+ export function assertValidRoute(route: IRouteConfig): IRouteConfig {
283
+ const validation = validateRouteConfig(route);
284
+ if (!validation.valid) {
285
+ throw new Error(`Invalid route configuration: ${validation.errors.join(', ')}`);
286
+ }
287
+ return route;
288
+ }
@@ -1,28 +0,0 @@
1
- import type { IForwardConfig } from './forwarding-types.js';
2
-
3
- /**
4
- * Domain configuration with unified forwarding configuration
5
- */
6
- export interface IDomainConfig {
7
- // Core properties - domain patterns
8
- domains: string[];
9
-
10
- // Unified forwarding configuration
11
- forwarding: IForwardConfig;
12
- }
13
-
14
- /**
15
- * Helper function to create a domain configuration
16
- */
17
- export function createDomainConfig(
18
- domains: string | string[],
19
- forwarding: IForwardConfig
20
- ): IDomainConfig {
21
- // Normalize domains to an array
22
- const domainArray = Array.isArray(domains) ? domains : [domains];
23
-
24
- return {
25
- domains: domainArray,
26
- forwarding
27
- };
28
- }