@push.rocks/smartproxy 21.1.6 → 22.4.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 (103) hide show
  1. package/changelog.md +89 -0
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/core/utils/shared-security-manager.d.ts +17 -0
  4. package/dist_ts/core/utils/shared-security-manager.js +66 -1
  5. package/dist_ts/proxies/http-proxy/default-certificates.d.ts +54 -0
  6. package/dist_ts/proxies/http-proxy/default-certificates.js +127 -0
  7. package/dist_ts/proxies/http-proxy/http-proxy.d.ts +1 -1
  8. package/dist_ts/proxies/http-proxy/http-proxy.js +9 -14
  9. package/dist_ts/proxies/http-proxy/index.d.ts +5 -1
  10. package/dist_ts/proxies/http-proxy/index.js +6 -2
  11. package/dist_ts/proxies/http-proxy/security-manager.d.ts +4 -12
  12. package/dist_ts/proxies/http-proxy/security-manager.js +66 -99
  13. package/dist_ts/proxies/nftables-proxy/index.d.ts +1 -0
  14. package/dist_ts/proxies/nftables-proxy/index.js +2 -1
  15. package/dist_ts/proxies/nftables-proxy/nftables-proxy.d.ts +4 -26
  16. package/dist_ts/proxies/nftables-proxy/nftables-proxy.js +84 -236
  17. package/dist_ts/proxies/nftables-proxy/utils/index.d.ts +9 -0
  18. package/dist_ts/proxies/nftables-proxy/utils/index.js +12 -0
  19. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.d.ts +66 -0
  20. package/dist_ts/proxies/nftables-proxy/utils/nft-command-executor.js +131 -0
  21. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.d.ts +39 -0
  22. package/dist_ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.js +112 -0
  23. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.d.ts +59 -0
  24. package/dist_ts/proxies/nftables-proxy/utils/nft-rule-validator.js +130 -0
  25. package/dist_ts/proxies/smart-proxy/certificate-manager.js +4 -3
  26. package/dist_ts/proxies/smart-proxy/connection-manager.d.ts +13 -2
  27. package/dist_ts/proxies/smart-proxy/connection-manager.js +16 -6
  28. package/dist_ts/proxies/smart-proxy/http-proxy-bridge.js +35 -10
  29. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +0 -1
  30. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +17 -0
  31. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +72 -9
  32. package/dist_ts/proxies/smart-proxy/security-manager.d.ts +14 -12
  33. package/dist_ts/proxies/smart-proxy/security-manager.js +80 -74
  34. package/dist_ts/proxies/smart-proxy/smart-proxy.js +1 -2
  35. package/dist_ts/proxies/smart-proxy/tls-manager.d.ts +2 -9
  36. package/dist_ts/proxies/smart-proxy/tls-manager.js +3 -26
  37. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +1 -1
  38. package/dist_ts/proxies/smart-proxy/utils/index.js +3 -4
  39. package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.d.ts +49 -0
  40. package/dist_ts/proxies/smart-proxy/utils/route-helpers/api-helpers.js +108 -0
  41. package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.d.ts +57 -0
  42. package/dist_ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.js +89 -0
  43. package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.d.ts +17 -0
  44. package/dist_ts/proxies/smart-proxy/utils/route-helpers/http-helpers.js +32 -0
  45. package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.d.ts +68 -0
  46. package/dist_ts/proxies/smart-proxy/utils/route-helpers/https-helpers.js +117 -0
  47. package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.d.ts +17 -0
  48. package/dist_ts/proxies/smart-proxy/utils/route-helpers/index.js +27 -0
  49. package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.d.ts +63 -0
  50. package/dist_ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.js +105 -0
  51. package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.d.ts +83 -0
  52. package/dist_ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.js +126 -0
  53. package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.d.ts +47 -0
  54. package/dist_ts/proxies/smart-proxy/utils/route-helpers/security-helpers.js +66 -0
  55. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.d.ts +70 -0
  56. package/dist_ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.js +287 -0
  57. package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.d.ts +46 -0
  58. package/dist_ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.js +67 -0
  59. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +4 -457
  60. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +6 -950
  61. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +2 -2
  62. package/dist_ts/proxies/smart-proxy/utils/route-validator.d.ts +67 -1
  63. package/dist_ts/proxies/smart-proxy/utils/route-validator.js +266 -6
  64. package/npmextra.json +12 -6
  65. package/package.json +34 -24
  66. package/readme.hints.md +184 -1
  67. package/readme.md +235 -172
  68. package/ts/00_commitinfo_data.ts +1 -1
  69. package/ts/core/utils/shared-security-manager.ts +98 -13
  70. package/ts/proxies/http-proxy/default-certificates.ts +150 -0
  71. package/ts/proxies/http-proxy/http-proxy.ts +9 -15
  72. package/ts/proxies/http-proxy/index.ts +6 -1
  73. package/ts/proxies/http-proxy/security-manager.ts +141 -161
  74. package/ts/proxies/nftables-proxy/index.ts +1 -0
  75. package/ts/proxies/nftables-proxy/nftables-proxy.ts +116 -290
  76. package/ts/proxies/nftables-proxy/utils/index.ts +38 -0
  77. package/ts/proxies/nftables-proxy/utils/nft-command-executor.ts +162 -0
  78. package/ts/proxies/nftables-proxy/utils/nft-port-spec-normalizer.ts +125 -0
  79. package/ts/proxies/nftables-proxy/utils/nft-rule-validator.ts +156 -0
  80. package/ts/proxies/smart-proxy/certificate-manager.ts +3 -2
  81. package/ts/proxies/smart-proxy/connection-manager.ts +21 -8
  82. package/ts/proxies/smart-proxy/http-proxy-bridge.ts +39 -13
  83. package/ts/proxies/smart-proxy/models/interfaces.ts +0 -1
  84. package/ts/proxies/smart-proxy/route-connection-handler.ts +88 -16
  85. package/ts/proxies/smart-proxy/security-manager.ts +98 -86
  86. package/ts/proxies/smart-proxy/smart-proxy.ts +0 -2
  87. package/ts/proxies/smart-proxy/tls-manager.ts +1 -37
  88. package/ts/proxies/smart-proxy/utils/index.ts +3 -5
  89. package/ts/proxies/smart-proxy/utils/route-helpers/api-helpers.ts +144 -0
  90. package/ts/proxies/smart-proxy/utils/route-helpers/dynamic-helpers.ts +124 -0
  91. package/ts/proxies/smart-proxy/utils/route-helpers/http-helpers.ts +40 -0
  92. package/ts/proxies/smart-proxy/utils/route-helpers/https-helpers.ts +163 -0
  93. package/ts/proxies/smart-proxy/utils/route-helpers/index.ts +62 -0
  94. package/ts/proxies/smart-proxy/utils/route-helpers/load-balancer-helpers.ts +154 -0
  95. package/ts/proxies/smart-proxy/utils/route-helpers/nftables-helpers.ts +202 -0
  96. package/ts/proxies/smart-proxy/utils/route-helpers/security-helpers.ts +96 -0
  97. package/ts/proxies/smart-proxy/utils/route-helpers/socket-handlers.ts +337 -0
  98. package/ts/proxies/smart-proxy/utils/route-helpers/websocket-helpers.ts +98 -0
  99. package/ts/proxies/smart-proxy/utils/route-helpers.ts +5 -1302
  100. package/ts/proxies/smart-proxy/utils/route-utils.ts +1 -1
  101. package/ts/proxies/smart-proxy/utils/route-validator.ts +289 -7
  102. package/ts/proxies/http-proxy/certificate-manager.ts +0 -244
  103. package/ts/proxies/smart-proxy/utils/route-validators.ts +0 -283
