123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- import http from 'http';
- import https from 'https';
- import vm2 from 'vm2';
- import NodeCache from 'node-cache';
- const { NodeVM } = vm2;
- const allowedModules = ['http', 'https', 'stream', 'url', 'zlib', 'encoding'];
- class RemoteCode {
- constructor({ disableCache = false, preProcess = (script) => script }) {
- const cacheConfig = {
- useClones: false,
- stdTTL: 200,
- checkperiod: 250,
- };
- Object.assign(this, {
- disableCache,
- scriptCache: new NodeCache(cacheConfig),
- cacheConfig,
- preProcess,
- });
- this.vm = new NodeVM({
- console: 'inherit',
- require: {
- builtin: allowedModules,
- root: './',
- },
- });
- }
- /**
- * Get and cache the script designed by name from remote
- * @param {string} scriptPath script name.
- * @param {string} extraCommands to be concatened at the end of script.
- */
- async cacheOrFetch(req, remote, scriptPath, extraCommands = '') {
- if (!this.scriptCache.has(remote)) {
- this.scriptCache.set(remote, new NodeCache(this.cacheConfig));
- }
- const cache = this.scriptCache.get(remote);
- if (cache.has(scriptPath) && !this.disableCache) {
- return cache.get(scriptPath);
- } else {
- const httpClient = remote.startsWith('https') ? https : http;
- return new Promise((resolve, reject) => {
- const scriptUrl = `${remote}/${scriptPath}`.replace('//', '/');
- httpClient
- .get(scriptUrl, (resp) => {
- if (resp.statusCode === 404) {
- reject({ status: 'not-found' });
- return;
- }
- let script = '';
- resp.on('data', (chunk) => {
- script += chunk;
- });
- resp.on('end', () => {
- try {
- script = this.preProcess.bind(this)(script, req);
- } catch (e) {
- reject({ status: 'error', error: e });
- }
- script += extraCommands;
- try {
- const scriptFunction = this.vm.run(script).default;
- cache.set(scriptPath, scriptFunction);
- this.scriptCache.set(remote, cache);
- resolve(scriptFunction);
- } catch (e) {
- reject({ status: 'error', error: e });
- }
- });
- })
- .on('error', (err) => {
- /* istanbul ignore next */
- reject({ status: 'error', error: err });
- });
- });
- }
- }
- async exec(req, remote, scriptPath, context) {
- try {
- const toRun = await this.cacheOrFetch(req, remote, scriptPath);
- return toRun({ ...context });
- } catch (e) {
- if (e.status === 'not-found') {
- throw `Script ${scriptPath} not found on remote ${remote}`;
- } else {
- if (e.error) {
- throw e.error;
- } else {
- throw e;
- }
- }
- }
- }
- clearCache(remote) {
- this.scriptCache.del(remote);
- }
- }
- export default RemoteCode;
|