feat(@embark/whisper): Add Whisper client config

Add option in communication config to choose which Whisper client to use.

Because Parity’s implementation of Whisper is not compatible with Whisper v6, and therefore web3.js in its current form, the following changes have been made:
1. remove any functionality associated with launching a Parity Whisper process.
2. Warn the user when the Parity Whisper client has been opted for in the communication config.
3. Return an error for API calls when Parity Whisper client has been opted for in the communication config.
4. Update Cockpit’s Communication module to show errors returned from API calls.
5. Update the messaging configuration documentation for the new communication client option.
This commit is contained in:
emizzle 2019-12-03 17:16:18 +11:00 committed by Iuri Matias
parent 446197baff
commit bd4b110a78
12 changed files with 58 additions and 150 deletions

View File

@ -4,6 +4,7 @@ module.exports = {
enabled: true,
provider: "whisper", // Communication provider. Currently, Embark only supports whisper
available_providers: ["whisper"], // Array of available providers
client: "geth"
},
// default environment, merges with the settings in default
@ -35,12 +36,12 @@ module.exports = {
// "embark run custom_name"
//custom_name: {
//}
// Use this section when you need a specific symmetric or private keys in whisper
/*
,keys: {
symmetricKey: "your_symmetric_key",// Symmetric key for message decryption
privateKey: "your_private_key" // Private Key to be used as a signing key and for message decryption
}
*/
// Use this section when you need a specific symmetric or private keys in whisper
/*
,keys: {
symmetricKey: "your_symmetric_key",// Symmetric key for message decryption
privateKey: "your_private_key" // Private Key to be used as a signing key and for message decryption
}
*/
//}
};

View File

@ -4,6 +4,7 @@ module.exports = {
enabled: true,
provider: "whisper", // Communication provider. Currently, Embark only supports whisper
available_providers: ["whisper"], // Array of available providers
client: "geth"
},
// default environment, merges with the settings in default
@ -36,11 +37,11 @@ module.exports = {
// "embark run custom_name"
//custom_name: {
//}
// Use this section when you need a specific symmetric or private keys in whisper
/*
,keys: {
symmetricKey: "your_symmetric_key",// Symmetric key for message decryption
privateKey: "your_private_key" // Private Key to be used as a signing key and for message decryption
}
*/
// Use this section when you need a specific symmetric or private keys in whisper
/*
,keys: {
symmetricKey: "your_symmetric_key",// Symmetric key for message decryption
privateKey: "your_private_key" // Private Key to be used as a signing key and for message decryption
}
*/
};

View File

