From 4d1b1bb2fd9831364da514182ac216237b64df61 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 2 Dec 2025 10:06:12 +0200 Subject: [PATCH] Catch crop cancelled errors (#9451) --- src/lib/strings/errors.ts | 12 ++++++++++++ src/screens/Onboarding/StepProfile/index.tsx | 18 +++++++++++++----- src/state/gallery.ts | 3 ++- src/view/com/util/UserAvatar.tsx | 7 ++++--- src/view/com/util/UserBanner.tsx | 6 ++++-- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/lib/strings/errors.ts b/src/lib/strings/errors.ts index 35b8b39ac4..22a6f061ac 100644 --- a/src/lib/strings/errors.ts +++ b/src/lib/strings/errors.ts @@ -52,3 +52,15 @@ export function isErrorMaybeAppPasswordPermissions(e: unknown) { const str = String(e) return str.includes('Bad token scope') || str.includes('Bad token method') } + +/** + * Intended to capture "User cancelled" or "Crop cancelled" errors + * that we often get from expo modules such expo-image-crop-tool + * + * The exact name has changed in the past so let's just see if the string + * contains "cancel" + */ +export function isCancelledError(e: unknown) { + const str = String(e).toLowerCase() + return str.includes('cancel') +} diff --git a/src/screens/Onboarding/StepProfile/index.tsx b/src/screens/Onboarding/StepProfile/index.tsx index 6066e42976..453184639a 100644 --- a/src/screens/Onboarding/StepProfile/index.tsx +++ b/src/screens/Onboarding/StepProfile/index.tsx @@ -15,6 +15,8 @@ import {openCropper} from '#/lib/media/picker' import {getDataUriSize} from '#/lib/media/util' import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' import {logEvent, useGate} from '#/lib/statsig/statsig' +import {isCancelledError} from '#/lib/strings/errors' +import {logger} from '#/logger' import {isNative, isWeb} from '#/platform/detection' import { DescriptionText, @@ -184,11 +186,17 @@ export function StepProfile() { if (!image) return if (!isWeb) { - image = await openCropper({ - imageUri: image.path, - shape: 'circle', - aspectRatio: 1 / 1, - }) + try { + image = await openCropper({ + imageUri: image.path, + shape: 'circle', + aspectRatio: 1 / 1, + }) + } catch (e) { + if (!isCancelledError(e)) { + logger.error('Failed to crop avatar in onboarding', {error: e}) + } + } } image = await compressIfNeeded(image, 1000000) diff --git a/src/state/gallery.ts b/src/state/gallery.ts index 2370df27d7..c8ddba7026 100644 --- a/src/state/gallery.ts +++ b/src/state/gallery.ts @@ -17,6 +17,7 @@ import {getImageDim} from '#/lib/media/manip' import {openCropper} from '#/lib/media/picker' import {type PickerImage} from '#/lib/media/picker.shared' import {getDataUriSize} from '#/lib/media/util' +import {isCancelledError} from '#/lib/strings/errors' import {isNative} from '#/platform/detection' export type ImageTransformation = { @@ -143,7 +144,7 @@ export async function cropImage(img: ComposerImage): Promise { }, } } catch (e) { - if (e instanceof Error && e.message.includes('User cancelled')) { + if (!isCancelledError(e)) { return img } diff --git a/src/view/com/util/UserAvatar.tsx b/src/view/com/util/UserAvatar.tsx index aa5b22bd39..8a9e51a332 100644 --- a/src/view/com/util/UserAvatar.tsx +++ b/src/view/com/util/UserAvatar.tsx @@ -26,6 +26,7 @@ import {openCamera, openCropper, openPicker} from '#/lib/media/picker' import {type PickerImage} from '#/lib/media/picker.shared' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' +import {isCancelledError} from '#/lib/strings/errors' import {sanitizeHandle} from '#/lib/strings/handles' import {logger} from '#/logger' import {isAndroid, isNative, isWeb} from '#/platform/detection' @@ -407,10 +408,10 @@ let EditableUserAvatar = ({ setRawImage(await createComposerImage(item)) editImageDialogControl.open() } - } catch (e: any) { + } catch (e) { // Don't log errors for cancelling selection to sentry on ios or android - if (!String(e).toLowerCase().includes('cancel')) { - logger.error('Failed to crop banner', {error: e}) + if (!isCancelledError(e)) { + logger.error('Failed to crop avatar', {error: e}) } } }, [ diff --git a/src/view/com/util/UserBanner.tsx b/src/view/com/util/UserBanner.tsx index 3600f5c24e..65e7b5a4a2 100644 --- a/src/view/com/util/UserBanner.tsx +++ b/src/view/com/util/UserBanner.tsx @@ -12,6 +12,7 @@ import { import {compressIfNeeded} from '#/lib/media/manip' import {openCamera, openCropper, openPicker} from '#/lib/media/picker' import {type PickerImage} from '#/lib/media/picker.shared' +import {isCancelledError} from '#/lib/strings/errors' import {logger} from '#/logger' import {isAndroid, isNative} from '#/platform/detection' import { @@ -87,8 +88,9 @@ export function UserBanner({ setRawImage(await createComposerImage(items[0])) editImageDialogControl.open() } - } catch (e: any) { - if (!String(e).includes('Canceled')) { + } catch (e) { + // Don't log errors for cancelling selection to sentry on ios or android + if (!isCancelledError(e)) { logger.error('Failed to crop banner', {error: e}) } }