diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index ab52fa49c..dee684716 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -1255,6 +1255,9 @@ "ledgerAccountRestriction": { "message": "You need to make use your last account before you can add a new one." }, + "ledgerConnectionInstructionCloseOtherApps": { + "message": "Close any other software connected to your device and then click here to refresh." + }, "ledgerConnectionInstructionHeader": { "message": "Prior to clicking confirm:" }, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2010ce69f..40832c0bc 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -845,6 +845,10 @@ export default class MetamaskController extends EventEmitter { this.setLedgerTransportPreference, this, ), + attemptLedgerTransportCreation: nodeify( + this.attemptLedgerTransportCreation, + this, + ), // mobile fetchInfoToSync: nodeify(this.fetchInfoToSync, this), @@ -1556,6 +1560,11 @@ export default class MetamaskController extends EventEmitter { return keyring; } + async attemptLedgerTransportCreation() { + const keyring = await this.getKeyringForDevice('ledger'); + return await keyring.attemptMakeApp(); + } + /** * Fetch account list from a trezor device. * diff --git a/package.json b/package.json index c5789c618..b991a755c 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "@material-ui/core": "^4.11.0", "@metamask/contract-metadata": "^1.28.0", "@metamask/controllers": "^17.0.0", - "@metamask/eth-ledger-bridge-keyring": "^0.9.0", + "@metamask/eth-ledger-bridge-keyring": "^0.10.0", "@metamask/eth-token-tracker": "^3.0.1", "@metamask/etherscan-link": "^2.1.0", "@metamask/jazzicon": "^2.0.0", diff --git a/shared/constants/hardware-wallets.js b/shared/constants/hardware-wallets.js index e780555fc..705754fe7 100644 --- a/shared/constants/hardware-wallets.js +++ b/shared/constants/hardware-wallets.js @@ -24,3 +24,10 @@ export const WEBHID_CONNECTED_STATUSES = { NOT_CONNECTED: 'notConnected', UNKNOWN: 'unknown', }; + +export const TRANSPORT_STATES = { + NONE: 'NONE', + VERIFIED: 'VERIFIED', + DEVICE_OPEN_FAILURE: 'DEVICE_OPEN_FAILURE', + UNKNOWN_FAILURE: 'UNKNOWN_FAILURE', +}; diff --git a/ui/components/app/ledger-instruction-field/ledger-instruction-field.js b/ui/components/app/ledger-instruction-field/ledger-instruction-field.js index 960b6ca07..287db2c8e 100644 --- a/ui/components/app/ledger-instruction-field/ledger-instruction-field.js +++ b/ui/components/app/ledger-instruction-field/ledger-instruction-field.js @@ -5,6 +5,7 @@ import { LEDGER_TRANSPORT_TYPES, LEDGER_USB_VENDOR_ID, WEBHID_CONNECTED_STATUSES, + TRANSPORT_STATES, } from '../../../../shared/constants/hardware-wallets'; import { PLATFORM_FIREFOX, @@ -14,6 +15,8 @@ import { import { setLedgerWebHidConnectedStatus, getLedgerWebHidConnectedStatus, + setLedgerTransportStatus, + getLedgerTransportStatus, } from '../../../ducks/app/app'; import Typography from '../../ui/typography/typography'; @@ -30,6 +33,7 @@ import { getEnvironmentType, } from '../../../../app/scripts/lib/util'; import { getLedgerTransportType } from '../../../ducks/metamask/metamask'; +import { attemptLedgerTransportCreation } from '../../../store/actions'; const renderInstructionStep = (text, show = true, color = COLORS.PRIMARY3) => { return ( @@ -52,6 +56,7 @@ export default function LedgerInstructionField({ showDataInstruction }) { const webHidConnectedStatus = useSelector(getLedgerWebHidConnectedStatus); const ledgerTransportType = useSelector(getLedgerTransportType); + const transportStatus = useSelector(getLedgerTransportStatus); const environmentType = getEnvironmentType(); const environmentTypeIsFullScreen = environmentType === ENVIRONMENT_TYPE_FULLSCREEN; @@ -75,8 +80,45 @@ export default function LedgerInstructionField({ showDataInstruction }) { ); } }; + const determineTransportStatus = async () => { + if ( + ledgerTransportType === LEDGER_TRANSPORT_TYPES.WEBHID && + webHidConnectedStatus === WEBHID_CONNECTED_STATUSES.CONNECTED && + transportStatus === TRANSPORT_STATES.NONE + ) { + try { + const transportedCreated = await attemptLedgerTransportCreation(); + dispatch( + setLedgerTransportStatus( + transportedCreated + ? TRANSPORT_STATES.VERIFIED + : TRANSPORT_STATES.UNKNOWN_FAILURE, + ), + ); + } catch (e) { + if (e.message.match('Failed to open the device')) { + dispatch( + setLedgerTransportStatus(TRANSPORT_STATES.DEVICE_OPEN_FAILURE), + ); + } else if (e.message.match('the device is already open')) { + dispatch(setLedgerTransportStatus(TRANSPORT_STATES.VERIFIED)); + } else { + dispatch( + setLedgerTransportStatus(TRANSPORT_STATES.UNKNOWN_FAILURE), + ); + } + } + } + }; + determineTransportStatus(); initialConnectedDeviceCheck(); - }, [dispatch, ledgerTransportType, webHidConnectedStatus]); + }, [dispatch, ledgerTransportType, webHidConnectedStatus, transportStatus]); + + useEffect(() => { + return () => { + dispatch(setLedgerTransportStatus(TRANSPORT_STATES.NONE)); + }; + }, [dispatch]); const usingLedgerLive = ledgerTransportType === LEDGER_TRANSPORT_TYPES.LIVE; const usingWebHID = ledgerTransportType === LEDGER_TRANSPORT_TYPES.WEBHID; @@ -104,6 +146,23 @@ export default function LedgerInstructionField({ showDataInstruction }) { `- ${t('ledgerConnectionInstructionStepFour')}`, showDataInstruction, )} + {renderInstructionStep( + + + , + transportStatus === TRANSPORT_STATES.DEVICE_OPEN_FAILURE, + )} {renderInstructionStep(