// The daemon client — newline-delimited JSON over the peer's unix socket.
//
// This is the ONLY thing the surface knows how to do. It holds no data, derives nothing, and caches
// nothing: every page is a question asked of favela-peer and answered by it. That is the boundary
// the README states, and it is what keeps a renderer from quietly becoming a second peer.
import net from '#node:net'
export class Client {
constructor (sock) {
this.sock = sock
}
// ⛔ Settle on every path, and never resolve without a response.
//
// The first version only resolved inside the `data` handler and only rejected on `error`. Against
// a socket that does not exist it produced an empty ARRAY — so "the daemon is down" rendered as
// "your timeline is empty", which is the exact confusion this surface exists to prevent, and it
// was in the code under a comment saying so. A socket that closes without answering is a failure,
// and a connect that never completes is a failure; both say so now.
call (cmd, args = {}, { timeout = 15000 } = {}) {
return new Promise((resolve, reject) => {
let settled = false
let buf = ''
const socket = net.connect(this.sock)
const done = (err, value) => {
if (settled) return
settled = true
clearTimeout(timer)
try { socket.destroy() } catch {}
err ? reject(err) : resolve(value)
}
const timer = setTimeout(() => done(new Error('timed out talking to favela-peer at ' + this.sock)), timeout)
socket.on('error', (err) => done(new Error(
err.code === 'ENOENT' || err.code === 'ECONNREFUSED'
? 'no daemon at ' + this.sock + ' — is favela-peer running?'
: err.message)))
// Closed or ended with nothing back is NOT success.
socket.on('close', () => done(new Error('favela-peer closed the connection without answering')))
socket.on('end', () => done(new Error('favela-peer ended the connection without answering')))
socket.on('connect', () => socket.write(JSON.stringify({ id: 1, cmd, args }) + '\n'))
socket.on('data', (chunk) => {
buf += chunk.toString()
const i = buf.indexOf('\n')
if (i === -1) return
let res
try { res = JSON.parse(buf.slice(0, i)) } catch (err) { return done(err) }
if (!res || typeof res !== 'object') return done(new Error('malformed response from favela-peer'))
done(res.ok ? null : new Error(res.error || 'favela-peer returned an error'), res.value)
})
})
}
}
export default Client