125 lines
3.8 KiB
JavaScript
125 lines
3.8 KiB
JavaScript
|
|
#!/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 <socket-path>
|
||
|
|
* 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 <socket-path>\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);
|
||
|
|
});
|