From 00e9f3c6ae2d4092f0c9270d113d7e6dd47ddf0b Mon Sep 17 00:00:00 2001 From: kumavis Date: Sun, 22 May 2016 15:23:16 -0700 Subject: inpage - refactor for modularity --- app/scripts/inpage.js | 155 +++---------------------------------- app/scripts/lib/auto-reload.js | 37 +++++++++ app/scripts/lib/ensnare.js | 24 ++++++ app/scripts/lib/inpage-provider.js | 123 +++++++++++++++++++++++++++++ package.json | 1 + 5 files changed, 197 insertions(+), 143 deletions(-) create mode 100644 app/scripts/lib/auto-reload.js create mode 100644 app/scripts/lib/ensnare.js create mode 100644 app/scripts/lib/inpage-provider.js diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 91d782a32..652a95d7c 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -1,17 +1,12 @@ cleanContextForImports() -const createPayload = require('web3-provider-engine/util/create-payload') -const StreamProvider = require('./lib/stream-provider.js') -const LocalMessageDuplexStream = require('./lib/local-message-stream.js') -const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex -const RemoteStore = require('./lib/remote-store.js').RemoteStore -const MetamaskConfig = require('./config.js') const Web3 = require('web3') -const once = require('once') +const LocalMessageDuplexStream = require('./lib/local-message-stream.js') +const setupDappAutoReload = require('./lib/auto-reload.js') +const MetamaskInpageProvider = require('./lib/inpage-provider.js') restoreContextAfterImports() -// rename on window +// remove from window delete window.Web3 -window.MetamaskWeb3 = Web3 // @@ -19,166 +14,40 @@ window.MetamaskWeb3 = Web3 // // setup background connection -var pluginStream = new LocalMessageDuplexStream({ +var metamaskStream = new LocalMessageDuplexStream({ name: 'inpage', target: 'contentscript', }) -var mx = setupMultiplex(pluginStream) -// connect to provider -var remoteProvider = new StreamProvider() -remoteProvider.pipe(mx.createStream('provider')).pipe(remoteProvider) -remoteProvider.on('error', console.error.bind(console)) - -// subscribe to metamask public config -var initState = JSON.parse(localStorage['MetaMask-Config'] || '{}') -var publicConfigStore = new RemoteStore(initState) -var storeStream = publicConfigStore.createStream() -storeStream.pipe(mx.createStream('publicConfig')).pipe(storeStream) -publicConfigStore.subscribe(function(state){ - localStorage['MetaMask-Config'] = JSON.stringify(state) -}) +// compose the inpage provider +var inpageProvider = new MetamaskInpageProvider(metamaskStream) // // setup web3 // -var web3 = new Web3(remoteProvider) +var web3 = new Web3(inpageProvider) web3.setProvider = function(){ console.log('MetaMask - overrode web3.setProvider') } console.log('MetaMask - injected web3') // -// automatic dapp reset -// - -// export web3 as a global, checking for usage -var pageIsUsingWeb3 = false -var resetWasRequested = false -window.web3 = ensnare(web3, once(function(){ - // if web3 usage happened after a reset request, trigger reset late - if (resetWasRequested) return triggerReset() - // mark web3 as used - pageIsUsingWeb3 = true - // reset web3 reference - window.web3 = web3 -})) - -// listen for reset requests -mx.createStream('control').once('data', function(){ - resetWasRequested = true - // ignore if web3 was not used - if (!pageIsUsingWeb3) return - // reload after short timeout - triggerReset() -}) - -function triggerReset(){ - setTimeout(function(){ - window.location.reload() - }, 500) -} - -// -// handle synchronous requests +// export global web3 with auto dapp reload // -global.publicConfigStore = publicConfigStore +var controlStream = inpageProvider.multiStream.createStream('control') +setupDappAutoReload(web3, controlStream) // set web3 defaultAcount -publicConfigStore.subscribe(function(state){ +inpageProvider.publicConfigStore.subscribe(function(state){ web3.eth.defaultAccount = state.selectedAddress }) -// setup sync http provider -updateProvider({ provider: publicConfigStore.get('provider') }) -publicConfigStore.subscribe(updateProvider) - -var syncProvider = null -var syncProviderUrl = null - -function updateProvider(state){ - var providerConfig = state.provider || {} - var newSyncProviderUrl = undefined - - if (providerConfig.rpcTarget) { - newSyncProviderUrl = providerConfig.rpcTarget - } else { - switch(providerConfig.type) { - case 'testnet': - newSyncProviderUrl = MetamaskConfig.network.testnet - break - case 'mainnet': - newSyncProviderUrl = MetamaskConfig.network.mainnet - break - default: - newSyncProviderUrl = MetamaskConfig.network.default - } - } - if (newSyncProviderUrl === syncProviderUrl) return - syncProvider = new Web3.providers.HttpProvider(newSyncProviderUrl) -} - -// handle sync methods -remoteProvider.send = function(payload){ - var result = null - switch (payload.method) { - - case 'eth_accounts': - // read from localStorage - var selectedAddress = publicConfigStore.get('selectedAddress') - result = selectedAddress ? [selectedAddress] : [] - break - - case 'eth_coinbase': - // read from localStorage - var selectedAddress = publicConfigStore.get('selectedAddress') - result = selectedAddress || '0x0000000000000000000000000000000000000000' - break - - // fallback to normal rpc - default: - return syncProvider.send(payload) - - } - - // return the result - return { - id: payload.id, - jsonrpc: payload.jsonrpc, - result: result, - } -} - - // // util // -// creates a proxy object that calls cb everytime the obj's properties/fns are accessed -function ensnare(obj, cb){ - var proxy = {} - Object.keys(obj).forEach(function(key){ - var val = obj[key] - switch (typeof val) { - case 'function': - proxy[key] = function(){ - cb() - val.apply(obj, arguments) - } - return - default: - Object.defineProperty(proxy, key, { - get: function(){ cb(); return obj[key] }, - set: function(val){ cb(); return obj[key] = val }, - }) - return - } - }) - return proxy -} - // need to make sure we aren't affected by overlapping namespaces // and that we dont affect the app with our namespace // mostly a fix for web3's BigNumber if AMD's "define" is defined... diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js new file mode 100644 index 000000000..95a744b2c --- /dev/null +++ b/app/scripts/lib/auto-reload.js @@ -0,0 +1,37 @@ +const once = require('once') +const ensnare = require('./ensnare.js') + +module.exports = setupDappAutoReload + + +function setupDappAutoReload(web3, controlStream){ + + // export web3 as a global, checking for usage + var pageIsUsingWeb3 = false + var resetWasRequested = false + global.web3 = ensnare(web3, once(function(){ + // if web3 usage happened after a reset request, trigger reset late + if (resetWasRequested) return triggerReset() + // mark web3 as used + pageIsUsingWeb3 = true + // reset web3 reference + global.web3 = web3 + })) + + // listen for reset requests from metamask + controlStream.once('data', function(){ + resetWasRequested = true + // ignore if web3 was not used + if (!pageIsUsingWeb3) return + // reload after short timeout + triggerReset() + }) + + // reload the page + function triggerReset(){ + setTimeout(function(){ + global.location.reload() + }, 500) + } + +} \ No newline at end of file diff --git a/app/scripts/lib/ensnare.js b/app/scripts/lib/ensnare.js new file mode 100644 index 000000000..b70330a5a --- /dev/null +++ b/app/scripts/lib/ensnare.js @@ -0,0 +1,24 @@ +module.exports = ensnare + +// creates a proxy object that calls cb everytime the obj's properties/fns are accessed +function ensnare(obj, cb){ + var proxy = {} + Object.keys(obj).forEach(function(key){ + var val = obj[key] + switch (typeof val) { + case 'function': + proxy[key] = function(){ + cb() + val.apply(obj, arguments) + } + return + default: + Object.defineProperty(proxy, key, { + get: function(){ cb(); return obj[key] }, + set: function(val){ cb(); return obj[key] = val }, + }) + return + } + }) + return proxy +} diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js new file mode 100644 index 000000000..66681c3a9 --- /dev/null +++ b/app/scripts/lib/inpage-provider.js @@ -0,0 +1,123 @@ +const HttpProvider = require('web3/lib/web3/httpprovider') +const Streams = require('mississippi') +const ObjectMultiplex = require('./obj-multiplex') +const StreamProvider = require('./stream-provider.js') +const RemoteStore = require('./remote-store.js').RemoteStore +const MetamaskConfig = require('../config.js') + +module.exports = MetamaskInpageProvider + + +function MetamaskInpageProvider(connectionStream){ + const self = this + + // setup connectionStream multiplexing + var multiStream = ObjectMultiplex() + Streams.pipe(connectionStream, multiStream, connectionStream, function(err){ + console.warn('MetamaskInpageProvider - lost connection to MetaMask') + if (err) throw err + }) + 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){ + console.warn('MetamaskInpageProvider - lost connection to MetaMask publicConfig') + if (err) throw err + }) + self.publicConfigStore = publicConfigStore + + // connect to sync provider + self.syncProvider = createSyncProvider(publicConfigStore.get('provider')) + // subscribe to publicConfig to update the syncProvider on change + publicConfigStore.subscribe(function(state){ + self.syncProvider = createSyncProvider(state.provider) + }) + + // connect to async provider + var asyncProvider = new StreamProvider() + Streams.pipe(asyncProvider, multiStream.createStream('provider'), asyncProvider, function(err){ + console.warn('MetamaskInpageProvider - lost connection to MetaMask provider') + if (err) throw err + }) + asyncProvider.on('error', console.error.bind(console)) + self.asyncProvider = asyncProvider + // overwrite own sendAsync method + self.sendAsync = asyncProvider.sendAsync.bind(asyncProvider) +} + +MetamaskInpageProvider.prototype.send = function(payload){ + const self = this + + var result = null + switch (payload.method) { + + case 'eth_accounts': + // read from localStorage + var selectedAddress = self.publicConfigStore.get('selectedAddress') + result = selectedAddress ? [selectedAddress] : [] + break + + case 'eth_coinbase': + // read from localStorage + var selectedAddress = self.publicConfigStore.get('selectedAddress') + result = selectedAddress || '0x0000000000000000000000000000000000000000' + break + + // fallback to normal rpc + default: + return self.syncProvider.send(payload) + + } + + // 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 createSyncProvider(providerConfig){ + providerConfig = providerConfig || {} + var syncProviderUrl = undefined + + if (providerConfig.rpcTarget) { + syncProviderUrl = providerConfig.rpcTarget + } else { + switch(providerConfig.type) { + case 'testnet': + syncProviderUrl = MetamaskConfig.network.testnet + break + case 'mainnet': + syncProviderUrl = MetamaskConfig.network.mainnet + break + default: + syncProviderUrl = MetamaskConfig.network.default + } + } + return new HttpProvider(syncProviderUrl) +} + +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 +} \ No newline at end of file diff --git a/package.json b/package.json index 2bbcb734e..49ea30d0a 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "jazzicon": "^1.1.3", "menu-droppo": "^1.1.0", "metamask-logo": "^1.1.5", + "mississippi": "^1.2.0", "multiplex": "^6.7.0", "once": "^1.3.3", "pojo-migrator": "^2.1.0", -- cgit