// CLI <-> daemon protocol: one JSON object per line over a Unix socket. // // -> {"id":1,"method":"messages.list","params":{...}} // <- {"id":1,"result":...} normal reply // <- {"id":1,"error":{"code":"EAUTH","message":"..."}} // <- {"id":1,"event":{...}} stream methods (events.watch), until closed // // Simple on purpose: any language can speak it (e.g. a Python bridge with // socket.AF_UNIX) without going through the `wa` binary. import net from 'node:net'; export class WaError extends Error { constructor(code, message, data) { super(message); this.code = code; if (data !== undefined) this.data = data; } } export function onLines(socket, fn) { let buf = ''; socket.setEncoding('utf8'); socket.on('data', (chunk) => { buf += chunk; let i; while ((i = buf.indexOf('\n')) >= 0) { const line = buf.slice(0, i); buf = buf.slice(i + 1); if (line.trim()) fn(line); } }); } export function send(socket, obj) { if (!socket.destroyed && socket.writable) socket.write(JSON.stringify(obj) + '\n'); } // Calls a method. With onEvent(event, close), it keeps listening for events until // either side closes. export function call(socketPath, method, params = {}, { onEvent } = {}) { return new Promise((resolve, reject) => { const sock = net.createConnection(socketPath); let settled = false; const done = (fn, v) => { if (settled) return; settled = true; fn(v); }; sock.on('connect', () => send(sock, { id: 1, method, params })); onLines(sock, (line) => { let msg; try { msg = JSON.parse(line); } catch { return done(reject, new WaError('EINTERNAL', `invalid reply from the daemon: ${line.slice(0, 200)}`)); } if (msg.event !== undefined) { onEvent?.(msg.event, () => sock.end()); } else if (msg.error) { done(reject, new WaError(msg.error.code, msg.error.message, msg.error.data)); sock.end(); } else { done(resolve, msg.result); sock.end(); } }); sock.on('error', (e) => done(reject, e)); sock.on('close', () => done(onEvent ? resolve : reject, onEvent ? undefined : new WaError('EDAEMON', 'the daemon closed the connection'))); }); } export function ping(socketPath) { return new Promise((resolve) => { const sock = net.createConnection(socketPath); sock.on('connect', () => { sock.destroy(); resolve(true); }); sock.on('error', () => resolve(false)); }); }