@ -1,5 +1,5 @@
import PropTypes from "prop-types";
import React, {Component} from 'react';
import React, { Component } from 'react';
import {
Button,
Card,
@ -13,6 +13,7 @@ import {
ListGroup,
ListGroupItem
} from 'reactstrap';
import Error from "./Error";
class Communication extends Component {
constructor(props) {
@ -61,6 +62,7 @@ class Communication extends Component {
return (
<Row className="justify-content-md-center">
<Col xs="12" sm="9" lg="9">
{this.props.error && this.props.error.error && <Error error={this.props.error.error} />}
<Card>
<CardHeader>
<strong>Listen to channel</strong>
@ -147,7 +149,8 @@ Communication.propTypes = {
sendMessage: PropTypes.func,
listenToMessages: PropTypes.func,
subscriptions: PropTypes.array,
channels: PropTypes.object
channels: PropTypes.object,
error: PropTypes.object
};
export default Communication;

View File

@ -5,7 +5,7 @@ import { Alert } from 'reactstrap';
import {messageSend, messageListen, versions} from "../actions";
import Communication from "../components/Communication";
import PageHead from "../components/PageHead";
import {getMessages, getMessageChannels, isOldWeb3, isWeb3Enabled} from "../reducers/selectors";
import { getMessages, getMessageChannels, isOldWeb3, isWeb3Enabled, getMessagesError } from "../reducers/selectors";
class CommunicationContainer extends Component {
componentDidMount() {
@ -37,9 +37,11 @@ class CommunicationContainer extends Component {
<React.Fragment>
<PageHead title="Communication" description="Interact with the decentralised communication protocols configured for Embark (ie Whisper)" />
<Communication listenToMessages={(channel) => this.listenToChannel(channel)}
sendMessage={(channel, message) => this.sendMessage(channel, message)}
channels={this.props.messages}
subscriptions={this.props.messageChannels}/>
sendMessage={(channel, message) => this.sendMessage(channel, message)}
channels={this.props.messages}
subscriptions={this.props.messageChannels}
error={this.props.error}
/>
</React.Fragment>
);
}
@ -60,7 +62,8 @@ CommunicationContainer.propTypes = {
isWeb3Enabled: PropTypes.bool,
messages: PropTypes.object,
messageChannels: PropTypes.array,
fetchVersions: PropTypes.func
fetchVersions: PropTypes.func,
error: PropTypes.object
};
function mapStateToProps(state) {
@ -68,7 +71,8 @@ function mapStateToProps(state) {
messages: getMessages(state),
messageChannels: getMessageChannels(state),
isOldWeb3: isOldWeb3(state),
isWeb3Enabled: isWeb3Enabled(state)
isWeb3Enabled: isWeb3Enabled(state),
error: getMessagesError(state)
};
}

View File

@ -150,6 +150,12 @@ export function getMessages(state) {
return messages;
}
export function getMessagesError(state) {
return {
error: state.errorEntities.messages
};
}
export function getFiddleDeploy(state) {
return {
data: last(state.entities.fiddleDeploys),

View File

@ -571,7 +571,10 @@ export function *listenToMessages(action) {
const channel = yield call(createChannel, socket);
while (true) {
const message = yield take(channel);
yield put(actions.messageListen.success([{channel: action.messageChannels[0], message: message.data, time: message.time}]));
if (message.error) {
return yield put(actions.messageListen.failure(message.error));
}
yield put(actions.messageListen.success([{ channel: action.messageChannels[0], message: message.data, time: message.time }]));
}
}

View File

@ -567,6 +567,7 @@ export class Config {
enabled: true,
provider: "whisper",
available_providers: ["whisper"],
client: "geth",
connection: {
host: defaultHost,
port: 8547,

View File

@ -18,7 +18,7 @@ class Whisper {
this.webSocketsChannels = {};
this.modulesPath = dappPath(embark.config.embarkConfig.generationDir, constants.dappArtifacts.symlinkDir);
if (!this.communicationConfig.enabled || this.blockchainConfig.client !== constants.blockchain.clients.geth) {
if (!this.communicationConfig.enabled || this.communicationConfig.client !== constants.blockchain.clients.geth) {
return;
}

View File

@ -1,7 +1,9 @@
import EmbarkJS from "embarkjs";
import EmbarkJSWhisper from "embarkjs-whisper";
class API {
export const PARITY_WHISPER_ERROR = "Parity's implementation of Whisper is not compatible with Whisper v6 (and therefore web3.js). Try changing the communication config to use '{client: \"geth\"}' instead.";
export class Api {
constructor(embark) {
this.embark = embark;
@ -25,13 +27,8 @@ class API {
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);
});
(_req, res) => {
res.status(500).send({ error: PARITY_WHISPER_ERROR });
});
}
@ -39,13 +36,8 @@ class API {
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));
});
(ws, _req) => {
ws.send(JSON.stringify({ error: PARITY_WHISPER_ERROR }));
});
}
}
module.exports = API;

View File

@ -1,65 +0,0 @@
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
};

View File

@ -1,8 +1,7 @@
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";
const { Api, PARITY_WHISPER_ERROR } = require("./api.js");
class Whisper {
constructor(embark, _options) {
@ -16,71 +15,33 @@ class Whisper {
this.webSocketsChannels = {};
this.modulesPath = dappPath(embark.config.embarkConfig.generationDir, constants.dappArtifacts.symlinkDir);
if (!this.communicationConfig.enabled || this.blockchainConfig.client !== constants.blockchain.clients.parity) {
if (!this.communicationConfig.enabled || this.communicationConfig.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.logger.warn(PARITY_WHISPER_ERROR);
readyCb();
this.registerServiceCheck();
});
this.events.on("communication:started", () => {
this.api = new API(embark);
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();
cb({ name: "Whisper Parity", status: "off" });
}, 1000 * 60 * 30, "off");
}
// esline-disable-next-line complexity

View File

@ -28,4 +28,5 @@ Option | Type: `default` | Value
`enabled` | boolean: `true/false` | To enable or completely disable communication support
`provider` | string: `whisper` | Desired provider to automatically connect to in the dapp.
`available_providers` | array: `["whisper"]` | List of communication platforms to be supported in the dapp. This will affect what's available with the EmbarkJS library in the dapp so if you don't need Whisper for example, removing it from this will considerably reduce the file size of the generated JS code.
`client` | string: `geth/parity` | Desired Whisper client for Embark to start. **NOTE:** Parity's implementation of Whisper does not currently adhere to Whisper v6 standards and thus is not supported by `web3.js`.