Save recent network balances in local storage (#5843)
* Use selector for state.metamask.accounts in all cases. * Default to cached balance when selecting metamask accounts * Adds the cached-balances controller * Documentation and small codes fixes for #5843 Co-Authored-By: danjm <danjm.com@gmail.com>feature/default_network_editable
parent
45a9f40aa6
commit
4c24555545
@ -0,0 +1,83 @@ |
|||||||
|
const ObservableStore = require('obs-store') |
||||||
|
const extend = require('xtend') |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} CachedBalancesOptions |
||||||
|
* @property {Object} accountTracker An {@code AccountTracker} reference |
||||||
|
* @property {Function} getNetwork A function to get the current network |
||||||
|
* @property {Object} initState The initial controller state |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* Background controller responsible for maintaining |
||||||
|
* a cache of account balances in local storage |
||||||
|
*/ |
||||||
|
class CachedBalancesController { |
||||||
|
/** |
||||||
|
* Creates a new controller instance |
||||||
|
* |
||||||
|
* @param {CachedBalancesOptions} [opts] Controller configuration parameters |
||||||
|
*/ |
||||||
|
constructor (opts = {}) { |
||||||
|
const { accountTracker, getNetwork } = opts |
||||||
|
|
||||||
|
this.accountTracker = accountTracker |
||||||
|
this.getNetwork = getNetwork |
||||||
|
|
||||||
|
const initState = extend({ |
||||||
|
cachedBalances: {}, |
||||||
|
}, opts.initState) |
||||||
|
this.store = new ObservableStore(initState) |
||||||
|
|
||||||
|
this._registerUpdates() |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Updates the cachedBalances property for the current network. Cached balances will be updated to those in the passed accounts |
||||||
|
* if balances in the passed accounts are truthy. |
||||||
|
* |
||||||
|
* @param {Object} obj The the recently updated accounts object for the current network |
||||||
|
* @returns {Promise<void>} |
||||||
|
*/ |
||||||
|
async updateCachedBalances ({ accounts }) { |
||||||
|
const network = await this.getNetwork() |
||||||
|
const balancesToCache = await this._generateBalancesToCache(accounts, network) |
||||||
|
this.store.updateState({ |
||||||
|
cachedBalances: balancesToCache, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
_generateBalancesToCache (newAccounts, currentNetwork) { |
||||||
|
const { cachedBalances } = this.store.getState() |
||||||
|
const currentNetworkBalancesToCache = { ...cachedBalances[currentNetwork] } |
||||||
|
|
||||||
|
Object.keys(newAccounts).forEach(accountID => { |
||||||
|
const account = newAccounts[accountID] |
||||||
|
|
||||||
|
if (account.balance) { |
||||||
|
currentNetworkBalancesToCache[accountID] = account.balance |
||||||
|
} |
||||||
|
}) |
||||||
|
const balancesToCache = { |
||||||
|
...cachedBalances, |
||||||
|
[currentNetwork]: currentNetworkBalancesToCache, |
||||||
|
} |
||||||
|
|
||||||
|
return balancesToCache |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets up listeners and subscriptions which should trigger an update of cached balances. These updates will |
||||||
|
* happen when the current account changes. Which happens on block updates, as well as on network and account |
||||||
|
* selections. |
||||||
|
* |
||||||
|
* @private |
||||||
|
* |
||||||
|
*/ |
||||||
|
_registerUpdates () { |
||||||
|
const update = this.updateCachedBalances.bind(this) |
||||||
|
this.accountTracker.store.subscribe(update) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = CachedBalancesController |
@ -0,0 +1,137 @@ |
|||||||
|
const assert = require('assert') |
||||||
|
const sinon = require('sinon') |
||||||
|
const CachedBalancesController = require('../../../../app/scripts/controllers/cached-balances') |
||||||
|
|
||||||
|
describe('CachedBalancesController', () => { |
||||||
|
describe('updateCachedBalances', () => { |
||||||
|
it('should update the cached balances', async () => { |
||||||
|
const controller = new CachedBalancesController({ |
||||||
|
getNetwork: () => Promise.resolve(17), |
||||||
|
accountTracker: { |
||||||
|
store: { |
||||||
|
subscribe: () => {}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
initState: { |
||||||
|
cachedBalances: 'mockCachedBalances', |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
controller._generateBalancesToCache = sinon.stub().callsFake(() => Promise.resolve('mockNewCachedBalances')) |
||||||
|
|
||||||
|
await controller.updateCachedBalances({ accounts: 'mockAccounts' }) |
||||||
|
|
||||||
|
assert.equal(controller._generateBalancesToCache.callCount, 1) |
||||||
|
assert.deepEqual(controller._generateBalancesToCache.args[0], ['mockAccounts', 17]) |
||||||
|
assert.equal(controller.store.getState().cachedBalances, 'mockNewCachedBalances') |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('_generateBalancesToCache', () => { |
||||||
|
it('should generate updated account balances where the current network was updated', () => { |
||||||
|
const controller = new CachedBalancesController({ |
||||||
|
accountTracker: { |
||||||
|
store: { |
||||||
|
subscribe: () => {}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
initState: { |
||||||
|
cachedBalances: { |
||||||
|
17: { |
||||||
|
a: '0x1', |
||||||
|
b: '0x2', |
||||||
|
c: '0x3', |
||||||
|
}, |
||||||
|
16: { |
||||||
|
a: '0xa', |
||||||
|
b: '0xb', |
||||||
|
c: '0xc', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
const result = controller._generateBalancesToCache({ |
||||||
|
a: { balance: '0x4' }, |
||||||
|
b: { balance: null }, |
||||||
|
c: { balance: '0x5' }, |
||||||
|
}, 17) |
||||||
|
|
||||||
|
assert.deepEqual(result, { |
||||||
|
17: { |
||||||
|
a: '0x4', |
||||||
|
b: '0x2', |
||||||
|
c: '0x5', |
||||||
|
}, |
||||||
|
16: { |
||||||
|
a: '0xa', |
||||||
|
b: '0xb', |
||||||
|
c: '0xc', |
||||||
|
}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('should generate updated account balances where the a new network was selected', () => { |
||||||
|
const controller = new CachedBalancesController({ |
||||||
|
accountTracker: { |
||||||
|
store: { |
||||||
|
subscribe: () => {}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
initState: { |
||||||
|
cachedBalances: { |
||||||
|
17: { |
||||||
|
a: '0x1', |
||||||
|
b: '0x2', |
||||||
|
c: '0x3', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
const result = controller._generateBalancesToCache({ |
||||||
|
a: { balance: '0x4' }, |
||||||
|
b: { balance: null }, |
||||||
|
c: { balance: '0x5' }, |
||||||
|
}, 16) |
||||||
|
|
||||||
|
assert.deepEqual(result, { |
||||||
|
17: { |
||||||
|
a: '0x1', |
||||||
|
b: '0x2', |
||||||
|
c: '0x3', |
||||||
|
}, |
||||||
|
16: { |
||||||
|
a: '0x4', |
||||||
|
c: '0x5', |
||||||
|
}, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
describe('_registerUpdates', () => { |
||||||
|
it('should subscribe to the account tracker with the updateCachedBalances method', async () => { |
||||||
|
const subscribeSpy = sinon.spy() |
||||||
|
const controller = new CachedBalancesController({ |
||||||
|
getNetwork: () => Promise.resolve(17), |
||||||
|
accountTracker: { |
||||||
|
store: { |
||||||
|
subscribe: subscribeSpy, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}) |
||||||
|
subscribeSpy.resetHistory() |
||||||
|
|
||||||
|
const updateCachedBalancesSpy = sinon.spy() |
||||||
|
controller.updateCachedBalances = updateCachedBalancesSpy |
||||||
|
controller._registerUpdates({ accounts: 'mockAccounts' }) |
||||||
|
|
||||||
|
assert.equal(subscribeSpy.callCount, 1) |
||||||
|
|
||||||
|
subscribeSpy.args[0][0]() |
||||||
|
|
||||||
|
assert.equal(updateCachedBalancesSpy.callCount, 1) |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
}) |
Loading…
Reference in new issue