import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { Box, BannerAlert } from '../../../components/component-library'; import ToggleButton from '../../../components/ui/toggle-button'; import TextField from '../../../components/ui/text-field'; import Button from '../../../components/ui/button'; import Dropdown from '../../../components/ui/dropdown'; import Dialog from '../../../components/ui/dialog'; import { Display, FlexDirection, JustifyContent, Severity, TextVariant, } from '../../../helpers/constants/design-system'; import { getPlatform } from '../../../../app/scripts/lib/util'; import { PLATFORM_FIREFOX } from '../../../../shared/constants/app'; import { getNumberOfSettingsInSection, handleSettingsRefs, } from '../../../helpers/utils/settings-search'; import { LedgerTransportTypes, LEDGER_USB_VENDOR_ID, } from '../../../../shared/constants/hardware-wallets'; import { MetaMetricsEventCategory, MetaMetricsEventName, } from '../../../../shared/constants/metametrics'; import { DEFAULT_AUTO_LOCK_TIME_LIMIT } from '../../../../shared/constants/preferences'; import { exportAsFile, ExportableContentType, } from '../../../helpers/utils/export-utils'; import ActionableMessage from '../../../components/ui/actionable-message'; import ZENDESK_URLS from '../../../helpers/constants/zendesk-url'; const CORRUPT_JSON_FILE = 'CORRUPT_JSON_FILE'; export default class AdvancedTab extends PureComponent { static contextTypes = { t: PropTypes.func, trackEvent: PropTypes.func, }; static propTypes = { setUseNonceField: PropTypes.func, useNonceField: PropTypes.bool, setHexDataFeatureFlag: PropTypes.func, displayWarning: PropTypes.func, showResetAccountConfirmationModal: PropTypes.func, showEthSignModal: PropTypes.func, warning: PropTypes.string, sendHexData: PropTypes.bool, showFiatInTestnets: PropTypes.bool, showTestNetworks: PropTypes.bool, autoLockTimeLimit: PropTypes.number, setAutoLockTimeLimit: PropTypes.func.isRequired, setShowFiatConversionOnTestnetsPreference: PropTypes.func.isRequired, setShowTestNetworks: PropTypes.func.isRequired, ledgerTransportType: PropTypes.oneOf(Object.values(LedgerTransportTypes)), setLedgerTransportPreference: PropTypes.func.isRequired, setDismissSeedBackUpReminder: PropTypes.func.isRequired, dismissSeedBackUpReminder: PropTypes.bool.isRequired, userHasALedgerAccount: PropTypes.bool.isRequired, backupUserData: PropTypes.func.isRequired, restoreUserData: PropTypes.func.isRequired, setDisabledRpcMethodPreference: PropTypes.func.isRequired, disabledRpcMethodPreferences: PropTypes.shape({ eth_sign: PropTypes.bool.isRequired, }), ///: BEGIN:ONLY_INCLUDE_IN(desktop) desktopEnabled: PropTypes.bool, ///: END:ONLY_INCLUDE_IN }; state = { autoLockTimeLimit: this.props.autoLockTimeLimit, autoLockTimeLimitBeforeNormalization: this.props.autoLockTimeLimit, lockTimeError: '', showLedgerTransportWarning: false, showResultMessage: false, restoreSuccessful: true, restoreMessage: null, }; settingsRefs = Array( getNumberOfSettingsInSection(this.context.t, this.context.t('advanced')), ) .fill(undefined) .map(() => { return React.createRef(); }); componentDidUpdate() { const { t } = this.context; handleSettingsRefs(t, t('advanced'), this.settingsRefs); } componentDidMount() { const { t } = this.context; handleSettingsRefs(t, t('advanced'), this.settingsRefs); } async getTextFromFile(file) { return new Promise((resolve, reject) => { const reader = new window.FileReader(); reader.onload = (e) => { const text = e.target.result; resolve(text); }; reader.onerror = (e) => { reject(e); }; reader.readAsText(file); }); } async handleFileUpload(event) { /** * we need this to be able to access event.target after * the event handler has been called. [Synthetic Event Pooling, pre React 17] * * @see https://fb.me/react-event-pooling */ event.persist(); const file = event.target.files[0]; const jsonString = await this.getTextFromFile(file); /** * so that we can restore same file again if we want to. * chrome blocks uploading same file twice. */ event.target.value = ''; try { const result = await this.props.restoreUserData(jsonString); this.setState({ showResultMessage: true, restoreSuccessful: result, restoreMessage: null, }); } catch (e) { if (e.message.match(/Unexpected.+JSON/iu)) { this.setState({ showResultMessage: true, restoreSuccessful: false, restoreMessage: CORRUPT_JSON_FILE, }); } } } backupUserData = async () => { const { fileName, data } = await this.props.backupUserData(); exportAsFile(fileName, data, ExportableContentType.JSON); this.context.trackEvent({ event: 'User Data Exported', category: 'Backup', properties: {}, }); }; renderStateLogs() { const { t } = this.context; const { displayWarning } = this.props; return (
{t('stateLogs')} {t('stateLogsDescription')}
); } renderResetAccount() { const { t } = this.context; const { showResetAccountConfirmationModal } = this.props; return (
{t('clearActivity')} {t('clearActivityDescription')}
); } renderHexDataOptIn() { const { t } = this.context; const { sendHexData, setHexDataFeatureFlag } = this.props; return (
{t('showHexData')}
{t('showHexDataDescription')}
setHexDataFeatureFlag(!value)} offLabel={t('off')} onLabel={t('on')} />
); } renderShowConversionInTestnets() { const { t } = this.context; const { showFiatInTestnets, setShowFiatConversionOnTestnetsPreference } = this.props; return (
{t('showFiatConversionInTestnets')}
{t('showFiatConversionInTestnetsDescription')}
setShowFiatConversionOnTestnetsPreference(!value) } offLabel={t('off')} onLabel={t('on')} />
); } renderToggleTestNetworks() { const { t } = this.context; const { showTestNetworks, setShowTestNetworks } = this.props; return (
{t('showTestnetNetworks')}
{t('showTestnetNetworksDescription')}
setShowTestNetworks(!value)} offLabel={t('off')} onLabel={t('on')} />
); } renderUseNonceOptIn() { const { t } = this.context; const { useNonceField, setUseNonceField } = this.props; return (
{t('nonceField')}
{t('nonceFieldDescription')}
setUseNonceField(!value)} offLabel={t('off')} onLabel={t('on')} />
); } renderAutoLockTimeLimit() { const { t } = this.context; const { lockTimeError } = this.state; const { setAutoLockTimeLimit } = this.props; return (
{t('autoLockTimeLimit')}
{t('autoLockTimeLimitDescription')}
this.handleLockChange(e.target.value)} error={lockTimeError} fullWidth margin="dense" min={0} />
); } renderLedgerLiveControl() { const { t } = this.context; const { ledgerTransportType, setLedgerTransportPreference, userHasALedgerAccount, ///: BEGIN:ONLY_INCLUDE_IN(desktop) desktopEnabled, ///: END:ONLY_INCLUDE_IN } = this.props; ///: BEGIN:ONLY_INCLUDE_IN(desktop) if (desktopEnabled) { return null; } ///: END:ONLY_INCLUDE_IN const LEDGER_TRANSPORT_NAMES = { LIVE: t('ledgerLive'), WEBHID: t('webhid'), U2F: t('u2f'), }; const transportTypeOptions = [ { name: LEDGER_TRANSPORT_NAMES.LIVE, value: LedgerTransportTypes.live, }, { name: LEDGER_TRANSPORT_NAMES.U2F, value: LedgerTransportTypes.u2f, }, ]; if (window.navigator.hid) { transportTypeOptions.push({ name: LEDGER_TRANSPORT_NAMES.WEBHID, value: LedgerTransportTypes.webhid, }); } const recommendedLedgerOption = window.navigator.hid ? LEDGER_TRANSPORT_NAMES.WEBHID : LEDGER_TRANSPORT_NAMES.U2F; return (
{t('preferredLedgerConnectionType')}
{t('ledgerConnectionPreferenceDescription', [ recommendedLedgerOption, , ])}
{ if ( ledgerTransportType === LedgerTransportTypes.live && transportType === LedgerTransportTypes.webhid ) { this.setState({ showLedgerTransportWarning: true }); } setLedgerTransportPreference(transportType); if ( transportType === LedgerTransportTypes.webhid && userHasALedgerAccount ) { await window.navigator.hid.requestDevice({ filters: [{ vendorId: LEDGER_USB_VENDOR_ID }], }); } }} /> {this.state.showLedgerTransportWarning ? (
{t('ledgerTransportChangeWarning')}
) : null}
); } renderDismissSeedBackupReminderControl() { const { t } = this.context; const { dismissSeedBackUpReminder, setDismissSeedBackUpReminder } = this.props; return (
{t('dismissReminderField')}
{t('dismissReminderDescriptionField')}
setDismissSeedBackUpReminder(!value)} offLabel={t('off')} onLabel={t('on')} />
); } renderToggleEthSignControl() { const { t, trackEvent } = this.context; const { disabledRpcMethodPreferences, showEthSignModal, setDisabledRpcMethodPreference, } = this.props; const toggleOff = (value) => { setDisabledRpcMethodPreference('eth_sign', !value); trackEvent({ category: MetaMetricsEventCategory.Settings, event: MetaMetricsEventName.OnboardingWalletAdvancedSettings, properties: { location: 'Settings', enable_eth_sign: false, }, }); }; return (
{t('toggleEthSignField')}
{t('toggleEthSignDescriptionField')}
{disabledRpcMethodPreferences?.eth_sign ? ( {t('toggleEthSignBannerDescription')} ) : null}
{ value ? toggleOff(value) : showEthSignModal(); }} offLabel={t('toggleEthSignOff')} onLabel={t('toggleEthSignOn')} />
); } handleLockChange(autoLockTimeLimitBeforeNormalization) { const { t } = this.context; if (autoLockTimeLimitBeforeNormalization === '') { this.setState({ autoLockTimeLimitBeforeNormalization, autoLockTimeLimit: DEFAULT_AUTO_LOCK_TIME_LIMIT.toString(), lockTimeError: '', }); return; } const autoLockTimeLimitAfterNormalization = Number( autoLockTimeLimitBeforeNormalization, ); if ( Number.isNaN(autoLockTimeLimitAfterNormalization) || autoLockTimeLimitAfterNormalization < 0 || autoLockTimeLimitAfterNormalization > 10080 ) { this.setState({ autoLockTimeLimitBeforeNormalization, autoLockTimeLimit: null, lockTimeError: t('lockTimeInvalid'), }); return; } const autoLockTimeLimit = autoLockTimeLimitAfterNormalization; this.setState({ autoLockTimeLimitBeforeNormalization, autoLockTimeLimit, lockTimeError: '', }); } renderUserDataBackup() { const { t } = this.context; return (
{t('backupUserData')} {t('backupUserDataDescription')}
); } renderRestoreUserData() { const { t } = this.context; const { showResultMessage, restoreSuccessful, restoreMessage } = this.state; const defaultRestoreMessage = restoreSuccessful ? t('restoreSuccessful') : t('restoreFailed'); const restoreMessageToRender = restoreMessage === CORRUPT_JSON_FILE ? t('dataBackupSeemsCorrupt') : defaultRestoreMessage; return (
{t('restoreUserData')} {t('restoreUserDataDescription')}
this.handleFileUpload(e)} />
{showResultMessage && ( { this.setState({ showResultMessage: false, restoreSuccessful: true, restoreMessage: null, }); }, }} /> )}
); } render() { const { warning } = this.props; const notUsingFirefox = getPlatform() !== PLATFORM_FIREFOX; return (
{warning ?
{warning}
: null} {this.renderStateLogs()} {this.renderResetAccount()} {this.renderHexDataOptIn()} {this.renderShowConversionInTestnets()} {this.renderToggleTestNetworks()} {this.renderUseNonceOptIn()} {this.renderAutoLockTimeLimit()} {this.renderUserDataBackup()} {this.renderRestoreUserData()} {notUsingFirefox ? this.renderLedgerLiveControl() : null} {this.renderDismissSeedBackupReminderControl()} {this.renderToggleEthSignControl()}
); } }