@push.rocks/smartproxy 19.4.2 → 19.5.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 (32) 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/smart-proxy/certificate-manager.js +30 -25
  5. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +9 -40
  6. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  7. package/dist_ts/proxies/smart-proxy/route-connection-handler.d.ts +2 -10
  8. package/dist_ts/proxies/smart-proxy/route-connection-handler.js +69 -43
  9. package/dist_ts/proxies/smart-proxy/utils/index.d.ts +2 -2
  10. package/dist_ts/proxies/smart-proxy/utils/index.js +3 -3
  11. package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +61 -20
  12. package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +240 -45
  13. package/dist_ts/proxies/smart-proxy/utils/route-patterns.d.ts +0 -18
  14. package/dist_ts/proxies/smart-proxy/utils/route-patterns.js +4 -43
  15. package/dist_ts/proxies/smart-proxy/utils/route-utils.js +14 -15
  16. package/dist_ts/proxies/smart-proxy/utils/route-validators.js +10 -31
  17. package/package.json +7 -7
  18. package/readme.hints.md +38 -1
  19. package/readme.plan.md +314 -382
  20. package/readme.plan2.md +764 -0
  21. package/ts/00_commitinfo_data.ts +1 -1
  22. package/ts/proxies/http-proxy/handlers/index.ts +1 -2
  23. package/ts/proxies/smart-proxy/certificate-manager.ts +29 -23
  24. package/ts/proxies/smart-proxy/models/route-types.ts +12 -56
  25. package/ts/proxies/smart-proxy/route-connection-handler.ts +73 -60
  26. package/ts/proxies/smart-proxy/utils/index.ts +0 -2
  27. package/ts/proxies/smart-proxy/utils/route-helpers.ts +278 -61
  28. package/ts/proxies/smart-proxy/utils/route-patterns.ts +6 -56
  29. package/ts/proxies/smart-proxy/utils/route-utils.ts +12 -15
  30. package/ts/proxies/smart-proxy/utils/route-validators.ts +9 -31
  31. package/ts/proxies/http-proxy/handlers/redirect-handler.ts +0 -105
  32. package/ts/proxies/http-proxy/handlers/static-handler.ts +0 -261
package/readme.plan.md CHANGED
@@ -1,384 +1,316 @@
1
1
  # SmartProxy Development Plan
2
2
 
