1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Fix #18984 - UX: Multichain - Network connection fixes (#19000)

This commit is contained in:
David Walsh 2023-05-05 09:02:28 -05:00 committed by GitHub
parent 33f9d6f480
commit 67dbac6b0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 54 deletions

View File

@ -2346,6 +2346,10 @@
"message": "Gas fees are $1 relative to the past 72 hours.", "message": "Gas fees are $1 relative to the past 72 hours.",
"description": "$1 is networks stability value - stable, low, high" "description": "$1 is networks stability value - stable, low, high"
}, },
"networkSwitchConnectionError": {
"message": "We can't connect to $1",
"description": "$1 represents the network name"
},
"networkURL": { "networkURL": {
"message": "Network URL" "message": "Network URL"
}, },

View File

@ -1,9 +1,24 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Button from '../../ui/button';
import LoadingScreen from '../../ui/loading-screen'; import LoadingScreen from '../../ui/loading-screen';
import { SECOND } from '../../../../shared/constants/time'; import { SECOND } from '../../../../shared/constants/time';
import { NETWORK_TYPES } from '../../../../shared/constants/network'; import { NETWORK_TYPES } from '../../../../shared/constants/network';
import Popover from '../../ui/popover/popover.component';
import {
ButtonPrimary,
ButtonSecondary,
Icon,
IconName,
IconSize,
Text,
} from '../../component-library';
import {
DISPLAY,
IconColor,
TextAlign,
TextVariant,
} from '../../../helpers/constants/design-system';
import Box from '../../ui/box/box';
export default class LoadingNetworkScreen extends PureComponent { export default class LoadingNetworkScreen extends PureComponent {
state = { state = {
@ -56,63 +71,76 @@ export default class LoadingNetworkScreen extends PureComponent {
} }
}; };
renderDeprecatedRpcUrlWarning = () => { renderConnectionFailureNotification = (message, showTryAgain = false) => {
const { showNetworkDropdown } = this.props;
return (
<div className="loading-overlay__error-screen">
<span className="loading-overlay__emoji">&#128542;</span>
<span>{this.context.t('currentRpcUrlDeprecated')}</span>
<div className="loading-overlay__error-buttons">
<Button
type="secondary"
onClick={() => {
window.clearTimeout(this.cancelCallTimeout);
showNetworkDropdown();
}}
>
{this.context.t('switchNetworks')}
</Button>
</div>
</div>
);
};
renderErrorScreenContent = () => {
const { showNetworkDropdown, setProviderArgs, setProviderType } = const { showNetworkDropdown, setProviderArgs, setProviderType } =
this.props; this.props;
return ( return (
<div className="loading-overlay__error-screen"> <Popover
<span className="loading-overlay__emoji">&#128542;</span> onClose={() => {
<span>{this.context.t('somethingWentWrong')}</span> window.clearTimeout(this.cancelCallTimeout);
<div className="loading-overlay__error-buttons"> }}
<Button centerTitle
type="secondary" title={
<Icon
name={IconName.Danger}
size={IconSize.Xl}
color={IconColor.warningDefault}
/>
}
>
<Text
variant={TextVariant.bodyLgMedium}
textAlign={TextAlign.Center}
margin={[0, 4, 4, 4]}
>
{message}
</Text>
<Box display={DISPLAY.FLEX} padding={4} gap={2}>
<ButtonSecondary
onClick={() => { onClick={() => {
window.clearTimeout(this.cancelCallTimeout); window.clearTimeout(this.cancelCallTimeout);
showNetworkDropdown(); showNetworkDropdown();
}} }}
variant={TextVariant.bodySm}
block
> >
{this.context.t('switchNetworks')} {this.context.t('switchNetworks')}
</Button> </ButtonSecondary>
{showTryAgain ? (
<ButtonPrimary
onClick={() => {
this.setState({ showErrorScreen: false });
setProviderType(...setProviderArgs);
window.clearTimeout(this.cancelCallTimeout);
this.cancelCallTimeout = setTimeout(
this.cancelCall,
this.props.cancelTime || SECOND * 15,
);
}}
variant={TextVariant.bodySm}
block
>
{this.context.t('tryAgain')}
</ButtonPrimary>
) : null}
</Box>
</Popover>
);
};
<Button renderDeprecatedRpcUrlWarning = () => {
type="primary" return this.renderConnectionFailureNotification(
onClick={() => { this.context.t('currentRpcUrlDeprecated'),
this.setState({ showErrorScreen: false }); false,
setProviderType(...setProviderArgs); );
window.clearTimeout(this.cancelCallTimeout); };
this.cancelCallTimeout = setTimeout(
this.cancelCall, renderErrorScreenContent = () => {
this.props.cancelTime || SECOND * 15, const { providerConfig } = this.props;
); return this.renderConnectionFailureNotification(
}} this.context.t('networkSwitchConnectionError', [providerConfig.nickname]),
> true,
{this.context.t('tryAgain')}
</Button>
</div>
</div>
); );
}; };