@@ -0,0 +1,202 @@
1
+ /**
2
+ * NFTables Route Helper Functions
3
+ *
4
+ * This module provides utility functions for creating NFTables-based route configurations
5
+ * for high-performance packet forwarding at the kernel level.
6
+ */
7
+
8
+ import type { IRouteConfig, IRouteMatch, IRouteAction, TPortRange } from '../../models/route-types.js';
9
+ import { createHttpToHttpsRedirect } from './https-helpers.js';
10
+
11
+ /**
12
+ * Create an NFTables-based route for high-performance packet forwarding
13
+ * @param nameOrDomains Name or domain(s) to match
14
+ * @param target Target host and port
15
+ * @param options Additional route options
16
+ * @returns Route configuration object
17
+ */
18
+ export function createNfTablesRoute(
19
+ nameOrDomains: string | string[],
20
+ target: { host: string; port: number | 'preserve' },
21
+ options: {
22
+ ports?: TPortRange;
23
+ protocol?: 'tcp' | 'udp' | 'all';
24
+ preserveSourceIP?: boolean;
25
+ ipAllowList?: string[];
26
+ ipBlockList?: string[];
27
+ maxRate?: string;
28
+ priority?: number;
29
+ useTls?: boolean;
30
+ tableName?: string;
31
+ useIPSets?: boolean;
32
+ useAdvancedNAT?: boolean;
33
+ } = {}
34
+ ): IRouteConfig {
35
+ // Determine if this is a name or domain
36
+ let name: string;
37
+ let domains: string | string[] | undefined;
38
+
39
+ if (Array.isArray(nameOrDomains) || (typeof nameOrDomains === 'string' && nameOrDomains.includes('.'))) {
40
+ domains = nameOrDomains;
41
+ name = Array.isArray(nameOrDomains) ? nameOrDomains[0] : nameOrDomains;
42
+ } else {
43
+ name = nameOrDomains;
44
+ domains = undefined; // No domains
45
+ }
46
+
47
+ // Create route match
48
+ const match: IRouteMatch = {
49
+ domains,
50
+ ports: options.ports || 80
51
+ };
52
+
53
+ // Create route action
54
+ const action: IRouteAction = {
55
+ type: 'forward',
56
+ targets: [{
57
+ host: target.host,
58
+ port: target.port
59
+ }],
60
+ forwardingEngine: 'nftables',
61
+ nftables: {
62
+ protocol: options.protocol || 'tcp',
63
+ preserveSourceIP: options.preserveSourceIP,
64
+ maxRate: options.maxRate,
65
+ priority: options.priority,
66
+ tableName: options.tableName,
67
+ useIPSets: options.useIPSets,
68
+ useAdvancedNAT: options.useAdvancedNAT
69
+ }
70
+ };
71
+
72
+ // Add TLS options if needed
73
+ if (options.useTls) {
74
+ action.tls = {
75
+ mode: 'passthrough'
76
+ };
77
+ }
78
+
79
+ // Create the route config
80
+ const routeConfig: IRouteConfig = {
81
+ name,
82
+ match,
83
+ action
84
+ };
85
+
86
+ // Add security if allowed or blocked IPs are specified
87
+ if (options.ipAllowList?.length || options.ipBlockList?.length) {
88
+ routeConfig.security = {
89
+ ipAllowList: options.ipAllowList,
90
+ ipBlockList: options.ipBlockList
91
+ };
92
+ }
93
+
94
+ return routeConfig;
95
+ }
96
+
97
+ /**
98
+ * Create an NFTables-based TLS termination route
99
+ * @param nameOrDomains Name or domain(s) to match
100
+ * @param target Target host and port
101
+ * @param options Additional route options
102
+ * @returns Route configuration object
103
+ */
104
+ export function createNfTablesTerminateRoute(
105
+ nameOrDomains: string | string[],
106
+ target: { host: string; port: number | 'preserve' },
107
+ options: {
108
+ ports?: TPortRange;
109
+ protocol?: 'tcp' | 'udp' | 'all';
110
+ preserveSourceIP?: boolean;
111
+ ipAllowList?: string[];
112
+ ipBlockList?: string[];
113
+ maxRate?: string;
114
+ priority?: number;
115
+ tableName?: string;
116
+ useIPSets?: boolean;
117
+ useAdvancedNAT?: boolean;
118
+ certificate?: 'auto' | { key: string; cert: string };
119
+ } = {}
120
+ ): IRouteConfig {
121
+ // Create basic NFTables route
122
+ const route = createNfTablesRoute(
123
+ nameOrDomains,
124
+ target,
125
+ {
126
+ ...options,
127
+ ports: options.ports || 443,
128
+ useTls: false
129
+ }
130
+ );
131
+
132
+ // Set TLS termination
133
+ route.action.tls = {
134
+ mode: 'terminate',
135
+ certificate: options.certificate || 'auto'
136
+ };
137
+
138
+ return route;
139
+ }
140
+
141
+ /**
142
+ * Create a complete NFTables-based HTTPS setup with HTTP redirect
143
+ * @param nameOrDomains Name or domain(s) to match
144
+ * @param target Target host and port
145
+ * @param options Additional route options
146
+ * @returns Array of two route configurations (HTTPS and HTTP redirect)
147
+ */
148
+ export function createCompleteNfTablesHttpsServer(
149
+ nameOrDomains: string | string[],
150
+ target: { host: string; port: number | 'preserve' },
151
+ options: {
152
+ httpPort?: TPortRange;
153
+ httpsPort?: TPortRange;
154
+ protocol?: 'tcp' | 'udp' | 'all';
155
+ preserveSourceIP?: boolean;
156
+ ipAllowList?: string[];
157
+ ipBlockList?: string[];
158
+ maxRate?: string;
159
+ priority?: number;
160
+ tableName?: string;
161
+ useIPSets?: boolean;
162
+ useAdvancedNAT?: boolean;
163
+ certificate?: 'auto' | { key: string; cert: string };
164
+ } = {}
165
+ ): IRouteConfig[] {
166
+ // Create the HTTPS route using NFTables
167
+ const httpsRoute = createNfTablesTerminateRoute(
168
+ nameOrDomains,
169
+ target,
170
+ {
171
+ ...options,
172
+ ports: options.httpsPort || 443
173
+ }
174
+ );
175
+
176
+ // Determine the domain(s) for HTTP redirect
177
+ const domains = typeof nameOrDomains === 'string' && !nameOrDomains.includes('.')
178
+ ? undefined
179
+ : nameOrDomains;
180
+
181
+ // Extract the HTTPS port for the redirect destination
182
+ const httpsPort = typeof options.httpsPort === 'number'
183
+ ? options.httpsPort
184
+ : Array.isArray(options.httpsPort) && typeof options.httpsPort[0] === 'number'
185
+ ? options.httpsPort[0]
186
+ : 443;
187
+
188
+ // Create the HTTP redirect route (this uses standard forwarding, not NFTables)
189
+ const httpRedirectRoute = createHttpToHttpsRedirect(
190
+ domains as any, // Type cast needed since domains can be undefined now
191
+ httpsPort,
192
+ {
193
+ match: {
194
+ ports: options.httpPort || 80,
195
+ domains: domains as any // Type cast needed since domains can be undefined now
196
+ },
197
+ name: `HTTP to HTTPS Redirect for ${Array.isArray(domains) ? domains.join(', ') : domains || 'all domains'}`
198
+ }
199
+ );
200
+
201
+ return [httpsRoute, httpRedirectRoute];
202
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Security Route Helper Functions
3
+ *
4
+ * This module provides utility functions for adding security features to routes.
5
+ */
6
+
7
+ import type { IRouteConfig } from '../../models/route-types.js';
8
+ import { mergeRouteConfigs } from '../route-utils.js';
9
+
10
+ /**
11
+ * Create a rate limiting route pattern
12
+ * @param baseRoute Base route to add rate limiting to
13
+ * @param rateLimit Rate limiting configuration
14
+ * @returns Route with rate limiting
15
+ */
16
+ export function addRateLimiting(
17
+ baseRoute: IRouteConfig,
18
+ rateLimit: {
19
+ maxRequests: number;
20
+ window: number; // Time window in seconds
21
+ keyBy?: 'ip' | 'path' | 'header';
22
+ headerName?: string; // Required if keyBy is 'header'
23
+ errorMessage?: string;
24
+ }
25
+ ): IRouteConfig {
26
+ return mergeRouteConfigs(baseRoute, {
27
+ security: {
28
+ rateLimit: {
29
+ enabled: true,
30
+ maxRequests: rateLimit.maxRequests,
31
+ window: rateLimit.window,
32
+ keyBy: rateLimit.keyBy || 'ip',
33
+ headerName: rateLimit.headerName,
34
+ errorMessage: rateLimit.errorMessage || 'Rate limit exceeded. Please try again later.'
35
+ }
36
+ }
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Create a basic authentication route pattern
42
+ * @param baseRoute Base route to add authentication to
43
+ * @param auth Authentication configuration
44
+ * @returns Route with basic authentication
45
+ */
46
+ export function addBasicAuth(
47
+ baseRoute: IRouteConfig,
48
+ auth: {
49
+ users: Array<{ username: string; password: string }>;
50
+ realm?: string;
51
+ excludePaths?: string[];
52
+ }
53
+ ): IRouteConfig {
54
+ return mergeRouteConfigs(baseRoute, {
55
+ security: {
56
+ basicAuth: {
57
+ enabled: true,
58
+ users: auth.users,
59
+ realm: auth.realm || 'Restricted Area',
60
+ excludePaths: auth.excludePaths || []
61
+ }
62
+ }
63
+ });
64
+ }
65
+
66
+ /**
67
+ * Create a JWT authentication route pattern
68
+ * @param baseRoute Base route to add JWT authentication to
69
+ * @param jwt JWT authentication configuration
70
+ * @returns Route with JWT authentication
71
+ */
72
+ export function addJwtAuth(
73
+ baseRoute: IRouteConfig,
74
+ jwt: {
75
+ secret: string;
76
+ algorithm?: string;
77
+ issuer?: string;
78
+ audience?: string;
79
+ expiresIn?: number; // Time in seconds
80
+ excludePaths?: string[];
81
+ }
82
+ ): IRouteConfig {
83
+ return mergeRouteConfigs(baseRoute, {
84
+ security: {
85
+ jwtAuth: {
86
+ enabled: true,
87
+ secret: jwt.secret,
88
+ algorithm: jwt.algorithm || 'HS256',
89
+ issuer: jwt.issuer,
90
+ audience: jwt.audience,
91
+ expiresIn: jwt.expiresIn,
92
+ excludePaths: jwt.excludePaths || []
93
+ }
94
+ }
95
+ });
96
+ }
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Socket Handler Functions
3
+ *
4
+ * This module provides pre-built socket handlers for common use cases
5
+ * like echoing, proxying, HTTP responses, and redirects.
6
+ */
7
+
8
+ import * as plugins from '../../../../plugins.js';
9
+ import type { IRouteConfig, TPortRange, IRouteContext } from '../../models/route-types.js';
10
+ import { ProtocolDetector } from '../../../../detection/index.js';
11
+ import { createSocketTracker } from '../../../../core/utils/socket-tracker.js';
12
+
13
+ /**
14
+ * Pre-built socket handlers for common use cases
15
+ */
16
+ export const SocketHandlers = {
17
+ /**
18
+ * Simple echo server handler
19
+ */
20
+ echo: (socket: plugins.net.Socket, context: IRouteContext) => {
21
+ socket.write('ECHO SERVER READY\n');
22
+ socket.on('data', data => socket.write(data));
23
+ },
24
+
25
+ /**
26
+ * TCP proxy handler
27
+ */
28
+ proxy: (targetHost: string, targetPort: number) => (socket: plugins.net.Socket, context: IRouteContext) => {
29
+ const target = plugins.net.connect(targetPort, targetHost);
30
+ socket.pipe(target);
31
+ target.pipe(socket);
32
+ socket.on('close', () => target.destroy());
33
+ target.on('close', () => socket.destroy());
34
+ target.on('error', (err) => {
35
+ console.error('Proxy target error:', err);
36
+ socket.destroy();
37
+ });
38
+ },
39
+
40
+ /**
41
+ * Line-based protocol handler
42
+ */
43
+ lineProtocol: (handler: (line: string, socket: plugins.net.Socket) => void) => (socket: plugins.net.Socket, context: IRouteContext) => {
44
+ let buffer = '';
45
+ socket.on('data', (data) => {
46
+ buffer += data.toString();
47
+ const lines = buffer.split('\n');
48
+ buffer = lines.pop() || '';
49
+ lines.forEach(line => {
50
+ if (line.trim()) {
51
+ handler(line.trim(), socket);
52
+ }
53
+ });
54
+ });
55
+ },
56
+
57
+ /**
58
+ * Simple HTTP response handler (for testing)
59
+ */
60
+ httpResponse: (statusCode: number, body: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
61
+ const response = [
62
+ `HTTP/1.1 ${statusCode} ${statusCode === 200 ? 'OK' : 'Error'}`,
63
+ 'Content-Type: text/plain',
64
+ `Content-Length: ${body.length}`,
65
+ 'Connection: close',
66
+ '',
67
+ body
68
+ ].join('\r\n');
69
+
70
+ socket.write(response);
71
+ socket.end();
72
+ },
73
+
74
+ /**
75
+ * Block connection immediately
76
+ */
77
+ block: (message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
78
+ const finalMessage = message || `Connection blocked from ${context.clientIp}`;
79
+ if (finalMessage) {
80
+ socket.write(finalMessage);
81
+ }
82
+ socket.end();
83
+ },
84
+
85
+ /**
86
+ * HTTP block response
87
+ */
88
+ httpBlock: (statusCode: number = 403, message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => {
89
+ const defaultMessage = `Access forbidden for ${context.domain || context.clientIp}`;
90
+ const finalMessage = message || defaultMessage;
91
+
92
+ const response = [
93
+ `HTTP/1.1 ${statusCode} ${finalMessage}`,
94
+ 'Content-Type: text/plain',
95
+ `Content-Length: ${finalMessage.length}`,
96
+ 'Connection: close',
97
+ '',
98
+ finalMessage
99
+ ].join('\r\n');
100
+
101
+ socket.write(response);
102
+ socket.end();
103
+ },
104
+
105
+ /**
106
+ * HTTP redirect handler
107
+ * Uses the centralized detection module for HTTP parsing
108
+ */
109
+ httpRedirect: (locationTemplate: string, statusCode: number = 301) => (socket: plugins.net.Socket, context: IRouteContext) => {
110
+ const tracker = createSocketTracker(socket);
111
+ const connectionId = ProtocolDetector.createConnectionId({
112
+ socketId: context.connectionId || `${Date.now()}-${Math.random()}`
113
+ });
114
+
115
+ const handleData = async (data: Buffer) => {
116
+ // Use detection module for parsing
117
+ const detectionResult = await ProtocolDetector.detectWithConnectionTracking(
118
+ data,
119
+ connectionId,
120
+ { extractFullHeaders: false } // We only need method and path
121
+ );
122
+
123
+ if (detectionResult.protocol === 'http' && detectionResult.connectionInfo.path) {
124
+ const method = detectionResult.connectionInfo.method || 'GET';
125
+ const path = detectionResult.connectionInfo.path || '/';
126
+
127
+ const domain = context.domain || 'localhost';
128
+ const port = context.port;
129
+
130
+ let finalLocation = locationTemplate
131
+ .replace('{domain}', domain)
132
+ .replace('{port}', String(port))
133
+ .replace('{path}', path)
134
+ .replace('{clientIp}', context.clientIp);
135
+
136
+ const message = `Redirecting to ${finalLocation}`;
137
+ const response = [
138
+ `HTTP/1.1 ${statusCode} ${statusCode === 301 ? 'Moved Permanently' : 'Found'}`,
139
+ `Location: ${finalLocation}`,
140
+ 'Content-Type: text/plain',
141
+ `Content-Length: ${message.length}`,
142
+ 'Connection: close',
143
+ '',
144
+ message
145
+ ].join('\r\n');
146
+
147
+ socket.write(response);
148
+ } else {
149
+ // Not a valid HTTP request, close connection
150
+ socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
151
+ }
152
+
153
+ socket.end();
154
+ // Clean up detection state
155
+ ProtocolDetector.cleanupConnections();
156
+ // Clean up all tracked resources
157
+ tracker.cleanup();
158
+ };
159
+
160
+ // Use tracker to manage the listener
161
+ socket.once('data', handleData);
162
+
163
+ tracker.addListener('error', (err) => {
164
+ tracker.safeDestroy(err);
165
+ });
166
+
167
+ tracker.addListener('close', () => {
168
+ tracker.cleanup();
169
+ });
170
+ },
171
+
172
+ /**
173
+ * HTTP server handler for ACME challenges and other HTTP needs
174
+ * Uses the centralized detection module for HTTP parsing
175
+ */
176
+ 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) => {
177
+ const tracker = createSocketTracker(socket);
178
+ let requestParsed = false;
179
+ let responseTimer: NodeJS.Timeout | null = null;
180
+ const connectionId = ProtocolDetector.createConnectionId({
181
+ socketId: context.connectionId || `${Date.now()}-${Math.random()}`
182
+ });
183
+
184
+ const processData = async (data: Buffer) => {
185
+ if (requestParsed) return; // Only handle the first request
186
+
187
+ // Use HttpDetector for parsing
188
+ const detectionResult = await ProtocolDetector.detectWithConnectionTracking(
189
+ data,
190
+ connectionId,
191
+ { extractFullHeaders: true }
192
+ );
193
+
194
+ if (detectionResult.protocol !== 'http' || !detectionResult.isComplete) {
195
+ // Not a complete HTTP request yet
196
+ return;
197
+ }
198
+
199
+ requestParsed = true;
200
+ // Remove data listener after parsing request
201
+ socket.removeListener('data', processData);
202
+ const connInfo = detectionResult.connectionInfo;
203
+
204
+ // Create request object from detection result
205
+ const req = {
206
+ method: connInfo.method || 'GET',
207
+ url: connInfo.path || '/',
208
+ headers: connInfo.headers || {},
209
+ body: detectionResult.remainingBuffer?.toString() || ''
210
+ };
211
+
212
+ // Create response object
213
+ let statusCode = 200;
214
+ const responseHeaders: Record<string, string> = {};
215
+ let ended = false;
216
+
217
+ const res = {
218
+ status: (code: number) => {
219
+ statusCode = code;
220
+ },
221
+ header: (name: string, value: string) => {
222
+ responseHeaders[name] = value;
223
+ },
224
+ send: (data: string) => {
225
+ if (ended) return;
226
+ ended = true;
227
+
228
+ // Clear response timer since we're sending now
229
+ if (responseTimer) {
230
+ clearTimeout(responseTimer);
231
+ responseTimer = null;
232
+ }
233
+
234
+ if (!responseHeaders['content-type']) {
235
+ responseHeaders['content-type'] = 'text/plain';
236
+ }
237
+ responseHeaders['content-length'] = String(data.length);
238
+ responseHeaders['connection'] = 'close';
239
+
240
+ const statusText = statusCode === 200 ? 'OK' :
241
+ statusCode === 404 ? 'Not Found' :
242
+ statusCode === 500 ? 'Internal Server Error' : 'Response';
243
+
244
+ let response = `HTTP/1.1 ${statusCode} ${statusText}\r\n`;
245
+ for (const [name, value] of Object.entries(responseHeaders)) {
246
+ response += `${name}: ${value}\r\n`;
247
+ }
248
+ response += '\r\n';
249
+ response += data;
250
+
251
+ socket.write(response);
252
+ socket.end();
253
+ },
254
+ end: () => {
255
+ if (ended) return;
256
+ ended = true;
257
+ socket.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n');
258
+ socket.end();
259
+ }
260
+ };
261
+
262
+ try {
263
+ handler(req, res);
264
+ // Ensure response is sent even if handler doesn't call send()
265
+ responseTimer = setTimeout(() => {
266
+ if (!ended) {
267
+ res.send('');
268
+ }
269
+ responseTimer = null;
270
+ }, 1000);
271
+ // Track and unref the timer
272
+ tracker.addTimer(responseTimer);
273
+ } catch (error) {
274
+ if (!ended) {
275
+ res.status(500);
276
+ res.send('Internal Server Error');
277
+ }
278
+ // Use safeDestroy for error cases
279
+ tracker.safeDestroy(error instanceof Error ? error : new Error('Handler error'));
280
+ }
281
+ };
282
+
283
+ // Use tracker to manage listeners
284
+ tracker.addListener('data', processData);
285
+
286
+ tracker.addListener('error', (err) => {
287
+ if (!requestParsed) {
288
+ tracker.safeDestroy(err);
289
+ }
290
+ });
291
+
292
+ tracker.addListener('close', () => {
293
+ // Clear any pending response timer
294
+ if (responseTimer) {
295
+ clearTimeout(responseTimer);
296
+ responseTimer = null;
297
+ }
298
+ // Clean up detection state
299
+ ProtocolDetector.cleanupConnections();
300
+ // Clean up all tracked resources
301
+ tracker.cleanup();
302
+ });
303
+ }
304
+ };
305
+
306
+ /**
307
+ * Create a socket handler route configuration
308
+ * @param domains Domain(s) to match
309
+ * @param ports Port(s) to listen on
310
+ * @param handler Socket handler function
311
+ * @param options Additional route options
312
+ * @returns Route configuration object
313
+ */
314
+ export function createSocketHandlerRoute(
315
+ domains: string | string[],
316
+ ports: TPortRange,
317
+ handler: (socket: plugins.net.Socket) => void | Promise<void>,
318
+ options: {
319
+ name?: string;
320
+ priority?: number;
321
+ path?: string;
322
+ } = {}
323
+ ): IRouteConfig {
324
+ return {
325
+ name: options.name || 'socket-handler-route',
326
+ priority: options.priority !== undefined ? options.priority : 50,
327
+ match: {
328
+ domains,
329
+ ports,
330
+ ...(options.path && { path: options.path })
331
+ },
332
+ action: {
333
+ type: 'socket-handler',
334
+ socketHandler: handler
335
+ }
336
+ };
337
+ }