openethereum/js/packages/shared/redux/providers/balances.js
Jaco Greeff 49fdd23d58 Ui 2 move to packages/* (#6113)
* Move secureApi to shell

* Extract isTestnet test

* Use mobx + subscriptions for status

* Re-add status indicator

* Add lerna

* Move intial packages to js/packages

* Move 3rdparty/{email,sms}-verification to correct location

* Move package.json & README to library src

* Move tests for library packages

* Move views & dapps to packages

* Move i18n to root

* Move shell to actual src (main app)

* Remove ~ references

* Change ~ to root (explicit imports)

* Finalise convert of ~

* Move views into dapps as well

* Move dapps to packages/

* Fix references

* Update css

* Update test spec locations

* Update tests

* Case fix

* Skip flakey tests

* Update enzyme

* Skip previously ignored tests

* Allow empty api for hw

* Re-add theme for embed
2017-07-21 15:46:53 +02:00

349 lines
8.5 KiB
JavaScript

// Copyright 2015-2017 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { throttle } from 'lodash';
import { padRight } from '@parity/api/util/format';
import Contracts from '@parity/shared/contracts';
import { fetchBalances, fetchTokensBalances, queryTokensFilter } from './balancesActions';
import { loadTokens, fetchTokens } from './tokensActions';
let instance = null;
export default class Balances {
constructor (store, api) {
this._api = api;
this._store = store;
this._tokenreg = null;
this._tokenregSID = null;
this._tokenMetaSID = null;
this._blockNumberSID = null;
this._accountsInfoSID = null;
// Throtthled load tokens (no more than once
// every minute)
this.loadTokens = throttle(
this._loadTokens,
60 * 1000,
{ leading: true, trailing: true }
);
// Throttled `_fetchBalances` function
// that gets called max once every 40s
this.longThrottledFetch = throttle(
this._fetchBalances,
40 * 1000,
{ leading: false, trailing: true }
);
this.shortThrottledFetch = throttle(
this._fetchBalances,
2 * 1000,
{ leading: false, trailing: true }
);
// Fetch all tokens every 2 minutes
this.throttledTokensFetch = throttle(
this._fetchTokens,
2 * 60 * 1000,
{ leading: false, trailing: true }
);
// Unsubscribe previous instance if it exists
if (instance) {
Balances.stop();
}
}
static get (store = {}) {
if (!instance && store) {
const { api } = store.getState();
return Balances.instantiate(store, api);
}
return instance;
}
static instantiate (store, api) {
if (!instance) {
instance = new Balances(store, api);
}
return instance;
}
static start () {
if (!instance) {
return Promise.reject('BalancesProvider has not been intiated yet');
}
const self = instance;
// Unsubscribe from previous subscriptions
return Balances
.stop()
.then(() => self.loadTokens())
.then(() => {
const promises = [
self.subscribeBlockNumber(),
self.subscribeAccountsInfo()
];
return Promise.all(promises);
});
}
static stop () {
if (!instance) {
return Promise.resolve();
}
const self = instance;
const promises = [];
if (self._blockNumberSID) {
const p = self._api
.unsubscribe(self._blockNumberSID)
.then(() => {
self._blockNumberSID = null;
});
promises.push(p);
}
if (self._accountsInfoSID) {
const p = self._api
.unsubscribe(self._accountsInfoSID)
.then(() => {
self._accountsInfoSID = null;
});
promises.push(p);
}
// Unsubscribe without adding the promises
// to the result, since it would have to wait for a
// reconnection to resolve if the Node is disconnected
if (self._tokenreg) {
if (self._tokenregSID) {
const tokenregSID = self._tokenregSID;
self._tokenreg
.unsubscribe(tokenregSID)
.then(() => {
if (self._tokenregSID === tokenregSID) {
self._tokenregSID = null;
}
});
}
if (self._tokenMetaSID) {
const tokenMetaSID = self._tokenMetaSID;
self._tokenreg
.unsubscribe(tokenMetaSID)
.then(() => {
if (self._tokenMetaSID === tokenMetaSID) {
self._tokenMetaSID = null;
}
});
}
}
return Promise.all(promises);
}
subscribeAccountsInfo () {
return this._api
.subscribe('parity_allAccountsInfo', (error, accountsInfo) => {
if (error) {
return;
}
this.fetchAllBalances();
})
.then((accountsInfoSID) => {
this._accountsInfoSID = accountsInfoSID;
})
.catch((error) => {
console.warn('_subscribeAccountsInfo', error);
});
}
subscribeBlockNumber () {
return this._api
.subscribe('eth_blockNumber', (error) => {
if (error) {
return console.warn('_subscribeBlockNumber', error);
}
this._store.dispatch(queryTokensFilter());
return this.fetchAllBalances();
})
.then((blockNumberSID) => {
this._blockNumberSID = blockNumberSID;
})
.catch((error) => {
console.warn('_subscribeBlockNumber', error);
});
}
fetchAllBalances (options = {}) {
// If it's a network change, reload the tokens
// ( and then fetch the tokens balances ) and fetch
// the accounts balances
if (options.changedNetwork) {
this.loadTokens({ skipNotifications: true });
this.loadTokens.flush();
this.fetchBalances({
force: true,
skipNotifications: true
});
return;
}
this.fetchTokensBalances(options);
this.fetchBalances(options);
}
fetchTokensBalances (options) {
const { skipNotifications = false, force = false } = options;
this.throttledTokensFetch(skipNotifications);
if (force) {
this.throttledTokensFetch.flush();
}
}
fetchBalances (options) {
const { skipNotifications = false, force = false } = options;
const { syncing } = this._store.getState().nodeStatus;
// If syncing, only retrieve balances once every
// few seconds
if (syncing || syncing === null) {
this.shortThrottledFetch.cancel();
this.longThrottledFetch(skipNotifications);
if (force) {
this.longThrottledFetch.flush();
}
return;
}
this.longThrottledFetch.cancel();
this.shortThrottledFetch(skipNotifications);
if (force) {
this.shortThrottledFetch.flush();
}
}
_fetchBalances (skipNotifications = false) {
this._store.dispatch(fetchBalances(null, skipNotifications));
}
_fetchTokens (skipNotifications = false) {
this._store.dispatch(fetchTokensBalances(null, null, skipNotifications));
}
getTokenRegistry () {
return Contracts.get(this._api).tokenReg.getContract();
}
_loadTokens (options = {}) {
return this
.getTokenRegistry()
.then((tokenreg) => {
this._tokenreg = tokenreg;
this._store.dispatch(loadTokens(options));
return this.attachToTokens(tokenreg);
})
.catch((error) => {
console.warn('balances::loadTokens', error);
});
}
attachToTokens (tokenreg) {
return Promise
.all([
this.attachToTokenMetaChange(tokenreg),
this.attachToNewToken(tokenreg)
]);
}
attachToNewToken (tokenreg) {
if (this._tokenregSID) {
return Promise.resolve();
}
return tokenreg.instance.Registered
.subscribe({
fromBlock: 0,
toBlock: 'latest',
skipInitFetch: true
}, (error, logs) => {
if (error) {
return console.error('balances::attachToNewToken', 'failed to attach to tokenreg Registered', error.toString(), error.stack);
}
this.handleTokensLogs(logs);
})
.then((tokenregSID) => {
this._tokenregSID = tokenregSID;
});
}
attachToTokenMetaChange (tokenreg) {
if (this._tokenMetaSID) {
return Promise.resolve();
}
return tokenreg.instance.MetaChanged
.subscribe({
fromBlock: 0,
toBlock: 'latest',
topics: [ null, padRight(this._api.util.asciiToHex('IMG'), 32) ],
skipInitFetch: true
}, (error, logs) => {
if (error) {
return console.error('balances::attachToTokenMetaChange', 'failed to attach to tokenreg MetaChanged', error.toString(), error.stack);
}
this.handleTokensLogs(logs);
})
.then((tokenMetaSID) => {
this._tokenMetaSID = tokenMetaSID;
});
}
handleTokensLogs (logs) {
const tokenIds = logs.map((log) => log.params.id.value.toNumber());
this._store.dispatch(fetchTokens(tokenIds));
}
}