Page MenuHomePhabricator

No OneTemporary

diff --git a/lib/hooks/login-hooks.js b/lib/hooks/login-hooks.js
index d49f7fed0..4ef63a891 100644
--- a/lib/hooks/login-hooks.js
+++ b/lib/hooks/login-hooks.js
@@ -1,192 +1,192 @@
// @flow
import * as React from 'react';
import { setDataLoadedActionType } from '../actions/client-db-store-actions.js';
import {
identityLogInActionTypes,
useIdentityPasswordLogIn,
useIdentityWalletLogIn,
logOutActionTypes,
useIdentityLogOut,
} from '../actions/user-actions.js';
-import { useKeyserverAuth } from '../keyserver-conn/keyserver-auth.js';
+import { useKeyserverAuthWithRetry } from '../keyserver-conn/keyserver-auth.js';
import { logInActionSources } from '../types/account-types.js';
import { authoritativeKeyserverID } from '../utils/authoritative-keyserver.js';
import { useDispatchActionPromise } from '../utils/redux-promise-utils.js';
import { useSelector, useDispatch } from '../utils/redux-utils.js';
import { waitUntilDatabaseDeleted } from '../utils/wait-until-db-deleted.js';
// We can't just do everything in one async callback, since the server calls
// would get bound to Redux state from before the login. In order to pick up the
// updated CSAT and currentUserInfo from Redux, we break the login into two
// steps.
type CurrentStep =
| { +step: 'inactive' }
| {
+step: 'identity_login_dispatched',
+resolve: () => void,
+reject: Error => void,
};
const inactiveStep = { step: 'inactive' };
type LogInInputs =
| {
+accountType: 'username',
+username: string,
+password: string,
}
| {
+accountType: 'ethereum',
+walletAddress: string,
+siweMessage: string,
+siweSignature: string,
};
function useLogIn(): LogInInputs => Promise<void> {
const [currentStep, setCurrentStep] =
React.useState<CurrentStep>(inactiveStep);
const identityPasswordLogIn = useIdentityPasswordLogIn();
const identityWalletLogIn = useIdentityWalletLogIn();
const dispatchActionPromise = useDispatchActionPromise();
const returnedFunc = React.useCallback(
(logInInputs: LogInInputs) =>
new Promise<void>(
// eslint-disable-next-line no-async-promise-executor
async (resolve, reject) => {
if (currentStep.step !== 'inactive') {
return;
}
const action =
logInInputs.accountType === 'username'
? identityPasswordLogIn(
logInInputs.username,
logInInputs.password,
)
: identityWalletLogIn(
logInInputs.walletAddress,
logInInputs.siweMessage,
logInInputs.siweSignature,
);
void dispatchActionPromise(identityLogInActionTypes, action);
try {
await action;
setCurrentStep({
step: 'identity_login_dispatched',
resolve,
reject,
});
} catch (e) {
reject(e);
}
},
),
[
currentStep,
dispatchActionPromise,
identityPasswordLogIn,
identityWalletLogIn,
],
);
- const keyserverAuth = useKeyserverAuth(authoritativeKeyserverID());
+ const keyserverAuth = useKeyserverAuthWithRetry(authoritativeKeyserverID());
const isRegisteredOnIdentity = useSelector(
state =>
!!state.commServicesAccessToken &&
!!state.currentUserInfo &&
!state.currentUserInfo.anonymous,
);
// We call identityLogOut in order to reset state if identity auth succeeds
// but authoritative keyserver auth fails
const identityLogOut = useIdentityLogOut();
const registeringOnAuthoritativeKeyserverRef = React.useRef(false);
const dispatch = useDispatch();
React.useEffect(() => {
if (
!isRegisteredOnIdentity ||
currentStep.step !== 'identity_login_dispatched' ||
registeringOnAuthoritativeKeyserverRef.current
) {
return;
}
registeringOnAuthoritativeKeyserverRef.current = true;
const { resolve, reject } = currentStep;
void (async () => {
try {
await keyserverAuth({
authActionSource: process.env.BROWSER
? logInActionSources.keyserverAuthFromWeb
: logInActionSources.keyserverAuthFromNative,
setInProgress: () => {},
hasBeenCancelled: () => false,
doNotRegister: false,
});
dispatch({
type: setDataLoadedActionType,
payload: {
dataLoaded: true,
},
});
resolve();
} catch (e) {
void dispatchActionPromise(logOutActionTypes, identityLogOut());
await waitUntilDatabaseDeleted();
reject(e);
} finally {
setCurrentStep(inactiveStep);
registeringOnAuthoritativeKeyserverRef.current = false;
}
})();
}, [
currentStep,
isRegisteredOnIdentity,
keyserverAuth,
dispatch,
dispatchActionPromise,
identityLogOut,
]);
return returnedFunc;
}
function usePasswordLogIn(): (
username: string,
password: string,
) => Promise<void> {
const logIn = useLogIn();
return React.useCallback(
(username: string, password: string) =>
logIn({
accountType: 'username',
username,
password,
}),
[logIn],
);
}
function useWalletLogIn(): (
walletAddress: string,
siweMessage: string,
siweSignature: string,
) => Promise<void> {
const logIn = useLogIn();
return React.useCallback(
(walletAddress: string, siweMessage: string, siweSignature: string) =>
logIn({
accountType: 'ethereum',
walletAddress,
siweMessage,
siweSignature,
}),
[logIn],
);
}
export { usePasswordLogIn, useWalletLogIn };
diff --git a/lib/keyserver-conn/keyserver-auth.js b/lib/keyserver-conn/keyserver-auth.js
index 46ede7793..884e29133 100644
--- a/lib/keyserver-conn/keyserver-auth.js
+++ b/lib/keyserver-conn/keyserver-auth.js
@@ -1,188 +1,199 @@
// @flow
import invariant from 'invariant';
import * as React from 'react';
import { useCallKeyserverEndpointContext } from './call-keyserver-endpoint-provider.react.js';
import { extractKeyserverIDFromID } from './keyserver-call-utils.js';
import {
CANCELLED_ERROR,
type CallKeyserverEndpoint,
} from './keyserver-conn-types.js';
import {
keyserverAuthActionTypes,
keyserverAuthRawAction,
} from '../actions/user-actions.js';
import { filterThreadIDsInFilterList } from '../reducers/calendar-filters-reducer.js';
import {
cookieSelector,
deviceTokenSelector,
} from '../selectors/keyserver-selectors.js';
import { IdentityClientContext } from '../shared/identity-client-context.js';
import type { AuthActionSource } from '../types/account-types.js';
import { authoritativeKeyserverID } from '../utils/authoritative-keyserver.js';
import { getConfig } from '../utils/config.js';
+import { getMessageForException } from '../utils/errors.js';
import { useDispatchActionPromise } from '../utils/redux-promise-utils.js';
import { useSelector } from '../utils/redux-utils.js';
import sleep from '../utils/sleep.js';
const AUTH_RETRY_DELAY_MS = 60000;
type KeyserverAuthInputs = {
+authActionSource: AuthActionSource,
+setInProgress: boolean => mixed,
+hasBeenCancelled: () => boolean,
+doNotRegister: boolean,
};
type RawKeyserverAuthFunc =
KeyserverAuthInputs => CallKeyserverEndpoint => Promise<void>;
function useRawKeyserverAuth(keyserverID: string): RawKeyserverAuthFunc {
const navInfo = useSelector(state => state.navInfo);
const calendarFilters = useSelector(state => state.calendarFilters);
const calendarQuery = React.useMemo(() => {
const filters = filterThreadIDsInFilterList(
calendarFilters,
(threadID: string) => extractKeyserverIDFromID(threadID) === keyserverID,
);
return {
startDate: navInfo.startDate,
endDate: navInfo.endDate,
filters,
};
}, [calendarFilters, keyserverID, navInfo.endDate, navInfo.startDate]);
const cookie = useSelector(cookieSelector(keyserverID));
const keyserverDeviceToken = useSelector(deviceTokenSelector(keyserverID));
// We have an assumption that we should be always connected to the
// authoritative keyserver. It is possible that a token which it has is
// correct, so we can try to use it. In worst case it is invalid and our
// push-handler will try to fix it.
const authoritativeKeyserverDeviceToken = useSelector(
deviceTokenSelector(authoritativeKeyserverID()),
);
const deviceToken = keyserverDeviceToken ?? authoritativeKeyserverDeviceToken;
const dispatchActionPromise = useDispatchActionPromise();
const identityContext = React.useContext(IdentityClientContext);
invariant(identityContext, 'Identity context should be set');
const { identityClient, getAuthMetadata } = identityContext;
const { olmAPI } = getConfig();
const currentUserInfo = useSelector(state => state.currentUserInfo);
return React.useCallback(
(inputs: KeyserverAuthInputs) =>
async (innerCallKeyserverEndpoint: CallKeyserverEndpoint) => {
const {
authActionSource,
setInProgress,
hasBeenCancelled,
doNotRegister,
} = inputs;
try {
const [keyserverKeys] = await Promise.all([
identityClient.getKeyserverKeys(keyserverID),
olmAPI.initializeCryptoAccount(),
]);
if (hasBeenCancelled()) {
throw new Error(CANCELLED_ERROR);
}
const [notifsSession, contentSession, { userID, deviceID }] =
await Promise.all([
olmAPI.notificationsSessionCreator(
cookie,
keyserverKeys.identityKeysBlob.notificationIdentityPublicKeys,
keyserverKeys.notifInitializationInfo,
keyserverID,
),
olmAPI.contentOutboundSessionCreator(
keyserverKeys.identityKeysBlob.primaryIdentityPublicKeys,
keyserverKeys.contentInitializationInfo,
),
getAuthMetadata(),
]);
invariant(userID, 'userID should be set');
invariant(deviceID, 'deviceID should be set');
const deviceTokenUpdateInput = deviceToken
? { [keyserverID]: { deviceToken } }
: {};
if (hasBeenCancelled()) {
throw new Error(CANCELLED_ERROR);
}
const authPromise = keyserverAuthRawAction(
innerCallKeyserverEndpoint,
)({
userID,
deviceID,
doNotRegister,
calendarQuery,
deviceTokenUpdateInput,
authActionSource,
keyserverData: {
[keyserverID]: {
initialContentEncryptedMessage:
contentSession.encryptedData.message,
initialNotificationsEncryptedMessage: notifsSession,
},
},
preRequestUserInfo: currentUserInfo,
});
void dispatchActionPromise(keyserverAuthActionTypes, authPromise);
await authPromise;
} catch (e) {
if (hasBeenCancelled()) {
return;
}
console.log(
`Error while authenticating to keyserver with id ${keyserverID}`,
e,
);
throw e;
} finally {
if (!hasBeenCancelled()) {
void (async () => {
await sleep(AUTH_RETRY_DELAY_MS);
setInProgress(false);
})();
}
}
},
[
calendarQuery,
cookie,
deviceToken,
dispatchActionPromise,
getAuthMetadata,
identityClient,
keyserverID,
olmAPI,
currentUserInfo,
],
);
}
type KeyserverAuthFunc = KeyserverAuthInputs => Promise<void>;
-function useKeyserverAuth(keyserverID: string): KeyserverAuthFunc {
+function useKeyserverAuthWithRetry(keyserverID: string): KeyserverAuthFunc {
const rawKeyserverAuth = useRawKeyserverAuth(keyserverID);
const { callKeyserverEndpoint } = useCallKeyserverEndpointContext();
return React.useCallback(
- (inputs: KeyserverAuthInputs) =>
- rawKeyserverAuth(inputs)(callKeyserverEndpoint),
+ async (inputs: KeyserverAuthInputs) => {
+ try {
+ return await rawKeyserverAuth(inputs)(callKeyserverEndpoint);
+ } catch (e) {
+ if (getMessageForException(e) === 'olm_session_creation_failure') {
+ // We retry in case we were accidentally vended an invalid OTK the
+ // first time
+ return await rawKeyserverAuth(inputs)(callKeyserverEndpoint);
+ }
+ throw e;
+ }
+ },
[rawKeyserverAuth, callKeyserverEndpoint],
);
}
-export { useRawKeyserverAuth, useKeyserverAuth };
+export { useRawKeyserverAuth, useKeyserverAuthWithRetry };
diff --git a/native/account/registration/registration-server-call.js b/native/account/registration/registration-server-call.js
index 32ee6d9ad..477553e92 100644
--- a/native/account/registration/registration-server-call.js
+++ b/native/account/registration/registration-server-call.js
@@ -1,461 +1,461 @@
// @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 { useKeyserverAuth } from 'lib/keyserver-conn/keyserver-auth.js';
+import { useKeyserverAuthWithRetry } from 'lib/keyserver-conn/keyserver-auth.js';
import { useLegacyAshoatKeyserverCall } from 'lib/keyserver-conn/legacy-keyserver-call.js';
import { isLoggedInToKeyserver } from 'lib/selectors/user-selectors.js';
import {
type LegacyLogInStartingPayload,
logInActionSources,
} from 'lib/types/account-types.js';
import { syncedMetadataNames } from 'lib/types/synced-metadata-types.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,
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 { 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 { 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,
+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<void> {
const [currentStep, setCurrentStep] =
React.useState<CurrentStep>(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,
) => {
const identityRegisterPromise = (async () => {
try {
return await callIdentityPasswordRegister(
accountSelection.username,
accountSelection.password,
farcasterID,
);
} catch (e) {
if (e.message === 'username reserved') {
Alert.alert(
UsernameReservedAlertDetails.title,
UsernameReservedAlertDetails.message,
);
} else if (e.message === 'username already exists') {
Alert.alert(
UsernameTakenAlertDetails.title,
UsernameTakenAlertDetails.message,
);
} else if (e.message === 'Unsupported version') {
Alert.alert(
AppOutOfDateAlertDetails.title,
AppOutOfDateAlertDetails.message,
);
} else {
Alert.alert(
UnknownErrorAlertDetails.title,
UnknownErrorAlertDetails.message,
);
}
throw e;
}
})();
void dispatchActionPromise(
identityRegisterActionTypes,
identityRegisterPromise,
);
await identityRegisterPromise;
},
[callIdentityPasswordRegister, dispatchActionPromise],
);
const legacyKeyserverRegisterUsernameAccount = React.useCallback(
async (
accountSelection: UsernameAccountSelection,
keyserverURL: string,
) => {
const extraInfo = await legacyLogInExtraInfo();
const legacyKeyserverRegisterPromise = (async () => {
try {
return await callLegacyKeyserverRegister(
{
...extraInfo,
username: accountSelection.username,
password: accountSelection.password,
},
{
urlPrefixOverride: keyserverURL,
},
);
} catch (e) {
if (e.message === 'username_reserved') {
Alert.alert(
UsernameReservedAlertDetails.title,
UsernameReservedAlertDetails.message,
);
} else if (e.message === 'username_taken') {
Alert.alert(
UsernameTakenAlertDetails.title,
UsernameTakenAlertDetails.message,
);
} else if (e.message === 'client_version_unsupported') {
Alert.alert(
AppOutOfDateAlertDetails.title,
AppOutOfDateAlertDetails.message,
);
} else {
Alert.alert(
UnknownErrorAlertDetails.title,
UnknownErrorAlertDetails.message,
);
}
throw e;
}
})();
void dispatchActionPromise(
legacyKeyserverRegisterActionTypes,
legacyKeyserverRegisterPromise,
undefined,
({
calendarQuery: extraInfo.calendarQuery,
}: LegacyLogInStartingPayload),
);
await legacyKeyserverRegisterPromise;
},
[legacyLogInExtraInfo, callLegacyKeyserverRegister, dispatchActionPromise],
);
const legacySiweServerCall = useLegacySIWEServerCall();
const identityWalletRegisterCall = useIdentityWalletRegisterCall();
const dispatch = useDispatch();
const returnedFunc = React.useCallback(
(input: RegistrationServerCallInput) =>
new Promise<void>(
// eslint-disable-next-line no-async-promise-executor
async (resolve, reject) => {
try {
if (currentStep.step !== 'inactive') {
return;
}
const {
accountSelection,
avatarData,
keyserverURL,
farcasterID,
siweBackupSecrets,
clearCachedSelections,
} = input;
if (
accountSelection.accountType === 'username' &&
!usingCommServicesAccessToken
) {
await legacyKeyserverRegisterUsernameAccount(
accountSelection,
keyserverURL,
);
} else if (accountSelection.accountType === 'username') {
await identityRegisterUsernameAccount(
accountSelection,
farcasterID,
);
} else if (!usingCommServicesAccessToken) {
try {
await legacySiweServerCall(accountSelection, {
urlPrefixOverride: keyserverURL,
});
} catch (e) {
Alert.alert(
UnknownErrorAlertDetails.title,
UnknownErrorAlertDetails.message,
);
throw e;
}
} else {
try {
await identityWalletRegisterCall({
address: accountSelection.address,
message: accountSelection.message,
signature: accountSelection.signature,
fid: farcasterID,
});
} catch (e) {
Alert.alert(
UnknownErrorAlertDetails.title,
UnknownErrorAlertDetails.message,
);
throw e;
}
}
dispatch({
type: setURLPrefix,
payload: keyserverURL,
});
if (farcasterID) {
dispatch({
type: setSyncedMetadataEntryActionType,
payload: {
name: syncedMetadataNames.CURRENT_USER_FID,
data: farcasterID,
},
});
}
if (siweBackupSecrets) {
await commCoreModule.setSIWEBackupSecrets(siweBackupSecrets);
}
const credentialsToSave =
accountSelection.accountType === 'username'
? {
username: accountSelection.username,
password: accountSelection.password,
}
: null;
setCurrentStep({
step: 'identity_registration_dispatched',
avatarData,
clearCachedSelections,
credentialsToSave,
resolve,
reject,
});
} catch (e) {
reject(e);
}
},
),
[
currentStep,
legacyKeyserverRegisterUsernameAccount,
identityRegisterUsernameAccount,
legacySiweServerCall,
dispatch,
identityWalletRegisterCall,
],
);
// STEP 2: REGISTERING ON AUTHORITATIVE KEYSERVER
- const keyserverAuth = useKeyserverAuth(authoritativeKeyserverID);
+ 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 registeringOnAuthoritativeKeyserverRef = React.useRef(false);
React.useEffect(() => {
if (
!isRegisteredOnIdentity ||
currentStep.step !== 'identity_registration_dispatched' ||
registeringOnAuthoritativeKeyserverRef.current
) {
return;
}
registeringOnAuthoritativeKeyserverRef.current = true;
const {
avatarData,
clearCachedSelections,
credentialsToSave,
resolve,
reject,
} = currentStep;
void (async () => {
try {
await keyserverAuth({
authActionSource: process.env.BROWSER
? logInActionSources.keyserverAuthFromWeb
: logInActionSources.keyserverAuthFromNative,
setInProgress: () => {},
hasBeenCancelled: () => false,
doNotRegister: false,
});
setCurrentStep({
step: 'authoritative_keyserver_registration_dispatched',
avatarData,
clearCachedSelections,
credentialsToSave,
resolve,
reject,
});
} catch (keyserverAuthException) {
const discardIdentityAccountPromise = (async () => {
try {
const deletionResult = await deleteDiscardedIdentityAccount();
Alert.alert(
UnknownErrorAlertDetails.title,
UnknownErrorAlertDetails.message,
);
return deletionResult;
} catch (deleteException) {
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.',
);
throw deleteException;
}
})();
void dispatchActionPromise(
deleteAccountActionTypes,
discardIdentityAccountPromise,
);
await waitUntilDatabaseDeleted();
reject(keyserverAuthException);
setCurrentStep(inactiveStep);
} finally {
registeringOnAuthoritativeKeyserverRef.current = false;
}
})();
}, [
currentStep,
isRegisteredOnIdentity,
keyserverAuth,
dispatchActionPromise,
deleteDiscardedIdentityAccount,
]);
// STEP 3: SETTING AVATAR
const uploadSelectedMedia = useUploadSelectedMedia();
const nativeSetUserAvatar = useNativeSetUserAvatar();
const isLoggedInToAuthoritativeKeyserver = useSelector(
isLoggedInToKeyserver(authoritativeKeyserverID),
);
const avatarBeingSetRef = React.useRef(false);
React.useEffect(() => {
if (
!isLoggedInToAuthoritativeKeyserver ||
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);
if (!updateUserAvatarRequest) {
return;
}
}
await nativeSetUserAvatar(updateUserAvatarRequest);
} finally {
dispatch({
type: setDataLoadedActionType,
payload: {
dataLoaded: true,
},
});
clearCachedSelections();
if (credentialsToSave) {
void setNativeCredentials(credentialsToSave);
}
setCurrentStep(inactiveStep);
avatarBeingSetRef.current = false;
resolve();
}
})();
}, [
currentStep,
isLoggedInToAuthoritativeKeyserver,
uploadSelectedMedia,
nativeSetUserAvatar,
dispatch,
]);
return returnedFunc;
}
export { useRegistrationServerCall };

File Metadata

Mime Type
text/x-diff
Expires
Mon, Dec 23, 6:00 AM (1 d, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2690436
Default Alt Text
(28 KB)

Event Timeline