Skip to content

Implement hardware connectivity confirmation screens #12725

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
3e53d72
Implement hardware connectivity confirmation screens
darkwing Nov 16, 2021
1d73fa4
Dropdown progress
darkwing Nov 19, 2021
17cf8b1
Progress
darkwing Nov 23, 2021
bac38d0
Remove path config from popover, lint
darkwing Nov 30, 2021
a37f51b
Add hardware connectivity functionality
darkwing Dec 1, 2021
5c1a3e6
Remove unused component
darkwing Dec 3, 2021
992f662
Wire up actual calls to the controller to poll for ledger connectivity
darkwing Jan 15, 2022
ea43299
Prevent navigation event during connection
darkwing Jan 18, 2022
6099099
Cleanup console logs
darkwing Feb 9, 2022
18a2899
Hide connection popover as soon as connection happens
darkwing Feb 9, 2022
bfaabad
Localize Ledger steps
darkwing Feb 9, 2022
132ea45
Remove logs, update state to not show advanced upon connection
darkwing Feb 14, 2022
25863cc
Use constants
darkwing Feb 15, 2022
a892954
Fix incorrect keyring localization
darkwing Feb 18, 2022
479f66a
Assume hardware ready upon connection
darkwing Feb 23, 2022
eadd017
Fix colors per Thomas' request
darkwing Feb 23, 2022
2d28d74
Add icons
darkwing Feb 23, 2022
7603b49
Fixes ci, lint, and unit test issues for hardware connectivity. (#13…
tmashuang Feb 24, 2022
6090d14
Remove console.logs
darkwing Feb 25, 2022
a022ed8
Clear interval upon page unload
darkwing Feb 25, 2022
418754d
Add close button to connect modal
darkwing Mar 3, 2022
4080488
Remove unused LedgerInstructionField component
darkwing Mar 7, 2022
818fd2e
Broaden language
darkwing Mar 7, 2022
66663b7
Rename prop to be more descriptive
darkwing Mar 8, 2022
05d97a8
Turn connection link to button
darkwing Mar 8, 2022
dbbced2
Change name of checkLedgerReady to more generic name
darkwing Mar 8, 2022
dda02c5
Don't show connect button when connected
darkwing Mar 10, 2022
bf0478e
Allow connecting in the popup to work
darkwing Mar 10, 2022
9b59864
Remove duplicate classname declaration
darkwing Apr 7, 2022
6308095
Fix success text color
darkwing Apr 7, 2022
6e0f447
Fix lint
darkwing Apr 14, 2022
219340c
Fix merge issues
darkwing May 2, 2022
d7cd8bd
Fix colors and prop
darkwing May 9, 2022
2ef45f3
Address feedback
darkwing May 12, 2022
bdf12e6
Fix lint
darkwing May 12, 2022
809ed94
Remove unused file
darkwing May 12, 2022
93cf4f1
Move transport dropdown to component library
darkwing May 13, 2022
f331227
Disable ledger connectivity UI for Firefox
darkwing May 13, 2022
575f518
Add storybook for LedgerTransportDropdown
darkwing May 13, 2022
d27c1f6
Fix outdated link
darkwing May 18, 2022
e912ef3
Rename variable
darkwing May 18, 2022
f60f7b2
EXPERIMENTAL FOR LINUX: Launch into full screen
darkwing May 20, 2022
8b07c09
Let the SEND screen direct when to stop and start keyring connection …
darkwing Jun 11, 2022
9e52c73
HardwareConnectivity: support dark mode ledger svg
digiwand Jun 16, 2022
aa5079f
Update ui/pages/confirm-transaction-base/hardware-connectivity/index.…
darkwing Jun 17, 2022
80451ba
Address feedback
darkwing Jun 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions app/_locales/en/messages.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions app/scripts/metamask-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,11 @@ export default class MetamaskController extends EventEmitter {
establishLedgerTransportPreference: this.establishLedgerTransportPreference.bind(
this,
),
checkDeviceReady: this.checkDeviceReady.bind(this),
startDeviceConnectionPolling: this.startDeviceConnectionPolling.bind(
this,
),
stopDeviceConnectionPolling: this.stopDeviceConnectionPolling.bind(this),

// qr hardware devices
submitQRHardwareCryptoHDKey: qrHardwareKeyring.submitCryptoHDKey.bind(
Expand Down Expand Up @@ -2527,6 +2532,34 @@ export default class MetamaskController extends EventEmitter {
return { ...keyState, identities };
}

async checkDeviceReady(address) {
const keyring = await this.keyringController.getKeyringForAccount(address);
if (!keyring) {
throw new Error('No keyring could be found for this address');
}

const isReady = Boolean(keyring?.isConnected?.());
return isReady;
}

async startDeviceConnectionPolling(address) {
const keyring = await this.keyringController.getKeyringForAccount(address);
if (!keyring) {
throw new Error('No keyring could be found for this address');
}

keyring?.startConnectionPolling?.();
}

async stopDeviceConnectionPolling(address) {
const keyring = await this.keyringController.getKeyringForAccount(address);
if (!keyring) {
throw new Error('No keyring could be found for this address');
}

keyring?.stopConnectionPolling?.();
}

//
// Account Management
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ export default class ConfirmPageContainerContent extends Component {
toAddress: PropTypes.string,
transactionType: PropTypes.string,
isBuyableChain: PropTypes.bool,
hideConfirmPageContainerSummaryAndButtons: PropTypes.bool.isRequired,
showHardwareConnectionContents: PropTypes.bool,
};

renderContent() {
Expand Down Expand Up @@ -127,6 +129,8 @@ export default class ConfirmPageContainerContent extends Component {
toAddress,
transactionType,
isBuyableChain,
hideConfirmPageContainerSummaryAndButtons,
showHardwareConnectionContents,
} = this.props;

const { t } = this.context;
Expand All @@ -146,24 +150,26 @@ export default class ConfirmPageContainerContent extends Component {
{ethGasPriceWarning && (
<ConfirmPageContainerWarning warning={ethGasPriceWarning} />
)}
<ConfirmPageContainerSummary
className={classnames({
'confirm-page-container-summary--border':
!detailsComponent || !dataComponent,
})}
action={action}
title={title}
image={image}
titleComponent={titleComponent}
subtitleComponent={subtitleComponent}
hideSubtitle={hideSubtitle}
tokenAddress={tokenAddress}
nonce={nonce}
origin={origin}
hideTitle={hideTitle}
toAddress={toAddress}
transactionType={transactionType}
/>
{showHardwareConnectionContents ? null : (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove and replace showHardwareConnectionContents in favor of hideConfirmPageContainerSummaryAndButtons

<ConfirmPageContainerSummary
className={classnames({
'confirm-page-container-summary--border':
!detailsComponent || !dataComponent,
})}
action={action}
title={title}
image={image}
titleComponent={titleComponent}
subtitleComponent={subtitleComponent}
hideSubtitle={hideSubtitle}
tokenAddress={tokenAddress}
nonce={nonce}
origin={origin}
hideTitle={hideTitle}
toAddress={toAddress}
transactionType={transactionType}
/>
)}
{this.renderContent()}
{!supportsEIP1559V2 &&
(errorKey || errorMessage) &&
Expand Down Expand Up @@ -209,17 +215,19 @@ export default class ConfirmPageContainerContent extends Component {
</div>
)}

<PageContainerFooter
onCancel={onCancel}
cancelText={cancelText}
onSubmit={onSubmit}
submitText={submitText}
disabled={disabled}
>
{unapprovedTxCount > 1 ? (
<a onClick={onCancelAll}>{rejectNText}</a>
) : null}
</PageContainerFooter>
{hideConfirmPageContainerSummaryAndButtons ? null : (
<PageContainerFooter
onCancel={onCancel}
cancelText={cancelText}
onSubmit={onSubmit}
submitText={submitText}
disabled={disabled}
>
{unapprovedTxCount > 1 ? (
<a onClick={onCancelAll}>{rejectNText}</a>
) : null}
</PageContainerFooter>
)}
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { INSUFFICIENT_FUNDS_ERROR_KEY } from '../../../helpers/constants/error-k
import Typography from '../../ui/typography';
import { TYPOGRAPHY } from '../../../helpers/constants/design-system';

import HardwareConnectivityPopover from '../../../pages/confirm-transaction-base/hardware-connectivity/hardware-connectivity-popover';
import EnableEIP1559V2Notice from './enableEIP1559V2-notice';
import {
ConfirmPageContainerHeader,
Expand Down Expand Up @@ -97,6 +98,10 @@ export default class ConfirmPageContainer extends Component {
nativeCurrency: PropTypes.string,
showBuyModal: PropTypes.func,
isBuyableChain: PropTypes.bool,
// Hardware
showHardwareConnectionContents: PropTypes.bool,
showHardwareConnectionAdvancedPopover: PropTypes.bool,
closeHardwareConnectionAdvancedPopover: PropTypes.func,
};

render() {
Expand Down Expand Up @@ -153,6 +158,9 @@ export default class ConfirmPageContainer extends Component {
showBuyModal,
isBuyableChain,
networkIdentifier,
showHardwareConnectionContents,
showHardwareConnectionAdvancedPopover,
closeHardwareConnectionAdvancedPopover,
} = this.props;

const showAddToAddressDialog =
Expand Down Expand Up @@ -261,6 +269,9 @@ export default class ConfirmPageContainer extends Component {
toAddress={toAddress}
transactionType={currentTransaction.type}
isBuyableChain={isBuyableChain}
hideConfirmPageContainerSummaryAndButtons={
showHardwareConnectionContents
}
/>
)}
{shouldDisplayWarning && errorKey === INSUFFICIENT_FUNDS_ERROR_KEY && (
Expand Down Expand Up @@ -330,6 +341,11 @@ export default class ConfirmPageContainer extends Component {
<AdvancedGasFeePopover />
</>
)}
{showHardwareConnectionAdvancedPopover ? (
<HardwareConnectivityPopover
onClose={closeHardwareConnectionAdvancedPopover}
/>
) : null}
</div>
</GasFeeContextProvider>
);
Expand Down
121 changes: 121 additions & 0 deletions ui/components/app/ledger-transport-dropdown/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useI18nContext } from '../../../hooks/useI18nContext';

import Dialog from '../../ui/dialog';
import Button from '../../ui/button';
import Dropdown from '../../ui/dropdown/dropdown';

import {
LEDGER_TRANSPORT_TYPES,
LEDGER_USB_VENDOR_ID,
} from '../../../../shared/constants/hardware-wallets';
import ZENDESK_URLS from '../../../helpers/constants/zendesk-url';

import {
getLedgerTransportType,
doesUserHaveALedgerAccount,
} from '../../../ducks/metamask/metamask';

import { setLedgerTransportPreference } from '../../../store/actions';

export default function LedgerTransportDropdown() {
const [showLedgerTransportWarning, setShowLedgerTransportWarning] = useState(
false,
);

const [ledgerTransportType, setLedgerTransportType] = useState(
useSelector(getLedgerTransportType),
);

const dispatch = useDispatch();

const t = useI18nContext();

const userHasALedgerAccount = useSelector(doesUserHaveALedgerAccount);

const LEDGER_TRANSPORT_NAMES = {
LIVE: t('ledgerLive'),
WEBHID: t('webhid'),
U2F: t('u2f'),
};

const transportTypeOptions = [
{
name: LEDGER_TRANSPORT_NAMES.LIVE,
value: LEDGER_TRANSPORT_TYPES.LIVE,
},
{
name: LEDGER_TRANSPORT_NAMES.U2F,
value: LEDGER_TRANSPORT_TYPES.U2F,
},
];

if (window.navigator.hid) {
transportTypeOptions.push({
name: LEDGER_TRANSPORT_NAMES.WEBHID,
value: LEDGER_TRANSPORT_TYPES.WEBHID,
});
}

const recommendedLedgerOption = window.navigator.hid
? LEDGER_TRANSPORT_NAMES.WEBHID
: LEDGER_TRANSPORT_NAMES.U2F;

return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{t('preferredLedgerConnectionType')}</span>
<div className="settings-page__content-description">
{t('ledgerConnectionPreferenceDescription', [
recommendedLedgerOption,
<Button
key="ledger-connection-settings-learn-more"
type="link"
href={ZENDESK_URLS.HARDWARE_WALLET_HELP}
target="_blank"
rel="noopener noreferrer"
className="settings-page__inline-link"
>
{t('learnMore')}
</Button>,
])}
</div>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Dropdown
id="ledger-transport-dropdown"
options={transportTypeOptions}
selectedOption={ledgerTransportType}
onChange={async (transportType) => {
if (
ledgerTransportType === LEDGER_TRANSPORT_TYPES.LIVE &&
transportType === LEDGER_TRANSPORT_TYPES.WEBHID
) {
setShowLedgerTransportWarning(true);
}
setLedgerTransportType(transportType);
dispatch(setLedgerTransportPreference(transportType));
if (
transportType === LEDGER_TRANSPORT_TYPES.WEBHID &&
userHasALedgerAccount
) {
await window.navigator.hid.requestDevice({
filters: [{ vendorId: LEDGER_USB_VENDOR_ID }],
});
}
}}
/>
{showLedgerTransportWarning ? (
<Dialog type="message">
<div className="settings-page__content-item-dialog">
{t('ledgerTransportChangeWarning')}
</div>
</Dialog>
) : null}
</div>
</div>
</div>
);
}
15 changes: 15 additions & 0 deletions ui/components/app/ledger-transport-dropdown/index.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import LedgerTransportDropdown from '.';

export default {
title: 'Components/App/LedgerTransportDropdown',
id: __filename,
};

export const DefaultStory = (args) => {
return <LedgerTransportDropdown {...args} />;
};

DefaultStory.storyName = 'Default';

DefaultStory.args = {};
1 change: 1 addition & 0 deletions ui/components/ui/actionable-message/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@

&--success {
border: 1px solid var(--color-success-default);
color: var(--color-success-default);

&::before {
background: var(--color-success-muted);
Expand Down
2 changes: 2 additions & 0 deletions ui/helpers/constants/zendesk-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const ZENDESK_URLS = {
'https://metamask.zendesk.com/hc/en-us/articles/360060826432-What-is-a-Secret-Recovery-Phrase-and-how-to-keep-your-crypto-wallet-secure',
PASSWORD_ARTICLE:
'https://metamask.zendesk.com/hc/en-us/articles/4404722782107',
HARDWARE_WALLET_HELP:
'https://metamask.zendesk.com/hc/en-us/articles/4408552261275-Hardware-Wallet-Hub',
};

export default ZENDESK_URLS;
Loading