From 58e3f34f938050c01ebf591c02c03400592de5c9 Mon Sep 17 00:00:00 2001 From: Devin Alexander Torres Date: Thu, 15 Feb 2018 20:27:28 -0600 Subject: [PATCH] feat(wallet): poll for account balance changes within a wallet --- app/account/model.js | 2 + app/account/serializer.js | 6 +++ app/components/download-progress/component.js | 2 +- app/rpc/service.js | 10 ++++ app/status/service.js | 4 +- app/utils/sum-amounts.js | 11 +++++ app/wallet/adapter.js | 17 +------ app/wallet/model.js | 19 ++++++-- app/wallets/controller.js | 48 +++++++++++++++++++ app/wallets/route.js | 8 ++++ tests/unit/utils/sum-amounts-test.js | 12 +++++ tests/unit/wallets/controller-test.js | 6 ++- 12 files changed, 123 insertions(+), 22 deletions(-) create mode 100644 app/utils/sum-amounts.js create mode 100644 tests/unit/utils/sum-amounts-test.js diff --git a/app/account/model.js b/app/account/model.js index 5e4b701c..bab2a8e2 100644 --- a/app/account/model.js +++ b/app/account/model.js @@ -16,6 +16,8 @@ export default DS.Model.extend({ @attr('big-number') balance: null, @attr('big-number') pending: null, + @attr('date') modifiedTimestamp: null, + toString() { const id = this.get('id'); const wallet = this.get('wallet.id'); diff --git a/app/account/serializer.js b/app/account/serializer.js index 79c8da1f..5e8bed45 100644 --- a/app/account/serializer.js +++ b/app/account/serializer.js @@ -1,5 +1,11 @@ import DS from 'ember-data'; +import { underscore } from '@ember/string'; + export default DS.JSONSerializer.extend({ primaryKey: 'account', + + keyForAttribute(attr) { + return underscore(attr); + }, }); diff --git a/app/components/download-progress/component.js b/app/components/download-progress/component.js index 30273e78..4e81ebc0 100644 --- a/app/components/download-progress/component.js +++ b/app/components/download-progress/component.js @@ -31,7 +31,7 @@ export default Component.extend({ }, updateProgress(value) { - if (!this.isDestroyed) { + if (!this.isDestroying) { this.set('value', value); } }, diff --git a/app/rpc/service.js b/app/rpc/service.js index b470adc8..852d6c9b 100644 --- a/app/rpc/service.js +++ b/app/rpc/service.js @@ -11,6 +11,7 @@ export const actions = { WALLET_CREATE: 'wallet_create', WALLET_LOCKED: 'wallet_locked', WALLET_BALANCE_TOTAL: 'wallet_balance_total', + WALLET_BALANCES: 'wallet_balances', WALLET_CHANGE_SEED: 'wallet_change_seed', ACCOUNT_CREATE: 'account_create', ACCOUNT_INFO: 'account_info', @@ -96,6 +97,11 @@ export default Service.extend({ return this.call(actions.WALLET_BALANCE_TOTAL, { wallet }); }, + async walletBalances(wallet) { + const { balances } = await this.call(actions.WALLET_BALANCES, { wallet }); + return balances; + }, + async walletChangeSeed(wallet, seed) { const { success } = await this.call(actions.WALLET_CHANGE_SEED, { wallet, seed }); return success === ''; @@ -126,6 +132,10 @@ export default Service.extend({ } } + if (!info.modified_timestamp) { + info.modified_timestamp = String(Date.now()); + } + return info; }, diff --git a/app/status/service.js b/app/status/service.js index 5dd38643..fca2527a 100644 --- a/app/status/service.js +++ b/app/status/service.js @@ -7,7 +7,7 @@ import { on } from 'ember-decorators/object/evented'; import { hash } from 'rsvp'; -const DEFAULT_INTERVAL = 10000; // 10s +const STATUS_POLL_INTERVAL = 10000; // 10s const Service = ObjectProxy.extend(PromiseProxyMixin, { @service pollboy: null, @@ -18,7 +18,7 @@ const Service = ObjectProxy.extend(PromiseProxyMixin, { @on('init') setupPoller() { const pollboy = this.get('pollboy'); - this.poller = pollboy.add(this, this.onPoll, DEFAULT_INTERVAL); + this.poller = pollboy.add(this, this.onPoll, STATUS_POLL_INTERVAL); this.poller.pause(); }, diff --git a/app/utils/sum-amounts.js b/app/utils/sum-amounts.js new file mode 100644 index 00000000..96c5a966 --- /dev/null +++ b/app/utils/sum-amounts.js @@ -0,0 +1,11 @@ +import { A } from '@ember/array'; +import { isPresent } from '@ember/utils'; + +import BigNumber from 'npm:bignumber.js'; + +export default function sumAmounts(amounts) { + return A(amounts) + .filter(isPresent) + .map(x => new BigNumber(String(x))) + .reduce((x, y) => y.plus(x), 0); +} diff --git a/app/wallet/adapter.js b/app/wallet/adapter.js index d11710b2..00e96cd9 100644 --- a/app/wallet/adapter.js +++ b/app/wallet/adapter.js @@ -1,6 +1,5 @@ import DS from 'ember-data'; -import { all } from 'rsvp'; import { service } from 'ember-decorators/service'; export default DS.Adapter.extend({ @@ -13,20 +12,8 @@ export default DS.Adapter.extend({ async findRecord(store, type, id, snapshot) { const rpc = this.get('rpc'); const { wallet } = this.serialize(snapshot, { includeId: true }); - const [ - { balance, pending }, - { accounts }, - ] = await all([ - rpc.walletBalanceTotal(id), - rpc.accountList(id), - ]); - - return { - wallet, - balance, - pending, - accounts, - }; + const { accounts } = await rpc.accountList(id); + return { wallet, accounts }; }, async createRecord(store, type, snapshot) { diff --git a/app/wallet/model.js b/app/wallet/model.js index e0e9618f..7c86c229 100644 --- a/app/wallet/model.js +++ b/app/wallet/model.js @@ -1,18 +1,31 @@ import DS from 'ember-data'; -import { get } from '@ember/object'; +import { A } from '@ember/array'; +import { get, computed } from '@ember/object'; import { service } from 'ember-decorators/service'; import { attr, hasMany } from 'ember-decorators/data'; +import sumAmounts from '../utils/sum-amounts'; + +const sumAccountsProperty = dependentKey => + computed(`accounts.@each.${dependentKey}`, { + get() { + const accounts = this.get('accounts'); + const amounts = A(accounts).mapBy(dependentKey); + return sumAmounts(amounts); + }, + }); + export default DS.Model.extend({ @service settings: null, @hasMany('account', { async: true }) accounts: null, - @attr('big-number') balance: null, - @attr('big-number') pending: null, @attr seed: null, + balance: sumAccountsProperty('balance'), + pending: sumAccountsProperty('pending'), + toString() { const id = this.get('id'); const settings = this.get('settings'); diff --git a/app/wallets/controller.js b/app/wallets/controller.js index aa928026..20429b38 100644 --- a/app/wallets/controller.js +++ b/app/wallets/controller.js @@ -1,7 +1,55 @@ import Controller from '@ember/controller'; +import { get } from '@ember/object'; import { service } from 'ember-decorators/service'; +import { on } from 'ember-decorators/object/evented'; + +const WALLET_POLL_INTERVAL = 5000; // 5s export default Controller.extend({ + @service pollboy: null, @service flashMessages: null, + @service rpc: null, + + poller: null, + + @on('init') + setupPoller() { + const pollboy = this.get('pollboy'); + this.poller = pollboy.add(this, this.onPoll, WALLET_POLL_INTERVAL); + this.poller.pause(); + }, + + willDestroy(...args) { + this._super(...args); + + const poller = this.get('poller'); + if (poller) { + this.get('pollboy').remove(poller); + } + }, + + async onPoll() { + if (!this.isDestroying) { + const model = this.get('model'); + if (model) { + const isNew = get(model, 'isNew'); + if (!isNew) { + const wallet = get(model, 'id'); + this.updateBalances(wallet); + } + } + } + }, + + async updateBalances(wallet) { + const balances = await this.get('rpc').walletBalances(wallet); + const data = Object.entries(balances).map(([id, attributes]) => ({ + id, + attributes, + type: 'account', + })); + + this.store.push({ data }); + }, }); diff --git a/app/wallets/route.js b/app/wallets/route.js index 31e1a811..903676a3 100644 --- a/app/wallets/route.js +++ b/app/wallets/route.js @@ -1,5 +1,6 @@ import Route from '@ember/routing/route'; import { get } from '@ember/object'; +import { tryInvoke } from '@ember/utils'; import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'; import { service } from 'ember-decorators/service'; @@ -35,4 +36,11 @@ export default Route.extend(AuthenticatedRouteMixin, { }); } }, + + setupController(controller, model) { + this._super(controller, model); + + const poller = get(controller, 'poller'); + tryInvoke(poller, 'resume'); + }, }); diff --git a/tests/unit/utils/sum-amounts-test.js b/tests/unit/utils/sum-amounts-test.js new file mode 100644 index 00000000..41acebff --- /dev/null +++ b/tests/unit/utils/sum-amounts-test.js @@ -0,0 +1,12 @@ +import { expect } from 'chai'; +import { describe, it } from 'mocha'; +import sumAmounts from '@nanocurrency/nano-desktop/utils/sum-amounts'; + +describe('Unit | Utility | sum-amounts', () => { + // Replace this with your real tests. + it('works', () => { + const result = sumAmounts([1, 2]); + expect(result).to.be.ok; + expect(result.toFixed(0)).to.equal('3'); + }); +}); diff --git a/tests/unit/wallets/controller-test.js b/tests/unit/wallets/controller-test.js index 998c160a..b8157503 100644 --- a/tests/unit/wallets/controller-test.js +++ b/tests/unit/wallets/controller-test.js @@ -5,7 +5,11 @@ import { setupTest } from 'ember-mocha'; describe('Unit | Controller | wallets', () => { setupTest('controller:wallets', { // Specify the other units that are required for this test. - needs: ['service:flashMessages'], + needs: [ + 'service:pollboy', + 'service:flashMessages', + 'service:rpc', + ], }); // Replace this with your real tests.