93 lines
2.9 KiB
TypeScript
93 lines
2.9 KiB
TypeScript
/**
|
|
* 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<StreamRouteOptions, "wss" | "isAuthenticated">;
|
|
side: Omit<SideRouteOptions, "wss" | "isAuthenticated">;
|
|
}
|
|
|
|
/**
|
|
* 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<StreamRouteOptions, "wss" | "isAuthenticated">,
|
|
): (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();
|
|
};
|
|
}
|