diff options
Diffstat (limited to 'app/scripts/lib')
-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 |
6 files changed, 234 insertions, 78 deletions
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 |