3
- ## ACME Route Port Binding Intelligence Improvement
4
-
5
- ### Problem Statement
6
- Currently, SmartProxy has an issue with port binding conflicts between regular routes and ACME challenge routes. While SmartProxy is designed to support multiple routes sharing the same port (differentiated by host, path, etc.), there's a specific conflict when adding ACME challenge routes to a port that is already in use by other routes.
7
-
8
- This results in the error: `Port 80 is already in use for ACME challenges` when SmartProxy tries to bind the ACME challenge route to a port that it's already using.
9
-
10
- ### Root Cause Analysis
11
- 1. **Double Binding Attempt**: SmartProxy tries to bind to port 80 twice - once for application routes and once for ACME challenge routes.
12
- 2. **Overlapping Route Updates**: When adding a challenge route, it triggers a port binding operation without checking if the port is already bound.
13
- 3. **Naive Error Handling**: The code detects EADDRINUSE but doesn't distinguish between external conflicts and internal conflicts.
14
- 4. **Port Binding Semantics**: The port manager doesn't recognize that a port already bound by SmartProxy can be reused for additional routes.
15
-
16
- ### Solution Architecture
17
- We need a more intelligent approach to port binding that understands when a port can be shared between routes vs. when a new binding is needed:
18
-
19
- 1. **Port Binding Awareness**: Track what ports are already bound by SmartProxy itself.
20
- 2. **Smart Route Updates**: Only attempt to bind to ports that aren't already bound by SmartProxy.
21
- 3. **Route Merging Logic**: When adding ACME challenge routes, merge them with existing routes on the same ports.
22
- 4. **Dynamic Port Management**: Release port bindings when no routes are using them and rebind when needed.
23
- 5. **Improved Error Recovery**: Handle port conflicts gracefully, with distinct handling for internal vs. external conflicts.
24
-
25
- ### Implementation Plan
26
-
27
- #### Phase 1: Improve Port Manager Intelligence
28
- - [x] Enhance `PortManager` to distinguish between ports that need new bindings vs ports that can reuse existing bindings
29
- - [x] Add an internal tracking mechanism to detect when a requested port is already bound internally
30
- - [x] Modify port addition logic to skip binding operations for ports already bound by SmartProxy
31
- - [x] Implement reference counting for port bindings to track how many routes use each port
32
- - [x] Add logic to release port bindings when no routes are using them anymore
33
- - [x] Update error handling to provide more context for port binding failures
34
-
35
- #### Phase 2: Refine ACME Challenge Route Integration
36
- - [x] Modify `addChallengeRoute()` to check if the port is already in use by SmartProxy
37
- - [x] Ensure route updates don't trigger unnecessary port binding operations
38
- - [x] Implement a merging strategy for ACME routes with existing routes on the same port
39
- - [x] Add diagnostic logging to track route and port binding relationships
40
-
41
- #### Phase 3: Enhance Proxy Route Management
42
- - [x] Restructure route update process to group routes by port
43
- - [x] Implement a more efficient route update mechanism that minimizes port binding operations
44
- - [x] Develop port lifecycle management to track usage across route changes
45
- - [x] Add validation to detect potential binding conflicts before attempting operations
46
- - [ ] Create a proper route dependency graph to understand the relationships between routes
47
- - [x] Implement efficient detection of "orphaned" ports that no longer have associated routes
48
-
49
- #### Phase 4: Improve Error Handling and Recovery
50
- - [x] Enhance error messages to be more specific about the nature of port conflicts
51
- - [x] Add recovery mechanisms for common port binding scenarios
52
- - [ ] Implement a fallback port selection strategy for ACME challenges
53
- - [x] Create a more robust validation system to catch issues before they cause runtime errors
54
-
55
- ### Detailed Technical Tasks
56
-
57
- #### Phase 1: Improve Port Manager Intelligence
58
- 1. Modify `/ts/proxies/smart-proxy/port-manager.ts`:
59
- - Add a new method `isPortBoundBySmartProxy(port: number): boolean`
60
- - Refactor `addPort()` to check if the port is already bound
61
- - Update `updatePorts()` to be more intelligent about which ports need binding
62
- - Add reference counting for port usage
63
-
64
- 2. Implement Port Reference Counting:
65
- ```typescript
66
- // Add to PortManager class
67
- private portRefCounts: Map<number, number> = new Map();
68
-
69
- public incrementPortRefCount(port: number): void {
70
- const currentCount = this.portRefCounts.get(port) || 0;
71
- this.portRefCounts.set(port, currentCount + 1);
72
- logger.log('debug', `Port ${port} reference count increased to ${currentCount + 1}`, { port, refCount: currentCount + 1 });
73
- }
74
-
75
- public decrementPortRefCount(port: number): number {
76
- const currentCount = this.portRefCounts.get(port) || 0;
77
- if (currentCount <= 0) {
78
- logger.log('warn', `Attempted to decrement reference count for port ${port} below zero`, { port });
79
- return 0;
80
- }
81
-
82
- const newCount = currentCount - 1;
83
- this.portRefCounts.set(port, newCount);
84
- logger.log('debug', `Port ${port} reference count decreased to ${newCount}`, { port, refCount: newCount });
85
- return newCount;
86
- }
87
-
88
- public getPortRefCount(port: number): number {
89
- return this.portRefCounts.get(port) || 0;
90
- }
91
- ```
92
-
93
- 3. Port Binding Logic Enhancements:
94
- ```typescript
95
- public async addPort(port: number): Promise<void> {
96
- // If already bound by this instance, just increment ref count and return
97
- if (this.servers.has(port)) {
98
- this.incrementPortRefCount(port);
99
- logger.log('debug', `Port ${port} is already bound by SmartProxy, reusing binding`, { port });
100
- return;
101
- }
102
-
103
- // Initialize ref count for new port
104
- this.portRefCounts.set(port, 1);
105
-
106
- // Continue with normal binding...
107
- }
108
-
109
- public async removePort(port: number): Promise<void> {
110
- // Decrement reference count
111
- const newCount = this.decrementPortRefCount(port);
112
-
113
- // If port is still in use by other routes, keep it
114
- if (newCount > 0) {
115
- logger.log('debug', `Port ${port} still in use by ${newCount} routes, keeping binding open`, { port, refCount: newCount });
116
- return;
117
- }
118
-
119
- // No more references, can actually close the port
120
- const server = this.servers.get(port);
121
- if (!server) {
122
- logger.log('warn', `Port ${port} not found in servers map`, { port });
123
- return;
124
- }
125
-
126
- // Continue with normal unbinding logic...
127
- }
128
- ```
129
-
130
- 4. Add Smarter Port Conflict Detection:
131
- ```typescript
132
- private isPortConflict(error: any): { isConflict: boolean; isExternal: boolean } {
133
- if (error.code !== 'EADDRINUSE') {
134
- return { isConflict: false, isExternal: false };
135
- }
136
-
137
- // Check if we already have this port
138
- const isBoundInternally = this.servers.has(Number(error.port));
139
- return { isConflict: true, isExternal: !isBoundInternally };
140
- }
141
- ```
142
-
143
- #### Phase 2: Refine ACME Challenge Route Integration
144
- 1. Modify `/ts/proxies/smart-proxy/certificate-manager.ts`:
145
- - Enhance `addChallengeRoute()` to be aware of existing port bindings
146
- - Add port verification before attempting to add challenge routes
147
-
148
- 2. Smart Route Merging Logic:
149
- ```typescript
150
- private async addChallengeRoute(): Promise<void> {
151
- // Check if route is already active
152
- if (this.challengeRouteActive) {
153
- return;
154
- }
155
-
156
- // Create challenge route
157
- const challengeRoute = this.challengeRoute;
158
- const challengePort = this.globalAcmeDefaults?.port || 80;
159
-
160
- // Check if port is already in use by another route
161
- const portAlreadyUsed = this.routes.some(r =>
162
- Array.isArray(r.match.ports)
163
- ? r.match.ports.includes(challengePort)
164
- : r.match.ports === challengePort
165
- );
166
-
167
- if (portAlreadyUsed) {
168
- logger.log('info', `Port ${challengePort} is already used by an existing route, merging ACME challenge route`);
169
- }
170
-
171
- // Continue with route update...
172
- }
173
- ```
174
-
175
- 3. Update Route Manager Communication:
176
- ```typescript
177
- // Add this method to smart-proxy.ts
178
- private async addRouteWithoutRebinding(route: IRouteConfig): Promise<void> {
179
- // Add route to configuration without triggering a port rebind
180
- this.settings.routes.push(route);
181
- this.routeManager.updateRoutes(this.settings.routes);
182
-
183
- // Update HttpProxy if needed, but skip port binding updates
184
- if (this.httpProxyBridge.getHttpProxy()) {
185
- await this.httpProxyBridge.syncRoutesToHttpProxy(this.settings.routes);
186
- }
187
- }
188
- ```
189
-
190
- #### Phase 3: Enhance Proxy Route Management
191
- 1. Modify `/ts/proxies/smart-proxy/smart-proxy.ts`:
192
- - Refactor `updateRoutes()` to group routes by port
193
- - Implement incremental updates that preserve port bindings
194
- - Add orphaned port detection and cleanup
195
-
196
- 2. Group Routes by Port:
197
- ```typescript
198
- private groupRoutesByPort(routes: IRouteConfig[]): Map<number, IRouteConfig[]> {
199
- const portMap = new Map<number, IRouteConfig[]>();
200
-
201
- for (const route of routes) {
202
- const ports = Array.isArray(route.match.ports)
203
- ? route.match.ports
204
- : [route.match.ports];
205
-
206
- for (const port of ports) {
207
- if (!portMap.has(port)) {
208
- portMap.set(port, []);
209
- }
210
- portMap.get(port)!.push(route);
211
- }
212
- }
213
-
214
- return portMap;
215
- }
216
- ```
217
-
218
- 3. Implement Port Usage Tracking:
219
- ```typescript
220
- private updatePortUsageMap(routes: IRouteConfig[]): Map<number, Set<string>> {
221
- // Map of port -> Set of route names using that port
222
- const portUsage = new Map<number, Set<string>>();
223
-
224
- for (const route of routes) {
225
- const ports = Array.isArray(route.match.ports)
226
- ? route.match.ports
227
- : [route.match.ports];
228
-
229
- const routeName = route.name || `unnamed_${Math.random().toString(36).substring(2, 9)}`;
230
-
231
- for (const port of ports) {
232
- if (!portUsage.has(port)) {
233
- portUsage.set(port, new Set());
234
- }
235
- portUsage.get(port)!.add(routeName);
236
- }
237
- }
238
-
239
- return portUsage;
240
- }
241
-
242
- private findOrphanedPorts(oldUsage: Map<number, Set<string>>, newUsage: Map<number, Set<string>>): number[] {
243
- // Find ports that have no routes in new configuration
244
- const orphanedPorts: number[] = [];
245
-
246
- for (const [port, routes] of oldUsage.entries()) {
247
- if (!newUsage.has(port) || newUsage.get(port)!.size === 0) {
248
- orphanedPorts.push(port);
249
- logger.log('info', `Port ${port} no longer has any associated routes, will be released`, { port });
250
- }
251
- }
252
-
253
- return orphanedPorts;
254
- }
255
- ```
256
-
257
- 4. Implement Incremental Update Logic:
258
- ```typescript
259
- public async updateRoutesIncremental(newRoutes: IRouteConfig[]): Promise<void> {
260
- // Track port usage before and after update
261
- const oldPortUsage = this.updatePortUsageMap(this.settings.routes);
262
- const newPortUsage = this.updatePortUsageMap(newRoutes);
263
-
264
- // Find orphaned ports - ports that no longer have any routes
265
- const orphanedPorts = this.findOrphanedPorts(oldPortUsage, newPortUsage);
266
-
267
- // Ports that need new bindings - not in old configuration
268
- const newBindingPorts = [...newPortUsage.keys()].filter(p => !oldPortUsage.has(p));
269
-
270
- // Close orphaned ports
271
- if (orphanedPorts.length > 0) {
272
- logger.log('info', `Releasing ${orphanedPorts.length} orphaned ports: ${orphanedPorts.join(', ')}`, { ports: orphanedPorts });
273
- await this.portManager.removePorts(orphanedPorts);
274
- }
275
-
276
- // Bind to new ports
277
- if (newBindingPorts.length > 0) {
278
- logger.log('info', `Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`, { ports: newBindingPorts });
279
- await this.portManager.addPorts(newBindingPorts);
280
- }
281
-
282
- // Update route configuration
283
- this.settings.routes = newRoutes;
284
- this.routeManager.updateRoutes(newRoutes);
285
-
286
- // Update other components...
287
- }
288
- ```
289
-
290
- #### Phase 4: Improve Error Handling and Recovery
291
- 1. Enhance Error Reporting:
292
- ```typescript
293
- private handlePortBindingError(port: number, error: any): void {
294
- if (error.code === 'EADDRINUSE') {
295
- const isInternalConflict = this.portManager.isPortBoundBySmartProxy(port);
296
- if (isInternalConflict) {
297
- logger.log('warn', `Port ${port} is already bound by SmartProxy. This is likely a route configuration issue.`, { port });
298
- } else {
299
- logger.log('error', `Port ${port} is in use by another application. Please choose a different port.`, { port });
300
- }
301
- } else {
302
- logger.log('error', `Failed to bind to port ${port}: ${error.message}`, { port, error });
303
- }
304
- }
305
- ```
306
-
307
- 2. Implement ACME Port Fallback Strategy:
308
- ```typescript
309
- private async selectAcmePort(): Promise<number> {
310
- const preferredPort = this.globalAcmeDefaults?.port || 80;
311
-
312
- // Check if preferred port is already bound internally
313
- if (this.portManager.isPortBoundBySmartProxy(preferredPort)) {
314
- // We can use it without a new binding
315
- return preferredPort;
316
- }
317
-
318
- // Try to bind to preferred port
319
- try {
320
- // Temporary test binding
321
- const server = plugins.net.createServer();
322
- await new Promise<void>((resolve, reject) => {
323
- server.listen(preferredPort, () => {
324
- server.close();
325
- resolve();
326
- }).on('error', reject);
327
- });
328
-
329
- // If we get here, port is available
330
- return preferredPort;
331
- } catch (error) {
332
- if (error.code === 'EADDRINUSE') {
333
- // Port is unavailable, try fallback ports
334
- for (const fallbackPort of [8080, 8081, 8082, 8083, 8084]) {
335
- try {
336
- // Test if we can bind to fallback
337
- const server = plugins.net.createServer();
338
- await new Promise<void>((resolve, reject) => {
339
- server.listen(fallbackPort, () => {
340
- server.close();
341
- resolve();
342
- }).on('error', reject);
343
- });
344
-
345
- logger.log('warn', `Primary ACME port ${preferredPort} is unavailable, using fallback port ${fallbackPort}`);
346
- return fallbackPort;
347
- } catch {
348
- // Try next fallback
349
- }
350
- }
351
- }
352
-
353
- // All attempts failed
354
- throw new Error(`Could not find an available port for ACME challenges`);
355
- }
356
- }
357
- ```
358
-
359
- ### Testing Strategy
360
- 1. **Unit Tests**:
361
- - Test port binding intelligence
362
- - Test route merging logic
363
- - Test error handling mechanisms
364
- - Test port reference counting
365
- - Test orphaned port detection and cleanup
366
-
367
- 2. **Integration Tests**:
368
- - Test multiple routes on the same port
369
- - Test ACME challenges on ports with existing routes
370
- - Test dynamic route addition and removal
371
- - Test port lifecycle (bind → share → release)
372
- - Test various recovery scenarios
373
-
374
- 3. **Stress Tests**:
375
- - Test rapid route updates
376
- - Test concurrent operations
377
- - Test large scale route changes (add/remove many at once)
378
- - Test frequent changes to see if ports are properly released
379
- - Test recovery from port conflicts
380
-
381
- ### Release Plan
382
- 1. **19.4.0** - Phase 1 & 2: Port Manager and ACME Route Improvements
383
- 2. **19.5.0** - Phase 3: Enhanced Route Management
384
- 3. **19.6.0** - Phase 4: Improved Error Handling and Recovery
3
+ ## Implementation Plan: Socket Handler Function Support (Simplified) ✅ COMPLETED
4
+
5
+ ### Overview
6
+ Add support for custom socket handler functions with the simplest possible API - just pass a function that receives the socket.
7
+
8
+ ### User Experience Goal
9
+ ```typescript
10
+ const proxy = new SmartProxy({
11
+ routes: [{
12
+ name: 'my-custom-protocol',
13
+ match: { ports: 9000, domains: 'custom.example.com' },
14
+ action: {
15
+ type: 'socket-handler',
16
+ socketHandler: (socket) => {
17
+ // User has full control of the socket
18
+ socket.write('Welcome!\n');
19
+ socket.on('data', (data) => {
20
+ socket.write(`Echo: ${data}`);
21
+ });
22
+ }
23
+ }
24
+ }]
25
+ });
26
+ ```
27
+
28
+ That's it. Simple and powerful.
29
+
30
+ ---
31
+
32
+ ## Phase 1: Minimal Type Changes
33
+
34
+ ### 1.1 Add Socket Handler Action Type
35
+ **File:** `ts/proxies/smart-proxy/models/route-types.ts`
36
+
37
+ ```typescript
38
+ // Update action type
39
+ export type TRouteActionType = 'forward' | 'redirect' | 'block' | 'static' | 'socket-handler';
40
+
41
+ // Add simple socket handler type
42
+ export type TSocketHandler = (socket: net.Socket) => void | Promise<void>;
43
+
44
+ // Extend IRouteAction
45
+ export interface IRouteAction {
46
+ // ... existing properties
47
+
48
+ // Socket handler function (when type is 'socket-handler')
49
+ socketHandler?: TSocketHandler;
50
+ }
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Phase 2: Simple Implementation
56
+
57
+ ### 2.1 Update Route Connection Handler
58
+ **File:** `ts/proxies/smart-proxy/route-connection-handler.ts`
59
+
60
+ In the `handleConnection` method, add handling for socket-handler:
61
+
62
+ ```typescript
63
+ // After route matching...
64
+ if (matchedRoute) {
65
+ const action = matchedRoute.action;
66
+
67
+ if (action.type === 'socket-handler') {
68
+ if (!action.socketHandler) {
69
+ logger.error('socket-handler action missing socketHandler function');
70
+ socket.destroy();
71
+ return;
72
+ }
73
+
74
+ try {
75
+ // Simply call the handler with the socket
76
+ const result = action.socketHandler(socket);
77
+
78
+ // If it returns a promise, handle errors
79
+ if (result instanceof Promise) {
80
+ result.catch(error => {
81
+ logger.error('Socket handler error:', error);
82
+ if (!socket.destroyed) {
83
+ socket.destroy();
84
+ }
85
+ });
86
+ }
87
+ } catch (error) {
88
+ logger.error('Socket handler error:', error);
89
+ if (!socket.destroyed) {
90
+ socket.destroy();
91
+ }
92
+ }
93
+
94
+ return; // Done - user has control now
95
+ }
96
+
97
+ // ... rest of existing action handling
98
+ }
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Phase 3: Optional Context (If Needed)
104
+
105
+ If users need more info, we can optionally pass a minimal context as a second parameter:
106
+
107
+ ```typescript
108
+ export type TSocketHandler = (
109
+ socket: net.Socket,
110
+ context?: {
111
+ route: IRouteConfig;
112
+ clientIp: string;
113
+ localPort: number;
114
+ }
115
+ ) => void | Promise<void>;
116
+ ```
117
+
118
+ Usage:
119
+ ```typescript
120
+ socketHandler: (socket, context) => {
121
+ console.log(`Connection from ${context.clientIp} to port ${context.localPort}`);
122
+ // Handle socket...
123
+ }
124
+ ```
125
+
126
+ ---
127
+
128
+ ## Phase 4: Helper Utilities (Optional)
129
+
130
+ ### 4.1 Common Patterns
131
+ **File:** `ts/proxies/smart-proxy/utils/route-helpers.ts`
132
+
133
+ ```typescript
134
+ // Simple helper to create socket handler routes
135
+ export function createSocketHandlerRoute(
136
+ domains: string | string[],
137
+ ports: TPortRange,
138
+ handler: TSocketHandler,
139
+ options?: { name?: string; priority?: number }
140
+ ): IRouteConfig {
141
+ return {
142
+ name: options?.name || 'socket-handler-route',
143
+ priority: options?.priority || 50,
144
+ match: { domains, ports },
145
+ action: {
146
+ type: 'socket-handler',
147
+ socketHandler: handler
148
+ }
149
+ };
150
+ }
151
+
152
+ // Pre-built handlers for common cases
153
+ export const SocketHandlers = {
154
+ // Simple echo server
155
+ echo: (socket: net.Socket) => {
156
+ socket.on('data', data => socket.write(data));
157
+ },
158
+
159
+ // TCP proxy
160
+ proxy: (targetHost: string, targetPort: number) => (socket: net.Socket) => {
161
+ const target = net.connect(targetPort, targetHost);
162
+ socket.pipe(target);
163
+ target.pipe(socket);
164
+ socket.on('close', () => target.destroy());
165
+ target.on('close', () => socket.destroy());
166
+ },
167
+
168
+ // Line-based protocol
169
+ lineProtocol: (handler: (line: string, socket: net.Socket) => void) => (socket: net.Socket) => {
170
+ let buffer = '';
171
+ socket.on('data', (data) => {
172
+ buffer += data.toString();
173
+ const lines = buffer.split('\n');
174
+ buffer = lines.pop() || '';
175
+ lines.forEach(line => handler(line, socket));
176
+ });
177
+ }
178
+ };
179
+ ```
180
+
181
+ ---
182
+
183
+ ## Usage Examples
184
+
185
+ ### Example 1: Custom Protocol
186
+ ```typescript
187
+ {
188
+ name: 'custom-protocol',
189
+ match: { ports: 9000 },
190
+ action: {
191
+ type: 'socket-handler',
192
+ socketHandler: (socket) => {
193
+ socket.write('READY\n');
194
+ socket.on('data', (data) => {
195
+ const cmd = data.toString().trim();
196
+ if (cmd === 'PING') socket.write('PONG\n');
197
+ else if (cmd === 'QUIT') socket.end();
198
+ else socket.write('ERROR: Unknown command\n');
199
+ });
200
+ }
201
+ }
202
+ }
203
+ ```
204
+
205
+ ### Example 2: Simple TCP Proxy
206
+ ```typescript
207
+ {
208
+ name: 'tcp-proxy',
209
+ match: { ports: 8080, domains: 'proxy.example.com' },
210
+ action: {
211
+ type: 'socket-handler',
212
+ socketHandler: SocketHandlers.proxy('backend.local', 3000)
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### Example 3: WebSocket with Custom Auth
218
+ ```typescript
219
+ {
220
+ name: 'custom-websocket',
221
+ match: { ports: [80, 443], path: '/ws' },
222
+ action: {
223
+ type: 'socket-handler',
224
+ socketHandler: async (socket) => {
225
+ // Read HTTP headers
226
+ const headers = await readHttpHeaders(socket);
227
+
228
+ // Custom auth check
229
+ if (!headers.authorization || !validateToken(headers.authorization)) {
230
+ socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
231
+ socket.end();
232
+ return;
233
+ }
234
+
235
+ // Proceed with WebSocket upgrade
236
+ const ws = new WebSocket(socket, headers);
237
+ // ... handle WebSocket
238
+ }
239
+ }
240
+ }
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Benefits of This Approach
246
+
247
+ 1. **Dead Simple API**: Just pass a function that gets the socket
248
+ 2. **No New Classes**: No ForwardingHandler subclass needed
249
+ 3. **Minimal Changes**: Only touches type definitions and one handler method
250
+ 4. **Full Power**: Users have complete control over the socket
251
+ 5. **Backward Compatible**: No changes to existing functionality
252
+ 6. **Easy to Test**: Just test the socket handler functions directly
253
+
254
+ ---
255
+
256
+ ## Implementation Steps
257
+
258
+ 1. Add `'socket-handler'` to `TRouteActionType` (5 minutes)
259
+ 2. Add `socketHandler?: TSocketHandler` to `IRouteAction` (5 minutes)
260
+ 3. Add socket-handler case in `RouteConnectionHandler.handleConnection()` (15 minutes)
261
+ 4. Add helper functions (optional, 30 minutes)
262
+ 5. Write tests (2 hours)
263
+ 6. Update documentation (1 hour)
264
+
265
+ **Total implementation time: ~4 hours** (vs 6 weeks for the complex version)
266
+
267
+ ---
268
+
269
+ ## What We're NOT Doing
270
+
271
+ - Creating new ForwardingHandler classes
272
+ - Complex context objects with utils
273
+ - ❌ HTTP request handling for socket handlers
274
+ - ❌ Complex protocol detection mechanisms
275
+ - ❌ Middleware patterns
276
+ - Lifecycle hooks
277
+
278
+ Keep it simple. The user just wants to handle a socket.
279
+
280
+ ---
281
+
282
+ ## Success Criteria
283
+
284
+ - ✅ Users can define a route with `type: 'socket-handler'`
285
+ - ✅ Users can provide a function that receives the socket
286
+ - The function is called when a connection matches the route
287
+ - ✅ Error handling prevents crashes
288
+ - ✅ No performance impact on existing routes
289
+ - ✅ Clean, simple API that's easy to understand
290
+
291
+ ---
292
+
293
+ ## Implementation Notes (Completed)
294
+
295
+ ### What Was Implemented
296
+ 1. **Type Definitions** - Added 'socket-handler' to TRouteActionType and TSocketHandler type
297
+ 2. **Route Handler** - Added socket-handler case in RouteConnectionHandler switch statement
298
+ 3. **Error Handling** - Both sync and async errors are caught and logged
299
+ 4. **Initial Data Handling** - Initial chunks are re-emitted to handler's listeners
300
+ 5. **Helper Functions** - Added createSocketHandlerRoute and pre-built handlers (echo, proxy, etc.)
301
+ 6. **Full Test Coverage** - All test cases pass including async handlers and error handling
302
+
303
+ ### Key Implementation Details
304
+ - Socket handlers require initial data from client to trigger routing (not TLS handshake)
305
+ - The handler receives the raw socket after route matching
306
+ - Both sync and async handlers are supported
307
+ - Errors in handlers terminate the connection gracefully
308
+ - Helper utilities provide common patterns (echo server, TCP proxy, line protocol)
309
+
310
+ ### Usage Notes
311
+ - Clients must send initial data to trigger the handler (even just a newline)
312
+ - The socket is passed directly to the handler function
313
+ - Handler has complete control over the socket lifecycle
314
+ - No special context object needed - keeps it simple
315
+
316
+ **Total implementation time: ~3 hours**