From ff920102035e761c10e0310e3d0dd526eec7d0bf Mon Sep 17 00:00:00 2001 From: Ashley Canning Date: Fri, 27 Feb 2026 12:08:35 +0000 Subject: [PATCH] code example --- src/shared/errors.ts | 11 +++++++++++ src/shared/thirdweb/emailAuth.ts | 4 ++++ src/shared/utils/errorHandler.ts | 12 +++++++----- src/shared/utils/errorHandler.types.ts | 1 + 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/src/shared/errors.ts b/src/shared/errors.ts index 4d6de10f..65baebb8 100644 --- a/src/shared/errors.ts +++ b/src/shared/errors.ts @@ -1,3 +1,14 @@ +/** + * Thrown when an error is caused by user input rather than a system failure. + * These are intentionally excluded from Sentry to avoid noise. + */ +export class UserFacingError extends Error { + constructor(message: string) { + super(message) + this.name = 'UserFacingError' + } +} + export function isErrorWithMessage(error: unknown): error is Error { return error !== undefined && error !== null && typeof error === 'object' && 'message' in error } diff --git a/src/shared/thirdweb/emailAuth.ts b/src/shared/thirdweb/emailAuth.ts index 5bb85a8c..17e27195 100644 --- a/src/shared/thirdweb/emailAuth.ts +++ b/src/shared/thirdweb/emailAuth.ts @@ -1,4 +1,5 @@ import { inAppWallet, preAuthenticate } from 'thirdweb/wallets' +import { UserFacingError, isErrorWithMessage } from '../errors' import { getThirdwebClient } from './client' // Store the wallet instance for reuse @@ -81,6 +82,9 @@ export const verifyEmailOTPAndConnect = async (email: string, verificationCode: return account } catch (error) { console.error('[Thirdweb] Error verifying OTP:', error) + if (isErrorWithMessage(error) && /verify|invalid|expired/i.test(error.message)) { + throw new UserFacingError(error.message) + } throw error } } diff --git a/src/shared/utils/errorHandler.ts b/src/shared/utils/errorHandler.ts index d70b4dab..a3f6caf4 100644 --- a/src/shared/utils/errorHandler.ts +++ b/src/shared/utils/errorHandler.ts @@ -1,6 +1,6 @@ import { captureException } from '@sentry/react' import { TrackingEvents } from '../../modules/analytics/types' -import { isErrorWithMessage } from '../errors' +import { UserFacingError, isErrorWithMessage } from '../errors' import { trackEvent } from './analytics' import { ErrorContext, HandleErrorOptions } from './errorHandler.types' @@ -11,10 +11,12 @@ const handleError = (error: unknown, context: string, options?: HandleErrorOptio console.error(`${context}:`, errorMessage) } - captureException(error, { - tags: options?.sentryTags, - extra: options?.sentryExtra - }) + if (!options?.skipSentry && !(error instanceof UserFacingError)) { + captureException(error, { + tags: options?.sentryTags, + extra: options?.sentryExtra + }) + } if (!options?.skipTracking) { const trackingEvent = (options?.trackingEvent as TrackingEvents) || TrackingEvents.LOGIN_ERROR diff --git a/src/shared/utils/errorHandler.types.ts b/src/shared/utils/errorHandler.types.ts index 171a071f..dcfdde0c 100644 --- a/src/shared/utils/errorHandler.types.ts +++ b/src/shared/utils/errorHandler.types.ts @@ -71,5 +71,6 @@ export interface HandleErrorOptions { sentryExtra?: SentryExtra skipLogging?: boolean skipTracking?: boolean + skipSentry?: boolean trackingEvent?: string }