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 @@ -12,6 +12,7 @@ } from 'lib/actions/user-actions.js'; import { isLoggedInToKeyserver } from 'lib/selectors/user-selectors.js'; import type { LogInStartingPayload } from 'lib/types/account-types.js'; +import type { SIWEBackupSecrets } from 'lib/types/siwe-types.js'; import { syncedMetadataNames } from 'lib/types/synced-metadata-types.js'; import { useLegacyAshoatKeyserverCall } from 'lib/utils/action-utils.js'; import { useDispatchActionPromise } from 'lib/utils/redux-promise-utils.js'; @@ -29,6 +30,7 @@ useNativeSetUserAvatar, useUploadSelectedMedia, } from '../../avatars/avatar-hooks.js'; +import { commCoreModule } from '../../native-modules.js'; import { useSelector } from '../../redux/redux-utils.js'; import { nativeLogInExtraInfoSelector } from '../../selectors/account-selectors.js'; import { @@ -58,9 +60,27 @@ | { +step: 'inactive' } | { +step: 'waiting_for_registration_call', + +siweBackupSecrets?: ?SIWEBackupSecrets, + +clearCachedSelections: () => void, +avatarData: ?AvatarData, +resolve: () => void, +reject: Error => void, + } + | { + +step: 'waiting_for_keyserver_auth', + +avatarData: ?AvatarData, + +clearCachedSelections: () => void, + +sqlitePersistPromise: Promise, + +resolve: () => void, + +reject: Error => void, + } + | { + +step: 'clear_cached_selections', + +sqlitePersistPromise: Promise, + +uploadAvatarPromise: Promise, + +clearCachedSelections: () => void, + +resolve: () => void, + +reject: Error => void, }; const inactiveStep = { step: 'inactive' }; @@ -199,8 +219,14 @@ if (currentStep.step !== 'inactive') { return; } - const { accountSelection, avatarData, keyserverURL, farcasterID } = - input; + const { + accountSelection, + avatarData, + keyserverURL, + farcasterID, + siweBackupSecrets, + clearCachedSelections, + } = input; if ( accountSelection.accountType === 'username' && !usingCommServicesAccessToken @@ -258,6 +284,8 @@ setCurrentStep({ step: 'waiting_for_registration_call', avatarData, + siweBackupSecrets, + clearCachedSelections, resolve, reject, }); @@ -276,42 +304,130 @@ ], ); - // STEP 2: SETTING AVATAR + // STEP 2: SET SQLITE DATA + const hasCurrentUserInfo = useSelector( + state => !!state.currentUserInfo && !state.currentUserInfo.anonymous, + ); - const uploadSelectedMedia = useUploadSelectedMedia(); - const nativeSetUserAvatar = useNativeSetUserAvatar(); + const sqlitePersistenceBeingHandlerRef = React.useRef(false); + React.useEffect(() => { + if ( + !hasCurrentUserInfo || + currentStep.step !== 'waiting_for_registration_call' || + sqlitePersistenceBeingHandlerRef.current + ) { + return; + } + + sqlitePersistenceBeingHandlerRef.current = true; + const { + siweBackupSecrets, + resolve, + reject, + avatarData, + clearCachedSelections, + } = currentStep; + + const sqlitePersistPromise = (async () => { + if (siweBackupSecrets) { + await commCoreModule.setSIWEBackupSecrets(siweBackupSecrets); + } + sqlitePersistenceBeingHandlerRef.current = false; + })(); + + setCurrentStep({ + step: 'waiting_for_keyserver_auth', + avatarData, + sqlitePersistPromise, + clearCachedSelections, + resolve, + reject, + }); + }, [hasCurrentUserInfo, currentStep]); + + // STEP 3: SETTING AVATAR const isLoggedInToAuthoritativeKeyserver = useSelector( isLoggedInToKeyserver(authoritativeKeyserverID), ); + const uploadSelectedMedia = useUploadSelectedMedia(); + const nativeSetUserAvatar = useNativeSetUserAvatar(); + const avatarBeingSetRef = React.useRef(false); React.useEffect(() => { if ( !isLoggedInToAuthoritativeKeyserver || - currentStep.step !== 'waiting_for_registration_call' || + currentStep.step !== 'waiting_for_keyserver_auth' || avatarBeingSetRef.current ) { return; } avatarBeingSetRef.current = true; - const { avatarData, resolve } = currentStep; - void (async () => { - try { - if (!avatarData) { + const { + avatarData, + resolve, + reject, + clearCachedSelections, + sqlitePersistPromise, + } = currentStep; + + const uploadAvatarPromise = (async () => { + if (!avatarData) { + return; + } + let updateUserAvatarRequest; + if (!avatarData.needsUpload) { + ({ updateUserAvatarRequest } = avatarData); + } else { + const { mediaSelection } = avatarData; + updateUserAvatarRequest = await uploadSelectedMedia(mediaSelection); + if (!updateUserAvatarRequest) { return; } - let updateUserAvatarRequest; - if (!avatarData.needsUpload) { - ({ updateUserAvatarRequest } = avatarData); - } else { - const { mediaSelection } = avatarData; - updateUserAvatarRequest = await uploadSelectedMedia(mediaSelection); - if (!updateUserAvatarRequest) { - return; - } - } - await nativeSetUserAvatar(updateUserAvatarRequest); + } + await nativeSetUserAvatar(updateUserAvatarRequest); + + avatarBeingSetRef.current = false; + })(); + + setCurrentStep({ + step: 'clear_cached_selections', + reject, + resolve, + uploadAvatarPromise, + clearCachedSelections, + sqlitePersistPromise, + }); + }, [ + currentStep, + isLoggedInToAuthoritativeKeyserver, + uploadSelectedMedia, + nativeSetUserAvatar, + dispatch, + ]); + + // STEP 4: CLEAR CACHED SELECTIONS + const clearCachedSelectionsBeingHandledRef = React.useRef(false); + + React.useEffect(() => { + if ( + currentStep.step !== 'clear_cached_selections' || + clearCachedSelectionsBeingHandledRef.current + ) { + return; + } + clearCachedSelectionsBeingHandledRef.current = true; + const { + sqlitePersistPromise, + uploadAvatarPromise, + resolve, + clearCachedSelections, + } = currentStep; + + void (async () => { + try { + await Promise.all([sqlitePersistPromise, uploadAvatarPromise]); } finally { dispatch({ type: setDataLoadedActionType, @@ -319,8 +435,10 @@ dataLoaded: true, }, }); + setCurrentStep(inactiveStep); - avatarBeingSetRef.current = false; + clearCachedSelections(); + clearCachedSelectionsBeingHandledRef.current = false; resolve(); } })(); 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 @@ -4,6 +4,8 @@ import * as React from 'react'; import { Text, View, Image, Linking } from 'react-native'; +import type { SIWEBackupSecrets } from 'lib/types/siwe-types.js'; + import RegistrationButtonContainer from './registration-button-container.react.js'; import RegistrationButton from './registration-button.react.js'; import RegistrationContainer from './registration-container.react.js'; @@ -26,6 +28,7 @@ +farcasterID: ?string, +accountSelection: AccountSelection, +avatarData: ?AvatarData, + +siweBackupSecrets?: ?SIWEBackupSecrets, }, }; @@ -44,20 +47,25 @@ function RegistrationTerms(props: Props): React.Node { const registrationContext = React.useContext(RegistrationContext); invariant(registrationContext, 'registrationContext should be set'); - const { register } = registrationContext; + const { register, setCachedSelections } = registrationContext; const [registrationInProgress, setRegistrationInProgress] = React.useState(false); const { userSelections } = props.route.params; + + const clearCachedSelections = React.useCallback(() => { + setCachedSelections({}); + }, [setCachedSelections]); + const onProceed = React.useCallback(async () => { setRegistrationInProgress(true); try { - await register(userSelections); + await register({ ...userSelections, clearCachedSelections }); } finally { setRegistrationInProgress(false); } - }, [register, userSelections]); + }, [register, userSelections, clearCachedSelections]); const styles = useStyles(unboundStyles); 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 @@ -43,6 +43,8 @@ +farcasterID: ?string, +accountSelection: AccountSelection, +avatarData: ?AvatarData, + +siweBackupSecrets?: ?SIWEBackupSecrets, + +clearCachedSelections: () => void, }; export type CachedUserSelections = { diff --git a/native/account/registration/siwe-backup-message-creation.react.js b/native/account/registration/siwe-backup-message-creation.react.js --- a/native/account/registration/siwe-backup-message-creation.react.js +++ b/native/account/registration/siwe-backup-message-creation.react.js @@ -44,6 +44,7 @@ function CreateSIWEBackupMessage(props: Props): React.Node { const { navigate } = props.navigation; const { params } = props.route; + const { userSelections } = params; const styles = useStyles(unboundStyles); @@ -76,24 +77,36 @@ const onSuccessfulWalletSignature = React.useCallback( (result: SIWEResult) => { const { message, signature } = result; + const newUserSelections = { + ...userSelections, + siweBackupSecrets: { message, signature }, + }; setCachedSelections(oldUserSelections => ({ ...oldUserSelections, siweBackupSecrets: { message, signature }, })); navigate<'RegistrationTerms'>({ name: RegistrationTermsRouteName, - params, + params: { userSelections: newUserSelections }, }); }, - [navigate, params, setCachedSelections], + [navigate, setCachedSelections, userSelections], ); + const { siweBackupSecrets } = cachedSelections; const onExistingWalletSignature = React.useCallback(() => { + const registrationTermsParams = { + userSelections: { + ...userSelections, + siweBackupSecrets, + }, + }; + navigate<'RegistrationTerms'>({ name: RegistrationTermsRouteName, - params, + params: registrationTermsParams, }); - }, [params, navigate]); + }, [navigate, siweBackupSecrets, userSelections]); let siwePanel; if (panelState !== 'closed') { @@ -109,8 +122,6 @@ ); } - const { siweBackupSecrets } = cachedSelections; - const newSignatureButtonText = siweBackupSecrets ? 'Encrypt with new signature' : 'Encrypt with Ethereum signature';