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;