UNPKG

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
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;