View File

@ -1,7 +1,11 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { NETWORK_TYPES } from '../../../../shared/constants/network'; import { NETWORK_TYPES } from '../../../../shared/constants/network';
import * as actions from '../../../store/actions'; import * as actions from '../../../store/actions';
import { getNetworkIdentifier, isNetworkLoading } from '../../../selectors'; import {
getAllEnabledNetworks,
getNetworkIdentifier,
isNetworkLoading,
} from '../../../selectors';
import { getProviderConfig } from '../../../ducks/metamask/metamask'; import { getProviderConfig } from '../../../ducks/metamask/metamask';
import LoadingNetworkScreen from './loading-network-screen.component'; import LoadingNetworkScreen from './loading-network-screen.component';
@ -21,11 +25,27 @@ const mapStateToProps = (state) => {
const isInfuraRpcUrl = rpcUrl && new URL(rpcUrl).host.endsWith('.infura.io'); const isInfuraRpcUrl = rpcUrl && new URL(rpcUrl).host.endsWith('.infura.io');
const showDeprecatedRpcUrlWarning = isDeprecatedNetwork && isInfuraRpcUrl; const showDeprecatedRpcUrlWarning = isDeprecatedNetwork && isInfuraRpcUrl;
// Ensure we have a nickname to provide the user
// in case of connection error
let networkName = nickname;
if (networkName === undefined) {
const networks = getAllEnabledNetworks(state);
const desiredNetwork = networks.find(
(network) => network.chainId === chainId,
);
if (desiredNetwork) {
networkName = desiredNetwork.nickname;
}
}
return { return {
isNetworkLoading: isNetworkLoading(state), isNetworkLoading: isNetworkLoading(state),
loadingMessage, loadingMessage,
setProviderArgs, setProviderArgs,
providerConfig, providerConfig: {
...providerConfig,
nickname: networkName,
},
providerId: getNetworkIdentifier(state), providerId: getNetworkIdentifier(state),
showDeprecatedRpcUrlWarning, showDeprecatedRpcUrlWarning,
}; };
@ -38,7 +58,12 @@ const mapDispatchToProps = (dispatch) => {
}, },
rollbackToPreviousProvider: () => rollbackToPreviousProvider: () =>
dispatch(actions.rollbackToPreviousProvider()), dispatch(actions.rollbackToPreviousProvider()),
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()), showNetworkDropdown: () => {
if (process.env.MULTICHAIN) {
return dispatch(actions.toggleNetworkMenu());
}
return dispatch(actions.showNetworkDropdown());
},
}; };
}; };

View File

