diff --git a/native/account/fullscreen-siwe-panel.react.js b/native/account/fullscreen-siwe-panel.react.js --- a/native/account/fullscreen-siwe-panel.react.js +++ b/native/account/fullscreen-siwe-panel.react.js @@ -8,7 +8,7 @@ import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js'; import { useWalletLogIn } from 'lib/hooks/login-hooks.js'; import { type SIWEResult, SIWEMessageTypes } from 'lib/types/siwe-types.js'; -import { ServerError } from 'lib/utils/errors.js'; +import { ServerError, getMessageForException } from 'lib/utils/errors.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js'; @@ -67,6 +67,20 @@ ], ); + const onNonceExpired = React.useCallback( + (registrationOrLogin: 'registration' | 'login') => { + Alert.alert( + registrationOrLogin === 'registration' + ? 'Registration attempt timed out' + : 'Login attempt timed out', + 'Please try again', + [{ text: 'OK', onPress: goBackToPrompt }], + { cancelable: false }, + ); + }, + [goBackToPrompt], + ); + const legacySiweServerCall = useLegacySIWEServerCall(); const walletLogIn = useWalletLogIn(); const successRef = React.useRef(false); @@ -80,20 +94,39 @@ await commRustModule.findUserIDForWalletAddress(result.address); const findUserIDResponse = JSON.parse(findUserIDResponseString); if (findUserIDResponse.userID || findUserIDResponse.isReserved) { - await walletLogIn(result.address, result.message, result.signature); + try { + await walletLogIn( + result.address, + result.message, + result.signature, + ); + } catch (e) { + const messageForException = getMessageForException(e); + if (messageForException === 'nonce expired') { + onNonceExpired('login'); + } else { + throw e; + } + } } else if (enableNewRegistrationMode) { await onAccountDoesNotExist(result); } else { - await registrationServerCall({ - farcasterID: null, - accountSelection: { - accountType: 'ethereum', - ...result, - avatarURI: null, - }, - avatarData: null, - clearCachedSelections: () => {}, - }); + try { + await registrationServerCall({ + farcasterID: null, + accountSelection: { + accountType: 'ethereum', + ...result, + avatarURI: null, + }, + avatarData: null, + clearCachedSelections: () => {}, + onNonceExpired: () => onNonceExpired('registration'), + }); + } catch { + // We swallow exceptions here because registrationServerCall + // already handles showing Alerts, and we don't want to show two + } } } catch (e) { Alert.alert( @@ -102,7 +135,6 @@ [{ text: 'OK', onPress: goBackToPrompt }], { cancelable: false }, ); - throw e; } } else { try { @@ -124,7 +156,7 @@ [{ text: 'OK', onPress: goBackToPrompt }], { cancelable: false }, ); - throw e; + return; } dispatch({ type: setDataLoadedActionType, @@ -141,6 +173,7 @@ dispatch, legacySiweServerCall, onAccountDoesNotExist, + onNonceExpired, ], ); diff --git a/native/account/registration/registration-server-call.js b/native/account/registration/registration-server-call.js --- a/native/account/registration/registration-server-call.js +++ b/native/account/registration/registration-server-call.js @@ -20,6 +20,7 @@ logInActionSources, } from 'lib/types/account-types.js'; import { syncedMetadataNames } from 'lib/types/synced-metadata-types.js'; +import { getMessageForException } from 'lib/utils/errors.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; import { useDispatch } from 'lib/utils/redux-utils.js'; import { usingCommServicesAccessToken } from 'lib/utils/services-utils.js'; @@ -219,6 +220,7 @@ farcasterID, siweBackupSecrets, clearCachedSelections, + onNonceExpired, } = input; const keyserverURL = passedKeyserverURL ?? defaultURLPrefix; if ( @@ -255,10 +257,15 @@ fid: farcasterID, }); } catch (e) { - Alert.alert( - UnknownErrorAlertDetails.title, - UnknownErrorAlertDetails.message, - ); + const messageForException = getMessageForException(e); + if (messageForException === 'nonce expired') { + onNonceExpired(); + } else { + Alert.alert( + UnknownErrorAlertDetails.title, + UnknownErrorAlertDetails.message, + ); + } throw e; } } diff --git a/native/account/registration/registration-terms.react.js b/native/account/registration/registration-terms.react.js --- a/native/account/registration/registration-terms.react.js +++ b/native/account/registration/registration-terms.react.js @@ -1,8 +1,13 @@ // @flow +import type { + StackNavigationEventMap, + StackNavigationState, + StackOptions, +} from '@react-navigation/core'; import invariant from 'invariant'; import * as React from 'react'; -import { Text, View, Image, Linking } from 'react-native'; +import { Text, View, Image, Linking, Alert } from 'react-native'; import type { SIWEBackupSecrets } from 'lib/types/siwe-types.js'; @@ -19,7 +24,11 @@ } from './registration-types.js'; import commSwooshSource from '../../img/comm-swoosh.png'; import { logInActionType } from '../../navigation/action-types.js'; -import type { NavigationRoute } from '../../navigation/route-names.js'; +import type { RootNavigationProp } from '../../navigation/root-navigator.react.js'; +import type { + NavigationRoute, + ScreenParamList, +} from '../../navigation/route-names.js'; import { useStyles } from '../../themes/colors.js'; export type RegistrationTermsParams = { @@ -59,16 +68,43 @@ setCachedSelections({}); }, [setCachedSelections]); + const { navigation } = props; + const goBackToHome = navigation.getParent< + ScreenParamList, + 'Registration', + StackNavigationState, + StackOptions, + StackNavigationEventMap, + RootNavigationProp<'Registration'>, + >()?.goBack; + const onNonceExpired = React.useCallback(() => { + setCachedSelections(oldUserSelections => ({ + ...oldUserSelections, + ethereumAccount: undefined, + })); + Alert.alert( + 'Registration attempt timed out', + 'Please try to connect your Ethereum wallet again', + [{ text: 'OK', onPress: goBackToHome }], + { + cancelable: false, + }, + ); + }, [goBackToHome, setCachedSelections]); + const onProceed = React.useCallback(async () => { setRegistrationInProgress(true); try { - await register({ ...userSelections, clearCachedSelections }); + await register({ + ...userSelections, + clearCachedSelections, + onNonceExpired, + }); } finally { setRegistrationInProgress(false); } - }, [register, userSelections, clearCachedSelections]); + }, [register, userSelections, clearCachedSelections, onNonceExpired]); - const { navigation } = props; React.useEffect(() => { if (!registrationInProgress) { return undefined; diff --git a/native/account/registration/registration-types.js b/native/account/registration/registration-types.js --- a/native/account/registration/registration-types.js +++ b/native/account/registration/registration-types.js @@ -45,6 +45,7 @@ +avatarData: ?AvatarData, +siweBackupSecrets?: ?SIWEBackupSecrets, +clearCachedSelections: () => void, + +onNonceExpired: () => mixed, }; export type CachedUserSelections = {