From e855ffc1c5125661c9e7e61cdbd343a2a073ceb7 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 28 Apr 2025 21:02:31 -0500 Subject: [PATCH] Clean errors --- .../EmailDialog/screens/Manage2FA/Disable.tsx | 9 +- .../EmailDialog/screens/Manage2FA/Enable.tsx | 5 +- .../dialogs/EmailDialog/screens/Update.tsx | 5 +- .../dialogs/EmailDialog/screens/Verify.tsx | 8 +- src/lib/hooks/useCleanError.ts | 91 +++++++++++++++++++ 5 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 src/lib/hooks/useCleanError.ts diff --git a/src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx b/src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx index 88f4f74381..6094c1c981 100644 --- a/src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx +++ b/src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx @@ -4,6 +4,7 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {wait} from '#/lib/async/wait' +import {useCleanError} from '#/lib/hooks/useCleanError' import {logger} from '#/logger' import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' @@ -86,6 +87,7 @@ function reducer(state: State, action: Action): State { export function Disable() { const t = useTheme() const {_} = useLingui() + const cleanError = useCleanError() const {currentAccount} = useSession() const {mutateAsync: requestEmailUpdate} = useRequestEmailUpdate() const {mutateAsync: manageEmail2FA} = useManageEmail2FA() @@ -111,10 +113,10 @@ export function Disable() { logger.error('Manage2FA: email update code request failed', { safeMessage: e, }) - // TODO rate limit + const {clean} = cleanError(e) dispatch({ type: 'setError', - error: _(msg`Failed to send email, please try again.`), + error: clean || _(msg`Failed to send email, please try again.`), }) } } @@ -130,9 +132,10 @@ export function Disable() { }, 1000) } catch (e) { logger.error('Manage2FA: disable email 2FA failed', {safeMessage: e}) + const {clean} = cleanError(e) dispatch({ type: 'setError', - error: _(msg`Update to email 2FA settings failed`), + error: clean || _(msg`Update to email 2FA settings failed`), }) } } diff --git a/src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx b/src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx index fbf114075e..8b926e1bf6 100644 --- a/src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx +++ b/src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx @@ -4,6 +4,7 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {wait} from '#/lib/async/wait' +import {useCleanError} from '#/lib/hooks/useCleanError' import {logger} from '#/logger' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Admonition} from '#/components/Admonition' @@ -55,6 +56,7 @@ function reducer(state: State, action: Action): State { export function Enable() { const t = useTheme() const {_} = useLingui() + const cleanError = useCleanError() const {gtPhone} = useBreakpoints() const {mutateAsync: manageEmail2FA} = useManageEmail2FA() const control = useDialogContext() @@ -75,9 +77,10 @@ export function Enable() { }, 1000) } catch (e) { logger.error('Manage2FA: enable email 2FA failed', {safeMessage: e}) + const {clean} = cleanError(e) dispatch({ type: 'setError', - error: _(msg`Update to email 2FA settings failed`), + error: clean || _(msg`Update to email 2FA settings failed`), }) } } diff --git a/src/components/dialogs/EmailDialog/screens/Update.tsx b/src/components/dialogs/EmailDialog/screens/Update.tsx index fb3ee39ed1..1c873fd114 100644 --- a/src/components/dialogs/EmailDialog/screens/Update.tsx +++ b/src/components/dialogs/EmailDialog/screens/Update.tsx @@ -5,6 +5,7 @@ import {useLingui} from '@lingui/react' import {validate as validateEmail} from 'email-validator' import {wait} from '#/lib/async/wait' +import {useCleanError} from '#/lib/hooks/useCleanError' import {logger} from '#/logger' import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' @@ -102,6 +103,7 @@ function reducer(state: State, action: Action): State { export function Update(_props: ScreenProps) { const t = useTheme() const {_} = useLingui() + const cleanError = useCleanError() const {currentAccount} = useSession() const [state, dispatch] = useReducer(reducer, { step: 'email', @@ -176,9 +178,10 @@ export function Update(_props: ScreenProps) { } } catch (e) { logger.error('EmailDialog: update email failed', {safeMessage: e}) + const {clean} = cleanError(e) dispatch({ type: 'setError', - error: _(msg`Email updated failed, please try again.`), + error: clean || _(msg`Email updated failed, please try again.`), }) } } diff --git a/src/components/dialogs/EmailDialog/screens/Verify.tsx b/src/components/dialogs/EmailDialog/screens/Verify.tsx index 2eec7dbf8e..f53f649261 100644 --- a/src/components/dialogs/EmailDialog/screens/Verify.tsx +++ b/src/components/dialogs/EmailDialog/screens/Verify.tsx @@ -4,6 +4,7 @@ import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {wait} from '#/lib/async/wait' +import {useCleanError} from '#/lib/hooks/useCleanError' import {logger} from '#/logger' import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' @@ -87,6 +88,7 @@ function reducer(state: State, action: Action): State { export function Verify({config}: ScreenProps) { const t = useTheme() const {_} = useLingui() + const cleanError = useCleanError() const {currentAccount} = useSession() const [state, dispatch] = useReducer(reducer, { step: 'email', @@ -125,9 +127,10 @@ export function Verify({config}: ScreenProps) { logger.error('EmailDialog: sending verification email failed', { safeMessage: e, }) + const {clean} = cleanError(e) dispatch({ type: 'setError', - error: _(msg`Failed to send email, please try again.`), + error: clean || _(msg`Failed to send email, please try again.`), }) } } @@ -148,9 +151,10 @@ export function Verify({config}: ScreenProps) { logger.error('EmailDialog: confirming email failed', { safeMessage: e, }) + const {clean} = cleanError(e) dispatch({ type: 'setError', - error: _(msg`Failed to verify email, please try again.`), + error: clean || _(msg`Failed to verify email, please try again.`), }) } } diff --git a/src/lib/hooks/useCleanError.ts b/src/lib/hooks/useCleanError.ts new file mode 100644 index 0000000000..81d8153de3 --- /dev/null +++ b/src/lib/hooks/useCleanError.ts @@ -0,0 +1,91 @@ +import {useCallback} from 'react' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +type CleanedError = { + raw: string | undefined + clean: string | undefined +} + +export function useCleanError() { + const {_} = useLingui() + + return useCallback<(error?: any) => CleanedError>( + error => { + if (!error) + return { + raw: undefined, + clean: undefined, + } + + let raw = error.toString() + + if (isNetworkError(raw)) { + return { + raw, + clean: _( + msg`Unable to connect. Please check your internet connection and try again.`, + ), + } + } + + if ( + raw.includes('Upstream Failure') || + raw.includes('NotEnoughResources') || + raw.includes('pipethrough network error') + ) { + return { + raw, + clean: _( + msg`The server appears to be experiencing issues. Please try again in a few moments.`, + ), + } + } + + if (raw.includes('Bad token scope')) { + return { + raw, + clean: _( + msg`This feature is not available while using an App Password. Please sign in with your main password.`, + ), + } + } + + if (raw.includes('Rate Limit Exceeded')) { + return { + raw, + clean: _( + msg`You've reached the maximum number of requests allowed. Please try again later.`, + ), + } + } + + if (raw.startsWith('Error: ')) { + raw = raw.slice('Error: '.length) + } + + return { + raw, + clean: undefined, + } + }, + [_], + ) +} + +const NETWORK_ERRORS = [ + 'Abort', + 'Network request failed', + 'Failed to fetch', + 'Load failed', +] + +export function isNetworkError(e: unknown) { + const str = String(e) + for (const err of NETWORK_ERRORS) { + if (str.includes(err)) { + return true + } + } + return false +}