@push.rocks/smartproxy 19.4.2 → 19.5.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 (42) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/proxies/http-proxy/handlers/index.d.ts +1 -2
  3. package/dist_ts/proxies/http-proxy/handlers/index.js +3 -3
  4. package/dist_ts/proxies/http-proxy/models/types.js +2 -2
  5. package/dist_ts/proxies/smart-proxy/certificate-manager.js +30 -25
  6. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +2 -5
  7. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +9 -41
  8. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  9. package/dist_ts/proxies/smart-proxy/nftables-manager.js +5 -6
  10. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +4 -12
  11. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +152 -47
  12. package/dist_ts/proxies/smart-proxy/route-manager.d.ts +2 -0
  13. package/dist_ts/proxies/smart-proxy/route-manager.js +7 -8
  14. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +2 -2
  15. package/dist_ts/proxies/smart-proxy/utils/index.js +3 -3
  16. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +61 -20
  17. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +249 -53
  18. package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +0 -18
  19. package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +4 -43
  20. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +14 -15
  21. package/dist_ts/proxies/smart-proxy/utils/route-validators.js +10 -31
  22. package/package.json +7 -7
  23. package/readme.hints.md +168 -5
  24. package/readme.plan.md +314 -382
  25. package/readme.plan2.md +764 -0
  26. package/readme.problems.md +86 -0
  27. package/ts/00_commitinfo_data.ts +1 -1
  28. package/ts/proxies/http-proxy/handlers/index.ts +1 -2
  29. package/ts/proxies/http-proxy/models/types.ts +1 -1
  30. package/ts/proxies/smart-proxy/certificate-manager.ts +29 -23
  31. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +1 -4
  32. package/ts/proxies/smart-proxy/models/route-types.ts +12 -59
  33. package/ts/proxies/smart-proxy/nftables-manager.ts +4 -5
  34. package/ts/proxies/smart-proxy/route-connection-handler.ts +170 -64
  35. package/ts/proxies/smart-proxy/route-manager.ts +7 -8
  36. package/ts/proxies/smart-proxy/utils/index.ts +0 -2
  37. package/ts/proxies/smart-proxy/utils/route-helpers.ts +289 -70
  38. package/ts/proxies/smart-proxy/utils/route-patterns.ts +6 -56
  39. package/ts/proxies/smart-proxy/utils/route-utils.ts +12 -15
  40. package/ts/proxies/smart-proxy/utils/route-validators.ts +9 -31
  41. package/ts/proxies/http-proxy/handlers/redirect-handler.ts +0 -105
  42. package/ts/proxies/http-proxy/handlers/static-handler.ts +0 -261
