import type { Page, WebSocketRoute } from "@playwright/test"; import { daemonWsRoutePattern } from "./daemon-port"; export interface DirectoryBootstrapCounts { agents: number; workspaces: number; } export interface DirectoryRequestStartCounts { subscribed: DirectoryBootstrapCounts; unsubscribed: DirectoryBootstrapCounts; total: DirectoryBootstrapCounts; } interface ClientRequest { type?: unknown; subscribe?: unknown; page?: { cursor?: unknown }; } function readClientRequest(message: string | Buffer): ClientRequest | null { if (typeof message !== "string") return null; try { const envelope = JSON.parse(message) as { type?: unknown; message?: ClientRequest; }; return envelope.type === "session" ? (envelope.message ?? null) : envelope; } catch { return null; } } function directoryForRequest(request: ClientRequest): keyof DirectoryBootstrapCounts | null { if (request.page?.cursor) return null; if (request.type === "fetch_agents_request") return "agents"; if (request.type === "fetch_workspaces_request") return "workspaces"; return null; } export async function installDaemonWebSocketGate(page: Page) { let acceptingConnections = true; const activeSockets = new Set(); const directoryStarts: DirectoryRequestStartCounts = { subscribed: { agents: 0, workspaces: 0 }, unsubscribed: { agents: 0, workspaces: 0 }, total: { agents: 0, workspaces: 0 }, }; const clientRequestCounts = new Map(); await page.routeWebSocket(daemonWsRoutePattern(), (ws) => { if (!acceptingConnections) { void ws.close({ code: 1008, reason: "Blocked by reconnect test." }); return; } activeSockets.add(ws); const server = ws.connectToServer(); ws.onMessage((message) => { if (!acceptingConnections) return; const request = readClientRequest(message); if (typeof request?.type === "string") { clientRequestCounts.set(request.type, (clientRequestCounts.get(request.type) ?? 0) + 1); const directory = directoryForRequest(request); if (directory) { const subscription = request.subscribe === undefined ? "unsubscribed" : "subscribed"; directoryStarts[subscription][directory] += 1; directoryStarts.total[directory] += 1; } } try { server.send(message); } catch { activeSockets.delete(ws); } }); server.onMessage((message) => { if (!acceptingConnections) return; try { ws.send(message); } catch { activeSockets.delete(ws); } }); }); return { async drop(): Promise { acceptingConnections = false; const sockets = Array.from(activeSockets); activeSockets.clear(); await Promise.all( sockets.map((ws) => ws.close({ code: 1008, reason: "Dropped by reconnect test." }).catch(() => undefined), ), ); }, restore(): void { acceptingConnections = true; }, getDirectoryRequestStartCounts(): DirectoryRequestStartCounts { return { subscribed: { ...directoryStarts.subscribed }, unsubscribed: { ...directoryStarts.unsubscribed }, total: { ...directoryStarts.total }, }; }, getClientRequestCount(type: string): number { return clientRequestCounts.get(type) ?? 0; }, }; }