import type { Server } from "node:http"; import type { AddressInfo } from "node:net"; import express from "express"; import type { ResolvedBrowserConfig } from "./config.js"; import type { BrowserRouteRegistrar } from "./routes/types.js"; import { isLoopbackHost } from "../gateway/net.js"; import { deleteBridgeAuthForPort, setBridgeAuthForPort } from "./bridge-auth-registry.js"; import { registerBrowserRoutes } from "./routes/index.js"; import { type BrowserServerState, createBrowserRouteContext, type ProfileContext, } from "./server-context.js"; import { installBrowserAuthMiddleware, installBrowserCommonMiddleware, } from "./server-middleware.js"; export type BrowserBridge = { server: Server; port: number; baseUrl: string; state: BrowserServerState; }; export async function startBrowserBridgeServer(params: { resolved: ResolvedBrowserConfig; host?: string; port?: number; authToken?: string; authPassword?: string; onEnsureAttachTarget?: (profile: ProfileContext["profile"]) => Promise; }): Promise { const host = params.host ?? "127.0.0.1"; if (!isLoopbackHost(host)) { throw new Error(`bridge server must bind to loopback host (got ${host})`); } const port = params.port ?? 0; const app = express(); installBrowserCommonMiddleware(app); const authToken = params.authToken?.trim() || undefined; const authPassword = params.authPassword?.trim() || undefined; if (!authToken && !authPassword) { throw new Error("bridge server requires auth (authToken/authPassword missing)"); } installBrowserAuthMiddleware(app, { token: authToken, password: authPassword }); const state: BrowserServerState = { server: null as unknown as Server, port, resolved: params.resolved, profiles: new Map(), }; const ctx = createBrowserRouteContext({ getState: () => state, onEnsureAttachTarget: params.onEnsureAttachTarget, }); registerBrowserRoutes(app as unknown as BrowserRouteRegistrar, ctx); const server = await new Promise((resolve, reject) => { const s = app.listen(port, host, () => resolve(s)); s.once("error", reject); }); const address = server.address() as AddressInfo | null; const resolvedPort = address?.port ?? port; state.server = server; state.port = resolvedPort; state.resolved.controlPort = resolvedPort; setBridgeAuthForPort(resolvedPort, { token: authToken, password: authPassword }); const baseUrl = `http://${host}:${resolvedPort}`; return { server, port: resolvedPort, baseUrl, state }; } export async function stopBrowserBridgeServer(server: Server): Promise { try { const address = server.address() as AddressInfo | null; if (address?.port) { deleteBridgeAuthForPort(address.port); } } catch { // ignore } await new Promise((resolve) => { server.close(() => resolve()); }); }