diff --git a/packages/connect/src/core/index.ts b/packages/connect/src/core/index.ts index c2deb1238d3..62c96354a6d 100644 --- a/packages/connect/src/core/index.ts +++ b/packages/connect/src/core/index.ts @@ -714,8 +714,13 @@ const onCallDevice = async ( const response = messageResponse; if (response) { - await device.cleanup(); + const shouldReleaseSession = + response.success || + (!response.success && + // @ts-expect-error + response?.payload?.error !== 'device disconnected during action'); + await device.cleanup(shouldReleaseSession); if (useCoreInPopup) { // We need to send response before closing popup sendCoreMessage(response); diff --git a/packages/connect/src/device/Device.ts b/packages/connect/src/device/Device.ts index 7571c969f87..a21aa5eee85 100644 --- a/packages/connect/src/device/Device.ts +++ b/packages/connect/src/device/Device.ts @@ -327,13 +327,15 @@ export class Device extends TypedEmitter { this.keepTransportSession = false; } - async cleanup() { + async cleanup(release = true) { // remove all listeners this.eventNames().forEach(e => this.removeAllListeners(e as keyof DeviceEvents)); // make sure that Device_CallInProgress will not be thrown delete this.runPromise; - await this.release(); + if (release) { + await this.release(); + } } // call only once, right after device creation @@ -349,32 +351,33 @@ export class Device extends TypedEmitter { try { await this.run(); } catch (error) { + _log.warn(`device.run error.message: ${error.message}, code: ${error.code}`); + if ( error.code === 'Device_NotFound' || error.message === TRANSPORT_ERROR.DEVICE_NOT_FOUND || error.message === TRANSPORT_ERROR.DEVICE_DISCONNECTED_DURING_ACTION || - error.message === TRANSPORT_ERROR.UNEXPECTED_ERROR || error.message === TRANSPORT_ERROR.DESCRIPTOR_NOT_FOUND || error.message === TRANSPORT_ERROR.HTTP_ERROR // bridge died during device initialization ) { // disconnected, do nothing } else if ( + // we don't know what really happened + error.message === TRANSPORT_ERROR.UNEXPECTED_ERROR || + // someone else took the device at the same time error.message === TRANSPORT_ERROR.SESSION_WRONG_PREVIOUS || - error.code === 'Device_UsedElsewhere' // most common error - someone else took the device at the same time + // device had some session when first seen -> we do not read it so that we don't interrupt somebody else's flow + error.code === 'Device_UsedElsewhere' || + // TODO: is this needed? can't I just use transport error? + error.code === 'Device_InitializeFailed' ) { - // TODO needed only for TRANSPORT_ERROR.SESSION_WRONG_PREVIOUS - // this.enumerate(transport); this.emitLifecycle(DEVICE.CONNECT_UNACQUIRED); } else if ( // device was claimed by another application on transport api layer (claimInterface in usb nomenclature) but never released (releaseInterface in usb nomenclature) // the only remedy for this is to reconnect device manually - // or possibly there are 2 applications without common sessions background error.message === TRANSPORT_ERROR.INTERFACE_UNABLE_TO_OPEN_DEVICE || // catch one of trezord LIBUSB_ERRORs - error.message?.indexOf(ERRORS.LIBUSB_ERROR_MESSAGE) >= 0 || - // we tried to initialize device (either automatically after enumeration or after user click) - // but it did not work out. this device is effectively unreadable and user should do something about it - error.code === 'Device_InitializeFailed' + error.message?.indexOf(ERRORS.LIBUSB_ERROR_MESSAGE) >= 0 ) { this.unreadableError = error?.message; this.emitLifecycle(DEVICE.CONNECT_UNACQUIRED); @@ -553,6 +556,7 @@ export class Device extends TypedEmitter { ]); } } catch (error) { + _log.warn('Device._runInner error: ', error.message); if ( !this.inconsistent && (error.message === 'GetFeatures timeout' || error.message === 'Unknown message') @@ -750,7 +754,7 @@ export class Device extends TypedEmitter { this._updateFeatures(message); } - async checkFirmwareHash(): Promise { + private async checkFirmwareHash(): Promise { const createFailResult = (error: FirmwareHashCheckError, errorPayload?: unknown) => ({ success: false, error, @@ -818,7 +822,7 @@ export class Device extends TypedEmitter { } } - async checkFirmwareRevision() { + private async checkFirmwareRevision() { const firmwareVersion = this.getVersion(); if (!firmwareVersion || !this.features) { diff --git a/packages/suite/src/components/suite/ConnectDevicePrompt.tsx b/packages/suite/src/components/suite/ConnectDevicePrompt.tsx index 70c7ac7acbb..8cd53af6bc3 100644 --- a/packages/suite/src/components/suite/ConnectDevicePrompt.tsx +++ b/packages/suite/src/components/suite/ConnectDevicePrompt.tsx @@ -74,8 +74,10 @@ const getMessageId = ({ return isDesktop() ? 'TR_NO_TRANSPORT_DESKTOP' : 'TR_NO_TRANSPORT'; case 'device-bootloader': return 'TR_DEVICE_CONNECTED_BOOTLOADER'; - case 'device-unacquired': + case 'device-used-elsewhere': return 'TR_DEVICE_CONNECTED_UNACQUIRED'; + case 'device-unacquired': + return 'TR_NEEDS_ATTENTION_UNABLE_TO_CONNECT'; default: { if (connected) { return !showWarning ? 'TR_DEVICE_CONNECTED' : 'TR_DEVICE_CONNECTED_WRONG_STATE'; diff --git a/packages/suite/src/components/suite/PrerequisitesGuide/DeviceAcquire.tsx b/packages/suite/src/components/suite/PrerequisitesGuide/DeviceAcquire.tsx index 5d1e53be6bb..947f62c61ec 100644 --- a/packages/suite/src/components/suite/PrerequisitesGuide/DeviceAcquire.tsx +++ b/packages/suite/src/components/suite/PrerequisitesGuide/DeviceAcquire.tsx @@ -2,14 +2,16 @@ import { MouseEventHandler } from 'react'; import { acquireDevice } from '@suite-common/wallet-core'; import { Button } from '@trezor/components'; -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'; +import { + TROUBLESHOOTING_TIP_RECONNECT, + TROUBLESHOOTING_TIP_CLOSE_ALL_TABS, +} from 'src/components/suite/troubleshooting/tips'; export const DeviceAcquire = () => { - const { isLocked, device } = useDevice(); + const { isLocked } = useDevice(); const dispatch = useDispatch(); const isDeviceLocked = isLocked(); @@ -21,45 +23,15 @@ export const DeviceAcquire = () => { const ctaButton = ( ); - const tips = [ - { - key: 'device-used-elsewhere', - heading: , - description: device?.transportSessionOwner ? ( - - ) : ( - // legacy bridge does not share transportSessionOwner information - - ), - }, - { - key: 'device-acquire', - heading: , - description: ( - - ), - }, - TROUBLESHOOTING_TIP_RECONNECT, - ]; + const tips = [TROUBLESHOOTING_TIP_CLOSE_ALL_TABS, TROUBLESHOOTING_TIP_RECONNECT]; return ( } + label={} cta={ctaButton} items={tips} /> diff --git a/packages/suite/src/components/suite/PrerequisitesGuide/DeviceUnreadable.tsx b/packages/suite/src/components/suite/PrerequisitesGuide/DeviceUnreadable.tsx index f406ab225de..3e38dcfe667 100644 --- a/packages/suite/src/components/suite/PrerequisitesGuide/DeviceUnreadable.tsx +++ b/packages/suite/src/components/suite/PrerequisitesGuide/DeviceUnreadable.tsx @@ -13,6 +13,7 @@ import { TROUBLESHOOTING_TIP_UNREADABLE_HID, TROUBLESHOOTING_TIP_SUITE_DESKTOP_TOGGLE_BRIDGE, TROUBLESHOOTING_TIP_RECONNECT, + TROUBLESHOOTING_TIP_CLOSE_ALL_TABS, } from 'src/components/suite/troubleshooting/tips'; import { useSelector, useDispatch } from 'src/hooks/suite'; import type { TrezorDevice } from 'src/types/suite'; @@ -119,16 +120,7 @@ export const DeviceUnreadable = ({ device }: DeviceUnreadableProps) => { } // 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, - ]; + const items = []; // only for unreadable HID devices if ( @@ -140,9 +132,26 @@ export const DeviceUnreadable = ({ device }: DeviceUnreadableProps) => { // 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 the knowledge base', items.push(TROUBLESHOOTING_TIP_UNREADABLE_HID); + // if on web - try installing desktop. this takes you to using bridge which should be more powerful than WebUSB. + // at the time of writing this, there is still an option to opt-in for legacy bridge in suite-desktop which can + // communicate with this device. see the next troubleshooting point + items.push(TROUBLESHOOTING_TIP_SUITE_DESKTOP); // 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); + } else { + // it might also be unreadable because device was acquired on transport layer by another app and never released. + // this should be rather exceptional case that happens only when sessions synchronization is broken or other app + // is not cooperating with us + items.push(TROUBLESHOOTING_TIP_CLOSE_ALL_TABS); + // 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 + items.push(TROUBLESHOOTING_TIP_RECONNECT); + // if on web - try installing desktop. this takes you to using bridge which should be more powerful than WebUSB + items.push(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 + items.push(TROUBLESHOOTING_TIP_DIFFERENT_COMPUTER); } return ( diff --git a/packages/suite/src/components/suite/PrerequisitesGuide/DeviceUsedElsewhere.tsx b/packages/suite/src/components/suite/PrerequisitesGuide/DeviceUsedElsewhere.tsx new file mode 100644 index 00000000000..7c8394371a7 --- /dev/null +++ b/packages/suite/src/components/suite/PrerequisitesGuide/DeviceUsedElsewhere.tsx @@ -0,0 +1,58 @@ +import { MouseEventHandler } from 'react'; + +import { acquireDevice } from '@suite-common/wallet-core'; +import { Button } from '@trezor/components'; + +import { Translation, TroubleshootingTips } from 'src/components/suite'; +import { useDevice, useDispatch } from 'src/hooks/suite'; +import { + TROUBLESHOOTING_TIP_RECONNECT, + TROUBLESHOOTING_TIP_CLOSE_ALL_TABS, +} from 'src/components/suite/troubleshooting/tips'; + +export const DeviceUsedElsewhere = () => { + const { isLocked, device } = useDevice(); + const dispatch = useDispatch(); + + const isDeviceLocked = isLocked(); + + const handleClick: MouseEventHandler = e => { + e.stopPropagation(); + dispatch(acquireDevice()); + }; + + const ctaButton = ( + + ); + + const tips = [ + { + key: 'device-used-elsewhere', + heading: , + description: ( + + ), + }, + TROUBLESHOOTING_TIP_CLOSE_ALL_TABS, + TROUBLESHOOTING_TIP_RECONNECT, + ]; + + return ( + } + cta={ctaButton} + items={tips} + /> + ); +}; diff --git a/packages/suite/src/components/suite/PrerequisitesGuide/PrerequisitesGuide.tsx b/packages/suite/src/components/suite/PrerequisitesGuide/PrerequisitesGuide.tsx index 93e33acedf2..b19368df188 100644 --- a/packages/suite/src/components/suite/PrerequisitesGuide/PrerequisitesGuide.tsx +++ b/packages/suite/src/components/suite/PrerequisitesGuide/PrerequisitesGuide.tsx @@ -26,6 +26,7 @@ import { DeviceNoFirmware } from './DeviceNoFirmware'; import { DeviceUpdateRequired } from './DeviceUpdateRequired'; import { DeviceDisconnectRequired } from './DeviceDisconnectRequired'; import { MultiShareBackupInProgress } from './MultiShareBackupInProgress'; +import { DeviceUsedElsewhere } from './DeviceUsedElsewhere'; const Wrapper = styled.div` display: flex; @@ -68,6 +69,8 @@ export const PrerequisitesGuide = ({ allowSwitchDevice }: PrerequisitesGuideProp return ; case 'device-unacquired': return ; + case 'device-used-elsewhere': + return ; case 'device-unreadable': return ; case 'device-unknown': diff --git a/packages/suite/src/components/suite/troubleshooting/tips/index.tsx b/packages/suite/src/components/suite/troubleshooting/tips/index.tsx index 81b2fe20555..958ebd3b505 100644 --- a/packages/suite/src/components/suite/troubleshooting/tips/index.tsx +++ b/packages/suite/src/components/suite/troubleshooting/tips/index.tsx @@ -99,3 +99,17 @@ export const TROUBLESHOOTING_TIP_RECONNECT = { /> ), }; + +export const TROUBLESHOOTING_TIP_CLOSE_ALL_TABS = { + key: 'device-acquire', + heading: , + description: ( + + ), +}; diff --git a/packages/suite/src/support/messages.ts b/packages/suite/src/support/messages.ts index 5c9d9b944e7..c8778124d9d 100644 --- a/packages/suite/src/support/messages.ts +++ b/packages/suite/src/support/messages.ts @@ -2087,6 +2087,10 @@ const messages = defineMessagesWithTypeCheck({ defaultMessage: 'Trezor is not readable.', id: 'TR_NEEDS_ATTENTION_UNREADABLE', }, + TR_NEEDS_ATTENTION_UNABLE_TO_CONNECT: { + defaultMessage: 'Failed to communicate with Trezor', + id: 'TR_NEEDS_ATTENTION_UNABLE_TO_CONNECT', + }, TR_UDEV_DOWNLOAD_TITLE: { defaultMessage: 'Download udev rules', id: 'TR_UDEV_DOWNLOAD_TITLE', @@ -6790,11 +6794,6 @@ const messages = defineMessagesWithTypeCheck({ defaultMessage: 'The app {transportSessionOwner} may currently be using this device. You can take control of the device if needed.', }, - TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP: { - id: 'TR_DEVICE_CONNECTED_UNACQUIRED_DESCRIPTION_UNKNOWN_APP', - defaultMessage: - 'Another app may currently be using this device. You can take control of the device if needed.', - }, TR_WIPE_OR_UPDATE: { id: 'TR_WIPE_OR_UPDATE', defaultMessage: 'Reset device or update firmware', diff --git a/packages/suite/src/utils/suite/prerequisites.ts b/packages/suite/src/utils/suite/prerequisites.ts index 5c3521dae4a..6ecad9ca3f2 100644 --- a/packages/suite/src/utils/suite/prerequisites.ts +++ b/packages/suite/src/utils/suite/prerequisites.ts @@ -28,6 +28,9 @@ export const getPrerequisiteName = ({ router, device, transport }: GetPrerequisi return 'device-disconnect-required'; } + if (device.type === 'unacquired' && device?.transportSessionOwner) + return 'device-used-elsewhere'; + // device features cannot be read, device is probably used in another window if (device.type === 'unacquired') return 'device-unacquired'; diff --git a/packages/transport/src/api/abstract.ts b/packages/transport/src/api/abstract.ts index 1ca42b102ed..c0e0e41af00 100644 --- a/packages/transport/src/api/abstract.ts +++ b/packages/transport/src/api/abstract.ts @@ -113,6 +113,7 @@ export abstract class AbstractApi extends TypedEmitter<{ | typeof ERRORS.UNEXPECTED_ERROR | typeof ERRORS.ABORTED_BY_TIMEOUT | typeof ERRORS.ABORTED_BY_SIGNAL + | typeof ERRORS.LIBUSB_ERROR_ACCESS >; /** diff --git a/packages/transport/src/api/usb.ts b/packages/transport/src/api/usb.ts index 8e1100b86c9..6f65df1d1c8 100644 --- a/packages/transport/src/api/usb.ts +++ b/packages/transport/src/api/usb.ts @@ -27,11 +27,6 @@ interface TransportInterfaceDevice { device: USBDevice; } -/** - * Local error. We cast it to "device disconnected during action" from bridge as it means the same - */ -const INTERFACE_DEVICE_DISCONNECTED = 'The device was disconnected.' as const; - export class UsbApi extends AbstractApi { chunkSize = 64; @@ -208,7 +203,7 @@ export class UsbApi extends AbstractApi { { signal, onAbort: () => device?.reset() }, ); this.logger?.debug( - `usb: device.transferIn done. status: ${res.status}, byteLength: ${res.data?.byteLength}. device: ${this.formatDeviceForLog(device)}`, + `usb: device.transferIn done. status: ${res.status}, byteLength: ${res.data?.byteLength}.`, ); if (!res.data?.byteLength) { @@ -218,11 +213,8 @@ export class UsbApi extends AbstractApi { return this.success(Buffer.from(res.data.buffer)); } catch (err) { this.logger?.error(`usb: device.transferIn error ${err}`); - if (err.message === INTERFACE_DEVICE_DISCONNECTED) { - return this.error({ error: ERRORS.DEVICE_DISCONNECTED_DURING_ACTION }); - } - return this.error({ error: ERRORS.INTERFACE_DATA_TRANSFER, message: err.message }); + return this.handleReadWriteError(err); } } @@ -245,9 +237,7 @@ export class UsbApi extends AbstractApi { ), { signal, onAbort: () => device?.reset() }, ); - this.logger?.debug( - `usb: device.transferOut done. device: ${this.formatDeviceForLog(device)}`, - ); + this.logger?.debug(`usb: device.transferOut done.`); if (result.status !== 'ok') { this.logger?.error(`usb: device.transferOut status not ok: ${result.status}`); throw new Error('transfer out status not ok'); @@ -255,12 +245,7 @@ export class UsbApi extends AbstractApi { return this.success(undefined); } catch (err) { - this.logger?.error(`usb: device.transferOut error ${err}`); - if (err.message === INTERFACE_DEVICE_DISCONNECTED) { - return this.error({ error: ERRORS.DEVICE_DISCONNECTED_DURING_ACTION }); - } - - return this.error({ error: ERRORS.INTERFACE_DATA_TRANSFER, message: err.message }); + return this.handleReadWriteError(err); } } @@ -296,6 +281,9 @@ export class UsbApi extends AbstractApi { this.logger?.debug(`usb: device.open done. device: ${this.formatDeviceForLog(device)}`); } catch (err) { this.logger?.error(`usb: device.open error ${err}`); + if (err.message.includes('LIBUSB_ERROR_ACCESS')) { + return this.error({ error: ERRORS.LIBUSB_ERROR_ACCESS }); + } return this.error({ error: ERRORS.INTERFACE_UNABLE_TO_OPEN_DEVICE, @@ -309,9 +297,7 @@ export class UsbApi extends AbstractApi { await this.abortableMethod(() => device.selectConfiguration(CONFIGURATION_ID), { signal, }); - this.logger?.debug( - `usb: device.selectConfiguration done: ${CONFIGURATION_ID}. device: ${this.formatDeviceForLog(device)}`, - ); + this.logger?.debug(`usb: device.selectConfiguration done: ${CONFIGURATION_ID}.`); } catch (err) { this.logger?.error( `usb: device.selectConfiguration error ${err}. device: ${this.formatDeviceForLog(device)}`, @@ -321,9 +307,7 @@ export class UsbApi extends AbstractApi { // reset fails on ChromeOS and windows this.logger?.debug('usb: device.reset'); await this.abortableMethod(() => device?.reset(), { signal }); - this.logger?.debug( - `usb: device.reset done. device: ${this.formatDeviceForLog(device)}`, - ); + this.logger?.debug(`usb: device.reset done.`); } catch (err) { this.logger?.error( `usb: device.reset error ${err}. device: ${this.formatDeviceForLog(device)}`, @@ -336,13 +320,9 @@ export class UsbApi extends AbstractApi { this.logger?.debug(`usb: device.claimInterface: ${interfaceId}`); // claim device for exclusive access by this app await this.abortableMethod(() => device.claimInterface(interfaceId), { signal }); - this.logger?.debug( - `usb: device.claimInterface done: ${interfaceId}. device: ${this.formatDeviceForLog(device)}`, - ); + this.logger?.debug(`usb: device.claimInterface done: ${interfaceId}.`); } catch (err) { - this.logger?.error( - `usb: device.claimInterface error ${err}. device: ${this.formatDeviceForLog(device)}`, - ); + this.logger?.error(`usb: device.claimInterface error ${err}.`); return this.error({ error: ERRORS.INTERFACE_UNABLE_TO_OPEN_DEVICE, @@ -381,13 +361,9 @@ export class UsbApi extends AbstractApi { this.logger?.debug(`usb: device.releaseInterface: ${interfaceId}`); await device.releaseInterface(interfaceId); - this.logger?.debug( - `usb: device.releaseInterface done: ${interfaceId}. device: ${this.formatDeviceForLog(device)}`, - ); + this.logger?.debug(`usb: device.releaseInterface done: ${interfaceId}.`); } catch (err) { - this.logger?.error( - `usb: releaseInterface error ${err}. device: ${this.formatDeviceForLog(device)}`, - ); + this.logger?.error(`usb: releaseInterface error ${err}.`); // ignore } } @@ -396,13 +372,9 @@ export class UsbApi extends AbstractApi { try { this.logger?.debug(`usb: device.close`); await device.close(); - this.logger?.debug( - `usb: device.close done. device: ${this.formatDeviceForLog(device)}`, - ); + this.logger?.debug(`usb: device.close done.`); } catch (err) { - this.logger?.debug( - `usb: device.close error ${err}. device: ${this.formatDeviceForLog(device)}`, - ); + this.logger?.debug(`usb: device.close error ${err}.`); return this.error({ error: ERRORS.INTERFACE_UNABLE_TO_CLOSE_DEVICE, @@ -517,6 +489,29 @@ export class UsbApi extends AbstractApi { return [hidDevices, nonHidDevices]; } + // https://github.com/trezor/trezord-go/blob/db03d99230f5b609a354e3586f1dfc0ad6da16f7/usb/libusb.go#L545 + private handleReadWriteError(err: Error) { + if ( + [ + // node usb + 'LIBUSB_TRANSFER_ERROR', + 'LIBUSB_ERROR_PIPE', + 'LIBUSB_ERROR_IO', + 'LIBUSB_ERROR_NO_DEVICE', + 'LIBUSB_ERROR_OTHER', + // web usb + ERRORS.INTERFACE_DATA_TRANSFER, + 'The device was disconnected.', + ].some(disconnectedErr => { + return err.message.includes(disconnectedErr); + }) + ) { + return this.error({ error: ERRORS.DEVICE_DISCONNECTED_DURING_ACTION }); + } + + return this.unknownError(err); + } + public dispose() { if (this.usbInterface) { this.usbInterface.onconnect = null; diff --git a/packages/transport/src/errors.ts b/packages/transport/src/errors.ts index c893926029b..80b710c4f39 100644 --- a/packages/transport/src/errors.ts +++ b/packages/transport/src/errors.ts @@ -92,3 +92,7 @@ export const ABORTED_BY_SIGNAL = 'Aborted by signal' as const; * see scheduleAction */ export const ABORTED_BY_TIMEOUT = 'Aborted by timeout' as const; +/** + * missing udev rules on linux + */ +export const LIBUSB_ERROR_ACCESS = 'LIBUSB_ERROR_ACCESS' as const; diff --git a/packages/transport/src/sessions/background.ts b/packages/transport/src/sessions/background.ts index 12918044094..20b5acf0452 100644 --- a/packages/transport/src/sessions/background.ts +++ b/packages/transport/src/sessions/background.ts @@ -118,11 +118,11 @@ export class SessionsBackground if (result && result.success && result.payload) { if ('descriptors' in result.payload) { const { descriptors } = result.payload; - setTimeout(() => this.emit('descriptors', Object.values(descriptors)), 0); + this.emit('descriptors', Object.values(descriptors)); } if ('releaseRequest' in result.payload && result.payload.releaseRequest) { const { releaseRequest } = result.payload; - setTimeout(() => this.emit('releaseRequest', releaseRequest)); + this.emit('releaseRequest', releaseRequest); } } } diff --git a/packages/transport/src/transports/abstract.ts b/packages/transport/src/transports/abstract.ts index 8ac7f51874e..5d36c138a9c 100644 --- a/packages/transport/src/transports/abstract.ts +++ b/packages/transport/src/transports/abstract.ts @@ -205,6 +205,7 @@ export abstract class AbstractTransport extends TransportEmitter { | typeof ERRORS.ABORTED_BY_TIMEOUT | typeof ERRORS.ABORTED_BY_SIGNAL | typeof ERRORS.WRONG_ENVIRONMENT + | typeof ERRORS.LIBUSB_ERROR_ACCESS >; /** diff --git a/packages/transport/src/transports/bridge.ts b/packages/transport/src/transports/bridge.ts index 664095ef2bf..25648454f25 100644 --- a/packages/transport/src/transports/bridge.ts +++ b/packages/transport/src/transports/bridge.ts @@ -380,6 +380,7 @@ export class BridgeTransport extends AbstractTransport { ERRORS.DEVICE_NOT_FOUND, ERRORS.INTERFACE_UNABLE_TO_OPEN_DEVICE, ERRORS.DEVICE_DISCONNECTED_DURING_ACTION, + ERRORS.LIBUSB_ERROR_ACCESS, ]); case '/call': case '/read': diff --git a/packages/transport/tests/apiUsb.test.ts b/packages/transport/tests/apiUsb.test.ts index f448dd57c25..86b0723bd7e 100644 --- a/packages/transport/tests/apiUsb.test.ts +++ b/packages/transport/tests/apiUsb.test.ts @@ -58,7 +58,7 @@ describe('api/usb', () => { const result = await promise; if (result.success) throw new Error('Unexpected success'); - expect(result.error).toContain('A transfer error has occurred.'); + expect(result.error).toContain('unexpected error'); expect(reset).toHaveBeenCalledTimes(1); });