diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/scripts/keyring-controller.js | 39 | ||||
-rw-r--r-- | app/scripts/lib/config-manager.js | 47 | ||||
-rw-r--r-- | app/scripts/lib/controllers/currency.js | 70 | ||||
-rw-r--r-- | app/scripts/lib/eth-store.js | 214 | ||||
-rw-r--r-- | app/scripts/lib/idStore.js | 3 | ||||
-rw-r--r-- | app/scripts/lib/message-manager.js | 52 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 168 | ||||
-rw-r--r-- | app/scripts/migrations/007.js | 38 | ||||
-rw-r--r-- | app/scripts/migrations/008.js | 36 | ||||
-rw-r--r-- | app/scripts/migrations/009.js | 40 | ||||
-rw-r--r-- | app/scripts/migrations/index.js | 3 | ||||
-rw-r--r-- | app/scripts/notice-controller.js | 50 | ||||
-rw-r--r-- | app/scripts/transaction-manager.js | 65 |
13 files changed, 478 insertions, 347 deletions
diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index d1d706165..348f81fc9 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -29,11 +29,11 @@ class KeyringController extends EventEmitter { this.keyringTypes = keyringTypes this.store = new ObservableStore(initState) this.memStore = new ObservableStore({ + isUnlocked: false, keyringTypes: this.keyringTypes.map(krt => krt.type), keyrings: [], identities: {}, }) - this.configManager = opts.configManager this.ethStore = opts.ethStore this.encryptor = encryptor this.keyrings = [] @@ -53,37 +53,7 @@ class KeyringController extends EventEmitter { // Not all methods end with this, that might be a nice refactor. fullUpdate () { this.emit('update') - return Promise.resolve(this.getState()) - } - - // Get State - // returns @object state - // - // This method returns a hash representing the current state - // that the keyringController manages. - // - // It is extended in the MetamaskController along with the EthStore - // state, and its own state, to create the metamask state branch - // that is passed to the UI. - // - // This is currently a rare example of a synchronously resolving method - // in this class, but will need to be Promisified when we move our - // persistence to an async model. - getState () { - - // old wallet - const memState = this.memStore.getState() - const result = { - // computed - isUnlocked: (!!this.password), - // memStore - keyringTypes: memState.keyringTypes, - identities: memState.identities, - keyrings: memState.keyrings, - // configManager - seedWords: this.configManager.getSeedWords(), - } - return result + return Promise.resolve(this.memStore.getState()) } // Create New Vault And Keychain @@ -147,7 +117,10 @@ class KeyringController extends EventEmitter { // // This method deallocates all secrets, and effectively locks metamask. setLocked () { + // set locked this.password = null + this.memStore.updateState({ isUnlocked: false }) + // remove keyrings this.keyrings = [] this._updateMemStoreKeyrings() return this.fullUpdate() @@ -385,6 +358,7 @@ class KeyringController extends EventEmitter { persistAllKeyrings (password = this.password) { if (typeof password === 'string') { this.password = password + this.memStore.updateState({ isUnlocked: true }) } return Promise.all(this.keyrings.map((keyring) => { return Promise.all([keyring.type, keyring.serialize()]) @@ -421,6 +395,7 @@ class KeyringController extends EventEmitter { return this.encryptor.decrypt(password, encryptedVault) .then((vault) => { this.password = password + this.memStore.updateState({ isUnlocked: true }) vault.forEach(this.restoreKeyring.bind(this)) return this.keyrings }) diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 357e081b1..a9b86ca8c 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -250,53 +250,6 @@ ConfigManager.prototype.getTOSHash = function () { return data.TOSHash } -ConfigManager.prototype.setCurrentFiat = function (currency) { - var data = this.getData() - data.fiatCurrency = currency - this.setData(data) -} - -ConfigManager.prototype.getCurrentFiat = function () { - var data = this.getData() - return data.fiatCurrency || 'USD' -} - -ConfigManager.prototype.updateConversionRate = function () { - var data = this.getData() - return fetch(`https://www.cryptonator.com/api/ticker/eth-${data.fiatCurrency}`) - .then(response => response.json()) - .then((parsedResponse) => { - this.setConversionPrice(parsedResponse.ticker.price) - this.setConversionDate(parsedResponse.timestamp) - }).catch((err) => { - console.warn('MetaMask - Failed to query currency conversion.') - this.setConversionPrice(0) - this.setConversionDate('N/A') - }) -} - -ConfigManager.prototype.setConversionPrice = function (price) { - var data = this.getData() - data.conversionRate = Number(price) - this.setData(data) -} - -ConfigManager.prototype.setConversionDate = function (datestring) { - var data = this.getData() - data.conversionDate = datestring - this.setData(data) -} - -ConfigManager.prototype.getConversionRate = function () { - var data = this.getData() - return (data.conversionRate) || 0 -} - -ConfigManager.prototype.getConversionDate = function () { - var data = this.getData() - return (data.conversionDate) || 'N/A' -} - ConfigManager.prototype.getShapeShiftTxList = function () { var data = this.getData() var shapeShiftTxList = data.shapeShiftTxList ? data.shapeShiftTxList : [] diff --git a/app/scripts/lib/controllers/currency.js b/app/scripts/lib/controllers/currency.js new file mode 100644 index 000000000..c4904f8ac --- /dev/null +++ b/app/scripts/lib/controllers/currency.js @@ -0,0 +1,70 @@ +const ObservableStore = require('obs-store') +const extend = require('xtend') + +// every ten minutes +const POLLING_INTERVAL = 600000 + +class CurrencyController { + + constructor (opts = {}) { + const initState = extend({ + currentCurrency: 'USD', + conversionRate: 0, + conversionDate: 'N/A', + }, opts.initState) + this.store = new ObservableStore(initState) + } + + // + // PUBLIC METHODS + // + + getCurrentCurrency () { + return this.store.getState().currentCurrency + } + + setCurrentCurrency (currentCurrency) { + this.store.updateState({ currentCurrency }) + } + + getConversionRate () { + return this.store.getState().conversionRate + } + + setConversionRate (conversionRate) { + this.store.updateState({ conversionRate }) + } + + getConversionDate () { + return this.store.getState().conversionDate + } + + setConversionDate (conversionDate) { + this.store.updateState({ conversionDate }) + } + + updateConversionRate () { + const currentCurrency = this.getCurrentCurrency() + return fetch(`https://www.cryptonator.com/api/ticker/eth-${currentCurrency}`) + .then(response => response.json()) + .then((parsedResponse) => { + this.setConversionRate(Number(parsedResponse.ticker.price)) + this.setConversionDate(Number(parsedResponse.timestamp)) + }).catch((err) => { + console.warn('MetaMask - Failed to query currency conversion.') + this.setConversionRate(0) + this.setConversionDate('N/A') + }) + } + + scheduleConversionInterval () { + if (this.conversionInterval) { + clearInterval(this.conversionInterval) + } + this.conversionInterval = setInterval(() => { + this.updateConversionRate() + }, POLLING_INTERVAL) + } +} + +module.exports = CurrencyController diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index 7e2caf884..96b4a60f2 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -7,140 +7,122 @@ * on each new block. */ -const EventEmitter = require('events').EventEmitter -const inherits = require('util').inherits const async = require('async') -const clone = require('clone') const EthQuery = require('eth-query') +const ObservableStore = require('obs-store') +function noop() {} -module.exports = EthereumStore +class EthereumStore extends ObservableStore { -inherits(EthereumStore, EventEmitter) -function EthereumStore(engine) { - const self = this - EventEmitter.call(self) - self._currentState = { - accounts: {}, - transactions: {}, + constructor (opts = {}) { + super({ + accounts: {}, + transactions: {}, + }) + this._provider = opts.provider + this._query = new EthQuery(this._provider) + this._blockTracker = opts.blockTracker + // subscribe to latest block + this._blockTracker.on('block', this._updateForBlock.bind(this)) } - self._query = new EthQuery(engine) - - engine.on('block', self._updateForBlock.bind(self)) -} - -// -// public -// -EthereumStore.prototype.getState = function () { - const self = this - return clone(self._currentState) -} - -EthereumStore.prototype.addAccount = function (address) { - const self = this - self._currentState.accounts[address] = {} - self._didUpdate() - if (!self.currentBlockNumber) return - self._updateAccount(address, () => { - self._didUpdate() - }) -} + // + // public + // -EthereumStore.prototype.removeAccount = function (address) { - const self = this - delete self._currentState.accounts[address] - self._didUpdate() -} + addAccount (address) { + const accounts = this.getState().accounts + accounts[address] = {} + this.updateState({ accounts }) + if (!this._currentBlockNumber) return + this._updateAccount(address) + } -EthereumStore.prototype.addTransaction = function (txHash) { - const self = this - self._currentState.transactions[txHash] = {} - self._didUpdate() - if (!self.currentBlockNumber) return - self._updateTransaction(self.currentBlockNumber, txHash, noop) -} + removeAccount (address) { + const accounts = this.getState().accounts + delete accounts[address] + this.updateState({ accounts }) + } -EthereumStore.prototype.removeTransaction = function (address) { - const self = this - delete self._currentState.transactions[address] - self._didUpdate() -} + addTransaction (txHash) { + const transactions = this.getState().transactions + transactions[txHash] = {} + this.updateState({ transactions }) + if (!this._currentBlockNumber) return + this._updateTransaction(this._currentBlockNumber, txHash, noop) + } + removeTransaction (txHash) { + const transactions = this.getState().transactions + delete transactions[txHash] + this.updateState({ transactions }) + } -// -// private -// -EthereumStore.prototype._didUpdate = function () { - const self = this - var state = self.getState() - self.emit('update', state) -} + // + // private + // + + _updateForBlock (block) { + const blockNumber = '0x' + block.number.toString('hex') + this._currentBlockNumber = blockNumber + async.parallel([ + this._updateAccounts.bind(this), + this._updateTransactions.bind(this, blockNumber), + ], (err) => { + if (err) return console.error(err) + this.emit('block', this.getState()) + }) + } -EthereumStore.prototype._updateForBlock = function (block) { - const self = this - var blockNumber = '0x' + block.number.toString('hex') - self.currentBlockNumber = blockNumber - async.parallel([ - self._updateAccounts.bind(self), - self._updateTransactions.bind(self, blockNumber), - ], function (err) { - if (err) return console.error(err) - self.emit('block', self.getState()) - self._didUpdate() - }) -} + _updateAccounts (cb = noop) { + const accounts = this.getState().accounts + const addresses = Object.keys(accounts) + async.each(addresses, this._updateAccount.bind(this), cb) + } -EthereumStore.prototype._updateAccounts = function (cb) { - var accountsState = this._currentState.accounts - var addresses = Object.keys(accountsState) - async.each(addresses, this._updateAccount.bind(this), cb) -} + _updateAccount (address, cb = noop) { + const accounts = this.getState().accounts + this._getAccount(address, (err, result) => { + if (err) return cb(err) + result.address = address + // only populate if the entry is still present + if (accounts[address]) { + accounts[address] = result + } + cb(null, result) + }) + } -EthereumStore.prototype._updateAccount = function (address, cb) { - var accountsState = this._currentState.accounts - this.getAccount(address, function (err, result) { - if (err) return cb(err) - result.address = address - // only populate if the entry is still present - if (accountsState[address]) { - accountsState[address] = result - } - cb(null, result) - }) -} + _updateTransactions (block, cb = noop) { + const transactions = this.getState().transactions + const txHashes = Object.keys(transactions) + async.each(txHashes, this._updateTransaction.bind(this, block), cb) + } -EthereumStore.prototype.getAccount = function (address, cb) { - const query = this._query - async.parallel({ - balance: query.getBalance.bind(query, address), - nonce: query.getTransactionCount.bind(query, address), - code: query.getCode.bind(query, address), - }, cb) -} + _updateTransaction (block, txHash, cb = noop) { + // would use the block here to determine how many confirmations the tx has + const transactions = this.getState().transactions + this._query.getTransaction(txHash, (err, result) => { + if (err) return cb(err) + // only populate if the entry is still present + if (transactions[txHash]) { + transactions[txHash] = result + } + cb(null, result) + }) + } -EthereumStore.prototype._updateTransactions = function (block, cb) { - const self = this - var transactionsState = self._currentState.transactions - var txHashes = Object.keys(transactionsState) - async.each(txHashes, self._updateTransaction.bind(self, block), cb) -} + _getAccount (address, cb = noop) { + const query = this._query + async.parallel({ + balance: query.getBalance.bind(query, address), + nonce: query.getTransactionCount.bind(query, address), + code: query.getCode.bind(query, address), + }, cb) + } -EthereumStore.prototype._updateTransaction = function (block, txHash, cb) { - const self = this - // would use the block here to determine how many confirmations the tx has - var transactionsState = self._currentState.transactions - self._query.getTransaction(txHash, function (err, result) { - if (err) return cb(err) - // only populate if the entry is still present - if (transactionsState[txHash]) { - transactionsState[txHash] = result - self._didUpdate() - } - cb(null, result) - }) } -function noop() {} +module.exports = EthereumStore
\ No newline at end of file diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index e4cbca456..ac395440d 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -97,9 +97,6 @@ IdentityStore.prototype.getState = function () { isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(), selectedAddress: configManager.getSelectedAccount(), shapeShiftTxList: configManager.getShapeShiftTxList(), - currentFiat: configManager.getCurrentFiat(), - conversionRate: configManager.getConversionRate(), - conversionDate: configManager.getConversionDate(), gasMultiplier: configManager.getGasMultiplier(), })) } diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index 18bf54ae1..38fa42017 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -6,18 +6,11 @@ const createId = require('./random-id') module.exports = class MessageManager extends EventEmitter{ constructor (opts) { super() - this.memStore = new ObservableStore({ messages: [] }) - } - - getState() { - return { - unapprovedMsgs: this.getUnapprovedMsgs(), - messages: this.getMsgList(), - } - } - - getMsgList () { - return this.memStore.getState().messages + this.memStore = new ObservableStore({ + unapprovedMsgs: {}, + unapprovedMsgCount: 0, + }) + this.messages = [] } get unapprovedMsgCount () { @@ -25,8 +18,7 @@ module.exports = class MessageManager extends EventEmitter{ } getUnapprovedMsgs () { - let messages = this.getMsgList() - return messages.filter(msg => msg.status === 'unapproved') + return this.messages.filter(msg => msg.status === 'unapproved') .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } @@ -41,10 +33,6 @@ module.exports = class MessageManager extends EventEmitter{ status: 'unapproved', } this.addMsg(msgData) - console.log('addUnapprovedMessage:', msgData) - - // keep the cb around for after approval (requires user interaction) - // This cb fires completion to the Dapp's write operation. // signal update this.emit('update') @@ -52,15 +40,12 @@ module.exports = class MessageManager extends EventEmitter{ } addMsg (msg) { - let messages = this.getMsgList() - messages.push(msg) - this._saveMsgList(messages) + this.messages.push(msg) + this._saveMsgList() } getMsg (msgId) { - let messages = this.getMsgList() - let matching = messages.filter(msg => msg.id === msgId) - return matching.length > 0 ? matching[0] : null + return this.messages.find(msg => msg.id === msgId) } approveMessage (msgParams) { @@ -85,7 +70,10 @@ module.exports = class MessageManager extends EventEmitter{ brodcastMessage (rawSig, msgId, status) { this.emit(`${msgId}:finished`, {status, rawSig}) } -// PRIVATE METHODS + + // + // PRIVATE METHODS + // _setMsgStatus (msgId, status) { let msg = this.getMsg(msgId) @@ -94,18 +82,18 @@ module.exports = class MessageManager extends EventEmitter{ } _updateMsg (msg) { - let messages = this.getMsgList() - let index = messages.findIndex((message) => message.id === msg.id) + let index = this.messages.findIndex((message) => message.id === msg.id) if (index !== -1) { - messages[index] = msg + this.messages[index] = msg } - this._saveMsgList(messages) + this._saveMsgList() } - _saveMsgList (msgList) { + _saveMsgList () { + const unapprovedMsgs = this.getUnapprovedMsgs() + const unapprovedMsgCount = Object.keys(unapprovedMsgs).length + this.memStore.updateState({ unapprovedMsgs, unapprovedMsgCount }) this.emit('updateBadge') - this.memStore.updateState({ messages: msgList }) } - } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a56203a88..c0910014f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -12,6 +12,7 @@ const MetaMaskProvider = require('web3-provider-engine/zero.js') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const KeyringController = require('./keyring-controller') const PreferencesController = require('./lib/controllers/preferences') +const CurrencyController = require('./lib/controllers/currency') const NoticeController = require('./notice-controller') const MessageManager = require('./lib/message-manager') const TxManager = require('./transaction-manager') @@ -29,38 +30,48 @@ module.exports = class MetamaskController extends EventEmitter { constructor (opts) { super() this.opts = opts - this.state = { network: 'loading' } let initState = opts.initState || {} // observable state store this.store = new ObservableStore(initState) + // network store + this.networkStore = new ObservableStore({ network: 'loading' }) + // config manager this.configManager = new ConfigManager({ store: this.store, }) - this.configManager.updateConversionRate() // preferences controller this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, }) + // currency controller + this.currencyController = new CurrencyController({ + initState: initState.CurrencyController, + }) + this.currencyController.updateConversionRate() + this.currencyController.scheduleConversionInterval() + // rpc provider this.provider = this.initializeProvider(opts) this.provider.on('block', this.logBlock.bind(this)) - this.provider.on('error', this.getNetwork.bind(this)) + this.provider.on('error', this.verifyNetwork.bind(this)) // eth data query tools this.ethQuery = new EthQuery(this.provider) - this.ethStore = new EthStore(this.provider) + this.ethStore = new EthStore({ + provider: this.provider, + blockTracker: this.provider, + }) // key mgmt this.keyringController = new KeyringController({ initState: initState.KeyringController, ethStore: this.ethStore, - configManager: this.configManager, - getNetwork: this.getStateNetwork.bind(this), + getNetwork: this.getNetworkState.bind(this), }) this.keyringController.on('newAccount', (address) => { this.preferencesController.setSelectedAddress(address) @@ -69,12 +80,11 @@ module.exports = class MetamaskController extends EventEmitter { // tx mgmt this.txManager = new TxManager({ - txList: this.configManager.getTxList(), + initState: initState.TransactionManager, + networkStore: this.networkStore, + preferencesStore: this.preferencesController.store, txHistoryLimit: 40, - setTxList: this.configManager.setTxList.bind(this.configManager), - getSelectedAddress: this.preferencesController.getSelectedAddress.bind(this.preferencesController), - getGasMultiplier: this.configManager.getGasMultiplier.bind(this.configManager), - getNetwork: this.getStateNetwork.bind(this), + getNetwork: this.getNetworkState.bind(this), signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, blockTracker: this.provider, @@ -82,36 +92,49 @@ module.exports = class MetamaskController extends EventEmitter { // notices this.noticeController = new NoticeController({ - configManager: this.configManager, + initState: initState.NoticeController, }) this.noticeController.updateNoticesList() // to be uncommented when retrieving notices from a remote server. // this.noticeController.startPolling() - this.getNetwork() + this.lookupNetwork() this.messageManager = new MessageManager() this.publicConfigStore = this.initPublicConfigStore() this.checkTOSChange() - this.scheduleConversionInterval() - // TEMPORARY UNTIL FULL DEPRECATION: this.idStoreMigrator = new IdStoreMigrator({ configManager: this.configManager, }) - // manual state subscriptions - this.ethStore.on('update', this.sendUpdate.bind(this)) - this.keyringController.on('update', this.sendUpdate.bind(this)) - this.txManager.on('update', this.sendUpdate.bind(this)) - this.messageManager.on('update', this.sendUpdate.bind(this)) + // manual disk state subscriptions + this.txManager.store.subscribe((state) => { + this.store.updateState({ TransactionManager: state }) + }) this.keyringController.store.subscribe((state) => { this.store.updateState({ KeyringController: state }) }) this.preferencesController.store.subscribe((state) => { this.store.updateState({ PreferencesController: state }) }) + this.currencyController.store.subscribe((state) => { + this.store.updateState({ CurrencyController: state }) + }) + this.noticeController.store.subscribe((state) => { + this.store.updateState({ NoticeController: state }) + }) + + // manual mem state subscriptions + this.networkStore.subscribe(this.sendUpdate.bind(this)) + this.ethStore.subscribe(this.sendUpdate.bind(this)) + this.txManager.memStore.subscribe(this.sendUpdate.bind(this)) + this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) + this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) + this.preferencesController.store.subscribe(this.sendUpdate.bind(this)) + this.currencyController.store.subscribe(this.sendUpdate.bind(this)) + this.noticeController.memStore.subscribe(this.sendUpdate.bind(this)) } // @@ -174,22 +197,21 @@ module.exports = class MetamaskController extends EventEmitter { { isInitialized, }, - this.state, + this.networkStore.getState(), this.ethStore.getState(), - this.txManager.getState(), - this.messageManager.getState(), - this.keyringController.getState(), + this.txManager.memStore.getState(), + this.messageManager.memStore.getState(), + this.keyringController.memStore.getState(), this.preferencesController.store.getState(), - this.noticeController.getState(), + this.currencyController.store.getState(), + this.noticeController.memStore.getState(), // config manager this.configManager.getConfig(), { shapeShiftTxList: this.configManager.getShapeShiftTxList(), lostAccounts: this.configManager.getLostAccounts(), - currentFiat: this.configManager.getCurrentFiat(), - conversionRate: this.configManager.getConversionRate(), - conversionDate: this.configManager.getConversionDate(), isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), + seedWords: this.configManager.getSeedWords(), } ) } @@ -213,7 +235,7 @@ module.exports = class MetamaskController extends EventEmitter { useEtherscanProvider: this.useEtherscanProvider.bind(this), agreeToDisclaimer: this.agreeToDisclaimer.bind(this), resetDisclaimer: this.resetDisclaimer.bind(this), - setCurrentFiat: this.setCurrentFiat.bind(this), + setCurrentCurrency: this.setCurrentCurrency.bind(this), setTOSHash: this.setTOSHash.bind(this), checkTOSChange: this.checkTOSChange.bind(this), setGasMultiplier: this.setGasMultiplier.bind(this), @@ -243,11 +265,13 @@ module.exports = class MetamaskController extends EventEmitter { saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController), exportAccount: nodeify(keyringController.exportAccount).bind(keyringController), - // signing methods + // txManager approveTransaction: txManager.approveTransaction.bind(txManager), cancelTransaction: txManager.cancelTransaction.bind(txManager), - signMessage: this.signMessage.bind(this), - cancelMessage: messageManager.rejectMsg.bind(messageManager), + + // messageManager + signMessage: this.signMessage.bind(this), + cancelMessage: messageManager.rejectMsg.bind(messageManager), // notices checkNotices: noticeController.updateNoticesList.bind(noticeController), @@ -339,7 +363,7 @@ module.exports = class MetamaskController extends EventEmitter { .then((serialized) => { const seedWords = serialized.mnemonic this.configManager.setSeedWords(seedWords) - promiseToCallback(this.keyringController.fullUpdate())(cb) + cb() }) } @@ -538,44 +562,38 @@ module.exports = class MetamaskController extends EventEmitter { this.verifyNetwork() } - setCurrentFiat (fiat, cb) { + setCurrentCurrency (currencyCode, cb) { try { - this.configManager.setCurrentFiat(fiat) - this.configManager.updateConversionRate() - this.scheduleConversionInterval() + this.currencyController.setCurrentCurrency(currencyCode) + this.currencyController.updateConversionRate() const data = { - conversionRate: this.configManager.getConversionRate(), - currentFiat: this.configManager.getCurrentFiat(), - conversionDate: this.configManager.getConversionDate(), + conversionRate: this.currencyController.getConversionRate(), + currentFiat: this.currencyController.getCurrentCurrency(), + conversionDate: this.currencyController.getConversionDate(), } - cb(data) + cb(null, data) } catch (err) { - cb(null, err) - } - } - - scheduleConversionInterval () { - if (this.conversionInterval) { - clearInterval(this.conversionInterval) + cb(err) } - this.conversionInterval = setInterval(() => { - this.configManager.updateConversionRate() - }, 300000) } buyEth (address, amount) { if (!amount) amount = '5' - var network = this.state.network - var url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH` + const network = this.getNetworkState() + let url + + switch (network) { + case '1': + url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH` + break - if (network === '3') { - url = 'https://faucet.metamask.io/' + case '3': + url = 'https://faucet.metamask.io/' + break } - extension.tabs.create({ - url, - }) + if (url) extension.tabs.create({ url }) } createShapeShiftTx (depositAddress, depositType) { @@ -584,7 +602,7 @@ module.exports = class MetamaskController extends EventEmitter { setGasMultiplier (gasMultiplier, cb) { try { - this.configManager.setGasMultiplier(gasMultiplier) + this.txManager.setGasMultiplier(gasMultiplier) cb() } catch (err) { cb(err) @@ -597,21 +615,19 @@ module.exports = class MetamaskController extends EventEmitter { verifyNetwork () { // Check network when restoring connectivity: - if (this.state.network === 'loading') { - this.getNetwork() - } + if (this.isNetworkLoading()) this.lookupNetwork() } setRpcTarget (rpcTarget) { this.configManager.setRpcTarget(rpcTarget) extension.runtime.reload() - this.getNetwork() + this.lookupNetwork() } setProviderType (type) { this.configManager.setProviderType(type) extension.runtime.reload() - this.getNetwork() + this.lookupNetwork() } useEtherscanProvider () { @@ -619,26 +635,32 @@ module.exports = class MetamaskController extends EventEmitter { extension.runtime.reload() } - getStateNetwork () { - return this.state.network + getNetworkState () { + return this.networkStore.getState().network + } + + setNetworkState (network) { + return this.networkStore.updateState({ network }) + } + + isNetworkLoading () { + return this.getNetworkState() === 'loading' } - getNetwork (err) { + lookupNetwork (err) { if (err) { - this.state.network = 'loading' - this.sendUpdate() + this.setNetworkState('loading') } this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { if (err) { - this.state.network = 'loading' - return this.sendUpdate() + this.setNetworkState('loading') + return } if (global.METAMASK_DEBUG) { console.log('web3.getNetwork returned ' + network) } - this.state.network = network - this.sendUpdate() + this.setNetworkState(network) }) } diff --git a/app/scripts/migrations/007.js b/app/scripts/migrations/007.js new file mode 100644 index 000000000..3ae8cdc2d --- /dev/null +++ b/app/scripts/migrations/007.js @@ -0,0 +1,38 @@ +const version = 7 + +/* + +This migration breaks out the TransactionManager substate + +*/ + +const extend = require('xtend') + +module.exports = { + version, + + migrate: function (versionedData) { + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = extend(state, { + TransactionManager: { + transactions: state.transactions || [], + gasMultiplier: state.gasMultiplier || 1, + }, + }) + delete newState.transactions + delete newState.gasMultiplier + + return newState +} diff --git a/app/scripts/migrations/008.js b/app/scripts/migrations/008.js new file mode 100644 index 000000000..7f6e72ee6 --- /dev/null +++ b/app/scripts/migrations/008.js @@ -0,0 +1,36 @@ +const version = 8 + +/* + +This migration breaks out the NoticeController substate + +*/ + +const extend = require('xtend') + +module.exports = { + version, + + migrate: function (versionedData) { + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = extend(state, { + NoticeController: { + noticesList: state.noticesList || [], + }, + }) + delete newState.noticesList + + return newState +} diff --git a/app/scripts/migrations/009.js b/app/scripts/migrations/009.js new file mode 100644 index 000000000..61b8b7fa2 --- /dev/null +++ b/app/scripts/migrations/009.js @@ -0,0 +1,40 @@ +const version = 9 + +/* + +This migration breaks out the CurrencyController substate + +*/ + +const merge = require('deep-merge') + +module.exports = { + version, + + migrate: function (versionedData) { + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = merge(state, { + CurrencyController: { + currentCurrency: state.currentFiat || 'USD', + conversionRate: state.conversionRate, + conversionDate: state.conversionDate, + }, + }) + delete newState.currentFiat + delete newState.conversionRate + delete newState.conversionDate + + return newState +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 17c191448..22bf008ba 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -17,4 +17,7 @@ module.exports = [ require('./004'), require('./005'), require('./006'), + require('./007'), + require('./008'), + require('./009'), ] diff --git a/app/scripts/notice-controller.js b/app/scripts/notice-controller.js index c3777b4b1..ba7c68df4 100644 --- a/app/scripts/notice-controller.js +++ b/app/scripts/notice-controller.js @@ -1,36 +1,37 @@ const EventEmitter = require('events').EventEmitter +const extend = require('xtend') +const ObservableStore = require('obs-store') const hardCodedNotices = require('../../notices/notices.json') module.exports = class NoticeController extends EventEmitter { constructor (opts) { super() - this.configManager = opts.configManager this.noticePoller = null + const initState = extend({ + noticesList: [], + }, opts.initState) + this.store = new ObservableStore(initState) + this.memStore = new ObservableStore({}) + this.store.subscribe(() => this._updateMemstore()) } - getState () { - var lastUnreadNotice = this.getLatestUnreadNotice() + getNoticesList () { + return this.store.getState().noticesList + } - return { - lastUnreadNotice: lastUnreadNotice, - noActiveNotices: !lastUnreadNotice, - } + getUnreadNotices () { + const notices = this.getNoticesList() + return notices.filter((notice) => notice.read === false) } - getNoticesList () { - var data = this.configManager.getData() - if ('noticesList' in data) { - return data.noticesList - } else { - return [] - } + getLatestUnreadNotice () { + const unreadNotices = this.getUnreadNotices() + return unreadNotices[unreadNotices.length - 1] } - setNoticesList (list) { - var data = this.configManager.getData() - data.noticesList = list - this.configManager.setData(data) + setNoticesList (noticesList) { + this.store.updateState({ noticesList }) return Promise.resolve(true) } @@ -56,14 +57,6 @@ module.exports = class NoticeController extends EventEmitter { }) } - getLatestUnreadNotice () { - var notices = this.getNoticesList() - var filteredNotices = notices.filter((notice) => { - return notice.read === false - }) - return filteredNotices[filteredNotices.length - 1] - } - startPolling () { if (this.noticePoller) { clearInterval(this.noticePoller) @@ -92,5 +85,10 @@ module.exports = class NoticeController extends EventEmitter { return Promise.resolve(hardCodedNotices) } + _updateMemstore () { + const lastUnreadNotice = this.getLatestUnreadNotice() + const noActiveNotices = !lastUnreadNotice + this.memStore.updateState({ lastUnreadNotice, noActiveNotices }) + } } diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 6fecdba39..6299091f2 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -2,6 +2,7 @@ const EventEmitter = require('events') const async = require('async') const extend = require('xtend') const Semaphore = require('semaphore') +const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const BN = require('ethereumjs-util').BN const TxProviderUtil = require('./lib/tx-utils') @@ -10,33 +11,53 @@ const createId = require('./lib/random-id') module.exports = class TransactionManager extends EventEmitter { constructor (opts) { super() - this.txList = opts.txList || [] - this._setTxList = opts.setTxList + this.store = new ObservableStore(extend({ + transactions: [], + gasMultiplier: 1, + }, opts.initState)) + this.memStore = new ObservableStore({}) + this.networkStore = opts.networkStore || new ObservableStore({}) + this.preferencesStore = opts.preferencesStore || new ObservableStore({}) this.txHistoryLimit = opts.txHistoryLimit - this.getSelectedAddress = opts.getSelectedAddress this.provider = opts.provider this.blockTracker = opts.blockTracker this.txProviderUtils = new TxProviderUtil(this.provider) this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) - this.getGasMultiplier = opts.getGasMultiplier - this.getNetwork = opts.getNetwork this.signEthTx = opts.signTransaction this.nonceLock = Semaphore(1) + + // memstore is computed from a few different stores + this._updateMemstore() + this.store.subscribe(() => this._updateMemstore() ) + this.networkStore.subscribe(() => this._updateMemstore() ) + this.preferencesStore.subscribe(() => this._updateMemstore() ) } getState () { - var selectedAddress = this.getSelectedAddress() - return { - transactions: this.getTxList(), - unapprovedTxs: this.getUnapprovedTxList(), - selectedAddressTxList: this.getFilteredTxList({metamaskNetworkId: this.getNetwork(), from: selectedAddress}), - } + return this.memStore.getState() + } + + getNetwork () { + return this.networkStore.getState().network } -// Returns the tx list + getSelectedAddress () { + return this.preferencesStore.getState().selectedAddress + } + + // Returns the tx list getTxList () { let network = this.getNetwork() - return this.txList.filter(txMeta => txMeta.metamaskNetworkId === network) + let fullTxList = this.store.getState().transactions + return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network) + } + + getGasMultiplier () { + return this.store.getState().gasMultiplier + } + + setGasMultiplier (gasMultiplier) { + return this.store.updateState({ gasMultiplier }) } // Adds a tx to the txlist @@ -108,7 +129,7 @@ module.exports = class TransactionManager extends EventEmitter { id: txId, time: time, status: 'unapproved', - gasMultiplier: this.getGasMultiplier() || 1, + gasMultiplier: this.getGasMultiplier(), metamaskNetworkId: this.getNetwork(), txParams: txParams, } @@ -239,7 +260,7 @@ module.exports = class TransactionManager extends EventEmitter { getTxsByMetaData (key, value, txList = this.getTxList()) { return txList.filter((txMeta) => { - if (key in txMeta.txParams) { + if (txMeta.txParams[key]) { return txMeta.txParams[key] === value } else { return txMeta[key] === value @@ -351,9 +372,17 @@ module.exports = class TransactionManager extends EventEmitter { // Saves the new/updated txList. // Function is intended only for internal use - _saveTxList (txList) { - this.txList = txList - this._setTxList(txList) + _saveTxList (transactions) { + this.store.updateState({ transactions }) + } + + _updateMemstore () { + const unapprovedTxs = this.getUnapprovedTxList() + const selectedAddressTxList = this.getFilteredTxList({ + from: this.getSelectedAddress(), + metamaskNetworkId: this.getNetwork(), + }) + this.memStore.updateState({ unapprovedTxs, selectedAddressTxList }) } } |