From e43580a009f595aa9c0038c0e8b00c85de269b07 Mon Sep 17 00:00:00 2001 From: MattClabs <139958714+MattClabs@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:01:47 +0200 Subject: [PATCH 01/10] Update messages.json Updating the AO process outage copy --- assets/_locales/en/messages.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json index a3309f0d..de800224 100644 --- a/assets/_locales/en/messages.json +++ b/assets/_locales/en/messages.json @@ -1564,7 +1564,7 @@ "description": "Not connected indicator" }, "not_connected_text": { - "message": "\"Connect / Disconnect\" is an indicator for when your ArConnect wallet is connected to an Arweave or AO application. For regular use like sending, receiving, checking balances, etc., ArConnect will always show disconnected.", + "message": "\"Connect / Disconnect\" is a label for when your ArConnect wallet is connected to an Arweave or AO dapp. For regular use like sending, receiving, checking balances, etc., ArConnect will always show disconnected.", "description": "Not connected indicator explainer" }, "disconnect": { @@ -2009,11 +2009,11 @@ "description": "Popup description about ao token transfer learn more" }, "ao_degraded": { - "message": "Unable to connect to AO Token Process", + "message": "Token balance error", "description": "ao degraded title text" }, "ao_degraded_description": { - "message": "AO balance will be available when
network issues are resolved.", + "message": "Ops, we are unable to fetch your token balance. This is just an UI issue. Please try again later.", "description": "ao degraded description text" }, "network_issue": { From 71f15acedbc99279c0033f5320c2c21f29778467 Mon Sep 17 00:00:00 2001 From: MattClabs <139958714+MattClabs@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:04:31 +0200 Subject: [PATCH 02/10] Update messages.json Updating the Chinese copy --- assets/_locales/zh_CN/messages.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/_locales/zh_CN/messages.json b/assets/_locales/zh_CN/messages.json index 199c4db1..0bfc4df4 100644 --- a/assets/_locales/zh_CN/messages.json +++ b/assets/_locales/zh_CN/messages.json @@ -1999,11 +1999,11 @@ "description": "Popup description about ao token transfer learn more" }, "ao_degraded": { - "message": "无法连接到 AO 令牌进程", + "message": "代币余额错误", "description": "ao degraded title text" }, "ao_degraded_description": { - "message": "网络问题解决后,AO 余额将可用。", + "message": "ArConnect 无法获取您的代币余额。这只是一个用户界面问题。请稍后重试。", "description": "ao degraded description text" }, "network_issue": { From bb3d7c3c99a42f217677a84dd99ed38d3391cc39 Mon Sep 17 00:00:00 2001 From: MattClabs <139958714+MattClabs@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:05:41 +0200 Subject: [PATCH 03/10] Updating AO Process outage copy - English.json --- assets/_locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json index de800224..2b3c1106 100644 --- a/assets/_locales/en/messages.json +++ b/assets/_locales/en/messages.json @@ -2013,7 +2013,7 @@ "description": "ao degraded title text" }, "ao_degraded_description": { - "message": "Ops, we are unable to fetch your token balance. This is just an UI issue. Please try again later.", + "message": "Ops, ArConnect is unable to fetch your token balance. This is just an UI issue. Please try again later.", "description": "ao degraded description text" }, "network_issue": { From 34d3854a85cf05a8212311e76920f1aa08a4a4e6 Mon Sep 17 00:00:00 2001 From: MattClabs <139958714+MattClabs@users.noreply.github.com> Date: Thu, 24 Oct 2024 17:19:07 +0200 Subject: [PATCH 04/10] Updating AO Token process outage copy - english.json --- assets/_locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json index 2b3c1106..6e667d67 100644 --- a/assets/_locales/en/messages.json +++ b/assets/_locales/en/messages.json @@ -2013,7 +2013,7 @@ "description": "ao degraded title text" }, "ao_degraded_description": { - "message": "Ops, ArConnect is unable to fetch your token balance. This is just an UI issue. Please try again later.", + "message": "Ops, ArConnect is unable to fetch your token balance. Please try again later.", "description": "ao degraded description text" }, "network_issue": { From 801b713ee3f3ae12e5521416345ac14b97fbe19e Mon Sep 17 00:00:00 2001 From: 7i7o Date: Thu, 24 Oct 2024 17:30:28 +0200 Subject: [PATCH 05/10] Use Narrow tootlip for messages --- src/components/popup/Token.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index f23afbe2..af3ca72b 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -166,13 +166,13 @@ export default function Token({ onClick, ...props }: Props) { {props?.loading ? ( ) : props?.error ? ( - + - + ) : props?.networkError ? ( - + - + ) : ( <> {showTooltip ? ( @@ -373,6 +373,10 @@ const BalanceTooltip = styled(TooltipV2)` margin-right: 1rem; `; +const MessageTooltip = styled(TooltipV2)` + max-width: 290px; +`; + const Image = styled.img` width: 16px; padding: 0 8px; From 30dc2ee4ffa42e56f593f177ed2f1b3fbc910415 Mon Sep 17 00:00:00 2001 From: MattClabs <139958714+MattClabs@users.noreply.github.com> Date: Thu, 24 Oct 2024 18:07:33 +0200 Subject: [PATCH 06/10] Oops update.json --- assets/_locales/en/messages.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/_locales/en/messages.json b/assets/_locales/en/messages.json index 6e667d67..608509bc 100644 --- a/assets/_locales/en/messages.json +++ b/assets/_locales/en/messages.json @@ -2013,7 +2013,7 @@ "description": "ao degraded title text" }, "ao_degraded_description": { - "message": "Ops, ArConnect is unable to fetch your token balance. Please try again later.", + "message": "Oops, ArConnect is unable to fetch your token balance. Please try again later.", "description": "ao degraded description text" }, "network_issue": { From a0a231334490fd945fa535e5c9c120aaebc13178 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Tue, 29 Oct 2024 14:10:45 +0545 Subject: [PATCH 07/10] refactor: add timeout retry to ar balance fetch --- src/components/popup/Token.tsx | 25 ++++-------------- src/components/popup/home/Balance.tsx | 38 ++++++++++++++++----------- src/utils/retry.ts | 38 +++++++++++++++++++++++++++ src/wallets/hooks.ts | 37 +++++++++++++++++--------- 4 files changed, 91 insertions(+), 47 deletions(-) diff --git a/src/components/popup/Token.tsx b/src/components/popup/Token.tsx index af3ca72b..fd0d6a73 100644 --- a/src/components/popup/Token.tsx +++ b/src/components/popup/Token.tsx @@ -1,8 +1,4 @@ -import { - formatFiatBalance, - formatTokenBalance, - balanceToFractioned -} from "~tokens/currency"; +import { formatFiatBalance, balanceToFractioned } from "~tokens/currency"; import { type MouseEventHandler, useEffect, @@ -23,8 +19,6 @@ import * as viewblock from "~lib/viewblock"; import Squircle from "~components/Squircle"; import useSetting from "~settings/hook"; import styled from "styled-components"; -import Arweave from "arweave"; -import { useGateway } from "~gateways/wayfinder"; import aoLogo from "url:/assets/ecosystem/ao-logo.svg"; import { getUserAvatar } from "~lib/avatar"; import { formatBalance } from "~utils/format"; @@ -34,6 +28,7 @@ import BigNumber from "bignumber.js"; import JSConfetti from "js-confetti"; import browser from "webextension-polyfill"; import { AO_NATIVE_TOKEN } from "~utils/ao_import"; +import { useBalance } from "~wallets/hooks"; export default function Token({ onClick, ...props }: Props) { const ref = useRef(null); @@ -490,33 +485,23 @@ export function ArToken({ onClick }: ArTokenProps) { }); // load ar balance - const [balance, setBalance] = useState(BigNumber("0")); const [fiatBalance, setFiatBalance] = useState(BigNumber("0")); const [displayBalance, setDisplayBalance] = useState("0"); const [totalBalance, setTotalBalance] = useState(""); const [showTooltip, setShowTooltip] = useState(false); - // memoized requirements to ensure stability - const requirements = useMemo(() => ({ ensureStake: true }), []); - const gateway = useGateway(requirements); + const balance = useBalance(); useEffect(() => { (async () => { if (!activeAddress) return; - const arweave = new Arweave(gateway); - - // fetch balance - const winstonBalance = await arweave.wallets.getBalance(activeAddress); - const arBalance = BigNumber(arweave.ar.winstonToAr(winstonBalance)); - setBalance(arBalance); - - const formattedBalance = formatBalance(arBalance); + const formattedBalance = formatBalance(balance); setTotalBalance(formattedBalance.tooltipBalance); setShowTooltip(formattedBalance.showTooltip); setDisplayBalance(formattedBalance.displayBalance); })(); - }, [activeAddress, gateway]); + }, [activeAddress, balance]); useEffect(() => { setFiatBalance(balance.multipliedBy(price)); diff --git a/src/components/popup/home/Balance.tsx b/src/components/popup/home/Balance.tsx index e8194427..714d7f44 100644 --- a/src/components/popup/home/Balance.tsx +++ b/src/components/popup/home/Balance.tsx @@ -6,7 +6,6 @@ import { Loading, TooltipV2 } from "@arconnect/components"; import { useEffect, useMemo, useState, type HTMLProps } from "react"; import { useStorage } from "@plasmohq/storage/hook"; import { ExtensionStorage } from "~utils/storage"; -import { useHistory } from "~utils/hash_router"; import { useBalance } from "~wallets/hooks"; import { getArPrice } from "~lib/coingecko"; import { getAppURL } from "~utils/format"; @@ -25,8 +24,8 @@ import styled from "styled-components"; import Arweave from "arweave"; import { removeDecryptionKey } from "~wallets/auth"; import { findGateway } from "~gateways/wayfinder"; -import type { Gateway } from "~gateways/gateway"; import BigNumber from "bignumber.js"; +import { retryWithDelay, retryWithDelayAndTimeout } from "~utils/retry"; export default function Balance() { const [loading, setLoading] = useState(false); @@ -103,8 +102,7 @@ export default function Balance() { (async () => { if (!activeAddress) return; setLoading(true); - const gateway = await findGateway({ graphql: true }); - const history = await balanceHistory(activeAddress, gateway); + const history = await balanceHistory(activeAddress); setHistoricalBalance(history); setLoading(false); @@ -192,8 +190,9 @@ export default function Balance() { ); } -async function balanceHistory(address: string, gateway: Gateway) { - const arweave = new Arweave(gateway); +async function balanceHistory(address: string) { + const gateway = await findGateway({ graphql: true }); + let arweave = new Arweave(gateway); let minHeight = 0; try { const { height } = await arweave.network.getInfo(); @@ -203,8 +202,9 @@ async function balanceHistory(address: string, gateway: Gateway) { // find txs coming in and going out const inTxs = ( - await gql( - ` + await retryWithDelay(() => + gql( + ` query($recipient: String!, $minHeight: Int!) { transactions(recipients: [$recipient], first: 100, bundledIn: null, block: {min: $minHeight}) { edges { @@ -226,13 +226,15 @@ async function balanceHistory(address: string, gateway: Gateway) { } } `, - { recipient: address, minHeight } + { recipient: address, minHeight } + ) ) ).data.transactions.edges; const outTxs = ( - await gql( - ` + await retryWithDelay(() => + gql( + ` query($owner: String!, $minHeight: Int!) { transactions(owners: [$owner], first: 100, bundledIn: null, block: {min: $minHeight}) { edges { @@ -254,7 +256,8 @@ async function balanceHistory(address: string, gateway: Gateway) { } } `, - { owner: address, minHeight } + { owner: address, minHeight } + ) ) ).data.transactions.edges; @@ -266,9 +269,14 @@ async function balanceHistory(address: string, gateway: Gateway) { .sort((a, b) => b.block.timestamp - a.block.timestamp); // Sort by newest to oldest // Get the current balance - let balance = BigNumber( - arweave.ar.winstonToAr(await arweave.wallets.getBalance(address)) - ); + const winstonBalance = await retryWithDelayAndTimeout(async () => { + const gateway = await findGateway({ ensureStake: true }); + arweave = new Arweave(gateway); + const balance = await arweave.wallets.getBalance(address); + if (isNaN(+balance)) throw new Error("Balance is invalid"); + return balance; + }); + let balance = BigNumber(arweave.ar.winstonToAr(winstonBalance)); // Initialize the result array with the current balance const res = [balance.toNumber()]; diff --git a/src/utils/retry.ts b/src/utils/retry.ts index 4c7e48b0..09f190f4 100644 --- a/src/utils/retry.ts +++ b/src/utils/retry.ts @@ -30,3 +30,41 @@ export async function retryWithDelay( return attempt(); } + +/** + * Retries a given asynchronous function up to a maximum number of attempts with a timeout for each attempt. + * @param fn - The asynchronous function to retry, which should return a Promise. + * @param maxAttempts - The maximum number of attempts to make. + * @param delay - The delay between attempts in milliseconds. + * @param timeout - The maximum time to wait for each attempt in milliseconds. + * @return A Promise that resolves with the result of the function or rejects after all attempts fail. + */ +export async function retryWithDelayAndTimeout( + fn: () => Promise, + maxAttempts: number = 3, + delay: number = 1000, + timeout: number = 10000 +): Promise { + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + try { + // Create a race between the function and the timeout + const result = await Promise.race([ + fn(), + new Promise((_, reject) => + setTimeout(() => reject(new Error("Request timed out")), timeout) + ) + ]); + return result; + } catch (error) { + if (attempt < maxAttempts) { + // console.log(`Attempt ${attempt} failed: ${error.message}. Retrying in ${delay}ms...`); + await new Promise((resolve) => setTimeout(resolve, delay)); + } else { + throw error; + } + } + } + + // Final fallback error + throw new Error("Max attempts reached without success."); +} diff --git a/src/wallets/hooks.ts b/src/wallets/hooks.ts index 6bafbcd5..08874d6b 100644 --- a/src/wallets/hooks.ts +++ b/src/wallets/hooks.ts @@ -1,7 +1,7 @@ import type { WalletInterface } from "~components/welcome/load/Migrate"; import type { JWKInterface } from "arweave/web/lib/wallet"; import { type AnsUser, getAnsProfile } from "~lib/ans"; -import { useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useStorage } from "@plasmohq/storage/hook"; import { defaultGateway } from "~gateways/gateway"; import { ExtensionStorage } from "~utils/storage"; @@ -10,6 +10,7 @@ import type { HardwareApi } from "./hardware"; import type { StoredWallet } from "~wallets"; import Arweave from "arweave"; import BigNumber from "bignumber.js"; +import { retryWithDelayAndTimeout } from "~utils/retry"; /** * Wallets with details hook @@ -113,19 +114,31 @@ export function useBalance() { // balance in AR const [balance, setBalance] = useState(BigNumber("0")); - useEffect(() => { - (async () => { - if (!activeAddress) return; - - const gateway = await findGateway({}); - const arweave = new Arweave(gateway); + const fetchBalance = useCallback(async () => { + if (!activeAddress) { + setBalance(BigNumber("0")); + return; + } + + const gateway = await findGateway({ ensureStake: true }); + const arweave = new Arweave(gateway); + + // fetch balance + const winstonBalance = await arweave.wallets.getBalance(activeAddress); + if (isNaN(+winstonBalance)) { + throw new Error("Invalid balance returned"); + } + const arBalance = BigNumber(arweave.ar.winstonToAr(winstonBalance)); + setBalance(arBalance); + }, [activeAddress]); - // fetch balance - const winstonBalance = await arweave.wallets.getBalance(activeAddress); + useEffect(() => { + if (!activeAddress) return; - setBalance(BigNumber(arweave.ar.winstonToAr(winstonBalance))); - })(); - }, [activeAddress]); + retryWithDelayAndTimeout(fetchBalance).catch((error) => { + console.log(`Error fetching balance: ${error}`); + }); + }, [activeAddress, fetchBalance]); return balance; } From 5a7e8de0e27947293dcc93fab65a026f1bda0760 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Tue, 29 Oct 2024 18:31:01 +0545 Subject: [PATCH 08/10] refactor: Show Print Arweave tx description & network fee in tx history --- src/components/popup/home/Transactions.tsx | 15 +++++++-------- src/lib/transactions.ts | 15 ++++++++++++++- src/notifications/api.ts | 3 +++ src/notifications/utils.ts | 1 + src/routes/popup/transaction/[id].tsx | 6 ++++++ 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/components/popup/home/Transactions.tsx b/src/components/popup/home/Transactions.tsx index 6d55b533..8e24e4d7 100644 --- a/src/components/popup/home/Transactions.tsx +++ b/src/components/popup/home/Transactions.tsx @@ -166,14 +166,13 @@ export default function Transactions() { : "Pending"} - {transaction.transactionType !== "printArchive" && ( -
-
{getFormattedAmount(transaction)}
- - {getFormattedFiatAmount(transaction, arPrice, currency)} - -
- )} + +
+
{getFormattedAmount(transaction)}
+ + {getFormattedFiatAmount(transaction, arPrice, currency)} + +
)) diff --git a/src/lib/transactions.ts b/src/lib/transactions.ts index 698a9ad6..7e747f05 100644 --- a/src/lib/transactions.ts +++ b/src/lib/transactions.ts @@ -143,6 +143,8 @@ export const getFormattedAmount = (transaction: ExtendedTransaction) => { }).toFixed()} ${transaction.aoInfo.tickerName}`; } return ""; + case "printArchive": + return `${parseFloat(transaction.node.fee.ar).toFixed(3)} AR`; default: return ""; } @@ -154,11 +156,22 @@ export const getFormattedFiatAmount = ( currency: string ) => { try { - if (transaction.node.quantity) { + if ( + transaction.node.quantity && + transaction.transactionType !== "printArchive" + ) { const fiatBalance = BigNumber(transaction.node.quantity.ar).multipliedBy( arPrice ); return formatFiatBalance(fiatBalance, currency); + } else if ( + transaction.node.fee && + transaction.transactionType === "printArchive" + ) { + const fiatBalance = BigNumber(transaction.node.fee.ar).multipliedBy( + arPrice + ); + return formatFiatBalance(fiatBalance, currency); } } catch {} return ""; diff --git a/src/notifications/api.ts b/src/notifications/api.ts index 5600b72c..81021229 100644 --- a/src/notifications/api.ts +++ b/src/notifications/api.ts @@ -26,6 +26,9 @@ export type RawTransaction = { quantity: { ar: string; }; + fee: { + ar: string; + }; block: { timestamp: number; height: number; diff --git a/src/notifications/utils.ts b/src/notifications/utils.ts index 0a09f4b4..f306a99d 100644 --- a/src/notifications/utils.ts +++ b/src/notifications/utils.ts @@ -258,6 +258,7 @@ query ($address: String!) { recipient owner { address } quantity { ar } + fee { ar } block { timestamp, height } tags { name diff --git a/src/routes/popup/transaction/[id].tsx b/src/routes/popup/transaction/[id].tsx index 9a1e8c25..f70c9c64 100644 --- a/src/routes/popup/transaction/[id].tsx +++ b/src/routes/popup/transaction/[id].tsx @@ -475,6 +475,12 @@ export default function Transaction({ id: rawId, gw, message }: Props) { + + + {browser.i18n.getMessage("transaction_fee")} + + {transaction.fee.ar} AR + {!message && ( From 9d4b87d3980bb839c8d49f4aa24c8e49c5f66435 Mon Sep 17 00:00:00 2001 From: Pawan Paudel Date: Tue, 29 Oct 2024 21:29:26 +0545 Subject: [PATCH 09/10] refactor: make sure we get random gateway --- src/components/popup/home/Balance.tsx | 2 +- src/utils/retry.ts | 2 +- src/wallets/hooks.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/popup/home/Balance.tsx b/src/components/popup/home/Balance.tsx index 714d7f44..b27543f9 100644 --- a/src/components/popup/home/Balance.tsx +++ b/src/components/popup/home/Balance.tsx @@ -270,7 +270,7 @@ async function balanceHistory(address: string) { // Get the current balance const winstonBalance = await retryWithDelayAndTimeout(async () => { - const gateway = await findGateway({ ensureStake: true }); + const gateway = await findGateway({}); arweave = new Arweave(gateway); const balance = await arweave.wallets.getBalance(address); if (isNaN(+balance)) throw new Error("Balance is invalid"); diff --git a/src/utils/retry.ts b/src/utils/retry.ts index 09f190f4..8d7e4a92 100644 --- a/src/utils/retry.ts +++ b/src/utils/retry.ts @@ -45,7 +45,7 @@ export async function retryWithDelayAndTimeout( delay: number = 1000, timeout: number = 10000 ): Promise { - for (let attempt = 1; attempt <= maxAttempts; attempt++) { + for (let attempt = 0; attempt <= maxAttempts; attempt++) { try { // Create a race between the function and the timeout const result = await Promise.race([ diff --git a/src/wallets/hooks.ts b/src/wallets/hooks.ts index 08874d6b..67ee3ade 100644 --- a/src/wallets/hooks.ts +++ b/src/wallets/hooks.ts @@ -120,7 +120,7 @@ export function useBalance() { return; } - const gateway = await findGateway({ ensureStake: true }); + const gateway = await findGateway({}); const arweave = new Arweave(gateway); // fetch balance From fb8dc898cd0c67de8731f3ab003c67a0602651ae Mon Sep 17 00:00:00 2001 From: nicholas ma Date: Wed, 30 Oct 2024 12:49:47 -0700 Subject: [PATCH 10/10] refactor: moved withRetry to retry.ts --- src/tokens/aoTokens/sync.ts | 39 +------------------------------------ src/utils/retry.ts | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 38 deletions(-) diff --git a/src/tokens/aoTokens/sync.ts b/src/tokens/aoTokens/sync.ts index 2607fe4e..11d0a5dc 100644 --- a/src/tokens/aoTokens/sync.ts +++ b/src/tokens/aoTokens/sync.ts @@ -17,6 +17,7 @@ import { Id, Owner } from "./ao"; +import { withRetry } from "~utils/retry"; /** Tokens storage name */ const AO_TOKENS = "ao_tokens"; @@ -36,44 +37,6 @@ const gateway = { protocol: "https" }; -/** - * Generic retry function for any async operation. - * @param fn - The async function to be retried. - * @param maxRetries - Maximum retry attempts. - * @param retryDelay - Delay between retries in milliseconds. - * @returns A promise of the type that the async function returns. - */ -async function withRetry( - fn: () => Promise, - maxRetries: number = 3, - retryDelay: number = 100 -): Promise { - let lastError: any; - - const delay = (ms: number) => - new Promise((resolve) => setTimeout(resolve, ms)); - - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - return await fn(); - } catch (error) { - lastError = error; - if (attempt < maxRetries) { - const waitTime = Math.pow(2, attempt - 1) * retryDelay; - console.log(`Attempt ${attempt} failed, retrying in ${waitTime}ms...`); - await delay(waitTime); - } else { - console.error( - `All ${maxRetries} attempts failed. Last error:`, - lastError - ); - } - } - } - - throw lastError; -} - async function getTokenInfo(id: string): Promise { const body = { Id, diff --git a/src/utils/retry.ts b/src/utils/retry.ts index 8d7e4a92..e487e40b 100644 --- a/src/utils/retry.ts +++ b/src/utils/retry.ts @@ -68,3 +68,41 @@ export async function retryWithDelayAndTimeout( // Final fallback error throw new Error("Max attempts reached without success."); } + +/** + * Generic retry function for any async operation. + * @param fn - The async function to be retried. + * @param maxRetries - Maximum retry attempts. + * @param retryDelay - Delay between retries in milliseconds. + * @returns A promise of the type that the async function returns. + */ +export async function withRetry( + fn: () => Promise, + maxRetries: number = 3, + retryDelay: number = 100 +): Promise { + let lastError: any; + + const delay = (ms: number) => + new Promise((resolve) => setTimeout(resolve, ms)); + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await fn(); + } catch (error) { + lastError = error; + if (attempt < maxRetries) { + const waitTime = Math.pow(2, attempt - 1) * retryDelay; + console.log(`Attempt ${attempt} failed, retrying in ${waitTime}ms...`); + await delay(waitTime); + } else { + console.error( + `All ${maxRetries} attempts failed. Last error:`, + lastError + ); + } + } + } + + throw lastError; +}