diff --git a/lib/components/farcaster-data-handler.react.js b/lib/components/farcaster-data-handler.react.js index b77d9376f..d26a4b7c0 100644 --- a/lib/components/farcaster-data-handler.react.js +++ b/lib/components/farcaster-data-handler.react.js @@ -1,234 +1,231 @@ // @flow import * as React from 'react'; import { NeynarClientContext } from './neynar-client-provider.react.js'; import { useUserIdentityCache } from './user-identity-cache.react.js'; import { setAuxUserFIDsActionType } from '../actions/aux-user-actions.js'; import { updateRelationshipsActionTypes } from '../actions/relationship-actions.js'; -import { setSyncedMetadataEntryActionType } from '../actions/synced-metadata-actions.js'; import { useIsLoggedInToIdentityAndAuthoritativeKeyserver } from '../hooks/account-hooks.js'; import { useUpdateRelationships } from '../hooks/relationship-hooks.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import { relationshipActions } from '../types/relationship-types.js'; -import { syncedMetadataNames } from '../types/synced-metadata-types.js'; -import { useCurrentUserFID, useUnlinkFID } from '../utils/farcaster-utils.js'; +import { + useCurrentUserFID, + useUnlinkFID, + useSetLocalFID, +} from '../utils/farcaster-utils.js'; import { useDispatchActionPromise } from '../utils/redux-promise-utils.js'; import { useSelector, useDispatch } from '../utils/redux-utils.js'; import { usingCommServicesAccessToken } from '../utils/services-utils.js'; type Props = { +children?: React.Node, }; function FarcasterDataHandler(props: Props): React.Node { const { children } = props; const isActive = useSelector(state => state.lifecycleState !== 'background'); const currentUserID = useSelector(state => state.currentUserInfo?.id); const loggedIn = useIsLoggedInToIdentityAndAuthoritativeKeyserver(); const neynarClient = React.useContext(NeynarClientContext)?.client; const identityServiceClient = React.useContext(IdentityClientContext); const getFarcasterUsers = identityServiceClient?.identityClient.getFarcasterUsers; const { getCachedUserIdentity, getUserIdentities: findUserIdentities } = useUserIdentityCache(); const dispatch = useDispatch(); const dispatchActionPromise = useDispatchActionPromise(); const updateRelationships = useUpdateRelationships(); const createThreadsAndRobotextForFarcasterMutuals = React.useCallback( (userIDs: $ReadOnlyArray) => updateRelationships(relationshipActions.FARCASTER_MUTUAL, userIDs), [updateRelationships], ); const userInfos = useSelector(state => state.userStore.userInfos); const fid = useCurrentUserFID(); const unlinkFID = useUnlinkFID(); const prevCanQueryRef = React.useRef(); // It's possible for the user to log out while handleFarcasterMutuals below // is running. It's not a big deal, but this can lead to a useless server call // at the end. To avoid that useless server call, we want to check whether the // user is logged in beforehand, but the value of loggedIn bound in the // callback will be outdated. Instead, we can check this ref, which will be // updated on every render. const loggedInRef = React.useRef(loggedIn); loggedInRef.current = loggedIn; const handleFarcasterMutuals = React.useCallback(async () => { const canQuery = isActive && !!fid && loggedIn; if (canQuery === prevCanQueryRef.current) { return; } prevCanQueryRef.current = canQuery; if ( !loggedIn || !isActive || !fid || !neynarClient || !getFarcasterUsers || !currentUserID ) { return; } const followerFIDs = await neynarClient.fetchFriendFIDs(fid); const commFCUsers = await getFarcasterUsers(followerFIDs); const newCommUsers = commFCUsers.filter(({ userID }) => !userInfos[userID]); if (newCommUsers.length === 0) { return; } const newCommUserIDs = newCommUsers.map(({ userID }) => userID); if (!loggedInRef.current) { return; } void dispatchActionPromise( updateRelationshipsActionTypes, createThreadsAndRobotextForFarcasterMutuals(newCommUserIDs), ); }, [ isActive, fid, loggedIn, neynarClient, getFarcasterUsers, userInfos, dispatchActionPromise, createThreadsAndRobotextForFarcasterMutuals, currentUserID, ]); const handleUserStoreFIDs = React.useCallback(async () => { if (!loggedIn || !isActive) { return; } const userStoreIDs = Object.keys(userInfos); const { identities: userIdentities } = await findUserIdentities(userStoreIDs); const userStoreFarcasterUsers = Object.entries(userIdentities) .filter(([, identity]) => identity.farcasterID !== null) .map(([userID, identity]) => ({ userID, username: identity.username, farcasterID: identity.farcasterID, })); dispatch({ type: setAuxUserFIDsActionType, payload: { farcasterUsers: userStoreFarcasterUsers, }, }); }, [loggedIn, isActive, findUserIdentities, userInfos, dispatch]); const prevCanQueryHandleCurrentUserFIDRef = React.useRef(); const canQueryHandleCurrentUserFID = isActive && loggedIn; const [fidLoaded, setFIDLoaded] = React.useState(false); + const setLocalFID = useSetLocalFID(); const handleCurrentUserFID = React.useCallback(async () => { if ( canQueryHandleCurrentUserFID === prevCanQueryHandleCurrentUserFIDRef.current ) { return; } prevCanQueryHandleCurrentUserFIDRef.current = canQueryHandleCurrentUserFID; if (!canQueryHandleCurrentUserFID || !currentUserID || !neynarClient) { return; } const { identities: userIdentities } = await findUserIdentities([ currentUserID, ]); const identityFID = userIdentities[currentUserID]?.farcasterID; const identityFIDRequestTimedOut = identityFID ? false : getCachedUserIdentity(currentUserID) === undefined; if (fid && !identityFIDRequestTimedOut && fid !== identityFID) { await unlinkFID(); return; } else if (fid) { const isCurrentUserFIDValid = await neynarClient.checkIfCurrentUserFIDIsValid(fid); if (!isCurrentUserFIDValid) { await unlinkFID(); } return; } if (identityFID) { - dispatch({ - type: setSyncedMetadataEntryActionType, - payload: { - name: syncedMetadataNames.CURRENT_USER_FID, - data: identityFID, - }, - }); + setLocalFID(identityFID); } setFIDLoaded(true); }, [ canQueryHandleCurrentUserFID, findUserIdentities, currentUserID, neynarClient, fid, unlinkFID, - dispatch, + setLocalFID, getCachedUserIdentity, ]); React.useEffect(() => { if (!usingCommServicesAccessToken) { return; } void handleFarcasterMutuals(); void handleUserStoreFIDs(); void handleCurrentUserFID(); }, [handleCurrentUserFID, handleFarcasterMutuals, handleUserStoreFIDs]); React.useEffect(() => { if (loggedIn) { return; } setFIDLoaded(false); }, [loggedIn]); const farcasterDataHandler = React.useMemo(() => { if (!fidLoaded) { return null; } return children; }, [children, fidLoaded]); return farcasterDataHandler; } export { FarcasterDataHandler }; diff --git a/lib/utils/farcaster-utils.js b/lib/utils/farcaster-utils.js index cfc1556a6..72e338728 100644 --- a/lib/utils/farcaster-utils.js +++ b/lib/utils/farcaster-utils.js @@ -1,86 +1,92 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; import { setSyncedMetadataEntryActionType } from '../actions/synced-metadata-actions.js'; import { IdentityClientContext } from '../shared/identity-client-context.js'; import { syncedMetadataNames } from '../types/synced-metadata-types.js'; import { useSelector, useDispatch } from '../utils/redux-utils.js'; const DISABLE_CONNECT_FARCASTER_ALERT = true; const NO_FID_METADATA = 'NONE'; function useCurrentUserFID(): ?string { // There is a distinction between null & undefined for the fid value. // If the fid is null this means that the user has decided NOT to set // a Farcaster association. If the fid is undefined this means that // the user has not yet been prompted to set a Farcaster association. const currentUserFID = useSelector( state => state.syncedMetadataStore.syncedMetadata[ syncedMetadataNames.CURRENT_USER_FID ] ?? undefined, ); if (currentUserFID === NO_FID_METADATA) { return null; } return currentUserFID; } +function useSetLocalFID(): (fid: ?string) => void { + const dispatch = useDispatch(); + return React.useCallback( + (fid: ?string) => { + // If we're unsetting the FID, we should set it to NO_FID_METADATA to + // avoid prompting the user for it again + const fidToSet = fid ?? NO_FID_METADATA; + dispatch({ + type: setSyncedMetadataEntryActionType, + payload: { + name: syncedMetadataNames.CURRENT_USER_FID, + data: fidToSet, + }, + }); + }, + [dispatch], + ); +} + function useLinkFID(): (fid: string) => Promise { const identityClientContext = React.useContext(IdentityClientContext); invariant(identityClientContext, 'identityClientContext should be set'); const { identityClient } = identityClientContext; const { linkFarcasterAccount } = identityClient; - const dispatch = useDispatch(); + const setLocalFID = useSetLocalFID(); return React.useCallback( async (fid: string) => { await linkFarcasterAccount(fid); - - dispatch({ - type: setSyncedMetadataEntryActionType, - payload: { - name: syncedMetadataNames.CURRENT_USER_FID, - data: fid, - }, - }); + setLocalFID(fid); }, - [dispatch, linkFarcasterAccount], + [setLocalFID, linkFarcasterAccount], ); } function useUnlinkFID(): () => Promise { const identityClientContext = React.useContext(IdentityClientContext); invariant(identityClientContext, 'identityClientContext should be set'); const { identityClient } = identityClientContext; const { unlinkFarcasterAccount } = identityClient; - const dispatch = useDispatch(); + const setLocalFID = useSetLocalFID(); return React.useCallback(async () => { await unlinkFarcasterAccount(); - - dispatch({ - type: setSyncedMetadataEntryActionType, - payload: { - name: syncedMetadataNames.CURRENT_USER_FID, - data: NO_FID_METADATA, - }, - }); - }, [dispatch, unlinkFarcasterAccount]); + setLocalFID(null); + }, [setLocalFID, unlinkFarcasterAccount]); } export { DISABLE_CONNECT_FARCASTER_ALERT, NO_FID_METADATA, useCurrentUserFID, + useSetLocalFID, useLinkFID, useUnlinkFID, }; diff --git a/native/account/registration/registration-server-call.js b/native/account/registration/registration-server-call.js index e03ceed27..204dff382 100644 --- a/native/account/registration/registration-server-call.js +++ b/native/account/registration/registration-server-call.js @@ -1,613 +1,608 @@ // @flow import * as React from 'react'; import { setDataLoadedActionType } from 'lib/actions/client-db-store-actions.js'; import { setSyncedMetadataEntryActionType } from 'lib/actions/synced-metadata-actions.js'; import { legacyKeyserverRegisterActionTypes, legacyKeyserverRegister, useIdentityPasswordRegister, identityRegisterActionTypes, deleteAccountActionTypes, useDeleteDiscardedIdentityAccount, } from 'lib/actions/user-actions.js'; import { useIsLoggedInToAuthoritativeKeyserver } from 'lib/hooks/account-hooks.js'; import { useKeyserverAuthWithRetry } from 'lib/keyserver-conn/keyserver-auth.js'; import { useLegacyAshoatKeyserverCall } from 'lib/keyserver-conn/legacy-keyserver-call.js'; import { usePreRequestUserState } from 'lib/selectors/account-selectors.js'; import { type LegacyLogInStartingPayload, logInActionSources, type LogOutResult, } from 'lib/types/account-types.js'; import { syncedMetadataNames } from 'lib/types/synced-metadata-types.js'; import { getMessageForException } from 'lib/utils/errors.js'; -import { NO_FID_METADATA } from 'lib/utils/farcaster-utils.js'; +import { useSetLocalFID } from 'lib/utils/farcaster-utils.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'; import { setURLPrefix } from 'lib/utils/url-utils.js'; import { waitUntilDatabaseDeleted } from 'lib/utils/wait-until-db-deleted.js'; import type { RegistrationServerCallInput, UsernameAccountSelection, EthereumAccountSelection, AvatarData, } from './registration-types.js'; import { authoritativeKeyserverID } from '../../authoritative-keyserver.js'; import { useNativeSetUserAvatar, useUploadSelectedMedia, } from '../../avatars/avatar-hooks.js'; import { commCoreModule } from '../../native-modules.js'; import { persistConfig } from '../../redux/persist.js'; import { useSelector } from '../../redux/redux-utils.js'; import { nativeLegacyLogInExtraInfoSelector } from '../../selectors/account-selectors.js'; import { appOutOfDateAlertDetails, usernameReservedAlertDetails, usernameTakenAlertDetails, unknownErrorAlertDetails, } from '../../utils/alert-messages.js'; import Alert from '../../utils/alert.js'; import { defaultURLPrefix } from '../../utils/url-utils.js'; import { setNativeCredentials } from '../native-credentials.js'; import { useLegacySIWEServerCall, useIdentityWalletRegisterCall, } from '../siwe-hooks.js'; // We can't just do everything in one async callback, since the server calls // would get bound to Redux state from before the registration. The registration // flow has multiple steps where critical Redux state is changed, where // subsequent steps depend on accessing the updated Redux state. // To address this, we break the registration process up into multiple steps. // When each step completes we update the currentStep state, and we have Redux // selectors that trigger useEffects for subsequent steps when relevant data // starts to appear in Redux. type CurrentStep = | { +step: 'inactive' } | { +step: 'identity_registration_dispatched', +clearCachedSelections: () => void, +onAlertAcknowledged: ?() => mixed, +avatarData: ?AvatarData, +credentialsToSave: ?{ +username: string, +password: string }, +resolve: () => void, +reject: Error => void, } | { +step: 'authoritative_keyserver_registration_dispatched', +clearCachedSelections: () => void, +avatarData: ?AvatarData, +credentialsToSave: ?{ +username: string, +password: string }, +resolve: () => void, +reject: Error => void, }; const inactiveStep = { step: 'inactive' }; function useRegistrationServerCall(): RegistrationServerCallInput => Promise { const [currentStep, setCurrentStep] = React.useState(inactiveStep); // STEP 1: ACCOUNT REGISTRATION const legacyLogInExtraInfo = useSelector(nativeLegacyLogInExtraInfoSelector); const dispatchActionPromise = useDispatchActionPromise(); const callLegacyKeyserverRegister = useLegacyAshoatKeyserverCall( legacyKeyserverRegister, ); const callIdentityPasswordRegister = useIdentityPasswordRegister(); const identityRegisterUsernameAccount = React.useCallback( async ( accountSelection: UsernameAccountSelection, farcasterID: ?string, onAlertAcknowledged: ?() => mixed, ) => { const identityRegisterPromise = (async () => { try { return await callIdentityPasswordRegister( accountSelection.username, accountSelection.password, farcasterID, ); } catch (e) { const messageForException = getMessageForException(e); if (messageForException === 'username_reserved') { Alert.alert( usernameReservedAlertDetails.title, usernameReservedAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } else if (messageForException === 'username_already_exists') { Alert.alert( usernameTakenAlertDetails.title, usernameTakenAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } else if (messageForException === 'unsupported_version') { Alert.alert( appOutOfDateAlertDetails.title, appOutOfDateAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } else { Alert.alert( unknownErrorAlertDetails.title, unknownErrorAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } throw e; } })(); void dispatchActionPromise( identityRegisterActionTypes, identityRegisterPromise, ); await identityRegisterPromise; }, [callIdentityPasswordRegister, dispatchActionPromise], ); const legacyKeyserverRegisterUsernameAccount = React.useCallback( async ( accountSelection: UsernameAccountSelection, keyserverURL: string, onAlertAcknowledged: ?() => mixed, ) => { const extraInfo = await legacyLogInExtraInfo(); const legacyKeyserverRegisterPromise = (async () => { try { return await callLegacyKeyserverRegister( { ...extraInfo, username: accountSelection.username, password: accountSelection.password, }, { urlPrefixOverride: keyserverURL, }, ); } catch (e) { const messageForException = getMessageForException(e); if (messageForException === 'username_reserved') { Alert.alert( usernameReservedAlertDetails.title, usernameReservedAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } else if (messageForException === 'username_taken') { Alert.alert( usernameTakenAlertDetails.title, usernameTakenAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } else if (messageForException === 'client_version_unsupported') { Alert.alert( appOutOfDateAlertDetails.title, appOutOfDateAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } else { Alert.alert( unknownErrorAlertDetails.title, unknownErrorAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } throw e; } })(); void dispatchActionPromise( legacyKeyserverRegisterActionTypes, legacyKeyserverRegisterPromise, undefined, ({ calendarQuery: extraInfo.calendarQuery, }: LegacyLogInStartingPayload), ); await legacyKeyserverRegisterPromise; }, [legacyLogInExtraInfo, callLegacyKeyserverRegister, dispatchActionPromise], ); const legacySiweServerCall = useLegacySIWEServerCall(); const legacyKeyserverRegisterEthereumAccount = React.useCallback( async ( accountSelection: EthereumAccountSelection, keyserverURL: string, onAlertAcknowledged: ?() => mixed, ) => { try { await legacySiweServerCall(accountSelection, { urlPrefixOverride: keyserverURL, }); } catch (e) { const messageForException = getMessageForException(e); if (messageForException === 'client_version_unsupported') { Alert.alert( appOutOfDateAlertDetails.title, appOutOfDateAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } else { Alert.alert( unknownErrorAlertDetails.title, unknownErrorAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } throw e; } }, [legacySiweServerCall], ); const identityWalletRegisterCall = useIdentityWalletRegisterCall(); const identityRegisterEthereumAccount = React.useCallback( async ( accountSelection: EthereumAccountSelection, farcasterID: ?string, onNonceExpired: () => mixed, onAlertAcknowledged: ?() => mixed, ) => { try { await identityWalletRegisterCall({ address: accountSelection.address, message: accountSelection.message, signature: accountSelection.signature, fid: farcasterID, }); } catch (e) { const messageForException = getMessageForException(e); if (messageForException === 'nonce_expired') { onNonceExpired(); } else if (messageForException === 'unsupported_version') { Alert.alert( appOutOfDateAlertDetails.title, appOutOfDateAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } else { Alert.alert( unknownErrorAlertDetails.title, unknownErrorAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } throw e; } }, [identityWalletRegisterCall], ); const dispatch = useDispatch(); + const setLocalFID = useSetLocalFID(); const returnedFunc = React.useCallback( (input: RegistrationServerCallInput) => new Promise( // eslint-disable-next-line no-async-promise-executor async (resolve, reject) => { try { if (currentStep.step !== 'inactive') { return; } const { accountSelection, avatarData, keyserverURL: passedKeyserverURL, farcasterID, siweBackupSecrets, clearCachedSelections, onNonceExpired, onAlertAcknowledged, } = input; const keyserverURL = passedKeyserverURL ?? defaultURLPrefix; if ( accountSelection.accountType === 'username' && !usingCommServicesAccessToken ) { await legacyKeyserverRegisterUsernameAccount( accountSelection, keyserverURL, onAlertAcknowledged, ); } else if (accountSelection.accountType === 'username') { await identityRegisterUsernameAccount( accountSelection, farcasterID, onAlertAcknowledged, ); } else if (!usingCommServicesAccessToken) { await legacyKeyserverRegisterEthereumAccount( accountSelection, keyserverURL, onAlertAcknowledged, ); } else { await identityRegisterEthereumAccount( accountSelection, farcasterID, onNonceExpired, onAlertAcknowledged, ); } if (passedKeyserverURL) { dispatch({ type: setURLPrefix, payload: passedKeyserverURL, }); } - const fidToSave = farcasterID ?? NO_FID_METADATA; - dispatch({ - type: setSyncedMetadataEntryActionType, - payload: { - name: syncedMetadataNames.CURRENT_USER_FID, - data: fidToSave, - }, - }); + setLocalFID(farcasterID); if (siweBackupSecrets) { await commCoreModule.setSIWEBackupSecrets(siweBackupSecrets); } const credentialsToSave = accountSelection.accountType === 'username' ? { username: accountSelection.username, password: accountSelection.password, } : null; if (usingCommServicesAccessToken) { setCurrentStep({ step: 'identity_registration_dispatched', avatarData, clearCachedSelections, onAlertAcknowledged, credentialsToSave, resolve, reject, }); } else { setCurrentStep({ step: 'authoritative_keyserver_registration_dispatched', avatarData, clearCachedSelections, credentialsToSave, resolve, reject, }); } } catch (e) { reject(e); } }, ), [ currentStep, legacyKeyserverRegisterUsernameAccount, identityRegisterUsernameAccount, legacyKeyserverRegisterEthereumAccount, identityRegisterEthereumAccount, dispatch, + setLocalFID, ], ); // STEP 2: REGISTERING ON AUTHORITATIVE KEYSERVER const keyserverAuth = useKeyserverAuthWithRetry(authoritativeKeyserverID); const isRegisteredOnIdentity = useSelector( state => !!state.commServicesAccessToken && !!state.currentUserInfo && !state.currentUserInfo.anonymous, ); // We call deleteDiscardedIdentityAccount in order to reset state if identity // registration succeeds but authoritative keyserver auth fails const deleteDiscardedIdentityAccount = useDeleteDiscardedIdentityAccount(); const preRequestUserState = usePreRequestUserState(); const commServicesAccessToken = useSelector( state => state.commServicesAccessToken, ); const registeringOnAuthoritativeKeyserverRef = React.useRef(false); React.useEffect(() => { if ( !isRegisteredOnIdentity || currentStep.step !== 'identity_registration_dispatched' || registeringOnAuthoritativeKeyserverRef.current ) { return; } registeringOnAuthoritativeKeyserverRef.current = true; const { avatarData, clearCachedSelections, onAlertAcknowledged, credentialsToSave, resolve, reject, } = currentStep; void (async () => { try { await keyserverAuth({ authActionSource: process.env.BROWSER ? logInActionSources.keyserverAuthFromWeb : logInActionSources.keyserverAuthFromNative, setInProgress: () => {}, hasBeenCancelled: () => false, doNotRegister: false, password: credentialsToSave?.password, }); setCurrentStep({ step: 'authoritative_keyserver_registration_dispatched', avatarData, clearCachedSelections, credentialsToSave, resolve, reject, }); } catch (keyserverAuthException) { const messageForException = getMessageForException( keyserverAuthException, ); const discardIdentityAccountPromise: Promise = (async () => { try { const deletionResult = await deleteDiscardedIdentityAccount( credentialsToSave?.password, ); if (messageForException === 'client_version_unsupported') { Alert.alert( appOutOfDateAlertDetails.title, appOutOfDateAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } else { Alert.alert( unknownErrorAlertDetails.title, unknownErrorAlertDetails.message, [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); } return deletionResult; } catch (deleteException) { // We swallow the exception here because // discardIdentityAccountPromise is used in a scenario where the // user is visibly logged-out, and it's only used to reset state // (eg. Redux, SQLite) to a logged-out state. The state reset // only occurs when a success action is dispatched, so by // swallowing exceptions we ensure that we always dispatch a // success. Alert.alert( 'Account created but login failed', 'We were able to create your account, but were unable to log ' + 'you in. Try going back to the login screen and logging in ' + 'with your new credentials.', [{ text: 'OK', onPress: onAlertAcknowledged }], { cancelable: !onAlertAcknowledged }, ); return { currentUserInfo: null, preRequestUserState: { ...preRequestUserState, commServicesAccessToken, }, }; } })(); void dispatchActionPromise( deleteAccountActionTypes, discardIdentityAccountPromise, ); await waitUntilDatabaseDeleted(); reject(keyserverAuthException); setCurrentStep(inactiveStep); } finally { registeringOnAuthoritativeKeyserverRef.current = false; } })(); }, [ currentStep, isRegisteredOnIdentity, keyserverAuth, dispatchActionPromise, deleteDiscardedIdentityAccount, preRequestUserState, commServicesAccessToken, ]); // STEP 3: SETTING AVATAR const uploadSelectedMedia = useUploadSelectedMedia(); const nativeSetUserAvatar = useNativeSetUserAvatar(); const isLoggedInToAuthKeyserver = useIsLoggedInToAuthoritativeKeyserver(); const avatarBeingSetRef = React.useRef(false); React.useEffect(() => { if ( !isLoggedInToAuthKeyserver || currentStep.step !== 'authoritative_keyserver_registration_dispatched' || avatarBeingSetRef.current ) { return; } avatarBeingSetRef.current = true; const { avatarData, resolve, clearCachedSelections, credentialsToSave } = currentStep; void (async () => { try { if (!avatarData) { return; } let updateUserAvatarRequest; if (!avatarData.needsUpload) { ({ updateUserAvatarRequest } = avatarData); } else { const { mediaSelection } = avatarData; updateUserAvatarRequest = await uploadSelectedMedia( mediaSelection, 'keyserver', ); if (!updateUserAvatarRequest) { return; } } await nativeSetUserAvatar(updateUserAvatarRequest); } finally { dispatch({ type: setSyncedMetadataEntryActionType, payload: { name: syncedMetadataNames.DB_VERSION, data: `${persistConfig.version}`, }, }); dispatch({ type: setDataLoadedActionType, payload: { dataLoaded: true, }, }); clearCachedSelections(); if (credentialsToSave) { void setNativeCredentials(credentialsToSave); } setCurrentStep(inactiveStep); avatarBeingSetRef.current = false; resolve(); } })(); }, [ currentStep, isLoggedInToAuthKeyserver, uploadSelectedMedia, nativeSetUserAvatar, dispatch, ]); return returnedFunc; } export { useRegistrationServerCall };