Merge pull request #751 from embark-framework/feature/embarkjs-console

Feature/embarkjs console
This commit is contained in:
Iuri Matias 2018-08-31 07:46:54 -04:00 committed by GitHub
commit 46076c8d4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 212 additions and 181 deletions

View File

@ -55,7 +55,6 @@ class EmbarkController {
let self = this;
self.context = options.context || [constants.contexts.run, constants.contexts.build];
let Dashboard = require('./dashboard/dashboard.js');
let REPL = require('./dashboard/repl.js');
let webServerConfig = {
enabled: options.runWebserver
@ -93,14 +92,6 @@ class EmbarkController {
async.parallel([
function startDashboard(callback) {
if (!options.useDashboard) {
new REPL({
env: engine.env,
plugins: engine.plugins,
version: engine.version,
events: engine.events,
logger: engine.logger,
ipc: engine.ipc
}).startConsole();
return callback();
}
@ -109,8 +100,7 @@ class EmbarkController {
logger: engine.logger,
plugins: engine.plugins,
version: self.version,
env: engine.env,
ipc: engine.ipc
env: engine.env
});
dashboard.start(function () {
engine.logger.info(__('dashboard start'));
@ -133,6 +123,7 @@ class EmbarkController {
engine.startService("storage");
engine.startService("codeGenerator");
engine.startService("namingSystem");
engine.startService("console");
engine.events.on('check:backOnline:Ethereum', function () {
engine.logger.info(__('Ethereum node detected') + '..');
@ -279,11 +270,14 @@ class EmbarkController {
engine.startService("storage");
engine.startService("codeGenerator");
engine.startService("webServer");
engine.startService("namingSystem");
engine.startService("console");
return callback();
}
engine.startService("codeRunner");
engine.startService("console");
callback();
});
},
@ -315,6 +309,7 @@ class EmbarkController {
if(engine.ipc.connected && engine.ipc.isClient()) {
return callback();
}
engine.config.reloadConfig();
engine.events.request('deploy:contracts', function (err) {
callback(err);
});
@ -331,15 +326,7 @@ class EmbarkController {
});
},
function startREPL(callback) {
let repl = new REPL({
env: engine.env,
plugins: engine.plugins,
version: engine.version,
events: engine.events,
logger: engine.logger,
ipc: engine.ipc
});
repl.start(callback);
new REPL({events: engine.events, env: engine.env}).start(callback);
}
], function (err, _result) {
if (err) {

View File

@ -2,7 +2,6 @@ let async = require('async');
let windowSize = require('window-size');
let Monitor = require('./monitor.js');
let Console = require('./console.js');
class Dashboard {
constructor(options) {
@ -11,7 +10,6 @@ class Dashboard {
this.plugins = options.plugins;
this.version = options.version;
this.env = options.env;
this.ipc = options.ipc;
this.events.on('firstDeploymentDone', this.checkWindowSize.bind(this));
this.events.on('outputDone', this.checkWindowSize.bind(this));
@ -25,44 +23,23 @@ class Dashboard {
}
start(done) {
let console, monitor;
let self = this;
let monitor;
async.waterfall([
function startConsole(callback) {
console = new Console({
events: self.events,
plugins: self.plugins,
version: self.version,
ipc: self.ipc,
logger: self.logger
});
callback();
},
function startMonitor(callback) {
monitor = new Monitor({env: self.env, console: console, events: self.events});
self.logger.logFunction = monitor.logEntry;
monitor = new Monitor({env: this.env, events: this.events});
this.logger.logFunction = monitor.logEntry;
self.events.on('contractsState', monitor.setContracts);
self.events.on('status', monitor.setStatus.bind(monitor));
self.events.on('servicesState', monitor.availableServices.bind(monitor));
this.events.on('contractsState', monitor.setContracts);
this.events.on('status', monitor.setStatus.bind(monitor));
this.events.on('servicesState', monitor.availableServices.bind(monitor));
self.events.setCommandHandler("console:command", monitor.executeCmd.bind(monitor));
this.events.setCommandHandler("console:command", monitor.executeCmd.bind(monitor));
self.logger.info('========================'.bold.green);
self.logger.info((__('Welcome to Embark') + ' ' + self.version).yellow.bold);
self.logger.info('========================'.bold.green);
this.logger.info('========================'.bold.green);
this.logger.info((__('Welcome to Embark') + ' ' + this.version).yellow.bold);
this.logger.info('========================'.bold.green);
// TODO: do this after monitor is rendered
callback();
}
], function () {
self.console = console;
self.monitor = monitor;
done();
});
done();
}
}
module.exports = Dashboard;

View File

@ -366,11 +366,10 @@ class Monitor {
}
executeCmd(cmd, cb) {
const self = this;
self.logText.log('console> '.bold.green + cmd);
self.console.executeCmd(cmd, function (err, result) {
this.logText.log('console> '.bold.green + cmd);
this.events.request('console:executeCmd', cmd, (err, result) => {
let message = err || result;
self.logText.log(message);
this.logText.log(message);
if (cb) {
cb(message);
}

View File

@ -1,30 +1,13 @@
const repl = require("repl");
const util = require("util");
const Console = require('./console.js');
class REPL {
constructor(options) {
this.logger = options.logger;
this.env = options.env;
this.plugins = options.plugins;
this.events = options.events;
this.version = options.version;
this.ipc = options.ipc;
}
startConsole(){
this.console = new Console({
events: this.events,
plugins: this.plugins,
version: this.version,
ipc: this.ipc,
logger: this.logger
});
this.env = options.env
}
enhancedEval(cmd, context, filename, callback) {
this.console.executeCmd(cmd.trim(), callback);
this.events.request('console:executeCmd', cmd.trim(), callback);
}
enhancedWriter(output) {
@ -36,7 +19,6 @@ class REPL {
}
start(done) {
this.startConsole();
this.replServer = repl.start({
prompt: "Embark (" + this.env + ") > ",
useGlobal: true,

View File

@ -63,10 +63,7 @@ Config.prototype.loadConfigFiles = function(options) {
this.loadStorageConfigFile();
this.loadCommunicationConfigFile();
this.loadNameSystemConfigFile();
this.loadContractsConfigFile();
this.loadPipelineConfigFile();
this.loadContractsConfigFile();
this.loadExternalContractsFiles();
this.loadWebServerConfigFile();
@ -82,7 +79,6 @@ Config.prototype.reloadConfig = function() {
this.loadStorageConfigFile();
this.loadCommunicationConfigFile();
this.loadNameSystemConfigFile();
this.loadContractsConfigFile();
this.loadPipelineConfigFile();
this.loadContractsConfigFile();
this.loadExternalContractsFiles();

View File

@ -60,6 +60,7 @@ class Engine {
"fileWatcher": this.fileWatchService,
"webServer": this.webServerService,
"namingSystem": this.namingSystem,
"console": this.console,
"web3": this.web3Service,
"libraryManager": this.libraryManagerService,
"processManager": this.processManagerService,
@ -129,9 +130,21 @@ class Engine {
this.registerModule('ens');
}
console(_options) {
this.registerModule('console', {
events: this.events,
plugins: this.plugins,
version: this.version,
ipc: this.ipc,
logger: this.logger,
config: this.config
});
}
codeRunnerService(_options) {
const CodeRunner = require('./modules/coderunner/codeRunner.js');
this.codeRunner = new CodeRunner({
config: this.config,
plugins: this.plugins,
events: this.events,
logger: this.logger,

View File

@ -1,76 +1,91 @@
// still needs to be run on a separate file due to the global context
var RunCode = require('./runCode.js');
const RunCode = require('./runCode.js');
class CodeRunner {
constructor(options) {
this.config = options.config;
this.plugins = options.plugins;
this.logger = options.logger;
this.events = options.events;
this.ipc = options.ipc;
this.commands = [];
this.runCode = new RunCode();
let self = this;
this.registerIpcEvents();
this.IpcClientListen();
this.registerEvents();
this.registerCommands();
}
if (this.ipc.isServer()) {
this.ipc.on('runcode:getCommands', (_err, callback) => {
let result = {web3Config: self.runCode.getWeb3Config(), commands: self.commands};
callback(null, result);
});
registerIpcEvents() {
if (!this.ipc.isServer()) {
return;
}
if (this.ipc.isClient() && this.ipc.connected) {
this.ipc.listenTo('runcode:newCommand', function (command) {
if (command.varName) {
self.events.emit("runcode:register", command.varName, command.code);
} else {
self.events.request("runcode:eval", command.code);
}
});
}
this.events.on("runcode:register", (varName, code) => {
if (self.ipc.isServer() && varName !== 'web3') {
self.commands.push({varName, code});
self.ipc.broadcast("runcode:newCommand", {varName, code});
}
self.runCode.registerVar(varName, code);
});
this.events.setCommandHandler('runcode:getContext', (cb) => {
cb(self.runCode.context);
});
this.events.setCommandHandler('runcode:eval', (code, cb, forConsoleOnly = false) => {
if (!cb) {
cb = function() {};
}
const awaitIdx = code.indexOf('await');
if (awaitIdx > -1) {
if (awaitIdx < 2) {
let end = code.length;
if (code[end - 1] === ';') {
end--; // Remove the `;` because we add function calls
}
code = code.substring(5, end); // remove await keyword
} else {
code = `(async function() {${code}})();`;
}
}
let result = self.runCode.doEval(code);
if (forConsoleOnly && self.ipc.isServer()) {
self.commands.push({code});
self.ipc.broadcast("runcode:newCommand", {code});
}
if (result instanceof Promise) {
return result.then((value) => cb(null, value)).catch(cb);
}
cb(null, result);
this.ipc.on('runcode:getCommands', (_err, callback) => {
let result = {web3Config: this.runCode.getWeb3Config(), commands: this.commands};
callback(null, result);
});
}
IpcClientListen() {
if (!this.ipc.isClient() || !this.ipc.connected) {
return;
}
this.ipc.listenTo('runcode:newCommand', (command) => {
if (command.varName) {
this.events.emit("runcode:register", command.varName, command.code);
} else {
this.events.request("runcode:eval", command.code);
}
});
}
registerEvents() {
this.events.on("runcode:register", this.registerVar.bind(this));
}
registerCommands() {
this.events.setCommandHandler('runcode:getContext', (cb) => {
cb(this.runCode.context);
});
this.events.setCommandHandler('runcode:eval', this.evalCode.bind(this));
}
registerVar(varName, code, toRecord = true) {
if (this.ipc.isServer() && toRecord) {
this.commands.push({varName, code});
this.ipc.broadcast("runcode:newCommand", {varName, code});
}
this.runCode.registerVar(varName, code);
}
evalCode(code, cb, forConsoleOnly = false) {
cb = cb || function() {};
const awaitIdx = code.indexOf('await');
if (awaitIdx > -1) {
if (awaitIdx < 2) {
let end = code.length;
if (code[end - 1] === ';') {
end--; // Remove the `;` because we add function calls
}
code = code.substring(5, end); // remove await keyword
} else {
code = `(async function() {${code}})();`;
}
}
let result = this.runCode.doEval(code);
if (forConsoleOnly && this.ipc.isServer()) {
this.commands.push({code});
this.ipc.broadcast("runcode:newCommand", {code});
}
if (result instanceof Promise) {
return result.then((value) => cb(null, value)).catch(cb);
}
cb(null, result);
}
}
module.exports = CodeRunner;

View File

@ -2,11 +2,15 @@ const vm = require('vm');
class RunCode {
constructor() {
this.context = Object.assign({}, global.this);
this.context = Object.assign({}, {global, console, exports, require, module, __filename, __dirname});
}
doEval(code) {
return vm.runInNewContext(code, this.context);
try {
return vm.runInNewContext(code, this.context);
} catch(e) {
console.error(e.message);
}
}
registerVar(varName, code) {
@ -16,6 +20,7 @@ class RunCode {
if (varName === 'web3') {
global.web3 = code;
}
this.context["global"][varName] = code;
this.context[varName] = code;
}

View File

@ -25,6 +25,7 @@ var Plugin = function(options) {
this.imports = [];
this.embarkjs_code = [];
this.embarkjs_init_code = {};
this.embarkjs_init_console_code = {};
this.afterContractsDeployActions = [];
this.onDeployActions = [];
this.eventActions = {};
@ -189,6 +190,12 @@ Plugin.prototype.addProviderInit = function(providerType, code, initCondition) {
this.addPluginType('initCode');
};
Plugin.prototype.addConsoleProviderInit = function(providerType, code, initCondition) {
this.embarkjs_init_console_code[providerType] = this.embarkjs_init_console_code[providerType] || [];
this.embarkjs_init_console_code[providerType].push([code, initCondition]);
this.addPluginType('initConsoleCode');
};
Plugin.prototype.registerImportFile = function(importName, importLocation) {
this.imports.push([importName, importLocation]);
this.addPluginType('imports');

View File

@ -254,7 +254,7 @@ class BlockchainConnector {
registerWeb3Object() {
// doesn't feel quite right, should be a cmd or plugin method
// can just be a command without a callback
this.events.emit("runcode:register", "web3", this.web3);
this.events.emit("runcode:register", "web3", this.web3, false);
}
subscribeToPendingTransactions() {

View File

@ -99,6 +99,9 @@ class CodeGenerator {
self.buildPlaceholderPage(cb);
});
self.events.setCommandHandler('code-generator:embarkjs:provider-code', (cb) => {
cb(self.getEmbarkJsProviderCode());
});
}
generateContext() {
@ -117,7 +120,7 @@ class CodeGenerator {
result += Templates.main_context();
result += Templates.load_manager();
result += Templates.define_when_env_loaded();
if (self.blockchainConfig === {} || self.blockchainConfig.enabled === false) {
return result;
}
@ -216,7 +219,6 @@ class CodeGenerator {
return result;
}
generateStorageInitialization(useEmbarkJS) {
if (!useEmbarkJS || this.storageConfig === {}) return "";
@ -313,18 +315,18 @@ class CodeGenerator {
web3Location = web3Location.replace(/\\/g, '/'); // Import paths must always have forward slashes
code += "\nimport Web3 from '" + web3Location + "';\n";
code += "\nimport web3 from 'Embark/web3';\n";
code += "\nimport IpfsApi from 'ipfs-api';\n";
next();
},
function getJSCode(next) {
code += "\n" + embarkjsCode + "\n";
let pluginsWithCode = self.plugins.getPluginsFor('embarkjsCode');
for (let plugin of pluginsWithCode) {
code += plugin.embarkjs_code.join('\n');
}
code += self.getEmbarkJsProviderCode();
code += self.generateCommunicationInitialization(true);
code += self.generateStorageInitialization(true);
code += self.generateNamesInitialization(true);
next();
},
function writeFile(next) {
@ -337,6 +339,12 @@ class CodeGenerator {
});
}
getEmbarkJsProviderCode() {
return this.plugins.getPluginsFor('embarkjsCode').reduce((code, plugin) => (
code += plugin.embarkjs_code.join('\n')
), '');
}
buildContractJS(contractName, contractJSON, cb) {
let contractCode = "";
contractCode += "import web3 from 'Embark/web3';\n";

View File

@ -1,16 +1,22 @@
let utils = require('../../lib/utils/utils.js');
let utils = require('../../utils/utils');
const EmbarkJS = require('embarkjs');
const IpfsApi = require('ipfs-api');
const Web3 = require('web3');
class Console {
constructor(options) {
constructor(_embark, options) {
this.events = options.events;
this.plugins = options.plugins;
this.version = options.version;
this.logger = options.logger;
this.ipc = options.ipc;
this.config = options.config;
if (this.ipc.isServer()) {
this.ipc.on('console:executeCmd', this.executeCmd.bind(this));
}
this.events.setCommandHandler("console:executeCmd", this.executeCmd.bind(this));
this.registerEmbarkJs();
}
processEmbarkCmd (cmd) {
@ -65,6 +71,44 @@ class Console {
callback(e);
}
}
registerEmbarkJs() {
this.events.emit('runcode:register', 'IpfsApi', IpfsApi, false);
this.events.emit('runcode:register', 'Web3', Web3, false);
this.events.emit('runcode:register', 'EmbarkJS', EmbarkJS, false);
this.events.on('code-generator-ready', () => {
if (this.ipc.connected) {
return;
}
this.events.request('code-generator:embarkjs:provider-code', (code) => {
const func = () => {};
this.events.request('runcode:eval', code, func, true);
this.events.request('runcode:eval', this.getInitProviderCode(), func, true);
});
});
}
getInitProviderCode() {
const codeTypes = {
'communication': this.config.communicationConfig || {},
'names': this.config.namesystemConfig || {},
'storage': this.config.storageConfig || {}
};
return this.plugins.getPluginsFor('initConsoleCode').reduce((acc, plugin) => {
Object.keys(codeTypes).forEach(codeTypeName => {
(plugin.embarkjs_init_console_code[codeTypeName] || []).forEach(initCode => {
let [block, shouldInit] = initCode;
if (shouldInit.call(plugin, codeTypes[codeTypeName])) {
acc += block;
}
});
});
return acc;
}, '');
}
}
module.exports = Console;

View File

@ -156,10 +156,10 @@ __embarkENS.setProvider = function (config) {
web3.eth.net.getId()
.then((id) => {
const registryAddress = self.registryAddresses[id] || config.registryAddress;
self.isAvailable = true;
self.ens = new EmbarkJS.Contract({abi: config.registryAbi, address: registryAddress});
self.registrar = new EmbarkJS.Contract({abi: config.registrarAbi, address: config.registrarAddress});
self.resolver = new EmbarkJS.Contract({abi: config.resolverAbi, address: config.resolverAddress});
self._isAvailable = true;
self.ens = new EmbarkJS.Contract({abi: config.registryAbi, address: registryAddress, web3: web3});
self.registrar = new EmbarkJS.Contract({abi: config.registrarAbi, address: config.registrarAddress, web3: web3});
self.resolver = new EmbarkJS.Contract({abi: config.resolverAbi, address: config.resolverAddress, web3: web3});
})
.catch(err => {
if (err.message.indexOf('Provider not set or invalid') > -1) {
@ -192,7 +192,7 @@ __embarkENS.resolve = function (name, callback) {
if (resolverAddress === voidAddress) {
return cb('Name not yet registered');
}
let resolverContract = new EmbarkJS.Contract({abi: this.resolverInterface, address: resolverAddress});
let resolverContract = new EmbarkJS.Contract({abi: this.resolverInterface, address: resolverAddress, web3: web3});
resolverContract.methods.addr(node).call(cb);
});
};
@ -221,7 +221,7 @@ __embarkENS.lookup = function (address, callback) {
if (resolverAddress === voidAddress) {
return cb('Address not associated to a resolver');
}
let resolverContract = new EmbarkJS.Contract({abi: this.resolverInterface, address: resolverAddress});
let resolverContract = new EmbarkJS.Contract({abi: this.resolverInterface, address: resolverAddress, web3: web3});
resolverContract.methods.name(node).call(cb);
});
};
@ -245,5 +245,5 @@ __embarkENS.registerSubDomain = function (name, address, callback) {
};
__embarkENS.isAvailable = function () {
return Boolean(this.isAvailable);
return Boolean(this._isAvailable);
};

View File

@ -307,7 +307,6 @@ class ENS {
}
addSetProvider(config) {
let code = "\nEmbarkJS.Names.setProvider('ens'," + JSON.stringify(config) + ");";
let shouldInit = (namesConfig) => {
@ -315,6 +314,7 @@ class ENS {
};
this.embark.addProviderInit('names', code, shouldInit);
this.embark.addConsoleProviderInit('names', code, shouldInit);
}
}

View File

@ -1,4 +1,4 @@
import IpfsApi from 'ipfs-api';
/*global IpfsApi*/
let __embarkIPFS = {};

View File

@ -35,6 +35,7 @@ class Storage {
};
this.embark.addProviderInit('storage', code, shouldInit);
this.embark.addConsoleProviderInit('storage', code, shouldInit);
}
}

View File

@ -73,21 +73,22 @@ class Whisper {
addSetProvider() {
let connection = this.communicationConfig.connection || {};
const shouldInit = (communicationConfig) => {
return (communicationConfig.provider === 'whisper' && communicationConfig.enabled === true);
};
// todo: make the add code a function as well
let config = {
const config = {
server: canonicalHost(connection.host || defaultHost),
port: connection.port || '8546',
type: connection.type || 'ws'
};
let code = `\nEmbarkJS.Messages.setProvider('whisper', ${JSON.stringify(config)});`;
let shouldInit = (communicationConfig) => {
return (communicationConfig.provider === 'whisper' && communicationConfig.enabled === true);
};
const code = `\nEmbarkJS.Messages.setProvider('whisper', ${JSON.stringify(config)});`;
this.embark.addProviderInit('communication', code, shouldInit);
const consoleConfig = Object.assign({}, config, {providerOptions: {headers: {Origin: "embark"}}});
const consoleCode = `\nEmbarkJS.Messages.setProvider('whisper', ${JSON.stringify(consoleConfig)});`;
this.embark.addConsoleProviderInit('communication', consoleCode, shouldInit);
}
}

View File

@ -12,7 +12,7 @@ __embarkWhisperNewWeb3.setProvider = function (options) {
provider = options.server + ':' + options.port;
}
// TODO: take into account type
self.web3 = new Web3(new Web3.providers.WebsocketProvider("ws://" + provider));
self.web3 = new Web3(new Web3.providers.WebsocketProvider("ws://" + provider, options.providerOptions));
self.getWhisperVersion(function (err, version) {
if (err) {
console.log("whisper not available");

View File

@ -1,4 +1,3 @@
let __MessageEvents = function() {
this.cb = function() {};
};

View File

@ -146,7 +146,7 @@ class ENS extends React.Component {
</FormGroup>
</Form>
<h3>Register subdomain for embark</h3>
<h3>Register subdomain</h3>
<Form inline onKeyDown={(e) => this.checkEnter(e, this.registerSubDomain)}>
<FormGroup>
{this.state.responseRegister &&

View File

@ -21,8 +21,7 @@ class App extends React.Component {
error: null,
activeKey: 1,
whisperEnabled: false,
storageEnabled: false,
ensEnabled: false
storageEnabled: false
};
}
@ -56,10 +55,6 @@ class App extends React.Component {
}).catch(() => {
this.setState({storageEnabled: false});
});
this.setState({
ensEnabled: EmbarkJS.Names.isAvailable()
});
});
}
@ -76,6 +71,7 @@ class App extends React.Component {
}
render() {
const ensEnabled = EmbarkJS.Names.currentNameSystems && EmbarkJS.Names.isAvailable();
if (this.state.error) {
return (<div>
<div>Something went wrong connecting to ethereum. Please make sure you have a node running or are using metamask to connect to the ethereum network:</div>
@ -94,8 +90,8 @@ class App extends React.Component {
<Tab eventKey={3} title={this._renderStatus('P2P communication (Whisper)', this.state.whisperEnabled)}>
<Whisper enabled={this.state.whisperEnabled}/>
</Tab>
<Tab eventKey={4} title={this._renderStatus('Naming (ENS)', this.state.ensEnabled)}>
<ENS enabled={this.state.ensEnabled}/>
<Tab eventKey={4} title={this._renderStatus('Naming (ENS)', ensEnabled)}>
<ENS enabled={ensEnabled}/>
</Tab>
</Tabs>
</div>);

View File

@ -1,5 +1,5 @@
/*globals describe, it*/
let Console = require('../cmd/dashboard/console.js');
let Console = require('../lib/modules/console/');
let Plugins = require('../lib/core/plugins.js');
let IPC = require('../lib/core/ipc.js');
let assert = require('assert');
@ -8,7 +8,8 @@ let version = require('../package.json').version;
describe('embark.Console', function() {
let ipc = new IPC({ipcRole: 'none'});
let plugins = new Plugins({plugins: {}});
let console = new Console({plugins, version, ipc});
let events = {on: () => {}, setCommandHandler: () => {}, emit: () => {}};
let console = new Console({}, {plugins, version, ipc, events});
describe('#executeCmd', function() {