// CLI side: find (or start) the account daemon and talk to it. import { spawn } from 'node:child_process'; import fs from 'node:fs'; import { fileURLToPath } from 'node:url'; import { loadConfig, paths } from './config.js'; import { WaError, call, ping } from './rpc.js'; const BIN = fileURLToPath(new URL('../bin/wa.js', import.meta.url)); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); export class Session { constructor(account, { autostart } = {}) { this.account = account; this.paths = paths(account); this.config = loadConfig(account); this.autostart = autostart ?? this.config.autostart; } running() { return ping(this.paths.socket); } async call(method, params = {}, opts = {}) { await this.ensureDaemon(); try { return await call(this.paths.socket, method, params, opts); } catch (e) { if (e.syscall === 'connect') throw new WaError('EDAEMON', `could not connect to the daemon (${e.code})`); throw e; } } async ensureDaemon() { if (await this.running()) return; if (!this.autostart) { throw new WaError('EDAEMON', `the daemon for account "${this.account}" is not running: ` + `\`wa daemon start\` or \`systemctl --user start wa-daemon@${this.account}\``); } await this.startBackground(); } async startBackground() { const P = this.paths; fs.mkdirSync(P.stateDir, { recursive: true, mode: 0o700 }); const logFd = fs.openSync(P.logFile, 'a'); if (process.stderr.isTTY) process.stderr.write(`wa: starting the daemon (account ${this.account}), log in ${P.logFile}\n`); const child = spawn(process.execPath, [BIN, 'daemon', 'start', '--foreground', '--account', this.account], { detached: true, stdio: ['ignore', logFd, logFd], env: { ...process.env, WA_CLI_ACCOUNT: this.account }, }); fs.closeSync(logFd); let exited = null; child.on('exit', (code) => { exited = code; }); child.unref(); const deadline = Date.now() + 30000; while (Date.now() < deadline) { if (await this.running()) return; if (exited !== null) { const tail = fs.readFileSync(P.logFile, 'utf8').trimEnd().split('\n').slice(-5).join('\n'); throw new WaError('EDAEMON', `the daemon exited while starting (code ${exited}):\n${tail}`); } await sleep(150); } throw new WaError('EDAEMON', `the daemon did not open the socket within 30 s (see ${P.logFile})`); } }