Newer
Older
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
//
// SPDX-License-Identifier: BSD-3-Clause
import BigNumber from 'bignumber.js';
import { combineLatest, interval, Observable, fromEvent, merge } from 'rxjs';
import { compose, mapPropsStream } from 'recompose';
distinctUntilChanged,
filter,
map,
publishReplay,
startWith,
switchMap,
take
} from 'rxjs/operators';
import isElectron from 'is-electron';
import isEqual from 'lodash/isEqual';
import { peerCount$, syncStatus$, withoutLoading } from '@parity/light.js';
import parityStore from '../stores/parityStore';
const electron = isElectron() ? window.require('electron') : null;
// List here all possible states of our health store. Each state can have a
// payload.
export const STATUS = {
CLOCKNOTSYNC: Symbol('CLOCKNOTSYNC'), // Local clock is not sync
DOWNLOADING: Symbol('DOWNLOADING'), // Currently downloading Parity
GOOD: Symbol('GOOD'), // Everything's fine
NOINTERNET: Symbol('NOINTERNET'), // No Internet connection
NOPEERS: Symbol('NOPEERS'), // Not connected to any peers
LAUNCHING: Symbol('LAUNCHING'), // Parity is being launched (only happens at startup)
SYNCING: Symbol('SYNCING') // Obvious
};
const isApiConnected$ = parityStore.isApiConnected$;
const isParityRunning$ = Observable.create(observer => {
if (electron) {
electron.ipcRenderer.on('parity-running', (_, isParityRunning) => {
observer.next(isParityRunning);
});
}
}).pipe(
startWith(electron ? !!electron.remote.getGlobal('isParityRunning') : false)
);
const downloadProgress$ = Observable.create(observer => {
if (electron) {
electron.ipcRenderer.on('parity-download-progress', (_, progress) => {
observer.next(progress);
});
}
const isClockSync$ = Observable.create(observer => {
if (electron) {
electron.ipcRenderer.send('asynchronous-message', 'check-clock-sync');
electron.ipcRenderer.once('check-clock-sync-reply', (_, clockSync) => {
observer.next(clockSync.isClockSync);
});
}
const online$ = merge(
fromEvent(window, 'online').pipe(map(() => true)),
fromEvent(window, 'offline').pipe(map(() => false))
// Subscribe to the RPCs only once we set a provider
const rpcs$ = isApiConnected$.pipe(
filter(isApiConnected => isApiConnected),
take(1),
switchMap(() =>
combineLatest(
syncStatus$()
.pipe(
map(syncStatus => {
if (!syncStatus) {
return {
isSync: true
};
}
const { currentBlock, highestBlock, startingBlock } = syncStatus;
const percentage = currentBlock
.minus(startingBlock)
.multipliedBy(100)
.div(highestBlock.minus(startingBlock));
return {
isSync: false,
syncPayload: {
currentBlock,
highestBlock,
percentage,
startingBlock
}
};
})
// Emit "not synced" only if we haven't been synced for over 2 seconds
)
.pipe(audit(syncStatus => interval(syncStatus.isSync ? 0 : 2000))),
peerCount$().pipe(withoutLoading())
)
),
startWith([{ isSync: false }, undefined]), // Don't stall the HOC's combineLatest; emit immediately
publishReplay(1)
);
rpcs$.connect();
// Inject node health information as health.{status, payload} props
export default compose(
mapPropsStream(props$ =>
combineLatest(props$, combined$, rpcs$).pipe(
[
isParityRunning,
isApiConnected,
downloadProgress,
online,
isClockSync
],
[{ isSync, syncPayload }, peerCount]
// No connexion to the internet
if (!online) {
return {
...props,
health: {
status: STATUS.NOINTERNET
}
};
}
// Parity is being downloaded
if (downloadProgress > 0 && !isParityRunning) {
return {
...props,
health: {
status: STATUS.DOWNLOADING,
payload: {
percentage: new BigNumber(Math.round(downloadProgress * 100))
}
}
};
}
// Parity is being launched
if (!isApiConnected) {
return {
...props,
health: {
status: STATUS.LAUNCHING
}
};
}
// At this point we have a successful connection to parity
// Clock is not synchronized
if (!isClockSync) {
return {
...props,
health: {
status: STATUS.CLOCKNOTSYNC
}
};
}
// Not enough peers
if (peerCount === undefined || peerCount.lte(1)) {
return {
...props,
health: {
return {
...props,
health: {
status: STATUS.SYNCING,
payload: syncPayload
}
};
}
// Everything's OK
return {
...props,
health: {
status: STATUS.GOOD
}
};
}
),
distinctUntilChanged(isEqual) // Perform deep comparison