Merge pull request from gerlofvanek/ws

JS: Fix websocket delay / loading tables faster.
This commit is contained in:
tecnovert 2025-02-17 09:57:17 +00:00 committed by GitHub
commit 8d317e4b67
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -75,6 +75,7 @@ let filterTimeout = null;
// CONFIGURATION CONSTANTS // CONFIGURATION CONSTANTS
// TIME CONSTANTS // TIME CONSTANTS
const CACHE_DURATION = 10 * 60 * 1000; const CACHE_DURATION = 10 * 60 * 1000;
const wsPort = config.port || window.ws_port || '11700';
// APP CONSTANTS // APP CONSTANTS
const itemsPerPage = 50; const itemsPerPage = 50;
@ -206,7 +207,7 @@ const WebSocketManager = {
this.connect(); this.connect();
} }
this.startHealthCheck(); this.startHealthCheck();
}, 1000); }, 0);
}, },
startHealthCheck() { startHealthCheck() {
@ -1158,26 +1159,50 @@ async function fetchOffers() {
const refreshText = document.getElementById('refreshText'); const refreshText = document.getElementById('refreshText');
try { try {
refreshButton.disabled = true; if (refreshButton) {
refreshIcon.classList.add('animate-spin'); refreshButton.disabled = true;
refreshText.textContent = 'Refreshing...'; refreshIcon.classList.add('animate-spin');
refreshButton.classList.add('opacity-75', 'cursor-wait'); refreshText.textContent = 'Refreshing...';
refreshButton.classList.add('opacity-75', 'cursor-wait');
}
const endpoint = isSentOffers ? '/json/sentoffers' : '/json/offers'; const [offersResponse, pricesData] = await Promise.all([
const response = await fetch(endpoint); fetch(isSentOffers ? '/json/sentoffers' : '/json/offers'),
const data = await response.json(); fetchLatestPrices()
]);
jsonData = formatInitialData(data); if (!offersResponse.ok) {
throw new Error(`HTTP error! status: ${offersResponse.status}`);
}
const data = await offersResponse.json();
const processedData = Array.isArray(data) ? data : Object.values(data);
jsonData = formatInitialData(processedData);
originalJsonData = [...jsonData]; originalJsonData = [...jsonData];
latestPrices = pricesData || getEmptyPriceData();
await updateOffersTable(); await updateOffersTable();
updatePaginationInfo(); updatePaginationInfo();
} catch (error) { } catch (error) {
console.error('[Debug] Error fetching offers:', error); console.error('[Debug] Error fetching offers:', error);
const cachedOffers = CacheManager.get('offers_cached');
if (cachedOffers?.value) {
jsonData = cachedOffers.value;
originalJsonData = [...jsonData];
await updateOffersTable();
}
ui.displayErrorMessage('Failed to fetch offers. Please try again later.'); ui.displayErrorMessage('Failed to fetch offers. Please try again later.');
} finally { } finally {
stopRefreshAnimation(); if (refreshButton) {
refreshButton.disabled = false;
refreshIcon.classList.remove('animate-spin');
refreshText.textContent = 'Refresh';
refreshButton.classList.remove('opacity-75', 'cursor-wait');
}
} }
} }
@ -1467,78 +1492,50 @@ async function updateOffersTable() {
window.TooltipManager.cleanup(); window.TooltipManager.cleanup();
} }
const PRICES_CACHE_KEY = 'prices_coingecko';
const cachedPrices = CacheManager.get(PRICES_CACHE_KEY);
latestPrices = cachedPrices?.value || getEmptyPriceData();
const validOffers = getValidOffers(); const validOffers = getValidOffers();
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = Math.min(startIndex + itemsPerPage, validOffers.length);
const itemsToDisplay = validOffers.slice(startIndex, endIndex);
fetchLatestPrices().then(freshPrices => {
if (freshPrices) {
latestPrices = freshPrices;
updateProfitLossDisplays();
}
}).catch(error => {
console.warn('Price fetch failed:', error);
});
const BATCH_SIZE = 5;
const identities = [];
for (let i = 0; i < itemsToDisplay.length; i += BATCH_SIZE) {
const batch = itemsToDisplay.slice(i, i + BATCH_SIZE);
const batchPromises = batch.map(offer =>
offer.addr_from ? IdentityManager.getIdentityData(offer.addr_from) : Promise.resolve(null)
);
const batchResults = await Promise.all(batchPromises);
identities.push(...batchResults);
if (i + BATCH_SIZE < itemsToDisplay.length) {
await new Promise(resolve => setTimeout(resolve, 100));
}
}
if (validOffers.length === 0) { if (validOffers.length === 0) {
handleNoOffersScenario(); handleNoOffersScenario();
return; return;
} }
const totalPages = Math.max(1, Math.ceil(validOffers.length / itemsPerPage)); const startIndex = (currentPage - 1) * itemsPerPage;
currentPage = Math.min(currentPage, totalPages); const endIndex = Math.min(startIndex + itemsPerPage, validOffers.length);
const itemsToDisplay = validOffers.slice(startIndex, endIndex);
const existingRows = offersBody.querySelectorAll('tr');
existingRows.forEach(row => {
cleanupRow(row);
});
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
itemsToDisplay.forEach((offer, index) => {
const identity = identities[index];
const row = createTableRow(offer, identity);
if (row) {
fragment.appendChild(row);
}
});
offersBody.textContent = ''; const BATCH_SIZE = 10;
offersBody.appendChild(fragment); for (let i = 0; i < itemsToDisplay.length; i += BATCH_SIZE) {
const batch = itemsToDisplay.slice(i, i + BATCH_SIZE);
const tooltipTriggers = offersBody.querySelectorAll('[data-tooltip-target]'); const batchPromises = batch.map(offer =>
tooltipTriggers.forEach(trigger => { offer.addr_from ? IdentityManager.getIdentityData(offer.addr_from) : Promise.resolve(null)
const targetId = trigger.getAttribute('data-tooltip-target'); );
const tooltipContent = document.getElementById(targetId);
const batchIdentities = await Promise.all(batchPromises);
if (tooltipContent) {
window.TooltipManager.create(trigger, tooltipContent.innerHTML, { batch.forEach((offer, index) => {
placement: trigger.getAttribute('data-tooltip-placement') || 'top' const row = createTableRow(offer, batchIdentities[index]);
}); if (row) fragment.appendChild(row);
});
if (i + BATCH_SIZE < itemsToDisplay.length) {
await new Promise(resolve => setTimeout(resolve, 16));
} }
}); }
if (offersBody) {
const existingRows = offersBody.querySelectorAll('tr');
existingRows.forEach(row => cleanupRow(row));
offersBody.textContent = '';
offersBody.appendChild(fragment);
}
initializeTooltips();
requestAnimationFrame(() => { requestAnimationFrame(() => {
updateRowTimes(); updateRowTimes();
updatePaginationControls(totalPages); updatePaginationControls(Math.ceil(validOffers.length / itemsPerPage));
if (tableRateModule?.initializeTable) { if (tableRateModule?.initializeTable) {
tableRateModule.initializeTable(); tableRateModule.initializeTable();
} }
@ -1550,16 +1547,6 @@ async function updateOffersTable() {
} catch (error) { } catch (error) {
console.error('[Debug] Error in updateOffersTable:', error); console.error('[Debug] Error in updateOffersTable:', error);
handleTableError(); handleTableError();
try {
const cachedOffers = CacheManager.get('offers_cached');
if (cachedOffers?.value) {
jsonData = cachedOffers.value;
updateOffersTable();
}
} catch (recoveryError) {
console.error('Recovery attempt failed:', recoveryError);
}
} }
} }
@ -1837,12 +1824,11 @@ function createOrderbookColumn(offer, coinFrom) {
} }
function createRateColumn(offer, coinFrom, coinTo) { function createRateColumn(offer, coinFrom, coinTo) {
const rate = parseFloat(offer.rate); const rate = parseFloat(offer.rate) || 0;
const inverseRate = 1 / rate; const inverseRate = rate ? (1 / rate) : 0;
const fromSymbol = getCoinSymbol(coinFrom);
const toSymbol = getCoinSymbol(coinTo);
const getPriceKey = (coin) => { const getPriceKey = (coin) => {
if (!coin) return null;
const lowerCoin = coin.toLowerCase(); const lowerCoin = coin.toLowerCase();
if (lowerCoin === 'firo' || lowerCoin === 'zcoin') { if (lowerCoin === 'firo' || lowerCoin === 'zcoin') {
return 'zcoin'; return 'zcoin';
@ -1857,13 +1843,15 @@ function createRateColumn(offer, coinFrom, coinTo) {
}; };
const toSymbolKey = getPriceKey(coinTo); const toSymbolKey = getPriceKey(coinTo);
let toPriceUSD = latestPrices[toSymbolKey]?.usd; let toPriceUSD = latestPrices && toSymbolKey ? latestPrices[toSymbolKey]?.usd : null;
if (!toPriceUSD || isNaN(toPriceUSD)) { if (!toPriceUSD || isNaN(toPriceUSD)) {
toPriceUSD = tableRateModule.getFallbackValue(toSymbolKey); toPriceUSD = tableRateModule.getFallbackValue(toSymbolKey);
} }
const rateInUSD = toPriceUSD && !isNaN(toPriceUSD) && !isNaN(rate) ? rate * toPriceUSD : null; const rateInUSD = toPriceUSD && !isNaN(toPriceUSD) && !isNaN(rate) ? rate * toPriceUSD : null;
const fromSymbol = getCoinSymbol(coinFrom);
const toSymbol = getCoinSymbol(coinTo);
return ` return `
<td class="py-3 semibold monospace text-xs text-right items-center rate-table-info"> <td class="py-3 semibold monospace text-xs text-right items-center rate-table-info">
@ -1884,6 +1872,7 @@ function createRateColumn(offer, coinFrom, coinTo) {
`; `;
} }
function createPercentageColumn(offer) { function createPercentageColumn(offer) {
return ` return `
<td class="py-3 px-2 bold text-sm text-center monospace items-center rate-table-info"> <td class="py-3 px-2 bold text-sm text-center monospace items-center rate-table-info">
@ -2710,95 +2699,83 @@ const timerManager = {
} }
}; };
// INITIALIZATION AND EVENT BINDING async function initializeTableAndData() {
document.addEventListener('DOMContentLoaded', () => { loadSavedSettings();
const saved = localStorage.getItem('offersTableSettings'); updateClearFiltersButton();
if (saved) { initializeTableEvents();
const settings = JSON.parse(saved); initializeTooltips();
updateCoinFilterImages();
['coin_to', 'coin_from', 'status', 'sent_from'].forEach(id => { try {
const element = document.getElementById(id); await fetchOffers();
if (element && settings[id]) element.value = settings[id]; applyFilters();
}); } catch (error) {
console.error('Error loading initial data:', error);
ui.displayErrorMessage('Error loading data. Retrying in background...');
}
}
function loadSavedSettings() {
const saved = localStorage.getItem('offersTableSettings');
if (saved) {
const settings = JSON.parse(saved);
['coin_to', 'coin_from', 'status', 'sent_from'].forEach(id => {
const element = document.getElementById(id);
if (element && settings[id]) element.value = settings[id];
});
if (settings.sortColumn !== undefined) { if (settings.sortColumn !== undefined) {
currentSortColumn = settings.sortColumn; currentSortColumn = settings.sortColumn;
currentSortDirection = settings.sortDirection; currentSortDirection = settings.sortDirection;
updateSortIndicators();
}
}
}
function updateSortIndicators() {
document.querySelectorAll('.sort-icon').forEach(icon => {
icon.classList.remove('text-blue-500');
icon.textContent = '↓';
});
document.querySelectorAll('.sort-icon').forEach(icon => { const sortIcon = document.getElementById(`sort-icon-${currentSortColumn}`);
icon.classList.remove('text-blue-500'); if (sortIcon) {
icon.textContent = '↓'; sortIcon.textContent = currentSortDirection === 'asc' ? '↑' : '↓';
}); sortIcon.classList.add('text-blue-500');
}
}
const sortIcon = document.getElementById(`sort-icon-${currentSortColumn}`); document.addEventListener('DOMContentLoaded', async () => {
if (sortIcon) { const tableLoadPromise = initializeTableAndData();
sortIcon.textContent = currentSortDirection === 'asc' ? '↑' : '↓';
sortIcon.classList.add('text-blue-500');
}
}
}
updateClearFiltersButton(); WebSocketManager.initialize();
initializeTableEvents();
initializeTooltips();
updateCoinFilterImages();
setTimeout(() => { await tableLoadPromise;
WebSocketManager.initialize();
}, 1000);
if (initializeTableRateModule()) { timerManager.addInterval(() => {
continueInitialization(); if (WebSocketManager.isConnected()) {
} else { console.log('🟢 WebSocket connection established');
let retryCount = 0; }
const maxRetries = 5; }, 30000);
const retryInterval = setInterval(() => {
retryCount++;
if (initializeTableRateModule()) {
clearInterval(retryInterval);
continueInitialization();
} else if (retryCount >= maxRetries) {
clearInterval(retryInterval);
continueInitialization();
}
}, 1000);
}
timerManager.addInterval(() => { timerManager.addInterval(() => {
if (WebSocketManager.isConnected()) { CacheManager.cleanup();
console.log('🟢 WebSocket connection one established'); }, 300000);
}
}, 30000);
timerManager.addInterval(() => { timerManager.addInterval(updateRowTimes, 900000);
CacheManager.cleanup();
}, 300000);
timerManager.addInterval(updateRowTimes, 900000); EventManager.add(document, 'visibilitychange', () => {
if (!document.hidden) {
if (!WebSocketManager.isConnected()) {
WebSocketManager.connect();
}
}
});
EventManager.add(document, 'visibilitychange', () => { EventManager.add(window, 'beforeunload', () => {
if (!document.hidden) { cleanup();
if (!WebSocketManager.isConnected()) { });
WebSocketManager.connect();
}
}
});
EventManager.add(window, 'beforeunload', () => {
cleanup();
});
updateCoinFilterImages();
fetchOffers().then(() => {
applyFilters();
if (!isSentOffers) {
return;
}
}).catch(error => {
console.error('Error fetching initial offers:', error);
});
}); });
async function cleanup() { async function cleanup() {