#!/usr/bin/env node /** * Mock "Rust daemon" for testing the SocketTransport and RustBridge socket mode. * Creates a Unix socket server, accepts connections, and speaks the same * JSON-over-newline IPC protocol as mock-rust-binary.mjs. * * Usage: node mock-socket-server.mjs * Signals readiness by writing a JSON line to stdout: {"socketPath":"...","ready":true} */ import * as net from 'net'; import * as fs from 'fs'; const socketPath = process.argv[2]; if (!socketPath) { process.stderr.write('Usage: mock-socket-server.mjs \n'); process.exit(1); } // Remove stale socket file try { fs.unlinkSync(socketPath); } catch { /* ignore */ } /** * Backpressure-aware write to a socket. */ function writeResponse(conn, data) { const json = JSON.stringify(data) + '\n'; if (!conn.write(json)) { conn.once('drain', () => {}); } } function handleLine(line, conn) { let request; try { request = JSON.parse(line); } catch { return; } const { id, method, params } = request; if (method === 'echo') { writeResponse(conn, { id, success: true, result: params }); } else if (method === 'largeEcho') { writeResponse(conn, { id, success: true, result: params }); } else if (method === 'error') { writeResponse(conn, { id, success: false, error: 'Test error message' }); } else if (method === 'emitEvent') { writeResponse(conn, { event: params.eventName, data: params.eventData }); writeResponse(conn, { id, success: true, result: null }); } else if (method === 'slow') { setTimeout(() => { writeResponse(conn, { id, success: true, result: { delayed: true } }); }, 100); } else if (method === 'streamEcho') { const count = params.count || 0; let sent = 0; const interval = setInterval(() => { if (sent < count) { writeResponse(conn, { id, stream: true, data: { index: sent, value: `chunk_${sent}` } }); sent++; } else { clearInterval(interval); writeResponse(conn, { id, success: true, result: { totalChunks: count } }); } }, 10); } else if (method === 'streamError') { writeResponse(conn, { id, stream: true, data: { index: 0, value: 'before_error' } }); setTimeout(() => { writeResponse(conn, { id, success: false, error: 'Stream error after chunk' }); }, 20); } else if (method === 'streamEmpty') { writeResponse(conn, { id, success: true, result: { totalChunks: 0 } }); } else if (method === 'exit') { writeResponse(conn, { id, success: true, result: null }); // In socket mode, 'exit' just closes this connection, not the server setTimeout(() => conn.end(), 50); } else { writeResponse(conn, { id, success: false, error: `Unknown method: ${method}` }); } } const server = net.createServer((conn) => { // Send ready event on each new connection writeResponse(conn, { event: 'ready', data: { version: '1.0.0' } }); // Buffer-based newline scanner for incoming data let buffer = Buffer.alloc(0); conn.on('data', (chunk) => { buffer = Buffer.concat([buffer, chunk]); let idx; while ((idx = buffer.indexOf(0x0A)) !== -1) { const lineBuffer = buffer.subarray(0, idx); buffer = buffer.subarray(idx + 1); const line = lineBuffer.toString('utf8').trim(); if (line) { handleLine(line, conn); } } }); conn.on('error', () => { /* ignore client errors */ }); }); server.listen(socketPath, () => { // Signal to parent that the server is ready process.stdout.write(JSON.stringify({ socketPath, ready: true }) + '\n'); }); // Handle SIGTERM gracefully process.on('SIGTERM', () => { server.close(); try { fs.unlinkSync(socketPath); } catch { /* ignore */ } process.exit(0); }); // Handle SIGINT process.on('SIGINT', () => { server.close(); try { fs.unlinkSync(socketPath); } catch { /* ignore */ } process.exit(0); });