pomeloes-robot
Version:
A fully modernized pomelo-robot, upgraded with ES6+ and latest dependencies. Now supports standalone mode.
128 lines (108 loc) • 4.07 kB
JavaScript
const vm = require('vm');
const fs = require('fs');
const path = require('path');
const { EventEmitter } = require('events');
const { Console } = require('console');
const monitor = require('../monitor/monitor');
class Actor extends EventEmitter {
constructor(conf, aid) {
super();
this.id = aid;
this.script = conf.script;
this.on('start', (action, reqId) => {
monitor.beginTime(action, this.id, reqId);
});
this.on('end', (action, reqId) => {
monitor.endTime(action, this.id, reqId);
});
this.on('incr', (action) => {
monitor.incr(action);
});
this.on('decr', (action) => {
monitor.decr(action);
});
}
run() {
if (!this.script) {
const err = new Error(`Actor ${this.id} run failed due to missing script.`);
this.emit('error', err.stack);
return;
}
try {
const scriptContent = fs.readFileSync(this.script, 'utf-8');
const scriptFilename = this.script;
const scriptDirname = path.dirname(scriptFilename);
const sandboxedRequire = (modulePath) => {
const resolvedPath = require.resolve(modulePath, { paths: [scriptDirname] });
return require(resolvedPath);
};
Object.assign(sandboxedRequire, require);
const sandboxedConsole = new Console(process.stdout, process.stderr);
const sandboxedModule = { exports: {} };
const initSandbox = {
console: sandboxedConsole,
require: sandboxedRequire,
actor: this,
setTimeout: setTimeout,
clearTimeout: clearTimeout,
setInterval: setInterval,
clearInterval: clearInterval,
global: global,
process: process, // Inject the global process directly
__filename: scriptFilename,
__dirname: scriptDirname,
module: sandboxedModule,
exports: sandboxedModule.exports,
};
const context = vm.createContext(initSandbox);
vm.runInContext(scriptContent, context);
// After running the script, check if it exported a function to reconcile the
// modern 'standalone' execution pattern with the legacy 'client' one.
const exported = context.module.exports;
if (typeof exported === 'function') {
const frameworkApp = {
get: (name) => {
if (name === 'actor') return context.actor;
return null;
}
};
// Support both sync and async exported functions.
const result = exported(frameworkApp);
if (result && typeof result.catch === 'function') {
result.catch(e => this.emit('error', e.stack));
}
}
} catch (ex) {
this.emit('error', ex.stack);
}
}
reset() {
monitor.clear();
}
later(fn, time) {
if (time > 0 && typeof (fn) === 'function') {
return setTimeout(fn, time);
}
}
interval(fn, timeConfig) {
if (typeof fn !== 'function') {
console.error('First argument to interval must be a function.');
return;
}
if (typeof timeConfig === 'number' && timeConfig > 0) {
return setInterval(fn, timeConfig);
}
if (Array.isArray(timeConfig) && timeConfig.length === 2) {
const [start, end] = timeConfig;
const randomTime = Math.round(Math.random() * (end - start) + start);
return setTimeout(() => {
fn();
this.interval(fn, timeConfig);
}, randomTime);
}
}
clean(timerId) {
clearTimeout(timerId);
}
}
exports.Actor = Actor;