diff --git a/common/actions/config/actionCreators.ts b/common/actions/config/actionCreators.ts index 757ee34c..5c18d5b9 100644 --- a/common/actions/config/actionCreators.ts +++ b/common/actions/config/actionCreators.ts @@ -53,6 +53,14 @@ export function changeNodeIntent(payload: string): interfaces.ChangeNodeIntentAc }; } +export type TChangeNodeIntentOneTime = typeof changeNodeIntentOneTime; +export function changeNodeIntentOneTime(payload: string): interfaces.ChangeNodeIntentOneTimeAction { + return { + type: TypeKeys.CONFIG_NODE_CHANGE_INTENT_ONETIME, + payload + }; +} + export type TChangeNodeForce = typeof changeNodeForce; export function changeNodeForce(payload: string): interfaces.ChangeNodeForceAction { return { diff --git a/common/actions/config/actionTypes.ts b/common/actions/config/actionTypes.ts index eb3e3597..0b8a57ba 100644 --- a/common/actions/config/actionTypes.ts +++ b/common/actions/config/actionTypes.ts @@ -39,6 +39,13 @@ export interface ChangeNodeIntentAction { type: TypeKeys.CONFIG_NODE_CHANGE_INTENT; payload: string; } + +/*** Change Node Onetime ***/ +export interface ChangeNodeIntentOneTimeAction { + type: TypeKeys.CONFIG_NODE_CHANGE_INTENT_ONETIME; + payload: string; +} + /*** Force Change Node ***/ export interface ChangeNodeForceAction { type: TypeKeys.CONFIG_NODE_CHANGE_FORCE; diff --git a/common/actions/config/constants.ts b/common/actions/config/constants.ts index cc985e95..b2c2d421 100644 --- a/common/actions/config/constants.ts +++ b/common/actions/config/constants.ts @@ -13,6 +13,7 @@ export enum TypeKeys { CONFIG_NODE_WEB3_UNSET = 'CONFIG_NODE_WEB3_UNSET', CONFIG_NODE_CHANGE = 'CONFIG_NODE_CHANGE', CONFIG_NODE_CHANGE_INTENT = 'CONFIG_NODE_CHANGE_INTENT', + CONFIG_NODE_CHANGE_INTENT_ONETIME = 'CONFIG_NODE_CHANGE_INTENT_ONETIME', CONFIG_NODE_CHANGE_FORCE = 'CONFIG_NODE_CHANGE_FORCE', CONFIG_ADD_CUSTOM_NODE = 'CONFIG_ADD_CUSTOM_NODE', diff --git a/common/components/Header/index.tsx b/common/components/Header/index.tsx index 50fb5a39..85cddc9c 100644 --- a/common/components/Header/index.tsx +++ b/common/components/Header/index.tsx @@ -1,12 +1,14 @@ import { TChangeLanguage, TChangeNodeIntent, + TChangeNodeIntentOneTime, TAddCustomNode, TRemoveCustomNode, TAddCustomNetwork, AddCustomNodeAction, changeLanguage, changeNodeIntent, + changeNodeIntentOneTime, addCustomNode, removeCustomNode, addCustomNetwork @@ -34,15 +36,21 @@ import { CustomNodeOption, NodeOption, getNodeOptions, - getNetworkConfig + getNetworkConfig, + isStaticNodeId } from 'selectors/config'; import { NetworkConfig } from 'types/network'; -import { connect } from 'react-redux'; +import { connect, MapStateToProps } from 'react-redux'; import { stripWeb3Network } from 'libs/nodes'; +interface OwnProps { + networkParam: string | null; +} + interface DispatchProps { changeLanguage: TChangeLanguage; changeNodeIntent: TChangeNodeIntent; + changeNodeIntentOneTime: TChangeNodeIntentOneTime; setGasPriceField: TSetGasPriceField; addCustomNode: TAddCustomNode; removeCustomNode: TRemoveCustomNode; @@ -50,6 +58,7 @@ interface DispatchProps { } interface StateProps { + shouldSetNodeFromQS: boolean; network: NetworkConfig; languageSelection: AppState['config']['meta']['languageSelection']; node: NodeConfig; @@ -59,7 +68,11 @@ interface StateProps { nodeOptions: (CustomNodeOption | NodeOption)[]; } -const mapStateToProps = (state: AppState): StateProps => ({ +const mapStateToProps: MapStateToProps = ( + state, + { networkParam } +): StateProps => ({ + shouldSetNodeFromQS: !!(networkParam && isStaticNodeId(state, networkParam)), isOffline: getOffline(state), isChangingNode: isNodeChanging(state), languageSelection: getLanguageSelection(state), @@ -73,6 +86,7 @@ const mapDispatchToProps: DispatchProps = { setGasPriceField, changeLanguage, changeNodeIntent, + changeNodeIntentOneTime, addCustomNode, removeCustomNode, addCustomNetwork @@ -82,13 +96,17 @@ interface State { isAddingCustomNode: boolean; } -type Props = StateProps & DispatchProps; +type Props = OwnProps & StateProps & DispatchProps; class Header extends Component { public state = { isAddingCustomNode: false }; + public componentDidMount() { + this.attemptSetNodeFromQueryParameter(); + } + public render() { const { languageSelection, @@ -220,6 +238,13 @@ class Header extends Component { this.setState({ isAddingCustomNode: false }); this.props.addCustomNode(payload); }; + + private attemptSetNodeFromQueryParameter() { + const { shouldSetNodeFromQS, networkParam } = this.props; + if (shouldSetNodeFromQS) { + this.props.changeNodeIntentOneTime(networkParam!); + } + } } export default connect(mapStateToProps, mapDispatchToProps)(Header); diff --git a/common/components/renderCbs/Query.tsx b/common/components/renderCbs/Query.tsx index 9762a35f..9f98b519 100644 --- a/common/components/renderCbs/Query.tsx +++ b/common/components/renderCbs/Query.tsx @@ -13,7 +13,15 @@ interface IQueryResults { [key: string]: string | null; } -export type Param = 'to' | 'data' | 'readOnly' | 'tokenSymbol' | 'value' | 'gaslimit' | 'limit'; +export type Param = + | 'to' + | 'data' + | 'readOnly' + | 'tokenSymbol' + | 'value' + | 'gaslimit' + | 'limit' + | 'network'; interface Props extends RouteComponentProps<{}> { params: Param[]; diff --git a/common/containers/TabSection/index.tsx b/common/containers/TabSection/index.tsx index 198778dd..1f631396 100644 --- a/common/containers/TabSection/index.tsx +++ b/common/containers/TabSection/index.tsx @@ -5,6 +5,7 @@ import { AppState } from 'reducers'; import Notifications from './Notifications'; import OfflineTab from './OfflineTab'; import { getOffline, getLatestBlock } from 'selectors/config'; +import { Query } from 'components/renderCbs'; interface StateProps { isOffline: AppState['config']['meta']['offline']; @@ -24,7 +25,12 @@ class TabSection extends Component { return (
-
+ ( +
+ )} + />
{isUnavailableOffline && isOffline ? : children}
diff --git a/common/sagas/config/node.ts b/common/sagas/config/node.ts index 1e79e769..b0198631 100644 --- a/common/sagas/config/node.ts +++ b/common/sagas/config/node.ts @@ -28,7 +28,8 @@ import { setLatestBlock, AddCustomNodeAction, ChangeNodeForceAction, - ChangeNodeIntentAction + ChangeNodeIntentAction, + ChangeNodeIntentOneTimeAction } from 'actions/config'; import { showNotification } from 'actions/notifications'; import { resetWallet } from 'actions/wallet'; @@ -103,6 +104,15 @@ export function* reload(): SagaIterator { setTimeout(() => location.reload(), 1150); } +export function* handleNodeChangeIntentOneTime(): SagaIterator { + const action: ChangeNodeIntentOneTimeAction = yield take( + TypeKeys.CONFIG_NODE_CHANGE_INTENT_ONETIME + ); + // allow shepherdProvider async init to complete. TODO - don't export shepherdProvider as promise + yield call(delay, 100); + yield put(changeNodeIntent(action.payload)); +} + export function* handleNodeChangeIntent({ payload: nodeIdToSwitchTo }: ChangeNodeIntentAction): SagaIterator { @@ -210,6 +220,7 @@ export function* handleNodeChangeForce({ payload: staticNodeIdToSwitchTo }: Chan } export const node = [ + fork(handleNodeChangeIntentOneTime), takeEvery(TypeKeys.CONFIG_NODE_CHANGE_INTENT, handleNodeChangeIntent), takeEvery(TypeKeys.CONFIG_NODE_CHANGE_FORCE, handleNodeChangeForce), takeLatest(TypeKeys.CONFIG_POLL_OFFLINE_STATUS, handlePollOfflineStatus), diff --git a/spec/reducers/config/config.spec.ts b/spec/reducers/config/config.spec.ts index 01c0eea8..07b74d05 100644 --- a/spec/reducers/config/config.spec.ts +++ b/spec/reducers/config/config.spec.ts @@ -8,13 +8,17 @@ import { changeNode, changeNodeIntent, changeNodeForce, - setLatestBlock + setLatestBlock, + TypeKeys, + ChangeNodeIntentOneTimeAction, + changeNodeIntentOneTime } from 'actions/config'; import { handleNodeChangeIntent, handlePollOfflineStatus, pollOfflineStatus, - handleNewNetwork + handleNewNetwork, + handleNodeChangeIntentOneTime } from 'sagas/config/node'; import { getNodeId, @@ -243,6 +247,23 @@ describe('handleNodeChangeIntent*', () => { }); }); +describe('handleNodeChangeIntentOneTime', () => { + const saga = handleNodeChangeIntentOneTime(); + const action: ChangeNodeIntentOneTimeAction = changeNodeIntentOneTime('eth_auto'); + it('should take a one time action based on the url containing a valid network to switch to', () => { + expect(saga.next().value).toEqual(take(TypeKeys.CONFIG_NODE_CHANGE_INTENT_ONETIME)); + }); + it(`should delay for 10 ms to allow shepherdProvider async init to complete`, () => { + expect(saga.next(action).value).toEqual(call(delay, 100)); + }); + it('should dispatch the change node intent', () => { + expect(saga.next().value).toEqual(put(changeNodeIntent(action.payload))); + }); + it('should be done', () => { + expect(saga.next().done).toEqual(true); + }); +}); + describe('unsetWeb3Node*', () => { const previousNodeId = 'eth_mycrypto'; const mockNodeId = 'web3';