aboutsummaryrefslogtreecommitdiffstats
path: root/src/session.js
blob: 9197636c36c2a11bfdae10dbf9aac39a6de7d1d4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 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})`);
    }
}