diff --git a/packages/extension-polkagate/src/util/misc.ts b/packages/extension-polkagate/src/util/misc.ts index 3060fbaef..ed47b3acd 100644 --- a/packages/extension-polkagate/src/util/misc.ts +++ b/packages/extension-polkagate/src/util/misc.ts @@ -167,21 +167,8 @@ export function extractBaseUrl (url: string | undefined) { export async function fastestConnection (endpoints: DropdownOption[]): Promise { try { const urls = endpoints.map(({ value }) => ({ value: value as string })); - const { api, connections } = await fastestEndpoint(urls); - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const selectedEndpoint = api.registry.knownTypes.provider.endpoint as string; - const connectionsToDisconnect = connections.filter(({ wsProvider }) => wsProvider.endpoint !== selectedEndpoint); - - connectionsToDisconnect.forEach(({ wsProvider }) => { - wsProvider.disconnect().catch(console.error); - }); - - return { - api, - selectedEndpoint - }; + return await fastestEndpoint(urls); } catch (error) { console.error('Unable to make an API connection!', error); @@ -219,4 +206,4 @@ export const removeZeroBalanceRecords = (toBeSavedAssets: SavedAssets): SavedAss }); return _toBeSavedAssets; -}; \ No newline at end of file +}; diff --git a/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnAssetHub.js b/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnAssetHub.js index 6c39b91d6..3c116324b 100644 --- a/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnAssetHub.js +++ b/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnAssetHub.js @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { FETCHING_ASSETS_FUNCTION_NAMES } from '../../constants'; -import { closeWebsockets, fastestEndpoint, getChainEndpoints, metadataFromApi, toGetNativeToken } from '../utils'; +import { fastestEndpoint, getChainEndpoints, metadataFromApi, toGetNativeToken } from '../utils'; import { getAssets } from './getAssets.js'; /** @@ -15,7 +15,7 @@ import { getAssets } from './getAssets.js'; */ export async function getAssetOnAssetHub (addresses, assetsToBeFetched, chainName, userAddedEndpoints, port) { const endpoints = getChainEndpoints(chainName, userAddedEndpoints); - const { api, connections } = await fastestEndpoint(endpoints); + const { api } = await fastestEndpoint(endpoints); const { metadata } = metadataFromApi(api); @@ -42,5 +42,6 @@ export async function getAssetOnAssetHub (addresses, assetsToBeFetched, chainNam console.info(chainName, ': account assets fetched.'); port.postMessage(JSON.stringify({ functionName: FETCHING_ASSETS_FUNCTION_NAMES.ASSET_HUB, results })); - closeWebsockets(connections); + + api.disconnect().catch(console.error); } diff --git a/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnMultiAssetChain.js b/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnMultiAssetChain.js index 685a0a151..ec31780d8 100644 --- a/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnMultiAssetChain.js +++ b/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnMultiAssetChain.js @@ -8,7 +8,7 @@ import { getSubstrateAddress } from '../../address'; import { FETCHING_ASSETS_FUNCTION_NAMES } from '../../constants'; import { toTitleCase } from '../../string'; // eslint-disable-next-line import/extensions -import { balancifyAsset, closeWebsockets, fastestEndpoint, getChainEndpoints, metadataFromApi, toGetNativeToken } from '../utils'; +import { balancifyAsset, fastestEndpoint, getChainEndpoints, metadataFromApi, toGetNativeToken } from '../utils'; /** * @@ -20,7 +20,7 @@ import { balancifyAsset, closeWebsockets, fastestEndpoint, getChainEndpoints, me */ export async function getAssetOnMultiAssetChain (assetsToBeFetched, addresses, chainName, userAddedEndpoints, port) { const endpoints = getChainEndpoints(chainName, userAddedEndpoints); - const { api, connections } = await fastestEndpoint(endpoints); + const { api } = await fastestEndpoint(endpoints); const { metadata } = metadataFromApi(api); @@ -37,7 +37,6 @@ export async function getAssetOnMultiAssetChain (assetsToBeFetched, addresses, c return; } - // @ts-ignore const [formatted, assetIdRaw] = entry[0].toHuman() ?? []; let assetId; @@ -51,7 +50,6 @@ export async function getAssetOnMultiAssetChain (assetsToBeFetched, addresses, c const storageKey = entry[0].toString(); - // @ts-ignore let maybeAssetInfo = assetsToBeFetched.find((_asset) => { const currencyId = _asset?.extras?.['currencyIdScale'].replace('0x', ''); @@ -79,7 +77,6 @@ export async function getAssetOnMultiAssetChain (assetsToBeFetched, addresses, c } if (maybeAssetInfo) { - // @ts-ignore const totalBalance = balance.free.add(balance.reserved); const asset = { @@ -97,7 +94,6 @@ export async function getAssetOnMultiAssetChain (assetsToBeFetched, addresses, c const address = getSubstrateAddress(formatted); - // @ts-ignore results[address]?.push(asset) ?? (results[address] = [asset]); } else { console.info(`NOTE: There is an asset on ${chainName} for ${formatted} which is not whitelisted. assetInfo`, storageKey, balance?.toHuman()); @@ -106,5 +102,6 @@ export async function getAssetOnMultiAssetChain (assetsToBeFetched, addresses, c console.info(chainName, ': account assets fetched.'); port.postMessage(JSON.stringify({ functionName: FETCHING_ASSETS_FUNCTION_NAMES.MULTI_ASSET, results })); - closeWebsockets(connections); + + api.disconnect().catch(console.error); } diff --git a/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnRelayChain.js b/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnRelayChain.js index 2b70e8627..684e57a47 100644 --- a/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnRelayChain.js +++ b/packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnRelayChain.js @@ -3,7 +3,7 @@ import { FETCHING_ASSETS_FUNCTION_NAMES, NATIVE_TOKEN_ASSET_ID, TEST_NETS } from '../../constants'; import { getPriceIdByChainName } from '../../misc'; -import { balancify, closeWebsockets } from '../utils'; +import { balancify } from '../utils'; import { getBalances } from './getBalances.js'; /** @@ -16,9 +16,9 @@ export async function getAssetOnRelayChain (addresses, chainName, userAddedEndpo const results = {}; try { - const { api, balanceInfo, connectionsToBeClosed } = await getBalances(chainName, addresses, userAddedEndpoints, port) ?? {}; + const { api, balanceInfo } = await getBalances(chainName, addresses, userAddedEndpoints, port) ?? {}; - if (!api || !balanceInfo || !connectionsToBeClosed) { + if (!api || !balanceInfo) { return; } @@ -46,7 +46,7 @@ export async function getAssetOnRelayChain (addresses, chainName, userAddedEndpo }]; }); - closeWebsockets(connectionsToBeClosed); + api.disconnect().catch(console.error); } catch (error) { console.error(`getAssetOnRelayChain: Error fetching balances for ${chainName}:`, error); } finally { diff --git a/packages/extension-polkagate/src/util/workers/shared-helpers/getBalances.js b/packages/extension-polkagate/src/util/workers/shared-helpers/getBalances.js index 04226fc3f..74d4a0fef 100644 --- a/packages/extension-polkagate/src/util/workers/shared-helpers/getBalances.js +++ b/packages/extension-polkagate/src/util/workers/shared-helpers/getBalances.js @@ -17,7 +17,7 @@ import { getStakingBalances } from './getStakingBalances'; */ export async function getBalances (chainName, addresses, userAddedEndpoints, port) { const chainEndpoints = getChainEndpoints(chainName, userAddedEndpoints); - const { api, connections } = await fastestEndpoint(chainEndpoints); + const { api } = await fastestEndpoint(chainEndpoints); if (api.isConnected && api.derive.balances) { const { metadata } = metadataFromApi(api); @@ -49,7 +49,7 @@ export async function getBalances (chainName, addresses, userAddedEndpoints, por }; }); - return { api, balanceInfo: await Promise.all(requests), connectionsToBeClosed: connections }; + return { api, balanceInfo: await Promise.all(requests) }; } return undefined; diff --git a/packages/extension-polkagate/src/util/workers/shared-helpers/getNFTs.js b/packages/extension-polkagate/src/util/workers/shared-helpers/getNFTs.js index 24c470866..5bed02361 100644 --- a/packages/extension-polkagate/src/util/workers/shared-helpers/getNFTs.js +++ b/packages/extension-polkagate/src/util/workers/shared-helpers/getNFTs.js @@ -5,7 +5,7 @@ import { SUPPORTED_NFT_CHAINS } from '../../../fullscreen/nft/utils/constants'; import { getFormattedAddress } from '../../address'; -import { closeWebsockets, fastestEndpoint, getChainEndpoints } from '../utils'; +import { fastestEndpoint, getChainEndpoints } from '../utils'; const NFT_FUNCTION_NAME = 'getNFTs'; @@ -191,9 +191,9 @@ async function getNFTs (addresses) { const formattedAddresses = addresses.map((address) => getFormattedAddress(address, undefined, prefix)); const endpoints = getChainEndpoints(name, undefined); - const { api, connections } = await fastestEndpoint(endpoints); + const { api } = await fastestEndpoint(endpoints); - return ({ api, chainName, connections, formattedAddresses, originalAddresses: addresses }); + return ({ api, chainName, formattedAddresses, originalAddresses: addresses }); }); const apis = await Promise.all(apiPromises); @@ -224,7 +224,9 @@ async function getNFTs (addresses) { return itemsByAddress; } finally { // Ensure all websocket connections are closed - apis.forEach(({ connections }) => closeWebsockets(connections)); + apis.forEach(({ api }) => { + api.disconnect().catch(console.error); + }); } } diff --git a/packages/extension-polkagate/src/util/workers/shared-helpers/getPool.js b/packages/extension-polkagate/src/util/workers/shared-helpers/getPool.js index 526c9fc6d..89bbcd50b 100644 --- a/packages/extension-polkagate/src/util/workers/shared-helpers/getPool.js +++ b/packages/extension-polkagate/src/util/workers/shared-helpers/getPool.js @@ -5,7 +5,7 @@ import { BN, BN_ZERO, bnMax, hexToString } from '@polkadot/util'; import getChainName from '../../getChainName'; import getPoolAccounts from '../../getPoolAccounts'; -import { closeWebsockets, fastestEndpoint, getChainEndpoints } from '../utils'; +import { fastestEndpoint, getChainEndpoints } from '../utils'; /** * Get all information regarding a pool @@ -31,7 +31,7 @@ export async function getPool (genesisHash, stakerAddress, id, port) { const chainName = getChainName(genesisHash); const endpoints = getChainEndpoints(chainName ?? ''); - const { api, connections } = await fastestEndpoint(endpoints); + const { api } = await fastestEndpoint(endpoints); console.log(`getPool is called for ${stakerAddress} on chain ${chainName}`); id && console.log('getPool is called to fetch the pool with poolId:', id); @@ -133,5 +133,5 @@ export async function getPool (genesisHash, stakerAddress, id, port) { port.postMessage(JSON.stringify({ functionName: 'getPool', results: JSON.stringify(poolInfo) })); - closeWebsockets(connections); + api.disconnect().catch(console.error); } diff --git a/packages/extension-polkagate/src/util/workers/shared-helpers/getValidatorsInformation.js b/packages/extension-polkagate/src/util/workers/shared-helpers/getValidatorsInformation.js index ebaa4abe0..a777bebaf 100644 --- a/packages/extension-polkagate/src/util/workers/shared-helpers/getValidatorsInformation.js +++ b/packages/extension-polkagate/src/util/workers/shared-helpers/getValidatorsInformation.js @@ -6,7 +6,7 @@ import { hexToString } from '@polkadot/util'; import { KUSAMA_GENESIS_HASH, POLKADOT_GENESIS_HASH } from '../../constants'; import getChainName from '../../getChainName'; -import { closeWebsockets, fastestEndpoint, getChainEndpoints } from '../utils'; +import { fastestEndpoint, getChainEndpoints } from '../utils'; const BATCH_SIZE = 50; @@ -72,7 +72,7 @@ export default async function getValidatorsInformation (genesisHash, port) { const endpoints = getChainEndpoints(chainName); try { - const { api, connections } = await fastestEndpoint(endpoints); + const { api } = await fastestEndpoint(endpoints); console.log('getting validators information on ' + chainName); @@ -84,14 +84,14 @@ export default async function getValidatorsInformation (genesisHash, port) { console.log('electedInfo, waitingInfo, currentEra fetched successfully'); - // Close the initial connections to the relay chain - closeWebsockets(connections); + // Close the initial connection to the relay chain + api.disconnect().catch(console.error); // Start connect to the People chain endpoints in order to fetch identities console.log('Connecting to People chain endpoints...'); const peopleChainName = getPeopleChainName(genesisHash); const peopleEndpoints = getChainEndpoints(peopleChainName); - const { api: peopleApi, connections: peopleConnections } = await fastestEndpoint(peopleEndpoints); + const { api: peopleApi } = await fastestEndpoint(peopleEndpoints); // Keep elected and waiting validators separate const electedValidatorsInfo = electedInfo.info; @@ -136,7 +136,7 @@ export default async function getValidatorsInformation (genesisHash, port) { await processSubIdentities(peopleApi, waitingMayHaveSubId, waitingValidatorsInformation, waitingAccountSubInfo); await processParentIdentities(peopleApi, waitingAccountSubInfo, waitingValidatorsInformation); - closeWebsockets(peopleConnections); + peopleApi.disconnect().catch(console.error); const results = { eraIndex: Number(currentEra?.toString() || '0'), diff --git a/packages/extension-polkagate/src/util/workers/utils/closeWebsockets.js b/packages/extension-polkagate/src/util/workers/utils/closeWebsockets.js deleted file mode 100644 index ff9b6421c..000000000 --- a/packages/extension-polkagate/src/util/workers/utils/closeWebsockets.js +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2019-2025 @polkadot/extension-polkagate authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -/** - * @typedef {Object} ConnectionInfo - * @property {Promise} connection - The connection promise - * @property {string} connectionEndpoint - The endpoint URL - * @property {import('@polkadot/api').WsProvider} wsProvider - The WebSocket provider - */ - -/** - * Closes the open connections - * @param {ConnectionInfo[]} connections - */ -export function closeWebsockets (connections) { - Promise.allSettled( - connections.map(({ wsProvider }) => wsProvider.disconnect()) - ).catch(console.error); -} diff --git a/packages/extension-polkagate/src/util/workers/utils/fastestApi.js b/packages/extension-polkagate/src/util/workers/utils/fastestApi.js deleted file mode 100644 index dd2c2b386..000000000 --- a/packages/extension-polkagate/src/util/workers/utils/fastestApi.js +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2019-2025 @polkadot/extension-polkagate authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import getChainName from '../../getChainName'; -import { fastestEndpoint } from './fastestEndpoint'; -import { getChainEndpoints } from './getChainEndpoints'; - -/** - * @param {string | undefined} genesisHash - */ -export async function fastestApi (genesisHash) { - const chainName = getChainName(genesisHash); - const endpoints = getChainEndpoints(chainName ?? ''); - - const { api, connections } = await fastestEndpoint(endpoints); - - return { - api, - connections - }; -} diff --git a/packages/extension-polkagate/src/util/workers/utils/fastestEndpoint.js b/packages/extension-polkagate/src/util/workers/utils/fastestEndpoint.js index 36f4d7a1f..78706a916 100644 --- a/packages/extension-polkagate/src/util/workers/utils/fastestEndpoint.js +++ b/packages/extension-polkagate/src/util/workers/utils/fastestEndpoint.js @@ -4,43 +4,57 @@ import { ApiPromise, WsProvider } from '@polkadot/api'; /** - * @param {{ value: string; }[]} endpoints + * Connects to multiple endpoints and returns the fastest one. + * Automatically disconnects all other providers. + * + * @param {{ value: string }[]} endpoints */ export async function fastestEndpoint (endpoints) { - let connection; - - const connections = endpoints.map(({ value }) => { - // Check if e.value matches the pattern 'wss://' - // ignore due to its rate limits - if (/^wss:\/\/\d+$/.test(value) || (value).includes('onfinality') || value.startsWith('light')) { - return undefined; - } - - const wsProvider = new WsProvider(value); - - connection = ApiPromise.create({ provider: wsProvider }); - - return { - connection, - connectionEndpoint: value, - wsProvider - }; - }).filter((i) => !!i); - - const api = await Promise.any(connections.map(({ connection }) => connection)); - - // Find the matching connection that created this API - // @ts-ignore - const notConnectedEndpoint = connections.filter(({ connectionEndpoint }) => connectionEndpoint !== api?._options?.provider?.endpoint); - // @ts-ignore - const connectedEndpoint = connections.find(({ connectionEndpoint }) => connectionEndpoint === api?._options?.provider?.endpoint); - - notConnectedEndpoint.forEach(({ wsProvider }) => { - wsProvider.disconnect().catch(() => null); - }); - - return { - api, - connections: connectedEndpoint ? [connectedEndpoint] : [] - }; + // Filter invalid endpoints + const validEndpoints = endpoints + .map(({ value }) => value) + .filter( + (value) => + !/^wss:\/\/\d+$/.test(value) && + !value.includes('onfinality') && + !value.startsWith('light') + ); + + if (!validEndpoints.length) { + throw new Error('No valid endpoints provided'); + } + + // Create all providers + const providers = validEndpoints.map((value) => new WsProvider(value)); + + // Wrap each ApiPromise creation in an object, so we keep the link between api and provider + const apiPromises = providers.map((provider) => + ApiPromise.create({ provider }) + .then((api) => ({ api, provider })) // attach provider reference + .catch((error) => { + provider.disconnect().catch(() => null); + throw error; + }) + ); + + // Get the fastest connection + let api, provider; + + try { + ({ api, provider } = await Promise.any(apiPromises)); + } catch (e) { + // e is AggregateError if all fail + throw new Error('All endpoints failed to connect', { cause: e }); + } + + // Disconnect all other providers (non-blocking) + Promise.all( + providers.map((p) => + p === provider + ? Promise.resolve() + : p.disconnect().catch(() => null) + ) + ).catch(() => null); + + return { api, selectedEndpoint: provider.endpoint }; } diff --git a/packages/extension-polkagate/src/util/workers/utils/index.ts b/packages/extension-polkagate/src/util/workers/utils/index.ts index 12244c24f..00e254820 100644 --- a/packages/extension-polkagate/src/util/workers/utils/index.ts +++ b/packages/extension-polkagate/src/util/workers/utils/index.ts @@ -2,8 +2,6 @@ // SPDX-License-Identifier: Apache-2.0 export * from './balancify.js'; -export * from './closeWebsockets.js'; -export * from './fastestApi.js'; export * from './fastestEndpoint.js'; export * from './getChainEndpoints.js'; export * from './getChainEndpointsFromGenesisHash.js';