import * as fs from "node:fs"; import * as net from "node:net"; import * as path from "node:path"; import { MAX_REQUEST_BYTES, SOCKET_TIMEOUT_MS } from "./constants"; import { dbg } from "./util"; /** One-shot NDJSON request over a unix socket. */ export function sockRequest(sockPath: string, payload: unknown, timeoutMs = SOCKET_TIMEOUT_MS): Promise { return new Promise((resolve, reject) => { const conn = net.createConnection(sockPath); let buf = ""; const timer = setTimeout(() => { conn.destroy(); reject(new Error(`timeout talking to ${path.basename(sockPath)}`)); }, timeoutMs); conn.on("connect", () => conn.write(JSON.stringify(payload) + "\n")); conn.on("data", (d) => { buf += d.toString(); const nl = buf.indexOf("\n"); if (nl >= 0) { clearTimeout(timer); conn.end(); try { resolve(JSON.parse(buf.slice(0, nl))); } catch (e) { reject(e as Error); } } }); conn.on("error", (e) => { clearTimeout(timer); reject(e); }); }); } export type RequestHandler = (req: any) => Promise; /** * NDJSON server on a unix socket: one request per connection, hardened — * re-entrancy guard (async handler + multiple data events), request size cap, * idle-connection timeout. Removes any stale socket file before listening. */ export function createNdjsonServer(sockPath: string, handler: RequestHandler): net.Server { fs.rmSync(sockPath, { force: true }); const server = net.createServer((conn) => { let buf = ""; let handled = false; conn.setTimeout(SOCKET_TIMEOUT_MS * 2, () => conn.destroy()); conn.on("data", async (d) => { if (handled) return; buf += d.toString(); if (buf.length > MAX_REQUEST_BYTES) { handled = true; conn.destroy(); return; } const nl = buf.indexOf("\n"); if (nl < 0) return; handled = true; let req: any; try { req = JSON.parse(buf.slice(0, nl)); } catch { conn.end(JSON.stringify({ ok: false, error: "bad json" }) + "\n"); return; } try { conn.end(JSON.stringify(await handler(req)) + "\n"); } catch (e: any) { dbg("server handler error", String(e?.message ?? e)); conn.end(JSON.stringify({ ok: false, error: String(e?.message ?? e) }) + "\n"); } }); conn.on("error", () => {}); }); server.listen(sockPath); server.on("error", (e) => dbg("server error", String(e))); return server; }