mirror of
https://github.com/embarklabs/embark.git
synced 2025-01-11 06:16:01 +00:00
feat(@embark/core): Auto generate EmbarkJS events
For every provider registered and set with EmbarkJS, auto generate events to inform other modules of the state of the provider. Any provider that belongs to EmbarkJS (currently Blockchain, Names, Messages, and Storage) will have both a `runcode:<provider name>:providerRegistered` and a `runcode:<provider name>:providerSet` event created automatically when the `CodeRunner` is instantiated. Once the `registerProvider` code is run through the VM, ie `EmbarkJS.Blockchain.registerProvider(…)`, the corresponding event will be fired, ie `runcode:blockchain:providerRegistered`. Likewise, once the `setProvider` code is run through the VM, ie `EmbarkJS.Blockchain.setProvider(…)`, the corresponding event will be fired, ie `runcode:blockchain:providerSet`. Additional updates/fixes with this PR: * Move `CodeRunner` to TypeScript * Fix console errors with ENS and Storage due to a recent PR that waits for `code-generator:ready`. The solution here was to ensure that `code-generator:ready` is requested, so that premature events can be handled.
This commit is contained in:
parent
58ea3d9c30
commit
d378ccf150
28
packages/embark-typings/src/embark.d.ts
vendored
28
packages/embark-typings/src/embark.d.ts
vendored
@ -8,24 +8,26 @@ export interface Events {
|
||||
setCommandHandler(name: string, callback: (options: any, cb: () => void) => void): void;
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
contractsFiles: any[];
|
||||
embarkConfig: {
|
||||
contracts: string[] | string;
|
||||
config: {
|
||||
contracts: string;
|
||||
};
|
||||
versions: {
|
||||
solc: string;
|
||||
}
|
||||
};
|
||||
reloadConfig(): void;
|
||||
}
|
||||
|
||||
export interface Embark {
|
||||
events: Events;
|
||||
registerAPICall: any;
|
||||
registerConsoleCommand: any;
|
||||
logger: Logger;
|
||||
fs: any;
|
||||
config: {
|
||||
contractsFiles: any[];
|
||||
embarkConfig: {
|
||||
contracts: string[] | string;
|
||||
config: {
|
||||
contracts: string;
|
||||
};
|
||||
versions: {
|
||||
solc: string;
|
||||
}
|
||||
};
|
||||
reloadConfig(): void;
|
||||
};
|
||||
config: Config;
|
||||
registerActionForEvent(name: string, action: (callback: () => void) => void): void;
|
||||
}
|
||||
|
2
packages/embark-typings/src/logger.d.ts
vendored
2
packages/embark-typings/src/logger.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
export interface Logger {
|
||||
info(text: string): void;
|
||||
error(text: string): void;
|
||||
error(text: string, ...args: Array<string|Error>): void;
|
||||
}
|
||||
|
@ -1,131 +0,0 @@
|
||||
const VM = require('./vm');
|
||||
const fs = require('../../core/fs');
|
||||
const EmbarkJS = require('embarkjs');
|
||||
const Web3 = require('web3');
|
||||
|
||||
class CodeRunner {
|
||||
constructor(embark, options) {
|
||||
this.ready = false;
|
||||
this.blockchainConnected = false;
|
||||
this.config = embark.config;
|
||||
this.plugins = embark.plugins;
|
||||
this.logger = embark.logger;
|
||||
this.events = embark.events;
|
||||
this.ipc = options.ipc;
|
||||
this.vm = new VM({
|
||||
sandbox: {
|
||||
EmbarkJS,
|
||||
Web3
|
||||
},
|
||||
require: {
|
||||
mock: {
|
||||
fs: {
|
||||
access: fs.access,
|
||||
diagramPath: fs.diagramPath,
|
||||
dappPath: fs.dappPath,
|
||||
embarkPath: fs.embarkPath,
|
||||
existsSync: fs.existsSync,
|
||||
ipcPath: fs.ipcPath,
|
||||
pkgPath: fs.pkgPath,
|
||||
readFile: fs.readFile,
|
||||
readFileSync: fs.readFileSync,
|
||||
readJSONSync: fs.readJSONSync,
|
||||
readdir: fs.readdir,
|
||||
readdirSync: fs.readdirSync,
|
||||
stat: fs.stat,
|
||||
statSync: fs.statSync,
|
||||
tmpDir: fs.tmpDir
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this.logger);
|
||||
this.embark = embark;
|
||||
this.commands = [];
|
||||
|
||||
this.registerEvents();
|
||||
this.registerCommands();
|
||||
this.events.emit('runcode:ready');
|
||||
this.ready = true;
|
||||
}
|
||||
|
||||
registerEvents() {
|
||||
this.events.on("runcode:register", this.registerVar.bind(this));
|
||||
|
||||
this.events.on("runcode:init-console-code:updated", (code, cb) => {
|
||||
this.evalCode(code, (err, _result) => {
|
||||
if(err) {
|
||||
this.logger.error("Error running init console code: ", err.message || err);
|
||||
}
|
||||
else if(code.includes("EmbarkJS.Blockchain.setProvider")) {
|
||||
this.events.emit('runcode:blockchain:connected');
|
||||
this.blockchainConnected = true;
|
||||
}
|
||||
cb();
|
||||
});
|
||||
});
|
||||
|
||||
this.events.on("runcode:embarkjs-code:updated", (code, cb) => {
|
||||
this.evalCode(code, (err, _result) => {
|
||||
if(err) {
|
||||
this.logger.error("Error running embarkjs code: ", err.message || err);
|
||||
}
|
||||
cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
registerCommands() {
|
||||
this.events.setCommandHandler('runcode:getContext', (cb) => {
|
||||
cb(this.vm.options.sandbox);
|
||||
});
|
||||
this.events.setCommandHandler('runcode:eval', this.evalCode.bind(this));
|
||||
this.events.setCommandHandler('runcode:ready', (cb) => {
|
||||
if (this.ready) {
|
||||
return cb();
|
||||
}
|
||||
this.events.once("runcode:ready", cb);
|
||||
});
|
||||
this.events.setCommandHandler('runcode:blockchain:connected', (cb) => {
|
||||
if (this.blockchainConnected) {
|
||||
return cb();
|
||||
}
|
||||
this.events.once("runcode:blockchain:connected", cb);
|
||||
});
|
||||
this.events.setCommandHandler('runcode:embarkjs:reset', this.resetEmbarkJS.bind(this));
|
||||
}
|
||||
|
||||
resetEmbarkJS(cb) {
|
||||
this.events.request("code-generator:embarkjs:provider-code", (code) => {
|
||||
this.evalCode(code, (err) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
this.events.request("code-generator:embarkjs:init-provider-code", (providerCode) => {
|
||||
this.evalCode(providerCode, (err, _result) => {
|
||||
cb(err);
|
||||
}, true);
|
||||
});
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
registerVar(varName, code, cb = () => {}) {
|
||||
this.vm.registerVar(varName, code, cb);
|
||||
}
|
||||
|
||||
evalCode(code, cb, tolerateError = false) {
|
||||
cb = cb || function () {};
|
||||
|
||||
if (!code) return cb(null, '');
|
||||
|
||||
this.vm.doEval(code, tolerateError, (err, result) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
cb(null, result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CodeRunner;
|
179
packages/embark/src/lib/modules/codeRunner/index.ts
Normal file
179
packages/embark/src/lib/modules/codeRunner/index.ts
Normal file
@ -0,0 +1,179 @@
|
||||
import VM from "./vm";
|
||||
const fs = require("../../core/fs");
|
||||
import { Callback, Embark, Events, Logger } from "embark";
|
||||
import Web3 from "web3";
|
||||
const EmbarkJS = require("embarkjs");
|
||||
|
||||
export enum ProviderEventType {
|
||||
ProviderRegistered = "providerRegistered",
|
||||
ProviderSet = "providerSet",
|
||||
}
|
||||
|
||||
class CodeRunner {
|
||||
private ready: boolean = false;
|
||||
private blockchainConnected: boolean = false;
|
||||
private logger: Logger;
|
||||
private events: Events;
|
||||
private vm: VM;
|
||||
private providerStates: { [key: string]: boolean } = {};
|
||||
constructor(embark: Embark, _options: any) {
|
||||
this.logger = embark.logger;
|
||||
this.events = embark.events;
|
||||
this.vm = new VM({
|
||||
require: {
|
||||
mock: {
|
||||
fs: {
|
||||
access: fs.access,
|
||||
dappPath: fs.dappPath,
|
||||
diagramPath: fs.diagramPath,
|
||||
embarkPath: fs.embarkPath,
|
||||
existsSync: fs.existsSync,
|
||||
ipcPath: fs.ipcPath,
|
||||
pkgPath: fs.pkgPath,
|
||||
readFile: fs.readFile,
|
||||
readFileSync: fs.readFileSync,
|
||||
readJSONSync: fs.readJSONSync,
|
||||
readdir: fs.readdir,
|
||||
readdirSync: fs.readdirSync,
|
||||
stat: fs.stat,
|
||||
statSync: fs.statSync,
|
||||
tmpDir: fs.tmpDir,
|
||||
},
|
||||
},
|
||||
},
|
||||
sandbox: {
|
||||
EmbarkJS,
|
||||
Web3,
|
||||
},
|
||||
}, this.logger);
|
||||
|
||||
this.registerEvents();
|
||||
this.registerCommands();
|
||||
this.events.emit("runcode:ready");
|
||||
this.ready = true;
|
||||
}
|
||||
|
||||
private generateListener(provider: string, eventType: ProviderEventType) {
|
||||
const providerStateName = `${provider}:${eventType}`;
|
||||
const eventName = `runcode:${providerStateName}`;
|
||||
this.providerStates[`${provider}:${eventType}`] = false;
|
||||
this.events.setCommandHandler(eventName, (cb) => {
|
||||
if (this.providerStates[providerStateName]) {
|
||||
return cb();
|
||||
}
|
||||
this.events.once(eventName, cb);
|
||||
});
|
||||
}
|
||||
|
||||
private fireEmbarkJSEvents(code: string) {
|
||||
const regexRegister = /EmbarkJS\.(.*)\.registerProvider/gm;
|
||||
const regexSet = /EmbarkJS\.(.*)\.setProvider/gm;
|
||||
let matches = regexRegister.exec(code);
|
||||
if (matches) {
|
||||
let [, provider] = matches;
|
||||
provider = provider.toLowerCase();
|
||||
this.providerStates[`${provider}:${ProviderEventType.ProviderRegistered}`] = true;
|
||||
this.events.emit(`runcode:${provider}:${ProviderEventType.ProviderRegistered}`);
|
||||
}
|
||||
matches = regexSet.exec(code);
|
||||
if (matches) {
|
||||
let [, provider] = matches;
|
||||
provider = provider.toLowerCase();
|
||||
this.providerStates[`${provider}:${ProviderEventType.ProviderSet}`] = true;
|
||||
this.events.emit(`runcode:${provider}:${ProviderEventType.ProviderSet}`);
|
||||
}
|
||||
}
|
||||
|
||||
private registerEvents() {
|
||||
this.events.on("runcode:register", this.registerVar.bind(this));
|
||||
|
||||
this.events.on("runcode:init-console-code:updated", (code: string, cb: Callback<null>) => {
|
||||
this.evalCode(code, (err, _result) => {
|
||||
if (err) {
|
||||
this.logger.error("Error running init console code: ", err.message || err);
|
||||
}
|
||||
this.fireEmbarkJSEvents(code);
|
||||
cb();
|
||||
});
|
||||
});
|
||||
|
||||
this.events.on("runcode:embarkjs-code:updated", (code: string, cb: Callback<any>) => {
|
||||
this.evalCode(code, (err, _result) => {
|
||||
if (err) {
|
||||
this.logger.error("Error running embarkjs code: ", err.message || err);
|
||||
}
|
||||
this.fireEmbarkJSEvents(code);
|
||||
cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private registerCommands() {
|
||||
this.events.setCommandHandler("runcode:getContext", (cb) => {
|
||||
cb(this.vm.options.sandbox);
|
||||
});
|
||||
this.events.setCommandHandler("runcode:eval", this.evalCode.bind(this));
|
||||
this.events.setCommandHandler("runcode:ready", (cb) => {
|
||||
if (this.ready) {
|
||||
return cb();
|
||||
}
|
||||
this.events.once("runcode:ready", cb);
|
||||
});
|
||||
this.events.setCommandHandler("runcode:embarkjs:reset", this.resetEmbarkJS.bind(this));
|
||||
|
||||
// register listeners for when EmbarkJS runs registerProvider through the console.
|
||||
// For example, when `EmbarkJS.Storage.registerProvider(...)` is run through the console,
|
||||
// emit the `runcode:storage:providerRegistered` event, and fire any requests attached to it
|
||||
Object.keys(EmbarkJS)
|
||||
.filter((propName) => EmbarkJS[propName].hasOwnProperty("registerProvider"))
|
||||
.forEach((providerName) => {
|
||||
this.generateListener(providerName.toLowerCase(), ProviderEventType.ProviderRegistered);
|
||||
});
|
||||
|
||||
// register listeners for when EmbarkJS runs setProvider through the console.
|
||||
// For example, when `EmbarkJS.Storage.setProvider(...)` is run through the console,
|
||||
// emit the `runcode:storage:providerSet` event, and fire any requests attached to it
|
||||
Object.keys(EmbarkJS)
|
||||
.filter((propName) => EmbarkJS[propName].hasOwnProperty("setProvider"))
|
||||
.forEach((providerName) => {
|
||||
this.generateListener(providerName.toLowerCase(), ProviderEventType.ProviderSet);
|
||||
});
|
||||
}
|
||||
|
||||
private resetEmbarkJS(cb: Callback<null>) {
|
||||
this.events.request("code-generator:embarkjs:provider-code", (code: string) => {
|
||||
this.evalCode(code, (err) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
this.events.request("code-generator:embarkjs:init-provider-code", (providerCode: string) => {
|
||||
this.evalCode(providerCode, (errInitProvider, _result) => {
|
||||
cb(errInitProvider);
|
||||
}, true);
|
||||
});
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
private registerVar(varName: string, code: any, cb = () => { }) {
|
||||
this.vm.registerVar(varName, code, cb);
|
||||
}
|
||||
|
||||
private evalCode(code: string, cb: Callback < any >, tolerateError = false) {
|
||||
cb = cb || (() => { });
|
||||
|
||||
if (!code) {
|
||||
return cb(null, "");
|
||||
}
|
||||
|
||||
this.vm.doEval(code, tolerateError, (err, result) => {
|
||||
if (err) {
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
cb(null, result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CodeRunner;
|
@ -31,7 +31,7 @@ class VM {
|
||||
* Currently, all of the allowed external requires appear in the EmbarkJS scripts. If
|
||||
* the requires change in any of the EmbarkJS scripts, they will need to be updated here.
|
||||
*/
|
||||
private options: NodeVMOptions = {
|
||||
private _options: NodeVMOptions = {
|
||||
require: {
|
||||
builtin: ["path", "rxjs", "util"],
|
||||
external: [
|
||||
@ -54,11 +54,15 @@ class VM {
|
||||
* @param {Logger} logger Logger.
|
||||
*/
|
||||
constructor(options: NodeVMOptions, private logger: Logger) {
|
||||
this.options = recursiveMerge(this.options, options);
|
||||
this._options = recursiveMerge(this._options, options);
|
||||
|
||||
this.setupNodeVm(() => { });
|
||||
}
|
||||
|
||||
public get options(): NodeVMOptions {
|
||||
return this._options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a snippet of code such that the last expression is returned,
|
||||
* so long as it is not an assignment.
|
||||
@ -128,7 +132,7 @@ class VM {
|
||||
}
|
||||
|
||||
this.updateState(() => {
|
||||
this.options.sandbox[varName] = code;
|
||||
this._options.sandbox[varName] = code;
|
||||
this.setupNodeVm(cb);
|
||||
});
|
||||
}
|
||||
@ -137,10 +141,10 @@ class VM {
|
||||
if (!this.vm) { return cb(); }
|
||||
|
||||
// update sandbox state from VM
|
||||
each(Object.keys(this.options.sandbox), (sandboxVar: string, next: Callback<null>) => {
|
||||
each(Object.keys(this._options.sandbox), (sandboxVar: string, next: Callback<null>) => {
|
||||
this.doEval(sandboxVar, false, (err?: Error | null, result?: any) => {
|
||||
if (!err) {
|
||||
this.options.sandbox[sandboxVar] = result;
|
||||
this._options.sandbox[sandboxVar] = result;
|
||||
}
|
||||
next(err);
|
||||
});
|
||||
@ -153,9 +157,9 @@ class VM {
|
||||
* by calling @function {registerVar}.
|
||||
*/
|
||||
private setupNodeVm(cb: Callback<null>) {
|
||||
this.vm = new NodeVM(this.options);
|
||||
this.vm = new NodeVM(this._options);
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = VM;
|
||||
export default VM;
|
||||
|
@ -17,6 +17,7 @@ const Templates = {
|
||||
|
||||
class CodeGenerator {
|
||||
constructor(embark, options) {
|
||||
this.ready = false;
|
||||
this.blockchainConfig = embark.config.blockchainConfig || {};
|
||||
this.embarkConfig = embark.config.embarkConfig;
|
||||
this.fs = embark.fs;
|
||||
@ -34,6 +35,7 @@ class CodeGenerator {
|
||||
this.events = embark.events;
|
||||
|
||||
this.listenToCommands();
|
||||
this.ready = true;
|
||||
this.events.emit('code-generator:ready');
|
||||
}
|
||||
|
||||
@ -87,6 +89,13 @@ class CodeGenerator {
|
||||
this.events.setCommandHandler("code-generator:embarkjs:build", (cb) => {
|
||||
this.buildEmbarkJS(cb);
|
||||
});
|
||||
|
||||
this.events.setCommandHandler('code-generator:ready', (cb) => {
|
||||
if (this.ready) {
|
||||
return cb();
|
||||
}
|
||||
this.events.once('code-generator:ready', cb);
|
||||
});
|
||||
}
|
||||
|
||||
generateContracts(contractsList, useEmbarkJS, isDeployment, useLoader) {
|
||||
|
@ -216,7 +216,7 @@ class Console {
|
||||
if (this.isEmbarkConsole) {
|
||||
return next();
|
||||
}
|
||||
this.events.request("runcode:blockchain:connected", next);
|
||||
this.events.request("runcode:blockchain:providerSet", next);
|
||||
},
|
||||
(next: any) => {
|
||||
if (this.isEmbarkConsole) {
|
||||
|
@ -372,7 +372,7 @@ class ENS {
|
||||
return this.logger.error(err.message || err);
|
||||
}
|
||||
|
||||
this.events.once('code-generator:ready', () => {
|
||||
this.events.request('code-generator:ready', () => {
|
||||
this.events.request('code-generator:symlink:generate', location, 'eth-ens-namehash', (err, symlinkDest) => {
|
||||
if (err) {
|
||||
this.logger.error(__('Error creating a symlink to eth-ens-namehash'));
|
||||
|
@ -34,7 +34,9 @@ class Storage {
|
||||
};
|
||||
|
||||
this.embark.addProviderInit('storage', code, shouldInit);
|
||||
this.embark.addConsoleProviderInit('storage', code, shouldInit);
|
||||
this.embark.events.request("runcode:storage:providerRegistered", () => {
|
||||
this.embark.addConsoleProviderInit('storage', code, shouldInit);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*globals describe, it*/
|
||||
const TestLogger = require('../lib/utils/test_logger');
|
||||
const VM = require('../lib/modules/codeRunner/vm');
|
||||
const VM = require('../lib/modules/codeRunner/vm').default;
|
||||
const {expect} = require('chai');
|
||||
|
||||
describe('embark.vm', function () {
|
||||
|
Loading…
x
Reference in New Issue
Block a user