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