/** * WebSocket upgrade routing. * * Routes incoming HTTP Upgrade requests to the appropriate WebSocket handler * based on the request path and session/topic. Non-matching paths are * destroyed immediately. * * Routes: * /ws — LEGACY browser HTML client WebSocket endpoint * /sessions/:id/stream — binary ANSI stream per tmux session (T-1.5) */ import type { IncomingMessage } from "node:http"; import type { Socket } from "node:net"; import { handleSideUpgrade, type SideRouteOptions } from "./routes/side.js"; import { handleStreamUpgrade, type StreamRouteOptions, } from "./routes/stream.js"; import type { WsClient, WsServer } from "./types.js"; export interface UpgradeHandlerOptions { wss: WsServer; isAuthenticated: (req: IncomingMessage) => boolean; stream: Omit; side: Omit; } /** * Create the HTTP `upgrade` event handler. * * @param opts - Handler options including wss, auth predicate, and stream config. * @returns A handler suitable for `httpServer.on("upgrade", handler)`. */ export function createUpgradeHandler( wss: WsServer, isAuthenticated: (req: IncomingMessage) => boolean, streamOpts?: Omit, ): (request: IncomingMessage, socket: Socket, head: Buffer) => void { return (request: IncomingMessage, socket: Socket, head: Buffer): void => { const url = new URL(request.url ?? "/", "http://localhost"); if (url.pathname === "/ws") { // LEGACY: browser HTML client WebSocket endpoint — auth guard if (!isAuthenticated(request)) { socket.write("HTTP/1.1 403 Forbidden\r\n\r\n"); socket.destroy(); return; } wss.handleUpgrade(request, socket, head, (ws: WsClient) => { wss.emit("connection", ws, request); }); return; } // /sessions/:id/stream const streamMatch = url.pathname.match(/^\/sessions\/([^/]+)\/stream$/); if (streamMatch) { if (!isAuthenticated(request)) { socket.write("HTTP/1.1 403 Forbidden\r\n\r\n"); socket.destroy(); return; } const sessionId = decodeURIComponent(streamMatch[1]); handleStreamUpgrade(sessionId, request, socket, head, { wss, isAuthenticated, ...streamOpts, }); return; } // /sessions/:id/side const sideMatch = url.pathname.match(/^\/sessions\/([^/]+)\/side$/); if (sideMatch) { if (!isAuthenticated(request)) { socket.write("HTTP/1.1 403 Forbidden\r\n\r\n"); socket.destroy(); return; } const sessionId = decodeURIComponent(sideMatch[1]); handleSideUpgrade(sessionId, request, socket, head, { wss, isAuthenticated, }); return; } // Unknown upgrade path — reject socket.destroy(); }; }