From 6b6504f32f34041f5c33ef3348a244d32bffe399 Mon Sep 17 00:00:00 2001 From: RyukTheCoder Date: Sun, 26 Jan 2025 09:26:18 +0000 Subject: [PATCH] fix: remove namespace from storage on auto connect failure --- wallets/react/src/hub/autoConnect.ts | 93 ++++++++++++++++--------- wallets/react/src/hub/helpers.ts | 12 +++- wallets/react/src/legacy/autoConnect.ts | 36 ++++------ 3 files changed, 82 insertions(+), 59 deletions(-) diff --git a/wallets/react/src/hub/autoConnect.ts b/wallets/react/src/hub/autoConnect.ts index 94e02ac33..90670f54f 100644 --- a/wallets/react/src/hub/autoConnect.ts +++ b/wallets/react/src/hub/autoConnect.ts @@ -15,10 +15,15 @@ import { import { HUB_LAST_CONNECTED_WALLETS } from '../legacy/mod.js'; -import { sequentiallyRun } from './helpers.js'; +import { runSequentiallyWithoutFailure } from './helpers.js'; import { LastConnectedWalletsFromStorage } from './lastConnectedWallets.js'; import { convertNamespaceNetworkToEvmChainId } from './utils.js'; +// Getting connected wallets from storage +const lastConnectedWalletsFromStorage = new LastConnectedWalletsFromStorage( + HUB_LAST_CONNECTED_WALLETS +); + /** * Run `.connect` action on some selected namespaces (passed as param) for a provider. */ @@ -60,21 +65,60 @@ async function eagerConnect( targetNamespaces.push([namespaceInput, result]); }); - const finalResult = targetNamespaces.map(([info, namespace]) => { - const evmChain = legacyIsEvmNamespace(info) - ? convertNamespaceNetworkToEvmChainId(info, allBlockChains || []) - : undefined; - const chain = evmChain || info.network; - - return async () => await namespace.connect(chain); - }); + const connectNamespacesPromises = targetNamespaces.map( + ([info, namespace]) => { + const evmChain = legacyIsEvmNamespace(info) + ? convertNamespaceNetworkToEvmChainId(info, allBlockChains || []) + : undefined; + const chain = evmChain || info.network; + + return async () => + await namespace.connect(chain).catch((e) => { + /* + * Since we check for connect failures using `instanceof Error` + * this check is added here to make sure the thrown error always is an instance of `Error` + */ + if (e instanceof Error) { + throw e; + } + throw new Error(e); + }); + } + ); /** * Sometimes calling methods on a instance in parallel, would cause an error in wallet. * We are running a method at a time to make sure we are covering this. * e.g. when we are trying to eagerConnect evm and solana on phantom at the same time, the last namespace throw an error. */ - return await sequentiallyRun(finalResult); + const connectNamespacesResult = await runSequentiallyWithoutFailure( + connectNamespacesPromises + ); + + const failedNamespaces: LegacyNamespaceInputForConnect[] = []; + connectNamespacesResult.forEach((result, index) => { + if (result instanceof Error) { + failedNamespaces.push(targetNamespaces[index][0]); + } + }); + + if (failedNamespaces.length > 0) { + lastConnectedWalletsFromStorage.removeNamespacesFromWallet( + type, + failedNamespaces.map((namespace) => namespace.namespace) + ); + } + + const atLeastOneNamespaceConnectedSuccessfully = + connectNamespacesResult.length - failedNamespaces.length > 0; + if (!atLeastOneNamespaceConnectedSuccessfully) { + throw new Error(`No namespace connected for ${type}`); + } + + const successfulResult = connectNamespacesResult.filter( + (result) => !(result instanceof Error) + ); + return successfulResult; } /* @@ -92,11 +136,6 @@ export async function autoConnect(deps: { wallets?: (WalletType | LegacyProviderInterface)[]; }): Promise { const { getHub, allBlockChains, getLegacyProvider, wallets } = deps; - - // Getting connected wallets from storage - const lastConnectedWalletsFromStorage = new LastConnectedWalletsFromStorage( - HUB_LAST_CONNECTED_WALLETS - ); const lastConnectedWallets = lastConnectedWalletsFromStorage.list(); const walletIds = Object.keys(lastConnectedWallets); @@ -123,7 +162,8 @@ export async function autoConnect(deps: { legacyInstance = legacyProvider.getInstance(); } catch (e) { console.warn( - "It seems instance isn't available yet. This can happens when extension not loaded yet (sometimes when opening browser for first time) or extension is disabled." + "It seems instance isn't available yet for auto connect. This can happen when extension not loaded yet (sometimes when opening browser for first time) or extension is disabled. Desired wallet:", + providerName ); return; } @@ -161,26 +201,15 @@ export async function autoConnect(deps: { providerName, }).catch((e) => { walletsToRemoveFromPersistance.push(providerName); - throw e; + console.warn(e); }) ); }); - await Promise.allSettled(eagerConnectQueue); - - /* - *After successfully connecting to at least one wallet, - *we will removing the other wallets from persistence. - *If we are unable to connect to any wallet, - *the persistence will not be removed and the eager connection will be retried with another page load. - */ - const canRestoreAnyConnection = - walletIds.length > walletsToRemoveFromPersistance.length; + await Promise.all(eagerConnectQueue); - if (canRestoreAnyConnection) { - lastConnectedWalletsFromStorage.removeWallets( - walletsToRemoveFromPersistance - ); - } + lastConnectedWalletsFromStorage.removeWallets( + walletsToRemoveFromPersistance + ); } } diff --git a/wallets/react/src/hub/helpers.ts b/wallets/react/src/hub/helpers.ts index 2a9cfa6c7..5a8310ed3 100644 --- a/wallets/react/src/hub/helpers.ts +++ b/wallets/react/src/hub/helpers.ts @@ -42,14 +42,20 @@ export function fromAccountIdToLegacyAddressFormat(account: string): string { /** * Getting a list of (lazy) promises and run them one after another. */ -export async function sequentiallyRun Promise>( +export async function runSequentiallyWithoutFailure< + T extends () => Promise +>( promises: Array ): Promise Promise ? R : never>> { const result = await promises.reduce(async (prev, task) => { const previousResults = await prev; - const taskResult = await task(); + try { + const taskResult = await task(); - return [...previousResults, taskResult]; + return [...previousResults, taskResult]; + } catch (error) { + return [...previousResults, error]; + } }, Promise.resolve([]) as Promise); return result; } diff --git a/wallets/react/src/legacy/autoConnect.ts b/wallets/react/src/legacy/autoConnect.ts index 2d04d78a3..f33991135 100644 --- a/wallets/react/src/legacy/autoConnect.ts +++ b/wallets/react/src/legacy/autoConnect.ts @@ -46,33 +46,21 @@ export async function autoConnect( eagerConnectQueue.map(async ({ eagerConnect }) => eagerConnect()) ); - const canRestoreAnyConnection = !!result.find( - ({ status }) => status === 'fulfilled' - ); - - /* - *After successfully connecting to at least one wallet, - *we will removing the other wallets from persistence. - *If we are unable to connect to any wallet, - *the persistence will not be removed and the eager connection will be retried with another page load. - */ - if (canRestoreAnyConnection) { - const walletsToRemoveFromPersistance: WalletType[] = []; - result.forEach((settleResult, index) => { - const { status } = settleResult; - - if (status === 'rejected') { - walletsToRemoveFromPersistance.push( - eagerConnectQueue[index].walletType - ); - } - }); + const walletsToRemoveFromPersistance: WalletType[] = []; + result.forEach((settleResult, index) => { + const { status } = settleResult; - if (walletsToRemoveFromPersistance.length) { - lastConnectedWalletsFromStorage.removeWallets( - walletsToRemoveFromPersistance + if (status === 'rejected') { + walletsToRemoveFromPersistance.push( + eagerConnectQueue[index].walletType ); } + }); + + if (walletsToRemoveFromPersistance.length) { + lastConnectedWalletsFromStorage.removeWallets( + walletsToRemoveFromPersistance + ); } } }