diff options
Merge pull request #204 from MetaMask/dev
Merge UI redesign into master
Diffstat (limited to 'app/scripts')
-rw-r--r-- | app/scripts/background.js | 36 | ||||
-rw-r--r-- | app/scripts/config.js | 12 | ||||
-rw-r--r-- | app/scripts/contentscript.js | 8 | ||||
-rw-r--r-- | app/scripts/inpage.js | 138 | ||||
-rw-r--r-- | app/scripts/lib/auto-reload.js | 37 | ||||
-rw-r--r-- | app/scripts/lib/config-manager.js | 27 | ||||
-rw-r--r-- | app/scripts/lib/ensnare.js | 24 | ||||
-rw-r--r-- | app/scripts/lib/idStore.js | 29 | ||||
-rw-r--r-- | app/scripts/lib/inpage-provider.js | 123 | ||||
-rw-r--r-- | app/scripts/lib/stream-provider.js | 72 | ||||
-rw-r--r-- | app/scripts/popup.js | 2 |
11 files changed, 277 insertions, 231 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js index a52eab2d3..8c6adff04 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,11 +1,12 @@ const Dnode = require('dnode') -const ObjectMultiplex = require('./lib/obj-multiplex') const eos = require('end-of-stream') const combineStreams = require('pumpify') const extend = require('xtend') const EthStore = require('eth-store') -const PortStream = require('./lib/port-stream.js') const MetaMaskProvider = require('web3-provider-engine/zero.js') +const handleRequestsFromStream = require('web3-stream-provider/handler') +const ObjectMultiplex = require('./lib/obj-multiplex') +const PortStream = require('./lib/port-stream.js') const IdentityStore = require('./lib/idStore') const createTxNotification = require('./lib/notifications.js').createTxNotification const createMsgNotification = require('./lib/notifications.js').createMsgNotification @@ -132,25 +133,6 @@ function storeSetFromObj(store, obj){ } - -// handle rpc requests -function onRpcRequest(remoteStream, payload){ - // console.log('MetaMaskPlugin - incoming payload:', payload) - provider.sendAsync(payload, function onPayloadHandled(err, response){ - // provider engine errors are included in response objects - if (!payload.isMetamaskInternal) { - console.log('MetaMaskPlugin - RPC complete:', payload, '->', response) - if (response.error) console.error('Error in RPC response:\n'+response.error.message) - } - try { - remoteStream.write(response) - } catch (err) { - console.error(err) - } - }) -} - - // // remote features // @@ -161,7 +143,15 @@ function setupPublicConfig(stream){ } function setupProviderConnection(stream){ - stream.on('data', onRpcRequest.bind(null, stream)) + handleRequestsFromStream(stream, provider, logger) + + function logger(err, request, response){ + if (err) return console.error(err.stack) + if (!request.isMetamaskInternal) { + console.log('MetaMaskPlugin - RPC complete:', request, '->', response) + if (response.error) console.error('Error in RPC response:\n'+response.error.message) + } + } } function setupControllerConnection(stream){ @@ -182,6 +172,8 @@ function setupControllerConnection(stream){ setLocked: idStore.setLocked.bind(idStore), clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore), exportAccount: idStore.exportAccount.bind(idStore), + revealAccount: idStore.revealAccount.bind(idStore), + saveAccountLabel: idStore.saveAccountLabel.bind(idStore), }) stream.pipe(dnode).pipe(stream) dnode.on('remote', function(remote){ diff --git a/app/scripts/config.js b/app/scripts/config.js new file mode 100644 index 000000000..f26e6778d --- /dev/null +++ b/app/scripts/config.js @@ -0,0 +1,12 @@ +const MAINET_RPC_URL = 'https://mainnet.infura.io/' +const TESTNET_RPC_URL = 'https://morden.infura.io/' +const DEFAULT_RPC_URL = TESTNET_RPC_URL + +module.exports = { + network: { + default: DEFAULT_RPC_URL, + mainnet: MAINET_RPC_URL, + testnet: TESTNET_RPC_URL, + }, +} + diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 1b7b98ec9..43ae5bc98 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -25,14 +25,14 @@ pluginStream.on('error', console.error.bind(console)) // forward communication plugin->inpage pageStream.pipe(pluginStream).pipe(pageStream) -// connect contentscript->inpage control stream +// connect contentscript->inpage reload stream var mx = ObjectMultiplex() mx.on('error', console.error.bind(console)) mx.pipe(pageStream) -var controlStream = mx.createStream('control') -controlStream.on('error', console.error.bind(console)) +var reloadStream = mx.createStream('reload') +reloadStream.on('error', console.error.bind(console)) // if we lose connection with the plugin, trigger tab refresh pluginStream.on('close', function(){ - controlStream.write({ method: 'reset' }) + reloadStream.write({ method: 'reset' }) })
\ No newline at end of file diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 33e2c9358..b8532747e 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -1,18 +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 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 - -const DEFAULT_RPC_URL = 'https://rpc.metamask.io/' // @@ -20,148 +14,40 @@ const DEFAULT_RPC_URL = 'https://rpc.metamask.io/' // // 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 reloadStream = inpageProvider.multiStream.createStream('reload') +setupDappAutoReload(web3, reloadStream) // set web3 defaultAcount -publicConfigStore.subscribe(function(state){ +inpageProvider.publicConfigStore.subscribe(function(state){ web3.eth.defaultAccount = state.selectedAddress }) -// setup sync http provider -var providerConfig = publicConfigStore.get('provider') || {} -var providerUrl = providerConfig.rpcTarget ? providerConfig.rpcTarget : DEFAULT_RPC_URL -var syncProvider = new Web3.providers.HttpProvider(providerUrl) -publicConfigStore.subscribe(function(state){ - if (!state.provider) return - if (!state.provider.rpcTarget || state.provider.rpcTarget === providerUrl) return - providerUrl = state.provider.rpcTarget - syncProvider = new Web3.providers.HttpProvider(providerUrl) -}) - -// 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/config-manager.js b/app/scripts/lib/config-manager.js index 3c9326db9..f5e1cf38d 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -1,11 +1,12 @@ const Migrator = require('pojo-migrator') const extend = require('xtend') +const MetamaskConfig = require('../config.js') +const migrations = require('./migrations') const STORAGE_KEY = 'metamask-config' -const TESTNET_RPC = 'https://morden.infura.io' -const MAINNET_RPC = 'https://mainnet.infura.io/' +const TESTNET_RPC = MetamaskConfig.network.testnet +const MAINNET_RPC = MetamaskConfig.network.mainnet -const migrations = require('./migrations') /* The config-manager is a convenience object * wrapping a pojo-migrator. @@ -229,6 +230,26 @@ ConfigManager.prototype.updateTx = function(tx) { this._saveTxList(transactions) } +// wallet nickname methods + +ConfigManager.prototype.getWalletNicknames = function() { + var data = this.getData() + let nicknames = ('walletNicknames' in data) ? data.walletNicknames : {} + return nicknames +} + +ConfigManager.prototype.nicknameForWallet = function(account) { + let nicknames = this.getWalletNicknames() + return nicknames[account] +} + +ConfigManager.prototype.setNicknameForWallet = function(account, nickname) { + let nicknames = this.getWalletNicknames() + nicknames[account] = nickname + var data = this.getData() + data.walletNicknames = nicknames + this.setData(data) +} // observable 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/idStore.js b/app/scripts/lib/idStore.js index b8d825d8b..9d2552e8b 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -105,14 +105,29 @@ IdentityStore.prototype.getSelectedAddress = function(){ return configManager.getSelectedAccount() } -IdentityStore.prototype.setSelectedAddress = function(address){ +IdentityStore.prototype.setSelectedAddress = function(address, cb){ if (!address) { var addresses = this._getAddresses() address = addresses[0] } configManager.setSelectedAccount(address) + if (cb) return cb(null, address) +} + +IdentityStore.prototype.revealAccount = function(cb) { + let addresses = this._getAddresses() + const derivedKey = this._idmgmt.derivedKey + const keyStore = this._keyStore + + keyStore.setDefaultHdDerivationPath(this.hdPathString) + keyStore.generateNewAddress(derivedKey, 1) + configManager.setWallet(keyStore.serialize()) + + addresses = this._getAddresses() + this._loadIdentities() this._didUpdate() + cb(null) } IdentityStore.prototype.getNetwork = function(tries) { @@ -310,9 +325,10 @@ IdentityStore.prototype._loadIdentities = function(){ // // add to ethStore this._ethStore.addAccount(address) // add to identities + const defaultLabel = 'Wallet ' + (i+1) + const nickname = configManager.nicknameForWallet(address) var identity = { - name: 'Wallet ' + (i+1), - img: 'QmW6hcwYzXrNkuHrpvo58YeZvbZxUddv69ATSHY3BHpPdd', + name: nickname || defaultLabel, address: address, mayBeFauceting: this._mayBeFauceting(i), } @@ -321,6 +337,13 @@ IdentityStore.prototype._loadIdentities = function(){ this._didUpdate() } +IdentityStore.prototype.saveAccountLabel = function(account, label, cb) { + configManager.setNicknameForWallet(account, label) + this._loadIdentities() + cb(null, label) + this._didUpdate() +} + // mayBeFauceting // If on testnet, index 0 may be fauceting. // The UI will have to check the balance to know. diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js new file mode 100644 index 000000000..70b0d80dd --- /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('web3-stream-provider') +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/app/scripts/lib/stream-provider.js b/app/scripts/lib/stream-provider.js deleted file mode 100644 index 505e45d1f..000000000 --- a/app/scripts/lib/stream-provider.js +++ /dev/null @@ -1,72 +0,0 @@ -const Duplex = require('readable-stream').Duplex -const inherits = require('util').inherits - -module.exports = StreamProvider - - -inherits(StreamProvider, Duplex) - -function StreamProvider(){ - Duplex.call(this, { - objectMode: true, - }) - - this._payloads = {} -} - -// public - -StreamProvider.prototype.send = function(payload){ - throw new Error('StreamProvider - does not support synchronous RPC calls. called: "'+payload.method+'"') -} - -StreamProvider.prototype.sendAsync = function(payload, callback){ - // console.log('StreamProvider - sending payload', payload) - var id = payload.id - if (Array.isArray(payload)) { - id = 'batch'+payload[0].id - } - this._payloads[id] = [payload, callback] - // console.log('payload for plugin:', payload) - this.push(payload) -} - -StreamProvider.prototype.isConnected = function(){ - return true -} - -// private - -StreamProvider.prototype._onResponse = function(response){ - // console.log('StreamProvider - got response', payload) - var id = response.id - if (Array.isArray(response)) { - id = 'batch'+response[0].id - } - var data = this._payloads[id] - if (!data) throw new Error('StreamProvider - Unknown response id') - delete this._payloads[id] - var payload = data[0] - var callback = data[1] - - // logging - var res = Array.isArray(response) ? response : [response] - // ;(Array.isArray(payload) ? payload : [payload]).forEach(function(payload, index){ - // console.log('plugin response:', payload.id, payload.method, payload.params, '->', res[index].result) - // }) - - callback(null, response) -} - -// stream plumbing - -StreamProvider.prototype._read = noop - -StreamProvider.prototype._write = function(msg, encoding, cb){ - this._onResponse(msg) - cb() -} - -// util - -function noop(){}
\ No newline at end of file diff --git a/app/scripts/popup.js b/app/scripts/popup.js index e9ca7cd71..4fa6e1127 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -7,7 +7,7 @@ const MetaMaskUi = require('../../ui') const MetaMaskUiCss = require('../../ui/css') const injectCss = require('inject-css') const PortStream = require('./lib/port-stream.js') -const StreamProvider = require('./lib/stream-provider.js') +const StreamProvider = require('web3-stream-provider') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex // setup app |