From 296b2785926ffce7f65b86b12d7733111a43d30e Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 16:46:56 -0800 Subject: actions - dont expect background to provide newState, manually update --- ui/app/actions.js | 39 +++++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/ui/app/actions.js b/ui/app/actions.js index fa1e0deb5..a27ab2c14 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -158,6 +158,7 @@ var actions = { showNewKeychain: showNewKeychain, callBackgroundThenUpdate, + forceUpdateMetamaskState, } module.exports = actions @@ -179,13 +180,13 @@ function tryUnlockMetamask (password) { return (dispatch) => { dispatch(actions.showLoadingIndication()) dispatch(actions.unlockInProgress()) - background.submitPassword(password, (err, newState) => { + background.submitPassword(password, (err) => { dispatch(actions.hideLoadingIndication()) if (err) { dispatch(actions.unlockFailed(err.message)) } else { dispatch(actions.transitionForward()) - dispatch(actions.updateMetamaskState(newState)) + forceUpdateMetamaskState(dispatch) } }) } @@ -232,16 +233,16 @@ function createNewVaultAndRestore (password, seed) { function createNewVaultAndKeychain (password) { return (dispatch) => { dispatch(actions.showLoadingIndication()) - background.createNewVaultAndKeychain(password, (err, newState) => { + background.createNewVaultAndKeychain(password, (err) => { if (err) { return dispatch(actions.displayWarning(err.message)) } - background.placeSeedWords((err, newState) => { + background.placeSeedWords((err) => { if (err) { return dispatch(actions.displayWarning(err.message)) } dispatch(actions.hideLoadingIndication()) - dispatch(actions.updateMetamaskState(newState)) + forceUpdateMetamaskState(dispatch) }) }) } @@ -280,13 +281,18 @@ function addNewKeyring (type, opts) { function importNewAccount (strategy, args) { return (dispatch) => { dispatch(actions.showLoadingIndication('This may take a while, be patient.')) - background.importAccountWithStrategy(strategy, args, (err, newState) => { + background.importAccountWithStrategy(strategy, args, (err) => { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) - dispatch(actions.updateMetamaskState(newState)) - dispatch({ - type: actions.SHOW_ACCOUNT_DETAIL, - value: newState.selectedAddress, + background.getState((err, newState) => { + if (err) { + return dispatch(actions.displayWarning(err.message)) + } + dispatch(actions.updateMetamaskState(newState)) + dispatch({ + type: actions.SHOW_ACCOUNT_DETAIL, + value: newState.selectedAddress, + }) }) }) } @@ -876,12 +882,21 @@ function shapeShiftRequest (query, options, cb) { function callBackgroundThenUpdate (method, ...args) { return (dispatch) => { dispatch(actions.showLoadingIndication()) - method.call(background, ...args, (err, newState) => { + method.call(background, ...args, (err) => { dispatch(actions.hideLoadingIndication()) if (err) { return dispatch(actions.displayWarning(err.message)) } - dispatch(actions.updateMetamaskState(newState)) + forceUpdateMetamaskState(dispatch) }) } } + +function forceUpdateMetamaskState(dispatch){ + background.getState((err, newState) => { + if (err) { + return dispatch(actions.displayWarning(err.message)) + } + dispatch(actions.updateMetamaskState(newState)) + }) +} \ No newline at end of file -- cgit v1.2.3 From 5c5aa6ea012b6a5d0cb8e8386ac0429f023c9e1a Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 16:54:16 -0800 Subject: keyring - rm config dep --- app/scripts/keyring-controller.js | 3 --- app/scripts/metamask-controller.js | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index d1d706165..023dcbcab 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -33,7 +33,6 @@ class KeyringController extends EventEmitter { keyrings: [], identities: {}, }) - this.configManager = opts.configManager this.ethStore = opts.ethStore this.encryptor = encryptor this.keyrings = [] @@ -80,8 +79,6 @@ class KeyringController extends EventEmitter { keyringTypes: memState.keyringTypes, identities: memState.identities, keyrings: memState.keyrings, - // configManager - seedWords: this.configManager.getSeedWords(), } return result } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a56203a88..695491c2a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -59,7 +59,6 @@ module.exports = class MetamaskController extends EventEmitter { this.keyringController = new KeyringController({ initState: initState.KeyringController, ethStore: this.ethStore, - configManager: this.configManager, getNetwork: this.getStateNetwork.bind(this), }) this.keyringController.on('newAccount', (address) => { @@ -190,6 +189,7 @@ module.exports = class MetamaskController extends EventEmitter { conversionRate: this.configManager.getConversionRate(), conversionDate: this.configManager.getConversionDate(), isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), + seedWords: this.configManager.getSeedWords(), } ) } @@ -339,7 +339,7 @@ module.exports = class MetamaskController extends EventEmitter { .then((serialized) => { const seedWords = serialized.mnemonic this.configManager.setSeedWords(seedWords) - promiseToCallback(this.keyringController.fullUpdate())(cb) + cb() }) } -- cgit v1.2.3 From 134a4c7bc3dcb8d750de4867f7dade413f438981 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 16:59:15 -0800 Subject: keyring - manage isUnlocked in memStore --- app/scripts/keyring-controller.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 023dcbcab..59b8486ff 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -29,6 +29,7 @@ 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: {}, @@ -74,7 +75,7 @@ class KeyringController extends EventEmitter { const memState = this.memStore.getState() const result = { // computed - isUnlocked: (!!this.password), + isUnlocked: memState.isUnlocked, // memStore keyringTypes: memState.keyringTypes, identities: memState.identities, @@ -144,7 +145,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() @@ -382,6 +386,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()]) @@ -418,6 +423,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 }) -- cgit v1.2.3 From 79ed2e902fa3e1d9aadd9e3ecd88f627de5bb100 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 17:07:52 -0800 Subject: keyring - replace getState with memStore --- app/scripts/keyring-controller.js | 30 +----------------------------- app/scripts/metamask-controller.js | 2 +- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 59b8486ff..348f81fc9 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -53,35 +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: memState.isUnlocked, - // memStore - keyringTypes: memState.keyringTypes, - identities: memState.identities, - keyrings: memState.keyrings, - } - return result + return Promise.resolve(this.memStore.getState()) } // Create New Vault And Keychain diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 695491c2a..02dd60528 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -177,7 +177,7 @@ module.exports = class MetamaskController extends EventEmitter { this.ethStore.getState(), this.txManager.getState(), this.messageManager.getState(), - this.keyringController.getState(), + this.keyringController.memStore.getState(), this.preferencesController.store.getState(), this.noticeController.getState(), // config manager -- cgit v1.2.3 From bcb86f38cbe91fb814d30b3129de76dba45bff66 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 18:21:22 -0800 Subject: messageManager - move view state to obs-store --- app/scripts/lib/message-manager.js | 52 +++++++++++++++----------------------- app/scripts/metamask-controller.js | 14 +++++----- test/unit/message-manager-test.js | 17 +++---------- 3 files changed, 32 insertions(+), 51 deletions(-) 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 02dd60528..b6a096488 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -100,17 +100,19 @@ module.exports = class MetamaskController extends EventEmitter { 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.keyringController.store.subscribe((state) => { this.store.updateState({ KeyringController: state }) }) this.preferencesController.store.subscribe((state) => { this.store.updateState({ PreferencesController: state }) }) + + // manual mem state subscriptions + this.ethStore.on('update', this.sendUpdate.bind(this)) + this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) + this.txManager.on('update', this.sendUpdate.bind(this)) + this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) } // @@ -176,7 +178,7 @@ module.exports = class MetamaskController extends EventEmitter { this.state, this.ethStore.getState(), this.txManager.getState(), - this.messageManager.getState(), + this.messageManager.memStore.getState(), this.keyringController.memStore.getState(), this.preferencesController.store.getState(), this.noticeController.getState(), diff --git a/test/unit/message-manager-test.js b/test/unit/message-manager-test.js index 68b977058..faf7429d4 100644 --- a/test/unit/message-manager-test.js +++ b/test/unit/message-manager-test.js @@ -13,7 +13,7 @@ describe('Transaction Manager', function() { describe('#getMsgList', function() { it('when new should return empty array', function() { - var result = messageManager.getMsgList() + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 0) }) @@ -22,20 +22,11 @@ describe('Transaction Manager', function() { }) }) - describe('#_saveMsgList', function() { - it('saves the submitted data to the Msg list', function() { - var target = [{ foo: 'bar', metamaskNetworkId: 'unit test' }] - messageManager._saveMsgList(target) - var result = messageManager.getMsgList() - assert.equal(result[0].foo, 'bar') - }) - }) - describe('#addMsg', function() { it('adds a Msg returned in getMsgList', function() { var Msg = { id: 1, status: 'approved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) - var result = messageManager.getMsgList() + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].id, 1) @@ -47,7 +38,7 @@ describe('Transaction Manager', function() { var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.setMsgStatusApproved(1) - var result = messageManager.getMsgList() + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'approved') @@ -59,7 +50,7 @@ describe('Transaction Manager', function() { var Msg = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } messageManager.addMsg(Msg) messageManager.rejectMsg(1) - var result = messageManager.getMsgList() + var result = messageManager.messages assert.ok(Array.isArray(result)) assert.equal(result.length, 1) assert.equal(result[0].status, 'rejected') -- cgit v1.2.3 From 99fa9ab13aaf69cb8602612816a4df49209fa8a6 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 20:20:13 -0800 Subject: migration 7 - break off TransactionManager substate --- app/scripts/metamask-controller.js | 21 ++++--- app/scripts/migrations/007.js | 38 +++++++++++++ app/scripts/migrations/index.js | 1 + app/scripts/transaction-manager.js | 54 ++++++++++++------ test/integration/index.html | 2 +- test/unit/tx-manager-test.js | 112 +++++++++++++++++++------------------ ui/app/actions.js | 30 ++++++++++ 7 files changed, 179 insertions(+), 79 deletions(-) create mode 100644 app/scripts/migrations/007.js diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b6a096488..468e7d285 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -68,11 +68,9 @@ module.exports = class MetamaskController extends EventEmitter { // tx mgmt this.txManager = new TxManager({ - txList: this.configManager.getTxList(), + initState: initState.TxManager, 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), signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, @@ -107,11 +105,14 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController.store.subscribe((state) => { this.store.updateState({ PreferencesController: state }) }) + this.txManager.store.subscribe((state) => { + this.store.updateState({ TransactionManager: state }) + }) // manual mem state subscriptions this.ethStore.on('update', this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) - this.txManager.on('update', this.sendUpdate.bind(this)) + this.txManager.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) } @@ -177,7 +178,7 @@ module.exports = class MetamaskController extends EventEmitter { }, this.state, this.ethStore.getState(), - this.txManager.getState(), + this.txManager.memStore.getState(), this.messageManager.memStore.getState(), this.keyringController.memStore.getState(), this.preferencesController.store.getState(), @@ -245,11 +246,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), @@ -586,7 +589,7 @@ module.exports = class MetamaskController extends EventEmitter { setGasMultiplier (gasMultiplier, cb) { try { - this.configManager.setGasMultiplier(gasMultiplier) + this.txManager.setGasMultiplier(gasMultiplier) cb() } catch (err) { cb(err) 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/index.js b/app/scripts/migrations/index.js index 17c191448..d608f5314 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -17,4 +17,5 @@ module.exports = [ require('./004'), require('./005'), require('./006'), + require('./007'), ] diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 6fecdba39..7949fb854 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,46 @@ 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({ + txList: [], + gasMultiplier: 1, + }, opts.initState)) + this.memStore = new ObservableStore({}) + // this.networkStore = opts.networkStore || new ObservableStore({}) + this.getNetwork = opts.getNetwork + 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 diskStore + this._updateMemstore() + this.store.subscribe(() => this._updateMemstore() ) + // this.networkStore.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() } -// Returns the tx list + // Returns the tx list getTxList () { let network = this.getNetwork() - return this.txList.filter(txMeta => txMeta.metamaskNetworkId === network) + let fullTxList = this.store.getState().txList + 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 +122,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 +253,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 @@ -352,8 +366,16 @@ 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) + this.store.updateState({ txList }) + } + + _updateMemstore () { + const unapprovedTxs = this.getUnapprovedTxList() + const selectedAddressTxList = this.getFilteredTxList({ + from: this.getSelectedAddress(), + metamaskNetworkId: this.getNetwork(), + }) + this.memStore.updateState({ unapprovedTxs, selectedAddressTxList }) } } diff --git a/test/integration/index.html b/test/integration/index.html index 8a54cb829..430814a8a 100644 --- a/test/integration/index.html +++ b/test/integration/index.html @@ -15,7 +15,7 @@ - diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js index a66003f85..f03294ce3 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-manager-test.js @@ -3,19 +3,18 @@ const extend = require('xtend') const EventEmitter = require('events') const STORAGE_KEY = 'metamask-persistance-key' const TransactionManager = require('../../app/scripts/transaction-manager') +const noop = () => true describe('Transaction Manager', function() { let txManager - const onTxDoneCb = () => true beforeEach(function() { txManager = new TransactionManager ({ - txList: [], - setTxList: () => {}, provider: "testnet", txHistoryLimit: 10, blockTracker: new EventEmitter(), - getNetwork: function(){ return 'unit test' } + getNetwork: function(){ return 'unit test' }, + getSelectedAddress: function(){ return '0xabcd' }, }) }) @@ -53,7 +52,7 @@ describe('Transaction Manager', function() { describe('#_saveTxList', function() { it('saves the submitted data to the tx list', function() { - var target = [{ foo: 'bar', metamaskNetworkId: 'unit test' }] + var target = [{ foo: 'bar', metamaskNetworkId: 'unit test', txParams: {} }] txManager._saveTxList(target) var result = txManager.getTxList() assert.equal(result[0].foo, 'bar') @@ -62,8 +61,8 @@ describe('Transaction Manager', function() { describe('#addTx', function() { it('adds a tx returned in getTxList', function() { - var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test' } - txManager.addTx(tx, onTxDoneCb) + var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} } + txManager.addTx(tx, noop) var result = txManager.getTxList() assert.ok(Array.isArray(result)) assert.equal(result.length, 1) @@ -73,8 +72,8 @@ describe('Transaction Manager', function() { it('cuts off early txs beyond a limit', function() { const limit = txManager.txHistoryLimit for (let i = 0; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test' } - txManager.addTx(tx, onTxDoneCb) + let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} } + txManager.addTx(tx, noop) } var result = txManager.getTxList() assert.equal(result.length, limit, `limit of ${limit} txs enforced`) @@ -84,8 +83,8 @@ describe('Transaction Manager', function() { it('cuts off early txs beyond a limit whether or not it is confirmed or rejected', function() { const limit = txManager.txHistoryLimit for (let i = 0; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: 'unit test' } - txManager.addTx(tx, onTxDoneCb) + let tx = { id: i, time: new Date(), status: 'rejected', metamaskNetworkId: 'unit test', txParams: {} } + txManager.addTx(tx, noop) } var result = txManager.getTxList() assert.equal(result.length, limit, `limit of ${limit} txs enforced`) @@ -93,12 +92,12 @@ describe('Transaction Manager', function() { }) it('cuts off early txs beyond a limit but does not cut unapproved txs', function() { - var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: 'unit test' } - txManager.addTx(unconfirmedTx, onTxDoneCb) + var unconfirmedTx = { id: 0, time: new Date(), status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } + txManager.addTx(unconfirmedTx, noop) const limit = txManager.txHistoryLimit for (let i = 1; i < limit + 1; i++) { - let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test' } - txManager.addTx(tx, onTxDoneCb) + let tx = { id: i, time: new Date(), status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} } + txManager.addTx(tx, noop) } var result = txManager.getTxList() assert.equal(result.length, limit, `limit of ${limit} txs enforced`) @@ -110,8 +109,8 @@ describe('Transaction Manager', function() { describe('#setTxStatusSigned', function() { it('sets the tx status to signed', function() { - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } - txManager.addTx(tx, onTxDoneCb) + var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } + txManager.addTx(tx, noop) txManager.setTxStatusSigned(1) var result = txManager.getTxList() assert.ok(Array.isArray(result)) @@ -121,20 +120,20 @@ describe('Transaction Manager', function() { it('should emit a signed event to signal the exciton of callback', (done) => { this.timeout(10000) - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } - let onTxDoneCb = function () { - assert(true, 'event listener has been triggered and onTxDoneCb executed') + var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } + let noop = function () { + assert(true, 'event listener has been triggered and noop executed') done() } txManager.addTx(tx) - txManager.on('1:signed', onTxDoneCb) + txManager.on('1:signed', noop) txManager.setTxStatusSigned(1) }) }) describe('#setTxStatusRejected', function() { it('sets the tx status to rejected', function() { - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } txManager.addTx(tx) txManager.setTxStatusRejected(1) var result = txManager.getTxList() @@ -145,13 +144,13 @@ describe('Transaction Manager', function() { it('should emit a rejected event to signal the exciton of callback', (done) => { this.timeout(10000) - var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test' } + var tx = { id: 1, status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} } txManager.addTx(tx) - let onTxDoneCb = function (err, txId) { - assert(true, 'event listener has been triggered and onTxDoneCb executed') + let noop = function (err, txId) { + assert(true, 'event listener has been triggered and noop executed') done() } - txManager.on('1:rejected', onTxDoneCb) + txManager.on('1:rejected', noop) txManager.setTxStatusRejected(1) }) @@ -159,9 +158,9 @@ describe('Transaction Manager', function() { describe('#updateTx', function() { it('replaces the tx with the same id', function() { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb) - txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test' }) + txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }, noop) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop) + txManager.updateTx({ id: '1', status: 'blah', hash: 'foo', metamaskNetworkId: 'unit test', txParams: {} }) var result = txManager.getTx('1') assert.equal(result.hash, 'foo') }) @@ -169,8 +168,8 @@ describe('Transaction Manager', function() { describe('#getUnapprovedTxList', function() { it('returns unapproved txs in a hash', function() { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb) + txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }, noop) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop) let result = txManager.getUnapprovedTxList() assert.equal(typeof result, 'object') assert.equal(result['1'].status, 'unapproved') @@ -180,8 +179,8 @@ describe('Transaction Manager', function() { describe('#getTx', function() { it('returns a tx with the requested id', function() { - txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test' }, onTxDoneCb) - txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test' }, onTxDoneCb) + txManager.addTx({ id: '1', status: 'unapproved', metamaskNetworkId: 'unit test', txParams: {} }, noop) + txManager.addTx({ id: '2', status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} }, noop) assert.equal(txManager.getTx('1').status, 'unapproved') assert.equal(txManager.getTx('2').status, 'confirmed') }) @@ -189,26 +188,33 @@ describe('Transaction Manager', function() { describe('#getFilteredTxList', function() { it('returns a tx with the requested data', function() { - var foop = 0 - var zoop = 0 - for (let i = 0; i < 10; ++i ){ - let everyOther = i % 2 - txManager.addTx({ id: i, - status: everyOther ? 'unapproved' : 'confirmed', - metamaskNetworkId: 'unit test', - txParams: { - from: everyOther ? 'foop' : 'zoop', - to: everyOther ? 'zoop' : 'foop', - } - }, onTxDoneCb) - everyOther ? ++foop : ++zoop - } - assert.equal(txManager.getFilteredTxList({status: 'confirmed', from: 'zoop'}).length, zoop) - assert.equal(txManager.getFilteredTxList({status: 'confirmed', to: 'foop'}).length, zoop) - assert.equal(txManager.getFilteredTxList({status: 'confirmed', from: 'foop'}).length, 0) - assert.equal(txManager.getFilteredTxList({status: 'confirmed'}).length, zoop) - assert.equal(txManager.getFilteredTxList({from: 'foop'}).length, foop) - assert.equal(txManager.getFilteredTxList({from: 'zoop'}).length, zoop) + let txMetas = [ + { id: 0, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, + { id: 1, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, + { id: 2, status: 'unapproved', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, + { id: 3, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, + { id: 4, status: 'unapproved', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, + { id: 5, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, + { id: 6, status: 'confirmed', txParams: { from: '0xaa', to: '0xbb' }, metamaskNetworkId: 'unit test' }, + { id: 7, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, + { id: 8, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, + { id: 9, status: 'confirmed', txParams: { from: '0xbb', to: '0xaa' }, metamaskNetworkId: 'unit test' }, + ] + txMetas.forEach((txMeta) => txManager.addTx(txMeta, noop)) + let filterParams + + filterParams = { status: 'unapproved', from: '0xaa' } + assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { status: 'unapproved', to: '0xaa' } + assert.equal(txManager.getFilteredTxList(filterParams).length, 2, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { status: 'confirmed', from: '0xbb' } + assert.equal(txManager.getFilteredTxList(filterParams).length, 3, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { status: 'confirmed' } + assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { from: '0xaa' } + assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) + filterParams = { to: '0xaa' } + assert.equal(txManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`) }) }) diff --git a/ui/app/actions.js b/ui/app/actions.js index a27ab2c14..228adcf18 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -180,6 +180,7 @@ function tryUnlockMetamask (password) { return (dispatch) => { dispatch(actions.showLoadingIndication()) dispatch(actions.unlockInProgress()) + if (global.METAMASK_DEBUG) console.log(`background.submitPassword`) background.submitPassword(password, (err) => { dispatch(actions.hideLoadingIndication()) if (err) { @@ -207,6 +208,7 @@ function transitionBackward () { function confirmSeedWords () { return (dispatch) => { dispatch(actions.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.clearSeedWordCache`) background.clearSeedWordCache((err, account) => { dispatch(actions.hideLoadingIndication()) if (err) { @@ -222,6 +224,7 @@ function confirmSeedWords () { function createNewVaultAndRestore (password, seed) { return (dispatch) => { dispatch(actions.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.createNewVaultAndRestore`) background.createNewVaultAndRestore(password, seed, (err) => { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) @@ -233,10 +236,12 @@ function createNewVaultAndRestore (password, seed) { function createNewVaultAndKeychain (password) { return (dispatch) => { dispatch(actions.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.createNewVaultAndKeychain`) background.createNewVaultAndKeychain(password, (err) => { if (err) { return dispatch(actions.displayWarning(err.message)) } + if (global.METAMASK_DEBUG) console.log(`background.placeSeedWords`) background.placeSeedWords((err) => { if (err) { return dispatch(actions.displayWarning(err.message)) @@ -257,8 +262,10 @@ function revealSeedConfirmation () { function requestRevealSeed (password) { return (dispatch) => { dispatch(actions.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.submitPassword`) background.submitPassword(password, (err) => { if (err) return dispatch(actions.displayWarning(err.message)) + if (global.METAMASK_DEBUG) console.log(`background.placeSeedWords`) background.placeSeedWords((err) => { if (err) return dispatch(actions.displayWarning(err.message)) dispatch(actions.hideLoadingIndication()) @@ -270,6 +277,7 @@ function requestRevealSeed (password) { function addNewKeyring (type, opts) { return (dispatch) => { dispatch(actions.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.addNewKeyring`) background.addNewKeyring(type, opts, (err) => { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) @@ -281,9 +289,11 @@ function addNewKeyring (type, opts) { function importNewAccount (strategy, args) { return (dispatch) => { dispatch(actions.showLoadingIndication('This may take a while, be patient.')) + if (global.METAMASK_DEBUG) console.log(`background.importAccountWithStrategy`) background.importAccountWithStrategy(strategy, args, (err) => { dispatch(actions.hideLoadingIndication()) if (err) return dispatch(actions.displayWarning(err.message)) + if (global.METAMASK_DEBUG) console.log(`background.getState`) background.getState((err, newState) => { if (err) { return dispatch(actions.displayWarning(err.message)) @@ -305,6 +315,7 @@ function navigateToNewAccountScreen() { } function addNewAccount () { + if (global.METAMASK_DEBUG) console.log(`background.addNewAccount`) return callBackgroundThenUpdate(background.addNewAccount) } @@ -317,6 +328,7 @@ function showInfoPage () { function setCurrentFiat (fiat) { return (dispatch) => { dispatch(this.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.setCurrentFiat`) background.setCurrentFiat(fiat, (data, err) => { dispatch(this.hideLoadingIndication()) dispatch({ @@ -335,6 +347,7 @@ function signMsg (msgData) { return (dispatch) => { dispatch(actions.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.signMessage`) background.signMessage(msgData, (err) => { dispatch(actions.hideLoadingIndication()) @@ -346,6 +359,7 @@ function signMsg (msgData) { function signTx (txData) { return (dispatch) => { + if (global.METAMASK_DEBUG) console.log(`background.setGasMultiplier`) background.setGasMultiplier(txData.gasMultiplier, (err) => { if (err) return dispatch(actions.displayWarning(err.message)) web3.eth.sendTransaction(txData, (err, data) => { @@ -361,6 +375,7 @@ function signTx (txData) { function sendTx (txData) { return (dispatch) => { + if (global.METAMASK_DEBUG) console.log(`background.approveTransaction`) background.approveTransaction(txData.id, (err) => { if (err) { alert(err.message) @@ -387,11 +402,13 @@ function txError (err) { } function cancelMsg (msgData) { + if (global.METAMASK_DEBUG) console.log(`background.cancelMessage`) background.cancelMessage(msgData.id) return actions.completedTx(msgData.id) } function cancelTx (txData) { + if (global.METAMASK_DEBUG) console.log(`background.cancelTransaction`) background.cancelTransaction(txData.id) return actions.completedTx(txData.id) } @@ -433,6 +450,7 @@ function showImportPage () { function agreeToDisclaimer () { return (dispatch) => { dispatch(this.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.agreeToDisclaimer`) background.agreeToDisclaimer((err) => { if (err) { return dispatch(actions.displayWarning(err.message)) @@ -503,12 +521,14 @@ function updateMetamaskState (newState) { } function lockMetamask () { + if (global.METAMASK_DEBUG) console.log(`background.setLocked`) return callBackgroundThenUpdate(background.setLocked) } function showAccountDetail (address) { return (dispatch) => { dispatch(actions.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.setSelectedAddress`) background.setSelectedAddress(address, (err) => { dispatch(actions.hideLoadingIndication()) if (err) { @@ -581,6 +601,7 @@ function goBackToInitView () { function markNoticeRead (notice) { return (dispatch) => { dispatch(this.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.markNoticeRead`) background.markNoticeRead(notice, (err, notice) => { dispatch(this.hideLoadingIndication()) if (err) { @@ -612,6 +633,7 @@ function clearNotices () { } function markAccountsFound() { + if (global.METAMASK_DEBUG) console.log(`background.markAccountsFound`) return callBackgroundThenUpdate(background.markAccountsFound) } @@ -620,6 +642,7 @@ function markAccountsFound() { // function setRpcTarget (newRpc) { + if (global.METAMASK_DEBUG) console.log(`background.setRpcTarget`) background.setRpcTarget(newRpc) return { type: actions.SET_RPC_TARGET, @@ -628,6 +651,7 @@ function setRpcTarget (newRpc) { } function setProviderType (type) { + if (global.METAMASK_DEBUG) console.log(`background.setProviderType`) background.setProviderType(type) return { type: actions.SET_PROVIDER_TYPE, @@ -636,6 +660,7 @@ function setProviderType (type) { } function useEtherscanProvider () { + if (global.METAMASK_DEBUG) console.log(`background.useEtherscanProvider`) background.useEtherscanProvider() return { type: actions.USE_ETHERSCAN_PROVIDER, @@ -692,6 +717,7 @@ function exportAccount (address) { return function (dispatch) { dispatch(self.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.exportAccount`) background.exportAccount(address, function (err, result) { dispatch(self.hideLoadingIndication()) @@ -715,6 +741,7 @@ function showPrivateKey (key) { function saveAccountLabel (account, label) { return (dispatch) => { dispatch(actions.showLoadingIndication()) + if (global.METAMASK_DEBUG) console.log(`background.saveAccountLabel`) background.saveAccountLabel(account, label, (err) => { dispatch(actions.hideLoadingIndication()) if (err) { @@ -736,6 +763,7 @@ function showSendPage () { function buyEth (address, amount) { return (dispatch) => { + if (global.METAMASK_DEBUG) console.log(`background.buyEth`) background.buyEth(address, amount) dispatch({ type: actions.BUY_ETH, @@ -814,6 +842,7 @@ function coinShiftRquest (data, marketData) { if (response.error) return dispatch(actions.displayWarning(response.error)) var message = ` Deposit your ${response.depositType} to the address bellow:` + if (global.METAMASK_DEBUG) console.log(`background.createShapeShiftTx`) background.createShapeShiftTx(response.deposit, response.depositType) dispatch(actions.showQrView(response.deposit, [message].concat(marketData))) }) @@ -893,6 +922,7 @@ function callBackgroundThenUpdate (method, ...args) { } function forceUpdateMetamaskState(dispatch){ + if (global.METAMASK_DEBUG) console.log(`background.getState`) background.getState((err, newState) => { if (err) { return dispatch(actions.displayWarning(err.message)) -- cgit v1.2.3 From 0c6df24ecff566c84810a1b29316c1efa2c83870 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 20:59:47 -0800 Subject: metamask - introduce networkStore --- app/scripts/metamask-controller.js | 68 ++++++++++++++++++++++---------------- app/scripts/transaction-manager.js | 17 ++++++---- test/unit/tx-manager-test.js | 15 ++------- 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 468e7d285..cc30a97e6 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -29,12 +29,14 @@ 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, @@ -49,7 +51,7 @@ module.exports = class MetamaskController extends EventEmitter { // 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) @@ -59,7 +61,7 @@ module.exports = class MetamaskController extends EventEmitter { this.keyringController = new KeyringController({ initState: initState.KeyringController, ethStore: this.ethStore, - getNetwork: this.getStateNetwork.bind(this), + getNetwork: this.getNetworkState.bind(this), }) this.keyringController.on('newAccount', (address) => { this.preferencesController.setSelectedAddress(address) @@ -68,10 +70,11 @@ module.exports = class MetamaskController extends EventEmitter { // tx mgmt this.txManager = new TxManager({ - initState: initState.TxManager, + initState: initState.TransactionManager, + networkStore: this.networkStore, txHistoryLimit: 40, getSelectedAddress: this.preferencesController.getSelectedAddress.bind(this.preferencesController), - getNetwork: this.getStateNetwork.bind(this), + getNetwork: this.getNetworkState.bind(this), signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, blockTracker: this.provider, @@ -85,7 +88,7 @@ module.exports = class MetamaskController extends EventEmitter { // 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() @@ -111,6 +114,7 @@ module.exports = class MetamaskController extends EventEmitter { // manual mem state subscriptions this.ethStore.on('update', this.sendUpdate.bind(this)) + this.networkStore.subscribe(this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) this.txManager.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) @@ -176,7 +180,7 @@ module.exports = class MetamaskController extends EventEmitter { { isInitialized, }, - this.state, + this.networkStore.getState(), this.ethStore.getState(), this.txManager.memStore.getState(), this.messageManager.memStore.getState(), @@ -571,16 +575,20 @@ module.exports = class MetamaskController extends EventEmitter { 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) { @@ -602,21 +610,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 () { @@ -624,26 +630,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/transaction-manager.js b/app/scripts/transaction-manager.js index 7949fb854..8d4cf6df3 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -12,12 +12,11 @@ module.exports = class TransactionManager extends EventEmitter { constructor (opts) { super() this.store = new ObservableStore(extend({ - txList: [], + transactions: [], gasMultiplier: 1, }, opts.initState)) this.memStore = new ObservableStore({}) - // this.networkStore = opts.networkStore || new ObservableStore({}) - this.getNetwork = opts.getNetwork + this.networkStore = opts.networkStore || new ObservableStore({}) this.txHistoryLimit = opts.txHistoryLimit this.getSelectedAddress = opts.getSelectedAddress @@ -31,17 +30,21 @@ module.exports = class TransactionManager extends EventEmitter { // memstore is computed from diskStore this._updateMemstore() this.store.subscribe(() => this._updateMemstore() ) - // this.networkStore.subscribe(() => this._updateMemstore() ) + this.networkStore.subscribe(() => this._updateMemstore() ) } getState () { return this.memStore.getState() } + getNetwork () { + return this.networkStore.getState().network + } + // Returns the tx list getTxList () { let network = this.getNetwork() - let fullTxList = this.store.getState().txList + let fullTxList = this.store.getState().transactions return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network) } @@ -365,8 +368,8 @@ module.exports = class TransactionManager extends EventEmitter { // Saves the new/updated txList. // Function is intended only for internal use - _saveTxList (txList) { - this.store.updateState({ txList }) + _saveTxList (transactions) { + this.store.updateState({ transactions }) } _updateMemstore () { diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js index f03294ce3..3b64f340b 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-manager-test.js @@ -1,6 +1,7 @@ const assert = require('assert') const extend = require('xtend') const EventEmitter = require('events') +const ObservableStore = require('obs-store') const STORAGE_KEY = 'metamask-persistance-key' const TransactionManager = require('../../app/scripts/transaction-manager') const noop = () => true @@ -9,11 +10,10 @@ describe('Transaction Manager', function() { let txManager beforeEach(function() { - txManager = new TransactionManager ({ - provider: "testnet", + txManager = new TransactionManager({ + networkStore: new ObservableStore({ network: 'unit test' }), txHistoryLimit: 10, blockTracker: new EventEmitter(), - getNetwork: function(){ return 'unit test' }, getSelectedAddress: function(){ return '0xabcd' }, }) }) @@ -50,15 +50,6 @@ describe('Transaction Manager', function() { }) }) - describe('#_saveTxList', function() { - it('saves the submitted data to the tx list', function() { - var target = [{ foo: 'bar', metamaskNetworkId: 'unit test', txParams: {} }] - txManager._saveTxList(target) - var result = txManager.getTxList() - assert.equal(result[0].foo, 'bar') - }) - }) - describe('#addTx', function() { it('adds a tx returned in getTxList', function() { var tx = { id: 1, status: 'confirmed', metamaskNetworkId: 'unit test', txParams: {} } -- cgit v1.2.3 From f08f40aee2614c9e72efce5d2d10f6e4b84d7a10 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 21:09:17 -0800 Subject: txManager - depend on preferencesStore --- app/scripts/metamask-controller.js | 2 +- app/scripts/transaction-manager.js | 10 +++++++--- test/unit/tx-manager-test.js | 1 - 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index cc30a97e6..de6e08e0b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -72,8 +72,8 @@ module.exports = class MetamaskController extends EventEmitter { this.txManager = new TxManager({ initState: initState.TransactionManager, networkStore: this.networkStore, + preferencesStore: this.preferencesController.store, txHistoryLimit: 40, - getSelectedAddress: this.preferencesController.getSelectedAddress.bind(this.preferencesController), getNetwork: this.getNetworkState.bind(this), signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 8d4cf6df3..6299091f2 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -17,9 +17,8 @@ module.exports = class TransactionManager extends EventEmitter { }, 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) @@ -27,10 +26,11 @@ module.exports = class TransactionManager extends EventEmitter { this.signEthTx = opts.signTransaction this.nonceLock = Semaphore(1) - // memstore is computed from diskStore + // 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 () { @@ -41,6 +41,10 @@ module.exports = class TransactionManager extends EventEmitter { return this.networkStore.getState().network } + getSelectedAddress () { + return this.preferencesStore.getState().selectedAddress + } + // Returns the tx list getTxList () { let network = this.getNetwork() diff --git a/test/unit/tx-manager-test.js b/test/unit/tx-manager-test.js index 3b64f340b..f64f048e3 100644 --- a/test/unit/tx-manager-test.js +++ b/test/unit/tx-manager-test.js @@ -14,7 +14,6 @@ describe('Transaction Manager', function() { networkStore: new ObservableStore({ network: 'unit test' }), txHistoryLimit: 10, blockTracker: new EventEmitter(), - getSelectedAddress: function(){ return '0xabcd' }, }) }) -- cgit v1.2.3 From 73edfc9f31b1cbd44ae8b5372e7bef5d1d5959ad Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 22:05:06 -0800 Subject: eth-store - convert to obs-store subclass --- app/scripts/lib/eth-store.js | 214 +++++++++++++++++-------------------- app/scripts/metamask-controller.js | 7 +- package.json | 6 +- 3 files changed, 106 insertions(+), 121 deletions(-) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index 7e2caf884..64a3c6b59 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) { + 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) { + 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) { + 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) { + // 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) { + 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/metamask-controller.js b/app/scripts/metamask-controller.js index de6e08e0b..9f79cf038 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -55,7 +55,10 @@ module.exports = class MetamaskController extends EventEmitter { // 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({ @@ -113,7 +116,7 @@ module.exports = class MetamaskController extends EventEmitter { }) // manual mem state subscriptions - this.ethStore.on('update', this.sendUpdate.bind(this)) + this.ethStore.subscribe(this.sendUpdate.bind(this)) this.networkStore.subscribe(this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) this.txManager.memStore.subscribe(this.sendUpdate.bind(this)) diff --git a/package.json b/package.json index ecf906684..3285bab5f 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "disc": "gulp disc --debug", "dist": "gulp dist --disableLiveReload", "test": "npm run lint && npm run fastTest && npm run ci", - "fastTest": "METAMASK_ENV=test mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"", - "watch": "mocha watch --compilers js:babel-register --recursive \"test/unit/**/*.js\"", + "fastTest": "METAMASK_ENV=test mocha --require test/helper.js --recursive \"test/unit/**/*.js\"", + "watch": "mocha watch --recursive \"test/unit/**/*.js\"", "genStates": "node development/genStates.js", "ui": "npm run genStates && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", @@ -71,7 +71,7 @@ "mississippi": "^1.2.0", "mkdirp": "^0.5.1", "multiplex": "^6.7.0", - "obs-store": "^2.3.0", + "obs-store": "^2.3.1", "once": "^1.3.3", "ping-pong-stream": "^1.0.0", "pojo-migrator": "^2.1.0", -- cgit v1.2.3 From 8b5e3aa287ef5e803a68730db5ccb8bec0b6b254 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 22:23:45 -0800 Subject: migration #8 - break out NoticeController substate --- app/scripts/metamask-controller.js | 4 +-- app/scripts/migrations/008.js | 36 +++++++++++++++++++++++++++ app/scripts/migrations/index.js | 1 + app/scripts/notice-controller.js | 50 ++++++++++++++++++-------------------- 4 files changed, 63 insertions(+), 28 deletions(-) create mode 100644 app/scripts/migrations/008.js diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 9f79cf038..785bb7b33 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -85,7 +85,7 @@ 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. @@ -189,7 +189,7 @@ module.exports = class MetamaskController extends EventEmitter { this.messageManager.memStore.getState(), this.keyringController.memStore.getState(), this.preferencesController.store.getState(), - this.noticeController.getState(), + this.noticeController.memStore.getState(), // config manager this.configManager.getConfig(), { 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/index.js b/app/scripts/migrations/index.js index d608f5314..a25d3b044 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -18,4 +18,5 @@ module.exports = [ require('./005'), require('./006'), require('./007'), + require('./008'), ] 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 }) + } } -- cgit v1.2.3 From b233e7e37c56852080bdc7b1843eb68ba96b5382 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 22:32:00 -0800 Subject: eth-store - cbs default to noop --- app/scripts/lib/eth-store.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/scripts/lib/eth-store.js b/app/scripts/lib/eth-store.js index 64a3c6b59..96b4a60f2 100644 --- a/app/scripts/lib/eth-store.js +++ b/app/scripts/lib/eth-store.js @@ -76,13 +76,13 @@ class EthereumStore extends ObservableStore { }) } - _updateAccounts (cb) { + _updateAccounts (cb = noop) { const accounts = this.getState().accounts const addresses = Object.keys(accounts) async.each(addresses, this._updateAccount.bind(this), cb) } - _updateAccount (address, cb) { + _updateAccount (address, cb = noop) { const accounts = this.getState().accounts this._getAccount(address, (err, result) => { if (err) return cb(err) @@ -95,13 +95,13 @@ class EthereumStore extends ObservableStore { }) } - _updateTransactions (block, cb) { + _updateTransactions (block, cb = noop) { const transactions = this.getState().transactions const txHashes = Object.keys(transactions) async.each(txHashes, this._updateTransaction.bind(this, block), cb) } - _updateTransaction (block, txHash, 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) => { @@ -114,7 +114,7 @@ class EthereumStore extends ObservableStore { }) } - _getAccount (address, cb) { + _getAccount (address, cb = noop) { const query = this._query async.parallel({ balance: query.getBalance.bind(query, address), -- cgit v1.2.3 From 9e4ef45b6ac460e6539e0f79ad5c78959fa1c4cb Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 23:32:24 -0800 Subject: migration #9 - break out CurrencyController substate --- app/scripts/lib/config-manager.js | 47 ------------------ app/scripts/lib/controllers/currency.js | 70 ++++++++++++++++++++++++++ app/scripts/lib/idStore.js | 3 -- app/scripts/metamask-controller.js | 47 +++++++++--------- app/scripts/migrations/009.js | 40 +++++++++++++++ app/scripts/migrations/index.js | 1 + package.json | 1 + test/unit/config-manager-test.js | 75 ---------------------------- test/unit/currency-controller-test.js | 87 +++++++++++++++++++++++++++++++++ ui/app/actions.js | 8 ++- 10 files changed, 227 insertions(+), 152 deletions(-) create mode 100644 app/scripts/lib/controllers/currency.js create mode 100644 app/scripts/migrations/009.js create mode 100644 test/unit/currency-controller-test.js 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/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/metamask-controller.js b/app/scripts/metamask-controller.js index 785bb7b33..2f1623c7b 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') @@ -41,13 +42,19 @@ module.exports = class MetamaskController extends EventEmitter { 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)) @@ -97,8 +104,6 @@ module.exports = class MetamaskController extends EventEmitter { this.checkTOSChange() - this.scheduleConversionInterval() - // TEMPORARY UNTIL FULL DEPRECATION: this.idStoreMigrator = new IdStoreMigrator({ configManager: this.configManager, @@ -114,11 +119,15 @@ module.exports = class MetamaskController extends EventEmitter { this.txManager.store.subscribe((state) => { this.store.updateState({ TransactionManager: state }) }) + this.currencyController.store.subscribe((state) => { + this.store.updateState({ CurrencyController: state }) + }) // manual mem state subscriptions this.ethStore.subscribe(this.sendUpdate.bind(this)) this.networkStore.subscribe(this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) + this.currencyController.store.subscribe(this.sendUpdate.bind(this)) this.txManager.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) } @@ -189,15 +198,13 @@ module.exports = class MetamaskController extends EventEmitter { this.messageManager.memStore.getState(), this.keyringController.memStore.getState(), this.preferencesController.store.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(), } @@ -223,7 +230,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), @@ -550,29 +557,19 @@ 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) { 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 a25d3b044..22bf008ba 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -19,4 +19,5 @@ module.exports = [ require('./006'), require('./007'), require('./008'), + require('./009'), ] diff --git a/package.json b/package.json index 3285bab5f..ed0be3d4b 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "clone": "^1.0.2", "copy-to-clipboard": "^2.0.0", "debounce": "^1.0.0", + "deep-merge": "^1.0.0", "denodeify": "^1.2.1", "disc": "^1.3.2", "dnode": "^1.2.2", diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js index acc73ebb4..c6f60192f 100644 --- a/test/unit/config-manager-test.js +++ b/test/unit/config-manager-test.js @@ -14,81 +14,6 @@ describe('config-manager', function() { configManager = configManagerGen() }) - describe('currency conversions', function() { - - describe('#setCurrentFiat', function() { - it('should return USD as default', function() { - assert.equal(configManager.getCurrentFiat(), 'USD') - }) - - it('should be able to set to other currency', function() { - assert.equal(configManager.getCurrentFiat(), 'USD') - configManager.setCurrentFiat('JPY') - var result = configManager.getCurrentFiat() - assert.equal(result, 'JPY') - }) - }) - - describe('#getConversionRate', function() { - it('should return undefined if non-existent', function() { - var result = configManager.getConversionRate() - assert.ok(!result) - }) - }) - - describe('#updateConversionRate', function() { - it('should retrieve an update for ETH to USD and set it in memory', function(done) { - this.timeout(15000) - var usdMock = nock('https://www.cryptonator.com') - .get('/api/ticker/eth-USD') - .reply(200, '{"ticker":{"base":"ETH","target":"USD","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}') - - assert.equal(configManager.getConversionRate(), 0) - var promise = new Promise( - function (resolve, reject) { - configManager.setCurrentFiat('USD') - configManager.updateConversionRate().then(function() { - resolve() - }) - }) - - promise.then(function() { - var result = configManager.getConversionRate() - assert.equal(typeof result, 'number') - done() - }).catch(function(err) { - console.log(err) - }) - - }) - - it('should work for JPY as well.', function() { - this.timeout(15000) - assert.equal(configManager.getConversionRate(), 0) - - var jpyMock = nock('https://www.cryptonator.com') - .get('/api/ticker/eth-JPY') - .reply(200, '{"ticker":{"base":"ETH","target":"JPY","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}') - - - var promise = new Promise( - function (resolve, reject) { - configManager.setCurrentFiat('JPY') - configManager.updateConversionRate().then(function() { - resolve() - }) - }) - - promise.then(function() { - var result = configManager.getConversionRate() - assert.equal(typeof result, 'number') - }).catch(function(err) { - console.log(err) - }) - }) - }) - }) - describe('confirmation', function() { describe('#getConfirmedDisclaimer', function() { diff --git a/test/unit/currency-controller-test.js b/test/unit/currency-controller-test.js new file mode 100644 index 000000000..c57b522c7 --- /dev/null +++ b/test/unit/currency-controller-test.js @@ -0,0 +1,87 @@ +// polyfill fetch +global.fetch = global.fetch || require('isomorphic-fetch') + +const assert = require('assert') +const extend = require('xtend') +const rp = require('request-promise') +const nock = require('nock') +const CurrencyController = require('../../app/scripts/lib/controllers/currency') + +describe('config-manager', function() { + var currencyController + + beforeEach(function() { + currencyController = new CurrencyController() + }) + + describe('currency conversions', function() { + + describe('#setCurrentCurrency', function() { + it('should return USD as default', function() { + assert.equal(currencyController.getCurrentCurrency(), 'USD') + }) + + it('should be able to set to other currency', function() { + assert.equal(currencyController.getCurrentCurrency(), 'USD') + currencyController.setCurrentCurrency('JPY') + var result = currencyController.getCurrentCurrency() + assert.equal(result, 'JPY') + }) + }) + + describe('#getConversionRate', function() { + it('should return undefined if non-existent', function() { + var result = currencyController.getConversionRate() + assert.ok(!result) + }) + }) + + describe('#updateConversionRate', function() { + it('should retrieve an update for ETH to USD and set it in memory', function(done) { + this.timeout(15000) + var usdMock = nock('https://www.cryptonator.com') + .get('/api/ticker/eth-USD') + .reply(200, '{"ticker":{"base":"ETH","target":"USD","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}') + + assert.equal(currencyController.getConversionRate(), 0) + currencyController.setCurrentCurrency('USD') + currencyController.updateConversionRate() + .then(function() { + var result = currencyController.getConversionRate() + console.log('currencyController.getConversionRate:', result) + assert.equal(typeof result, 'number') + done() + }).catch(function(err) { + done(err) + }) + + }) + + it('should work for JPY as well.', function() { + this.timeout(15000) + assert.equal(currencyController.getConversionRate(), 0) + + var jpyMock = nock('https://www.cryptonator.com') + .get('/api/ticker/eth-JPY') + .reply(200, '{"ticker":{"base":"ETH","target":"JPY","price":"11.02456145","volume":"44948.91745289","change":"-0.01472534"},"timestamp":1472072136,"success":true,"error":""}') + + + var promise = new Promise( + function (resolve, reject) { + currencyController.setCurrentCurrency('JPY') + currencyController.updateConversionRate().then(function() { + resolve() + }) + }) + + promise.then(function() { + var result = currencyController.getConversionRate() + assert.equal(typeof result, 'number') + }).catch(function(err) { + done(err) + }) + }) + }) + }) + +}) diff --git a/ui/app/actions.js b/ui/app/actions.js index 228adcf18..c153a55a6 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -325,12 +325,16 @@ function showInfoPage () { } } -function setCurrentFiat (fiat) { +function setCurrentFiat (currencyCode) { return (dispatch) => { dispatch(this.showLoadingIndication()) if (global.METAMASK_DEBUG) console.log(`background.setCurrentFiat`) - background.setCurrentFiat(fiat, (data, err) => { + background.setCurrentCurrency(currencyCode, (err, data) => { dispatch(this.hideLoadingIndication()) + if (err) { + console.error(err.stack) + return dispatch(actions.displayWarning(err.message)) + } dispatch({ type: this.SET_CURRENT_FIAT, value: { -- cgit v1.2.3 From 970d4fd69519bab1de972839190a4ede888914bb Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 2 Feb 2017 23:47:00 -0800 Subject: metamask - add missing diskState and memState subscriptions --- app/scripts/metamask-controller.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2f1623c7b..c0910014f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -110,26 +110,31 @@ module.exports = class MetamaskController extends EventEmitter { }) // 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.txManager.store.subscribe((state) => { - this.store.updateState({ TransactionManager: 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.ethStore.subscribe(this.sendUpdate.bind(this)) this.networkStore.subscribe(this.sendUpdate.bind(this)) - this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) - this.currencyController.store.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)) } // -- cgit v1.2.3