diff --git a/native/account/restore-password-account-screen.react.js b/native/account/restore-password-account-screen.react.js --- a/native/account/restore-password-account-screen.react.js +++ b/native/account/restore-password-account-screen.react.js @@ -3,15 +3,26 @@ import * as React from 'react'; import { Text, TextInput, View } from 'react-native'; +import { usePasswordLogIn } from 'lib/hooks/login-hooks.js'; +import { getMessageForException } from 'lib/utils/errors.js'; + +import { setNativeCredentials } from './native-credentials.js'; import PromptButton from './prompt-button.react.js'; import RegistrationButtonContainer from './registration/registration-button-container.react.js'; import RegistrationContainer from './registration/registration-container.react.js'; import RegistrationContentContainer from './registration/registration-content-container.react.js'; import RegistrationTextInput from './registration/registration-text-input.react.js'; import type { SignInNavigationProp } from './sign-in-navigator.react.js'; +import { useClientBackup } from '../backup/use-client-backup.js'; import type { NavigationRoute } from '../navigation/route-names.js'; import { RestoreBackupScreenRouteName } from '../navigation/route-names.js'; import { useStyles } from '../themes/colors.js'; +import { + appOutOfDateAlertDetails, + unknownErrorAlertDetails, + userNotFoundAlertDetails, +} from '../utils/alert-messages.js'; +import Alert from '../utils/alert.js'; type Props = { +navigation: SignInNavigationProp<'RestorePasswordAccountScreen'>, @@ -27,9 +38,36 @@ passwordInputRef.current?.focus(); }, []); + const usernameInputRef = React.useRef>(); + const focusUsernameInput = React.useCallback(() => { + usernameInputRef.current?.focus(); + }, []); + + const onUnsuccessfulLoginAlertAcknowledged = React.useCallback(() => { + setUsername(''); + setPassword(''); + focusUsernameInput(); + }, [focusUsernameInput]); + + const identityPasswordLogIn = usePasswordLogIn(); + const { retrieveLatestBackupInfo } = useClientBackup(); const areCredentialsPresent = !!username && !!password; - const onProceed = React.useCallback(() => { - if (areCredentialsPresent) { + const [isProcessing, setIsProcessing] = React.useState(false); + const onProceed = React.useCallback(async () => { + if (!areCredentialsPresent) { + return; + } + setIsProcessing(true); + try { + const latestBackupInfo = await retrieveLatestBackupInfo(username); + if (!latestBackupInfo) { + await identityPasswordLogIn(username, password); + await setNativeCredentials({ + username, + password, + }); + return; + } props.navigation.navigate(RestoreBackupScreenRouteName, { userIdentifier: username, credentials: { @@ -37,8 +75,46 @@ password, }, }); + } catch (e) { + const messageForException = getMessageForException(e); + let alertMessage = unknownErrorAlertDetails; + let onPress = null; + if ( + messageForException === 'user_not_found' || + messageForException === 'login_failed' + ) { + alertMessage = userNotFoundAlertDetails; + onPress = onUnsuccessfulLoginAlertAcknowledged; + } else if ( + messageForException === 'unsupported_version' || + messageForException === 'client_version_unsupported' || + messageForException === 'use_new_flow' + ) { + alertMessage = appOutOfDateAlertDetails; + } + Alert.alert( + alertMessage.title, + alertMessage.message, + [{ text: 'OK', onPress }], + { cancelable: false }, + ); + } finally { + setIsProcessing(false); } - }, [areCredentialsPresent, password, props.navigation, username]); + }, [ + areCredentialsPresent, + identityPasswordLogIn, + onUnsuccessfulLoginAlertAcknowledged, + password, + props.navigation, + retrieveLatestBackupInfo, + username, + ]); + + let restoreButtonVariant = 'loading'; + if (!isProcessing) { + restoreButtonVariant = areCredentialsPresent ? 'enabled' : 'disabled'; + } const styles = useStyles(unboundStyles); return ( @@ -57,6 +133,7 @@ autoComplete="username" returnKeyType="next" onSubmitEditing={focusPasswordInput} + ref={usernameInputRef} /> diff --git a/native/account/restore-prompt-screen.react.js b/native/account/restore-prompt-screen.react.js --- a/native/account/restore-prompt-screen.react.js +++ b/native/account/restore-prompt-screen.react.js @@ -3,6 +3,7 @@ import * as React from 'react'; import { Text, View } from 'react-native'; +import { useWalletLogIn } from 'lib/hooks/login-hooks.js'; import type { SIWEResult } from 'lib/types/siwe-types.js'; import { getMessageForException } from 'lib/utils/errors.js'; @@ -20,7 +21,10 @@ RestorePasswordAccountScreenRouteName, } from '../navigation/route-names.js'; import { useColors, useStyles } from '../themes/colors.js'; -import { unknownErrorAlertDetails } from '../utils/alert-messages.js'; +import { + appOutOfDateAlertDetails, + unknownErrorAlertDetails, +} from '../utils/alert-messages.js'; import Alert from '../utils/alert.js'; import RestoreIcon from '../vectors/restore-icon.react.js'; @@ -40,12 +44,19 @@ props.navigation.navigate(RestorePasswordAccountScreenRouteName); }, [props.navigation]); + const [authInProgress, setAuthInProgress] = React.useState(false); const { retrieveLatestBackupInfo } = useClientBackup(); + const walletLogIn = useWalletLogIn(); const onSIWESuccess = React.useCallback( async (result: SIWEResult) => { try { + setAuthInProgress(true); const { address, signature, message } = result; const backupInfo = await retrieveLatestBackupInfo(address); + if (!backupInfo) { + await walletLogIn(result.address, result.message, result.signature); + return; + } const { siweBackupData } = backupInfo; if (!siweBackupData) { @@ -71,16 +82,30 @@ console.log( `SIWE restore error: ${messageForException ?? 'unknown error'}`, ); - const alertDetails = unknownErrorAlertDetails; + let alertDetails = unknownErrorAlertDetails; + if ( + messageForException === 'unsupported_version' || + messageForException === 'client_version_unsupported' || + messageForException === 'use_new_flow' + ) { + alertDetails = appOutOfDateAlertDetails; + } else if (messageForException === 'nonce_expired') { + alertDetails = { + title: 'Login attempt timed out', + message: 'Please try again', + }; + } Alert.alert( alertDetails.title, alertDetails.message, [{ text: 'OK', onPress: props.navigation.goBack }], { cancelable: false }, ); + } finally { + setAuthInProgress(false); } }, - [props.navigation, retrieveLatestBackupInfo], + [props.navigation, retrieveLatestBackupInfo, walletLogIn], ); const { @@ -104,6 +129,12 @@ ); } + const openSIWEPanel = React.useCallback(() => { + if (!authInProgress) { + openPanel(); + } + }, [authInProgress, openPanel]); + const colors = useColors(); return ( <> @@ -130,8 +161,10 @@