JS: Enhanced 429 fix + better error handle. Updated refresh button. (#229)

* JS: Enhanced 429 fix + better error handle. Updated refresh button.

* Update offerstable.js
This commit is contained in:
Gerlof van Ek 2025-01-18 18:14:49 +01:00 committed by GitHub
parent 5d1bed6423
commit 67d808cbe4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 490 additions and 309 deletions

View file

@ -77,7 +77,7 @@ let filterTimeout = null;
const CACHE_DURATION = 10 * 60 * 1000; const CACHE_DURATION = 10 * 60 * 1000;
// Application Constants // Application Constants
const itemsPerPage = 50; const itemsPerPage = 100;
const isSentOffers = window.offersTableConfig.isSentOffers; const isSentOffers = window.offersTableConfig.isSentOffers;
const offersConfig = { const offersConfig = {
@ -1106,7 +1106,7 @@ function getEmptyPriceData() {
async function fetchLatestPrices() { async function fetchLatestPrices() {
const PRICES_CACHE_KEY = 'prices_coingecko'; const PRICES_CACHE_KEY = 'prices_coingecko';
const minRequestInterval = 30000; const minRequestInterval = 60000;
const currentTime = Date.now(); const currentTime = Date.now();
if (!window.isManualRefresh) { if (!window.isManualRefresh) {
@ -2553,17 +2553,51 @@ function initializeTableEvents() {
}); });
} }
const refreshButton = document.getElementById('refreshOffers'); const refreshButton = document.getElementById('refreshOffers');
if (refreshButton) { if (refreshButton) {
let lastRefreshTime = 0;
const REFRESH_COOLDOWN = 6000;
let countdownInterval;
EventManager.add(refreshButton, 'click', async () => { EventManager.add(refreshButton, 'click', async () => {
const now = Date.now();
if (now - lastRefreshTime < REFRESH_COOLDOWN) {
console.log('Refresh rate limited. Please wait before refreshing again.');
const startTime = now;
const refreshText = document.getElementById('refreshText');
refreshButton.classList.remove('bg-blue-600', 'hover:bg-green-600', 'border-blue-500', 'hover:border-green-600');
refreshButton.classList.add('bg-red-600', 'border-red-500', 'cursor-not-allowed');
if (countdownInterval) clearInterval(countdownInterval);
countdownInterval = setInterval(() => {
const currentTime = Date.now();
const elapsedTime = currentTime - startTime;
const remainingTime = Math.ceil((REFRESH_COOLDOWN - elapsedTime) / 1000);
if (remainingTime <= 0) {
clearInterval(countdownInterval);
refreshText.textContent = 'Refresh';
refreshButton.classList.remove('bg-red-600', 'border-red-500', 'cursor-not-allowed');
refreshButton.classList.add('bg-blue-600', 'hover:bg-green-600', 'border-blue-500', 'hover:border-green-600');
} else {
refreshText.textContent = `Refresh (${remainingTime}s)`;
}
}, 100);
return;
}
console.log('Manual refresh initiated'); console.log('Manual refresh initiated');
lastRefreshTime = now;
const refreshIcon = document.getElementById('refreshIcon'); const refreshIcon = document.getElementById('refreshIcon');
const refreshText = document.getElementById('refreshText'); const refreshText = document.getElementById('refreshText');
refreshButton.disabled = true; refreshButton.disabled = true;
refreshIcon.classList.add('animate-spin'); refreshIcon.classList.add('animate-spin');
refreshText.textContent = 'Refreshing...'; refreshText.textContent = 'Refreshing...';
refreshButton.classList.add('opacity-75', 'cursor-wait'); refreshButton.classList.add('opacity-75', 'cursor-wait');
try { try {
const cachedPrices = CacheManager.get('prices_coingecko'); const cachedPrices = CacheManager.get('prices_coingecko');
let previousPrices = cachedPrices ? cachedPrices.value : null; let previousPrices = cachedPrices ? cachedPrices.value : null;
@ -2591,12 +2625,15 @@ if (refreshButton) {
} }
updateJsonView(); updateJsonView();
updatePaginationInfo(); updatePaginationInfo();
lastRefreshTime = Date.now(); lastRefreshTime = now;
updateLastRefreshTime(); updateLastRefreshTime();
console.log('Manual refresh completed successfully'); console.log('Manual refresh completed successfully');
} catch (error) { } catch (error) {
console.error('Error during manual refresh:', error); console.error('Error during manual refresh:', error);
ui.displayErrorMessage('Unable to refresh data. Previous data will be preserved.'); ui.displayErrorMessage('Unable to refresh data. Previous data will be preserved.');
const cachedData = CacheManager.get('prices_coingecko'); const cachedData = CacheManager.get('prices_coingecko');
if (cachedData?.value) { if (cachedData?.value) {
latestPrices = cachedData.value; latestPrices = cachedData.value;
@ -2608,6 +2645,13 @@ if (refreshButton) {
refreshIcon.classList.remove('animate-spin'); refreshIcon.classList.remove('animate-spin');
refreshText.textContent = 'Refresh'; refreshText.textContent = 'Refresh';
refreshButton.classList.remove('opacity-75', 'cursor-wait'); refreshButton.classList.remove('opacity-75', 'cursor-wait');
refreshButton.classList.remove('bg-red-600', 'border-red-500', 'cursor-not-allowed');
refreshButton.classList.add('bg-blue-600', 'hover:bg-green-600', 'border-blue-500', 'hover:border-green-600');
if (countdownInterval) {
clearInterval(countdownInterval);
}
} }
}); });
} }

View file

@ -76,7 +76,6 @@ const logger = {
error: (message) => console.error(`[AppError] ${new Date().toISOString()}: ${message}`) error: (message) => console.error(`[AppError] ${new Date().toISOString()}: ${message}`)
}; };
// API
const api = { const api = {
makePostRequest: (url, headers = {}) => { makePostRequest: (url, headers = {}) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -114,34 +113,31 @@ const api = {
}, },
fetchCryptoCompareDataXHR: (coin) => { fetchCryptoCompareDataXHR: (coin) => {
return rateLimiter.queueRequest('cryptocompare', async () => {
const url = `${config.apiEndpoints.cryptoCompare}?fsyms=${coin}&tsyms=USD,BTC&api_key=${config.apiKeys.cryptoCompare}`; const url = `${config.apiEndpoints.cryptoCompare}?fsyms=${coin}&tsyms=USD,BTC&api_key=${config.apiKeys.cryptoCompare}`;
const headers = { const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5', 'Accept-Language': 'en-US,en;q=0.5',
}; };
return api.makePostRequest(url, headers).catch(error => ({ try {
error: error.message return await api.makePostRequest(url, headers);
})); } catch (error) {
return { error: error.message };
}
});
}, },
fetchCoinGeckoDataXHR: async () => { fetchCoinGeckoDataXHR: async () => {
const cacheKey = 'coinGeckoOneLiner'; const cacheKey = 'coinGeckoOneLiner';
const minRequestInterval = 30000;
const currentTime = Date.now();
const lastRequestTime = window.lastGeckoRequest || 0;
if (currentTime - lastRequestTime < minRequestInterval) {
console.log('Request too soon, using cache');
const cachedData = cache.get(cacheKey);
if (cachedData) {
return cachedData.value;
}
}
window.lastGeckoRequest = currentTime;
const cachedData = cache.get(cacheKey); const cachedData = cache.get(cacheKey);
if (cachedData) { if (cachedData) {
console.log('Using cached CoinGecko data'); console.log('Using cached CoinGecko data');
return cachedData.value; return cachedData.value;
} }
return rateLimiter.queueRequest('coingecko', async () => {
try { try {
const existingCache = localStorage.getItem(cacheKey); const existingCache = localStorage.getItem(cacheKey);
let fallbackData = null; let fallbackData = null;
@ -153,23 +149,28 @@ const api = {
console.warn('Failed to parse existing cache:', e); console.warn('Failed to parse existing cache:', e);
} }
} }
const coinIds = config.coins const coinIds = config.coins
.filter(coin => coin.usesCoinGecko) .filter(coin => coin.usesCoinGecko)
.map(coin => coin.name) .map(coin => coin.name)
.join(','); .join(',');
const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=${coinIds}&vs_currencies=usd,btc&include_24hr_vol=true&include_24hr_change=true&api_key=${config.apiKeys.coinGecko}`; const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=${coinIds}&vs_currencies=usd,btc&include_24hr_vol=true&include_24hr_change=true&api_key=${config.apiKeys.coinGecko}`;
const response = await api.makePostRequest(url, { const response = await api.makePostRequest(url, {
'User-Agent': 'Mozilla/5.0', 'User-Agent': 'Mozilla/5.0',
'Accept': 'application/json', 'Accept': 'application/json',
'Accept-Language': 'en-US,en;q=0.5' 'Accept-Language': 'en-US,en;q=0.5'
}); });
if (typeof response !== 'object' || response === null) { if (typeof response !== 'object' || response === null) {
if (fallbackData) { if (fallbackData) {
console.log('Using fallback data due to invalid response'); console.log('Using fallback data due to invalid response');
return fallbackData; return fallbackData;
} }
throw new AppError(`Invalid data structure received from CoinGecko`); throw new AppError('Invalid data structure received from CoinGecko');
} }
if (response.error || response.Error) { if (response.error || response.Error) {
if (fallbackData) { if (fallbackData) {
console.log('Using fallback data due to API error'); console.log('Using fallback data due to API error');
@ -177,6 +178,7 @@ const api = {
} }
throw new AppError(response.error || response.Error); throw new AppError(response.error || response.Error);
} }
const transformedData = {}; const transformedData = {};
Object.entries(response).forEach(([id, values]) => { Object.entries(response).forEach(([id, values]) => {
const coinConfig = config.coins.find(coin => coin.name === id); const coinConfig = config.coins.find(coin => coin.name === id);
@ -189,23 +191,27 @@ const api = {
displayName: coinConfig?.displayName || coinConfig?.symbol || id displayName: coinConfig?.displayName || coinConfig?.symbol || id
}; };
}); });
cache.set(cacheKey, transformedData); cache.set(cacheKey, transformedData);
return transformedData; return transformedData;
} catch (error) { } catch (error) {
console.error(`Error fetching CoinGecko data:`, error); console.error('Error fetching CoinGecko data:', error);
const cachedData = cache.get(cacheKey);
if (cachedData) { if (cachedData) {
console.log('Using expired cache data due to error'); console.log('Using expired cache data due to error');
return cachedData.value; return cachedData.value;
} }
return { error: error.message }; return { error: error.message };
} }
}, });
},
fetchHistoricalDataXHR: async (coinSymbols) => { fetchHistoricalDataXHR: async (coinSymbols) => {
if (!Array.isArray(coinSymbols)) { if (!Array.isArray(coinSymbols)) {
coinSymbols = [coinSymbols]; coinSymbols = [coinSymbols];
} }
const results = {}; const results = {};
const fetchPromises = coinSymbols.map(async coin => { const fetchPromises = coinSymbols.map(async coin => {
const coinConfig = config.coins.find(c => c.symbol === coin); const coinConfig = config.coins.find(c => c.symbol === coin);
@ -217,7 +223,9 @@ fetchHistoricalDataXHR: async (coinSymbols) => {
results[coin] = cachedData.value; results[coin] = cachedData.value;
return; return;
} }
if (coin === 'WOW') { if (coin === 'WOW') {
return rateLimiter.queueRequest('coingecko', async () => {
const url = `${config.apiEndpoints.coinGecko}/coins/wownero/market_chart?vs_currency=usd&days=1&api_key=${config.apiKeys.coinGecko}`; const url = `${config.apiEndpoints.coinGecko}/coins/wownero/market_chart?vs_currency=usd&days=1&api_key=${config.apiKeys.coinGecko}`;
try { try {
const response = await api.makePostRequest(url); const response = await api.makePostRequest(url);
@ -231,7 +239,9 @@ fetchHistoricalDataXHR: async (coinSymbols) => {
results[coin] = cachedData.value; results[coin] = cachedData.value;
} }
} }
});
} else { } else {
return rateLimiter.queueRequest('cryptocompare', async () => {
const resolution = config.resolutions[config.currentResolution]; const resolution = config.resolutions[config.currentResolution];
let url; let url;
if (resolution.interval === 'hourly') { if (resolution.interval === 'hourly') {
@ -239,6 +249,7 @@ fetchHistoricalDataXHR: async (coinSymbols) => {
} else { } else {
url = `${config.apiEndpoints.cryptoCompareHistorical}?fsym=${coin}&tsym=USD&limit=${resolution.days}&api_key=${config.apiKeys.cryptoCompare}`; url = `${config.apiEndpoints.cryptoCompareHistorical}?fsym=${coin}&tsym=USD&limit=${resolution.days}&api_key=${config.apiKeys.cryptoCompare}`;
} }
try { try {
const response = await api.makePostRequest(url); const response = await api.makePostRequest(url);
if (response.Response === "Error") { if (response.Response === "Error") {
@ -256,11 +267,65 @@ fetchHistoricalDataXHR: async (coinSymbols) => {
results[coin] = cachedData.value; results[coin] = cachedData.value;
} }
} }
});
} }
}); });
await Promise.all(fetchPromises); await Promise.all(fetchPromises);
return results; return results;
} }
};
const rateLimiter = {
lastRequestTime: {},
minRequestInterval: {
coingecko: 15000,
cryptocompare: 1000
},
requestQueue: {},
canMakeRequest: function(apiName) {
const now = Date.now();
const lastRequest = this.lastRequestTime[apiName] || 0;
return (now - lastRequest) >= this.minRequestInterval[apiName];
},
updateLastRequestTime: function(apiName) {
this.lastRequestTime[apiName] = Date.now();
},
getWaitTime: function(apiName) {
const now = Date.now();
const lastRequest = this.lastRequestTime[apiName] || 0;
return Math.max(0, this.minRequestInterval[apiName] - (now - lastRequest));
},
queueRequest: async function(apiName, requestFn) {
if (!this.requestQueue[apiName]) {
this.requestQueue[apiName] = Promise.resolve();
}
try {
await this.requestQueue[apiName];
const executeRequest = async () => {
const waitTime = this.getWaitTime(apiName);
if (waitTime > 0) {
await new Promise(resolve => setTimeout(resolve, waitTime));
}
this.updateLastRequestTime(apiName);
return await requestFn();
};
this.requestQueue[apiName] = executeRequest();
return await this.requestQueue[apiName];
} catch (error) {
console.error(`Queue error for ${apiName}:`, error);
throw error;
}
}
}; };
// CACHE // CACHE
@ -927,8 +992,6 @@ const app = {
if (chartContainer) { if (chartContainer) {
chartModule.initChart(); chartModule.initChart();
chartModule.showChartLoader(); chartModule.showChartLoader();
} else {
//console.warn('Chart container not found, skipping chart initialization');
} }
console.log('Loading all coin data...'); console.log('Loading all coin data...');
@ -938,16 +1001,20 @@ const app = {
config.currentResolution = 'day'; config.currentResolution = 'day';
await chartModule.updateChart('BTC'); await chartModule.updateChart('BTC');
app.updateResolutionButtons('BTC'); app.updateResolutionButtons('BTC');
const chartTitle = document.getElementById('chart-title');
if (chartTitle) {
chartTitle.textContent = 'Price Chart (BTC)';
}
} }
ui.setActiveContainer('btc-container'); ui.setActiveContainer('btc-container');
//console.log('Setting up event listeners and initializations...');
app.setupEventListeners(); app.setupEventListeners();
app.initializeSelectImages(); app.initializeSelectImages();
app.initAutoRefresh(); app.initAutoRefresh();
} catch (error) { } catch (error) {
//console.error('Error during initialization:', error);
ui.displayErrorMessage('Failed to initialize the dashboard. Please try refreshing the page.'); ui.displayErrorMessage('Failed to initialize the dashboard. Please try refreshing the page.');
} finally { } finally {
ui.hideLoader(); ui.hideLoader();
@ -957,7 +1024,6 @@ const app = {
console.log('App onLoad completed'); console.log('App onLoad completed');
} }
}, },
loadAllCoinData: async () => { loadAllCoinData: async () => {
//console.log('Loading data for all coins...'); //console.log('Loading data for all coins...');
try { try {
@ -1021,13 +1087,15 @@ const app = {
//console.log(`Data loaded for ${coin.symbol}`); //console.log(`Data loaded for ${coin.symbol}`);
}, },
setupEventListeners: () => { setupEventListeners: () => {
//console.log('Setting up event listeners...');
config.coins.forEach(coin => { config.coins.forEach(coin => {
const container = document.getElementById(`${coin.symbol.toLowerCase()}-container`); const container = document.getElementById(`${coin.symbol.toLowerCase()}-container`);
if (container) { if (container) {
container.addEventListener('click', () => { container.addEventListener('click', () => {
//console.log(`${coin.symbol} container clicked`); const chartTitle = document.getElementById('chart-title');
if (chartTitle) {
chartTitle.textContent = `Price Chart (${coin.symbol})`;
}
ui.setActiveContainer(`${coin.symbol.toLowerCase()}-container`); ui.setActiveContainer(`${coin.symbol.toLowerCase()}-container`);
if (chartModule.chart) { if (chartModule.chart) {
if (coin.symbol === 'WOW') { if (coin.symbol === 'WOW') {
@ -1102,79 +1170,142 @@ const app = {
} else { } else {
nextRefreshTime = now + config.cacheTTL; nextRefreshTime = now + config.cacheTTL;
} }
const timeUntilRefresh = nextRefreshTime - now; const timeUntilRefresh = nextRefreshTime - now;
console.log(`Next refresh scheduled in ${timeUntilRefresh / 1000} seconds`); console.log(`Next refresh scheduled in ${timeUntilRefresh / 1000} seconds`);
app.nextRefreshTime = nextRefreshTime; app.nextRefreshTime = nextRefreshTime;
app.autoRefreshInterval = setTimeout(() => { app.autoRefreshInterval = setTimeout(() => {
console.log('Auto-refresh triggered'); console.log('Auto-refresh triggered');
app.refreshAllData(); app.refreshAllData();
}, timeUntilRefresh); }, timeUntilRefresh);
localStorage.setItem('nextRefreshTime', app.nextRefreshTime.toString()); localStorage.setItem('nextRefreshTime', app.nextRefreshTime.toString());
app.updateNextRefreshTime(); app.updateNextRefreshTime();
}, },
refreshAllData: async () => { refreshAllData: async () => {
if (app.isRefreshing) { if (app.isRefreshing) {
console.log('Refresh already in progress, skipping...'); console.log('Refresh already in progress, skipping...');
return; return;
} }
const lastGeckoRequest = rateLimiter.lastRequestTime['coingecko'] || 0;
const timeSinceLastRequest = Date.now() - lastGeckoRequest;
if (timeSinceLastRequest < rateLimiter.minRequestInterval.coingecko) {
let waitTime = Math.ceil((rateLimiter.minRequestInterval.coingecko - timeSinceLastRequest) / 1000);
ui.displayErrorMessage(`Rate limit: Please wait ${waitTime} seconds before refreshing`);
const countdownInterval = setInterval(() => {
waitTime--;
if (waitTime > 0) {
ui.displayErrorMessage(`Rate limit: Please wait ${waitTime} seconds before refreshing`);
} else {
clearInterval(countdownInterval);
ui.hideErrorMessage();
}
}, 1000);
return;
}
console.log('Refreshing all data...'); console.log('Refreshing all data...');
app.isRefreshing = true; app.isRefreshing = true;
ui.showLoader(); ui.showLoader();
chartModule.showChartLoader(); chartModule.showChartLoader();
try { try {
ui.hideErrorMessage();
cache.clear(); cache.clear();
await app.updateBTCPrice(); const btcUpdateSuccess = await app.updateBTCPrice();
if (!btcUpdateSuccess) {
console.warn('BTC price update failed, continuing with cached or default value');
}
await new Promise(resolve => setTimeout(resolve, 1000));
const allCoinData = await api.fetchCoinGeckoDataXHR(); const allCoinData = await api.fetchCoinGeckoDataXHR();
if (allCoinData.error) { if (allCoinData.error) {
throw new Error(allCoinData.error); throw new Error(`CoinGecko API Error: ${allCoinData.error}`);
} }
const failedCoins = [];
for (const coin of config.coins) { for (const coin of config.coins) {
const symbol = coin.symbol.toLowerCase(); const symbol = coin.symbol.toLowerCase();
const coinData = allCoinData[symbol]; const coinData = allCoinData[symbol];
if (coinData) {
coinData.displayName = coin.displayName || coin.symbol;
try {
if (!coinData) {
throw new Error(`No data received`);
}
coinData.displayName = coin.displayName || coin.symbol;
ui.displayCoinData(coin.symbol, coinData); ui.displayCoinData(coin.symbol, coinData);
const cacheKey = `coinData_${coin.symbol}`; const cacheKey = `coinData_${coin.symbol}`;
cache.set(cacheKey, coinData); cache.set(cacheKey, coinData);
} else {
//console.error(`No data found for ${coin.symbol}`); } catch (coinError) {
console.warn(`Failed to update ${coin.symbol}: ${coinError.message}`);
failedCoins.push(coin.symbol);
} }
} }
await new Promise(resolve => setTimeout(resolve, 1000));
if (chartModule.currentCoin) { if (chartModule.currentCoin) {
try {
await chartModule.updateChart(chartModule.currentCoin, true); await chartModule.updateChart(chartModule.currentCoin, true);
} catch (chartError) {
console.error('Chart update failed:', chartError);
}
} }
app.lastRefreshedTime = new Date(); app.lastRefreshedTime = new Date();
localStorage.setItem('lastRefreshedTime', app.lastRefreshedTime.getTime().toString()); localStorage.setItem('lastRefreshedTime', app.lastRefreshedTime.getTime().toString());
ui.updateLastRefreshedTime(); ui.updateLastRefreshedTime();
console.log('All data refreshed successfully'); if (failedCoins.length > 0) {
const failureMessage = failedCoins.length === config.coins.length
? 'Failed to update any coin data'
: `Failed to update some coins: ${failedCoins.join(', ')}`;
let countdown = 5;
ui.displayErrorMessage(`${failureMessage} (${countdown}s)`);
const countdownInterval = setInterval(() => {
countdown--;
if (countdown > 0) {
ui.displayErrorMessage(`${failureMessage} (${countdown}s)`);
} else {
clearInterval(countdownInterval);
ui.hideErrorMessage();
}
}, 1000);
}
console.log(`Refresh completed. Failed coins: ${failedCoins.length}`);
} catch (error) { } catch (error) {
//console.error('Error refreshing all data:', error); console.error('Critical error during refresh:', error);
ui.displayErrorMessage('Failed to refresh all data. Please try again.'); let countdown = 10;
ui.displayErrorMessage(`Refresh failed: ${error.message}. Please try again later. (${countdown}s)`);
const countdownInterval = setInterval(() => {
countdown--;
if (countdown > 0) {
ui.displayErrorMessage(`Refresh failed: ${error.message}. Please try again later. (${countdown}s)`);
} else {
clearInterval(countdownInterval);
ui.hideErrorMessage();
}
}, 1000);
} finally { } finally {
ui.hideLoader(); ui.hideLoader();
chartModule.hideChartLoader(); chartModule.hideChartLoader();
app.isRefreshing = false; app.isRefreshing = false;
if (app.isAutoRefreshEnabled) { if (app.isAutoRefreshEnabled) {
app.scheduleNextRefresh(); app.scheduleNextRefresh();
} }
} }
}, },
updateNextRefreshTime: () => { updateNextRefreshTime: () => {
console.log('Updating next refresh time display'); console.log('Updating next refresh time display');
@ -1266,26 +1397,32 @@ const app = {
} }
}, },
updateBTCPrice: async () => { updateBTCPrice: async () => {
//console.log('Updating BTC price...'); console.log('Updating BTC price...');
try { try {
const priceData = await api.fetchCoinGeckoDataXHR(); const priceData = await api.fetchCoinGeckoDataXHR();
if (priceData.error) {
//console.error('Error fetching BTC price:', priceData.error);
app.btcPriceUSD = 0;
} else if (priceData.btc && priceData.btc.current_price) {
if (priceData.error) {
console.warn('API error when fetching BTC price:', priceData.error);
return false;
}
if (priceData.btc && typeof priceData.btc.current_price === 'number') {
app.btcPriceUSD = priceData.btc.current_price; app.btcPriceUSD = priceData.btc.current_price;
} else { return true;
//console.error('Unexpected BTC data structure:', priceData); } else if (priceData.bitcoin && typeof priceData.bitcoin.usd === 'number') {
app.btcPriceUSD = 0; app.btcPriceUSD = priceData.bitcoin.usd;
return true;
} }
console.warn('Unexpected BTC price data structure:', priceData);
return false;
} catch (error) { } catch (error) {
//console.error('Error fetching BTC price:', error); console.error('Error fetching BTC price:', error);
app.btcPriceUSD = 0; return false;
} }
//console.log('Current BTC price:', app.btcPriceUSD); },
},
sortTable: (columnIndex) => { sortTable: (columnIndex) => {
//console.log(`Sorting column: ${columnIndex}`); //console.log(`Sorting column: ${columnIndex}`);