mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-28 23:06:37 +01:00
22f931e6b2
* Prevent automatic rejection of confirmations Confirmations are now only automatically rejected if a user explicitly closes the notification window. If we close the window programmatically because there are no notifications left to show, nothing gets rejected. This partially avoids a race condition where a confirmation gets rejected automatically without the user having seen the confirmation first. This could happen if the confirmation was processed just as the notification window was being closed. It's still possible for a confirmation that the user has never seen to get rejected as a result of the user closing the window. But at least now it's no longer possible for a confirmation to get rejected in this manner after the user resolves the last confirmation in the queue. * Fix bug that prevented automatic closure detection All windows were being detected as explicit window closures, essentially just as they were previously, because this variable was cleared too soon. * Re-open popup when necessary After the window is automatically closed, a confirmation may have been queued up while the window was closing. If so, the popup is now re- opened.
544 lines
18 KiB
JavaScript
544 lines
18 KiB
JavaScript
import React, { PureComponent } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import { Redirect, Route } from 'react-router-dom';
|
|
import { formatDate } from '../../helpers/utils/util';
|
|
import AssetList from '../../components/app/asset-list';
|
|
import CollectiblesTab from '../../components/app/collectibles-tab';
|
|
import HomeNotification from '../../components/app/home-notification';
|
|
import MultipleNotifications from '../../components/app/multiple-notifications';
|
|
import TransactionList from '../../components/app/transaction-list';
|
|
import MenuBar from '../../components/app/menu-bar';
|
|
import Popover from '../../components/ui/popover';
|
|
import Button from '../../components/ui/button';
|
|
import ConnectedSites from '../connected-sites';
|
|
import ConnectedAccounts from '../connected-accounts';
|
|
import { Tabs, Tab } from '../../components/ui/tabs';
|
|
import { EthOverview } from '../../components/app/wallet-overview';
|
|
import WhatsNewPopup from '../../components/app/whats-new-popup';
|
|
import RecoveryPhraseReminder from '../../components/app/recovery-phrase-reminder';
|
|
import ActionableMessage from '../../components/ui/actionable-message/actionable-message';
|
|
import Typography from '../../components/ui/typography/typography';
|
|
import { TYPOGRAPHY, FONT_WEIGHT } from '../../helpers/constants/design-system';
|
|
|
|
import { isBeta } from '../../helpers/utils/build-types';
|
|
|
|
import {
|
|
ASSET_ROUTE,
|
|
RESTORE_VAULT_ROUTE,
|
|
CONFIRM_TRANSACTION_ROUTE,
|
|
CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE,
|
|
INITIALIZE_BACKUP_SEED_PHRASE_ROUTE,
|
|
CONNECT_ROUTE,
|
|
CONNECTED_ROUTE,
|
|
CONNECTED_ACCOUNTS_ROUTE,
|
|
AWAITING_SWAP_ROUTE,
|
|
BUILD_QUOTE_ROUTE,
|
|
VIEW_QUOTE_ROUTE,
|
|
CONFIRMATION_V_NEXT_ROUTE,
|
|
ADD_COLLECTIBLE_ROUTE,
|
|
} from '../../helpers/constants/routes';
|
|
import BetaHomeFooter from './beta-home-footer.component';
|
|
|
|
const LEARN_MORE_URL =
|
|
'https://metamask.zendesk.com/hc/en-us/articles/360045129011-Intro-to-MetaMask-v8-extension';
|
|
const LEGACY_WEB3_URL =
|
|
'https://metamask.zendesk.com/hc/en-us/articles/360053147012';
|
|
const INFURA_BLOCKAGE_URL =
|
|
'https://metamask.zendesk.com/hc/en-us/articles/360059386712';
|
|
|
|
function shouldCloseNotificationPopup({
|
|
isNotification,
|
|
totalUnapprovedCount,
|
|
isSigningQRHardwareTransaction,
|
|
}) {
|
|
return (
|
|
isNotification &&
|
|
totalUnapprovedCount === 0 &&
|
|
!isSigningQRHardwareTransaction
|
|
);
|
|
}
|
|
|
|
export default class Home extends PureComponent {
|
|
static contextTypes = {
|
|
t: PropTypes.func,
|
|
};
|
|
|
|
static propTypes = {
|
|
history: PropTypes.object,
|
|
forgottenPassword: PropTypes.bool,
|
|
suggestedAssets: PropTypes.array,
|
|
unconfirmedTransactionsCount: PropTypes.number,
|
|
shouldShowSeedPhraseReminder: PropTypes.bool.isRequired,
|
|
isPopup: PropTypes.bool,
|
|
isNotification: PropTypes.bool.isRequired,
|
|
threeBoxSynced: PropTypes.bool,
|
|
setupThreeBox: PropTypes.func,
|
|
turnThreeBoxSyncingOn: PropTypes.func,
|
|
showRestorePrompt: PropTypes.bool,
|
|
selectedAddress: PropTypes.string,
|
|
restoreFromThreeBox: PropTypes.func,
|
|
setShowRestorePromptToFalse: PropTypes.func,
|
|
threeBoxLastUpdated: PropTypes.number,
|
|
firstPermissionsRequestId: PropTypes.string,
|
|
// This prop is used in the `shouldCloseNotificationPopup` function
|
|
// eslint-disable-next-line react/no-unused-prop-types
|
|
totalUnapprovedCount: PropTypes.number.isRequired,
|
|
setConnectedStatusPopoverHasBeenShown: PropTypes.func,
|
|
connectedStatusPopoverHasBeenShown: PropTypes.bool,
|
|
defaultHomeActiveTabName: PropTypes.string,
|
|
onTabClick: PropTypes.func.isRequired,
|
|
haveSwapsQuotes: PropTypes.bool.isRequired,
|
|
showAwaitingSwapScreen: PropTypes.bool.isRequired,
|
|
swapsFetchParams: PropTypes.object,
|
|
shouldShowWeb3ShimUsageNotification: PropTypes.bool.isRequired,
|
|
setWeb3ShimUsageAlertDismissed: PropTypes.func.isRequired,
|
|
originOfCurrentTab: PropTypes.string,
|
|
disableWeb3ShimUsageAlert: PropTypes.func.isRequired,
|
|
pendingConfirmations: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
infuraBlocked: PropTypes.bool.isRequired,
|
|
showWhatsNewPopup: PropTypes.bool.isRequired,
|
|
hideWhatsNewPopup: PropTypes.func.isRequired,
|
|
notificationsToShow: PropTypes.bool.isRequired,
|
|
showRecoveryPhraseReminder: PropTypes.bool.isRequired,
|
|
setRecoveryPhraseReminderHasBeenShown: PropTypes.func.isRequired,
|
|
setRecoveryPhraseReminderLastShown: PropTypes.func.isRequired,
|
|
seedPhraseBackedUp: PropTypes.bool.isRequired,
|
|
newNetworkAdded: PropTypes.string,
|
|
setNewNetworkAdded: PropTypes.func.isRequired,
|
|
// This prop is used in the `shouldCloseNotificationPopup` function
|
|
// eslint-disable-next-line react/no-unused-prop-types
|
|
isSigningQRHardwareTransaction: PropTypes.bool.isRequired,
|
|
newCollectibleAddedMessage: PropTypes.string,
|
|
setNewCollectibleAddedMessage: PropTypes.func.isRequired,
|
|
closeNotificationPopup: PropTypes.func.isRequired,
|
|
};
|
|
|
|
state = {
|
|
canShowBlockageNotification: true,
|
|
notificationClosing: false,
|
|
redirecting: false,
|
|
};
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
const {
|
|
closeNotificationPopup,
|
|
firstPermissionsRequestId,
|
|
haveSwapsQuotes,
|
|
isNotification,
|
|
showAwaitingSwapScreen,
|
|
suggestedAssets = [],
|
|
swapsFetchParams,
|
|
unconfirmedTransactionsCount,
|
|
} = this.props;
|
|
|
|
if (shouldCloseNotificationPopup(props)) {
|
|
this.state.notificationClosing = true;
|
|
closeNotificationPopup();
|
|
} else if (
|
|
firstPermissionsRequestId ||
|
|
unconfirmedTransactionsCount > 0 ||
|
|
suggestedAssets.length > 0 ||
|
|
(!isNotification &&
|
|
(showAwaitingSwapScreen || haveSwapsQuotes || swapsFetchParams))
|
|
) {
|
|
this.state.redirecting = true;
|
|
}
|
|
}
|
|
|
|
checkStatusAndNavigate() {
|
|
const {
|
|
firstPermissionsRequestId,
|
|
history,
|
|
isNotification,
|
|
suggestedAssets = [],
|
|
unconfirmedTransactionsCount,
|
|
haveSwapsQuotes,
|
|
showAwaitingSwapScreen,
|
|
swapsFetchParams,
|
|
pendingConfirmations,
|
|
} = this.props;
|
|
if (!isNotification && showAwaitingSwapScreen) {
|
|
history.push(AWAITING_SWAP_ROUTE);
|
|
} else if (!isNotification && haveSwapsQuotes) {
|
|
history.push(VIEW_QUOTE_ROUTE);
|
|
} else if (!isNotification && swapsFetchParams) {
|
|
history.push(BUILD_QUOTE_ROUTE);
|
|
} else if (firstPermissionsRequestId) {
|
|
history.push(`${CONNECT_ROUTE}/${firstPermissionsRequestId}`);
|
|
} else if (unconfirmedTransactionsCount > 0) {
|
|
history.push(CONFIRM_TRANSACTION_ROUTE);
|
|
} else if (suggestedAssets.length > 0) {
|
|
history.push(CONFIRM_ADD_SUGGESTED_TOKEN_ROUTE);
|
|
} else if (pendingConfirmations.length > 0) {
|
|
history.push(CONFIRMATION_V_NEXT_ROUTE);
|
|
}
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.checkStatusAndNavigate();
|
|
}
|
|
|
|
static getDerivedStateFromProps(props) {
|
|
if (shouldCloseNotificationPopup(props)) {
|
|
return { notificationClosing: true };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
componentDidUpdate(_prevProps, prevState) {
|
|
const {
|
|
closeNotificationPopup,
|
|
setupThreeBox,
|
|
showRestorePrompt,
|
|
threeBoxLastUpdated,
|
|
threeBoxSynced,
|
|
isNotification,
|
|
} = this.props;
|
|
const { notificationClosing } = this.state;
|
|
|
|
if (notificationClosing && !prevState.notificationClosing) {
|
|
closeNotificationPopup();
|
|
} else if (isNotification) {
|
|
this.checkStatusAndNavigate();
|
|
} else if (
|
|
threeBoxSynced &&
|
|
showRestorePrompt &&
|
|
threeBoxLastUpdated === null
|
|
) {
|
|
setupThreeBox();
|
|
}
|
|
}
|
|
|
|
onRecoveryPhraseReminderClose = () => {
|
|
const {
|
|
setRecoveryPhraseReminderHasBeenShown,
|
|
setRecoveryPhraseReminderLastShown,
|
|
} = this.props;
|
|
setRecoveryPhraseReminderHasBeenShown(true);
|
|
setRecoveryPhraseReminderLastShown(new Date().getTime());
|
|
};
|
|
|
|
renderNotifications() {
|
|
const { t } = this.context;
|
|
const {
|
|
history,
|
|
shouldShowSeedPhraseReminder,
|
|
isPopup,
|
|
selectedAddress,
|
|
restoreFromThreeBox,
|
|
turnThreeBoxSyncingOn,
|
|
setShowRestorePromptToFalse,
|
|
showRestorePrompt,
|
|
threeBoxLastUpdated,
|
|
shouldShowWeb3ShimUsageNotification,
|
|
setWeb3ShimUsageAlertDismissed,
|
|
originOfCurrentTab,
|
|
disableWeb3ShimUsageAlert,
|
|
infuraBlocked,
|
|
newNetworkAdded,
|
|
setNewNetworkAdded,
|
|
newCollectibleAddedMessage,
|
|
setNewCollectibleAddedMessage,
|
|
} = this.props;
|
|
return (
|
|
<MultipleNotifications>
|
|
{newCollectibleAddedMessage ? (
|
|
<ActionableMessage
|
|
type={newCollectibleAddedMessage === 'success' ? 'info' : 'warning'}
|
|
className="home__new-network-notification"
|
|
message={
|
|
<div className="home__new-network-notification-message">
|
|
{newCollectibleAddedMessage === 'success' ? (
|
|
<img
|
|
src="./images/check_circle.svg"
|
|
className="home__new-network-notification-message--image"
|
|
/>
|
|
) : null}
|
|
<Typography
|
|
variant={TYPOGRAPHY.H7}
|
|
fontWeight={FONT_WEIGHT.NORMAL}
|
|
>
|
|
{newCollectibleAddedMessage === 'success'
|
|
? t('newCollectibleAddedMessage')
|
|
: t('newCollectibleAddFailed', [
|
|
newCollectibleAddedMessage,
|
|
])}
|
|
</Typography>
|
|
<button
|
|
className="fas fa-times home__close"
|
|
title={t('close')}
|
|
onClick={() => setNewCollectibleAddedMessage('')}
|
|
/>
|
|
</div>
|
|
}
|
|
/>
|
|
) : null}
|
|
{newNetworkAdded ? (
|
|
<ActionableMessage
|
|
type="info"
|
|
className="home__new-network-notification"
|
|
message={
|
|
<div className="home__new-network-notification-message">
|
|
<img
|
|
src="./images/check_circle.svg"
|
|
className="home__new-network-notification-message--image"
|
|
/>
|
|
<Typography
|
|
variant={TYPOGRAPHY.H7}
|
|
fontWeight={FONT_WEIGHT.NORMAL}
|
|
>
|
|
{t('newNetworkAdded', [newNetworkAdded])}
|
|
</Typography>
|
|
<button
|
|
className="fas fa-times home__close"
|
|
title={t('close')}
|
|
onClick={() => setNewNetworkAdded('')}
|
|
/>
|
|
</div>
|
|
}
|
|
/>
|
|
) : null}
|
|
{shouldShowWeb3ShimUsageNotification ? (
|
|
<HomeNotification
|
|
descriptionText={t('web3ShimUsageNotification', [
|
|
<span
|
|
key="web3ShimUsageNotificationLink"
|
|
className="home-notification__text-link"
|
|
onClick={() =>
|
|
global.platform.openTab({ url: LEGACY_WEB3_URL })
|
|
}
|
|
>
|
|
{t('here')}
|
|
</span>,
|
|
])}
|
|
ignoreText={t('dismiss')}
|
|
onIgnore={(disable) => {
|
|
setWeb3ShimUsageAlertDismissed(originOfCurrentTab);
|
|
if (disable) {
|
|
disableWeb3ShimUsageAlert();
|
|
}
|
|
}}
|
|
checkboxText={t('dontShowThisAgain')}
|
|
checkboxTooltipText={t('canToggleInSettings')}
|
|
key="home-web3ShimUsageNotification"
|
|
/>
|
|
) : null}
|
|
{shouldShowSeedPhraseReminder ? (
|
|
<HomeNotification
|
|
descriptionText={t('backupApprovalNotice')}
|
|
acceptText={t('backupNow')}
|
|
onAccept={() => {
|
|
if (isPopup) {
|
|
global.platform.openExtensionInBrowser(
|
|
INITIALIZE_BACKUP_SEED_PHRASE_ROUTE,
|
|
);
|
|
} else {
|
|
history.push(INITIALIZE_BACKUP_SEED_PHRASE_ROUTE);
|
|
}
|
|
}}
|
|
infoText={t('backupApprovalInfo')}
|
|
key="home-backupApprovalNotice"
|
|
/>
|
|
) : null}
|
|
{threeBoxLastUpdated && showRestorePrompt ? (
|
|
<HomeNotification
|
|
descriptionText={t('restoreWalletPreferences', [
|
|
formatDate(threeBoxLastUpdated, 'M/d/y'),
|
|
])}
|
|
acceptText={t('restore')}
|
|
ignoreText={t('noThanks')}
|
|
infoText={t('dataBackupFoundInfo')}
|
|
onAccept={() => {
|
|
restoreFromThreeBox(selectedAddress).then(() => {
|
|
turnThreeBoxSyncingOn();
|
|
});
|
|
}}
|
|
onIgnore={() => {
|
|
setShowRestorePromptToFalse();
|
|
}}
|
|
key="home-privacyModeDefault"
|
|
/>
|
|
) : null}
|
|
{infuraBlocked && this.state.canShowBlockageNotification ? (
|
|
<HomeNotification
|
|
descriptionText={t('infuraBlockedNotification', [
|
|
<span
|
|
key="infuraBlockedNotificationLink"
|
|
className="home-notification__text-link"
|
|
onClick={() =>
|
|
global.platform.openTab({ url: INFURA_BLOCKAGE_URL })
|
|
}
|
|
>
|
|
{t('here')}
|
|
</span>,
|
|
])}
|
|
ignoreText={t('dismiss')}
|
|
onIgnore={() => {
|
|
this.setState({
|
|
canShowBlockageNotification: false,
|
|
});
|
|
}}
|
|
key="home-infuraBlockedNotification"
|
|
/>
|
|
) : null}
|
|
</MultipleNotifications>
|
|
);
|
|
}
|
|
|
|
renderPopover = () => {
|
|
const { setConnectedStatusPopoverHasBeenShown } = this.props;
|
|
const { t } = this.context;
|
|
return (
|
|
<Popover
|
|
title={t('whatsThis')}
|
|
onClose={setConnectedStatusPopoverHasBeenShown}
|
|
className="home__connected-status-popover"
|
|
showArrow
|
|
CustomBackground={({ onClose }) => {
|
|
return (
|
|
<div
|
|
className="home__connected-status-popover-bg-container"
|
|
onClick={onClose}
|
|
>
|
|
<div className="home__connected-status-popover-bg" />
|
|
</div>
|
|
);
|
|
}}
|
|
footer={
|
|
<>
|
|
<a href={LEARN_MORE_URL} target="_blank" rel="noopener noreferrer">
|
|
{t('learnMore')}
|
|
</a>
|
|
<Button
|
|
type="primary"
|
|
onClick={setConnectedStatusPopoverHasBeenShown}
|
|
>
|
|
{t('dismiss')}
|
|
</Button>
|
|
</>
|
|
}
|
|
>
|
|
<main className="home__connect-status-text">
|
|
<div>{t('metaMaskConnectStatusParagraphOne')}</div>
|
|
<div>{t('metaMaskConnectStatusParagraphTwo')}</div>
|
|
<div>{t('metaMaskConnectStatusParagraphThree')}</div>
|
|
</main>
|
|
</Popover>
|
|
);
|
|
};
|
|
|
|
render() {
|
|
const { t } = this.context;
|
|
const {
|
|
defaultHomeActiveTabName,
|
|
onTabClick,
|
|
forgottenPassword,
|
|
history,
|
|
connectedStatusPopoverHasBeenShown,
|
|
isPopup,
|
|
notificationsToShow,
|
|
showWhatsNewPopup,
|
|
hideWhatsNewPopup,
|
|
seedPhraseBackedUp,
|
|
showRecoveryPhraseReminder,
|
|
} = this.props;
|
|
|
|
if (forgottenPassword) {
|
|
return <Redirect to={{ pathname: RESTORE_VAULT_ROUTE }} />;
|
|
} else if (this.state.notificationClosing || this.state.redirecting) {
|
|
return null;
|
|
}
|
|
|
|
const showWhatsNew = notificationsToShow && showWhatsNewPopup;
|
|
|
|
return (
|
|
<div className="main-container">
|
|
<Route path={CONNECTED_ROUTE} component={ConnectedSites} exact />
|
|
<Route
|
|
path={CONNECTED_ACCOUNTS_ROUTE}
|
|
component={ConnectedAccounts}
|
|
exact
|
|
/>
|
|
<div className="home__container">
|
|
{showWhatsNew ? <WhatsNewPopup onClose={hideWhatsNewPopup} /> : null}
|
|
{!showWhatsNew && showRecoveryPhraseReminder ? (
|
|
<RecoveryPhraseReminder
|
|
hasBackedUp={seedPhraseBackedUp}
|
|
onConfirm={this.onRecoveryPhraseReminderClose}
|
|
/>
|
|
) : null}
|
|
{isPopup && !connectedStatusPopoverHasBeenShown
|
|
? this.renderPopover()
|
|
: null}
|
|
<div className="home__main-view">
|
|
<MenuBar />
|
|
<div className="home__balance-wrapper">
|
|
<EthOverview />
|
|
</div>
|
|
<Tabs
|
|
defaultActiveTabName={defaultHomeActiveTabName}
|
|
onTabClick={onTabClick}
|
|
tabsClassName="home__tabs"
|
|
>
|
|
<Tab
|
|
activeClassName="home__tab--active"
|
|
className="home__tab"
|
|
data-testid="home__asset-tab"
|
|
name={t('assets')}
|
|
>
|
|
<AssetList
|
|
onClickAsset={(asset) =>
|
|
history.push(`${ASSET_ROUTE}/${asset}`)
|
|
}
|
|
/>
|
|
</Tab>
|
|
{process.env.COLLECTIBLES_V1 ? (
|
|
<Tab
|
|
activeClassName="home__tab--active"
|
|
className="home__tab"
|
|
data-testid="home__nfts-tab"
|
|
name={t('nfts')}
|
|
>
|
|
<CollectiblesTab
|
|
onAddNFT={() => {
|
|
history.push(ADD_COLLECTIBLE_ROUTE);
|
|
}}
|
|
/>
|
|
</Tab>
|
|
) : null}
|
|
<Tab
|
|
activeClassName="home__tab--active"
|
|
className="home__tab"
|
|
data-testid="home__activity-tab"
|
|
name={t('activity')}
|
|
>
|
|
<TransactionList />
|
|
</Tab>
|
|
</Tabs>
|
|
<div className="home__support">
|
|
{isBeta() ? (
|
|
<BetaHomeFooter />
|
|
) : (
|
|
t('needHelp', [
|
|
<a
|
|
href="https://support.metamask.io"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
key="need-help-link"
|
|
>
|
|
{t('needHelpLinkText')}
|
|
</a>,
|
|
])
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{this.renderNotifications()}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|