Skip to content
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

chore(suite): unreadable device tips improvements #14637

Merged
merged 6 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions packages/connect/src/device/Device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export class Device extends TypedEmitter<DeviceEvents> {
public readonly protocol: TransportProtocol;
private readonly transportPath;
private readonly transportSessionOwner;
private readonly transportDescriptorType;
private session;
private isLocalSession;

Expand Down Expand Up @@ -216,6 +217,7 @@ export class Device extends TypedEmitter<DeviceEvents> {
this.transport = transport;
this.transportPath = descriptor.path;
this.transportSessionOwner = descriptor.sessionOwner;
this.transportDescriptorType = descriptor.type;

this.session = descriptor.session;
this.isLocalSession = false;
Expand Down Expand Up @@ -1143,6 +1145,7 @@ export class Device extends TypedEmitter<DeviceEvents> {
type: 'unreadable',
error: this.unreadableError, // provide error details
label: 'Unreadable device',
transportDescriptorType: this.transportDescriptorType,
};
}
if (this.isUnacquired()) {
Expand Down
4 changes: 4 additions & 0 deletions packages/connect/src/types/device.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Descriptor } from '@trezor/transport';
import type { PROTO } from '../constants';
import type { ReleaseInfo } from './firmware';

Expand Down Expand Up @@ -97,6 +98,7 @@ export type KnownDevice = BaseDevice & {
// Maybe add AuthenticityCheck result here?
};
transportSessionOwner?: undefined;
transportDescriptorType?: typeof undefined;
};

export type UnknownDevice = BaseDevice & {
Expand All @@ -117,6 +119,7 @@ export type UnknownDevice = BaseDevice & {
unavailableCapabilities?: typeof undefined;
availableTranslations?: typeof undefined;
transportSessionOwner?: string;
transportDescriptorType?: typeof undefined;
};

export type UnreadableDevice = BaseDevice & {
Expand All @@ -137,6 +140,7 @@ export type UnreadableDevice = BaseDevice & {
unavailableCapabilities?: typeof undefined;
availableTranslations?: typeof undefined;
transportSessionOwner?: undefined;
transportDescriptorType: Descriptor['type'];
};

export type Device = KnownDevice | UnknownDevice | UnreadableDevice;
Expand Down
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 @@ -6,6 +6,7 @@ import { isDesktop } from '@trezor/env-utils';

import { Translation, TroubleshootingTips } from 'src/components/suite';
import { useDevice, useDispatch } from 'src/hooks/suite';
import { TROUBLESHOOTING_TIP_RECONNECT } from 'src/components/suite/troubleshooting/tips';

export const DeviceAcquire = () => {
const { isLocked, device } = useDevice();
Expand Down Expand Up @@ -53,19 +54,7 @@ export const DeviceAcquire = () => {
/>
),
},
{
key: 'device-reconnect',
heading: <Translation id="TR_RECONNECT_YOUR_DEVICE" />,
description: (
<Translation
id={
isDesktop()
? 'TR_RECONNECT_DEVICE_DESCRIPTION_DESKTOP'
: 'TR_RECONNECT_DEVICE_DESCRIPTION'
}
/>
),
},
TROUBLESHOOTING_TIP_RECONNECT,
];

return (
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"
/>
);
Comment on lines -8 to -23
Copy link
Contributor Author

Choose a reason for hiding this comment

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

no changes here. sorry for diff

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>
);
};
46 changes: 44 additions & 2 deletions packages/suite/src/components/suite/troubleshooting/tips/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { isWeb, isLinux, isAndroid } from '@trezor/env-utils';
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 Expand Up @@ -57,3 +85,17 @@ export const TROUBLESHOOTING_TIP_UDEV = {
description: <UdevDescription />,
hide: !isLinux(),
};

export const TROUBLESHOOTING_TIP_RECONNECT = {
key: 'device-reconnect',
heading: <Translation id="TR_RECONNECT_YOUR_DEVICE" />,
description: (
<Translation
id={
isDesktop()
? 'TR_RECONNECT_DEVICE_DESCRIPTION_DESKTOP'
: 'TR_RECONNECT_DEVICE_DESCRIPTION'
}
/>
),
};
Loading
Loading