diff --git a/basicswap/static/js/coin_icons.js b/basicswap/static/js/coin_icons.js index 7c03819..f8db7e7 100644 --- a/basicswap/static/js/coin_icons.js +++ b/basicswap/static/js/coin_icons.js @@ -14,7 +14,7 @@ document.addEventListener('DOMContentLoaded', () => { const image = selectedOption.getAttribute('data-image') || ''; const name = selectedOption.textContent.trim(); select.style.backgroundImage = image ? `url(${image}?${new Date().getTime()})` : ''; - + const selectImage = select.nextElementSibling.querySelector('.select-image'); if (selectImage) { selectImage.src = image; diff --git a/basicswap/static/js/main.js b/basicswap/static/js/main.js index 72291b2..38f6e4e 100644 --- a/basicswap/static/js/main.js +++ b/basicswap/static/js/main.js @@ -19,8 +19,8 @@ document.addEventListener('DOMContentLoaded', function() { const backdrop = document.querySelectorAll('.navbar-backdrop'); if (close.length) { - for (var i = 0; i < close.length; i++) { - close[i].addEventListener('click', function() { + for (var k = 0; k < close.length; k++) { + close[k].addEventListener('click', function() { for (var j = 0; j < menu.length; j++) { menu[j].classList.toggle('hidden'); } @@ -29,12 +29,12 @@ document.addEventListener('DOMContentLoaded', function() { } if (backdrop.length) { - for (var i = 0; i < backdrop.length; i++) { - backdrop[i].addEventListener('click', function() { + for (var l = 0; l < backdrop.length; l++) { + backdrop[l].addEventListener('click', function() { for (var j = 0; j < menu.length; j++) { menu[j].classList.toggle('hidden'); } }); } } -}); \ No newline at end of file +}); diff --git a/basicswap/static/js/new_offer.js b/basicswap/static/js/new_offer.js index 9472cc3..058e8dc 100644 --- a/basicswap/static/js/new_offer.js +++ b/basicswap/static/js/new_offer.js @@ -1,5 +1,5 @@ -window.addEventListener('DOMContentLoaded', (event) => { - let err_msgs = document.querySelectorAll('p.error_msg'); +window.addEventListener('DOMContentLoaded', () => { + const err_msgs = document.querySelectorAll('p.error_msg'); for (let i = 0; i < err_msgs.length; i++) { err_msg = err_msgs[i].innerText; if (err_msg.indexOf('coin_to') >= 0 || err_msg.indexOf('Coin To') >= 0) { @@ -29,9 +29,9 @@ window.addEventListener('DOMContentLoaded', (event) => { } // remove error class on input or select focus - let inputs = document.querySelectorAll('input.error'); - let selects = document.querySelectorAll('select.error'); - let elements = [...inputs, ...selects]; + const inputs = document.querySelectorAll('input.error'); + const selects = document.querySelectorAll('select.error'); + const elements = [...inputs, ...selects]; elements.forEach((element) => { element.addEventListener('focus', (event) => { event.target.classList.remove('error'); diff --git a/basicswap/static/js/offerstable.js b/basicswap/static/js/offerstable.js index 385b120..2652780 100644 --- a/basicswap/static/js/offerstable.js +++ b/basicswap/static/js/offerstable.js @@ -5,16 +5,16 @@ const EventManager = { if (!this.listeners.has(element)) { this.listeners.set(element, new Map()); } - + const elementListeners = this.listeners.get(element); if (!elementListeners.has(type)) { elementListeners.set(type, new Set()); } - + const handlerInfo = { handler, options }; elementListeners.get(type).add(handlerInfo); element.addEventListener(type, handler, options); - + return handlerInfo; }, @@ -74,14 +74,10 @@ let filterTimeout = null; // CONFIGURATION CONSTANTS // Time Constants -const MIN_REFRESH_INTERVAL = 60; // 60 sec const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes -const FALLBACK_CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours // Application Constants const itemsPerPage = 50; -const PRICE_INIT_RETRIES = 3; -const PRICE_INIT_RETRY_DELAY = 2000; const isSentOffers = window.offersTableConfig.isSentOffers; const offersConfig = { @@ -111,12 +107,6 @@ const coinNameToSymbol = { 'Bitcoin Cash': 'bitcoin-cash' }; -const symbolToCoinName = { - ...Object.fromEntries(Object.entries(coinNameToSymbol).map(([key, value]) => [value, key])), - 'zcoin': 'Firo', - 'firo': 'Firo' -}; - const coinNameToDisplayName = { 'Bitcoin': 'Bitcoin', 'Litecoin': 'Litecoin', @@ -152,76 +142,6 @@ const totalPagesSpan = document.getElementById('totalPages'); const lastRefreshTimeSpan = document.getElementById('lastRefreshTime'); const newEntriesCountSpan = document.getElementById('newEntriesCount'); -const ScrollOptimizer = { - scrollTimeout: null, - isScrolling: false, - tooltipCache: new WeakMap(), - - init() { - window.addEventListener('scroll', this.handleScroll.bind(this), { passive: true }); - - document.body.addEventListener('mouseenter', this.handleTooltipEnter.bind(this), true); - document.body.addEventListener('mouseleave', this.handleTooltipLeave.bind(this), true); - }, - - handleScroll() { - if (this.scrollTimeout) { - window.cancelAnimationFrame(this.scrollTimeout); - } - - if (!this.isScrolling) { - requestAnimationFrame(() => { - document.body.classList.add('is-scrolling'); - this.isScrolling = true; - }); - } - - this.scrollTimeout = window.requestAnimationFrame(() => { - document.body.classList.remove('is-scrolling'); - this.isScrolling = false; - }); - }, - - handleTooltipEnter(e) { - const tooltipTrigger = e.target.closest('[data-tooltip-target]'); - if (!tooltipTrigger) return; - - const tooltipId = tooltipTrigger.getAttribute('data-tooltip-target'); - let tooltip = this.tooltipCache.get(tooltipTrigger); - - if (!tooltip) { - tooltip = document.getElementById(tooltipId); - if (tooltip) { - this.tooltipCache.set(tooltipTrigger, tooltip); - } - } - - if (tooltip) { - tooltip.classList.remove('invisible', 'opacity-0'); - } - }, - - handleTooltipLeave(e) { - const tooltipTrigger = e.target.closest('[data-tooltip-target]'); - if (!tooltipTrigger) return; - - const tooltip = this.tooltipCache.get(tooltipTrigger); - if (tooltip) { - tooltip.classList.add('invisible', 'opacity-0'); - } - }, - - cleanup() { - if (this.scrollTimeout) { - window.cancelAnimationFrame(this.scrollTimeout); - } - window.removeEventListener('scroll', this.handleScroll); - document.body.removeEventListener('mouseenter', this.handleTooltipEnter); - document.body.removeEventListener('mouseleave', this.handleTooltipLeave); - this.tooltipCache = null; - } -}; - // MANAGER OBJECTS const WebSocketManager = { ws: null, @@ -259,7 +179,7 @@ const WebSocketManager = { this.handlePageVisible(); } }; - + document.addEventListener('visibilitychange', this.handlers.visibilityChange); }, @@ -550,8 +470,8 @@ const CacheManager = { try { localStorage.setItem(key, JSON.stringify(item)); return true; - } catch (retryError) { - //console.error('Storage quota exceeded even after cleanup'); + } catch (error) { + console.error('Storage quota exceeded even after cleanup:', error.message); return false; } } @@ -577,6 +497,7 @@ const CacheManager = { localStorage.removeItem(key); } catch (error) { + console.error("An error occured:", error.message); localStorage.removeItem(key); } return null; @@ -612,6 +533,7 @@ const CacheManager = { totalSize += size; itemCount++; } catch (error) { + console.error("An error occured:", error.message); localStorage.removeItem(key); } } @@ -662,6 +584,7 @@ const CacheManager = { expiredCount++; } } catch (error) { + console.error("An error occured:", error.message); } } @@ -721,14 +644,15 @@ const IdentityManager = { const response = await fetch(`/json/identities/${address}`, { signal: AbortSignal.timeout(5000) }); - + if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } - + return await response.json(); } catch (error) { if (attempt >= this.maxRetries) { + console.error("An error occured:", error.message); console.warn(`Failed to fetch identity for ${address} after ${attempt} attempts`); return null; } @@ -856,10 +780,6 @@ window.tableRateModule = { }; // CORE SYSTEM FUNCTIONS -function initializeWebSocket() { - return WebSocketManager.initialize(); -} - function initializeTableRateModule() { if (typeof window.tableRateModule !== 'undefined') { tableRateModule = window.tableRateModule; @@ -871,53 +791,12 @@ function initializeTableRateModule() { } } -async function initializePriceData() { - //console.log('Initializing price data...'); - let retryCount = 0; - let prices = null; - - const PRICES_CACHE_KEY = 'prices_coingecko'; - const cachedPrices = CacheManager.get(PRICES_CACHE_KEY); - if (cachedPrices && cachedPrices.value) { - console.log('Using cached price data'); - latestPrices = cachedPrices.value; - return true; - } - - while (retryCount < PRICE_INIT_RETRIES) { - try { - prices = await fetchLatestPrices(); - - if (prices && Object.keys(prices).length > 0) { - console.log('Successfully fetched initial price data'); - latestPrices = prices; - CacheManager.set(PRICES_CACHE_KEY, prices, CACHE_DURATION); - return true; - } - - retryCount++; - - if (retryCount < PRICE_INIT_RETRIES) { - await new Promise(resolve => setTimeout(resolve, PRICE_INIT_RETRY_DELAY)); - } - } catch (error) { - console.error(`Error fetching prices (attempt ${retryCount + 1}):`, error); - retryCount++; - - if (retryCount < PRICE_INIT_RETRIES) { - await new Promise(resolve => setTimeout(resolve, PRICE_INIT_RETRY_DELAY)); - } - } - } - - return false; -} - function continueInitialization() { updateCoinFilterImages(); fetchOffers().then(() => { applyFilters(); if (!isSentOffers) { + return; } }); @@ -928,32 +807,6 @@ function continueInitialization() { //console.log('Initialization completed'); } -function checkOfferAgainstFilters(offer, filters) { - if (filters.coin_to !== 'any' && !coinMatches(offer.coin_to, filters.coin_to)) { - return false; - } - if (filters.coin_from !== 'any' && !coinMatches(offer.coin_from, filters.coin_from)) { - return false; - } - if (filters.status && filters.status !== 'any') { - const currentTime = Math.floor(Date.now() / 1000); - const isExpired = offer.expire_at <= currentTime; - const isRevoked = Boolean(offer.is_revoked); - - switch (filters.status) { - case 'active': - return !isExpired && !isRevoked; - case 'expired': - return isExpired && !isRevoked; - case 'revoked': - return isRevoked; - default: - return true; - } - } - return true; -} - function initializeFlowbiteTooltips() { if (typeof Tooltip === 'undefined') { console.warn('Tooltip is not defined. Make sure the required library is loaded.'); @@ -971,65 +824,6 @@ function initializeFlowbiteTooltips() { } // DATA PROCESSING FUNCTIONS -async function checkExpiredAndFetchNew() { - if (isSentOffers) return Promise.resolve(); - - console.log('Starting checkExpiredAndFetchNew'); - const OFFERS_CACHE_KEY = 'offers_received'; - - try { - const response = await fetch('/json/offers'); - const data = await response.json(); - let newListings = Array.isArray(data) ? data : Object.values(data); - - newListings = newListings.map(offer => ({ - ...offer, - offer_id: String(offer.offer_id || ''), - swap_type: String(offer.swap_type || 'N/A'), - addr_from: String(offer.addr_from || ''), - coin_from: String(offer.coin_from || ''), - coin_to: String(offer.coin_to || ''), - amount_from: String(offer.amount_from || '0'), - amount_to: String(offer.amount_to || '0'), - rate: String(offer.rate || '0'), - created_at: Number(offer.created_at || 0), - expire_at: Number(offer.expire_at || 0), - is_own_offer: Boolean(offer.is_own_offer), - amount_negotiable: Boolean(offer.amount_negotiable), - unique_id: `${offer.offer_id}_${offer.created_at}_${offer.coin_from}_${offer.coin_to}` - })); - - newListings = newListings.filter(offer => !isOfferExpired(offer)); - originalJsonData = newListings; - - CacheManager.set(OFFERS_CACHE_KEY, newListings, CACHE_DURATION); - - const currentFilters = new FormData(filterForm); - const hasActiveFilters = currentFilters.get('coin_to') !== 'any' || - currentFilters.get('coin_from') !== 'any'; - - if (hasActiveFilters) { - jsonData = filterAndSortData(); - } else { - jsonData = [...newListings]; - } - - updateOffersTable(); - updateJsonView(); - updatePaginationInfo(); - - if (jsonData.length === 0) { - handleNoOffersScenario(); - } - - return jsonData.length; - } catch (error) { - //console.error('Error fetching new listings:', error); - nextRefreshCountdown = 60; - return Promise.reject(error); - } -} - function getValidOffers() { if (!jsonData) { //console.warn('jsonData is undefined or null'); @@ -1182,7 +976,7 @@ async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwn const fromPriceUSD = latestPrices[fromSymbol]?.usd; const toPriceUSD = latestPrices[toSymbol]?.usd; - if (fromPriceUSD === null || toPriceUSD === null || + if (fromPriceUSD === null || toPriceUSD === null || fromPriceUSD === undefined || toPriceUSD === undefined) { resolve(null); return; @@ -1202,42 +996,6 @@ async function calculateProfitLoss(fromCoin, toCoin, fromAmount, toAmount, isOwn }); } -async function getMarketRate(fromCoin, toCoin) { - return new Promise((resolve) => { - //console.log(`Attempting to get market rate for ${fromCoin} to ${toCoin}`); - if (!latestPrices) { - //console.warn('Latest prices object is not available'); - resolve(null); - return; - } - - const getPriceKey = (coin) => { - const lowerCoin = coin.toLowerCase(); - if (lowerCoin === 'firo' || lowerCoin === 'zcoin') { - return 'zcoin'; - } - if (lowerCoin === 'bitcoin cash') { - return 'bitcoin-cash'; - } - return coinNameToSymbol[coin] || lowerCoin; - }; - - const fromSymbol = getPriceKey(fromCoin); - const toSymbol = getPriceKey(toCoin); - - const fromPrice = latestPrices[fromSymbol]?.usd; - const toPrice = latestPrices[toSymbol]?.usd; - if (!fromPrice || !toPrice) { - //console.warn(`Missing price data for ${!fromPrice ? fromCoin : toCoin}`); - resolve(null); - return; - } - const rate = toPrice / fromPrice; - //console.log(`Market rate calculated: ${rate} ${toCoin}/${fromCoin}`); - resolve(rate); - }); -} - function getEmptyPriceData() { return { 'bitcoin': { usd: null, btc: null }, @@ -1259,7 +1017,7 @@ async function fetchLatestPrices() { const PRICES_CACHE_KEY = 'prices_coingecko'; const RETRY_DELAY = 5000; const MAX_RETRIES = 3; - + const cachedData = CacheManager.get(PRICES_CACHE_KEY); if (cachedData && cachedData.remainingTime > 30000) { console.log('Using cached price data'); @@ -1268,7 +1026,7 @@ async function fetchLatestPrices() { } const baseUrl = `${offersConfig.apiEndpoints.coinGecko}/simple/price?ids=bitcoin,bitcoin-cash,dash,dogecoin,decred,litecoin,particl,pivx,monero,zcoin,zano,wownero&vs_currencies=USD,BTC`; - + let retryCount = 0; let data = null; @@ -1282,7 +1040,7 @@ async function fetchLatestPrices() { try { console.log('Attempting price fetch with API key...'); const urlWithKey = `${baseUrl}&api_key=${offersConfig.apiKeys.coinGecko}`; - + const response = await fetch('/json/readurl', { method: 'POST', headers: { @@ -1306,9 +1064,9 @@ async function fetchLatestPrices() { continue; } - const hasValidPrices = Object.values(responseData).some(coin => - coin && typeof coin === 'object' && - typeof coin.usd === 'number' && + const hasValidPrices = Object.values(responseData).some(coin => + coin && typeof coin === 'object' && + typeof coin.usd === 'number' && !isNaN(coin.usd) ); @@ -1343,11 +1101,11 @@ async function fetchLatestPrices() { tableRateModule.setFallbackValue(coin, prices.usd); } }); - + return data; } -async function fetchOffers(manualRefresh = false) { +async function fetchOffers() { const refreshButton = document.getElementById('refreshOffers'); const refreshIcon = document.getElementById('refreshIcon'); const refreshText = document.getElementById('refreshText'); @@ -1557,7 +1315,7 @@ function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffe } }) .catch(error => { - //console.error('Error in updateProfitLoss:', error); + console.error('Error in updateProfitLoss:', error); profitLossElement.textContent = 'Error'; profitLossElement.className = 'profit-loss text-lg font-bold text-red-500'; }); @@ -1605,7 +1363,7 @@ function updateClearFiltersButton() { function cleanupRow(row) { EventManager.removeAll(row); - + const tooltips = row.querySelectorAll('[data-tooltip-target]'); tooltips.forEach(tooltip => { const tooltipId = tooltip.getAttribute('data-tooltip-target'); @@ -1618,7 +1376,7 @@ function cleanupRow(row) { function cleanupTable() { EventManager.clearAll(); - + if (offersBody) { const existingRows = offersBody.querySelectorAll('tr'); existingRows.forEach(row => { @@ -1686,13 +1444,13 @@ async function updateOffersTable() { 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); @@ -1789,10 +1547,6 @@ function handleTableError() { </tr>`; } -async function getIdentityData(address) { - return IdentityManager.getIdentityData(address); -} - function getIdentityInfo(address, identity) { if (!identity) { return { @@ -1928,10 +1682,6 @@ function createTimeColumn(offer, postedTime, expiresIn) { `; } -function shouldShowPublicTag(offers) { - return offers.some(offer => !offer.is_public); -} - function truncateText(text, maxLength = 15) { if (typeof text !== 'string') return ''; return text.length > maxLength @@ -1977,7 +1727,7 @@ function createDetailsColumn(offer, identity = null) { `; } -function createTakerAmountColumn(offer, coinTo, coinFrom) { +function createTakerAmountColumn(offer, coinTo) { const fromAmount = parseFloat(offer.amount_to); const toSymbol = getCoinSymbol(coinTo); return ` @@ -2021,7 +1771,7 @@ function createSwapColumn(offer, coinFromDisplay, coinToDisplay, coinFromSymbol, `; } -function createOrderbookColumn(offer, coinFrom, coinTo) { +function createOrderbookColumn(offer, coinFrom) { const toAmount = parseFloat(offer.amount_from); const fromSymbol = getCoinSymbol(coinFrom); return ` @@ -2055,7 +1805,6 @@ function createRateColumn(offer, coinFrom, coinTo) { return coinNameToSymbol[coin] || lowerCoin; }; - const fromPriceUSD = latestPrices[getPriceKey(coinFrom)]?.usd || 0; const toPriceUSD = latestPrices[getPriceKey(coinTo)]?.usd || 0; const rateInUSD = rate * toPriceUSD; @@ -2126,9 +1875,6 @@ function createActionColumn(offer, isActuallyExpired = false) { // TOOLTIP FUNCTIONS function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, toAmount, postedTime, expiresIn, isActuallyExpired, isRevoked, identity = null) { - const rate = parseFloat(offer.rate); - const fromSymbol = getCoinSymbolLowercase(coinFrom); - const toSymbol = getCoinSymbolLowercase(coinTo); const uniqueId = `${offer.offer_id}_${offer.created_at}`; const addrFrom = offer.addr_from || ''; @@ -2147,10 +1893,6 @@ function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, t ((identityInfo.stats.sentBidsSuccessful + identityInfo.stats.recvBidsSuccessful) / totalBids) * 100 ).toFixed(1) : 0; - const fromPriceUSD = latestPrices[fromSymbol]?.usd || 0; - const toPriceUSD = latestPrices[toSymbol]?.usd || 0; - const rateInUSD = rate * toPriceUSD; - const combinedRateTooltip = createCombinedRateTooltip(offer, coinFrom, coinTo, treatAsSentOffer); const percentageTooltipContent = createTooltipContent(treatAsSentOffer, coinFrom, coinTo, fromAmount, toAmount); @@ -2329,8 +2071,8 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou const getPriceKey = (coin) => { const lowerCoin = coin.toLowerCase(); - return lowerCoin === 'firo' || lowerCoin === 'zcoin' ? 'zcoin' : - lowerCoin === 'bitcoin cash' ? 'bitcoin-cash' : + return lowerCoin === 'firo' || lowerCoin === 'zcoin' ? 'zcoin' : + lowerCoin === 'bitcoin cash' ? 'bitcoin-cash' : lowerCoin === 'particl anon' || lowerCoin === 'particl blind' ? 'particl' : coinNameToSymbol[coin] || lowerCoin; }; @@ -2340,7 +2082,7 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou const fromPriceUSD = latestPrices[fromSymbol]?.usd; const toPriceUSD = latestPrices[toSymbol]?.usd; - if (fromPriceUSD === null || toPriceUSD === null || + if (fromPriceUSD === null || toPriceUSD === null || fromPriceUSD === undefined || toPriceUSD === undefined) { return `<p class="font-bold mb-1">Price Information Unavailable</p> <p>Current market prices are temporarily unavailable.</p> @@ -2421,7 +2163,7 @@ function createCombinedRateTooltip(offer, coinFrom, coinTo, treatAsSentOffer) { const fromPriceUSD = latestPrices[fromSymbol]?.usd; const toPriceUSD = latestPrices[toSymbol]?.usd; - if (fromPriceUSD === null || toPriceUSD === null || + if (fromPriceUSD === null || toPriceUSD === null || fromPriceUSD === undefined || toPriceUSD === undefined) { return ` <p class="font-bold mb-1">Exchange Rate Information</p> @@ -2526,13 +2268,6 @@ function clearFilters() { } function hasActiveFilters() { - const formData = new FormData(filterForm); - const filters = { - coin_to: formData.get('coin_to'), - coin_from: formData.get('coin_from'), - status: formData.get('status') - }; - const selectElements = filterForm.querySelectorAll('select'); let hasChangedFilters = false; @@ -2622,20 +2357,6 @@ function isOfferExpired(offer) { return isExpired; } -function getTimeUntilNextExpiration() { - const currentTime = Math.floor(Date.now() / 1000); - const nextExpiration = jsonData.reduce((earliest, offer) => { - const timeUntilExpiration = offer.expire_at - currentTime; - return timeUntilExpiration > 0 && timeUntilExpiration < earliest ? timeUntilExpiration : earliest; - }, Infinity); - - return Math.max(MIN_REFRESH_INTERVAL, Math.min(nextExpiration, 300)); -} - -function calculateInverseRate(rate) { - return (1 / parseFloat(rate)).toFixed(8); -} - function formatTime(timestamp, addAgoSuffix = false) { const now = Math.floor(Date.now() / 1000); const diff = Math.abs(now - timestamp); @@ -2796,47 +2517,6 @@ function initializeTableEvents() { } } -const eventListeners = { - listeners: [], - - add(element, eventType, handler, options = false) { - element.addEventListener(eventType, handler, options); - this.listeners.push({ element, eventType, handler, options }); - // console.log(`Added ${eventType} listener to`, element); - }, - - addWindowListener(eventType, handler, options = false) { - window.addEventListener(eventType, handler, options); - this.listeners.push({ element: window, eventType, handler, options }); - // console.log(`Added ${eventType} window listener`); - }, - - removeAll() { - console.log('Removing all event listeners...'); - this.listeners.forEach(({ element, eventType, handler, options }) => { - element.removeEventListener(eventType, handler, options); - //console.log(`Removed ${eventType} listener from`, element); - }); - this.listeners = []; - }, - - removeByElement(element) { - const remainingListeners = []; - this.listeners = this.listeners.filter(listener => { - if (listener.element === element) { - listener.element.removeEventListener( - listener.eventType, - listener.handler, - listener.options - ); - console.log(`✂️ Removed ${listener.eventType} listener from`, element); - return false; - } - return true; - }); - }, -}; - function handleTableSort(columnIndex, header) { if (currentSortColumn === columnIndex) { currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc'; @@ -2870,27 +2550,6 @@ function handleTableSort(columnIndex, header) { applyFilters(); } -function setupRowEventListeners(row, offer) { - const tooltipTriggers = row.querySelectorAll('[data-tooltip-target]'); - tooltipTriggers.forEach(trigger => { - EventManager.add(trigger, 'mouseenter', () => { - const tooltipId = trigger.getAttribute('data-tooltip-target'); - const tooltip = document.getElementById(tooltipId); - if (tooltip) { - tooltip.classList.remove('invisible', 'opacity-0'); - } - }); - - EventManager.add(trigger, 'mouseleave', () => { - const tooltipId = trigger.getAttribute('data-tooltip-target'); - const tooltip = document.getElementById(tooltipId); - if (tooltip) { - tooltip.classList.add('invisible', 'opacity-0'); - } - }); - }); -} - // TIMER MANAGEMENT const timerManager = { intervals: [], @@ -3015,7 +2674,7 @@ async function cleanup() { console.log(`Total cleanup time: ${totalTime}ms`); console.log('Steps completed:', this.steps.length); console.log('Errors encountered:', this.errors.length); - + if (this.steps.length > 0) { console.group('Steps Timeline'); this.steps.forEach(({step, time}) => { @@ -3023,7 +2682,7 @@ async function cleanup() { }); console.groupEnd(); } - + if (this.errors.length > 0) { console.group('Errors'); this.errors.forEach(({step, error, time}) => { diff --git a/basicswap/static/js/pricechart.js b/basicswap/static/js/pricechart.js index 889df3d..a4c63f2 100644 --- a/basicswap/static/js/pricechart.js +++ b/basicswap/static/js/pricechart.js @@ -40,9 +40,9 @@ const config = { // UTILS const utils = { - formatNumber: (number, decimals = 2) => + formatNumber: (number, decimals = 2) => number.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ','), - + formatDate: (timestamp, resolution) => { const date = new Date(timestamp); const options = { @@ -80,7 +80,7 @@ const logger = { // API const api = { makePostRequest: (url, headers = {}) => { - const apiKeys = getAPIKeys(); + // unused // const apiKeys = getAPIKeys(); return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.open('POST', '/json/readurl'); @@ -114,7 +114,7 @@ const api = { })); }); }, - + fetchCryptoCompareDataXHR: (coin) => { const url = `${config.apiEndpoints.cryptoCompare}?fsyms=${coin}&tsyms=USD,BTC&api_key=${config.apiKeys.cryptoCompare}`; const headers = { @@ -126,10 +126,10 @@ const api = { error: error.message })); }, - + fetchCoinGeckoDataXHR: async () => { const cacheKey = 'coinGeckoOneLiner'; - let cachedData = cache.get(cacheKey); + const cachedData = cache.get(cacheKey); if (cachedData) { console.log('Using cached CoinGecko data'); @@ -143,15 +143,15 @@ const api = { 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}`; //console.log(`Fetching data for multiple coins from CoinGecko: ${url}`); - + try { const data = await api.makePostRequest(url); //console.log(`Raw CoinGecko data:`, data); - + if (typeof data !== 'object' || data === null) { throw new AppError(`Invalid data structure received from CoinGecko`); } - + const transformedData = {}; Object.entries(data).forEach(([id, values]) => { const coinConfig = config.coins.find(coin => coin.name === id); @@ -164,7 +164,7 @@ const api = { displayName: coinConfig?.displayName || coinConfig?.symbol || id }; }); - + //console.log(`Transformed CoinGecko data:`, transformedData); cache.set(cacheKey, transformedData); return transformedData; @@ -202,7 +202,7 @@ const api = { //console.error(`Unexpected data structure for WOW:`, response); } } catch (error) { - //console.error(`Error fetching CoinGecko data for WOW:`, error); + console.error(`Error fetching CoinGecko data for WOW:`, error); } } else { const resolution = config.resolutions[config.currentResolution]; @@ -225,7 +225,7 @@ const api = { //console.error(`Unexpected data structure for ${coin}:`, response); } } catch (error) { - //console.error(`Error fetching CryptoCompare data for ${coin}:`, error); + console.error(`Error fetching CryptoCompare data for ${coin}:`, error); } } }); @@ -266,8 +266,8 @@ const cache = { //console.log(`Cache expired for ${key}`); localStorage.removeItem(key); } - } catch (e) { - //console.error('Error parsing cache item:', e); + } catch (error) { + console.error('Error parsing cache item:', error.message); localStorage.removeItem(key); } return null; @@ -288,7 +288,6 @@ const cache = { // UI const ui = { displayCoinData: (coin, data) => { - const coinConfig = config.coins.find(c => c.symbol === coin); let priceUSD, priceBTC, priceChange1d, volume24h; const updateUI = (isError = false) => { const priceUsdElement = document.querySelector(`#${coin.toLowerCase()}-price-usd`); @@ -296,16 +295,16 @@ displayCoinData: (coin, data) => { const volumeElement = document.querySelector(`#${coin.toLowerCase()}-volume-24h`); const btcPriceDiv = document.querySelector(`#${coin.toLowerCase()}-btc-price-div`); const priceBtcElement = document.querySelector(`#${coin.toLowerCase()}-price-btc`); - + if (priceUsdElement) { priceUsdElement.textContent = isError ? 'N/A' : `$ ${ui.formatPrice(coin, priceUSD)}`; } - + if (volumeDiv && volumeElement) { volumeElement.textContent = isError ? 'N/A' : `${utils.formatNumber(volume24h, 0)} USD`; volumeDiv.style.display = volumeToggle.isVisible ? 'flex' : 'none'; } - + if (btcPriceDiv && priceBtcElement) { if (coin === 'BTC') { btcPriceDiv.style.display = 'none'; @@ -314,10 +313,10 @@ displayCoinData: (coin, data) => { btcPriceDiv.style.display = 'flex'; } } - + ui.updatePriceChangeContainer(coin, isError ? null : priceChange1d); }; - + try { if (data.error) { throw new Error(data.error); @@ -325,51 +324,51 @@ displayCoinData: (coin, data) => { if (!data || !data.current_price) { throw new Error(`Invalid CoinGecko data structure for ${coin}`); } - + priceUSD = data.current_price; priceBTC = coin === 'BTC' ? 1 : data.price_btc || (data.current_price / app.btcPriceUSD); priceChange1d = data.price_change_percentage_24h; volume24h = data.total_volume; - + if (isNaN(priceUSD) || isNaN(priceBTC) || isNaN(volume24h)) { throw new Error(`Invalid numeric values in data for ${coin}`); } - + updateUI(false); } catch (error) { - //console.error(`Error displaying data for ${coin}:`, error.message); + console.error(`Error displaying data for ${coin}:`, error.message); updateUI(true); } }, - + showLoader: () => { const loader = document.getElementById('loader'); if (loader) { loader.classList.remove('hidden'); } }, - + hideLoader: () => { const loader = document.getElementById('loader'); if (loader) { loader.classList.add('hidden'); } }, - + showCoinLoader: (coinSymbol) => { const loader = document.getElementById(`${coinSymbol.toLowerCase()}-loader`); if (loader) { loader.classList.remove('hidden'); } }, - + hideCoinLoader: (coinSymbol) => { const loader = document.getElementById(`${coinSymbol.toLowerCase()}-loader`); if (loader) { loader.classList.add('hidden'); } }, - + updateCacheStatus: (isCached) => { const cacheStatusElement = document.getElementById('cache-status'); if (cacheStatusElement) { @@ -378,15 +377,15 @@ displayCoinData: (coin, data) => { cacheStatusElement.classList.toggle('text-blue-500', !isCached); } }, - + updateLoadTimeAndCache: (loadTime, cachedData) => { const loadTimeElement = document.getElementById('load-time'); const cacheStatusElement = document.getElementById('cache-status'); - + if (loadTimeElement) { loadTimeElement.textContent = `Load time: ${loadTime}ms`; } - + if (cacheStatusElement) { if (cachedData && cachedData.remainingTime) { const remainingMinutes = Math.ceil(cachedData.remainingTime / 60000); @@ -402,7 +401,7 @@ displayCoinData: (coin, data) => { ui.updateLastRefreshedTime(); }, - + updatePriceChangeContainer: (coin, priceChange) => { const container = document.querySelector(`#${coin.toLowerCase()}-price-change-container`); if (container) { @@ -411,7 +410,7 @@ displayCoinData: (coin, data) => { 'N/A'; } }, - + updateLastRefreshedTime: () => { const lastRefreshedElement = document.getElementById('last-refreshed-time'); if (lastRefreshedElement && app.lastRefreshedTime) { @@ -419,7 +418,7 @@ displayCoinData: (coin, data) => { lastRefreshedElement.textContent = `Last Refreshed: ${formattedTime}`; } }, - + positivePriceChangeHTML: (value) => ` <div class="flex flex-wrap items-center py-px px-1 border border-green-500 rounded-full"> <svg class="mr-0.5" width="15" height="10" viewBox="0 0 15 10" fill="none" xmlns="http://www.w3.org/2000/svg"> @@ -428,7 +427,7 @@ displayCoinData: (coin, data) => { <span class="text-xs text-green-500 font-medium">${value.toFixed(2)}%</span> </div> `, - + negativePriceChangeHTML: (value) => ` <div class="flex flex-wrap items-center py-px px-1 border border-red-500 rounded-full"> <svg class="mr-0.5" width="14" height="10" viewBox="0 0 14 10" fill="none" xmlns="http://www.w3.org/2000/svg"> @@ -437,7 +436,7 @@ displayCoinData: (coin, data) => { <span class="text-xs text-red-500 font-medium">${Math.abs(value).toFixed(2)}%</span> </div> `, - + formatPrice: (coin, price) => { if (typeof price !== 'number' || isNaN(price)) { logger.error(`Invalid price for ${coin}:`, price); @@ -449,7 +448,7 @@ displayCoinData: (coin, data) => { if (price < 1000) return price.toFixed(2); return price.toFixed(1); }, - + setActiveContainer: (containerId) => { const containerIds = ['btc', 'xmr', 'part', 'pivx', 'firo', 'dash', 'ltc', 'doge', 'eth', 'dcr', 'zano', 'wow', 'bch'].map(id => `${id}-container`); containerIds.forEach(id => { @@ -460,7 +459,7 @@ displayCoinData: (coin, data) => { } }); }, - + displayErrorMessage: (message) => { const errorOverlay = document.getElementById('error-overlay'); const errorMessage = document.getElementById('error-message'); @@ -471,7 +470,7 @@ displayCoinData: (coin, data) => { chartContainer.classList.add('blurred'); } }, - + hideErrorMessage: () => { const errorOverlay = document.getElementById('error-overlay'); const containersToBlur = document.querySelectorAll('.container-to-blur'); @@ -487,7 +486,7 @@ const chartModule = { chart: null, currentCoin: 'BTC', loadStartTime: 0, - + cleanup: () => { if (chartModule.chart) { chartModule.chart.destroy(); @@ -675,7 +674,7 @@ const chartModule = { plugins: [chartModule.verticalLinePlugin] }); }, - + prepareChartData: (coinSymbol, data) => { if (!data) { return []; @@ -689,13 +688,13 @@ const chartModule = { endTime.setUTCMinutes(0, 0, 0); const endUnix = endTime.getTime(); const startUnix = endUnix - (24 * 3600000); - + const hourlyPoints = []; - + for (let hourUnix = startUnix; hourUnix <= endUnix; hourUnix += 3600000) { const targetHour = new Date(hourUnix); targetHour.setUTCMinutes(0, 0, 0); - + const closestPoint = data.reduce((prev, curr) => { const prevTime = new Date(prev[0]); const currTime = new Date(curr[0]); @@ -744,6 +743,7 @@ const chartModule = { y: point.y })); } catch (error) { + console.error("An error occured:", error.message); return []; } }, @@ -756,11 +756,11 @@ const chartModule = { for (let i = 0; i < 24; i++) { const targetTime = new Date(twentyFourHoursAgo.getTime() + i * 60 * 60 * 1000); - const closestDataPoint = data.reduce((prev, curr) => - Math.abs(new Date(curr.x).getTime() - targetTime.getTime()) < + const closestDataPoint = data.reduce((prev, curr) => + Math.abs(new Date(curr.x).getTime() - targetTime.getTime()) < Math.abs(new Date(prev.x).getTime() - targetTime.getTime()) ? curr : prev ); - + hourlyData.push({ x: targetTime.getTime(), y: closestDataPoint.y @@ -774,11 +774,11 @@ const chartModule = { try { chartModule.showChartLoader(); chartModule.loadStartTime = Date.now(); - + const cacheKey = `chartData_${coinSymbol}_${config.currentResolution}`; let cachedData = !forceRefresh ? cache.get(cacheKey) : null; let data; - + if (cachedData && Object.keys(cachedData.value).length > 0) { data = cachedData.value; } else { @@ -807,7 +807,7 @@ const chartModule = { } else { const resolution = config.resolutions[config.currentResolution]; chartModule.chart.options.scales.x.time.unit = resolution.interval === 'hourly' ? 'hour' : 'day'; - + if (config.currentResolution === 'year' || config.currentResolution === 'sixMonths') { chartModule.chart.options.scales.x.time.unit = 'month'; } @@ -840,25 +840,25 @@ const chartModule = { showChartLoader: () => { const loader = document.getElementById('chart-loader'); const chart = document.getElementById('coin-chart'); - + if (!loader || !chart) { return; } - + loader.classList.remove('hidden'); chart.classList.add('hidden'); }, - + hideChartLoader: () => { const loader = document.getElementById('chart-loader'); const chart = document.getElementById('coin-chart'); - + if (!loader || !chart) { return; } - + loader.classList.add('hidden'); - chart.classList.remove('hidden'); + chart.classList.remove('hidden'); } }; @@ -866,7 +866,7 @@ Chart.register(chartModule.verticalLinePlugin); const volumeToggle = { isVisible: localStorage.getItem('volumeToggleState') === 'true', - + cleanup: () => { const toggleButton = document.getElementById('toggle-volume'); if (toggleButton) { @@ -876,7 +876,7 @@ const volumeToggle = { init: () => { volumeToggle.cleanup(); - + const toggleButton = document.getElementById('toggle-volume'); if (toggleButton) { toggleButton.addEventListener('click', volumeToggle.toggle); @@ -942,7 +942,7 @@ const app = { app.visibilityCleanup(); app.visibilityCleanup = null; } - + volumeToggle.cleanup(); app.removeEventListeners(); @@ -980,11 +980,11 @@ const app = { button.removeEventListener('click', window[oldListener]); delete window[oldListener]; } - + const listener = () => { const resolution = button.id.split('-')[1]; const currentCoin = chartModule.currentCoin; - + if (currentCoin !== 'WOW' || resolution === 'day') { config.currentResolution = resolution; chartModule.updateChart(currentCoin, true); @@ -1041,22 +1041,23 @@ const app = { chartModule.initChart(); chartModule.showChartLoader(); } - + console.log('Loading all coin data...'); await app.loadAllCoinData(); - + if (chartModule.chart) { config.currentResolution = 'day'; await chartModule.updateChart('BTC'); app.updateResolutionButtons('BTC'); } ui.setActiveContainer('btc-container'); - + app.setupEventListeners(); app.initializeSelectImages(); app.initAutoRefresh(); - + } catch (error) { + console.error("An error occured:", error.message); ui.displayErrorMessage('Failed to initialize the dashboard. Please try refreshing the page.'); } finally { ui.hideLoader(); @@ -1084,6 +1085,7 @@ const app = { } } } catch (error) { + console.error("An error occured:", error.message); ui.displayErrorMessage('Failed to load coin data. Please try refreshing the page.'); } }, @@ -1135,19 +1137,19 @@ const app = { }); } }); - + const refreshAllButton = document.getElementById('refresh-all'); if (refreshAllButton) { app.addEventListenerWithCleanup(refreshAllButton, 'click', app.refreshAllData); } - + const headers = document.querySelectorAll('th'); headers.forEach((header, index) => { - app.addEventListenerWithCleanup(header, 'click', () => + app.addEventListenerWithCleanup(header, 'click', () => app.sortTable(index, header.classList.contains('disabled')) ); }); - + const closeErrorButton = document.getElementById('close-error'); if (closeErrorButton) { app.addEventListenerWithCleanup(closeErrorButton, 'click', ui.hideErrorMessage); @@ -1187,6 +1189,7 @@ const app = { earliestExpiration = Math.min(earliestExpiration, cachedItem.expiresAt); } } catch (error) { + console.error("An error occured:", error.message); localStorage.removeItem(key); } } @@ -1222,16 +1225,16 @@ const app = { app.isRefreshing = true; ui.showLoader(); chartModule.showChartLoader(); - + try { cache.clear(); await app.updateBTCPrice(); - + const allCoinData = await api.fetchCoinGeckoDataXHR(); if (allCoinData.error) { throw new Error(allCoinData.error); } - + for (const coin of config.coins) { const symbol = coin.symbol.toLowerCase(); const coinData = allCoinData[symbol]; @@ -1242,16 +1245,17 @@ const app = { cache.set(cacheKey, coinData); } } - + if (chartModule.currentCoin) { await chartModule.updateChart(chartModule.currentCoin, true); } - + app.lastRefreshedTime = new Date(); localStorage.setItem('lastRefreshedTime', app.lastRefreshedTime.getTime().toString()); ui.updateLastRefreshedTime(); - + } catch (error) { + console.error("An error occured:", error.message); ui.displayErrorMessage('Failed to refresh all data. Please try again.'); } finally { ui.hideLoader(); @@ -1268,7 +1272,7 @@ const app = { const nextRefreshSpan = document.getElementById('next-refresh-time'); const labelElement = document.getElementById('next-refresh-label'); const valueElement = document.getElementById('next-refresh-value'); - + if (nextRefreshSpan && labelElement && valueElement) { if (app.nextRefreshTime) { if (app.updateNextRefreshTimeRAF) { @@ -1277,7 +1281,7 @@ const app = { const updateDisplay = () => { const timeUntilRefresh = Math.max(0, Math.ceil((app.nextRefreshTime - Date.now()) / 1000)); - + if (timeUntilRefresh === 0) { labelElement.textContent = ''; valueElement.textContent = app.refreshTexts.justRefreshed; @@ -1287,12 +1291,12 @@ const app = { labelElement.textContent = `${app.refreshTexts.label}: `; valueElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`; } - + if (timeUntilRefresh > 0) { app.updateNextRefreshTimeRAF = requestAnimationFrame(updateDisplay); } }; - + updateDisplay(); } else { labelElement.textContent = ''; @@ -1363,6 +1367,7 @@ const app = { app.btcPriceUSD = 0; } } catch (error) { + console.error("An error occured:", error.message); app.btcPriceUSD = 0; } }, @@ -1372,18 +1377,18 @@ const app = { if (!sortableColumns.includes(columnIndex)) { return; } - + const table = document.querySelector('table'); if (!table) { return; } - + const rows = Array.from(table.querySelectorAll('tbody tr')); const sortIcon = document.getElementById(`sort-icon-${columnIndex}`); if (!sortIcon) { return; } - + const sortOrder = sortIcon.textContent === '↓' ? 1 : -1; sortIcon.textContent = sortOrder === 1 ? '↑' : '↓'; @@ -1408,7 +1413,7 @@ const app = { } }; return (parseTime(bValue) - parseTime(aValue)) * sortOrder; - + case 5: // Rate case 6: // Market +/- aValue = getSafeTextContent(a.cells[columnIndex]); @@ -1417,26 +1422,26 @@ const app = { aValue = parseFloat(aValue.replace(/[^\d.-]/g, '') || '0'); bValue = parseFloat(bValue.replace(/[^\d.-]/g, '') || '0'); return (aValue - bValue) * sortOrder; - + case 7: // Trade const aCell = a.cells[columnIndex]; const bCell = b.cells[columnIndex]; - - aValue = getSafeTextContent(aCell.querySelector('a')) || - getSafeTextContent(aCell.querySelector('button')) || + + aValue = getSafeTextContent(aCell.querySelector('a')) || + getSafeTextContent(aCell.querySelector('button')) || getSafeTextContent(aCell); - bValue = getSafeTextContent(bCell.querySelector('a')) || - getSafeTextContent(bCell.querySelector('button')) || + bValue = getSafeTextContent(bCell.querySelector('a')) || + getSafeTextContent(bCell.querySelector('button')) || getSafeTextContent(bCell); - + aValue = aValue.toLowerCase(); bValue = bValue.toLowerCase(); - + if (aValue === bValue) return 0; if (aValue === "swap") return -1 * sortOrder; if (bValue === "swap") return 1 * sortOrder; return aValue.localeCompare(bValue) * sortOrder; - + default: aValue = getSafeTextContent(a.cells[columnIndex]); bValue = getSafeTextContent(b.cells[columnIndex]); @@ -1528,7 +1533,7 @@ const app = { console.log('Toggling auto-refresh'); app.isAutoRefreshEnabled = !app.isAutoRefreshEnabled; localStorage.setItem('autoRefreshEnabled', app.isAutoRefreshEnabled.toString()); - + if (app.isAutoRefreshEnabled) { console.log('Auto-refresh enabled, scheduling next refresh'); app.scheduleNextRefresh(); @@ -1541,7 +1546,7 @@ const app = { app.nextRefreshTime = null; localStorage.removeItem('nextRefreshTime'); } - + app.updateAutoRefreshButton(); app.updateNextRefreshTime(); }