@@ -211,9 +211,10 @@ export class RouteManager extends plugins.EventEmitter {
211
211
 
212
212
  /**
213
213
  * Check if a client IP is allowed by a route's security settings
214
+ * @deprecated Security is now checked in route-connection-handler.ts after route matching
214
215
  */
215
216
  private isClientIpAllowed(route: IRouteConfig, clientIp: string): boolean {
216
- const security = route.action.security;
217
+ const security = route.security;
217
218
 
218
219
  if (!security) {
219
220
  return true; // No security settings means allowed
@@ -330,8 +331,9 @@ export class RouteManager extends plugins.EventEmitter {
330
331
  clientIp: string;
331
332
  path?: string;
332
333
  tlsVersion?: string;
334
+ skipDomainCheck?: boolean;
333
335
  }): IRouteMatchResult | null {
334
- const { port, domain, clientIp, path, tlsVersion } = options;
336
+ const { port, domain, clientIp, path, tlsVersion, skipDomainCheck } = options;
335
337
 
336
338
  // Get all routes for this port
337
339
  const routesForPort = this.getRoutesForPort(port);
@@ -340,7 +342,7 @@ export class RouteManager extends plugins.EventEmitter {
340
342
  for (const route of routesForPort) {
341
343
  // Check domain match
342
344
  // If the route has domain restrictions and we have a domain to check
343
- if (route.match.domains) {
345
+ if (route.match.domains && !skipDomainCheck) {
344
346
  // If no domain was provided (non-TLS or no SNI), this route doesn't match
345
347
  if (!domain) {
346
348
  continue;
@@ -351,6 +353,7 @@ export class RouteManager extends plugins.EventEmitter {
351
353
  }
352
354
  }
353
355
  // If route has no domain restrictions, it matches all domains
356
+ // If skipDomainCheck is true, we skip domain validation for HTTP connections
354
357
 
355
358
  // Check path match if specified in both route and request
356
359
  if (path && route.match.path) {
@@ -371,12 +374,8 @@ export class RouteManager extends plugins.EventEmitter {
371
374
  continue;
372
375
  }
373
376
 
374
- // Check security settings
375
- if (!this.isClientIpAllowed(route, clientIp)) {
376
- continue;
377
- }
378
-
379
377
  // All checks passed, this route matches
378
+ // NOTE: Security is checked AFTER route matching in route-connection-handler.ts
380
379
  return { route };
381
380
  }
382
381
 
@@ -19,7 +19,6 @@ import {
19
19
  createWebSocketRoute as createWebSocketPatternRoute,
20
20
  createLoadBalancerRoute as createLoadBalancerPatternRoute,
21
21
  createApiGatewayRoute,
22
- createStaticFileServerRoute,
23
22
  addRateLimiting,
24
23
  addBasicAuth,
25
24
  addJwtAuth
@@ -29,7 +28,6 @@ export {
29
28
  createWebSocketPatternRoute,
30
29
  createLoadBalancerPatternRoute,
31
30
  createApiGatewayRoute,
32
- createStaticFileServerRoute,
33
31
  addRateLimiting,
34
32
  addBasicAuth,
35
33
  addJwtAuth
@@ -11,7 +11,6 @@
11
11
  * - HTTPS passthrough routes (createHttpsPassthroughRoute)
12
12
  * - Complete HTTPS servers with redirects (createCompleteHttpsServer)
13
13
  * - Load balancer routes (createLoadBalancerRoute)
14
- * - Static file server routes (createStaticFileRoute)
15
14
  * - API routes (createApiRoute)
16
15
  * - WebSocket routes (createWebSocketRoute)
17
16
  * - Port mapping routes (createPortMappingRoute, createOffsetPortMappingRoute)
@@ -19,6 +18,7 @@
19
18
  * - NFTables routes (createNfTablesRoute, createNfTablesTerminateRoute)
20
19
  */
21
20
 
21
+ import * as plugins from '../../../plugins.js';
22
22
  import type { IRouteConfig, IRouteMatch, IRouteAction, IRouteTarget, TPortRange, IRouteContext } from '../models/route-types.js';
23
23
 
24
24
  /**
@@ -118,11 +118,8 @@ export function createHttpToHttpsRedirect(
118
118
 
119
119
  // Create route action
120
120
  const action: IRouteAction = {
121
- type: 'redirect',
122
- redirect: {
123
- to: `https://{domain}:${httpsPort}{path}`,
124
- status: 301
125
- }
121
+ type: 'socket-handler',
122
+ socketHandler: SocketHandlers.httpRedirect(`https://{domain}:${httpsPort}{path}`, 301)
126
123
  };
127
124
 
128
125
  // Create the route config
@@ -266,60 +263,6 @@ export function createLoadBalancerRoute(
266
263
  };
267
264
  }
268
265
 
269
- /**
270
- * Create a static file server route
271
- * @param domains Domain(s) to match
272
- * @param rootDir Root directory path for static files
273
- * @param options Additional route options
274
- * @returns Route configuration object
275
- */
276
- export function createStaticFileRoute(
277
- domains: string | string[],
278
- rootDir: string,
279
- options: {
280
- indexFiles?: string[];
281
- serveOnHttps?: boolean;
282
- certificate?: 'auto' | { key: string; cert: string };
283
- httpPort?: number | number[];
284
- httpsPort?: number | number[];
285
- name?: string;
286
- [key: string]: any;
287
- } = {}
288
- ): IRouteConfig {
289
- // Create route match
290
- const match: IRouteMatch = {
291
- ports: options.serveOnHttps
292
- ? (options.httpsPort || 443)
293
- : (options.httpPort || 80),
294
- domains
295
- };
296
-
297
- // Create route action
298
- const action: IRouteAction = {
299
- type: 'static',
300
- static: {
301
- root: rootDir,
302
- index: options.indexFiles || ['index.html', 'index.htm']
303
- }
304
- };
305
-
306
- // Add TLS configuration if serving on HTTPS
307
- if (options.serveOnHttps) {
308
- action.tls = {
309
- mode: 'terminate',
310
- certificate: options.certificate || 'auto'
311
- };
312
- }
313
-
314
- // Create the route config
315
- return {
316
- match,
317
- action,
318
- name: options.name || `Static Files for ${Array.isArray(domains) ? domains.join(', ') : domains}`,
319
- ...options
320
- };
321
- }
322
-
323
266
  /**
324
267
  * Create an API route configuration
325
268
  * @param domains Domain(s) to match
@@ -682,14 +625,6 @@ export function createNfTablesRoute(
682
625
  }
683
626
  };
684
627
 
685
- // Add security if allowed or blocked IPs are specified
686
- if (options.ipAllowList?.length || options.ipBlockList?.length) {
687
- action.security = {
688
- ipAllowList: options.ipAllowList,
689
- ipBlockList: options.ipBlockList
690
- };
691
- }
692
-
693
628
  // Add TLS options if needed
694
629
  if (options.useTls) {
695
630
  action.tls = {
@@ -698,11 +633,21 @@ export function createNfTablesRoute(
698
633
  }
699
634
 
700
635
  // Create the route config
701
- return {
636
+ const routeConfig: IRouteConfig = {
702
637
  name,
703
638
  match,
704
639
  action
705
640
  };
641
+
642
+ // Add security if allowed or blocked IPs are specified
643
+ if (options.ipAllowList?.length || options.ipBlockList?.length) {
644
+ routeConfig.security = {
645
+ ipAllowList: options.ipAllowList,
646
+ ipBlockList: options.ipBlockList
647
+ };
648
+ }
649
+
650
+ return routeConfig;
706
651
  }
707
652
 
708
653
  /**
@@ -810,4 +755,278 @@ export function createCompleteNfTablesHttpsServer(
810
755
  );
811
756
 
812
757
  return [httpsRoute, httpRedirectRoute];
813
- }
758
+ }
759
+
760
+ /**
761
+ * Create a socket handler route configuration
762
+ * @param domains Domain(s) to match
763
+ * @param ports Port(s) to listen on
764
+ * @param handler Socket handler function
765
+ * @param options Additional route options
766
+ * @returns Route configuration object
767
+ */
768
+ export function createSocketHandlerRoute(
769
+ domains: string | string[],
770
+ ports: TPortRange,
771
+ handler: (socket: plugins.net.Socket) => void | Promise<void>,
772
+ options: {
773
+ name?: string;
774
+ priority?: number;
775
+ path?: string;
776
+ } = {}
777
+ ): IRouteConfig {
778
+ return {
779
+ name: options.name || 'socket-handler-route',
780
+ priority: options.priority !== undefined ? options.priority : 50,
781
+ match: {
782
+ domains,
783
+ ports,
784
+ ...(options.path && { path: options.path })
785
+ },
786
+ action: {
787
+ type: 'socket-handler',
788
+ socketHandler: handler
789
+ }
790
+ };
791
+ }
792
+
793
+ /**
794
+ * Pre-built socket handlers for common use cases
795
+ */
796
+ export const SocketHandlers = {
797
+ /**
798
+ * Simple echo server handler
799
+ */
800
+ echo: (socket: plugins.net.Socket, context: IRouteContext) => {
801
+ socket.write('ECHO SERVER READY\n');
802
+ socket.on('data', data => socket.write(data));
803
+ },
804
+
805
+ /**
806
+ * TCP proxy handler
807
+ */
808
+ proxy: (targetHost: string, targetPort: number) => (socket: plugins.net.Socket, context: IRouteContext) => {
809
+ const target = plugins.net.connect(targetPort, targetHost);
810
+ socket.pipe(target);
811
+ target.pipe(socket);
812
+ socket.on('close', () => target.destroy());
813
+ target.on('close', () => socket.destroy());
814
+ target.on('error', (err) => {
815
+ console.error('Proxy target error:', err);
816
+ socket.destroy();
817
+ });
818
+ },
819
+
820
+ /**
821
+ * Line-based protocol handler
822
+ */
823
+ lineProtocol: (handler: (line: string, socket: plugins.net.Socket) => void) => (socket: plugins.net.Socket, context: IRouteContext) => {
824
+ let buffer = '';
825
+ socket.on('data', (data) => {
826
+ buffer += data.toString();
827
+ const lines = buffer.split('\n');
828
+ buffer = lines.pop() || '';
829
+ lines.forEach(line => {
830
+ if (line.trim()) {
831
+ handler(line.trim(), socket);
832
+ }
833
+ });
834
+ });
835
+ },
836
+
837
+ /**
838
+ * Simple HTTP response handler (for testing)
839
+ */
840
+ httpResponse: (statusCode: number, body: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
841
+ const response = [
842
+ `HTTP/1.1 ${statusCode} ${statusCode === 200 ? 'OK' : 'Error'}`,
843
+ 'Content-Type: text/plain',
844
+ `Content-Length: ${body.length}`,
845
+ 'Connection: close',
846
+ '',
847
+ body
848
+ ].join('\r\n');
849
+
850
+ socket.write(response);
851
+ socket.end();
852
+ },
853
+
854
+ /**
855
+ * Block connection immediately
856
+ */
857
+ block: (message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
858
+ const finalMessage = message || `Connection blocked from ${context.clientIp}`;
859
+ if (finalMessage) {
860
+ socket.write(finalMessage);
861
+ }
862
+ socket.end();
863
+ },
864
+
865
+ /**
866
+ * HTTP block response
867
+ */
868
+ httpBlock: (statusCode: number = 403, message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
869
+ const defaultMessage = `Access forbidden for ${context.domain || context.clientIp}`;
870
+ const finalMessage = message || defaultMessage;
871
+
872
+ const response = [
873
+ `HTTP/1.1 ${statusCode} ${finalMessage}`,
874
+ 'Content-Type: text/plain',
875
+ `Content-Length: ${finalMessage.length}`,
876
+ 'Connection: close',
877
+ '',
878
+ finalMessage
879
+ ].join('\r\n');
880
+
881
+ socket.write(response);
882
+ socket.end();
883
+ },
884
+
885
+ /**
886
+ * HTTP redirect handler
887
+ */
888
+ httpRedirect: (locationTemplate: string, statusCode: number = 301) => (socket: plugins.net.Socket, context: IRouteContext) => {
889
+ let buffer = '';
890
+
891
+ socket.once('data', (data) => {
892
+ buffer += data.toString();
893
+
894
+ const lines = buffer.split('\r\n');
895
+ const requestLine = lines[0];
896
+ const [method, path] = requestLine.split(' ');
897
+
898
+ const domain = context.domain || 'localhost';
899
+ const port = context.port;
900
+
901
+ let finalLocation = locationTemplate
902
+ .replace('{domain}', domain)
903
+ .replace('{port}', String(port))
904
+ .replace('{path}', path)
905
+ .replace('{clientIp}', context.clientIp);
906
+
907
+ const message = `Redirecting to ${finalLocation}`;
908
+ const response = [
909
+ `HTTP/1.1 ${statusCode} ${statusCode === 301 ? 'Moved Permanently' : 'Found'}`,
910
+ `Location: ${finalLocation}`,
911
+ 'Content-Type: text/plain',
912
+ `Content-Length: ${message.length}`,
913
+ 'Connection: close',
914
+ '',
915
+ message
916
+ ].join('\r\n');
917
+
918
+ socket.write(response);
919
+ socket.end();
920
+ });
921
+ },
922
+
923
+ /**
924
+ * HTTP server handler for ACME challenges and other HTTP needs
925
+ */
926
+ httpServer: (handler: (req: { method: string; url: string; headers: Record<string, string>; body?: string }, res: { status: (code: number) => void; header: (name: string, value: string) => void; send: (data: string) => void; end: () => void }) => void) => (socket: plugins.net.Socket, context: IRouteContext) => {
927
+ let buffer = '';
928
+ let requestParsed = false;
929
+
930
+ socket.on('data', (data) => {
931
+ if (requestParsed) return; // Only handle the first request
932
+
933
+ buffer += data.toString();
934
+
935
+ // Check if we have a complete HTTP request
936
+ const headerEndIndex = buffer.indexOf('\r\n\r\n');
937
+ if (headerEndIndex === -1) return; // Need more data
938
+
939
+ requestParsed = true;
940
+
941
+ // Parse the HTTP request
942
+ const headerPart = buffer.substring(0, headerEndIndex);
943
+ const bodyPart = buffer.substring(headerEndIndex + 4);
944
+
945
+ const lines = headerPart.split('\r\n');
946
+ const [method, url] = lines[0].split(' ');
947
+
948
+ const headers: Record<string, string> = {};
949
+ for (let i = 1; i < lines.length; i++) {
950
+ const colonIndex = lines[i].indexOf(':');
951
+ if (colonIndex > 0) {
952
+ const name = lines[i].substring(0, colonIndex).trim().toLowerCase();
953
+ const value = lines[i].substring(colonIndex + 1).trim();
954
+ headers[name] = value;
955
+ }
956
+ }
957
+
958
+ // Create request object
959
+ const req = {
960
+ method: method || 'GET',
961
+ url: url || '/',
962
+ headers,
963
+ body: bodyPart
964
+ };
965
+
966
+ // Create response object
967
+ let statusCode = 200;
968
+ const responseHeaders: Record<string, string> = {};
969
+ let ended = false;
970
+
971
+ const res = {
972
+ status: (code: number) => {
973
+ statusCode = code;
974
+ },
975
+ header: (name: string, value: string) => {
976
+ responseHeaders[name] = value;
977
+ },
978
+ send: (data: string) => {
979
+ if (ended) return;
980
+ ended = true;
981
+
982
+ if (!responseHeaders['content-type']) {
983
+ responseHeaders['content-type'] = 'text/plain';
984
+ }
985
+ responseHeaders['content-length'] = String(data.length);
986
+ responseHeaders['connection'] = 'close';
987
+
988
+ const statusText = statusCode === 200 ? 'OK' :
989
+ statusCode === 404 ? 'Not Found' :
990
+ statusCode === 500 ? 'Internal Server Error' : 'Response';
991
+
992
+ let response = `HTTP/1.1 ${statusCode} ${statusText}\r\n`;
993
+ for (const [name, value] of Object.entries(responseHeaders)) {
994
+ response += `${name}: ${value}\r\n`;
995
+ }
996
+ response += '\r\n';
997
+ response += data;
998
+
999
+ socket.write(response);
1000
+ socket.end();
1001
+ },
1002
+ end: () => {
1003
+ if (ended) return;
1004
+ ended = true;
1005
+ socket.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n');
1006
+ socket.end();
1007
+ }
1008
+ };
1009
+
1010
+ try {
1011
+ handler(req, res);
1012
+ // Ensure response is sent even if handler doesn't call send()
1013
+ setTimeout(() => {
1014
+ if (!ended) {
1015
+ res.send('');
1016
+ }
1017
+ }, 1000);
1018
+ } catch (error) {
1019
+ if (!ended) {
1020
+ res.status(500);
1021
+ res.send('Internal Server Error');
1022
+ }
1023
+ }
1024
+ });
1025
+
1026
+ socket.on('error', () => {
1027
+ if (!requestParsed) {
1028
+ socket.end();
1029
+ }
1030
+ });
1031
+ }
1032
+ };
@@ -7,6 +7,7 @@
7
7
 
8
8
  import type { IRouteConfig, IRouteMatch, IRouteAction, IRouteTarget } from '../models/route-types.js';
9
9
  import { mergeRouteConfigs } from './route-utils.js';
10
+ import { SocketHandlers } from './route-helpers.js';
10
11
 
11
12
  /**
12
13
  * Create a basic HTTP route configuration
@@ -112,11 +113,11 @@ export function createHttpToHttpsRedirect(
112
113
  ports: 80
113
114
  },
114
115
  action: {
115
- type: 'redirect',
116
- redirect: {
117
- to: options.preservePath ? 'https://{domain}{path}' : 'https://{domain}',
118
- status: options.redirectCode || 301
119
- }
116
+ type: 'socket-handler',
117
+ socketHandler: SocketHandlers.httpRedirect(
118
+ options.preservePath ? 'https://{domain}{path}' : 'https://{domain}',
119
+ options.redirectCode || 301
120
+ )
120
121
  },
121
122
  name: options.name || `HTTP to HTTPS redirect: ${Array.isArray(domains) ? domains.join(', ') : domains}`
122
123
  };
@@ -214,57 +215,6 @@ export function createApiGatewayRoute(
214
215
  return mergeRouteConfigs(baseRoute, apiRoute);
215
216
  }
216
217
 
217
- /**
218
- * Create a static file server route pattern
219
- * @param domains Domain(s) to match
220
- * @param rootDirectory Root directory for static files
221
- * @param options Additional route options
222
- * @returns Static file server route configuration
223
- */
224
- export function createStaticFileServerRoute(
225
- domains: string | string[],
226
- rootDirectory: string,
227
- options: {
228
- useTls?: boolean;
229
- certificate?: 'auto' | { key: string; cert: string };
230
- indexFiles?: string[];
231
- cacheControl?: string;
232
- path?: string;
233
- [key: string]: any;
234
- } = {}
235
- ): IRouteConfig {
236
- // Create base route with static action
237
- const baseRoute: IRouteConfig = {
238
- match: {
239
- domains,
240
- ports: options.useTls ? 443 : 80,
241
- path: options.path || '/'
242
- },
243
- action: {
244
- type: 'static',
245
- static: {
246
- root: rootDirectory,
247
- index: options.indexFiles || ['index.html', 'index.htm'],
248
- headers: {
249
- 'Cache-Control': options.cacheControl || 'public, max-age=3600'
250
- }
251
- }
252
- },
253
- name: options.name || `Static Server: ${Array.isArray(domains) ? domains.join(', ') : domains}`,
254
- priority: options.priority || 50
255
- };
256
-
257
- // Add TLS configuration if requested
258
- if (options.useTls) {
259
- baseRoute.action.tls = {
260
- mode: 'terminate',
261
- certificate: options.certificate || 'auto'
262
- };
263
- }
264
-
265
- return baseRoute;
266
- }
267
-
268
218
  /**
269
219
  * Create a WebSocket route pattern
270
220
  * @param domains Domain(s) to match
@@ -53,7 +53,15 @@ export function mergeRouteConfigs(
53
53
  if (overrideRoute.action) {
54
54
  // If action types are different, replace the entire action
55
55
  if (overrideRoute.action.type && overrideRoute.action.type !== mergedRoute.action.type) {
56
- mergedRoute.action = JSON.parse(JSON.stringify(overrideRoute.action));
56
+ // Handle socket handler specially since it's a function
57
+ if (overrideRoute.action.type === 'socket-handler' && overrideRoute.action.socketHandler) {
58
+ mergedRoute.action = {
59
+ type: 'socket-handler',
60
+ socketHandler: overrideRoute.action.socketHandler
61
+ };
62
+ } else {
63
+ mergedRoute.action = JSON.parse(JSON.stringify(overrideRoute.action));
64
+ }
57
65
  } else {
58
66
  // Otherwise merge the action properties
59
67
  mergedRoute.action = { ...mergedRoute.action };
@@ -74,20 +82,9 @@ export function mergeRouteConfigs(
74
82
  };
75
83
  }
76
84
 
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
- };
85
+ // Handle socket handler update
86
+ if (overrideRoute.action.socketHandler) {
87
+ mergedRoute.action.socketHandler = overrideRoute.action.socketHandler;
91
88
  }
92
89
  }
93
90
  }
@@ -98,7 +98,7 @@ export function validateRouteAction(action: IRouteAction): { valid: boolean; err
98
98
  // Validate action type
99
99
  if (!action.type) {
100
100
  errors.push('Action type is required');
101
- } else if (!['forward', 'redirect', 'static', 'block'].includes(action.type)) {
101
+ } else if (!['forward', 'socket-handler'].includes(action.type)) {
102
102
  errors.push(`Invalid action type: ${action.type}`);
103
103
  }
104
104
 
@@ -143,30 +143,12 @@ export function validateRouteAction(action: IRouteAction): { valid: boolean; err
143
143
  }
144
144
  }
145
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
- }
146
+ // Validate socket handler for 'socket-handler' action
147
+ if (action.type === 'socket-handler') {
148
+ if (!action.socketHandler) {
149
+ errors.push('Socket handler function is required for socket-handler action');
150
+ } else if (typeof action.socketHandler !== 'function') {
151
+ errors.push('Socket handler must be a function');
170
152
  }
171
153
  }
172
154
 
@@ -261,12 +243,8 @@ export function hasRequiredPropertiesForAction(route: IRouteConfig, actionType:
261
243
  switch (actionType) {
262
244
  case 'forward':
263
245
  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
246
+ case 'socket-handler':
247
+ return !!route.action.socketHandler && typeof route.action.socketHandler === 'function';
270
248
  default:
271
249
  return false;
272
250
  }