Merge pull request #262 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

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 BATCH_SIZE = 10;
const row = createTableRow(offer, identity); for (let i = 0; i < itemsToDisplay.length; i += BATCH_SIZE) {
if (row) { const batch = itemsToDisplay.slice(i, i + BATCH_SIZE);
fragment.appendChild(row);
const batchPromises = batch.map(offer =>
offer.addr_from ? IdentityManager.getIdentityData(offer.addr_from) : Promise.resolve(null)
);
const batchIdentities = await Promise.all(batchPromises);
batch.forEach((offer, index) => {
const row = createTableRow(offer, batchIdentities[index]);
if (row) fragment.appendChild(row);
});
if (i + BATCH_SIZE < itemsToDisplay.length) {
await new Promise(resolve => setTimeout(resolve, 16));
} }
}); }
offersBody.textContent = ''; if (offersBody) {
offersBody.appendChild(fragment); const existingRows = offersBody.querySelectorAll('tr');
existingRows.forEach(row => cleanupRow(row));
offersBody.textContent = '';
offersBody.appendChild(fragment);
}
const tooltipTriggers = offersBody.querySelectorAll('[data-tooltip-target]'); initializeTooltips();
tooltipTriggers.forEach(trigger => {
const targetId = trigger.getAttribute('data-tooltip-target');
const tooltipContent = document.getElementById(targetId);
if (tooltipContent) {
window.TooltipManager.create(trigger, tooltipContent.innerHTML, {
placement: trigger.getAttribute('data-tooltip-placement') || 'top'
});
}
});
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);
if (settings.sortColumn !== undefined) { ['coin_to', 'coin_from', 'status', 'sent_from'].forEach(id => {
currentSortColumn = settings.sortColumn; const element = document.getElementById(id);
currentSortDirection = settings.sortDirection; if (element && settings[id]) element.value = settings[id];
});
if (settings.sortColumn !== undefined) {
currentSortColumn = settings.sortColumn;
currentSortDirection = settings.sortDirection;
updateSortIndicators();
}
}
}
document.querySelectorAll('.sort-icon').forEach(icon => { function updateSortIndicators() {
icon.classList.remove('text-blue-500'); document.querySelectorAll('.sort-icon').forEach(icon => {
icon.textContent = '↓'; icon.classList.remove('text-blue-500');
}); icon.textContent = '↓';
});
const sortIcon = document.getElementById(`sort-icon-${currentSortColumn}`); const sortIcon = document.getElementById(`sort-icon-${currentSortColumn}`);
if (sortIcon) { if (sortIcon) {
sortIcon.textContent = currentSortDirection === 'asc' ? '↑' : '↓'; sortIcon.textContent = currentSortDirection === 'asc' ? '↑' : '↓';
sortIcon.classList.add('text-blue-500'); sortIcon.classList.add('text-blue-500');
} }
} }
}
updateClearFiltersButton(); document.addEventListener('DOMContentLoaded', async () => {
initializeTableEvents(); const tableLoadPromise = initializeTableAndData();
initializeTooltips();
updateCoinFilterImages();
setTimeout(() => { WebSocketManager.initialize();
WebSocketManager.initialize();
}, 1000);
if (initializeTableRateModule()) { await tableLoadPromise;
continueInitialization();
} else {
let retryCount = 0;
const maxRetries = 5;
const retryInterval = setInterval(() => {
retryCount++;
if (initializeTableRateModule()) {
clearInterval(retryInterval);
continueInitialization();
} else if (retryCount >= maxRetries) {
clearInterval(retryInterval);
continueInitialization();
}
}, 1000);
}
timerManager.addInterval(() => { timerManager.addInterval(() => {
if (WebSocketManager.isConnected()) { if (WebSocketManager.isConnected()) {
console.log('🟢 WebSocket connection one established'); console.log('🟢 WebSocket connection established');
} }
}, 30000); }, 30000);
timerManager.addInterval(() => { timerManager.addInterval(() => {
CacheManager.cleanup(); CacheManager.cleanup();
}, 300000); }, 300000);
timerManager.addInterval(updateRowTimes, 900000); timerManager.addInterval(updateRowTimes, 900000);
EventManager.add(document, 'visibilitychange', () => { EventManager.add(document, 'visibilitychange', () => {
if (!document.hidden) { if (!document.hidden) {
if (!WebSocketManager.isConnected()) { if (!WebSocketManager.isConnected()) {
WebSocketManager.connect(); WebSocketManager.connect();
} }
} }
}); });
EventManager.add(window, 'beforeunload', () => { EventManager.add(window, 'beforeunload', () => {
cleanup(); cleanup();
}); });
updateCoinFilterImages();
fetchOffers().then(() => {
applyFilters();
if (!isSentOffers) {
return;
}
}).catch(error => {
console.error('Error fetching initial offers:', error);
});
}); });
async function cleanup() { async function cleanup() {