# SmartProxy Simplification Plan: Unify Action Types ## Summary Complete removal of 'redirect', 'block', and 'static' action types, leaving only 'forward' and 'socket-handler'. All old code will be deleted entirely - no migration paths or backwards compatibility. Socket handlers will be enhanced to receive IRouteContext as a second parameter. ## Goal Create a dramatically simpler SmartProxy with only two action types, where everything is either proxied (forward) or handled by custom code (socket-handler). ## Current State ```typescript export type TRouteActionType = 'forward' | 'redirect' | 'block' | 'static' | 'socket-handler'; export type TSocketHandler = (socket: plugins.net.Socket) => void | Promise; ``` ## Target State ```typescript export type TRouteActionType = 'forward' | 'socket-handler'; export type TSocketHandler = (socket: plugins.net.Socket, context: IRouteContext) => void | Promise; ``` ## Benefits 1. **Simpler API** - Only two action types to understand 2. **Unified handling** - Everything is either forwarding or custom socket handling 3. **More flexible** - Socket handlers can do anything the old types did and more 4. **Less code** - Remove specialized handlers and their dependencies 5. **Context aware** - Socket handlers get access to route context (domain, port, clientIp, etc.) 6. **Clean codebase** - No legacy code or migration paths --- ## Phase 1: Code to Remove ### 1.1 Action Type Handlers - `RouteConnectionHandler.handleRedirectAction()` - `RouteConnectionHandler.handleBlockAction()` - `RouteConnectionHandler.handleStaticAction()` ### 1.2 Handler Classes - `RedirectHandler` class (http-proxy/handlers/) - `StaticHandler` class (http-proxy/handlers/) ### 1.3 Type Definitions - 'redirect', 'block', 'static' from TRouteActionType - IRouteRedirect interface - IRouteStatic interface - Related properties in IRouteAction ### 1.4 Helper Functions - `createStaticFileRoute()` - Any other helpers that create redirect/block/static routes --- ## Phase 2: Create Predefined Socket Handlers ### 2.1 Block Handler ```typescript export const SocketHandlers = { // ... existing handlers /** * Block connection immediately */ block: (message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => { // Can use context for logging or custom messages const finalMessage = message || `Connection blocked from ${context.clientIp}`; if (finalMessage) { socket.write(finalMessage); } socket.end(); }, /** * HTTP block response */ httpBlock: (statusCode: number = 403, message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => { // Can customize message based on context const defaultMessage = `Access forbidden for ${context.domain || context.clientIp}`; const finalMessage = message || defaultMessage; const response = [ `HTTP/1.1 ${statusCode} ${finalMessage}`, 'Content-Type: text/plain', `Content-Length: ${finalMessage.length}`, 'Connection: close', '', finalMessage ].join('\r\n'); socket.write(response); socket.end(); } }; ``` ### 2.2 Redirect Handler ```typescript export const SocketHandlers = { // ... existing handlers /** * HTTP redirect handler */ httpRedirect: (locationTemplate: string, statusCode: number = 301) => (socket: plugins.net.Socket, context: IRouteContext) => { let buffer = ''; socket.once('data', (data) => { buffer += data.toString(); // Parse HTTP request const lines = buffer.split('\r\n'); const requestLine = lines[0]; const [method, path] = requestLine.split(' '); // Use domain from context (more reliable than Host header) const domain = context.domain || 'localhost'; const port = context.port; // Replace placeholders in location using context let finalLocation = locationTemplate .replace('{domain}', domain) .replace('{port}', String(port)) .replace('{path}', path) .replace('{clientIp}', context.clientIp); const message = `Redirecting to ${finalLocation}`; const response = [ `HTTP/1.1 ${statusCode} ${statusCode === 301 ? 'Moved Permanently' : 'Found'}`, `Location: ${finalLocation}`, 'Content-Type: text/plain', `Content-Length: ${message.length}`, 'Connection: close', '', message ].join('\r\n'); socket.write(response); socket.end(); }); } }; ``` ### 2.3 Benefits of Context in Socket Handlers With routeContext as a second parameter, socket handlers can: - Access client IP for logging or rate limiting - Use domain information for multi-tenant handling - Check if connection is TLS and what version - Access route name/ID for metrics - Build more intelligent responses based on context Example advanced handler: ```typescript const rateLimitHandler = (maxRequests: number) => { const ipCounts = new Map(); return (socket: net.Socket, context: IRouteContext) => { const count = (ipCounts.get(context.clientIp) || 0) + 1; ipCounts.set(context.clientIp, count); if (count > maxRequests) { socket.write(`Rate limit exceeded for ${context.clientIp}\n`); socket.end(); return; } // Process request... }; }; ``` --- ## Phase 3: Update Helper Functions ### 3.1 Update createHttpToHttpsRedirect ```typescript export function createHttpToHttpsRedirect( domains: string | string[], httpsPort: number = 443, options: Partial = {} ): IRouteConfig { return { name: options.name || `HTTP to HTTPS Redirect for ${Array.isArray(domains) ? domains.join(', ') : domains}`, match: { ports: options.match?.ports || 80, domains }, action: { type: 'socket-handler', socketHandler: SocketHandlers.httpRedirect(`https://{domain}:${httpsPort}{path}`, 301) }, ...options }; } ``` ### 3.2 Update createSocketHandlerRoute ```typescript export function createSocketHandlerRoute( domains: string | string[], ports: TPortRange, handler: TSocketHandler, options: { name?: string; priority?: number; path?: string } = {} ): IRouteConfig { return { name: options.name || 'socket-handler-route', priority: options.priority !== undefined ? options.priority : 50, match: { domains, ports, ...(options.path && { path: options.path }) }, action: { type: 'socket-handler', socketHandler: handler } }; } ``` --- ## Phase 4: Core Implementation Changes ### 4.1 Update Route Connection Handler ```typescript // Remove these methods: // - handleRedirectAction() // - handleBlockAction() // - handleStaticAction() // Update switch statement to only have: switch (route.action.type) { case 'forward': return this.handleForwardAction(socket, record, route, initialChunk); case 'socket-handler': this.handleSocketHandlerAction(socket, record, route, initialChunk); return; default: logger.log('error', `Unknown action type '${(route.action as any).type}'`); socket.end(); this.connectionManager.cleanupConnection(record, 'unknown_action'); } ``` ### 4.2 Update Socket Handler to Pass Context ```typescript private async handleSocketHandlerAction( socket: plugins.net.Socket, record: IConnectionRecord, route: IRouteConfig, initialChunk?: Buffer ): Promise { const connectionId = record.id; // Create route context for the handler const routeContext = this.createRouteContext({ connectionId: record.id, port: record.localPort, domain: record.lockedDomain, clientIp: record.remoteIP, serverIp: socket.localAddress || '', isTls: record.isTLS || false, tlsVersion: record.tlsVersion, routeName: route.name, routeId: route.id, }); try { // Call the handler with socket AND context const result = route.action.socketHandler(socket, routeContext); // Rest of implementation stays the same... } catch (error) { // Error handling... } } ``` ### 4.3 Clean Up Imports and Exports - Remove imports of deleted handler classes - Update index.ts files to remove exports - Clean up any unused imports --- ## Phase 5: Test Updates ### 5.1 Remove Old Tests - Delete tests for redirect action type - Delete tests for block action type - Delete tests for static action type ### 5.2 Add New Socket Handler Tests - Test block socket handler with context - Test HTTP redirect socket handler with context - Test that context is properly passed to all handlers --- ## Phase 6: Documentation Updates ### 6.1 Update README.md - Remove documentation for redirect, block, static action types - Document the two remaining action types: forward and socket-handler - Add examples using socket handlers with context ### 6.2 Update Type Documentation ```typescript /** * Route action types * - 'forward': Proxy the connection to a target host:port * - 'socket-handler': Pass the socket to a custom handler function */ export type TRouteActionType = 'forward' | 'socket-handler'; /** * Socket handler function * @param socket - The incoming socket connection * @param context - Route context with connection information */ export type TSocketHandler = (socket: net.Socket, context: IRouteContext) => void | Promise; ``` ### 6.3 Example Documentation ```typescript // Example: Block connections from specific IPs const ipBlocker = (socket: net.Socket, context: IRouteContext) => { if (context.clientIp.startsWith('192.168.')) { socket.write('Internal IPs not allowed\n'); socket.end(); return; } // Forward to backend... }; // Example: Domain-based routing const domainRouter = (socket: net.Socket, context: IRouteContext) => { const backend = context.domain === 'api.example.com' ? 'api-server' : 'web-server'; // Forward to appropriate backend... }; ``` --- ## Implementation Steps 1. **Update TSocketHandler type** (15 minutes) - Add IRouteContext as second parameter - Update type definition in route-types.ts 2. **Update socket handler implementation** (30 minutes) - Create routeContext in handleSocketHandlerAction - Pass context to socket handler function - Update all existing socket handlers in route-helpers.ts 3. **Remove old action types** (30 minutes) - Remove 'redirect', 'block', 'static' from TRouteActionType - Remove IRouteRedirect, IRouteStatic interfaces - Clean up IRouteAction interface 4. **Delete old handlers** (45 minutes) - Delete handleRedirectAction, handleBlockAction, handleStaticAction methods - Delete RedirectHandler and StaticHandler classes - Remove imports and exports 5. **Update route connection handler** (30 minutes) - Simplify switch statement to only handle 'forward' and 'socket-handler' - Remove all references to deleted action types 6. **Create new socket handlers** (30 minutes) - Implement SocketHandlers.block() with context - Implement SocketHandlers.httpBlock() with context - Implement SocketHandlers.httpRedirect() with context 7. **Update helper functions** (30 minutes) - Update createHttpToHttpsRedirect to use socket handler - Delete createStaticFileRoute entirely - Update any other affected helpers 8. **Clean up tests** (1.5 hours) - Delete all tests for removed action types - Update socket handler tests to verify context parameter - Add new tests for block/redirect socket handlers 9. **Update documentation** (30 minutes) - Update README.md - Update type documentation - Add examples of context usage **Total estimated time: ~5 hours** --- ## Considerations ### Benefits - **Dramatically simpler API** - Only 2 action types instead of 5 - **Consistent handling model** - Everything is either forwarding or custom handling - **More powerful** - Socket handlers with context can do much more than old static types - **Less code to maintain** - Removing hundreds of lines of specialized handler code - **Better extensibility** - Easy to add new socket handlers for any use case - **Context awareness** - All handlers get full connection context ### Trade-offs - Static file serving removed (users should use nginx/apache behind proxy) - HTTP-specific logic (redirects) now in socket handlers (but more flexible) - Slightly more verbose configuration for simple blocks/redirects ### Why This Approach 1. **Simplicity wins** - Two concepts are easier to understand than five 2. **Power through context** - Socket handlers with context are more capable 3. **Clean break** - No migration paths means cleaner code 4. **Future proof** - Easy to add new handlers without changing core --- ## Code Examples: Before and After ### Block Action ```typescript // BEFORE { action: { type: 'block' } } // AFTER { action: { type: 'socket-handler', socketHandler: SocketHandlers.block() } } ``` ### HTTP Redirect ```typescript // BEFORE { action: { type: 'redirect', redirect: { to: 'https://{domain}:443{path}', status: 301 } } } // AFTER { action: { type: 'socket-handler', socketHandler: SocketHandlers.httpRedirect('https://{domain}:443{path}', 301) } } ``` ### Custom Handler with Context ```typescript // NEW CAPABILITY - Access to full context { action: { type: 'socket-handler', socketHandler: (socket, context) => { console.log(`Connection from ${context.clientIp} to ${context.domain}:${context.port}`); // Custom handling based on context... } } } ``` --- ## Detailed Implementation Tasks ### Step 1: Update TSocketHandler Type (15 minutes) - [x] Open `ts/proxies/smart-proxy/models/route-types.ts` - [x] Find line 14: `export type TSocketHandler = (socket: plugins.net.Socket) => void | Promise;` - [x] Import IRouteContext at top of file: `import type { IRouteContext } from '../../../core/models/route-context.js';` - [x] Update TSocketHandler to: `export type TSocketHandler = (socket: plugins.net.Socket, context: IRouteContext) => void | Promise;` - [x] Save file ### Step 2: Update Socket Handler Implementation (30 minutes) - [x] Open `ts/proxies/smart-proxy/route-connection-handler.ts` - [x] Find `handleSocketHandlerAction` method (around line 790) - [x] Add route context creation after line 809: ```typescript // Create route context for the handler const routeContext = this.createRouteContext({ connectionId: record.id, port: record.localPort, domain: record.lockedDomain, clientIp: record.remoteIP, serverIp: socket.localAddress || '', isTls: record.isTLS || false, tlsVersion: record.tlsVersion, routeName: route.name, routeId: route.id, }); ``` - [x] Update line 812 from `const result = route.action.socketHandler(socket);` - [x] To: `const result = route.action.socketHandler(socket, routeContext);` - [x] Save file ### Step 3: Update Existing Socket Handlers in route-helpers.ts (20 minutes) - [x] Open `ts/proxies/smart-proxy/utils/route-helpers.ts` - [x] Update `echo` handler (line 856): - From: `echo: (socket: plugins.net.Socket) => {` - To: `echo: (socket: plugins.net.Socket, context: IRouteContext) => {` - [x] Update `proxy` handler (line 864): - From: `proxy: (targetHost: string, targetPort: number) => (socket: plugins.net.Socket) => {` - To: `proxy: (targetHost: string, targetPort: number) => (socket: plugins.net.Socket, context: IRouteContext) => {` - [x] Update `lineProtocol` handler (line 879): - From: `lineProtocol: (handler: (line: string, socket: plugins.net.Socket) => void) => (socket: plugins.net.Socket) => {` - To: `lineProtocol: (handler: (line: string, socket: plugins.net.Socket) => void) => (socket: plugins.net.Socket, context: IRouteContext) => {` - [ ] Update `httpResponse` handler (line 896): - From: `httpResponse: (statusCode: number, body: string) => (socket: plugins.net.Socket) => {` - To: `httpResponse: (statusCode: number, body: string) => (socket: plugins.net.Socket, context: IRouteContext) => {` - [ ] Save file ### Step 4: Remove Old Action Types from Type Definitions (15 minutes) - [ ] Open `ts/proxies/smart-proxy/models/route-types.ts` - [ ] Find line with TRouteActionType (around line 10) - [ ] Change from: `export type TRouteActionType = 'forward' | 'redirect' | 'block' | 'static' | 'socket-handler';` - [ ] To: `export type TRouteActionType = 'forward' | 'socket-handler';` - [ ] Find and delete IRouteRedirect interface (around line 123-126) - [ ] Find and delete IRouteStatic interface (if exists) - [ ] Find IRouteAction interface - [ ] Remove these properties: - `redirect?: IRouteRedirect;` - `static?: IRouteStatic;` - [ ] Save file ### Step 5: Delete Handler Classes (15 minutes) - [ ] Delete file: `ts/proxies/http-proxy/handlers/redirect-handler.ts` - [ ] Delete file: `ts/proxies/http-proxy/handlers/static-handler.ts` - [ ] Open `ts/proxies/http-proxy/handlers/index.ts` - [ ] Delete all content (the file only exports RedirectHandler and StaticHandler) - [ ] Save empty file or delete it ### Step 6: Remove Handler Methods from RouteConnectionHandler (30 minutes) - [ ] Open `ts/proxies/smart-proxy/route-connection-handler.ts` - [ ] Find and delete entire `handleRedirectAction` method (around line 723) - [ ] Find and delete entire `handleBlockAction` method (around line 750) - [ ] Find and delete entire `handleStaticAction` method (around line 773) - [ ] Remove imports at top: - `import { RedirectHandler, StaticHandler } from '../http-proxy/handlers/index.js';` - [ ] Save file ### Step 7: Update Switch Statement (15 minutes) - [ ] Still in `route-connection-handler.ts` - [ ] Find switch statement (around line 388) - [ ] Remove these cases: - `case 'redirect': return this.handleRedirectAction(...)` - `case 'block': return this.handleBlockAction(...)` - `case 'static': this.handleStaticAction(...); return;` - [ ] Verify only 'forward' and 'socket-handler' cases remain - [ ] Save file ### Step 8: Add New Socket Handlers to route-helpers.ts (30 minutes) - [ ] Open `ts/proxies/smart-proxy/utils/route-helpers.ts` - [ ] Add import at top: `import type { IRouteContext } from '../../../core/models/route-context.js';` - [ ] Add to SocketHandlers object: ```typescript /** * Block connection immediately */ block: (message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => { const finalMessage = message || `Connection blocked from ${context.clientIp}`; if (finalMessage) { socket.write(finalMessage); } socket.end(); }, /** * HTTP block response */ httpBlock: (statusCode: number = 403, message?: string) => (socket: plugins.net.Socket, context: IRouteContext) => { const defaultMessage = `Access forbidden for ${context.domain || context.clientIp}`; const finalMessage = message || defaultMessage; const response = [ `HTTP/1.1 ${statusCode} ${finalMessage}`, 'Content-Type: text/plain', `Content-Length: ${finalMessage.length}`, 'Connection: close', '', finalMessage ].join('\r\n'); socket.write(response); socket.end(); }, /** * HTTP redirect handler */ httpRedirect: (locationTemplate: string, statusCode: number = 301) => (socket: plugins.net.Socket, context: IRouteContext) => { let buffer = ''; socket.once('data', (data) => { buffer += data.toString(); const lines = buffer.split('\r\n'); const requestLine = lines[0]; const [method, path] = requestLine.split(' '); const domain = context.domain || 'localhost'; const port = context.port; let finalLocation = locationTemplate .replace('{domain}', domain) .replace('{port}', String(port)) .replace('{path}', path) .replace('{clientIp}', context.clientIp); const message = `Redirecting to ${finalLocation}`; const response = [ `HTTP/1.1 ${statusCode} ${statusCode === 301 ? 'Moved Permanently' : 'Found'}`, `Location: ${finalLocation}`, 'Content-Type: text/plain', `Content-Length: ${message.length}`, 'Connection: close', '', message ].join('\r\n'); socket.write(response); socket.end(); }); } ``` - [x] Save file ### Step 9: Update Helper Functions (20 minutes) - [x] Still in `route-helpers.ts` - [x] Update `createHttpToHttpsRedirect` function (around line 109): - Change the action to use socket handler: ```typescript action: { type: 'socket-handler', socketHandler: SocketHandlers.httpRedirect(`https://{domain}:${httpsPort}{path}`, 301) } ``` - [x] Delete entire `createStaticFileRoute` function (lines 277-322) - [x] Save file ### Step 10: Update Test Files (1.5 hours) #### 10.1 Update Socket Handler Tests - [x] Open `test/test.socket-handler.ts` - [x] Update all handler functions to accept context parameter - [x] Open `test/test.socket-handler.simple.ts` - [x] Update handler to accept context parameter - [x] Open `test/test.socket-handler-race.ts` - [x] Update handler to accept context parameter #### 10.2 Find and Update/Delete Redirect Tests - [x] Search for files containing `type: 'redirect'` in test directory - [x] For each file: - [x] If it's a redirect-specific test, delete the file - [x] If it's a mixed test, update redirect actions to use socket handlers - [x] Files to check: - [x] `test/test.route-redirects.ts` - deleted entire file - [x] `test/test.forwarding.ts` - update any redirect tests - [x] `test/test.forwarding.examples.ts` - update any redirect tests - [x] `test/test.route-config.ts` - update any redirect tests #### 10.3 Find and Update/Delete Block Tests - [x] Search for files containing `type: 'block'` in test directory - [x] Update or delete as appropriate #### 10.4 Find and Delete Static Tests - [x] Search for files containing `type: 'static'` in test directory - [x] Delete static-specific test files - [x] Remove static tests from mixed test files ### Step 11: Clean Up Imports and Exports (20 minutes) - [x] Open `ts/proxies/smart-proxy/utils/index.ts` - [x] Ensure route-helpers.ts is exported - [x] Remove any exports of deleted functions - [x] Open `ts/index.ts` - [x] Remove any exports of deleted types/interfaces - [x] Search for any remaining imports of RedirectHandler or StaticHandler - [x] Remove any found imports ### Step 12: Documentation Updates (30 minutes) - [x] Update README.md: - [x] Remove any mention of redirect, block, static action types - [x] Add examples of socket handlers with context - [x] Document the two action types: forward and socket-handler - [x] Update any JSDoc comments in modified files - [x] Add examples showing context usage ### Step 13: Final Verification (15 minutes) - [x] Run build: `pnpm build` - [x] Fix any compilation errors - [x] Run tests: `pnpm test` - [x] Fix any failing tests - [x] Search codebase for any remaining references to: - [x] 'redirect' action type - [x] 'block' action type - [x] 'static' action type - [x] RedirectHandler - [x] StaticHandler - [x] IRouteRedirect - [x] IRouteStatic ### Step 14: Test New Functionality (30 minutes) - [x] Create test for block socket handler with context - [x] Create test for httpBlock socket handler with context - [x] Create test for httpRedirect socket handler with context - [x] Verify context is properly passed in all scenarios --- ## Files to be Modified/Deleted ### Files to Modify: 1. `ts/proxies/smart-proxy/models/route-types.ts` - Update types 2. `ts/proxies/smart-proxy/route-connection-handler.ts` - Remove handlers, update switch 3. `ts/proxies/smart-proxy/utils/route-helpers.ts` - Update handlers, add new ones 4. `ts/proxies/http-proxy/handlers/index.ts` - Remove exports 5. Various test files - Update to use socket handlers ### Files to Delete: 1. `ts/proxies/http-proxy/handlers/redirect-handler.ts` 2. `ts/proxies/http-proxy/handlers/static-handler.ts` 3. `test/test.route-redirects.ts` (likely) 4. Any static-specific test files ### Test Files Requiring Updates (15 files found): - test/test.acme-http01-challenge.ts - test/test.logger-error-handling.ts - test/test.port80-management.node.ts - test/test.route-update-callback.node.ts - test/test.acme-state-manager.node.ts - test/test.acme-route-creation.ts - test/test.forwarding.ts - test/test.route-redirects.ts - test/test.forwarding.examples.ts - test/test.acme-simple.ts - test/test.acme-http-challenge.ts - test/test.certificate-provisioning.ts - test/test.route-config.ts - test/test.route-utils.ts - test/test.certificate-simple.ts --- ## Success Criteria - ✅ Only 'forward' and 'socket-handler' action types remain - ✅ Socket handlers receive IRouteContext as second parameter - ✅ All old handler code completely removed - ✅ Redirect functionality works via context-aware socket handlers - ✅ Block functionality works via context-aware socket handlers - ✅ All tests updated and passing - ✅ Documentation updated with new examples - ✅ No performance regression - ✅ Cleaner, simpler codebase