mirror of
https://github.com/basicswap/basicswap.git
synced 2025-04-24 23:18:22 +00:00
Merge pull request #262 from gerlofvanek/ws
JS: Fix websocket delay / loading tables faster.
This commit is contained in:
commit
8d317e4b67
1 changed files with 136 additions and 159 deletions
|
@ -75,6 +75,7 @@ let filterTimeout = null;
|
|||
// CONFIGURATION CONSTANTS
|
||||
// TIME CONSTANTS
|
||||
const CACHE_DURATION = 10 * 60 * 1000;
|
||||
const wsPort = config.port || window.ws_port || '11700';
|
||||
|
||||
// APP CONSTANTS
|
||||
const itemsPerPage = 50;
|
||||
|
@ -206,7 +207,7 @@ const WebSocketManager = {
|
|||
this.connect();
|
||||
}
|
||||
this.startHealthCheck();
|
||||
}, 1000);
|
||||
}, 0);
|
||||
},
|
||||
|
||||
startHealthCheck() {
|
||||
|
@ -1158,26 +1159,50 @@ async function fetchOffers() {
|
|||
const refreshText = document.getElementById('refreshText');
|
||||
|
||||
try {
|
||||
refreshButton.disabled = true;
|
||||
refreshIcon.classList.add('animate-spin');
|
||||
refreshText.textContent = 'Refreshing...';
|
||||
refreshButton.classList.add('opacity-75', 'cursor-wait');
|
||||
if (refreshButton) {
|
||||
refreshButton.disabled = true;
|
||||
refreshIcon.classList.add('animate-spin');
|
||||
refreshText.textContent = 'Refreshing...';
|
||||
refreshButton.classList.add('opacity-75', 'cursor-wait');
|
||||
}
|
||||
|
||||
const endpoint = isSentOffers ? '/json/sentoffers' : '/json/offers';
|
||||
const response = await fetch(endpoint);
|
||||
const data = await response.json();
|
||||
const [offersResponse, pricesData] = await Promise.all([
|
||||
fetch(isSentOffers ? '/json/sentoffers' : '/json/offers'),
|
||||
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];
|
||||
|
||||
latestPrices = pricesData || getEmptyPriceData();
|
||||
|
||||
await updateOffersTable();
|
||||
updatePaginationInfo();
|
||||
|
||||
} catch (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.');
|
||||
} 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();
|
||||
}
|
||||
|
||||
const PRICES_CACHE_KEY = 'prices_coingecko';
|
||||
const cachedPrices = CacheManager.get(PRICES_CACHE_KEY);
|
||||
latestPrices = cachedPrices?.value || getEmptyPriceData();
|
||||
|
||||
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) {
|
||||
handleNoOffersScenario();
|
||||
return;
|
||||
}
|
||||
|
||||
const totalPages = Math.max(1, Math.ceil(validOffers.length / itemsPerPage));
|
||||
currentPage = Math.min(currentPage, totalPages);
|
||||
|
||||
const existingRows = offersBody.querySelectorAll('tr');
|
||||
existingRows.forEach(row => {
|
||||
cleanupRow(row);
|
||||
});
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = Math.min(startIndex + itemsPerPage, validOffers.length);
|
||||
const itemsToDisplay = validOffers.slice(startIndex, endIndex);
|
||||
|
||||
const fragment = document.createDocumentFragment();
|
||||
itemsToDisplay.forEach((offer, index) => {
|
||||
const identity = identities[index];
|
||||
const row = createTableRow(offer, identity);
|
||||
if (row) {
|
||||
fragment.appendChild(row);
|
||||
}
|
||||
});
|
||||
|
||||
offersBody.textContent = '';
|
||||
offersBody.appendChild(fragment);
|
||||
const BATCH_SIZE = 10;
|
||||
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]');
|
||||
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'
|
||||
});
|
||||
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));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (offersBody) {
|
||||
const existingRows = offersBody.querySelectorAll('tr');
|
||||
existingRows.forEach(row => cleanupRow(row));
|
||||
offersBody.textContent = '';
|
||||
offersBody.appendChild(fragment);
|
||||
}
|
||||
|
||||
initializeTooltips();
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
updateRowTimes();
|
||||
updatePaginationControls(totalPages);
|
||||
updatePaginationControls(Math.ceil(validOffers.length / itemsPerPage));
|
||||
if (tableRateModule?.initializeTable) {
|
||||
tableRateModule.initializeTable();
|
||||
}
|
||||
|
@ -1550,16 +1547,6 @@ async function updateOffersTable() {
|
|||
} catch (error) {
|
||||
console.error('[Debug] Error in updateOffersTable:', error);
|
||||
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) {
|
||||
const rate = parseFloat(offer.rate);
|
||||
const inverseRate = 1 / rate;
|
||||
const fromSymbol = getCoinSymbol(coinFrom);
|
||||
const toSymbol = getCoinSymbol(coinTo);
|
||||
const rate = parseFloat(offer.rate) || 0;
|
||||
const inverseRate = rate ? (1 / rate) : 0;
|
||||
|
||||
const getPriceKey = (coin) => {
|
||||
if (!coin) return null;
|
||||
const lowerCoin = coin.toLowerCase();
|
||||
if (lowerCoin === 'firo' || lowerCoin === 'zcoin') {
|
||||
return 'zcoin';
|
||||
|
@ -1857,13 +1843,15 @@ function createRateColumn(offer, coinFrom, coinTo) {
|
|||
};
|
||||
|
||||
const toSymbolKey = getPriceKey(coinTo);
|
||||
let toPriceUSD = latestPrices[toSymbolKey]?.usd;
|
||||
let toPriceUSD = latestPrices && toSymbolKey ? latestPrices[toSymbolKey]?.usd : null;
|
||||
|
||||
if (!toPriceUSD || isNaN(toPriceUSD)) {
|
||||
toPriceUSD = tableRateModule.getFallbackValue(toSymbolKey);
|
||||
}
|
||||
|
||||
const rateInUSD = toPriceUSD && !isNaN(toPriceUSD) && !isNaN(rate) ? rate * toPriceUSD : null;
|
||||
const fromSymbol = getCoinSymbol(coinFrom);
|
||||
const toSymbol = getCoinSymbol(coinTo);
|
||||
|
||||
return `
|
||||
<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) {
|
||||
return `
|
||||
<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
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const saved = localStorage.getItem('offersTableSettings');
|
||||
if (saved) {
|
||||
const settings = JSON.parse(saved);
|
||||
async function initializeTableAndData() {
|
||||
loadSavedSettings();
|
||||
updateClearFiltersButton();
|
||||
initializeTableEvents();
|
||||
initializeTooltips();
|
||||
updateCoinFilterImages();
|
||||
|
||||
['coin_to', 'coin_from', 'status', 'sent_from'].forEach(id => {
|
||||
const element = document.getElementById(id);
|
||||
if (element && settings[id]) element.value = settings[id];
|
||||
});
|
||||
try {
|
||||
await fetchOffers();
|
||||
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) {
|
||||
currentSortColumn = settings.sortColumn;
|
||||
currentSortDirection = settings.sortDirection;
|
||||
if (settings.sortColumn !== undefined) {
|
||||
currentSortColumn = settings.sortColumn;
|
||||
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 => {
|
||||
icon.classList.remove('text-blue-500');
|
||||
icon.textContent = '↓';
|
||||
});
|
||||
const sortIcon = document.getElementById(`sort-icon-${currentSortColumn}`);
|
||||
if (sortIcon) {
|
||||
sortIcon.textContent = currentSortDirection === 'asc' ? '↑' : '↓';
|
||||
sortIcon.classList.add('text-blue-500');
|
||||
}
|
||||
}
|
||||
|
||||
const sortIcon = document.getElementById(`sort-icon-${currentSortColumn}`);
|
||||
if (sortIcon) {
|
||||
sortIcon.textContent = currentSortDirection === 'asc' ? '↑' : '↓';
|
||||
sortIcon.classList.add('text-blue-500');
|
||||
}
|
||||
}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const tableLoadPromise = initializeTableAndData();
|
||||
|
||||
updateClearFiltersButton();
|
||||
initializeTableEvents();
|
||||
initializeTooltips();
|
||||
updateCoinFilterImages();
|
||||
WebSocketManager.initialize();
|
||||
|
||||
setTimeout(() => {
|
||||
WebSocketManager.initialize();
|
||||
}, 1000);
|
||||
await tableLoadPromise;
|
||||
|
||||
if (initializeTableRateModule()) {
|
||||
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(() => {
|
||||
if (WebSocketManager.isConnected()) {
|
||||
console.log('🟢 WebSocket connection established');
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
timerManager.addInterval(() => {
|
||||
if (WebSocketManager.isConnected()) {
|
||||
console.log('🟢 WebSocket connection one established');
|
||||
}
|
||||
}, 30000);
|
||||
timerManager.addInterval(() => {
|
||||
CacheManager.cleanup();
|
||||
}, 300000);
|
||||
|
||||
timerManager.addInterval(() => {
|
||||
CacheManager.cleanup();
|
||||
}, 300000);
|
||||
timerManager.addInterval(updateRowTimes, 900000);
|
||||
|
||||
timerManager.addInterval(updateRowTimes, 900000);
|
||||
EventManager.add(document, 'visibilitychange', () => {
|
||||
if (!document.hidden) {
|
||||
if (!WebSocketManager.isConnected()) {
|
||||
WebSocketManager.connect();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
EventManager.add(document, 'visibilitychange', () => {
|
||||
if (!document.hidden) {
|
||||
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);
|
||||
});
|
||||
EventManager.add(window, 'beforeunload', () => {
|
||||
cleanup();
|
||||
});
|
||||
});
|
||||
|
||||
async function cleanup() {
|
||||
|
|
Loading…
Reference in a new issue