mirror of
https://github.com/embarklabs/embark.git
synced 2025-01-11 06:16:01 +00:00
feat(@embark/core): Improve VM
Improve support for external requires. Allows external requires to be called like so: ``` Embark.events.on('runcode:ready', () => { Embark.events.emit('runcode:register', 'generateClass', require('eth-contract-class'), false); Embark.registerCustomContractGenerator(contract => { return ` ${contract.className} = generateClass(${JSON.stringify(contract.abiDefinition)}, '${contract.code}'); ${contract.className}Instance = new ${contract.className}(web3, '${contract.deployedAddress}'); `; }); }); Embark.events.once('contracts:deploy:afterAll', () => { Embark.events.request('runcode:eval', 'SimpleStorageInstance', (err, SimpleStorageInstance) => { if(err) return console.error(err); SimpleStorageInstance.get().then((result) => { console.log(`=====> SimpleStorageInstance.get(): ${result}`); }); }); }); ``` * Add `runcode:ready` that is fired when the VM is ready to accept registration of variables. * Add support for registration of ES6 modules * Add callback to `registerVar` and `setupNodeVm` in the `VM` class. * Add support for retaining modified sandbox values across new VM instances. * Add VM unit tests for external reaquries and modified sandbox state.
This commit is contained in:
parent
df872fdd5b
commit
c1a5bfee3c
@ -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) {
|
||||
|
@ -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<null>) {
|
||||
// Disallow `eval` and `require`, just in case.
|
||||
if (code === eval || code === require) { return; }
|
||||
|
||||
// handle ES6 modules
|
||||
if (isEs6Module(code)) {
|
||||
code = code.default;
|
||||
}
|
||||
|
||||
this.updateState((_err) => {
|
||||
this.options.sandbox[varName] = code;
|
||||
this.setupNodeVm();
|
||||
this.setupNodeVm(cb);
|
||||
});
|
||||
}
|
||||
|
||||
private updateState(cb: Callback<null>) {
|
||||
if (!this.vm) { return cb(); }
|
||||
|
||||
// update sandbox state from VM
|
||||
each(Object.keys(this.options.sandbox), (sandboxVar: string, next: Callback<null>) => {
|
||||
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<null>) {
|
||||
this.vm = new NodeVM(this.options);
|
||||
cb();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user