diff --git a/src/lib/core/modules/coderunner/codeRunner.js b/src/lib/core/modules/coderunner/codeRunner.js index cb9e9f136..1310f8d7c 100644 --- a/src/lib/core/modules/coderunner/codeRunner.js +++ b/src/lib/core/modules/coderunner/codeRunner.js @@ -32,10 +32,12 @@ class CodeRunner { } } }, this.logger); + this.registerIpcEvents(); this.IpcClientListen(); this.registerEvents(); this.registerCommands(); + this.events.emit('runcode:ready'); } registerIpcEvents() { @@ -74,12 +76,12 @@ class CodeRunner { this.events.setCommandHandler('runcode:eval', this.evalCode.bind(this)); } - registerVar(varName, code, toRecord = true) { + registerVar(varName, code, toRecord = true, cb = () => {}) { if (this.ipc.isServer() && toRecord) { this.commands.push({varName, code}); this.ipc.broadcast("runcode:newCommand", {varName, code}); } - this.vm.registerVar(varName, code); + this.vm.registerVar(varName, code, cb); } async evalCode(code, cb, isNotUserInput = false, tolerateError = false) { diff --git a/src/lib/core/modules/coderunner/vm.ts b/src/lib/core/modules/coderunner/vm.ts index 5bdd7c7a4..2b753e328 100644 --- a/src/lib/core/modules/coderunner/vm.ts +++ b/src/lib/core/modules/coderunner/vm.ts @@ -1,9 +1,10 @@ +import { each } from "async"; import { NodeVM, NodeVMOptions } from "vm2"; import { Callback } from "../../../../typings/callbacks"; import { Logger } from "../../../../typings/logger"; const fs = require("../../fs"); -const { recursiveMerge } = require("../../../utils/utils"); +const { recursiveMerge, isEs6Module } = require("../../../utils/utils"); const Utils = require("../../../utils/utils"); const WEB3_INVALID_RESPONSE_ERROR: string = "Invalid JSON RPC response"; @@ -49,7 +50,7 @@ class VM { constructor(options: NodeVMOptions, private logger: Logger) { this.options = recursiveMerge(this.options, options); - this.setupNodeVm(); + this.setupNodeVm(() => { }); } /** @@ -108,12 +109,33 @@ class VM { * @param {String} varName Name of the variable to register. * @param {any} code Value of the variable to register. */ - public registerVar(varName: string, code: any) { + public registerVar(varName: string, code: any, cb: Callback) { // Disallow `eval` and `require`, just in case. if (code === eval || code === require) { return; } - this.options.sandbox[varName] = code; - this.setupNodeVm(); + // handle ES6 modules + if (isEs6Module(code)) { + code = code.default; + } + + this.updateState((_err) => { + this.options.sandbox[varName] = code; + this.setupNodeVm(cb); + }); + } + + private updateState(cb: Callback) { + if (!this.vm) { return cb(); } + + // update sandbox state from VM + each(Object.keys(this.options.sandbox), (sandboxVar: string, next: Callback) => { + this.doEval(sandboxVar, false, (err, result) => { + if (!err) { + this.options.sandbox[sandboxVar] = result; + } + next(err); + }); + }, cb); } /** @@ -121,8 +143,9 @@ class VM { * @type {VM} class is instantiated. The "sandbox" member of the options can be modified * by calling @function {registerVar}. */ - private setupNodeVm() { + private setupNodeVm(cb: Callback) { this.vm = new NodeVM(this.options); + cb(); } /** diff --git a/src/lib/core/plugin.js b/src/lib/core/plugin.js index 235b34394..7914815ba 100644 --- a/src/lib/core/plugin.js +++ b/src/lib/core/plugin.js @@ -90,7 +90,7 @@ Plugin.prototype.loadPlugin = function() { if (this.shouldInterceptLogs) { this.setUpLogger(); } - if (typeof this.pluginModule === 'object' && typeof this.pluginModule.default === 'function' && this.pluginModule.__esModule){ + if (utils.isEs6Module(this.pluginModule)) { this.pluginModule = this.pluginModule.default; return new this.pluginModule(this); } diff --git a/src/lib/utils/utils.js b/src/lib/utils/utils.js index 2506c2063..d1138afa0 100644 --- a/src/lib/utils/utils.js +++ b/src/lib/utils/utils.js @@ -620,6 +620,10 @@ function toposort(graph) { return toposortGraph(graph); } +function isEs6Module(module) { + return typeof module === 'object' && typeof module.default === 'function' && module.__esModule; +} + module.exports = { joinPath, dirname, @@ -669,5 +673,6 @@ module.exports = { fuzzySearch, jsonFunctionReplacer, getWindowSize, - toposort + toposort, + isEs6Module }; diff --git a/src/test/vm.js b/src/test/vm.js index 3891750c6..ad9dda0c4 100644 --- a/src/test/vm.js +++ b/src/test/vm.js @@ -33,4 +33,52 @@ describe('embark.vm', function () { }); }); }); + describe('#registerVar', function () { + it('should be able to evaluate code on a registered variable', function (done) { + vm.registerVar('success', true, () => { + vm.doEval('success', false, (err, result) => { + expect(err).to.be.null; + expect(result).to.be.equal(true); + done(); + }); + }); + }); + it('should be able to access a required module that was registered as a variable', function (done) { + vm.registerVar('externalRequire', (module.exports = () => { return "success"; }), () => { + vm.doEval('externalRequire()', false, (err, result) => { + expect(err).to.be.null; + expect(result).to.be.equal('success'); + done(); + }); + }); + }); + it('should be able to access a required ES6 module that was registered as a variable', function (done) { + const es6Module = { + default: () => { return "es6"; }, + __esModule: true + }; + vm.registerVar('externalRequireES6', es6Module, () => { + vm.doEval('externalRequireES6()', false, (err, result) => { + expect(err).to.be.null; + expect(result).to.be.equal("es6"); + done(); + }); + }); + }); + it('should be able to access changed state', function (done) { + vm.registerVar('one', 1, () => { + vm.doEval('one += 1; one;', false, (err1, result1) => { + expect(err1).to.be.null; + expect(result1).to.be.equal(2); + vm.registerVar('x', 'x', () => { // instantiates new VM, but should save state + vm.doEval('one', false, (err2, result2) => { + expect(err2).to.be.null; + expect(result2).to.be.equal(2); + done(); + }); + }); + }); + }); + }); + }); });