Fixing async hang bugs

This commit is contained in:
tripathyr 2025-08-25 10:01:24 +05:30 committed by GitHub
parent 822b1917bd
commit b374aacb4a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -15,6 +15,7 @@
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="css/main.min.css">
<script src="scripts/compactIDB.js" defer></script>
<script id="floGlobals">
/* Constants for FLO blockchain operations !!Make sure to add this at beginning!! */
const floGlobals = {
@ -32,7 +33,7 @@
<script src="scripts/lib.js" defer></script>
<script src="scripts/floCrypto.js" defer></script>
<script src="scripts/floBlockchainAPI.js" defer></script>
<script src="scripts/compactIDB.js" defer></script>
<script src="scripts/floCloudAPI.js" defer></script>
<script src="scripts/floDapps.js" defer></script>
<script src="scripts/fn_pay.js" defer></script>
@ -1621,16 +1622,19 @@
upiIds: {}
}
floGlobals.isSubAdmin = floGlobals.subAdmins.includes(myFloID)
Promise.all([floExchangeAPI.getSink(floExchangeAPI.serviceList.EXCHANGE), floExchangeAPI.getSink(floExchangeAPI.serviceList.CONVERT)])
.then(([exchangeSink, convertSink]) => {
floGlobals.exchangeSink = exchangeSink
floGlobals.convertSink = convertSink
console.log('Exchange sink: ', exchangeSink)
console.log('Convert sink: ', convertSink)
}).catch(error => {
console.error(error)
})
refreshBalance()
Promise.all([
floExchangeAPI.getSink(floExchangeAPI.serviceList.EXCHANGE)
.catch(e => { console.error('EXCHANGE sink error:', e); return null; }),
floExchangeAPI.getSink(floExchangeAPI.serviceList.CONVERT)
.catch(e => { console.error('CONVERT sink error:', e); return null; })
]).then(([exchangeSink, convertSink]) => {
floGlobals.exchangeSink = exchangeSink;
floGlobals.convertSink = convertSink;
console.log('Exchange sink: ', exchangeSink);
console.log('Convert sink: ', convertSink);
});
refreshBalance().catch(console.error);
if (floGlobals.isSubAdmin) {
cashierUI.renderRequests(Cashier.Requests);
Cashier.init().then(result => {
@ -1655,7 +1659,7 @@
floGlobals.loaded = true
}).catch(error => {
console.error(error)
//detectAdBlocker(error)
detectAdBlocker(error)
})
}
}).catch(error => {
@ -1676,8 +1680,8 @@
<script>
/*jshint esversion: 9 */
// Global variables
const { html, render: renderElem, svg } = uhtml;
const domRefs = {};
var { html, render: renderElem, svg } = uhtml;
var domRefs = {};
floGlobals.connectionErrorNotifications = []
let transactionsHistoryLoader = null;
let walletHistoryLoader = null;
@ -1696,6 +1700,40 @@
notify('We are back online.', 'success')
})
// ---- Balance cache helpers (IDB + localStorage fallback) ----
const BAL_STORE = 'lastBalances';
function balKey() { return (window.floGlobals && floGlobals.myFloID) || 'anon'; }
async function readCachedBalances() {
try {
// compactIDB.readData(store, key)
const data = await compactIDB.readData(BAL_STORE, balKey());
if (data && typeof data === 'object') return data;
} catch (_) {}
// fallback
try {
const raw = localStorage.getItem(`${BAL_STORE}:${balKey()}`);
return raw ? JSON.parse(raw) : null;
} catch (_) {}
return null;
}
async function writeCachedBalances(partial) {
try {
const prev = (await readCachedBalances()) || {};
const merged = { ...prev, ...partial, ts: Date.now() };
await compactIDB.writeData(BAL_STORE, merged, balKey());
try { localStorage.setItem(`${BAL_STORE}:${balKey()}`, JSON.stringify(merged)); } catch(_) {}
} catch (e) {
// If IDB write fails, at least keep localStorage
try {
const prev = JSON.parse(localStorage.getItem(`${BAL_STORE}:${balKey()}`) || '{}');
const merged = { ...prev, ...partial, ts: Date.now() };
localStorage.setItem(`${BAL_STORE}:${balKey()}`, JSON.stringify(merged));
} catch(_) {}
}
}
// Use instead of document.getElementById
function getRef(elementId) {
if (!domRefs.hasOwnProperty(elementId)) {
@ -1733,7 +1771,7 @@
}
// Use when a function needs to be executed after user finishes changes
const debounce = (callback, wait) => {
var debounce = (callback, wait) => {
let timeoutId = null;
return (...args) => {
window.clearTimeout(timeoutId);
@ -2147,11 +2185,11 @@
}
]
const appState = {
var appState = {
params: {},
openedPages: new Set(),
}
const generalPages = ['sign_up', 'sign_in', 'loading', 'landing']
var generalPages = ['sign_up', 'sign_in', 'loading', 'landing']
async function routeTo(targetPage, options = {}) {
console.log('route to', targetPage)
const { firstLoad, hashChange } = options
@ -2778,7 +2816,7 @@
User.cashToToken(cashier, amount, floGlobals.txCode/* , upiId */).then(result => {
console.log(result);
showChildElement('topup_wallet_process', 2);
refreshBalance()
refreshBalance().catch(console.error)
}).catch(error => {
console.error(error)
getRef('topup_failed_reason').textContent = error;
@ -2804,7 +2842,7 @@
console.warn(`Withdraw ${amount} from cashier ${cashier}`, txid);
User.tokenToCash(cashier, amount, txid, upiId).then(result => {
showChildElement('withdraw_wallet_process', 1);
refreshBalance();
refreshBalance().catch(console.error);
getRef('withdrawal_blockchain_link').classList.remove('hidden');
getRef('withdrawal_blockchain_link').href = `${floBlockchainAPI.current_server}tx/${txid}`
console.log(result);
@ -3903,82 +3941,156 @@
}
async function refreshBalance(button) {
if (button) buttonLoader(button, true);
async function refreshBalance(button) {
const online = !(typeof navigator !== 'undefined' && navigator.onLine === false);
// Rupee (unchanged)
floTokenAPI.getBalance(floGlobals.myFloID).then((balance = 0) => {
getRef('rupee_balance').textContent = formatAmount(balance);
if (button) buttonLoader(button, false);
});
// turn on once
if (button) buttonLoader(button, true);
// BTC (unchanged)
btcOperator.getBalance(floGlobals.myBtcID).then((btcBalance = 0) => {
getRef('btc_balance').textContent = formatAmount(btcBalance, 'btc');
});
// helper: race with timeout → fallback
const pTimeout = (p, ms, fallback) => Promise.race([
Promise.resolve(p).catch(() => fallback),
new Promise(res => setTimeout(() => res(fallback), ms))
]);
// FLO (unchanged)
try {
const [floBal, floRates] = await Promise.all([
floBlockchainAPI.getBalance(floGlobals.myFloID),
floExchangeAPI.getRates('FLO')
]);
getRef('flo_balance').textContent = formatAmount(floBal, 'flo');
if (floBal < floGlobals.settings.user_flo_threshold) {
getRef('low_user_flo_warning').textContent =
`Your FLO balance is low. You will receive ${floGlobals.settings.send_user_flo} ` +
`FLO of worth ₹${parseFloat(floRates.rate.toFixed(2))} deducted from top-up amount.`;
getRef('low_user_flo_warning').classList.remove('hidden');
} else {
getRef('low_user_flo_warning').classList.add('hidden');
}
if (button) buttonLoader(button, false);
} catch (e) {
console.error(e);
}
// refs
const refs = {
rupee: getRef('rupee_balance'),
btc: getRef('btc_balance'),
flo: getRef('flo_balance'),
warn: getRef('low_user_flo_warning'),
eth: getRef('eth_balance'),
usdc: getRef('usdc_erc20_balance'),
usdt: getRef('usdt_erc20_balance'),
};
// === NEW: ETH + USDC + USDT (ERC-20) ===
try {
// neutral placeholders while fetching
const ethOut = getRef('eth_balance');
const usdcOut = getRef('usdc_erc20_balance');
const usdtOut = getRef('usdt_erc20_balance');
if (ethOut) ethOut.textContent = '0';
if (usdcOut) usdcOut.textContent = '0';
if (usdtOut) usdtOut.textContent = '0';
// ------------- PAINT CACHED FIRST (instant) -------------
try {
const cached = await readCachedBalances();
if (cached) {
if (refs.rupee && cached.rupee != null) refs.rupee.textContent = formatAmount(Number(cached.rupee));
if (refs.btc && cached.btc != null) refs.btc.textContent = formatAmount(Number(cached.btc), 'btc');
if (refs.flo && cached.flo != null) refs.flo.textContent = formatAmount(Number(cached.flo), 'flo');
if (refs.eth && cached.eth != null) refs.eth.textContent = formatAmount(Number(cached.eth), 'eth');
if (refs.usdc && cached.usdc != null) refs.usdc.textContent = formatAmount(Number(cached.usdc), 'usd');
if (refs.usdt && cached.usdt != null) refs.usdt.textContent = formatAmount(Number(cached.usdt), 'usd');
} else {
// gentle placeholders if nothing cached yet
if (refs.rupee && !refs.rupee.textContent) refs.rupee.textContent = '0';
if (refs.btc && !refs.btc.textContent) refs.btc.textContent = '0';
if (refs.flo && !refs.flo.textContent) refs.flo.textContent = '0';
if (refs.eth) refs.eth.textContent = refs.eth.textContent || '0';
if (refs.usdc) refs.usdc.textContent = refs.usdc.textContent || '0';
if (refs.usdt) refs.usdt.textContent = refs.usdt.textContent || '0';
}
} catch (e) {
console.warn('readCachedBalances failed', e);
}
const ethAddr = await getUserEthAddress();
floGlobals.myEthID = ethAddr;
if (ethAddr && window.ethOperator) {
const [ethBal, usdcBal, usdtBal] = await Promise.all([
ethOperator.getBalance(ethAddr),
ethOperator.getTokenBalance(ethAddr, 'usdc'),
ethOperator.getTokenBalance(ethAddr, 'usdt'),
]);
// ------------- FETCH LIVE IN PARALLEL (soft timeouts) -------------
const newVals = {}; // well persist whatever we successfully get
// ETH
if (ethOut) ethOut.textContent = formatAmount(
typeof ethBal === 'string' ? Number(ethBal) : (ethBal || 0), 'eth'
);
const jobs = [];
// USDC (6 decimals typically; assume helper already returns display units)
if (usdcOut) usdcOut.textContent = formatAmount(
typeof usdcBal === 'string' ? Number(usdcBal) : (usdcBal || 0), 'usd'
);
// RUPEE
jobs.push(
pTimeout(floTokenAPI.getBalance(floGlobals.myFloID), 2500, null).then(bal => {
if (bal != null) {
newVals.rupee = Number(bal) || 0;
if (refs.rupee) refs.rupee.textContent = formatAmount(newVals.rupee);
}
})
);
// USDT
if (usdtOut) usdtOut.textContent = formatAmount(
typeof usdtBal === 'string' ? Number(usdtBal) : (usdtBal || 0), 'usd'
);
}
// If no ETH stack, placeholders remain
} catch (e) {
console.error('ERC-20 balance fetch failed:', e);
if (getRef('eth_balance')) getRef('eth_balance').textContent = '0';
if (getRef('usdc_erc20_balance')) getRef('usdc_erc20_balance').textContent = '0';
if (getRef('usdt_erc20_balance')) getRef('usdt_erc20_balance').textContent = '0';
}
}
// BTC (skip offline)
if (online) {
jobs.push(
pTimeout(btcOperator.getBalance(floGlobals.myBtcID), 2500, null).then(bal => {
if (bal != null) {
newVals.btc = Number(bal) || 0;
if (refs.btc) refs.btc.textContent = formatAmount(newVals.btc, 'btc');
}
}).catch(()=>{})
);
}
// FLO (balance + rate)
{
const floBalP = pTimeout(floBlockchainAPI.getBalance(floGlobals.myFloID), 3000, null);
const floRatesP = pTimeout(floExchangeAPI.getRates('FLO'), 2000, null);
jobs.push(
Promise.allSettled([floBalP, floRatesP]).then(([balRes, rateRes]) => {
const floBal = balRes.status === 'fulfilled' ? balRes.value : null;
const rates = rateRes.status === 'fulfilled' ? rateRes.value : null;
if (floBal != null) {
newVals.flo = Number(floBal) || 0;
if (refs.flo) refs.flo.textContent = formatAmount(newVals.flo, 'flo');
if (refs.warn) {
const th = (floGlobals.settings && floGlobals.settings.user_flo_threshold) || 0;
if (newVals.flo < th) {
const sendAmt = floGlobals.settings?.send_user_flo ?? 0;
const rateStr = Number(rates?.rate || 0).toFixed(2);
refs.warn.textContent =
`Your FLO balance is low. You will receive ${sendAmt} FLO of worth ₹${rateStr} deducted from top-up amount.`;
refs.warn.classList.remove('hidden');
} else {
refs.warn.classList.add('hidden');
}
}
}
})
);
}
// ETH + ERC-20 (skip offline and only if stack exists)
if (online && window.ethOperator) {
jobs.push((async () => {
try {
const ethAddr = await pTimeout(getUserEthAddress(), 1000, null);
if (!ethAddr) return;
floGlobals.myEthID = ethAddr;
const [ethBal, usdcBal, usdtBal] = await Promise.all([
pTimeout(ethOperator.getBalance(ethAddr), 3000, null),
pTimeout(ethOperator.getTokenBalance(ethAddr, 'usdc'), 3000, null),
pTimeout(ethOperator.getTokenBalance(ethAddr, 'usdt'), 3000, null),
]);
if (ethBal != null) {
newVals.eth = Number(ethBal) || 0;
if (refs.eth) refs.eth.textContent = formatAmount(newVals.eth, 'eth');
}
if (usdcBal != null) {
newVals.usdc = Number(usdcBal) || 0;
if (refs.usdc) refs.usdc.textContent = formatAmount(newVals.usdc, 'usd');
}
if (usdtBal != null) {
newVals.usdt = Number(usdtBal) || 0;
if (refs.usdt) refs.usdt.textContent = formatAmount(newVals.usdt, 'usd');
}
} catch (e) {
console.warn('ERC-20 balances failed', e);
}
})());
}
// ------------- SAVE WHATEVER WE GOT -------------
const all = Promise.allSettled(jobs).then(async () => {
if (Object.keys(newVals).length) {
try { await writeCachedBalances(newVals); } catch (e) { console.warn('writeCachedBalances failed', e); }
}
});
// turn loader off once: fastest of (all done OR 3.5s)
pTimeout(all, 3500, null).then(() => { if (button) buttonLoader(button, false); });
}
window.addEventListener('online', () => refreshBalance());
@ -4507,7 +4619,10 @@ async function refreshBalance(button) {
const assetIcons = {
btc: ` <svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"> <g> <rect fill="none" height="24" width="24"></rect> </g> <g> <path d="M17.06,11.57C17.65,10.88,18,9.98,18,9c0-1.86-1.27-3.43-3-3.87L15,3h-2v2h-2V3H9v2H6v2h2v10H6v2h3v2h2v-2h2v2h2v-2 c2.21,0,4-1.79,4-4C19,13.55,18.22,12.27,17.06,11.57z M10,7h4c1.1,0,2,0.9,2,2s-0.9,2-2,2h-4V7z M15,17h-5v-4h5c1.1,0,2,0.9,2,2 S16.1,17,15,17z"> </path> </g> </svg> `,
usd: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z"/></svg>`,
rupee: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M13.66,7C13.1,5.82,11.9,5,10.5,5L6,5V3h12v2l-3.26,0c0.48,0.58,0.84,1.26,1.05,2L18,7v2l-2.02,0c-0.25,2.8-2.61,5-5.48,5 H9.77l6.73,7h-2.77L7,14v-2h3.5c1.76,0,3.22-1.3,3.46-3L6,9V7L13.66,7z"/></g></g></svg>`
rupee: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M13.66,7C13.1,5.82,11.9,5,10.5,5L6,5V3h12v2l-3.26,0c0.48,0.58,0.84,1.26,1.05,2L18,7v2l-2.02,0c-0.25,2.8-2.61,5-5.48,5 H9.77l6.73,7h-2.77L7,14v-2h3.5c1.76,0,3.22-1.3,3.46-3L6,9V7L13.66,7z"/></g></g></svg>`,
ether: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none"> <g clip-path="url(#clip0_201_2)"> <path d="M12 0L19.6368 12.4368L12.1633 16.8L4.36325 12.4368L12 0Z"/> <path d="M12 24L4.36325 13.6099L11.8367 18L19.6368 13.6099L12 24Z"/> </g> <defs> <clipPath id="clip0_201_2"> <rect width="24" height="24" fill="white"/> </clipPath> </defs> </svg>`,
usdc: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" data-name="86977684-12db-4850-8f30-233a7c267d11" viewBox="0 0 2000 2000"> <path d="M1000 2000c554.17 0 1000-445.83 1000-1000S1554.17 0 1000 0 0 445.83 0 1000s445.83 1000 1000 1000z" fill="#2775ca"/> <path d="M1275 1158.33c0-145.83-87.5-195.83-262.5-216.66-125-16.67-150-50-150-108.34s41.67-95.83 125-95.83c75 0 116.67 25 137.5 87.5 4.17 12.5 16.67 20.83 29.17 20.83h66.66c16.67 0 29.17-12.5 29.17-29.16v-4.17c-16.67-91.67-91.67-162.5-187.5-170.83v-100c0-16.67-12.5-29.17-33.33-33.34h-62.5c-16.67 0-29.17 12.5-33.34 33.34v95.83c-125 16.67-204.16 100-204.16 204.17 0 137.5 83.33 191.66 258.33 212.5 116.67 20.83 154.17 45.83 154.17 112.5s-58.34 112.5-137.5 112.5c-108.34 0-145.84-45.84-158.34-108.34-4.16-16.66-16.66-25-29.16-25h-70.84c-16.66 0-29.16 12.5-29.16 29.17v4.17c16.66 104.16 83.33 179.16 220.83 200v100c0 16.66 12.5 29.16 33.33 33.33h62.5c16.67 0 29.17-12.5 33.34-33.33v-100c125-20.84 208.33-108.34 208.33-220.84z" fill="#fff"/> <path d="M787.5 1595.83c-325-116.66-491.67-479.16-370.83-800 62.5-175 200-308.33 370.83-370.83 16.67-8.33 25-20.83 25-41.67V325c0-16.67-8.33-29.17-25-33.33-4.17 0-12.5 0-16.67 4.16-395.83 125-612.5 545.84-487.5 941.67 75 233.33 254.17 412.5 487.5 487.5 16.67 8.33 33.34 0 37.5-16.67 4.17-4.16 4.17-8.33 4.17-16.66v-58.34c0-12.5-12.5-29.16-25-37.5zM1229.17 295.83c-16.67-8.33-33.34 0-37.5 16.67-4.17 4.17-4.17 8.33-4.17 16.67v58.33c0 16.67 12.5 33.33 25 41.67 325 116.66 491.67 479.16 370.83 800-62.5 175-200 308.33-370.83 370.83-16.67 8.33-25 20.83-25 41.67V1700c0 16.67 8.33 29.17 25 33.33 4.17 0 12.5 0 16.67-4.16 395.83-125 612.5-545.84 487.5-941.67-75-237.5-258.34-416.67-487.5-491.67z" fill="#fff"/></svg>`,
usdt: `<svg class="icon" xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 339.43 295.27"><title>tether-usdt-logo</title><path d="M62.15,1.45l-61.89,130a2.52,2.52,0,0,0,.54,2.94L167.95,294.56a2.55,2.55,0,0,0,3.53,0L338.63,134.4a2.52,2.52,0,0,0,.54-2.94l-61.89-130A2.5,2.5,0,0,0,275,0H64.45a2.5,2.5,0,0,0-2.3,1.45h0Z" style="fill:#50af95;fill-rule:evenodd"/><path d="M191.19,144.8v0c-1.2.09-7.4,0.46-21.23,0.46-11,0-18.81-.33-21.55-0.46v0c-42.51-1.87-74.24-9.27-74.24-18.13s31.73-16.25,74.24-18.15v28.91c2.78,0.2,10.74.67,21.74,0.67,13.2,0,19.81-.55,21-0.66v-28.9c42.42,1.89,74.08,9.29,74.08,18.13s-31.65,16.24-74.08,18.12h0Zm0-39.25V79.68h59.2V40.23H89.21V79.68H148.4v25.86c-48.11,2.21-84.29,11.74-84.29,23.16s36.18,20.94,84.29,23.16v82.9h42.78V151.83c48-2.21,84.12-11.73,84.12-23.14s-36.09-20.93-84.12-23.15h0Zm0,0h0Z" style="fill:#fff;fill-rule:evenodd"/><script xmlns=""/></svg>`,
}
function convertCurrency(amount) {
let convertedAmount = 0;