diff --git a/ts/components/loading/bar/SessionProgressBar.tsx b/ts/components/loading/bar/SessionProgressBar.tsx index a3dd274a7..7bc8ae0f8 100644 --- a/ts/components/loading/bar/SessionProgressBar.tsx +++ b/ts/components/loading/bar/SessionProgressBar.tsx @@ -25,6 +25,8 @@ const StyledTitle = styled(StyledText)` `; type Props = { + /** a percentage value */ + initialValue: number; /** a percentage value */ progress: number; color?: string; @@ -39,6 +41,7 @@ type Props = { export function SessionProgressBar(props: Props) { const { + initialValue, progress, width = '100%', backgroundColor = 'var(--border-color)', @@ -71,7 +74,7 @@ export function SessionProgressBar(props: Props) { diff --git a/ts/components/registration/hooks/index.tsx b/ts/components/registration/hooks/index.tsx index e6479d74e..9ecc84839 100644 --- a/ts/components/registration/hooks/index.tsx +++ b/ts/components/registration/hooks/index.tsx @@ -3,13 +3,11 @@ import { isEmpty } from 'lodash'; import { useCallback, useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { ONBOARDING_TIMES } from '../../../session/constants'; -import { trigger } from '../../../shims/events'; import { AccountRestoration, setAccountRestorationStep, } from '../../../state/onboarding/ducks/registration'; -import { registrationDone } from '../../../util/accountManager'; -import { setSignWithRecoveryPhrase } from '../../../util/storage'; +import { finishRestore } from '../stages/RestoreAccount'; let interval: NodeJS.Timeout; @@ -36,18 +34,14 @@ export const useRecoveryProgressEffect = (props: UseRecoveryProgressEffectProps) const dispatch = useDispatch(); const recoveryComplete = useCallback(async () => { - await setSignWithRecoveryPhrase(true); - await registrationDone(ourPubkey, displayName); - - window.log.debug(`WIP: [onboarding] restore account: logging in for ${displayName}`); - trigger('openInbox'); + await finishRestore(ourPubkey, displayName); }, [displayName, ourPubkey]); useEffect(() => { if (step === AccountRestoration.Loading) { interval = setInterval(() => { window.log.debug( - `WIP: [onboarding] restore account: progress ${progress} state ${AccountRestoration[step]}` + `WIP: [onboarding] restore account: ${AccountRestoration[step]} ${progress}%` ); if (progress < totalProgress) { @@ -57,9 +51,6 @@ export const useRecoveryProgressEffect = (props: UseRecoveryProgressEffectProps) if (progress >= totalProgress) { clearInterval(interval); // if we didn't get the display name in time, we need to enter it manually - window.log.debug( - `WIP: [onboarding] restore account: We failed with a time out when fetching a display name, so we restored manually` - ); dispatch(setAccountRestorationStep(AccountRestoration.DisplayName)); } }, ONBOARDING_TIMES.RECOVERY_TIMEOUT / totalProgress); @@ -67,6 +58,10 @@ export const useRecoveryProgressEffect = (props: UseRecoveryProgressEffectProps) if (step === AccountRestoration.Finishing) { interval = setInterval(() => { + window.log.debug( + `WIP: [onboarding] restore account: ${AccountRestoration[step]} ${progress}%` + ); + if (progress < totalProgress) { dispatch(setProgress(progress + 1)); } @@ -80,19 +75,24 @@ export const useRecoveryProgressEffect = (props: UseRecoveryProgressEffectProps) if (step === AccountRestoration.Finished) { interval = setInterval(() => { + window.log.debug( + `WIP: [onboarding] restore account: ${AccountRestoration[step]} ${progress}%` + ); + clearInterval(interval); if (!isEmpty(displayName)) { dispatch(setAccountRestorationStep(AccountRestoration.Complete)); } else { - window.log.debug( - `WIP: [onboarding] restore account: We failed with an error when fetching a display name, so we restored manually` - ); + // if we didn't get the display name in time, we need to enter it manually dispatch(setAccountRestorationStep(AccountRestoration.DisplayName)); } }, ONBOARDING_TIMES.RECOVERY_FINISHED); } if (step === AccountRestoration.Complete) { + window.log.debug( + `WIP: [onboarding] restore account: ${AccountRestoration[step]} ${progress}%` + ); clearInterval(interval); if (!isEmpty(ourPubkey) && !isEmpty(displayName)) { void recoveryComplete(); diff --git a/ts/components/registration/stages/RestoreAccount.tsx b/ts/components/registration/stages/RestoreAccount.tsx index 5bcf33acd..327637351 100644 --- a/ts/components/registration/stages/RestoreAccount.tsx +++ b/ts/components/registration/stages/RestoreAccount.tsx @@ -5,6 +5,7 @@ import { InvalidWordsError, NotEnoughWordsError } from '../../../session/crypto/ import { PromiseUtils } from '../../../session/utils'; import { TaskTimedOutError } from '../../../session/utils/Promise'; import { NotFoundError } from '../../../session/utils/errors'; +import { trigger } from '../../../shims/events'; import { AccountRestoration, setAccountRestorationStep, @@ -24,8 +25,12 @@ import { useRecoveryPassword, useRecoveryPasswordError, } from '../../../state/onboarding/selectors/registration'; -import { registerSingleDevice, signInByLinkingDevice } from '../../../util/accountManager'; -import { setSignInByLinking } from '../../../util/storage'; +import { + registerSingleDevice, + registrationDone, + signInByLinkingDevice, +} from '../../../util/accountManager'; +import { setSignInByLinking, setSignWithRecoveryPhrase } from '../../../util/storage'; import { Flex } from '../../basic/Flex'; import { SessionButton, SessionButtonColor } from '../../basic/SessionButton'; import { SpacerLG, SpacerSM } from '../../basic/Text'; @@ -38,59 +43,26 @@ import { useRecoveryProgressEffect } from '../hooks'; import { displayNameIsValid, resetRegistration, sanitizeDisplayNameOrToast } from '../utils'; import { AccountDetails } from './CreateAccount'; -type AccountRestoreDetails = AccountDetails & { dispatch: Dispatch }; +type AccountRestoreDetails = AccountDetails & { dispatch: Dispatch; abortSignal?: AbortSignal }; -/** - * Sign in/restore from seed. - * Ask for a display name, as we will drop incoming ConfigurationMessages if any are saved on the swarm. - * We will handle a ConfigurationMessage - */ -async function signInWithNewDisplayName(args: AccountRestoreDetails) { - const { displayName, recoveryPassword, dispatch } = args; +export async function finishRestore(pubkey: string, displayName: string) { + await setSignWithRecoveryPhrase(true); + await registrationDone(pubkey, displayName); - try { - const validDisplayName = displayNameIsValid(displayName); - - await resetRegistration(); - await registerSingleDevice( - recoveryPassword, - 'english', - validDisplayName, - async (pubkey: string) => { - dispatch(setHexGeneratedPubKey(pubkey)); - dispatch(setDisplayName(validDisplayName)); - dispatch(setAccountRestorationStep(AccountRestoration.Finishing)); - } - ); - } catch (e) { - await resetRegistration(); - throw e; - } + window.log.debug(`WIP: [onboarding] restore account: logging in for ${displayName}`); + trigger('openInbox'); } /** * This will try to sign in with the user recovery password. * If no ConfigurationMessage is received within ONBOARDING_RECOVERY_TIMEOUT, the user will be asked to enter a display name. */ -async function signInAndFetchDisplayName( - args: AccountRestoreDetails & { - /** this is used to trigger the loading animation further down the registration pipeline */ - loadingAnimationCallback: () => void; - abortSignal: AbortSignal; - } -) { - const { recoveryPassword, loadingAnimationCallback, dispatch, abortSignal } = args; +async function signInAndFetchDisplayName(args: AccountRestoreDetails) { + const { recoveryPassword, dispatch, abortSignal } = args; try { await resetRegistration(); - - const promiseLink = signInByLinkingDevice( - recoveryPassword, - 'english', - loadingAnimationCallback, - abortSignal - ); - + const promiseLink = signInByLinkingDevice(recoveryPassword, 'english', abortSignal); const promiseWait = PromiseUtils.waitForTask(done => { window.Whisper.events.on( 'configurationMessageReceived', @@ -112,6 +84,34 @@ async function signInAndFetchDisplayName( } } +/** + * Sign in/restore from seed. + * Ask for a display name, as we will drop incoming ConfigurationMessages if any are saved on the swarm. + * We will handle a ConfigurationMessage + */ +async function signInWithNewDisplayName(args: AccountRestoreDetails) { + const { displayName, recoveryPassword, dispatch } = args; + + try { + const validDisplayName = displayNameIsValid(displayName); + + await resetRegistration(); + await registerSingleDevice( + recoveryPassword, + 'english', + validDisplayName, + async (pubkey: string) => { + dispatch(setHexGeneratedPubKey(pubkey)); + dispatch(setDisplayName(validDisplayName)); + await finishRestore(pubkey, validDisplayName); + } + ); + } catch (e) { + await resetRegistration(); + throw e; + } +} + export const RestoreAccount = () => { const step = useOnboardAccountRestorationStep(); const recoveryPassword = useRecoveryPassword(); @@ -142,23 +142,21 @@ export const RestoreAccount = () => { `WIP: [onboarding] restore account: recoverAndFetchDisplayName() is starting recoveryPassword: ${recoveryPassword}` ); dispatch(setProgress(0)); + dispatch(setAccountRestorationStep(AccountRestoration.Loading)); await signInAndFetchDisplayName({ recoveryPassword, - loadingAnimationCallback: () => { - dispatch(setAccountRestorationStep(AccountRestoration.Loading)); - }, dispatch, abortSignal: abortController.signal, }); } catch (e) { - window.log.debug( - `WIP: [onboarding] restore account: restoration failed! Error: ${e.message || e}` - ); - if (e instanceof NotFoundError || e instanceof TaskTimedOutError) { - // abort the loading animation or display name polling if we get these errors. Now we enter a display name manually - abortController.abort(); - dispatch(setAccountRestorationStep(AccountRestoration.DisplayName)); + // abort display name polling if we get either error + if (!abortController.signal.aborted) { + abortController.abort(); + } + window.log.debug( + `WIP: [onboarding] restore account: We failed when fetching a display name, so we will enter it manually. Error: ${e.message || e} ` + ); return; } @@ -169,6 +167,9 @@ export const RestoreAccount = () => { } else { dispatch(setRecoveryPasswordError(window.i18n('recoveryPasswordErrorMessageGeneric'))); } + window.log.debug( + `WIP: [onboarding] restore account: there is a problem with the display nam. Error: ${e.message || e}` + ); dispatch(setAccountRestorationStep(AccountRestoration.RecoveryPassword)); } }; @@ -182,7 +183,6 @@ export const RestoreAccount = () => { window.log.debug( `WIP: [onboarding] restore account: recoverAndEnterDisplayName() is starting recoveryPassword: ${recoveryPassword} displayName: ${displayName}` ); - dispatch(setProgress(0)); await signInWithNewDisplayName({ displayName, recoveryPassword, @@ -206,7 +206,6 @@ export const RestoreAccount = () => { dispatch(setRecoveryPassword('')); dispatch(setDisplayName('')); dispatch(setProgress(0)); - dispatch(setRecoveryPasswordError(undefined)); dispatch(setDisplayNameError(undefined)); }} @@ -305,6 +304,9 @@ export const RestoreAccount = () => { alignItems="flex-start" > void, - abortSignal: AbortSignal + abortSignal?: AbortSignal ) { if (isEmpty(mnemonic)) { throw new Error('Session always needs a mnemonic. Either generated or given by the user'); @@ -79,7 +78,6 @@ export async function signInByLinkingDevice( const identityKeyPair = await generateKeypair(mnemonic, mnemonicLanguage); await setSignInByLinking(true); - loadingAnimationCallback(); await createAccount(identityKeyPair); await saveRecoveryPhrase(mnemonic); @@ -99,12 +97,13 @@ export async function signInByLinkingDevice( * @param mnemonic The mnemonic generated on first app loading and to use for this brand new user * @param mnemonicLanguage only 'english' is supported * @param displayName the display name to register, character limit is MAX_NAME_LENGTH_BYTES + * @param registerCallback when restoring an account, registration completion is handled elsewhere so we need to pass the pubkey back up to the caller */ export async function registerSingleDevice( generatedMnemonic: string, mnemonicLanguage: string, displayName: string, - restoreCallback?: (pubkey: string) => Promise + registerCallback?: (pubkey: string) => Promise ) { if (isEmpty(generatedMnemonic)) { throw new Error('Session always need a mnemonic. Either generated or given by the user'); @@ -126,9 +125,8 @@ export async function registerSingleDevice( throw new Error("We don't have a pubkey from the recovery password..."); } - if (restoreCallback) { - // when restoring an account completing the registration is handled by the RestoreAccount component - await restoreCallback(pubKeyString); + if (registerCallback) { + await registerCallback(pubKeyString); } else { await registrationDone(pubKeyString, displayName); }