diff --git a/packages/core/core/src/engine.ts b/packages/core/core/src/engine.ts index c173814b3..6d66591e7 100644 --- a/packages/core/core/src/engine.ts +++ b/packages/core/core/src/engine.ts @@ -303,7 +303,8 @@ export class Engine { } communicationComponents() { - this.registerModulePackage('embark-whisper'); + this.registerModulePackage('embark-whisper-geth'); + this.registerModulePackage('embark-whisper-parity'); } namesystemComponents() { diff --git a/packages/embark/package.json b/packages/embark/package.json index 37082aae7..0f8e2c9e5 100644 --- a/packages/embark/package.json +++ b/packages/embark/package.json @@ -118,7 +118,8 @@ "embark-watcher": "^5.0.0-alpha.1", "embark-web3": "^5.0.0-alpha.1", "embark-webserver": "^5.0.0-alpha.1", - "embark-whisper": "^5.0.0-alpha.1", + "embark-whisper-geth": "^5.0.0-alpha.1", + "embark-whisper-parity": "^5.0.0-alpha.1", "embarkjs-ens": "^5.0.0-alpha.1", "embarkjs-ipfs": "^5.0.0-alpha.1", "embarkjs-swarm": "^5.0.0-alpha.1", diff --git a/packages/plugins/geth/src/blockchain.js b/packages/plugins/geth/src/blockchain.js index cb7159d40..66b5c6e66 100644 --- a/packages/plugins/geth/src/blockchain.js +++ b/packages/plugins/geth/src/blockchain.js @@ -1,28 +1,27 @@ import { __ } from 'embark-i18n'; const fs = require('fs-extra'); const async = require('async'); -const {spawn, exec} = require('child_process'); +const { spawn, exec } = require('child_process'); const path = require('path'); const constants = require('embark-core/constants'); const GethClient = require('./gethClient.js'); -const WhisperGethClient = require('./whisperClient.js'); // const ParityClient = require('./parityClient.js'); import { IPC } from 'embark-core'; -import { compact, dappPath, defaultHost, dockerHostSwap, embarkPath} from 'embark-utils'; +import { compact, dappPath, defaultHost, dockerHostSwap, embarkPath } from 'embark-utils'; import { Logger } from 'embark-logger'; // time between IPC connection attempts (in ms) const IPC_CONNECT_INTERVAL = 2000; /*eslint complexity: ["error", 50]*/ -var Blockchain = function(userConfig, clientClass, communicationConfig) { +var Blockchain = function (userConfig, clientClass, communicationConfig) { this.userConfig = userConfig; this.env = userConfig.env || 'development'; this.isDev = userConfig.isDev; - this.onReadyCallback = userConfig.onReadyCallback || (() => {}); + this.onReadyCallback = userConfig.onReadyCallback || (() => { }); this.onExitCallback = userConfig.onExitCallback; - this.logger = userConfig.logger || new Logger({logLevel: 'debug', context: constants.contexts.blockchain}); // do not pass in events as we don't want any log events emitted + this.logger = userConfig.logger || new Logger({ logLevel: 'debug', context: constants.contexts.blockchain }); // do not pass in events as we don't want any log events emitted this.events = userConfig.events; this.isStandalone = userConfig.isStandalone; this.certOptions = userConfig.certOptions; @@ -103,7 +102,7 @@ var Blockchain = function(userConfig, clientClass, communicationConfig) { this.logger.error(__(spaceMessage, 'genesisBlock')); process.exit(1); } - this.client = new clientClass({config: this.config, env: this.env, isDev: this.isDev, communicationConfig: communicationConfig}); + this.client = new clientClass({ config: this.config, env: this.env, isDev: this.isDev, communicationConfig: communicationConfig }); if (this.isStandalone) { this.initStandaloneProcess(); @@ -118,20 +117,20 @@ var Blockchain = function(userConfig, clientClass, communicationConfig) { * * @returns {void} */ -Blockchain.prototype.initStandaloneProcess = function() { +Blockchain.prototype.initStandaloneProcess = function () { let logQueue = []; // on every log logged in logger (say that 3x fast), send the log // to the IPC serve listening (only if we're connected of course) this.logger.events.on('log', (logLevel, message) => { if (this.ipc.connected) { - this.ipc.request('blockchain:log', {logLevel, message}); + this.ipc.request('blockchain:log', { logLevel, message }); } else { - logQueue.push({logLevel, message}); + logQueue.push({ logLevel, message }); } }); - this.ipc = new IPC({ipcRole: 'client'}); + this.ipc = new IPC({ ipcRole: 'client' }); // Wait for an IPC server to start (ie `embark run`) by polling `.connect()`. // Do not kill this interval as the IPC server may restart (ie restart @@ -177,7 +176,7 @@ Blockchain.prototype.run = function () { function checkInstallation(next) { self.isClientInstalled((err) => { if (err) { - return next({message: err}); + return next({ message: err }); } next(); }); @@ -198,7 +197,7 @@ Blockchain.prototype.run = function () { next(null, cmd, args); }, true); } - ], function(err, cmd, args) { + ], function (err, cmd, args) { if (err) { self.logger.error(err.message || err); process.exit(1); @@ -207,7 +206,7 @@ Blockchain.prototype.run = function () { let full_cmd = cmd + " " + args.join(' '); self.logger.info(__(">>>>>>>>>>>>>>>> running: %s", full_cmd.underline).green); - self.child = spawn(cmd, args, {cwd: process.cwd()}); + self.child = spawn(cmd, args, { cwd: process.cwd() }); self.child.on('error', (err) => { err = err.toString(); @@ -291,15 +290,15 @@ Blockchain.prototype.isClientInstalled = function (callback) { const parsedVersion = this.client.parseVersion(stdout); const supported = this.client.isSupportedVersion(parsedVersion); if (supported === undefined) { - this.logger.error((__('WARNING! Ethereum client version could not be determined or compared with version range') + ' ' + this.client.versSupported + __(', for best results please use a supported version')).yellow); + this.logger.warn((__('WARNING! Ethereum client version could not be determined or compared with version range') + ' ' + this.client.versSupported + __(', for best results please use a supported version'))); } else if (!supported) { - this.logger.error((__('WARNING! Ethereum client version unsupported, for best results please use a version in range') + ' ' + this.client.versSupported).yellow); + this.logger.warn((__('WARNING! Ethereum client version unsupported, for best results please use a version in range') + ' ' + this.client.versSupported)); } callback(); }); }; -Blockchain.prototype.initDevChain = function(callback) { +Blockchain.prototype.initDevChain = function (callback) { const self = this; const ACCOUNTS_ALREADY_PRESENT = 'accounts_already_present'; // Init the dev chain @@ -337,10 +336,10 @@ Blockchain.prototype.initDevChain = function(callback) { if (!newAccountCommand) return next(); var accountNumber = 0; async.whilst( - function() { + function () { return accountNumber < accountsToCreate; }, - function(callback) { + function (callback) { accountNumber++; self.runCommand(newAccountCommand, {}, (err, stdout, _stderr) => { if (err) { @@ -350,7 +349,7 @@ Blockchain.prototype.initDevChain = function(callback) { callback(null, accountNumber); }); }, - function(err) { + function (err) { next(err); } ); @@ -430,14 +429,6 @@ export function BlockchainClient(userConfig, options, communicationConfig) { if (!userConfig.client) userConfig.client = constants.blockchain.clients.geth; // if clientName is set, it overrides preferences if (options.clientName) userConfig.client = options.clientName; - // Choose correct client instance based on clientName - let clientClass; - - if (communicationConfig) { - clientClass = WhisperGethClient; - } else { - clientClass = GethClient; - } userConfig.isDev = (userConfig.isDev || userConfig.default); userConfig.env = options.env; @@ -446,5 +437,5 @@ export function BlockchainClient(userConfig, options, communicationConfig) { userConfig.logger = options.logger; userConfig.certOptions = options.certOptions; userConfig.isStandalone = options.isStandalone; - return new Blockchain(userConfig, clientClass, communicationConfig); + return new Blockchain(userConfig, GethClient, communicationConfig); } diff --git a/packages/plugins/geth/src/index.js b/packages/plugins/geth/src/index.js index 46894ad1f..c50d733af 100644 --- a/packages/plugins/geth/src/index.js +++ b/packages/plugins/geth/src/index.js @@ -50,24 +50,6 @@ class Geth { cb(); } }); - - this.events.request("whisper:node:register", constants.blockchain.clients.geth, readyCb => { - this.events.request('processes:register', 'communication', { - launchFn: cb => { - this.startWhisperNode(cb); - }, - stopFn: cb => { - this.stopWhisperNode(cb); - } - }); - - this.events.request("processes:launch", "communication", (err) => { - if (err) { - this.logger.error(`Error launching whisper process: ${err.message || err}`); - } - readyCb(); - }); - }); } shouldInit() { @@ -132,31 +114,6 @@ class Geth { this.blockchainProcess.startBlockchainNode(callback); } - startWhisperNode(callback) { - this.whisperProcess = new BlockchainProcessLauncher({ - events: this.events, - logger: this.logger, - normalizeInput, - blockchainConfig: this.blockchainConfig, - communicationConfig: this.communicationConfig, - locale: this.locale, - client: this.client, - isDev: this.isDev, - embark: this.embark - }); - this.whisperProcess.startBlockchainNode(callback); - } - - stopWhisperNode(cb) { - if (!this.whisperProcess) { - return cb(); - } - this.whisperProcess.stopBlockchainNode(() => { - this.logger.info(`The whisper process has been stopped.`); - cb(); - }); - } - stopBlockchainNode(cb) { const message = __(`The blockchain process has been stopped. It can be restarted by running ${"service blockchain on".bold} in the Embark console.`); diff --git a/packages/plugins/parity/src/blockchain.js b/packages/plugins/parity/src/blockchain.js index c0dbf8627..d2233feed 100644 --- a/packages/plugins/parity/src/blockchain.js +++ b/packages/plugins/parity/src/blockchain.js @@ -303,9 +303,9 @@ Blockchain.prototype.isClientInstalled = function (callback) { const parsedVersion = this.client.parseVersion(stdout); const supported = this.client.isSupportedVersion(parsedVersion); if (supported === undefined) { - this.logger.error((__('WARNING! Ethereum client version could not be determined or compared with version range') + ' ' + this.client.versSupported + __(', for best results please use a supported version')).yellow); + this.logger.warn((__('WARNING! Ethereum client version could not be determined or compared with version range') + ' ' + this.client.versSupported + __(', for best results please use a supported version'))); } else if (!supported) { - this.logger.error((__('WARNING! Ethereum client version unsupported, for best results please use a version in range') + ' ' + this.client.versSupported).yellow); + this.logger.warn((__('WARNING! Ethereum client version unsupported, for best results please use a version in range') + ' ' + this.client.versSupported)); } callback(); }); diff --git a/packages/plugins/parity/src/check.js b/packages/plugins/parity/src/check.js index fe9874462..391876e50 100644 --- a/packages/plugins/parity/src/check.js +++ b/packages/plugins/parity/src/check.js @@ -7,6 +7,9 @@ const parseAndRespond = (data, cb) => { let resp; try { resp = JSON.parse(data); + if (resp.error) { + return cb(resp.error); + } } catch (e) { return cb('Version data is not valid JSON'); } diff --git a/packages/plugins/whisper/CHANGELOG.md b/packages/plugins/whisper-geth/CHANGELOG.md similarity index 100% rename from packages/plugins/whisper/CHANGELOG.md rename to packages/plugins/whisper-geth/CHANGELOG.md diff --git a/packages/plugins/whisper/README.md b/packages/plugins/whisper-geth/README.md similarity index 58% rename from packages/plugins/whisper/README.md rename to packages/plugins/whisper-geth/README.md index 980904f8e..455197c15 100644 --- a/packages/plugins/whisper/README.md +++ b/packages/plugins/whisper-geth/README.md @@ -1,6 +1,6 @@ -# `embark-whisper` +# `embark-whisper-geth` -> Module to add Whisper support to Embark +> Module to add Whisper support to Embark for the Geth blockchain Visit [embark.status.im](https://embark.status.im/) to get started with [Embark](https://github.com/embark-framework/embark). diff --git a/packages/plugins/whisper/package.json b/packages/plugins/whisper-geth/package.json similarity index 92% rename from packages/plugins/whisper/package.json rename to packages/plugins/whisper-geth/package.json index 60a8ba232..203997301 100644 --- a/packages/plugins/whisper/package.json +++ b/packages/plugins/whisper-geth/package.json @@ -1,10 +1,10 @@ { - "name": "embark-whisper", + "name": "embark-whisper-geth", "version": "5.0.0-alpha.1", "author": "Iuri Matias ", "contributors": [], "description": "Module to add Whisper support to Embark", - "homepage": "https://github.com/embark-framework/embark/tree/master/packages/plugins/whisper#readme", + "homepage": "https://github.com/embark-framework/embark/tree/master/packages/plugins/whisper-geth#readme", "bugs": "https://github.com/embark-framework/embark/issues", "keywords": [ "blockchain", @@ -20,7 +20,7 @@ ], "license": "MIT", "repository": { - "directory": "packages/plugins/whisper", + "directory": "packages/plugins/whisper-geth", "type": "git", "url": "https://github.com/embark-framework/embark.git" }, diff --git a/packages/plugins/whisper/src/api.js b/packages/plugins/whisper-geth/src/api.js similarity index 75% rename from packages/plugins/whisper/src/api.js rename to packages/plugins/whisper-geth/src/api.js index 5c498089d..7c399270c 100644 --- a/packages/plugins/whisper/src/api.js +++ b/packages/plugins/whisper-geth/src/api.js @@ -1,5 +1,5 @@ -import EmbarkJS from 'embarkjs'; -import EmbarkJSWhisper from 'embarkjs-whisper'; +import EmbarkJS from "embarkjs"; +import EmbarkJSWhisper from "embarkjs-whisper"; class API { @@ -11,8 +11,8 @@ class API { async initEmbarkJSWhisper() { if (Object.keys(EmbarkJS.Messages.Providers).includes("whisper")) return; - EmbarkJS.Messages.registerProvider('whisper', EmbarkJSWhisper); - EmbarkJS.Messages.setProvider('whisper', this.communicationConfig.connection); + EmbarkJS.Messages.registerProvider("whisper", EmbarkJSWhisper); + EmbarkJS.Messages.setProvider("whisper", this.communicationConfig.connection); } async registerAPICalls() { @@ -23,8 +23,8 @@ class API { async registerSendMessageCall() { this.embark.registerAPICall( - 'post', - '/embark-api/communication/sendMessage', + "post", + "/embark-api/communication/sendMessage", (req, res) => { EmbarkJS.Messages.sendMessage({ topic: req.body.topic, data: req.body.message }, (err, result) => { if (err) { @@ -37,8 +37,8 @@ class API { async registerListenToCall() { this.embark.registerAPICall( - 'ws', - '/embark-api/communication/listenTo/:topic', + "ws", + "/embark-api/communication/listenTo/:topic", (ws, req) => { EmbarkJS.Messages.listenTo({ topic: req.params.topic }).subscribe(data => { ws.send(JSON.stringify(data)); diff --git a/packages/plugins/whisper-geth/src/blockchain.js b/packages/plugins/whisper-geth/src/blockchain.js new file mode 100644 index 000000000..557a74ce9 --- /dev/null +++ b/packages/plugins/whisper-geth/src/blockchain.js @@ -0,0 +1,301 @@ +import { __ } from "embark-i18n"; +const async = require("async"); +// eslint-disable-next-line security/detect-child-process +const {spawn, exec} = require("child_process"); +const path = require("path"); +const constants = require("embark-core/constants"); +const WhisperGethClient = require("./whisperGethClient.js"); +import { IPC } from "embark-core"; + +import { compact, dappPath, defaultHost, dockerHostSwap, embarkPath} from "embark-utils"; +const Logger = require("embark-logger"); + +// time between IPC connection attempts (in ms) +const IPC_CONNECT_INTERVAL = 2000; + +/*eslint complexity: ["error", 50]*/ +var Blockchain = function(userConfig, clientClass, communicationConfig) { + this.userConfig = userConfig; + this.env = userConfig.env || "development"; + this.isDev = userConfig.isDev; + this.onReadyCallback = userConfig.onReadyCallback || (() => {}); + this.onExitCallback = userConfig.onExitCallback; + this.logger = userConfig.logger || new Logger({logLevel: "debug", context: constants.contexts.blockchain}); // do not pass in events as we don't want any log events emitted + this.events = userConfig.events; + this.isStandalone = userConfig.isStandalone; + this.certOptions = userConfig.certOptions; + + + let defaultWsApi = clientClass.DEFAULTS.WS_API; + if (this.isDev) defaultWsApi = clientClass.DEFAULTS.DEV_WS_API; + + this.config = { + silent: this.userConfig.silent, + client: this.userConfig.client, + ethereumClientBin: this.userConfig.ethereumClientBin || this.userConfig.client, + networkType: this.userConfig.networkType || clientClass.DEFAULTS.NETWORK_TYPE, + networkId: this.userConfig.networkId || clientClass.DEFAULTS.NETWORK_ID, + genesisBlock: this.userConfig.genesisBlock || false, + datadir: this.userConfig.datadir, + mineWhenNeeded: this.userConfig.mineWhenNeeded || false, + rpcHost: dockerHostSwap(this.userConfig.rpcHost) || defaultHost, + rpcPort: this.userConfig.rpcPort || 8545, + rpcCorsDomain: this.userConfig.rpcCorsDomain || false, + rpcApi: this.userConfig.rpcApi || clientClass.DEFAULTS.RPC_API, + port: this.userConfig.port || 30303, + nodiscover: this.userConfig.nodiscover || false, + mine: this.userConfig.mine || false, + account: {}, + whisper: (this.userConfig.whisper !== false), + maxpeers: ((this.userConfig.maxpeers === 0) ? 0 : (this.userConfig.maxpeers || 25)), + bootnodes: this.userConfig.bootnodes || "", + wsRPC: (this.userConfig.wsRPC !== false), + wsHost: dockerHostSwap(this.userConfig.wsHost) || defaultHost, + wsPort: this.userConfig.wsPort || 8546, + wsOrigins: this.userConfig.wsOrigins || false, + wsApi: this.userConfig.wsApi || defaultWsApi, + vmdebug: this.userConfig.vmdebug || false, + targetGasLimit: this.userConfig.targetGasLimit || false, + syncMode: this.userConfig.syncMode || this.userConfig.syncmode, + verbosity: this.userConfig.verbosity + }; + + this.devFunds = null; + + if (this.userConfig.accounts) { + const nodeAccounts = this.userConfig.accounts.find((account) => account.nodeAccounts); + if (nodeAccounts) { + this.config.account = { + numAccounts: nodeAccounts.numAddresses || 1, + password: nodeAccounts.password, + balance: nodeAccounts.balance + }; + } + } + + if (this.userConfig.default || JSON.stringify(this.userConfig) === "{'client':'geth'}") { + if (this.env === "development") { + this.isDev = true; + } else { + this.config.genesisBlock = embarkPath("templates/boilerplate/config/privatenet/genesis.json"); + } + this.config.datadir = dappPath(".embark/development/datadir"); + this.config.wsOrigins = this.config.wsOrigins || "http://localhost:8000"; + this.config.rpcCorsDomain = this.config.rpcCorsDomain || "http://localhost:8000"; + this.config.targetGasLimit = 8000000; + } + this.config.account.devPassword = path.join(this.config.datadir, "devPassword"); + + const spaceMessage = "The path for %s in blockchain config contains spaces, please remove them"; + if (this.config.datadir && this.config.datadir.indexOf(" ") > 0) { + this.logger.error(__(spaceMessage, "datadir")); + process.exit(1); + } + if (this.config.account.password && this.config.account.password.indexOf(" ") > 0) { + this.logger.error(__(spaceMessage, "accounts.password")); + process.exit(1); + } + if (this.config.genesisBlock && this.config.genesisBlock.indexOf(" ") > 0) { + this.logger.error(__(spaceMessage, "genesisBlock")); + process.exit(1); + } + this.client = new clientClass({config: this.config, env: this.env, isDev: this.isDev, communicationConfig: communicationConfig}); + + if (this.isStandalone) { + this.initStandaloneProcess(); + } +}; + +/** + * Polls for a connection to an IPC server (generally this is set up + * in the Embark process). Once connected, any logs logged to the + * Logger will be shipped off to the IPC server. In the case of `embark + * run`, the BlockchainListener module is listening for these logs. + * + * @return {void} + */ +Blockchain.prototype.initStandaloneProcess = function() { + let logQueue = []; + + // on every log logged in logger (say that 3x fast), send the log + // to the IPC serve listening (only if we're connected of course) + this.logger.events.on("log", (logLevel, message) => { + if (this.ipc.connected) { + this.ipc.request("blockchain:log", {logLevel, message}); + } else { + logQueue.push({logLevel, message}); + } + }); + + this.ipc = new IPC({ipcRole: "client"}); + + // Wait for an IPC server to start (ie `embark run`) by polling `.connect()`. + // Do not kill this interval as the IPC server may restart (ie restart + // `embark run` without restarting `embark blockchain`) + setInterval(() => { + if (!this.ipc.connected) { + this.ipc.connect(() => { + if (this.ipc.connected) { + logQueue.forEach((message) => { + this.ipc.request("blockchain:log", message); + }); + logQueue = []; + this.ipc.client.on("process:blockchain:stop", () => { + this.kill(); + process.exit(0); + }); + } + }); + } + }, IPC_CONNECT_INTERVAL); +}; + +Blockchain.prototype.runCommand = function (cmd, options, callback) { + this.logger.info(__("running: %s", cmd.underline).green); + if (this.config.silent) { + options.silent = true; + } + return exec(cmd, options, callback); +}; + +Blockchain.prototype.run = function () { + var self = this; + this.logger.info("===============================================================================".magenta); + this.logger.info("===============================================================================".magenta); + this.logger.info(__("Embark Whisper using %s", self.client.prettyName.underline).magenta); + this.logger.info("===============================================================================".magenta); + this.logger.info("===============================================================================".magenta); + + if (self.client.name === constants.blockchain.clients.geth) this.checkPathLength(); + + let address = ""; + async.waterfall([ + function checkInstallation(next) { + self.isClientInstalled((err) => { + if (err) { + return next({ message: err }); + } + next(); + }); + }, + function getMainCommand(next) { + self.client.mainCommand(address, function (cmd, args) { + next(null, cmd, args); + }, true); + } + ], function(err, cmd, args) { + if (err) { + self.logger.error(err.message); + return; + } + args = compact(args); + + let full_cmd = cmd + " " + args.join(" "); + self.logger.info(__(">>>>>>>>>>>>>>>> running: %s", full_cmd.underline).green); + self.child = spawn(cmd, args, {cwd: process.cwd()}); + + self.child.on("error", (err) => { + err = err.toString(); + self.logger.error("Blockchain error: ", err); + if (self.env === "development" && err.indexOf("Failed to unlock") > 0) { + self.logger.error("\n" + __("Development blockchain has changed to use the --dev option.").yellow); + self.logger.error(__("You can reset your workspace to fix the problem with").yellow + " embark reset".cyan); + self.logger.error(__("Otherwise, you can change your data directory in blockchain.json (datadir)").yellow); + } + }); + + // TOCHECK I don't understand why stderr and stdout are reverted. + // This happens with Geth and Parity, so it does not seems a client problem + self.child.stdout.on("data", (data) => { + self.logger.info(`${self.client.name} error: ${data}`); + }); + + self.child.stderr.on("data", async (data) => { + data = data.toString(); + if (!self.readyCalled && self.client.isReady(data)) { + self.readyCalled = true; + self.readyCallback(); + } + self.logger.info(`${self.client.name}: ${data}`); + }); + + self.child.on("exit", (code) => { + let strCode; + if (code) { + strCode = "with error code " + code; + } else { + strCode = "with no error code (manually killed?)"; + } + self.logger.error(self.client.name + " exited " + strCode); + if (self.onExitCallback) { + self.onExitCallback(); + } + }); + + self.child.on("uncaughtException", (err) => { + self.logger.error("Uncaught " + self.client.name + " exception", err); + if (self.onExitCallback) { + self.onExitCallback(); + } + }); + }); +}; + +Blockchain.prototype.readyCallback = function () { + if (this.onReadyCallback) { + this.onReadyCallback(); + } +}; + +Blockchain.prototype.kill = function () { + if (this.child) { + this.child.kill(); + } +}; + +Blockchain.prototype.checkPathLength = function () { + let _dappPath = dappPath(""); + if (_dappPath.length > 66) { + // this.logger.error is captured and sent to the console output regardless of silent setting + this.logger.error("===============================================================================".yellow); + this.logger.error("===========> ".yellow + __("WARNING! ÐApp path length is too long: ").yellow + _dappPath.yellow); + this.logger.error("===========> ".yellow + __("This is known to cause issues with starting geth, please consider reducing your ÐApp path\'s length to 66 characters or less.").yellow); + this.logger.error("===============================================================================".yellow); + } +}; + +Blockchain.prototype.isClientInstalled = function (callback) { + let versionCmd = this.client.determineVersionCommand(); + this.runCommand(versionCmd, {}, (err, stdout, stderr) => { + if (err || !stdout || stderr.indexOf("not found") >= 0 || stdout.indexOf("not found") >= 0) { + return callback(__("Ethereum client bin not found:") + " " + this.client.getBinaryPath()); + } + const parsedVersion = this.client.parseVersion(stdout); + const supported = this.client.isSupportedVersion(parsedVersion); + if (supported === undefined) { + this.logger.warn((__("WARNING! Ethereum client version could not be determined or compared with version range") + " " + this.client.versSupported + __(", for best results please use a supported version"))); + } else if (!supported) { + this.logger.warn((__("WARNING! Ethereum client version unsupported, for best results please use a version in range") + " " + this.client.versSupported)); + } + callback(); + }); +}; + +export function BlockchainClient(userConfig, options, communicationConfig) { + if ((JSON.stringify(userConfig) === "{'enabled':true}") && options.env !== "development") { + options.logger.info("===> " + __("warning: running default config on a non-development environment")); + } + // if client is not set in preferences, default is geth + if (!userConfig.client) userConfig.client = constants.blockchain.clients.geth; + // if clientName is set, it overrides preferences + if (options.clientName) userConfig.client = options.clientName; + + userConfig.isDev = (userConfig.isDev || userConfig.default); + userConfig.env = options.env; + userConfig.onReadyCallback = options.onReadyCallback; + userConfig.onExitCallback = options.onExitCallback; + userConfig.logger = options.logger; + userConfig.certOptions = options.certOptions; + userConfig.isStandalone = options.isStandalone; + return new Blockchain(userConfig, WhisperGethClient, communicationConfig); +} diff --git a/packages/plugins/whisper-geth/src/blockchainProcess.js b/packages/plugins/whisper-geth/src/blockchainProcess.js new file mode 100644 index 000000000..640bb8366 --- /dev/null +++ b/packages/plugins/whisper-geth/src/blockchainProcess.js @@ -0,0 +1,59 @@ +import * as i18n from "embark-i18n"; +import { ProcessWrapper } from "embark-core"; +const constants = require("embark-core/constants"); +import { BlockchainClient as blockchainClient } from "./blockchain"; + +let blockchainProcess; + +class BlockchainProcess extends ProcessWrapper { + constructor(options) { + super(); + this.blockchainConfig = options.blockchainConfig; + this.communicationConfig = options.communicationConfig; + this.client = options.client; + this.env = options.env; + this.isDev = options.isDev; + this.certOptions = options.certOptions; + + i18n.setOrDetectLocale(options.locale); + + this.blockchainConfig.silent = true; + this.blockchain = blockchainClient( + this.blockchainConfig, + { + clientName: this.client, + env: this.env, + certOptions: this.certOptions, + onReadyCallback: this.blockchainReady.bind(this), + onExitCallback: this.blockchainExit.bind(this), + logger: console + }, + this.communicationConfig + ); + + this.blockchain.run(); + } + + blockchainReady() { + blockchainProcess.send({result: constants.blockchain.blockchainReady}); + } + + blockchainExit() { + // tell our parent process that ethereum client has exited + blockchainProcess.send({result: constants.blockchain.blockchainExit}); + } + + kill() { + this.blockchain.kill(); + } +} + +process.on("message", (msg) => { + if (msg === "exit") { + return blockchainProcess.kill(); + } + if (msg.action === constants.blockchain.init) { + blockchainProcess = new BlockchainProcess(msg.options); + return blockchainProcess.send({result: constants.blockchain.initiated}); + } +}); diff --git a/packages/plugins/whisper-geth/src/blockchainProcessLauncher.js b/packages/plugins/whisper-geth/src/blockchainProcessLauncher.js new file mode 100644 index 000000000..f4d7883a3 --- /dev/null +++ b/packages/plugins/whisper-geth/src/blockchainProcessLauncher.js @@ -0,0 +1,84 @@ +import { __ } from "embark-i18n"; +import { ProcessLauncher } from "embark-core"; +import { joinPath } from "embark-utils"; +const constants = require("embark-core/constants"); + +export class BlockchainProcessLauncher { + + constructor(options) { + this.events = options.events; + this.logger = options.logger; + this.normalizeInput = options.normalizeInput; + this.blockchainConfig = options.blockchainConfig; + this.communicationConfig = options.communicationConfig; + this.locale = options.locale; + this.isDev = options.isDev; + this.client = options.client; + this.embark = options.embark; + } + + processEnded(code) { + this.logger.error(__("Blockchain process ended before the end of this process. Try running blockchain in a separate process using `$ embark blockchain`. Code: %s", code)); + } + + startBlockchainNode(readyCb) { + this.logger.info(__("Starting Whisper node in another process").cyan); + + this.blockchainProcess = new ProcessLauncher({ + name: "blockchain", + modulePath: joinPath(__dirname, "./blockchainProcess.js"), + logger: this.logger, + events: this.events, + silent: this.logger.logLevel !== "trace", + exitCallback: this.processEnded.bind(this), + embark: this.embark + }); + this.blockchainProcess.send({ + action: constants.blockchain.init, options: { + blockchainConfig: this.blockchainConfig, + communicationConfig: this.communicationConfig, + client: this.client, + env: this.env, + isDev: this.isDev, + locale: this.locale, + certOptions: this.embark.config.webServerConfig.certOptions, + events: this.events + } + }); + + this.blockchainProcess.once("result", constants.blockchain.blockchainReady, () => { + this.logger.info(__("Whisper blockchain node is ready").cyan); + readyCb(); + }); + + this.blockchainProcess.once("result", constants.blockchain.blockchainExit, () => { + // tell everyone that our blockchain process (ie geth) died + this.events.emit(constants.blockchain.blockchainExit); + + // then kill off the blockchain process + this.blockchainProcess.kill(); + }); + + this.events.on("logs:ethereum:enable", () => { + this.blockchainProcess.silent = false; + }); + + this.events.on("logs:ethereum:disable", () => { + this.blockchainProcess.silent = true; + }); + + this.events.on("exit", () => { + this.blockchainProcess.send("exit"); + }); + } + + stopBlockchainNode(cb) { + if (this.blockchainProcess) { + this.events.once(constants.blockchain.blockchainExit, cb); + this.blockchainProcess.exitCallback = () => { }; // don"t show error message as the process was killed on purpose + this.blockchainProcess.send("exit"); + } + } + +} + diff --git a/packages/plugins/whisper-geth/src/check.js b/packages/plugins/whisper-geth/src/check.js new file mode 100644 index 000000000..9f919ac4b --- /dev/null +++ b/packages/plugins/whisper-geth/src/check.js @@ -0,0 +1,63 @@ +const WebSocket = require("ws"); +const http = require("http"); + +const LIVENESS_CHECK = JSON.stringify({ + jsonrpc:'2.0', + method: 'shh_version', + params:[], + id:42 +}); + +// eslint-disable-next-line complexity +const parseAndRespond = (data, cb) => { + let resp; + try { + resp = JSON.parse(data); + if (resp.error) { + return cb(resp.error); + } + } catch (e) { + return cb("Version data is not valid JSON"); + } + if (!resp || !resp.result) { + return cb("No version returned"); + } + cb(null, resp.result); +}; + +const rpc = (host, port, cb) => { + const options = { + hostname: host, // TODO(andremedeiros): get from config + port, // TODO(andremedeiros): get from config + method: "POST", + timeout: 1000, + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(LIVENESS_CHECK) + } + }; + + const req = http.request(options, (res) => { + let data = ""; + res.on("data", (chunk) => { data += chunk; }); + res.on("end", () => parseAndRespond(data, cb)); + }); + req.on("error", (e) => cb(e)); + req.write(LIVENESS_CHECK); + req.end(); +}; + +const ws = (host, port, cb) => { + const conn = new WebSocket("ws://" + host + ":" + port); + conn.on("message", (data) => { + parseAndRespond(data, cb); + conn.close(); + }); + conn.on("open", () => conn.send(LIVENESS_CHECK)); + conn.on("error", (e) => cb(e)); +}; + +module.exports = { + ws, + rpc +}; diff --git a/packages/plugins/whisper-geth/src/index.js b/packages/plugins/whisper-geth/src/index.js new file mode 100644 index 000000000..a869f8425 --- /dev/null +++ b/packages/plugins/whisper-geth/src/index.js @@ -0,0 +1,115 @@ +import { __ } from "embark-i18n"; +import { dappPath, canonicalHost, defaultHost } from "embark-utils"; +const constants = require("embark-core/constants"); +const API = require("./api.js"); +import { BlockchainProcessLauncher } from "./blockchainProcessLauncher"; +import { ws, rpc } from "./check.js"; +const { normalizeInput } = require("embark-utils"); + +class Whisper { + constructor(embark, _options) { + this.logger = embark.logger; + this.events = embark.events; + this.fs = embark.fs; + this.blockchainConfig = embark.config.blockchainConfig; + this.communicationConfig = embark.config.communicationConfig; + this.embarkConfig = embark.config.embarkConfig; + this.embark = embark; + this.webSocketsChannels = {}; + this.modulesPath = dappPath(embark.config.embarkConfig.generationDir, constants.dappArtifacts.symlinkDir); + + if (!this.communicationConfig.enabled || this.blockchainConfig.client !== constants.blockchain.clients.geth) { + return; + } + + this.api = new API(embark); + this.whisperNodes = {}; + + this.events.request("embarkjs:plugin:register", "messages", "whisper", "embarkjs-whisper"); + this.events.request("embarkjs:console:register", "messages", "whisper", "embarkjs-whisper"); + + this.events.request("communication:node:register", "whisper", (readyCb) => { + this.events.request("processes:register", "communication", { + launchFn: (cb) => { + this.startWhisperNode(cb); + }, + stopFn: (cb) => { + this.stopWhisperNode(cb); + } + }); + + this.events.request("processes:launch", "communication", (err) => { + if (err) { + this.logger.error(`Error launching whisper process: ${err.message || err}`); + } + readyCb(); + }); + + this.registerServiceCheck(); + }); + + this.events.on("communication:started", () => { + this.api = new API(embark); + this.api.registerAPICalls(); + this.connectEmbarkJSProvider(); + }); + } + + _getNodeState(err, version, cb) { + if (err) return cb({ name: "Whisper node not found", status: "off" }); + + const name = `Whisper v${version} (Geth)`; + return cb({ name, status: "on" }); + } + + registerServiceCheck() { + this.events.request("services:register", "Whisper", (cb) => { + const { host, port, type } = this.communicationConfig.connection; + if (type === "ws") { + return ws(host, port, (err, version) => this._getNodeState(err, version, cb)); + } + rpc(host, port, (err, version) => this._getNodeState(err, version, cb)); + + }, 5000, "off"); + } + + startWhisperNode(callback) { + this.whisperProcess = new BlockchainProcessLauncher({ + events: this.events, + logger: this.logger, + normalizeInput, + blockchainConfig: this.blockchainConfig, + communicationConfig: this.communicationConfig, + locale: this.locale, + client: constants.blockchain.clients.whisper, + isDev: this.isDev, + embark: this.embark + }); + this.whisperProcess.startBlockchainNode(callback); + } + + stopWhisperNode(cb) { + if (!this.whisperProcess) { + return cb(); + } + this.whisperProcess.stopBlockchainNode(() => { + this.logger.info(`The whisper process has been stopped.`); + cb(); + }); + } + + // esline-disable-next-line complexity + async connectEmbarkJSProvider() { + let connection = this.communicationConfig.connection || {}; + const config = { + server: canonicalHost(connection.host || defaultHost), + port: connection.port || "8546", + type: connection.type || "ws" + }; + + this.events.request("embarkjs:console:setProvider", "messages", "whisper", config); + } + +} + +module.exports = Whisper; diff --git a/packages/plugins/geth/src/whisperClient.js b/packages/plugins/whisper-geth/src/whisperGethClient.js similarity index 54% rename from packages/plugins/geth/src/whisperClient.js rename to packages/plugins/whisper-geth/src/whisperGethClient.js index d32239f79..effece12c 100644 --- a/packages/plugins/geth/src/whisperClient.js +++ b/packages/plugins/whisper-geth/src/whisperGethClient.js @@ -1,17 +1,16 @@ -import { __ } from 'embark-i18n'; -const async = require('async'); -const GethMiner = require('./miner'); -const semver = require('semver'); -const constants = require('embark-core/constants'); +import { __ } from "embark-i18n"; +const async = require("async"); +const semver = require("semver"); +const constants = require("embark-core/constants"); const DEFAULTS = { "BIN": "geth", "VERSIONS_SUPPORTED": ">=1.9.7", "NETWORK_TYPE": "custom", "NETWORK_ID": 1337, - "RPC_API": ['web3'], - "WS_API": ['web3', 'shh'], - "DEV_WS_API": ['web3', 'shh'], + "RPC_API": ["web3"], + "WS_API": ["web3", "shh"], + "DEV_WS_API": ["web3", "shh"], "TARGET_GAS_LIMIT": 8000000 }; @@ -21,11 +20,12 @@ class WhisperGethClient { return DEFAULTS; } + // eslint-disable-next-line complexity constructor(options) { - this.config = options && options.hasOwnProperty('config') ? options.config : {}; + this.config = options && options.hasOwnProperty("config") ? options.config : {}; this.communicationConfig = options.communicationConfig; - this.env = options && options.hasOwnProperty('env') ? options.env : 'development'; - this.isDev = options && options.hasOwnProperty('isDev') ? options.isDev : (this.env === 'development'); + this.env = options && options.hasOwnProperty("env") ? options.env : "development"; + this.isDev = options && options.hasOwnProperty("isDev") ? options.isDev : (this.env === "development"); this.name = constants.blockchain.clients.geth; this.prettyName = "Go-Ethereum (https://github.com/ethereum/go-ethereum)"; this.bin = this.config.ethereumClientBin || DEFAULTS.BIN; @@ -35,26 +35,14 @@ class WhisperGethClient { } isReady(data) { - if (data.indexOf('WebSocket endpoint opened') > -1) { + if (data.indexOf("WebSocket endpoint opened") > -1) { this.wsReady = true; } return this.wsReady; } - /** - * Check if the client needs some sort of 'keep alive' transactions to avoid freezing by inactivity - * @returns {boolean} if keep alive is needed - */ - needKeepAlive() { - return false; - } - commonOptions() { - return ['--ipcdisable']; - } - - getMiner() { - return new GethMiner({datadir: this.config.datadir}); + return []; } getBinaryPath() { @@ -79,8 +67,10 @@ class WhisperGethClient { try { let v = semver(parsedVersion); v = `${v.major}.${v.minor}.${v.patch}`; + // eslint-disable-next-line no-cap test = semver.Range(this.versSupported).test(semver(v)); - if (typeof test !== 'boolean') { + if (typeof test !== "boolean") { + // eslint-disable-next-line no-undefined test = undefined; } } finally { @@ -91,11 +81,11 @@ class WhisperGethClient { determineNetworkType(config) { let cmd; - if (config.networkType === 'testnet') { + if (config.networkType === "testnet") { cmd = "--testnet"; - } else if (config.networkType === 'rinkeby') { + } else if (config.networkType === "rinkeby") { cmd = "--rinkeby"; - } else if (config.networkType === 'custom') { + } else if (config.networkType === "custom") { cmd = "--networkid=" + config.networkId; } return cmd; @@ -109,38 +99,6 @@ class WhisperGethClient { return ""; } - newAccountCommand() { - return ""; - } - - parseNewAccountCommandResultToAddress(data = "") { - if (data.match(/{(\w+)}/)) return "0x" + data.match(/{(\w+)}/)[1]; - return ""; - } - - listAccountsCommand() { - return ""; - } - - parseListAccountsCommandResultToAddress(data = "") { - if (data.match(/{(\w+)}/)) return "0x" + data.match(/{(\w+)}/)[1]; - return ""; - } - - parseListAccountsCommandResultToAddressList(data = "") { - const regex = RegExp(/{(\w+)}/g); - let match; - const accounts = []; - while ((match = regex.exec(data)) !== null) { - accounts.push('0x' + match[1]); - } - return accounts; - } - - parseListAccountsCommandResultToAddressCount(_data = "") { - return 0; - } - determineRpcOptions(config) { let cmd = []; cmd.push("--port=30304"); @@ -149,21 +107,22 @@ class WhisperGethClient { cmd.push("--rpcaddr=" + config.rpcHost); if (config.rpcCorsDomain) { - if (config.rpcCorsDomain === '*') { - console.warn('=================================='); - console.warn(__('rpcCorsDomain set to *')); - console.warn(__('make sure you know what you are doing')); - console.warn('=================================='); + if (config.rpcCorsDomain === "*") { + console.warn("=================================="); + console.warn(__("rpcCorsDomain set to *")); + console.warn(__("make sure you know what you are doing")); + console.warn("=================================="); } cmd.push("--rpccorsdomain=" + config.rpcCorsDomain); } else { - console.warn('=================================='); - console.warn(__('warning: cors is not set')); - console.warn('=================================='); + console.warn("=================================="); + console.warn(__("warning: cors is not set")); + console.warn("=================================="); } return cmd; } + // eslint-disable-next-line complexity determineWsOptions(config, communicationConfig) { let cmd = []; if (config.wsRPC) { @@ -173,17 +132,17 @@ class WhisperGethClient { cmd.push(`--wsaddr=${communicationConfig.connection.host || config.wsHost++}`); if (config.wsOrigins) { - if (config.wsOrigins === '*') { - console.warn('=================================='); - console.warn(__('wsOrigins set to *')); - console.warn(__('make sure you know what you are doing')); - console.warn('=================================='); + if (config.wsOrigins === "*") { + console.warn("=================================="); + console.warn(__("wsOrigins set to *")); + console.warn(__("make sure you know what you are doing")); + console.warn("=================================="); } cmd.push("--wsorigins=" + config.wsOrigins); } else { - console.warn('=================================='); - console.warn(__('warning: wsOrigins is not set')); - console.warn('=================================='); + console.warn("=================================="); + console.warn(__("warning: wsOrigins is not set")); + console.warn("=================================="); } } return cmd; @@ -196,8 +155,8 @@ class WhisperGethClient { mainCommand(address, done) { let self = this; let config = this.config; - let rpc_api = this.config.rpcApi; - let ws_api = this.config.wsApi; + let rpcApi = this.config.rpcApi; + let wsApi = this.config.wsApi; let args = []; async.series([ function commonOptions(callback) { @@ -217,7 +176,7 @@ class WhisperGethClient { } callback(null, ""); }, - function maxPeers(callback) { + function maxPeers(callback) { let cmd = "--maxpeers=" + config.maxpeers; args.push(cmd); callback(null, cmd); @@ -230,22 +189,22 @@ class WhisperGethClient { callback(""); }, function whisper(callback) { - rpc_api.push('shh'); - if (ws_api.indexOf('shh') === -1) { - ws_api.push('shh'); + rpcApi.push("shh"); + if (wsApi.indexOf("shh") === -1) { + wsApi.push("shh"); } args.push("--shh"); return callback(null, "--shh "); }, - function rpcApi(callback) { - args.push('--rpcapi=' + rpc_api.join(',')); - callback(null, '--rpcapi=' + rpc_api.join(',')); + function rpcApiArgs(callback) { + args.push("--rpcapi=" + rpcApi.join(",")); + callback(null, "--rpcapi=" + rpcApi.join(",")); }, - function wsApi(callback) { - args.push('--wsapi=' + ws_api.join(',')); - callback(null, '--wsapi=' + ws_api.join(',')); + function wsApiArgs(callback) { + args.push("--wsapi=" + wsApi.join(",")); + callback(null, "--wsapi=" + wsApi.join(",")); } - ], function(err) { + ], function (err) { if (err) { throw new Error(err.message); } diff --git a/packages/plugins/whisper-parity/CHANGELOG.md b/packages/plugins/whisper-parity/CHANGELOG.md new file mode 100644 index 000000000..711192094 --- /dev/null +++ b/packages/plugins/whisper-parity/CHANGELOG.md @@ -0,0 +1,115 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [5.0.0-alpha.1](https://github.com/embark-framework/embark/compare/v5.0.0-alpha.0...v5.0.0-alpha.1) (2019-11-05) + +**Note:** Version bump only for package embark-whisper + + + + + +# [5.0.0-alpha.0](https://github.com/embark-framework/embark/compare/v4.1.1...v5.0.0-alpha.0) (2019-10-28) + + +### Build System + +* bump all packages' engines settings ([#1985](https://github.com/embark-framework/embark/issues/1985)) ([ed02cc8](https://github.com/embark-framework/embark/commit/ed02cc8)) + + +### BREAKING CHANGES + +* node: >=10.17.0 <12.0.0 +npm: >=6.11.3 +yarn: >=1.19.1 + +node v10.17.0 is the latest in the 10.x series and is still in the Active LTS +lifecycle. Embark is still not compatible with node's 12.x and 13.x +series (because of some dependencies), otherwise it would probably make sense +to bump our minimum supported node version all the way to the most recent 12.x +release. + +npm v6.11.3 is the version that's bundled with node v10.17.0. + +yarn v1.19.1 is the most recent version as of the time node v10.17.0 was +released. + + + + + +## [4.1.1](https://github.com/embark-framework/embark/compare/v4.1.0...v4.1.1) (2019-08-28) + +**Note:** Version bump only for package embark-whisper + + + + + +# [4.1.0](https://github.com/embark-framework/embark/compare/v4.1.0-beta.6...v4.1.0) (2019-08-12) + +**Note:** Version bump only for package embark-whisper + + + + + +# [4.1.0-beta.6](https://github.com/embark-framework/embark/compare/v4.1.0-beta.5...v4.1.0-beta.6) (2019-08-09) + +**Note:** Version bump only for package embark-whisper + + + + + +# [4.1.0-beta.5](https://github.com/embark-framework/embark/compare/v4.1.0-beta.4...v4.1.0-beta.5) (2019-07-10) + +**Note:** Version bump only for package embark-whisper + + + + + +# [4.1.0-beta.4](https://github.com/embark-framework/embark/compare/v4.1.0-beta.3...v4.1.0-beta.4) (2019-06-27) + + +### Bug Fixes + +* alleviate races re: embarkjs by introducing Plugin#addGeneratedCode and related refactors ([fc4faa8](https://github.com/embark-framework/embark/commit/fc4faa8)) + + + + + +# [4.1.0-beta.3](https://github.com/embark-framework/embark/compare/v4.1.0-beta.2...v4.1.0-beta.3) (2019-06-07) + + +### Bug Fixes + +* **@embarkjs:** unconditionally require symlinked embarkjs-* modules ([b45b2e2](https://github.com/embark-framework/embark/commit/b45b2e2)) + + + + + +# [4.1.0-beta.2](https://github.com/embark-framework/embark/compare/v4.1.0-beta.1...v4.1.0-beta.2) (2019-05-22) + +**Note:** Version bump only for package embark-whisper + + + + + +# [4.1.0-beta.1](https://github.com/embark-framework/embark/compare/v4.1.0-beta.0...v4.1.0-beta.1) (2019-05-15) + +**Note:** Version bump only for package embark-whisper + + + + + +# [4.1.0-beta.0](https://github.com/embark-framework/embark/compare/v4.0.0...v4.1.0-beta.0) (2019-04-17) + +**Note:** Version bump only for package embark-whisper diff --git a/packages/plugins/whisper-parity/README.md b/packages/plugins/whisper-parity/README.md new file mode 100644 index 000000000..2dd5fea41 --- /dev/null +++ b/packages/plugins/whisper-parity/README.md @@ -0,0 +1,6 @@ +# `embark-whisper-parity` + +> Module to add Whisper support to Embark for the Parity blockchain + +Visit [embark.status.im](https://embark.status.im/) to get started with +[Embark](https://github.com/embark-framework/embark). diff --git a/packages/plugins/whisper-parity/package.json b/packages/plugins/whisper-parity/package.json new file mode 100644 index 000000000..a9ae34a81 --- /dev/null +++ b/packages/plugins/whisper-parity/package.json @@ -0,0 +1,65 @@ +{ + "name": "embark-whisper-parity", + "version": "5.0.0-alpha.1", + "author": "Iuri Matias ", + "contributors": [], + "description": "Module to add Whisper support to Embark", + "homepage": "https://github.com/embark-framework/embark/tree/master/packages/plugins/whisper-parity#readme", + "bugs": "https://github.com/embark-framework/embark/issues", + "keywords": [ + "blockchain", + "dapps", + "ethereum", + "ipfs", + "serverless", + "solc", + "solidity" + ], + "files": [ + "dist" + ], + "license": "MIT", + "repository": { + "directory": "packages/plugins/whisper-parity", + "type": "git", + "url": "https://github.com/embark-framework/embark.git" + }, + "main": "./dist/index.js", + "embark-collective": { + "build:node": true + }, + "scripts": { + "_build": "npm run solo -- build", + "ci": "npm run qa", + "clean": "npm run reset", + "lint": "eslint src/", + "qa": "npm-run-all lint _build", + "reset": "npx rimraf dist embark-*.tgz package", + "solo": "embark-solo" + }, + "eslintConfig": { + "extends": "../../../.eslintrc.json" + }, + "dependencies": { + "@babel/runtime-corejs3": "7.6.3", + "async": "2.6.1", + "core-js": "3.3.5", + "embark-core": "^5.0.0-alpha.1", + "embark-i18n": "^5.0.0-alpha.1", + "embark-utils": "^5.0.0-alpha.1", + "embarkjs-whisper": "^5.0.0-alpha.1", + "rxjs": "6.4.0" + }, + "devDependencies": { + "embark-solo": "^5.0.0-alpha.0", + "eslint": "5.7.0", + "npm-run-all": "4.1.5", + "rimraf": "3.0.0", + "web3": "1.2.1" + }, + "engines": { + "node": ">=10.17.0 <12.0.0", + "npm": ">=6.11.3", + "yarn": ">=1.19.1" + } +} diff --git a/packages/plugins/whisper-parity/src/api.js b/packages/plugins/whisper-parity/src/api.js new file mode 100644 index 000000000..7c399270c --- /dev/null +++ b/packages/plugins/whisper-parity/src/api.js @@ -0,0 +1,51 @@ +import EmbarkJS from "embarkjs"; +import EmbarkJSWhisper from "embarkjs-whisper"; + +class API { + + constructor(embark) { + this.embark = embark; + this.communicationConfig = embark.config.communicationConfig; + } + + async initEmbarkJSWhisper() { + if (Object.keys(EmbarkJS.Messages.Providers).includes("whisper")) return; + + EmbarkJS.Messages.registerProvider("whisper", EmbarkJSWhisper); + EmbarkJS.Messages.setProvider("whisper", this.communicationConfig.connection); + } + + async registerAPICalls() { + this.initEmbarkJSWhisper(); + this.registerSendMessageCall(); + this.registerListenToCall(); + } + + async registerSendMessageCall() { + this.embark.registerAPICall( + "post", + "/embark-api/communication/sendMessage", + (req, res) => { + EmbarkJS.Messages.sendMessage({ topic: req.body.topic, data: req.body.message }, (err, result) => { + if (err) { + return res.status(500).send({ error: err }); + } + res.send(result); + }); + }); + } + + async registerListenToCall() { + this.embark.registerAPICall( + "ws", + "/embark-api/communication/listenTo/:topic", + (ws, req) => { + EmbarkJS.Messages.listenTo({ topic: req.params.topic }).subscribe(data => { + ws.send(JSON.stringify(data)); + }); + }); + } + +} + +module.exports = API; diff --git a/packages/plugins/whisper-parity/src/check.js b/packages/plugins/whisper-parity/src/check.js new file mode 100644 index 000000000..95939036f --- /dev/null +++ b/packages/plugins/whisper-parity/src/check.js @@ -0,0 +1,65 @@ +const WebSocket = require("ws"); +const http = require("http"); + +const LIVENESS_CHECK = JSON.stringify({ + jsonrpc: '2.0', + method: 'web3_clientVersion', + params:[], + id:42 +}); + +// eslint-disable-next-line complexity +const parseAndRespond = (data, cb) => { + let resp; + try { + resp = JSON.parse(data); + if (resp.error) { + return cb(resp.error); + } + } catch (e) { + return cb("Version data is not valid JSON"); + } + if (!resp || !resp.result) { + return cb("No version returned"); + } + const result = resp.result.replace("//", "/"); + const [_, version, __] = result.split("/"); + cb(null, version); +}; + +const rpc = (host, port, cb) => { + const options = { + hostname: host, // TODO(andremedeiros): get from config + port, // TODO(andremedeiros): get from config + method: "POST", + timeout: 1000, + headers: { + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(LIVENESS_CHECK) + } + }; + + const req = http.request(options, (res) => { + let data = ""; + res.on("data", (chunk) => { data += chunk; }); + res.on("end", () => parseAndRespond(data, cb)); + }); + req.on("error", (e) => cb(e)); + req.write(LIVENESS_CHECK); + req.end(); +}; + +const ws = (host, port, cb) => { + const conn = new WebSocket("ws://" + host + ":" + port); + conn.on("message", (data) => { + parseAndRespond(data, cb); + conn.close(); + }); + conn.on("open", () => conn.send(LIVENESS_CHECK)); + conn.on("error", (e) => cb(e)); +}; + +module.exports = { + ws, + rpc +}; diff --git a/packages/plugins/whisper-parity/src/index.js b/packages/plugins/whisper-parity/src/index.js new file mode 100644 index 000000000..3d2895ae0 --- /dev/null +++ b/packages/plugins/whisper-parity/src/index.js @@ -0,0 +1,100 @@ +import { __ } from "embark-i18n"; +import { dappPath, canonicalHost, defaultHost } from "embark-utils"; +const constants = require("embark-core/constants"); +const API = require("./api.js"); +import { ws, rpc } from "./check.js"; + +class Whisper { + constructor(embark, _options) { + this.logger = embark.logger; + this.events = embark.events; + this.fs = embark.fs; + this.blockchainConfig = embark.config.blockchainConfig; + this.communicationConfig = embark.config.communicationConfig; + this.embarkConfig = embark.config.embarkConfig; + this.embark = embark; + this.webSocketsChannels = {}; + this.modulesPath = dappPath(embark.config.embarkConfig.generationDir, constants.dappArtifacts.symlinkDir); + + if (!this.communicationConfig.enabled || this.blockchainConfig.client !== constants.blockchain.clients.parity) { + return; + } + + this.api = new API(embark); + this.whisperNodes = {}; + + this.events.request("embarkjs:plugin:register", "messages", "whisper", "embarkjs-whisper"); + this.events.request("embarkjs:console:register", "messages", "whisper", "embarkjs-whisper"); + + this.events.request("communication:node:register", "whisper", (readyCb) => { + this.events.request("processes:register", "communication", { + launchFn: (cb) => { + this.startWhisperNode(cb); + }, + stopFn: (cb) => { + this.stopWhisperNode(cb); + } + }); + + this.events.request("processes:launch", "communication", (err) => { + if (err) { + this.logger.error(`Error launching whisper process: ${err.message || err}`); + } + readyCb(); + }); + + this.registerServiceCheck(); + }); + + this.events.on("communication:started", () => { + this.api = new API(embark); + this.api.registerAPICalls(); + this.connectEmbarkJSProvider(); + }); + } + + _getNodeState(err, version, cb) { + if (err) return cb({ name: "Whisper node not found", status: "off" }); + + let nodeName = "Parity"; + let versionNumber = version.split("-")[0]; + let name = nodeName + " " + versionNumber + " (Whisper)"; + return cb({ name, status: "on" }); + } + + registerServiceCheck() { + this.events.request("services:register", "Whisper", (cb) => { + const { host, port, type } = this.communicationConfig.connection; + if (type === "ws") { + return ws(host, port, (err, version) => this._getNodeState(err, version, cb)); + } + rpc(host, port, (err, version) => this._getNodeState(err, version, cb)); + + }, 5000, "off"); + } + + startWhisperNode(callback) { + this.logger.info(`Whisper node has already been started with the Parity blockchain.`); + callback(); + } + + stopWhisperNode(cb) { + this.logger.warn(`Cannot stop Whisper process as it has been started with the Parity blockchain.`); + cb(); + } + + // esline-disable-next-line complexity + async connectEmbarkJSProvider() { + let connection = this.communicationConfig.connection || {}; + const config = { + server: canonicalHost(connection.host || defaultHost), + port: connection.port || "8546", + type: connection.type || "ws" + }; + + this.events.request("embarkjs:console:setProvider", "messages", "whisper", config); + } + +} + +module.exports = Whisper; diff --git a/packages/plugins/whisper/src/index.js b/packages/plugins/whisper/src/index.js deleted file mode 100644 index 7edcfcd2a..000000000 --- a/packages/plugins/whisper/src/index.js +++ /dev/null @@ -1,56 +0,0 @@ -import { __ } from 'embark-i18n'; -import {canonicalHost, defaultHost} from 'embark-utils'; -const API = require('./api.js'); - -class Whisper { - constructor(embark, _options) { - this.logger = embark.logger; - this.events = embark.events; - this.fs = embark.fs; - this.communicationConfig = embark.config.communicationConfig; - this.embarkConfig = embark.config.embarkConfig; - this.embark = embark; - - this.api = new API(embark); - this.whisperNodes = {}; - - this.events.request("embarkjs:plugin:register", 'messages', 'whisper', 'embarkjs-whisper'); - this.events.request("embarkjs:console:register", 'messages', 'whisper', 'embarkjs-whisper'); - - this.events.setCommandHandler("whisper:node:register", (clientName, startCb) => { - this.whisperNodes[clientName] = startCb; - }); - - this.events.request("communication:node:register", "whisper", (readyCb) => { - if (this.communicationConfig.connection.port === this.embark.config.blockchainConfig.wsPort) { - this.logger.warn(__('Communication connection set to open on port %s, which is the same as your blockchain node. It will probably conflict', this.communicationConfig.connection.port)); - this.logger.warn(__('You can change that port in your communication config file as `connection.port`')); - } - - let clientName = this.communicationConfig.client || "geth"; - let registerCb = this.whisperNodes[clientName]; - if (!registerCb) return readyCb("whisper client " + clientName + " not found"); - registerCb.apply(registerCb, [readyCb]); - }); - - this.events.on("communication:started", () => { - this.api = new API(embark); - this.api.registerAPICalls(); - this.connectEmbarkJSProvider(); - }); - } - - async connectEmbarkJSProvider() { - let connection = this.communicationConfig.connection || {}; - const config = { - server: canonicalHost(connection.host || defaultHost), - port: connection.port || '8557', - type: connection.type || 'ws' - }; - - this.events.request("embarkjs:console:setProvider", 'messages', 'whisper', config); - } - -} - -module.exports = Whisper;