import { startCase } from 'lodash'; import PropTypes from 'prop-types'; import React, { PureComponent } from 'react'; import { addUrlProtocolPrefix, getEnvironmentType, } from '../../../../app/scripts/lib/util'; import { ENVIRONMENT_TYPE_POPUP } from '../../../../shared/constants/app'; import { MetaMetricsEventCategory, MetaMetricsEventKeyType, MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; import { IPFS_DEFAULT_GATEWAY_URL } from '../../../../shared/constants/network'; import { AUTO_DETECT_TOKEN_LEARN_MORE_LINK, COINGECKO_LINK, CONSENSYS_PRIVACY_LINK, CRYPTOCOMPARE_LINK, PRIVACY_POLICY_LINK, } from '../../../../shared/lib/ui-utils'; import SRPQuiz from '../../../components/app/srp-quiz-modal/SRPQuiz'; import { Button, BUTTON_SIZES, Box, Text, } from '../../../components/component-library'; import TextField from '../../../components/ui/text-field'; import ToggleButton from '../../../components/ui/toggle-button'; import { Display, FlexDirection, JustifyContent, TextColor, TextVariant, } from '../../../helpers/constants/design-system'; import { ADD_POPULAR_CUSTOM_NETWORK } from '../../../helpers/constants/routes'; import { getNumberOfSettingsInSection, handleSettingsRefs, } from '../../../helpers/utils/settings-search'; import IncomingTransactionToggle from '../../../components/app/incoming-trasaction-toggle/incoming-transaction-toggle'; export default class SecurityTab extends PureComponent { static contextTypes = { t: PropTypes.func, trackEvent: PropTypes.func, }; static propTypes = { warning: PropTypes.string, history: PropTypes.object, openSeaEnabled: PropTypes.bool, setOpenSeaEnabled: PropTypes.func, useNftDetection: PropTypes.bool, setUseNftDetection: PropTypes.func, participateInMetaMetrics: PropTypes.bool.isRequired, setParticipateInMetaMetrics: PropTypes.func.isRequired, incomingTransactionsPreferences: PropTypes.object.isRequired, allNetworks: PropTypes.array.isRequired, setIncomingTransactionsPreferences: PropTypes.func.isRequired, setUsePhishDetect: PropTypes.func.isRequired, usePhishDetect: PropTypes.bool.isRequired, setUse4ByteResolution: PropTypes.func.isRequired, use4ByteResolution: PropTypes.bool.isRequired, useTokenDetection: PropTypes.bool.isRequired, setUseTokenDetection: PropTypes.func.isRequired, setIpfsGateway: PropTypes.func.isRequired, ipfsGateway: PropTypes.string.isRequired, useMultiAccountBalanceChecker: PropTypes.bool.isRequired, setUseMultiAccountBalanceChecker: PropTypes.func.isRequired, useCurrencyRateCheck: PropTypes.bool.isRequired, setUseCurrencyRateCheck: PropTypes.func.isRequired, useAddressBarEnsResolution: PropTypes.bool.isRequired, setUseAddressBarEnsResolution: PropTypes.func.isRequired, }; state = { ipfsGateway: this.props.ipfsGateway || IPFS_DEFAULT_GATEWAY_URL, ipfsGatewayError: '', srpQuizModalVisible: false, ipfsToggle: this.props.ipfsGateway.length > 0, }; settingsRefCounter = 0; settingsRefs = Array( getNumberOfSettingsInSection( this.context.t, this.context.t('securityAndPrivacy'), ), ) .fill(undefined) .map(() => { return React.createRef(); }); componentDidUpdate() { const { t } = this.context; handleSettingsRefs(t, t('securityAndPrivacy'), this.settingsRefs); } componentDidMount() { const { t } = this.context; handleSettingsRefs(t, t('securityAndPrivacy'), this.settingsRefs); } toggleSetting(value, eventName, eventAction, toggleMethod) { this.context.trackEvent({ category: MetaMetricsEventCategory.Settings, event: eventName, properties: { action: eventAction, legacy_event: true, }, }); toggleMethod(!value); } hideSrpQuizModal = () => this.setState({ srpQuizModalVisible: false }); renderSeedWords() { const { t } = this.context; return ( <>
{t('secretRecoveryPhrase')}
{this.state.srpQuizModalVisible && ( )}
); } renderIncomingTransactionsOptIn() { const { incomingTransactionsPreferences, allNetworks, setIncomingTransactionsPreferences, } = this.props; return ( ); } renderPhishingDetectionToggle() { const { t } = this.context; const { usePhishDetect, setUsePhishDetect } = this.props; return (
{t('usePhishingDetection')}
{t('usePhishingDetectionDescription')}
setUsePhishDetect(!value)} offLabel={t('off')} onLabel={t('on')} />
); } renderUse4ByteResolutionToggle() { const { t } = this.context; const { use4ByteResolution, setUse4ByteResolution } = this.props; return (
{t('use4ByteResolution')}
{t('use4ByteResolutionDescription')}
setUse4ByteResolution(!value)} offLabel={t('off')} onLabel={t('on')} />
); } renderMetaMetricsOptIn() { const { t } = this.context; const { participateInMetaMetrics, setParticipateInMetaMetrics } = this.props; return (
{t('participateInMetaMetrics')}
{t('participateInMetaMetricsDescription')}
setParticipateInMetaMetrics(!value)} offLabel={t('off')} onLabel={t('on')} />
); } renderChooseYourNetworkButton() { const { t } = this.context; return (
{t('chooseYourNetwork')}
{t('chooseYourNetworkDescription', [ // TODO: Update to use real link {t('privacyMsg')} , ])}
); } renderIpfsGatewayControl() { const { t } = this.context; let ipfsError = ''; const handleIpfsGatewayChange = (url) => { if (url.length > 0) { try { const validUrl = addUrlProtocolPrefix(url); if (!validUrl) { ipfsError = t('invalidIpfsGateway'); } const urlObj = new URL(validUrl); // don't allow the use of this gateway if (urlObj.host === 'gateway.ipfs.io') { ipfsError = t('forbiddenIpfsGateway'); } if (ipfsError.length === 0) { this.props.setIpfsGateway(urlObj.host); } } catch (error) { ipfsError = t('invalidIpfsGateway'); } } else { ipfsError = t('invalidIpfsGateway'); } this.setState({ ipfsGateway: url, ipfsGatewayError: ipfsError, }); }; return (
{t('ipfsGateway')}
{t('ipfsGatewayDescription')}
{ if (value) { // turning from true to false this.props.setIpfsGateway(''); } else { // turning from false to true handleIpfsGatewayChange(this.state.ipfsGateway); } this.setState({ ipfsToggle: !value }); }} offLabel={t('off')} onLabel={t('on')} />
{this.state.ipfsToggle && (
{t('addIPFSGateway')}
handleIpfsGatewayChange(e.target.value)} error={this.state.ipfsGatewayError} fullWidth margin="dense" />
)}
{t('ensDomainsSettingTitle')}
{t('ensDomainsSettingDescriptionIntro')} {t('ensDomainsSettingDescriptionPoint1')} {t('ensDomainsSettingDescriptionPoint2')} {t('ensDomainsSettingDescriptionPoint3')} {t('ensDomainsSettingDescriptionOutro')}
this.props.setUseAddressBarEnsResolution(!value) } offLabel={t('off')} onLabel={t('on')} />
); } renderAutoDetectTokensToggle() { const { t } = this.context; const { useTokenDetection, setUseTokenDetection } = this.props; return (
{t('autoDetectTokens')}
{t('autoDetectTokensDescription', [ // TODO: Update to use real link {startCase(t('learnMore'))} , ])}
{ this.toggleSetting( value, MetaMetricsEventName.KeyAutoDetectTokens, MetaMetricsEventName.KeyAutoDetectTokens, setUseTokenDetection, ); }} offLabel={t('off')} onLabel={t('on')} />
); } renderBatchAccountBalanceRequestsToggle() { const { t } = this.context; const { useMultiAccountBalanceChecker, setUseMultiAccountBalanceChecker } = this.props; return (
{t('useMultiAccountBalanceChecker')}
{t('useMultiAccountBalanceCheckerSettingDescription')}
{ this.toggleSetting( value, MetaMetricsEventName.KeyBatchAccountBalanceRequests, MetaMetricsEventName.KeyBatchAccountBalanceRequests, setUseMultiAccountBalanceChecker, ); }} offLabel={t('off')} onLabel={t('on')} />
); } renderCurrencyRateCheckToggle() { const { t } = this.context; const { useCurrencyRateCheck, setUseCurrencyRateCheck } = this.props; return (
{t('currencyRateCheckToggle')}
{t('currencyRateCheckToggleDescription', [ {t('coingecko')} , {t('cryptoCompare')} , {t('privacyMsg')} , ])}
setUseCurrencyRateCheck(!value)} offLabel={t('off')} onLabel={t('on')} />
); } renderDisplayNftMediaToggle() { const { t } = this.context; const { openSeaEnabled, setOpenSeaEnabled, useNftDetection, setUseNftDetection, } = this.props; return (
{t('displayNftMedia')}
{t('displayNftMediaDescription')}
{ this.context.trackEvent({ category: MetaMetricsEventCategory.Settings, event: 'Enabled/Disable OpenSea', properties: { action: 'Enabled/Disable OpenSea', legacy_event: true, }, }); // value is positive when being toggled off if (value && useNftDetection) { setUseNftDetection(false); } setOpenSeaEnabled(!value); }} offLabel={t('off')} onLabel={t('on')} />
); } renderNftDetectionToggle() { const { t } = this.context; const { openSeaEnabled, setOpenSeaEnabled, useNftDetection, setUseNftDetection, } = this.props; return (
{t('useNftDetection')}
{t('useNftDetectionDescription')}
  • {t('useNftDetectionDescriptionLine2')}
  • {t('useNftDetectionDescriptionLine3')}
  • {t('useNftDetectionDescriptionLine4')}
{t('useNftDetectionDescriptionLine5')}
{ this.context.trackEvent({ category: MetaMetricsEventCategory.Settings, event: 'NFT Detected', properties: { action: 'NFT Detected', legacy_event: true, }, }); if (!value && !openSeaEnabled) { setOpenSeaEnabled(!value); } setUseNftDetection(!value); }} offLabel={t('off')} onLabel={t('on')} />
); } render() { const { warning } = this.props; return (
{warning &&
{warning}
} {this.context.t('security')} {this.renderSeedWords()} {this.context.t('privacy')}
{this.context.t('alerts')}
{this.renderPhishingDetectionToggle()}
{this.context.t('smartContracts')}
{this.renderUse4ByteResolutionToggle()}
{this.context.t('transactions')}
{this.renderCurrencyRateCheckToggle()} {this.renderIncomingTransactionsOptIn()}
{this.context.t('networkProvider')}
{this.renderChooseYourNetworkButton()} {this.renderIpfsGatewayControl()}
{this.context.t('tokenAutoDetection')}
{this.renderAutoDetectTokensToggle()} {this.renderBatchAccountBalanceRequestsToggle()} {this.renderDisplayNftMediaToggle()} {this.renderNftDetectionToggle()}
{this.context.t('metrics')}
{this.renderMetaMetricsOptIn()}
); } }