import { AppChildProcess } from "./AppChildProcess"; export class MonerodProcess extends AppChildProcess { protected static readonly stdoutPattern: string = '**********************************************************************'; public get interactive(): boolean { return this.args ? !this.args.includes('--non-interactive') : true; } public get detached(): boolean { return this.args ? this.args.includes('--detached') : false; } constructor({ monerodCmd, flags, isExe }: { monerodCmd: string, flags?: string[], isExe?: boolean }) { super({ command: monerodCmd, args: flags, isExe: isExe }) } public static async isValidMonerodPath(monerodPath: string): Promise { console.log(`MonerodProcess.isValidMonerodPath('${monerodPath}')`); if (typeof monerodPath !== 'string' || MonerodProcess.replaceAll(monerodPath, " ", "") == "") { return false; } try { MonerodProcess.checkExecutable(monerodPath); } catch { return false; } const proc = new AppChildProcess({ command: monerodPath, args: [ '--help' ], isExe: true }); const promise = new Promise((resolve) => { let foundUsage: boolean = false; proc.onError((err: Error) => { console.log(`MonerodProcess.isValidMonerodPath(): Error: '${err.message}'`); resolve(false); }); proc.onStdErr((err: string) => { console.log(`MonerodProcess.isValidMonerodPath(): Std Error: '${err}'`); resolve(false); }); proc.onStdOut((data: string) => { if (foundUsage) { return; } if ( `${data}`.includes('monerod [options|settings] [daemon_command...]') || `${data}`.includes('monerod.exe [options|settings] [daemon_command...]') ) { foundUsage = true; } }); proc.onClose((code: number | null) => { console.log(`MonerodProcess.isValidMonerodPath(): exit code '${code}', found usage: ${foundUsage}`); resolve(foundUsage && code == 0); }); }); try { await proc.start(); } catch(error: any) { console.log(`MonerodProcess.isValidMonerodPath(): exit code '${error}'`); } return await promise; } public override async start(): Promise { if (this._isExe) { const validPath = await MonerodProcess.isValidMonerodPath(this._command); if (!validPath) { throw new Error("Invalid monerod path provided: " + this._command); } } let message: string = "Starting monerod process"; message += `\n\t${this._isExe ? 'Path' : 'Command'}: ${this._command}`; if (this._args) { message += `\n\tFlags: ${this._args.join(" ")}` } console.log(message); let firstPatternFound = false; const waitForPattern = this._args ? !this._args.includes('--version') && !this.args.includes('--help') : true; const patternPromise = new Promise((resolve, reject) => { let firstStdout = true; let timeout: NodeJS.Timeout | undefined = undefined; const onStdOut = (out: string) => { if (firstStdout) { firstStdout = false; if (!waitForPattern) { return; } timeout = setTimeout(() => { if (this._process && this._process.exitCode == null) { this._process.kill(); } timeout = undefined; reject(new Error("MonerodProcess.start(): Timed out")); }, 90*1000); } const foundPattern = out.includes(MonerodProcess.stdoutPattern); if (!foundPattern) { return; } if (firstPatternFound) { if(timeout !== undefined) { clearTimeout(timeout); console.log("MonerodProcess.start(): Cleared timeout"); } else { console.log("MonerodProcess.start(): No timeout found"); } resolve(); } else { firstPatternFound = true; } }; this.onStdOut(onStdOut); }); await super.start(); if (!this._process || !this._process.pid) { throw new Error("Monerod process did not start!"); } try { console.log(`MonerodProcess.start(): wait for pattern: ${waitForPattern}`); if (waitForPattern) await patternPromise; console.log("Started monerod process pid: " + this._process.pid); } catch(error: any) { this._running = false; this._starting = false; this._stopping = false; if (error instanceof Error) { throw error; } else { throw new Error(`${error}`); } } } public async getVersion(): Promise { const proc = new MonerodProcess({ monerodCmd: this._command, flags: [ '--version' ], isExe: this._isExe }); const promise = new Promise((resolve, reject) => { proc.onError((err: Error) => { console.log("MonerodProcess.getVersion(): proc.onError():"); console.error(err); reject(err) }); proc.onStdErr((err: string) => { console.log("MonerodProcess.getVersion(): proc.onStdErr()"); console.error(err); reject(new Error(err)); }); proc.onStdOut((version: string) => { if (version == '') { return; } console.log("MonerodProcess.getVersion(): proc.onStdOut():"); console.log(version); resolve(version); }); }); console.log("MonerodProcess.getVersion(): Before proc.start()"); await proc.start(); console.log("MonerodProcess.getVersion(): After proc.start()"); return await promise; } }