From 768b339ed6a7ac55b16a73ad7f41a5d4081493fd Mon Sep 17 00:00:00 2001 From: Jiri Zbytovsky Date: Wed, 5 Feb 2025 18:29:15 +0100 Subject: [PATCH 1/3] fix(connect): gracefully handle offline FW revision check in react native --- .../src/device/checkFirmwareRevision.ts | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/connect/src/device/checkFirmwareRevision.ts b/packages/connect/src/device/checkFirmwareRevision.ts index 206d0ff31bb..672d9979001 100644 --- a/packages/connect/src/device/checkFirmwareRevision.ts +++ b/packages/connect/src/device/checkFirmwareRevision.ts @@ -6,13 +6,21 @@ import { FirmwareRelease, VersionArray } from '../types'; import { calculateRevisionForDevice } from './calculateRevisionForDevice'; import { FirmwareRevisionCheckError, FirmwareRevisionCheckResult } from '../types/device'; -/* - * error names that signify unavailable internet connection, see https://github.com/node-fetch/node-fetch/blob/main/docs/ERROR-HANDLING.md - * Only works in Suite Desktop, where `cross-fetch` uses `node-fetch` (nodeJS environment) - * In Suite Web, the errors are unfortunately indistinguishable from other errors, because they are all lumped as CORS errors +const isNodeJSNetworkError = (e: Error) => ['FetchError', 'AbortError'].includes(e.name); +const isReactNativeNetworkError = (e: Error) => + e.name === 'TypeError' && e.message.includes('Network request failed'); + +/** + * Check if an error signifies a missing fetch response (meaning network connection loss or unavailable host). + * This can only by correctly identified in nodeJS or React native runtimes (i.e. Suite Desktop main process, or Suite Lite). + * In browser runtime (Suite Web), all fetch errors are lumped together as CORS errors, therefore indistinguishable. * (even a request that had no response is CORS error, since a non-existent response does not have CORS headers) */ -const NODE_FETCH_OFFLINE_ERROR_NAMES = ['FetchError', 'AbortError'] as const; +const isNetworkError = (e: unknown): boolean => { + if (!(e instanceof Error)) return false; + + return isNodeJSNetworkError(e) || isReactNativeNetworkError(e); +}; type GetOnlineReleaseMetadataParams = { firmwareVersion: VersionArray; @@ -94,12 +102,10 @@ export const checkFirmwareRevision = async ({ } return { success: true }; - } catch (e) { - if (NODE_FETCH_OFFLINE_ERROR_NAMES.includes(e.name)) { - return failFirmwareRevisionCheck('cannot-perform-check-offline'); - } - - return failFirmwareRevisionCheck('other-error'); + } catch (e: unknown) { + return isNetworkError(e) + ? failFirmwareRevisionCheck('cannot-perform-check-offline') + : failFirmwareRevisionCheck('other-error'); } } From c86bf7da61c71276ea23f31d04fa65397f2c1e93 Mon Sep 17 00:00:00 2001 From: Jiri Zbytovsky Date: Wed, 5 Feb 2025 18:41:54 +0100 Subject: [PATCH 2/3] feat(suite): do not report FW revision offline error --- .../suite/SecurityCheck/useReportDeviceCompromised.ts | 4 ++-- packages/suite/src/constants/suite/firmware.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/suite/src/components/suite/SecurityCheck/useReportDeviceCompromised.ts b/packages/suite/src/components/suite/SecurityCheck/useReportDeviceCompromised.ts index f4f0a912ebf..574f3b2ee2d 100644 --- a/packages/suite/src/components/suite/SecurityCheck/useReportDeviceCompromised.ts +++ b/packages/suite/src/components/suite/SecurityCheck/useReportDeviceCompromised.ts @@ -5,7 +5,7 @@ import { FIRMWARE } from '@trezor/connect'; import { getFirmwareVersion } from '@trezor/device-utils'; import { isArrayMember } from '@trezor/utils'; -import { hashCheckErrorScenarios } from 'src/constants/suite/firmware'; +import { hashCheckErrorScenarios, revisionCheckErrorScenarios } from 'src/constants/suite/firmware'; import { useDevice, useSelector } from 'src/hooks/suite'; import { selectFirmwareRevisionCheckError } from 'src/reducers/suite/suiteReducer'; import { captureSentryMessage, withSentryScope } from 'src/utils/suite/sentry'; @@ -60,7 +60,7 @@ const useReportRevisionCheck = () => { const errorType = useSelector(selectFirmwareRevisionCheckError); useEffect(() => { - if (errorType !== null) { + if (errorType !== null && revisionCheckErrorScenarios[errorType].shouldReport) { reportCheckFail('Firmware revision', { ...commonData, errorType }); } }, [commonData, errorType]); diff --git a/packages/suite/src/constants/suite/firmware.ts b/packages/suite/src/constants/suite/firmware.ts index ee2904e3a83..366c0eec795 100644 --- a/packages/suite/src/constants/suite/firmware.ts +++ b/packages/suite/src/constants/suite/firmware.ts @@ -11,7 +11,7 @@ type BehaviorBaseType = { shouldReport: boolean; debugOnly?: boolean }; // will be ignored completely type SkippedBehavior = BehaviorBaseType & { type: 'skipped' }; // display a warning banner -type SoftWarningBehavior = BehaviorBaseType & { type: 'softWarning'; shouldReport: true }; +type SoftWarningBehavior = BehaviorBaseType & { type: 'softWarning' }; // display "Device Compromised" modal, after closing it display a warning banner, block receiving address type HardModalBehavior = BehaviorBaseType & { type: 'hardModal'; shouldReport: true }; @@ -24,7 +24,7 @@ type HashCheckErrorScenarios = Record export const revisionCheckErrorScenarios = { 'revision-mismatch': { type: 'hardModal', shouldReport: true }, 'firmware-version-unknown': { type: 'hardModal', shouldReport: true }, - 'cannot-perform-check-offline': { type: 'softWarning', shouldReport: true }, + 'cannot-perform-check-offline': { type: 'softWarning', shouldReport: false }, 'other-error': { type: 'softWarning', shouldReport: true }, } satisfies RevisionCheckErrorScenarios; From 1ba6a1bd7c297b149d9ee6e898999e039c3a654c Mon Sep 17 00:00:00 2001 From: Jiri Zbytovsky Date: Wed, 5 Feb 2025 18:49:19 +0100 Subject: [PATCH 3/3] feat(suite-native): do not report FW revision offline error --- suite-native/device/src/config/firmware.ts | 4 ++-- suite-native/device/src/hooks/useReportDeviceCompromised.ts | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/suite-native/device/src/config/firmware.ts b/suite-native/device/src/config/firmware.ts index 4dc57f0bda3..ddf1098c883 100644 --- a/suite-native/device/src/config/firmware.ts +++ b/suite-native/device/src/config/firmware.ts @@ -6,7 +6,7 @@ import { FirmwareRevisionCheckError } from '@trezor/connect'; */ // display a warning banner -type SoftWarningBehavior = { type: 'softWarning'; shouldReport: true }; +type SoftWarningBehavior = { type: 'softWarning'; shouldReport: boolean }; // display "Device Compromised" modal, after closing it dispaly a warning banner, block receiving address type HardModalBehavior = { type: 'hardModal'; shouldReport: true }; @@ -16,6 +16,6 @@ type RevisionCheckErrorScenarios = Record { @@ -31,7 +32,10 @@ export const useReportDeviceCompromised = () => { const revisionCheckError = useSelector(selectFirmwareRevisionCheckError); useEffect(() => { - if (revisionCheckError !== null) { + if ( + revisionCheckError !== null && + revisionCheckErrorScenarios[revisionCheckError].shouldReport + ) { reportCheckFail('Firmware revision', { ...commonData, revisionCheckError }); } }, [commonData, revisionCheckError]);