// DenoService JSON-RPC server — Go calls Deno for module lifecycle management. // Uses length-prefixed JSON over raw Unix socket (Deno's http2 server is broken). // Protocol: 4-byte big-endian length + JSON payload, newline-delimited. import { ModuleRegistry } from "./modules.ts"; export interface DenoServer { close(): void; } export async function startDenoServer( socketPath: string, registry: ModuleRegistry, ): Promise { // Remove stale socket try { Deno.removeSync(socketPath); } catch { // ignore } const listener = Deno.listen({ transport: "unix", path: socketPath }); const handleConnection = async (conn: Deno.UnixConn) => { const reader = conn.readable.getReader(); const writer = conn.writable.getWriter(); const decoder = new TextDecoder(); let buffer = ""; try { while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); // Process complete lines (newline-delimited JSON) let newlineIdx: number; while ((newlineIdx = buffer.indexOf("\n")) !== -1) { const line = buffer.slice(0, newlineIdx); buffer = buffer.slice(newlineIdx + 1); if (!line.trim()) continue; try { const req = JSON.parse(line); const resp = dispatch(req, registry); await writer.write( new TextEncoder().encode(JSON.stringify(resp) + "\n"), ); } catch (err) { const errResp = { error: err instanceof Error ? err.message : String(err), }; await writer.write( new TextEncoder().encode(JSON.stringify(errResp) + "\n"), ); } } } } catch { // Connection closed or error — expected during shutdown } finally { try { writer.close(); } catch { /* already closed */ } } }; // Accept connections in background const abortController = new AbortController(); (async () => { try { for await (const conn of listener) { if (abortController.signal.aborted) break; handleConnection(conn); } } catch { // Listener closed } })(); return { close() { abortController.abort(); listener.close(); }, }; } interface RPCRequest { method: string; code?: string; entry_point?: string; permissions?: { read?: string[]; write?: string[]; net?: string[]; run?: string[] }; process_id?: string; } function dispatch( req: RPCRequest, registry: ModuleRegistry, ): Record { switch (req.method) { case "LoadModule": { registry.load( req.code ?? "", req.entry_point ?? "", req.permissions ?? {}, ); return { ok: true, error: "" }; } case "UnloadModule": { const ok = registry.unload(req.code ?? ""); return { ok }; } case "ModuleStatus": { return { code: req.code, status: registry.status(req.code ?? "") }; } default: return { error: `unknown method: ${req.method}` }; } }