Skip to content

Commit

Permalink
chore(suite): rework UnreadableDevice troubleshooting
Browse files Browse the repository at this point in the history
  • Loading branch information
mroz22 committed Oct 30, 2024
1 parent a54bdff commit 75af574
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ jest.mock('cross-fetch', () => ({
default: () => Promise.resolve({ ok: false }),
}));

// mock desktopApi
jest.mock('@trezor/suite-desktop-api', () => ({
__esModule: true,
desktopApi: {
getBridgeStatus: () =>
Promise.resolve({ success: true, payload: { service: true, process: true } }),
getBridgeSettings: () => Promise.resolve({ success: true, payload: { enabled: true } }),
on: (_event: string, _cb: any) => {},
removeAllListeners: (_event: string) => {},
},
}));

// jest.mock('@firmware-components/ReconnectDevicePrompt', () => ({
// __esModule: true, // export as module
// default: ({ children }: any) => <div data-testid="box">{children}</div>,
Expand Down Expand Up @@ -215,7 +227,8 @@ describe('Preloader component', () => {
const device: DeepPartial<AppState['device']> = {
selectedDevice: {
type: 'unreadable',
error: 'LIBUSB_ERROR_ACCESS',
error: 'unable to open device',
transportDescriptorType: 0,
},
};

Expand All @@ -230,7 +243,7 @@ describe('Preloader component', () => {
const { unmount } = renderWithProviders(store, <Index app={store.getState().router.app} />);

expect(findByTestId('@connect-device-prompt')).not.toBeNull();
expect(findByTestId('@connect-device-prompt/unreadable-hid')).not.toBeNull();
expect(findByTestId('@connect-device-prompt/unreadable-unknown')).not.toBeNull();

unmount();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import { useState, MouseEvent } from 'react';
import { Button } from '@trezor/components';
import { desktopApi } from '@trezor/suite-desktop-api';
import { isDesktop, isLinux } from '@trezor/env-utils';
import { notificationsActions } from '@suite-common/toast-notifications';
import { selectDevice } from '@suite-common/wallet-core';

import { Translation, TroubleshootingTips, UdevDownload } from 'src/components/suite';
import {
TROUBLESHOOTING_TIP_BRIDGE_STATUS,
TROUBLESHOOTING_TIP_SUITE_DESKTOP,
TROUBLESHOOTING_TIP_CABLE,
TROUBLESHOOTING_TIP_USB,
TROUBLESHOOTING_TIP_DIFFERENT_COMPUTER,
TROUBLESHOOTING_TIP_UNREADABLE_HID,
TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE,
TROUBLESHOOTING_TIP_RECONNECT,
} from 'src/components/suite/troubleshooting/tips';
import { useDispatch } from 'src/hooks/suite';
import { notificationsActions } from '@suite-common/toast-notifications';
import { useSelector, useDispatch } from 'src/hooks/suite';
import type { TrezorDevice } from 'src/types/suite';

// linux web
Expand Down Expand Up @@ -99,28 +101,44 @@ const UdevDesktop = () => {

interface DeviceUnreadableProps {
device?: TrezorDevice; // this should be actually UnreadableDevice, but it is not worth type casting
isWebUsbTransport: boolean;
}

// We don't really know what happened, show some generic help and provide link to contact a support
export const DeviceUnreadable = ({ device, isWebUsbTransport }: DeviceUnreadableProps) => {
if (isWebUsbTransport) {
// only install bridge will help (webusb + HID device)
return (
<TroubleshootingTips
label={<Translation id="TR_TROUBLESHOOTING_UNREADABLE_WEBUSB" />}
items={[TROUBLESHOOTING_TIP_BRIDGE_STATUS, TROUBLESHOOTING_TIP_SUITE_DESKTOP]}
offerWebUsb
data-testid="@connect-device-prompt/unreadable-hid"
/>
);
}
/**
* Device was detected but @trezor/connect was not able to communicate with it. Reasons could be:
* - initial read from device (GetFeatures) failed because of some de-synchronization or clash with another application
* - device can't be communicated with using currently used transport (eg. hid / node bridge + webusb)
* - missing udev rule on linux
*/
export const DeviceUnreadable = ({ device }: DeviceUnreadableProps) => {
const selectedDevice = useSelector(selectDevice);

// this error is dispatched by trezord when udev rules are missing
if (isLinux() && device?.error === 'LIBUSB_ERROR_ACCESS') {
return <> {isDesktop() ? <UdevDesktop /> : <UdevWeb />}</>;
}

// generic troubleshooting tips
const items = [
// closing other apps and reloading should be the first step. Either we might have made a bug and let two apps to talk
// to device at the same time or there might be another application in the wild not really playing according to our rules
TROUBLESHOOTING_TIP_RECONNECT,
// if on web - try installing desktop. this takes you to using bridge which should be more powerful than WebUSB
TROUBLESHOOTING_TIP_SUITE_DESKTOP,
// unfortunately we have seen reports that even old bridge might not be enough for some Windows users. So the only chance
// is using another computer, or maybe it would be better to say another OS
TROUBLESHOOTING_TIP_DIFFERENT_COMPUTER,
];

// only for unreadable HID devices
if (selectedDevice?.transportDescriptorType === 0) {
// If even this did not work, go to support or knowledge base
// 'If the last time you updated your device firmware was in 2019 and earlier please follow instructions in <a>the knowledge base</a>',
items.push(TROUBLESHOOTING_TIP_UNREADABLE_HID);
// you might have a very old device which is no longer supported current bridge
// if on desktop - try toggling between the 2 bridges we have available
items.push(TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE);
}

return (
<TroubleshootingTips
label={
Expand All @@ -129,12 +147,7 @@ export const DeviceUnreadable = ({ device, isWebUsbTransport }: DeviceUnreadable
values={{ error: device?.error }}
/>
}
items={[
TROUBLESHOOTING_TIP_CABLE,
TROUBLESHOOTING_TIP_USB,
TROUBLESHOOTING_TIP_DIFFERENT_COMPUTER,
]}
offerWebUsb={isWebUsbTransport}
items={items}
data-testid="@connect-device-prompt/unreadable-unknown"
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ export const PrerequisitesGuide = ({ allowSwitchDevice }: PrerequisitesGuideProp
case 'device-unacquired':
return <DeviceAcquire />;
case 'device-unreadable':
return (
<DeviceUnreadable device={device} isWebUsbTransport={isWebUsbTransport} />
);
return <DeviceUnreadable device={device} />;
case 'device-unknown':
return <DeviceUnknown />;
case 'device-seedless':
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
import { Translation, TroubleshootingTips } from 'src/components/suite';

import {
TROUBLESHOOTING_TIP_SUITE_DESKTOP,
TROUBLESHOOTING_TIP_RESTART_COMPUTER,
TROUBLESHOOTING_TIP_WEBUSB_ENVIRONMENT,
} from 'src/components/suite/troubleshooting/tips';

export const Transport = () => (
// No transport layer (bridge/webUSB) is available
// On web it makes sense to
// - offer downloading Trezor Suite desktop, or
// - use a browser that supports WebUSB
// Desktop app should have Bridge transport layer available as it is built-in, if it is not available we fucked up something.
<TroubleshootingTips
label={<Translation id="TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED" />}
items={[
TROUBLESHOOTING_TIP_WEBUSB_ENVIRONMENT,
TROUBLESHOOTING_TIP_SUITE_DESKTOP,
TROUBLESHOOTING_TIP_RESTART_COMPUTER,
]}
data-testid="@connect-device-prompt/bridge-not-running"
/>
);
export const Transport = () => {
const items = [
TROUBLESHOOTING_TIP_WEBUSB_ENVIRONMENT,
TROUBLESHOOTING_TIP_SUITE_DESKTOP,
TROUBLESHOOTING_TIP_RESTART_COMPUTER,
];

return (
// No transport layer (bridge/webUSB) is available
// On web it makes sense to
// - offer downloading Trezor Suite desktop, or
// - use a browser that supports WebUSB
// Desktop app should have Bridge transport layer available as it is built-in, if it is not available we fucked up something.
<TroubleshootingTips
label={<Translation id="TR_TROUBLESHOOTING_DEVICE_NOT_DETECTED" />}
items={items}
data-testid="@connect-device-prompt/bridge-not-running"
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { typography } from '@trezor/theme';
import { TrezorLink } from 'src/components/suite';
import { Translation } from 'src/components/suite/Translation';
import { useOpenSuiteDesktop } from 'src/hooks/suite/useOpenSuiteDesktop';
import { useBridgeDesktopApi } from 'src/hooks/suite/useBridgeDesktopApi';
import { useSelector } from 'src/hooks/suite';

const Wrapper = styled.div`
export const Wrapper = styled.div`
a {
${typography.hint};
}
Expand Down Expand Up @@ -45,3 +47,34 @@ export const BridgeStatus = () => (
/>
</Wrapper>
);

export const BridgeToggle = () => {
const { changeBridgeSettings, bridgeSettings } = useBridgeDesktopApi();
const transport = useSelector(state => state.suite.transport);

if (!bridgeSettings) return null;

return (
<Wrapper>
<Translation
id="TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION"
values={{
currentVersion: transport?.version,
a: chunks => (
<TrezorLink
variant="underline"
onClick={() => {
changeBridgeSettings({
...bridgeSettings,
legacy: !bridgeSettings?.legacy,
});
}}
>
{chunks}
</TrezorLink>
),
}}
/>
</Wrapper>
);
};
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { isWeb, isDesktop, isLinux, isAndroid } from '@trezor/env-utils';
import { TREZOR_SUPPORT_DEVICE_URL } from '@trezor/urls';

import { TrezorLink } from 'src/components/suite';
import { Translation } from 'src/components/suite/Translation';

import { BridgeStatus, SuiteDesktopTip } from './BridgeTip';
import { BridgeStatus, SuiteDesktopTip, BridgeToggle, Wrapper } from './BridgeTip';
import { UdevDescription } from './UdevDescription';

export const TROUBLESHOOTING_TIP_BRIDGE_STATUS = {
Expand All @@ -19,13 +21,39 @@ export const TROUBLESHOOTING_TIP_WEBUSB_ENVIRONMENT = {
hide: !isWeb(),
};

export const TROUBLESHOOTING_TIP_UNREADABLE_HID = {
key: 'unreadable-hid',
heading: <Translation id="TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE" />,
description: (
<Wrapper>
<Translation
id="TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION"
values={{
a: chunks => (
<TrezorLink variant="underline" href={TREZOR_SUPPORT_DEVICE_URL}>
{chunks}
</TrezorLink>
),
}}
/>
</Wrapper>
),
};

export const TROUBLESHOOTING_TIP_SUITE_DESKTOP = {
key: 'suite-desktop',
heading: <Translation id="TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE" />,
description: <SuiteDesktopTip />,
hide: !isWeb(),
};

export const TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE = {
key: 'suite-desktop',
heading: <Translation id="TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE" />,
description: <BridgeToggle />,
hide: isWeb() || isAndroid(),
};

export const TROUBLESHOOTING_TIP_CABLE = {
key: 'cable',
heading: <Translation id="TR_TROUBLESHOOTING_TIP_CABLE_TITLE" />,
Expand Down
24 changes: 19 additions & 5 deletions packages/suite/src/support/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7147,6 +7147,15 @@ export default defineMessages({
'Only Chromium-based browsers currently allow direct communication with USB devices.',
id: 'TR_TROUBLESHOOTING_TIP_BROWSER_WEBUSB_DESCRIPTION',
},
TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE: {
defaultMessage: 'You may be using a very old Trezor model',
id: 'TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_TITLE',
},
TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION: {
defaultMessage:
'If the last time you updated your device firmware was in 2019 and earlier please follow instructions in <a>the knowledge base</a>',
id: 'TR_TROUBLESHOOTING_TIP_UNREADABLE_HID_DESCRIPTION',
},
TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE: {
id: 'TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TITLE',
defaultMessage: 'Use the Trezor Suite desktop app',
Expand All @@ -7155,6 +7164,15 @@ export default defineMessages({
id: 'TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_DESCRIPTION',
defaultMessage: 'Run the <a>Trezor Suite</a> desktop app',
},
TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE: {
id: 'TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_TITLE',
defaultMessage: 'Use another version of Trezor Bridge',
},
TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION: {
id: 'TR_TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE_DESCRIPTION',
defaultMessage:
'<a>Click to toggle</a> an alternative bridge implementation. Current version: ({currentVersion})',
},
TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION: {
id: 'TR_TROUBLESHOOTING_TIP_UDEV_INSTALL_DESCRIPTION',
defaultMessage:
Expand All @@ -7173,6 +7191,7 @@ export default defineMessages({
'After closing other browser tabs and windows, try quitting and reopening Trezor Suite.',
id: 'TR_TROUBLESHOOTING_CLOSE_TABS_DESCRIPTION_DESKTOP',
},

TR_TROUBLESHOOTING_TIP_CABLE_TITLE: {
id: 'TR_TROUBLESHOOTING_TIP_CABLE_TITLE',
defaultMessage: 'Try a different cable',
Expand Down Expand Up @@ -7208,11 +7227,6 @@ export default defineMessages({
defaultMessage:
'Restarting your computer may fix the communication issue between your browser and device.',
},
TR_TROUBLESHOOTING_UNREADABLE_WEBUSB: {
id: 'TR_TROUBLESHOOTING_UNREADABLE_WEBUSB',
defaultMessage:
"Your device is connected properly, but your browser can't communicate with it at the moment. You need to install Trezor Bridge.",
},
TR_TROUBLESHOOTING_UNREADABLE_UDEV: {
id: 'TR_TROUBLESHOOTING_UNREADABLE_UDEV',
defaultMessage: 'Missing udev rules',
Expand Down

0 comments on commit 75af574

Please sign in to comment.