From 2265c59dcc4e47a1c8bab3a8455960361ccc8e55 Mon Sep 17 00:00:00 2001 From: William Grant Date: Thu, 11 Apr 2024 15:41:02 +1000 Subject: [PATCH] fix: recover an account with a new display name if linking fails added stop function to swarm polling --- _locales/en/messages.json | 3 +- .../registration/RegistrationStages.tsx | 36 +++++++++---------- ts/components/registration/SignInTab.tsx | 11 +++--- ts/session/apis/snode_api/swarmPolling.ts | 25 ++++++++++--- ts/types/LocalizerKeys.ts | 1 + 5 files changed, 47 insertions(+), 29 deletions(-) diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 176161e2c..7324c04d2 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -578,5 +578,6 @@ "resolution": "Resolution", "duration": "Duration", "notApplicable": "N/A", - "unknownError": "Unknown Error" + "unknownError": "Unknown Error", + "displayNameErrorNew": "We were unable to load your display name. Please enter a new display name to continue." } diff --git a/ts/components/registration/RegistrationStages.tsx b/ts/components/registration/RegistrationStages.tsx index fe3e196d6..fe29943f4 100644 --- a/ts/components/registration/RegistrationStages.tsx +++ b/ts/components/registration/RegistrationStages.tsx @@ -1,12 +1,11 @@ import React, { createContext, useEffect, useState } from 'react'; -import { SignUpMode, SignUpTab } from './SignUpTab'; -import { SignInMode, SignInTab } from './SignInTab'; import { Data } from '../../data/data'; +import { SettingsKey } from '../../data/settings-key'; import { getSwarmPollingInstance } from '../../session/apis/snode_api'; import { getConversationController } from '../../session/conversations'; import { mnDecode } from '../../session/crypto/mnemonic'; import { PromiseUtils, StringUtils, ToastUtils } from '../../session/utils'; -import { TaskTimedOutError } from '../../session/utils/Promise'; +import { fromHex } from '../../session/utils/String'; import { trigger } from '../../shims/events'; import { generateMnemonic, @@ -14,9 +13,9 @@ import { sessionGenerateKeyPair, signInByLinkingDevice, } from '../../util/accountManager'; -import { fromHex } from '../../session/utils/String'; -import { setSignInByLinking, setSignWithRecoveryPhrase, Storage } from '../../util/storage'; -import { SettingsKey } from '../../data/settings-key'; +import { Storage, setSignInByLinking, setSignWithRecoveryPhrase } from '../../util/storage'; +import { SignInMode, SignInTab } from './SignInTab'; +import { SignUpMode, SignUpTab } from './SignUpTab'; export async function resetRegistration() { await Data.removeAll(); @@ -104,7 +103,10 @@ export async function signInWithRecovery(signInDetails: { * This is will try to sign in with the user recovery phrase. * If no ConfigurationMessage is received in 60seconds, the loading will be canceled. */ -export async function signInWithLinking(signInDetails: { userRecoveryPhrase: string }) { +export async function signInWithLinking( + signInDetails: { userRecoveryPhrase: string }, + setSignInMode: (phase: SignInMode) => void +) { const { userRecoveryPhrase } = signInDetails; window?.log?.info('LINKING DEVICE'); @@ -136,18 +138,14 @@ export async function signInWithLinking(signInDetails: { userRecoveryPhrase: str trigger('openInbox'); } catch (e) { await resetRegistration(); - if (e instanceof TaskTimedOutError) { - ToastUtils.pushToastError( - 'registrationError', - 'Could not find your display name. Please Sign In by Restoring Your Account instead.' - ); - } else { - ToastUtils.pushToastError( - 'registrationError', - `Error: ${e.message || 'Something went wrong'}` - ); - } - window?.log?.warn('exception during registration:', e); + ToastUtils.pushToastError('registrationError', window.i18n('displayNameErrorNew')); + window?.log?.error( + '[signInWithLinking] Error during sign in by linking lets try and sign in by recovery phrase', + e.message || e + ); + getSwarmPollingInstance().stop(e); + await setSignWithRecoveryPhrase(false); + setSignInMode(SignInMode.UsingRecoveryPhrase); } } diff --git a/ts/components/registration/SignInTab.tsx b/ts/components/registration/SignInTab.tsx index 088d4bc36..7392a137e 100644 --- a/ts/components/registration/SignInTab.tsx +++ b/ts/components/registration/SignInTab.tsx @@ -123,7 +123,7 @@ export const SignInTab = () => { // from the configuration message will be used. const showDisplayNameField = isRecovery; - // Display name is required only on isRecoveryMode + // Display name is required only on isRecoveryMode or if linking a device fails const displayNameOK = (isRecovery && !displayNameError && !!displayName) || isLinking; // Seed is mandatory no matter which mode @@ -139,9 +139,12 @@ export const SignInTab = () => { }); } else if (isLinking) { setIsLoading(true); - await signInWithLinking({ - userRecoveryPhrase: recoveryPhrase, - }); + await signInWithLinking( + { + userRecoveryPhrase: recoveryPhrase, + }, + setSignInMode + ); setIsLoading(false); } }; diff --git a/ts/session/apis/snode_api/swarmPolling.ts b/ts/session/apis/snode_api/swarmPolling.ts index c43fce899..e8ddcc092 100644 --- a/ts/session/apis/snode_api/swarmPolling.ts +++ b/ts/session/apis/snode_api/swarmPolling.ts @@ -57,6 +57,8 @@ export function extractWebSocketContent( } let instance: SwarmPolling | undefined; +const timeouts: Array = []; + export const getSwarmPollingInstance = () => { if (!instance) { instance = new SwarmPolling(); @@ -84,9 +86,11 @@ export class SwarmPolling { if (waitForFirstPoll) { await this.pollForAllKeys(); } else { - setTimeout(() => { - void this.pollForAllKeys(); - }, 4000); + timeouts.push( + setTimeout(() => { + void this.pollForAllKeys(); + }, 4000) + ); } } @@ -98,6 +102,17 @@ export class SwarmPolling { this.hasStarted = false; } + // TODO[epic=ses-50] this is a temporary solution until onboarding is merged + public stop(e: Error) { + window.log.error(`[swarmPolling] stopped polling due to error: ${e.message || e}`); + + for (let i = 0; i < timeouts.length; i++) { + clearTimeout(timeouts[i]); + window.log.debug(`[swarmPolling] cleared timeout ${timeouts[i]} `); + } + this.resetSwarmPolling(); + } + public forcePolledTimestamp(pubkey: PubKey, lastPoll: number) { this.groupPolling = this.groupPolling.map(group => { if (PubKey.isEqual(pubkey, group.pubkey)) { @@ -163,7 +178,7 @@ export class SwarmPolling { if (!window.getGlobalOnlineStatus()) { window?.log?.error('pollForAllKeys: offline'); // Very important to set up a new polling call so we do retry at some point - setTimeout(this.pollForAllKeys.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE); + timeouts.push(setTimeout(this.pollForAllKeys.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE)); return; } // we always poll as often as possible for our pubkey @@ -200,7 +215,7 @@ export class SwarmPolling { window?.log?.warn('pollForAllKeys exception: ', e); throw e; } finally { - setTimeout(this.pollForAllKeys.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE); + timeouts.push(setTimeout(this.pollForAllKeys.bind(this), SWARM_POLLING_TIMEOUT.ACTIVE)); } } diff --git a/ts/types/LocalizerKeys.ts b/ts/types/LocalizerKeys.ts index 3b31283ed..14aa1aba9 100644 --- a/ts/types/LocalizerKeys.ts +++ b/ts/types/LocalizerKeys.ts @@ -162,6 +162,7 @@ export type LocalizerKeys = | 'disappears' | 'displayName' | 'displayNameEmpty' + | 'displayNameErrorNew' | 'displayNameTooLong' | 'document' | 'documents'