303 lines
9.6 KiB
TypeScript
303 lines
9.6 KiB
TypeScript
import { delay, SagaIterator } from 'redux-saga';
|
||
import { call, fork, put, take, takeEvery, select, apply } from 'redux-saga/effects';
|
||
import { bindActionCreators } from 'redux';
|
||
import {
|
||
getNodeId,
|
||
getNodeConfig,
|
||
getOffline,
|
||
isStaticNodeId,
|
||
getCustomNodeFromId,
|
||
getStaticNodeFromId,
|
||
getNetworkConfigById,
|
||
getAllNodes
|
||
} from 'selectors/config';
|
||
import { TypeKeys } from 'actions/config/constants';
|
||
import {
|
||
setOnline,
|
||
setOffline,
|
||
changeNodeRequested,
|
||
changeNodeSucceeded,
|
||
changeNodeFailed,
|
||
changeNodeForce,
|
||
setLatestBlock,
|
||
AddCustomNodeAction,
|
||
ChangeNodeForceAction,
|
||
ChangeNodeRequestedAction,
|
||
ChangeNodeRequestedOneTimeAction,
|
||
ChangeNetworkRequestedAction,
|
||
RemoveCustomNodeAction
|
||
} from 'actions/config';
|
||
import { showNotification } from 'actions/notifications';
|
||
import { resetWallet } from 'actions/wallet';
|
||
import { translateRaw } from 'translations';
|
||
import { StaticNodeConfig, CustomNodeConfig, NodeConfig } from 'types/node';
|
||
import { CustomNetworkConfig, StaticNetworkConfig } from 'types/network';
|
||
import {
|
||
getShepherdOffline,
|
||
isAutoNode,
|
||
shepherd,
|
||
shepherdProvider,
|
||
stripWeb3Network,
|
||
makeProviderConfig,
|
||
getShepherdNetwork,
|
||
getShepherdPending,
|
||
makeAutoNodeName
|
||
} from 'libs/nodes';
|
||
import { INITIAL_STATE as selectedNodeInitialState } from 'reducers/config/nodes/selectedNode';
|
||
import { configuredStore as store } from 'store';
|
||
|
||
window.addEventListener('load', () => {
|
||
const getShepherdStatus = () => ({
|
||
pending: getShepherdPending(),
|
||
isOnline: !getShepherdOffline()
|
||
});
|
||
|
||
const { online, offline, lostNetworkNotif, offlineNotif, restoreNotif } = bindActionCreators(
|
||
{
|
||
offline: setOffline,
|
||
online: setOnline,
|
||
restoreNotif: () =>
|
||
showNotification('success', 'Your connection to the network has been restored!', 3000),
|
||
lostNetworkNotif: () =>
|
||
showNotification(
|
||
'danger',
|
||
`You’ve lost your connection to the network, check your internet
|
||
connection or try changing networks from the dropdown at the
|
||
top right of the page.`,
|
||
Infinity
|
||
),
|
||
|
||
offlineNotif: () =>
|
||
showNotification(
|
||
'info',
|
||
'You are currently offline. Some features will be unavailable.',
|
||
5000
|
||
)
|
||
},
|
||
store.dispatch
|
||
);
|
||
|
||
const getAppOnline = () => !getOffline(store.getState());
|
||
|
||
/**
|
||
* @description Repeatedly polls itself to check for online state conflict occurs, implemented in recursive style for flexible polling times
|
||
* as network requests take a variable amount of time.
|
||
*
|
||
* Whenever an app online state conflict occurs, it resolves the conflict with the following priority:
|
||
* * If shepherd is online but app is offline -> do a ping request via shepherd provider, with the result of the ping being the set app state
|
||
* * If shepherd is offline but app is online -> set app to offline as it wont be able to make requests anyway
|
||
*/
|
||
async function detectOnlineStateConflict() {
|
||
const shepherdStatus = getShepherdStatus();
|
||
const appOffline = getAppOnline();
|
||
const onlineStateConflict = shepherdStatus.isOnline !== appOffline;
|
||
|
||
if (shepherdStatus.pending || !onlineStateConflict) {
|
||
return setTimeout(detectOnlineStateConflict, 1000);
|
||
}
|
||
|
||
// if app reports online but shepherd offline, then set app offline
|
||
if (appOffline && !shepherdStatus.isOnline) {
|
||
lostNetworkNotif();
|
||
offline();
|
||
} else if (!appOffline && shepherdStatus.isOnline) {
|
||
// if app reports offline but shepherd reports online
|
||
// send a request to shepherd provider to see if we can still send out requests
|
||
const success = await shepherdProvider.ping().catch(() => false);
|
||
if (success) {
|
||
restoreNotif();
|
||
online();
|
||
}
|
||
}
|
||
detectOnlineStateConflict();
|
||
}
|
||
detectOnlineStateConflict();
|
||
|
||
window.addEventListener('offline', () => {
|
||
const previouslyOnline = getAppOnline();
|
||
|
||
// if browser reports as offline and we were previously online
|
||
// then set offline without checking balancer state
|
||
if (!navigator.onLine && previouslyOnline) {
|
||
offlineNotif();
|
||
offline();
|
||
}
|
||
});
|
||
});
|
||
|
||
// @HACK For now we reload the app when doing a language swap to force non-connected
|
||
// data to reload. Also the use of timeout to avoid using additional actions for now.
|
||
export function* reload(): SagaIterator {
|
||
setTimeout(() => location.reload(), 1150);
|
||
}
|
||
|
||
export function* handleChangeNodeRequestedOneTime(): SagaIterator {
|
||
const action: ChangeNodeRequestedOneTimeAction = yield take(
|
||
TypeKeys.CONFIG_CHANGE_NODE_REQUESTED_ONETIME
|
||
);
|
||
// allow shepherdProvider async init to complete. TODO - don't export shepherdProvider as promise
|
||
yield call(delay, 100);
|
||
yield put(changeNodeRequested(action.payload));
|
||
}
|
||
|
||
export function* handleChangeNodeRequested({
|
||
payload: nodeIdToSwitchTo
|
||
}: ChangeNodeRequestedAction): SagaIterator {
|
||
const isStaticNode: boolean = yield select(isStaticNodeId, nodeIdToSwitchTo);
|
||
const currentConfig: NodeConfig = yield select(getNodeConfig);
|
||
|
||
// Bail out if they're switching to the same node
|
||
if (currentConfig.id === nodeIdToSwitchTo) {
|
||
return;
|
||
}
|
||
|
||
function* bailOut(message: string) {
|
||
yield put(showNotification('danger', message, 5000));
|
||
yield put(changeNodeFailed());
|
||
}
|
||
|
||
let nextNodeConfig: CustomNodeConfig | StaticNodeConfig;
|
||
|
||
if (!isStaticNode) {
|
||
const config: CustomNodeConfig | undefined = yield select(
|
||
getCustomNodeFromId,
|
||
nodeIdToSwitchTo
|
||
);
|
||
|
||
if (config) {
|
||
nextNodeConfig = config;
|
||
} else {
|
||
return yield* bailOut(`Attempted to switch to unknown node '${nodeIdToSwitchTo}'`);
|
||
}
|
||
} else {
|
||
nextNodeConfig = yield select(getStaticNodeFromId, nodeIdToSwitchTo);
|
||
}
|
||
|
||
const nextNetwork: StaticNetworkConfig | CustomNetworkConfig = yield select(
|
||
getNetworkConfigById,
|
||
stripWeb3Network(nextNodeConfig.network)
|
||
);
|
||
|
||
if (!nextNetwork) {
|
||
return yield* bailOut(
|
||
`Unknown custom network for your node '${nodeIdToSwitchTo}', try re-adding it`
|
||
);
|
||
}
|
||
|
||
const isOffline = yield select(getOffline);
|
||
if (isAutoNode(nodeIdToSwitchTo)) {
|
||
shepherd.auto();
|
||
if (getShepherdNetwork() !== nextNodeConfig.network) {
|
||
yield apply(shepherd, shepherd.switchNetworks, [nextNodeConfig.network]);
|
||
}
|
||
} else {
|
||
try {
|
||
yield apply(shepherd, shepherd.manual, [nodeIdToSwitchTo, isOffline]);
|
||
} catch (err) {
|
||
console.error(err);
|
||
return yield* bailOut(translateRaw('ERROR_32'));
|
||
}
|
||
}
|
||
|
||
let currentBlock = '???';
|
||
try {
|
||
currentBlock = yield apply(shepherdProvider, shepherdProvider.getCurrentBlock);
|
||
} catch (err) {
|
||
if (!isOffline) {
|
||
console.error(err);
|
||
return yield* bailOut(translateRaw('ERROR_32'));
|
||
}
|
||
}
|
||
|
||
yield put(setLatestBlock(currentBlock));
|
||
yield put(changeNodeSucceeded({ networkId: nextNodeConfig.network, nodeId: nodeIdToSwitchTo }));
|
||
|
||
if (currentConfig.network !== nextNodeConfig.network) {
|
||
yield fork(handleNewNetwork);
|
||
}
|
||
}
|
||
|
||
export function* handleAddCustomNode(action: AddCustomNodeAction): SagaIterator {
|
||
const config = action.payload;
|
||
shepherd.useProvider(
|
||
'myccustom',
|
||
config.id,
|
||
makeProviderConfig({ network: config.network }),
|
||
config
|
||
);
|
||
yield put(changeNodeRequested(config.id));
|
||
}
|
||
|
||
export function* handleNewNetwork() {
|
||
yield put(resetWallet());
|
||
}
|
||
|
||
export function* handleNodeChangeForce({ payload: staticNodeIdToSwitchTo }: ChangeNodeForceAction) {
|
||
// does not perform node online check before changing nodes
|
||
// necessary when switching back from Web3 provider so node
|
||
// dropdown does not get stuck if node is offline
|
||
|
||
const isStaticNode: boolean = yield select(isStaticNodeId, staticNodeIdToSwitchTo);
|
||
|
||
if (!isStaticNode) {
|
||
return;
|
||
}
|
||
|
||
const nodeConfig = yield select(getStaticNodeFromId, staticNodeIdToSwitchTo);
|
||
|
||
// force the node change
|
||
yield put(changeNodeSucceeded({ networkId: nodeConfig.network, nodeId: staticNodeIdToSwitchTo }));
|
||
|
||
// also put the change through as usual so status check and
|
||
// error messages occur if the node is unavailable
|
||
yield put(changeNodeRequested(staticNodeIdToSwitchTo));
|
||
}
|
||
|
||
export function* handleChangeNetworkRequested({ payload: network }: ChangeNetworkRequestedAction) {
|
||
let desiredNode = '';
|
||
const autoNodeName = makeAutoNodeName(network);
|
||
const isStaticNode: boolean = yield select(isStaticNodeId, autoNodeName);
|
||
|
||
if (isStaticNode) {
|
||
desiredNode = autoNodeName;
|
||
} else {
|
||
const allNodes: { [id: string]: NodeConfig } = yield select(getAllNodes);
|
||
const networkNode = Object.values(allNodes).find(n => n.network === network);
|
||
if (networkNode) {
|
||
desiredNode = networkNode.id;
|
||
}
|
||
}
|
||
|
||
if (desiredNode) {
|
||
yield put(changeNodeRequested(desiredNode));
|
||
} else {
|
||
yield put(
|
||
showNotification(
|
||
'danger',
|
||
translateRaw('NETWORK_UNKNOWN_ERROR', {
|
||
$network: network
|
||
}),
|
||
5000
|
||
)
|
||
);
|
||
}
|
||
}
|
||
|
||
export function* handleRemoveCustomNode({ payload: nodeId }: RemoveCustomNodeAction): SagaIterator {
|
||
// If custom node is currently selected, go back to default node
|
||
const currentNodeId = yield select(getNodeId);
|
||
if (nodeId === currentNodeId) {
|
||
yield put(changeNodeForce(selectedNodeInitialState.nodeId));
|
||
}
|
||
}
|
||
|
||
export const node = [
|
||
fork(handleChangeNodeRequestedOneTime),
|
||
takeEvery(TypeKeys.CONFIG_CHANGE_NODE_REQUESTED, handleChangeNodeRequested),
|
||
takeEvery(TypeKeys.CONFIG_CHANGE_NODE_FORCE, handleNodeChangeForce),
|
||
takeEvery(TypeKeys.CONFIG_CHANGE_NETWORK_REQUESTED, handleChangeNetworkRequested),
|
||
takeEvery(TypeKeys.CONFIG_LANGUAGE_CHANGE, reload),
|
||
takeEvery(TypeKeys.CONFIG_ADD_CUSTOM_NODE, handleAddCustomNode),
|
||
takeEvery(TypeKeys.CONFIG_REMOVE_CUSTOM_NODE, handleRemoveCustomNode)
|
||
];
|