@ -11,12 +11,14 @@ import {
setShowTestNetworks, setShowTestNetworks,
setProviderType, setProviderType,
toggleNetworkMenu, toggleNetworkMenu,
upsertNetworkConfiguration,
} from '../../../store/actions'; } from '../../../store/actions';
import { CHAIN_IDS, TEST_CHAINS } from '../../../../shared/constants/network'; import { CHAIN_IDS, TEST_CHAINS } from '../../../../shared/constants/network';
import { import {
getShowTestNetworks, getShowTestNetworks,
getAllEnabledNetworks, getAllEnabledNetworks,
getCurrentChainId, getCurrentChainId,
getNetworkConfigurations,
} from '../../../selectors'; } from '../../../selectors';
import Box from '../../ui/box/box'; import Box from '../../ui/box/box';
import ToggleButton from '../../ui/toggle-button'; import ToggleButton from '../../ui/toggle-button';
@ -32,6 +34,7 @@ import { MetaMetricsContext } from '../../../contexts/metametrics';
import { import {
MetaMetricsEventCategory, MetaMetricsEventCategory,
MetaMetricsEventName, MetaMetricsEventName,
MetaMetricsNetworkEventSource,
} from '../../../../shared/constants/metametrics'; } from '../../../../shared/constants/metametrics';
const UNREMOVABLE_CHAIN_IDS = [CHAIN_IDS.MAINNET, ...TEST_CHAINS]; const UNREMOVABLE_CHAIN_IDS = [CHAIN_IDS.MAINNET, ...TEST_CHAINS];
@ -40,6 +43,7 @@ export const NetworkListMenu = ({ onClose }) => {
const t = useI18nContext(); const t = useI18nContext();
const networks = useSelector(getAllEnabledNetworks); const networks = useSelector(getAllEnabledNetworks);
const showTestNetworks = useSelector(getShowTestNetworks); const showTestNetworks = useSelector(getShowTestNetworks);
const networkConfigurations = useSelector(getNetworkConfigurations);
const currentChainId = useSelector(getCurrentChainId); const currentChainId = useSelector(getCurrentChainId);
const dispatch = useDispatch(); const dispatch = useDispatch();
const history = useHistory(); const history = useHistory();
@ -75,12 +79,34 @@ export const NetworkListMenu = ({ onClose }) => {
iconSrc={network?.rpcPrefs?.imageUrl} iconSrc={network?.rpcPrefs?.imageUrl}
key={network.id || network.chainId} key={network.id || network.chainId}
selected={isCurrentNetwork} selected={isCurrentNetwork}
onClick={() => { onClick={async () => {
dispatch(toggleNetworkMenu()); dispatch(toggleNetworkMenu());
if (network.providerType) { if (network.providerType) {
dispatch(setProviderType(network.providerType)); dispatch(setProviderType(network.providerType));
} else { } else {
dispatch(setActiveNetwork(network.id)); // Linea needs to be added as a custom network because
// it is not yet supported by Infura. The following lazily
// adds Linea to the custom network configurations object
let networkId = network.id;
if (network.chainId === CHAIN_IDS.LINEA_TESTNET) {
const lineaNetworkConfiguration = Object.values(
networkConfigurations,
).find(
({ chainId }) => chainId === CHAIN_IDS.LINEA_TESTNET,
);
if (lineaNetworkConfiguration) {
networkId = lineaNetworkConfiguration.id;
} else {
networkId = await dispatch(
upsertNetworkConfiguration(network, {
setActive: true,
source:
MetaMetricsNetworkEventSource.CustomNetworkForm,
}),
);
}
}
dispatch(setActiveNetwork(networkId));
} }
trackEvent({ trackEvent({
event: MetaMetricsEventName.NavNetworkSwitched, event: MetaMetricsEventName.NavNetworkSwitched,

View File

@ -29,6 +29,8 @@ import {
GOERLI_DISPLAY_NAME, GOERLI_DISPLAY_NAME,
ETH_TOKEN_IMAGE_URL, ETH_TOKEN_IMAGE_URL,
LINEA_TESTNET_DISPLAY_NAME, LINEA_TESTNET_DISPLAY_NAME,
CURRENCY_SYMBOLS,
TEST_NETWORK_TICKER_MAP,
} from '../../shared/constants/network'; } from '../../shared/constants/network';
import { import {
WebHIDConnectedStatuses, WebHIDConnectedStatuses,
@ -1180,13 +1182,18 @@ export function getAllNetworks(state) {
imageUrl: ETH_TOKEN_IMAGE_URL, imageUrl: ETH_TOKEN_IMAGE_URL,
}, },
providerType: NETWORK_TYPES.MAINNET, providerType: NETWORK_TYPES.MAINNET,
ticker: CURRENCY_SYMBOLS.ETH,
}); });
// Custom networks added // Custom networks added
networks.push( networks.push(
...Object.entries(networkConfigurations) ...Object.entries(networkConfigurations)
.filter( .filter(
([, network]) => ([, network]) =>
!localhostFilter(network) && network.chainId !== CHAIN_IDS.MAINNET, !localhostFilter(network) &&
network.chainId !== CHAIN_IDS.MAINNET &&
// Linea gets added as a custom network configuration so
// we must ignore it here to display in test networks
network.chainId !== CHAIN_IDS.LINEA_TESTNET,
) )
.map(([, network]) => network), .map(([, network]) => network),
); );
@ -1198,18 +1205,20 @@ export function getAllNetworks(state) {
nickname: GOERLI_DISPLAY_NAME, nickname: GOERLI_DISPLAY_NAME,
rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.GOERLI], rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.GOERLI],
providerType: NETWORK_TYPES.GOERLI, providerType: NETWORK_TYPES.GOERLI,
ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.GOERLI],
}, },
{ {
chainId: CHAIN_IDS.SEPOLIA, chainId: CHAIN_IDS.SEPOLIA,
nickname: SEPOLIA_DISPLAY_NAME, nickname: SEPOLIA_DISPLAY_NAME,
rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.SEPOLIA], rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.SEPOLIA],
providerType: NETWORK_TYPES.SEPOLIA, providerType: NETWORK_TYPES.SEPOLIA,
ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.SEPOLIA],
}, },
{ {
chainId: CHAIN_IDS.LINEA_TESTNET, chainId: CHAIN_IDS.LINEA_TESTNET,
nickname: LINEA_TESTNET_DISPLAY_NAME, nickname: LINEA_TESTNET_DISPLAY_NAME,
rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_TESTNET], rpcUrl: CHAIN_ID_TO_RPC_URL_MAP[CHAIN_IDS.LINEA_TESTNET],
provderType: NETWORK_TYPES.LINEA_TESTNET, ticker: TEST_NETWORK_TICKER_MAP[NETWORK_TYPES.LINEA_TESTNET],
}, },
], // Localhosts ], // Localhosts
...Object.entries(networkConfigurations) ...Object.entries(networkConfigurations)