diff --git a/js/src/api/transport/ws/ws.js b/js/src/api/transport/ws/ws.js
index 1cb1fb1c4..7b214fded 100644
--- a/js/src/api/transport/ws/ws.js
+++ b/js/src/api/transport/ws/ws.js
@@ -84,7 +84,7 @@ export default class Ws extends JsonRpcBase {
this._connecting = false;
if (this._autoConnect) {
- this._connect();
+ setTimeout(() => this._connect(), 500);
}
}
diff --git a/js/src/redux/actions.js b/js/src/redux/actions.js
index 58e9c2a36..bb5f42a33 100644
--- a/js/src/redux/actions.js
+++ b/js/src/redux/actions.js
@@ -16,7 +16,7 @@
import { newError } from '../ui/Errors/actions';
import { setAddressImage } from './providers/imagesActions';
-import { clearStatusLogs, toggleStatusLogs } from './providers/statusActions';
+import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from './providers/statusActions';
import { toggleView } from '../views/Settings';
export {
@@ -24,5 +24,6 @@ export {
clearStatusLogs,
setAddressImage,
toggleStatusLogs,
+ toggleStatusRefresh,
toggleView
};
diff --git a/js/src/redux/providers/status.js b/js/src/redux/providers/status.js
index 9f47517f5..4ef8d3de6 100644
--- a/js/src/redux/providers/status.js
+++ b/js/src/redux/providers/status.js
@@ -15,32 +15,29 @@
// along with Parity. If not, see .
import { statusBlockNumber, statusCollection, statusLogs } from './statusActions';
+import { isEqual } from 'lodash';
export default class Status {
constructor (store, api) {
this._api = api;
this._store = store;
+
+ this._pingable = false;
+ this._apiStatus = {};
+ this._status = {};
+ this._longStatus = {};
+ this._minerSettings = {};
+
+ this._pollPingTimeoutId = null;
+ this._longStatusTimeoutId = null;
}
start () {
this._subscribeBlockNumber();
this._pollPing();
this._pollStatus();
+ this._pollLongStatus();
this._pollLogs();
- this._fetchEnode();
- }
-
- _fetchEnode () {
- this._api.parity
- .enode()
- .then((enode) => {
- this._store.dispatch(statusCollection({ enode }));
- })
- .catch(() => {
- window.setTimeout(() => {
- this._fetchEnode();
- }, 1000);
- });
}
_subscribeBlockNumber () {
@@ -57,10 +54,43 @@ export default class Status {
});
}
+ /**
+ * Pinging should be smart. It should only
+ * be used when the UI is connecting or the
+ * Node is deconnected.
+ *
+ * @see src/views/Connection/connection.js
+ */
+ _shouldPing = () => {
+ const { isConnected, isConnecting } = this._apiStatus;
+ return isConnecting || !isConnected;
+ }
+
+ _stopPollPing = () => {
+ if (!this._pollPingTimeoutId) {
+ return;
+ }
+
+ clearTimeout(this._pollPingTimeoutId);
+ this._pollPingTimeoutId = null;
+ }
+
_pollPing = () => {
- const dispatch = (status, timeout = 500) => {
- this._store.dispatch(statusCollection({ isPingable: status }));
- setTimeout(this._pollPing, timeout);
+ // Already pinging, don't try again
+ if (this._pollPingTimeoutId) {
+ return;
+ }
+
+ const dispatch = (pingable, timeout = 1000) => {
+ if (pingable !== this._pingable) {
+ this._pingable = pingable;
+ this._store.dispatch(statusCollection({ isPingable: pingable }));
+ }
+
+ this._pollPingTimeoutId = setTimeout(() => {
+ this._stopPollPing();
+ this._pollPing();
+ }, timeout);
};
fetch('/', { method: 'HEAD' })
@@ -79,61 +109,162 @@ export default class Status {
}
_pollStatus = () => {
- const { secureToken, isConnected, isConnecting, needsToken } = this._api;
-
const nextTimeout = (timeout = 1000) => {
setTimeout(this._pollStatus, timeout);
};
- this._store.dispatch(statusCollection({ isConnected, isConnecting, needsToken, secureToken }));
+ const { isConnected, isConnecting, needsToken, secureToken } = this._api;
+
+ const apiStatus = {
+ isConnected,
+ isConnecting,
+ needsToken,
+ secureToken
+ };
+
+ const gotReconnected = !this._apiStatus.isConnected && apiStatus.isConnected;
+
+ if (gotReconnected) {
+ this._pollLongStatus();
+ }
+
+ if (!isEqual(apiStatus, this._apiStatus)) {
+ this._store.dispatch(statusCollection(apiStatus));
+ this._apiStatus = apiStatus;
+ }
+
+ // Ping if necessary, otherwise stop pinging
+ if (this._shouldPing()) {
+ this._pollPing();
+ } else {
+ this._stopPollPing();
+ }
if (!isConnected) {
- nextTimeout(250);
- return;
+ return nextTimeout(250);
}
+ const { refreshStatus } = this._store.getState().nodeStatus;
+
+ const statusPromises = [ this._api.eth.syncing() ];
+
+ if (refreshStatus) {
+ statusPromises.push(this._api.eth.hashrate());
+ statusPromises.push(this._api.parity.netPeers());
+ }
+
+ Promise
+ .all(statusPromises)
+ .then((statusResults) => {
+ const status = statusResults.length === 1
+ ? {
+ syncing: statusResults[0]
+ }
+ : {
+ syncing: statusResults[0],
+ hashrate: statusResults[1],
+ netPeers: statusResults[2]
+ };
+
+ if (!isEqual(status, this._status)) {
+ this._store.dispatch(statusCollection(status));
+ this._status = status;
+ }
+
+ nextTimeout();
+ })
+ .catch((error) => {
+ console.error('_pollStatus', error);
+ nextTimeout(250);
+ });
+ }
+
+ /**
+ * Miner settings should never changes unless
+ * Parity is restarted, or if the values are changed
+ * from the UI
+ */
+ _pollMinerSettings = () => {
+ Promise
+ .all([
+ this._api.eth.coinbase(),
+ this._api.parity.extraData(),
+ this._api.parity.minGasPrice(),
+ this._api.parity.gasFloorTarget()
+ ])
+ .then(([
+ coinbase, extraData, minGasPrice, gasFloorTarget
+ ]) => {
+ const minerSettings = {
+ coinbase,
+ extraData,
+ minGasPrice,
+ gasFloorTarget
+ };
+
+ if (!isEqual(minerSettings, this._minerSettings)) {
+ this._store.dispatch(statusCollection(minerSettings));
+ this._minerSettings = minerSettings;
+ }
+ })
+ .catch((error) => {
+ console.error('_pollMinerSettings', error);
+ });
+ }
+
+ /**
+ * The data fetched here should not change
+ * unless Parity is restarted. They are thus
+ * fetched every 30s just in case, and whenever
+ * the client got reconnected.
+ */
+ _pollLongStatus = () => {
+ const nextTimeout = (timeout = 30000) => {
+ if (this._longStatusTimeoutId) {
+ clearTimeout(this._longStatusTimeoutId);
+ }
+
+ this._longStatusTimeoutId = setTimeout(this._pollLongStatus, timeout);
+ };
+
+ // Poll Miner settings just in case
+ this._pollMinerSettings();
+
Promise
.all([
this._api.web3.clientVersion(),
- this._api.eth.coinbase(),
this._api.parity.defaultExtraData(),
- this._api.parity.extraData(),
- this._api.parity.gasFloorTarget(),
- this._api.eth.hashrate(),
- this._api.parity.minGasPrice(),
this._api.parity.netChain(),
- this._api.parity.netPeers(),
this._api.parity.netPort(),
- this._api.parity.nodeName(),
this._api.parity.rpcSettings(),
- this._api.eth.syncing()
+ this._api.parity.enode()
])
- .then(([clientVersion, coinbase, defaultExtraData, extraData, gasFloorTarget, hashrate, minGasPrice, netChain, netPeers, netPort, nodeName, rpcSettings, syncing, traceMode]) => {
+ .then(([
+ clientVersion, defaultExtraData, netChain, netPort, rpcSettings, enode
+ ]) => {
const isTest = netChain === 'morden' || netChain === 'testnet';
- this._store.dispatch(statusCollection({
+ const longStatus = {
clientVersion,
- coinbase,
defaultExtraData,
- extraData,
- gasFloorTarget,
- hashrate,
- minGasPrice,
netChain,
- netPeers,
netPort,
- nodeName,
rpcSettings,
- syncing,
- isTest,
- traceMode
- }));
+ enode,
+ isTest
+ };
+
+ if (!isEqual(longStatus, this._longStatus)) {
+ this._store.dispatch(statusCollection(longStatus));
+ this._longStatus = longStatus;
+ }
+
+ nextTimeout();
})
.catch((error) => {
- console.error('_pollStatus', error);
+ console.error('_pollLongStatus', error);
+ nextTimeout(250);
});
-
- nextTimeout();
}
_pollLogs = () => {
diff --git a/js/src/redux/providers/statusActions.js b/js/src/redux/providers/statusActions.js
index 142cdabdc..1c175c29d 100644
--- a/js/src/redux/providers/statusActions.js
+++ b/js/src/redux/providers/statusActions.js
@@ -47,3 +47,10 @@ export function clearStatusLogs () {
type: 'clearStatusLogs'
};
}
+
+export function toggleStatusRefresh (refreshStatus) {
+ return {
+ type: 'toggleStatusRefresh',
+ refreshStatus
+ };
+}
diff --git a/js/src/redux/providers/statusReducer.js b/js/src/redux/providers/statusReducer.js
index 98bb536ae..b0450083a 100644
--- a/js/src/redux/providers/statusReducer.js
+++ b/js/src/redux/providers/statusReducer.js
@@ -37,12 +37,13 @@ const initialState = {
max: new BigNumber(0)
},
netPort: new BigNumber(0),
- nodeName: '',
rpcSettings: {},
syncing: false,
- isApiConnected: true,
- isPingConnected: true,
+ isConnected: false,
+ isConnecting: false,
+ isPingable: false,
isTest: false,
+ refreshStatus: false,
traceMode: undefined
};
@@ -73,5 +74,10 @@ export default handleActions({
clearStatusLogs (state, action) {
return Object.assign({}, state, { devLogs: [] });
+ },
+
+ toggleStatusRefresh (state, action) {
+ const { refreshStatus } = action;
+ return Object.assign({}, state, { refreshStatus });
}
}, initialState);
diff --git a/js/src/views/Status/containers/StatusPage/StatusPage.js b/js/src/views/Status/containers/StatusPage/StatusPage.js
index afc7d60f7..617a6486a 100644
--- a/js/src/views/Status/containers/StatusPage/StatusPage.js
+++ b/js/src/views/Status/containers/StatusPage/StatusPage.js
@@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
-import { clearStatusLogs, toggleStatusLogs } from '../../../../redux/actions';
+import { clearStatusLogs, toggleStatusLogs, toggleStatusRefresh } from '../../../../redux/actions';
import Debug from '../../components/Debug';
import Status from '../../components/Status';
@@ -31,6 +31,14 @@ class StatusPage extends Component {
actions: PropTypes.object.isRequired
}
+ componentWillMount () {
+ this.props.actions.toggleStatusRefresh(true);
+ }
+
+ componentWillUnmount () {
+ this.props.actions.toggleStatusRefresh(false);
+ }
+
render () {
return (
@@ -49,7 +57,8 @@ function mapDispatchToProps (dispatch) {
return {
actions: bindActionCreators({
clearStatusLogs,
- toggleStatusLogs
+ toggleStatusLogs,
+ toggleStatusRefresh
}, dispatch)
};
}