const Streams = require('mississippi') const StreamProvider = require('web3-stream-provider') const ObjectMultiplex = require('./obj-multiplex') const RemoteStore = require('./remote-store.js').RemoteStore const createRandomId = require('./random-id') module.exports = MetamaskInpageProvider function MetamaskInpageProvider (connectionStream) { const self = this // setup connectionStream multiplexing var multiStream = ObjectMultiplex() Streams.pipe(connectionStream, multiStream, connectionStream, function (err) { let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask' if (err) warningMsg += '\n' + err.stack console.warn(warningMsg) }) self.multiStream = multiStream // subscribe to metamask public config var publicConfigStore = remoteStoreWithLocalStorageCache('MetaMask-Config') var storeStream = publicConfigStore.createStream() Streams.pipe(storeStream, multiStream.createStream('publicConfig'), storeStream, function (err) { let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask publicConfig' if (err) warningMsg += '\n' + err.stack console.warn(warningMsg) }) self.publicConfigStore = publicConfigStore // connect to async provider var asyncProvider = new StreamProvider() Streams.pipe(asyncProvider, multiStream.createStream('provider'), asyncProvider, function (err) { let warningMsg = 'MetamaskInpageProvider - lost connection to MetaMask provider' if (err) warningMsg += '\n' + err.stack console.warn(warningMsg) }) asyncProvider.on('error', console.error.bind(console)) self.asyncProvider = asyncProvider self.idMap = {} // handle sendAsync requests via asyncProvider self.sendAsync = function (payload, cb) { // rewrite request ids var request = eachJsonMessage(payload, (message) => { var newId = createRandomId() self.idMap[newId] = message.id message.id = newId return message }) // forward to asyncProvider asyncProvider.sendAsync(request, function (err, res) { if (err) return cb(err) // transform messages to original ids eachJsonMessage(res, (message) => { var oldId = self.idMap[message.id] delete self.idMap[message.id] message.id = oldId return message }) cb(null, res) }) } } MetamaskInpageProvider.prototype.send = function (payload) { const self = this let selectedAccount let result = null switch (payload.method) { case 'eth_accounts': // read from localStorage selectedAccount = self.publicConfigStore.get('selectedAddress') result = selectedAccount ? [selectedAccount] : [] break case 'eth_coinbase': // read from localStorage selectedAccount = self.publicConfigStore.get('selectedAddress') result = selectedAccount || '0x0000000000000000000000000000000000000000' break case 'eth_uninstallFilter': self.sendAsync(payload, noop) result = true break // throw not-supported Error default: var link = 'https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#dizzy-all-async---think-of-metamask-as-a-light-client' var message = `The MetaMask Web3 object does not support synchronous methods like ${payload.method} without a callback parameter. See ${link} for details.` throw new Error(message) } // return the result return { id: payload.id, jsonrpc: payload.jsonrpc, result: result, } } MetamaskInpageProvider.prototype.sendAsync = function () { throw new Error('MetamaskInpageProvider - sendAsync not overwritten') } MetamaskInpageProvider.prototype.isConnected = function () { return true } // util function remoteStoreWithLocalStorageCache (storageKey) { // read local cache var initState = JSON.parse(localStorage[storageKey] || '{}') var store = new RemoteStore(initState) // cache the latest state locally store.subscribe(function (state) { localStorage[storageKey] = JSON.stringify(state) }) return store } function eachJsonMessage (payload, transformFn) { if (Array.isArray(payload)) { return payload.map(transformFn) } else { return transformFn(payload) } } function noop () {}