/ lib / modules / whisper / index.js
index.js
  1  let utils = require('../../utils/utils.js');
  2  let fs = require('../../core/fs.js');
  3  let Web3 = require('web3');
  4  const {parallel} = require('async');
  5  const {sendMessage, listenTo} = require('./js/communicationFunctions');
  6  const messageEvents = require('./js/message_events');
  7  
  8  const {canonicalHost, defaultHost} = require('../../utils/host');
  9  
 10  class Whisper {
 11  
 12    constructor(embark, _options) {
 13      this.logger = embark.logger;
 14      this.events = embark.events;
 15      this.communicationConfig = embark.config.communicationConfig;
 16      this.web3 = new Web3();
 17      this.embark = embark;
 18      this.web3Ready = false;
 19  
 20      if (!this.communicationConfig.enabled) {
 21        return;
 22      }
 23  
 24      this.connectToProvider();
 25  
 26      this.events.request('processes:register', 'whisper', (cb) => {
 27        this.setServiceCheck()
 28        this.addWhisperToEmbarkJS();
 29        this.addSetProvider();
 30        this.waitForWeb3Ready(() => {
 31          this.registerAPICalls();
 32          cb();
 33        });
 34      });
 35  
 36      this.events.request('processes:launch', 'whisper');
 37    }
 38  
 39    connectToProvider() {
 40      let {host, port} = this.communicationConfig.connection;
 41      let web3Endpoint = 'ws://' + host + ':' + port;
 42      // Note: dont't pass to the provider things like {headers: {Origin: "embark"}}. Origin header is for browser to fill
 43      // to protect user, it has no meaning if it is used server-side. See here for more details: https://github.com/ethereum/go-ethereum/issues/16608
 44      // Moreover, Parity reject origins that are not urls so if you try to connect with Origin: "embark" it gives the followin error:
 45      // << Blocked connection to WebSockets server from untrusted origin: Some("embark") >>
 46      // The best choice is to use void origin, BUT Geth rejects void origin, so to keep both clients happy we can use http://embark
 47      this.web3.setProvider(new Web3.providers.WebsocketProvider(web3Endpoint, {headers: {Origin: "http://embark"}}));
 48    }
 49  
 50    waitForWeb3Ready(cb) {
 51      if (this.web3Ready) {
 52        return cb();
 53      }
 54      if (this.web3.currentProvider.connection.readyState !== 1) {
 55        return setTimeout(this.waitForWeb3Ready.bind(this, cb), 50);
 56      }
 57      this.web3Ready = true;
 58      cb();
 59    }
 60  
 61    setServiceCheck() {
 62      const self = this;
 63      self.events.request("services:register", 'Whisper', function(cb) {
 64        if (!self.web3.currentProvider || self.web3.currentProvider.connection.readyState !== 1) {
 65          return self.connectToProvider();
 66        }
 67        // 1) Parity does not implement shh_version JSON-RPC method
 68        // 2) web3 1.0 still does not implement web3_clientVersion
 69        // so we must do all by our own
 70        self.web3._requestManager.send({method: 'web3_clientVersion', params: []}, (err, clientVersion) => {
 71          if (err) return cb(err);
 72          if (clientVersion.indexOf("Parity-Ethereum//v2") === 0) {
 73            // This is Parity
 74            return self.web3.shh.getInfo(function(err) {
 75              if (err) {
 76                return cb({name: 'Whisper', status: 'off'});
 77              }
 78              // TOFIX Assume Whisper v6 until there's a way to understand it via JSON-RPC
 79              return cb({name: 'Whisper (version 6)', status: 'on'});
 80            });
 81          }
 82          // Assume it is a Geth compliant client
 83          self.web3.shh.getVersion(function(err, version) {
 84            if (err || version == "2") {
 85              return cb({name: 'Whisper', status: 'off'});
 86            }
 87            return cb({name: 'Whisper (version ' + version + ')', status: 'on'});
 88          });
 89        });
 90      });
 91    }
 92  
 93    addWhisperToEmbarkJS() {
 94      const self = this;
 95      // TODO: make this a shouldAdd condition
 96      if (this.communicationConfig === {}) {
 97        return;
 98      }
 99      if ((this.communicationConfig.available_providers.indexOf('whisper') < 0) && (this.communicationConfig.provider !== 'whisper' || this.communicationConfig.enabled !== true)) {
100        return;
101      }
102  
103      // TODO: possible race condition could be a concern
104      this.events.request("version:get:web3", function(web3Version) {
105        let code = "";
106        code += "\n" + fs.readFileSync(utils.joinPath(__dirname, 'js', 'message_events.js')).toString();
107  
108        if (web3Version[0] === "0") {
109          self.isOldWeb3 = true;
110          code += "\n" + fs.readFileSync(utils.joinPath(__dirname, 'js', 'embarkjs_old_web3.js')).toString();
111          code += "\nEmbarkJS.Messages.registerProvider('whisper', __embarkWhisperOld);";
112        } else {
113          code += "\n" + fs.readFileSync(utils.joinPath(__dirname, 'js', 'communicationFunctions.js')).toString();
114          code += "\n" + fs.readFileSync(utils.joinPath(__dirname, 'js', 'embarkjs.js')).toString();
115          code += "\nEmbarkJS.Messages.registerProvider('whisper', __embarkWhisperNewWeb3);";
116        }
117        self.embark.addCodeToEmbarkJS(code);
118      });
119    }
120  
121    addSetProvider() {
122      let connection = this.communicationConfig.connection || {};
123      const shouldInit = (communicationConfig) => {
124        return (communicationConfig.provider === 'whisper' && communicationConfig.enabled === true);
125      };
126  
127      // todo: make the add code a function as well
128      const config = {
129        server: canonicalHost(connection.host || defaultHost),
130        port: connection.port || '8546',
131        type: connection.type || 'ws'
132      };
133      const code = `\nEmbarkJS.Messages.setProvider('whisper', ${JSON.stringify(config)});`;
134      this.embark.addProviderInit('communication', code, shouldInit);
135  
136      const consoleConfig = Object.assign({}, config, {providerOptions: {headers: {Origin: "http://embark"}}});
137      const consoleCode = `\nEmbarkJS.Messages.setProvider('whisper', ${JSON.stringify(consoleConfig)});`;
138      this.embark.addConsoleProviderInit('communication', consoleCode, shouldInit);
139    }
140  
141    registerAPICalls() {
142      const self = this;
143      if (self.apiCallsRegistered) {
144        return;
145      }
146      self.apiCallsRegistered = true;
147      let symKeyID, sig;
148      parallel([
149        function(paraCb) {
150          self.web3.shh.newSymKey((err, id) => {
151            symKeyID = id;
152            paraCb(err);
153          });
154        },
155        function(paraCb) {
156          self.web3.shh.newKeyPair((err, id) => {
157            sig = id;
158            paraCb(err);
159          });
160        }
161      ], (err) => {
162        if (err) {
163          self.logger.error('Error getting Whisper keys:', err.message || err);
164          return;
165        }
166        self.embark.registerAPICall(
167          'post',
168          '/embark-api/communication/sendMessage',
169          (req, res) => {
170            sendMessage({
171              topic: req.body.topic,
172              data: req.body.message,
173              sig,
174              symKeyID,
175              fromAscii: self.web3.utils.asciiToHex,
176              toHex: self.web3.utils.toHex,
177              post: self.web3.shh.post
178            }, (err, result) => {
179              if (err) {
180                return res.status(500).send({error: err});
181              }
182              res.send(result);
183            });
184          });
185  
186        self.embark.registerAPICall(
187          'ws',
188          '/embark-api/communication/listenTo/:topic',
189          (ws, req) => {
190            self.webSocketsChannels[req.params.topic] = listenTo({
191              topic: req.params.topic,
192              messageEvents,
193              toHex: self.web3.utils.toHex,
194              toAscii: self.web3.utils.hexToAscii,
195              sig,
196              symKeyID,
197              subscribe: self.web3.shh.subscribe
198            }, (err, result) => {
199              if (ws.readyState === ws.CLOSED) {
200                return;
201              }
202              if (err) {
203                return ws.status(500).send(JSON.stringify({error: err}));
204              }
205              ws.send(JSON.stringify(result));
206            });
207          });
208      });
209    }
210  }
211  
212  module.exports = Whisper;