diff --git a/packages/suite-web/e2e/cypress.config.ts b/packages/suite-web/e2e/cypress.config.ts index 42b20e8706d..38fd9d6da4a 100644 --- a/packages/suite-web/e2e/cypress.config.ts +++ b/packages/suite-web/e2e/cypress.config.ts @@ -158,7 +158,7 @@ export default defineConfig({ return null; }, stealBridgeSession: async () => { - const bridge = new BridgeTransport({ messages }); + const bridge = new BridgeTransport({ messages, id: 'foo-bar' }); await bridge.init(); const enumerateRes = await bridge.enumerate(); if (!enumerateRes.success) return null; diff --git a/packages/suite/src/components/suite/ConnectDevicePrompt.tsx b/packages/suite/src/components/suite/ConnectDevicePrompt.tsx index 84bf16bc735..f872259950e 100644 --- a/packages/suite/src/components/suite/ConnectDevicePrompt.tsx +++ b/packages/suite/src/components/suite/ConnectDevicePrompt.tsx @@ -76,6 +76,8 @@ const getMessageId = ({ return isDesktop() ? 'TR_NO_TRANSPORT_DESKTOP' : 'TR_NO_TRANSPORT'; case 'device-bootloader': return 'TR_DEVICE_CONNECTED_BOOTLOADER'; + case 'device-unacquired': + return 'TR_DEVICE_CONNECTED_UNACQUIRED'; default: { if (connected) { return !showWarning ? 'TR_DEVICE_CONNECTED' : 'TR_DEVICE_CONNECTED_WRONG_STATE'; diff --git a/packages/suite/src/components/suite/Preloader/__tests__/Preloader.test.tsx b/packages/suite/src/components/suite/Preloader/__tests__/Preloader.test.tsx index 1561665e937..f1fd1ce740f 100644 --- a/packages/suite/src/components/suite/Preloader/__tests__/Preloader.test.tsx +++ b/packages/suite/src/components/suite/Preloader/__tests__/Preloader.test.tsx @@ -189,7 +189,10 @@ describe('Preloader component', () => { it('Unacquired device', () => { const device: DeepPartial = { - selectedDevice: { type: 'unacquired' }, + selectedDevice: { + transportSessionOwner: 'foo', + type: 'unacquired', + }, }; const store = initStore( @@ -210,7 +213,10 @@ describe('Preloader component', () => { it('Unreadable device: webusb HID', () => { const device: DeepPartial = { - selectedDevice: { type: 'unreadable', error: 'LIBUSB_ERROR_ACCESS' }, + selectedDevice: { + type: 'unreadable', + error: 'LIBUSB_ERROR_ACCESS', + }, }; const store = initStore( @@ -233,7 +239,10 @@ describe('Preloader component', () => { jest.spyOn(envUtils, 'isLinux').mockImplementation(() => true); const device: DeepPartial = { - selectedDevice: { type: 'unreadable', error: 'LIBUSB_ERROR_ACCESS' }, + selectedDevice: { + type: 'unreadable', + error: 'LIBUSB_ERROR_ACCESS', + }, }; const store = initStore( @@ -256,7 +265,10 @@ describe('Preloader component', () => { jest.spyOn(envUtils, 'isLinux').mockImplementation(() => false); const device: DeepPartial = { - selectedDevice: { type: 'unreadable', error: 'LIBUSB_ERROR_ACCESS' }, + selectedDevice: { + type: 'unreadable', + error: 'LIBUSB_ERROR_ACCESS', + }, }; const store = initStore( @@ -277,7 +289,10 @@ describe('Preloader component', () => { it('Unreadable device: unknown error', () => { const device: DeepPartial = { - selectedDevice: { type: 'unreadable', error: 'Unexpected error' }, + selectedDevice: { + type: 'unreadable', + error: 'Unexpected error', + }, }; const store = initStore( @@ -298,7 +313,10 @@ describe('Preloader component', () => { it('Unknown device (should never happen)', () => { const device: DeepPartial = { - selectedDevice: { features: undefined }, + selectedDevice: { + transportSessionOwner: 'foo', + features: undefined, + }, }; const store = initStore( @@ -319,7 +337,10 @@ describe('Preloader component', () => { it('Seedless device', () => { const device: DeepPartial = { - selectedDevice: { mode: 'seedless', features: {} }, + selectedDevice: { + mode: 'seedless', + features: {}, + }, }; const store = initStore( @@ -365,7 +386,10 @@ describe('Preloader component', () => { it('Not initialized device', () => { const device: DeepPartial = { - selectedDevice: { mode: 'initialize', features: {} }, + selectedDevice: { + mode: 'initialize', + features: {}, + }, }; const store = initStore( @@ -387,7 +411,10 @@ describe('Preloader component', () => { it('Bootloader device with installed firmware', () => { const device: DeepPartial = { - selectedDevice: { mode: 'bootloader', features: { firmware_present: true } }, + selectedDevice: { + mode: 'bootloader', + features: { firmware_present: true }, + }, }; const store = initStore( @@ -409,7 +436,10 @@ describe('Preloader component', () => { it('Bootloader device without firmware', () => { const device: DeepPartial = { - selectedDevice: { mode: 'bootloader', features: { firmware_present: false } }, + selectedDevice: { + mode: 'bootloader', + features: { firmware_present: false }, + }, }; const store = initStore( @@ -431,7 +461,10 @@ describe('Preloader component', () => { it('Required FW update device', () => { const device: DeepPartial = { - selectedDevice: { firmware: 'required', features: {} }, + selectedDevice: { + firmware: 'required', + features: {}, + }, }; const store = initStore( diff --git a/packages/suite/src/components/suite/PrerequisitesGuide/DeviceAcquire.tsx b/packages/suite/src/components/suite/PrerequisitesGuide/DeviceAcquire.tsx index dec9539e4dd..8e818d33bf9 100644 --- a/packages/suite/src/components/suite/PrerequisitesGuide/DeviceAcquire.tsx +++ b/packages/suite/src/components/suite/PrerequisitesGuide/DeviceAcquire.tsx @@ -8,7 +8,7 @@ import { Translation, TroubleshootingTips } from 'src/components/suite'; import { useDevice, useDispatch } from 'src/hooks/suite'; export const DeviceAcquire = () => { - const { isLocked } = useDevice(); + const { isLocked, device } = useDevice(); const dispatch = useDispatch(); const isDeviceLocked = isLocked(); @@ -25,6 +25,21 @@ export const DeviceAcquire = () => { ); const tips = [ + { + key: 'device-used-elsewhere', + heading: , + description: device?.transportSessionOwner ? ( + + ) : ( + // legacy bridge does not share transportSessionOwner information + + ), + }, { key: 'device-acquire', heading: , diff --git a/packages/suite/src/support/extraDependencies.ts b/packages/suite/src/support/extraDependencies.ts index a534bd99ebc..bd53f6fc915 100644 --- a/packages/suite/src/support/extraDependencies.ts +++ b/packages/suite/src/support/extraDependencies.ts @@ -38,6 +38,7 @@ import { } from '@suite-common/token-definitions'; import { selectSuiteSettings } from '../reducers/suite/suiteReducer'; import { addWalletThunk, openSwitchDeviceDialog } from 'src/actions/wallet/addWalletThunk'; +import { isDesktop } from '@trezor/env-utils'; const connectSrc = resolveStaticPath('connect/'); // 'https://localhost:8088/'; @@ -50,7 +51,7 @@ const connectInitSettings = { popup: false, manifest: { email: 'info@trezor.io', - appUrl: '@trezor/suite', + appUrl: isDesktop() ? '@trezor/suite' : window.origin, }, sharedLogger: false, enableFirmwareHashCheck: true, diff --git a/packages/suite/src/support/messages.ts b/packages/suite/src/support/messages.ts index 79b516674f1..362218b31bd 100644 --- a/packages/suite/src/support/messages.ts +++ b/packages/suite/src/support/messages.ts @@ -6788,6 +6788,20 @@ export default defineMessages({ id: 'TR_DEVICE_CONNECTED_BOOTLOADER_RECONNECT_IN_NORMAL_NO_TOUCH', defaultMessage: 'Reconnect the device without touching the screen.', }, + TR_DEVICE_CONNECTED_UNACQUIRED: { + id: 'TR_DEVICE_CONNECTED_UNACQUIRED', + defaultMessage: 'Device used elsewhere', + }, + TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION: { + id: 'TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION', + defaultMessage: + 'App {transportSessionOwner} might be using the device right now. You may acquire the device for yourself.', + }, + TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP: { + id: 'TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP', + defaultMessage: + 'Another app might be using the device right now. You may acquire the device for yourself.', + }, TR_WIPE_OR_UPDATE: { id: 'TR_WIPE_OR_UPDATE', defaultMessage: 'Reset device or update firmware', diff --git a/suite-common/test-utils/src/mocks.ts b/suite-common/test-utils/src/mocks.ts index bebf35101fc..14931e11233 100644 --- a/suite-common/test-utils/src/mocks.ts +++ b/suite-common/test-utils/src/mocks.ts @@ -147,7 +147,6 @@ type StringPath = Omit & { path */ const getConnectDevice = (dev?: Partial>, feat?: Partial): Device => { const path = DeviceUniquePath(dev?.path ?? '1'); - if (dev && typeof dev.type === 'string' && dev.type === 'unreadable') { return { type: 'unreadable', @@ -164,6 +163,7 @@ const getConnectDevice = (dev?: Partial>, feat?: Partial