UNPKG

browse

Version:

Unified Browserbase CLI for browser automation and cloud APIs.

166 lines (165 loc) 4.93 kB
import net from "node:net"; import readline from "node:readline"; import { DriverSessionManager } from "../session-manager.js"; import { cleanupDaemonFiles, ensureRuntimeDir, getPidPath, getSocketPath, writePrivateFile, } from "./paths.js"; import { parseRequest, serializeResponse, } from "./protocol.js"; export async function runDriverDaemon({ session, target, }) { await ensureRuntimeDir(); await cleanupDaemonFiles(session, { includeLock: false }); await writePrivateFile(getPidPath(session), String(process.pid)); const socketPath = getSocketPath(session); const manager = new DriverSessionManager(session, target); const server = net.createServer(); let shutdownPromise = null; const shutdown = () => { shutdownPromise ??= (async () => { await closeServer(server); await manager.close(); await cleanupDaemonFiles(session); })(); return shutdownPromise; }; server.on("connection", (socket) => { void handleConnection(socket, manager, shutdown); }); process.once("SIGTERM", () => { void shutdown(); }); process.once("SIGINT", () => { void shutdown(); }); await new Promise((resolve, reject) => { const handleListenError = (error) => { reject(error); }; server.once("error", handleListenError); server.listen(socketPath, () => { server.off("error", handleListenError); resolve(); }); }); server.on("error", () => { void shutdown(); }); await new Promise((resolve) => { server.once("close", resolve); }); await shutdown(); } async function handleConnection(socket, manager, shutdown) { const reader = readline.createInterface({ input: socket }); const idleTimer = setTimeout(() => { reader.close(); socket.destroy(); }, 5_000); const cleanup = () => { clearTimeout(idleTimer); reader.close(); }; socket.once("error", () => { cleanup(); socket.destroy(); }); socket.once("close", cleanup); reader.once("line", (line) => { clearTimeout(idleTimer); void handleLine(line, socket, manager) .then(async (shouldShutdown) => { await endSocket(socket); if (shouldShutdown) await shutdown(); }) .catch(() => { socket.destroy(); }) .finally(() => { reader.close(); }); }); } async function handleLine(line, socket, manager) { let request; try { request = parseRequest(line); } catch (error) { await writeResponse(socket, { error: formatError(error), type: "error" }); return false; } try { if (request.type === "open") { await writeResponse(socket, { data: await manager.execute("open", { timeoutMs: request.timeoutMs, url: request.url, waitUntil: request.waitUntil, }), id: request.id, type: "success", }); return false; } if (request.type === "command") { await writeResponse(socket, { data: await manager.execute(request.command, request.params), id: request.id, type: "success", }); return false; } if (request.type === "status") { await writeResponse(socket, { data: await manager.status(), id: request.id, type: "success", }); return false; } await writeResponse(socket, { data: { stopped: true }, id: request.id, type: "success", }); return true; } catch (error) { await writeResponse(socket, { error: formatError(error), id: request.id, type: "error", }); return false; } } function closeServer(server) { if (!server.listening) return Promise.resolve(); return new Promise((resolve, reject) => { server.close((error) => { if (error) { reject(error); return; } resolve(); }); }); } function writeResponse(socket, response) { return new Promise((resolve, reject) => { socket.write(serializeResponse(response), (error) => { if (error) { reject(error); return; } resolve(); }); }); } function endSocket(socket) { return new Promise((resolve) => { socket.end(resolve); }); } function formatError(error) { return error instanceof Error ? error.message : String(error); }