"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RPCHost = exports.RPCProcess = void 0;
const utils_subprocess_1 = require("@ionic/utils-subprocess");
const Debug = require("debug");
const fs = require("fs");
const errors_1 = require("../errors");
const debug = Debug('ionic:cli-framework:utils:ipc');
class RPCProcess {
constructor({ name = 'unnamed', timeout = 5000 } = {}) {
this.responseProcedures = new Map();
this.name = name;
this.timeout = timeout;
}
start(proc) {
if (this.proc) {
throw new errors_1.IPCError('RPC process already started.');
}
const p = proc;
if (!p.send) {
throw new errors_1.IPCError('Cannot use proc: `send()` undefined.');
}
this.proc = p;
p.on('message', async (msg) => {
if (isRPCRequest(msg)) {
debug('%s: Received RPC request: %O', this.name, msg);
const fn = this.responseProcedures.get(msg.procedure);
let err;
let data;
if (fn) {
try {
data = await fn(msg.args);
}
catch (e) {
err = e;
}
}
else {
err = new errors_1.IPCError(`Unknown procedure: ${msg.procedure}`);
err.code = errors_1.ERROR_IPC_UNKNOWN_PROCEDURE;
}
const response = { type: 'rpc-response', id: msg.id, procedure: msg.procedure, request: msg, err, data };
if (p.send) {
p.send(response);
debug('%s: Sent RPC response: %O', this.name, response);
}
else {
throw new errors_1.IPCError('Cannot use proc: `send()` undefined.');
}
}
});
p.on('error', err => {
debug('%s: Encountered error with proc: %O', this.name, err);
});
debug('%s: RPC process initiated (pid: %d)', this.name, p.pid);
}
register(procedure, fn) {
this.responseProcedures.set(procedure, fn);
}
async call(procedure, args) {
const p = this.proc;
if (!p) {
throw new errors_1.IPCError('Cannot call procedure: no proc started.');
}
const id = Math.random().toString(16).substring(2, 8);
const request = { type: 'rpc-request', id, procedure, args };
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new errors_1.IPCError(`Timeout of ${this.timeout}ms reached.`));
}, this.timeout);
const messageHandler = (msg) => {
if (isRPCResponse(msg) && msg.id === id) {
debug('%s: Received RPC response: %O', this.name, msg);
if (msg.err) {
reject(msg.err);
}
else {
resolve(msg.data);
}
p.removeListener('message', messageHandler);
p.removeListener('disconnect', disconnectHandler);
clearTimeout(timer);
}
};
const disconnectHandler = () => {
reject(new errors_1.IPCError('Unexpected disconnect. Rejecting call!'));
clearTimeout(timer);
};
p.on('message', messageHandler);
p.on('disconnect', disconnectHandler);
if (p.send) {
p.send(request);
debug('%s: Sent RPC request: %O', this.name, request);
}
else {
reject(new errors_1.IPCError('Cannot use proc: `send()` undefined.'));
clearTimeout(timer);
}
});
}
end() {
if (!this.proc) {
throw new errors_1.IPCError(`RPC process not started.`);
}
this.proc.disconnect();
debug('%s: Disconnected', this.name);
}
}
exports.RPCProcess = RPCProcess;
class RPCHost {
constructor(modulePath, args) {
this.modulePath = modulePath;
this.args = args;
this.rpc = new RPCProcess({ name: 'host' });
}
start() {
try {
fs.accessSync(this.modulePath, fs.constants.R_OK);
}
catch (e) {
debug('Error during access check: %O', e);
throw new errors_1.IPCError(`Module not accessible: ${this.modulePath}`);
}
const p = utils_subprocess_1.fork(this.modulePath, this.args, { stdio: ['ignore', 'ignore', 'ignore', 'ipc'] });
debug('RPC subprocess forked %o', [this.modulePath, ...this.args]);
this.rpc.start(p);
}
register(procedure, fn) {
this.rpc.register(procedure, fn);
}
async call(procedure, args) {
return this.rpc.call(procedure, args);
}
end() {
this.rpc.end();
}
}
exports.RPCHost = RPCHost;
function isRPCRequest(msg) {
return msg && msg.type === 'rpc-request' && typeof msg.procedure === 'string';
}
function isRPCResponse(msg) {
return msg && msg.type === 'rpc-response' && typeof msg.procedure === 'string';
}