diff --git a/basicswap/static/js/bids_sentreceived.js b/basicswap/static/js/bids_sentreceived.js index cdbebca..614c837 100644 --- a/basicswap/static/js/bids_sentreceived.js +++ b/basicswap/static/js/bids_sentreceived.js @@ -97,7 +97,7 @@ const EventManager = { add(element, type, handler, options = false) { if (!element) return null; - + if (!this.listeners.has(element)) { this.listeners.set(element, new Map()); } @@ -116,7 +116,7 @@ const EventManager = { remove(element, type, handler, options = false) { if (!element) return; - + const elementListeners = this.listeners.get(element); if (!elementListeners) return; @@ -140,7 +140,7 @@ const EventManager = { removeAll(element) { if (!element) return; - + const elementListeners = this.listeners.get(element); if (!elementListeners) return; @@ -167,7 +167,7 @@ const EventManager = { function cleanup() { //console.log('Starting comprehensive cleanup process for bids table'); - + try { if (searchTimeout) { clearTimeout(searchTimeout); @@ -184,7 +184,7 @@ function cleanup() { cleanupTooltips(); forceTooltipDOMCleanup(); - + if (window.TooltipManager) { window.TooltipManager.cleanup(); } @@ -237,12 +237,12 @@ function cleanup() { sent: [], received: [] }; - + state.currentPage = { sent: 1, received: 1 }; - + state.isLoading = false; state.isRefreshing = false; state.wsConnected = false; @@ -310,7 +310,7 @@ CleanupManager.addListener(document, 'visibilitychange', () => { if (window.TooltipManager && typeof window.TooltipManager.cleanup === 'function') { window.TooltipManager.cleanup(); } - + // Run memory optimization if (window.MemoryManager) { MemoryManager.forceCleanup(); @@ -326,7 +326,7 @@ CleanupManager.addListener(document, 'visibilitychange', () => { const lastUpdateTime = state.lastRefresh || 0; const now = Date.now(); const refreshInterval = 5 * 60 * 1000; // 5 minutes - + if (now - lastUpdateTime > refreshInterval) { setTimeout(() => { updateBidsTable(); @@ -366,7 +366,7 @@ function cleanupRow(row) { function optimizeMemoryUsage() { const MAX_BIDS_IN_MEMORY = 500; - + ['sent', 'received'].forEach(type => { if (state.data[type] && state.data[type].length > MAX_BIDS_IN_MEMORY) { console.log(`Trimming ${type} bids data from ${state.data[type].length} to ${MAX_BIDS_IN_MEMORY}`); @@ -581,7 +581,7 @@ function filterAndSortData(bids) { } catch (e) { console.warn('Error accessing identity for search:', e); } - + const matchesLabel = label.toLowerCase().includes(searchStr); let matchesDisplayedLabel = false; @@ -589,7 +589,7 @@ function filterAndSortData(bids) { try { const tableId = state.currentTab === 'sent' ? 'sent' : 'received'; const cells = document.querySelectorAll(`#${tableId} a[href^="/identity/"]`); - + for (const cell of cells) { const href = cell.getAttribute('href'); @@ -607,7 +607,7 @@ function filterAndSortData(bids) { console.warn('Error checking displayed labels:', e); } } - + if (!(matchesBidId || matchesIdentity || matchesLabel || matchesDisplayedLabel)) { return false; } @@ -627,7 +627,7 @@ async function preloadIdentitiesForSearch(bids) { if (!window.IdentityManager || typeof IdentityManager.getIdentityData !== 'function') { return; } - + try { const addresses = new Set(); bids.forEach(bid => { @@ -638,7 +638,7 @@ async function preloadIdentitiesForSearch(bids) { const BATCH_SIZE = 20; const addressArray = Array.from(addresses); - + for (let i = 0; i < addressArray.length; i += BATCH_SIZE) { const batch = addressArray.slice(i, i + BATCH_SIZE); await Promise.all(batch.map(addr => IdentityManager.getIdentityData(addr))); @@ -647,7 +647,7 @@ async function preloadIdentitiesForSearch(bids) { await new Promise(resolve => setTimeout(resolve, 10)); } } - + console.log(`Preloaded ${addressArray.length} identities for search`); } catch (error) { console.error('Error preloading identities:', error); @@ -847,7 +847,7 @@ const createIdentityTooltipContent = (identity) => { `; }; -let tooltipIdsToCleanup = new Set(); +const tooltipIdsToCleanup = new Set(); const cleanupTooltips = () => { if (window.TooltipManager) { @@ -869,7 +869,7 @@ const forceTooltipDOMCleanup = () => { foundCount += allTooltipElements.length; allTooltipElements.forEach(element => { - const isDetached = !document.body.contains(element) || + const isDetached = !document.body.contains(element) || element.classList.contains('hidden') || element.style.display === 'none'; @@ -877,7 +877,7 @@ const forceTooltipDOMCleanup = () => { const triggerId = element.id; const triggerElement = document.querySelector(`[data-tooltip-target="${triggerId}"]`); - if (!triggerElement || + if (!triggerElement || !document.body.contains(triggerElement) || triggerElement.classList.contains('hidden')) { element.remove(); @@ -899,7 +899,7 @@ const forceTooltipDOMCleanup = () => { const tippyRoots = document.querySelectorAll('[data-tippy-root]'); foundCount += tippyRoots.length; tippyRoots.forEach(element => { - const isOrphan = !element.children.length || + const isOrphan = !element.children.length || element.children[0].classList.contains('hidden') || !document.body.contains(element); @@ -926,7 +926,7 @@ const forceTooltipDOMCleanup = () => { } }); document.querySelectorAll('.tooltip').forEach(element => { - const isTrulyDetached = !element.parentElement || + const isTrulyDetached = !element.parentElement || !document.body.contains(element.parentElement) || element.classList.contains('hidden'); if (isTrulyDetached) { @@ -1108,7 +1108,7 @@ const updateTableContent = async (type) => { if (currentPageData.length > 0) { const BATCH_SIZE = 10; let allRows = []; - + for (let i = 0; i < currentPageData.length; i += BATCH_SIZE) { const batch = currentPageData.slice(i, i + BATCH_SIZE); const rowPromises = batch.map(bid => createTableRow(bid)); @@ -1166,7 +1166,7 @@ const initializeTooltips = () => { window.TooltipManager.cleanup(); - let selector = '#' + state.currentTab + ' [data-tooltip-target]'; + const selector = '#' + state.currentTab + ' [data-tooltip-target]'; const tooltipTriggers = document.querySelectorAll(selector); const tooltipCount = tooltipTriggers.length; if (tooltipCount > 50) { @@ -1187,7 +1187,7 @@ const initializeTooltips = () => { }); const offscreenTooltips = Array.from(tooltipTriggers).filter(t => !viewportTooltips.includes(t)); - + offscreenTooltips.forEach(trigger => { const createTooltipOnHover = () => { createTooltipForTrigger(trigger); @@ -1206,7 +1206,7 @@ const initializeTooltips = () => { const createTooltipForTrigger = (trigger) => { if (!trigger || !window.TooltipManager) return; - + const targetId = trigger.getAttribute('data-tooltip-target'); const tooltipContent = document.getElementById(targetId); @@ -1250,7 +1250,7 @@ function cleanupOffscreenTooltips() { const farOffscreenTriggers = Array.from(tooltipTriggers).filter(trigger => { const rect = trigger.getBoundingClientRect(); - return (rect.bottom < -window.innerHeight * 2 || + return (rect.bottom < -window.innerHeight * 2 || rect.top > window.innerHeight * 3); }); @@ -1312,7 +1312,7 @@ const fetchBids = async () => { activeFetchController.abort(); } }, 30000); - + const response = await fetch(endpoint, { method: 'POST', headers: { @@ -1328,14 +1328,14 @@ const fetchBids = async () => { }), signal: activeFetchController.signal }); - + clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } - let data = await response.json(); + const data = await response.json(); //console.log('Received raw data:', data.length, 'bids'); state.filters.with_expired = includeExpired; @@ -1351,7 +1351,7 @@ const fetchBids = async () => { } else { processedData = filterAndSortData(data); } - + return processedData; } catch (error) { if (error.name === 'AbortError') { @@ -1375,12 +1375,12 @@ const updateBidsTable = async () => { updateLoadingState(true); const bids = await fetchBids(); - + // Add identity preloading if we're searching if (state.filters.searchQuery && state.filters.searchQuery.length > 0) { await preloadIdentitiesForSearch(bids); } - + state.data[state.currentTab] = bids; state.currentPage[state.currentTab] = 1; @@ -1911,13 +1911,13 @@ function initialize() { WebSocketManager.initialize(); setupEventListeners(); }, 10); - + setTimeout(() => { setupRefreshButtons(); setupFilterEventListeners(); updateCoinFilterImages(); }, 50); - + setTimeout(() => { updateClearFiltersButton(); state.currentTab = 'sent'; diff --git a/basicswap/static/js/bids_sentreceived_export.js b/basicswap/static/js/bids_sentreceived_export.js index 823a1e4..acee851 100644 --- a/basicswap/static/js/bids_sentreceived_export.js +++ b/basicswap/static/js/bids_sentreceived_export.js @@ -5,7 +5,7 @@ const BidExporter = { } const isSent = type === 'sent'; - + const headers = [ 'Date/Time', 'Bid ID', @@ -19,9 +19,9 @@ const BidExporter = { 'Created At', 'Expires At' ]; - + let csvContent = headers.join(',') + '\n'; - + bids.forEach(bid => { const row = [ `"${formatTime(bid.created_at)}"`, @@ -36,17 +36,17 @@ const BidExporter = { bid.created_at, bid.expire_at ]; - + csvContent += row.join(',') + '\n'; }); - + return csvContent; }, - + download(content, filename) { try { const blob = new Blob([content], { type: 'text/csv;charset=utf-8;' }); - + if (window.navigator && window.navigator.msSaveOrOpenBlob) { window.navigator.msSaveOrOpenBlob(blob, filename); return; @@ -54,48 +54,48 @@ const BidExporter = { const url = URL.createObjectURL(blob); const link = document.createElement('a'); - + link.href = url; link.download = filename; link.style.display = 'none'; - + document.body.appendChild(link); link.click(); document.body.removeChild(link); - + setTimeout(() => { URL.revokeObjectURL(url); }, 100); } catch (error) { console.error('Error downloading CSV:', error); - + const csvData = 'data:text/csv;charset=utf-8,' + encodeURIComponent(content); const link = document.createElement('a'); link.setAttribute('href', csvData); link.setAttribute('download', filename); link.style.display = 'none'; - + document.body.appendChild(link); link.click(); document.body.removeChild(link); } }, - + exportCurrentView() { const type = state.currentTab; const data = state.data[type]; - + if (!data || !data.length) { alert('No data to export'); return; } - + const csvContent = this.toCSV(data, type); - + const now = new Date(); const dateStr = now.toISOString().split('T')[0]; const filename = `bsx_${type}_bids_${dateStr}.csv`; - + this.download(csvContent, filename); } }; @@ -111,7 +111,7 @@ document.addEventListener('DOMContentLoaded', function() { BidExporter.exportCurrentView(); }); } - + const exportReceivedButton = document.getElementById('exportReceivedBids'); if (exportReceivedButton) { EventManager.add(exportReceivedButton, 'click', (e) => { @@ -127,14 +127,14 @@ document.addEventListener('DOMContentLoaded', function() { const originalCleanup = window.cleanup || function(){}; window.cleanup = function() { originalCleanup(); - + const exportSentButton = document.getElementById('exportSentBids'); const exportReceivedButton = document.getElementById('exportReceivedBids'); - + if (exportSentButton && typeof EventManager !== 'undefined') { EventManager.remove(exportSentButton, 'click'); } - + if (exportReceivedButton && typeof EventManager !== 'undefined') { EventManager.remove(exportReceivedButton, 'click'); } diff --git a/basicswap/static/js/global.js b/basicswap/static/js/global.js index 5892e98..f6c24e2 100644 --- a/basicswap/static/js/global.js +++ b/basicswap/static/js/global.js @@ -84,7 +84,7 @@ function setupShutdownModal() { function showShutdownModal() { closeAllDropdowns(); - + const activeSwaps = parseInt(shutdownButtons[0].getAttribute('data-active-swaps') || '0'); if (activeSwaps > 0) { shutdownWarning.classList.remove('hidden'); @@ -142,7 +142,7 @@ function setupDarkMode() { const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon'); if (themeToggleDarkIcon && themeToggleLightIcon) { - if (localStorage.getItem('color-theme') === 'dark' || + if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) { themeToggleLightIcon.classList.remove('hidden'); } else { @@ -167,12 +167,12 @@ function setupDarkMode() { } else { setTheme('dark'); } - + if (themeToggleDarkIcon && themeToggleLightIcon) { themeToggleDarkIcon.classList.toggle('hidden'); themeToggleLightIcon.classList.toggle('hidden'); } - + toggleImages(); }); } diff --git a/basicswap/static/js/modules/api-manager.js b/basicswap/static/js/modules/api-manager.js index 2effc12..9f299e1 100644 --- a/basicswap/static/js/modules/api-manager.js +++ b/basicswap/static/js/modules/api-manager.js @@ -51,7 +51,7 @@ const ApiManager = (function() { try { await this.requestQueue[apiName]; - + const executeRequest = async () => { const waitTime = this.getWaitTime(apiName); if (waitTime > 0) { @@ -69,7 +69,7 @@ const ApiManager = (function() { return publicAPI.rateLimiter.queueRequest(apiName, requestFn, retryCount + 1); } - if ((error.message.includes('timeout') || error.name === 'NetworkError') && + if ((error.message.includes('timeout') || error.name === 'NetworkError') && retryCount < this.retryDelays.length) { const delay = this.retryDelays[retryCount]; console.warn(`Request failed, retrying in ${delay/1000} seconds...`, { @@ -87,10 +87,10 @@ const ApiManager = (function() { this.requestQueue[apiName] = executeRequest(); return await this.requestQueue[apiName]; - + } catch (error) { - if (error.message.includes('429') || - error.message.includes('timeout') || + if (error.message.includes('429') || + error.message.includes('timeout') || error.name === 'NetworkError') { const cacheKey = `coinData_${apiName}`; try { @@ -110,7 +110,7 @@ const ApiManager = (function() { const publicAPI = { config, rateLimiter, - + initialize: function(options = {}) { if (state.isInitialized) { console.warn('[ApiManager] Already initialized'); @@ -158,7 +158,7 @@ const ApiManager = (function() { } const response = await fetch(url, options); - + if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } @@ -215,18 +215,18 @@ const ApiManager = (function() { fetchCoinGeckoData: async function() { return this.rateLimiter.queueRequest('coingecko', async () => { try { - const coins = (window.config && window.config.coins) ? + const coins = (window.config && window.config.coins) ? window.config.coins .filter(coin => coin.usesCoinGecko) .map(coin => coin.name) - .join(',') : + .join(',') : 'bitcoin,monero,particl,bitcoincash,pivx,firo,dash,litecoin,dogecoin,decred'; //console.log('Fetching coin prices for:', coins); const response = await this.fetchCoinPrices(coins); - + //console.log('Full API response:', response); - + if (!response || typeof response !== 'object') { throw new Error('Invalid response type'); } @@ -249,11 +249,11 @@ const ApiManager = (function() { fetchVolumeData: async function() { return this.rateLimiter.queueRequest('coingecko', async () => { try { - const coins = (window.config && window.config.coins) ? + const coins = (window.config && window.config.coins) ? window.config.coins .filter(coin => coin.usesCoinGecko) .map(coin => getCoinBackendId ? getCoinBackendId(coin.name) : coin.name) - .join(',') : + .join(',') : 'bitcoin,monero,particl,bitcoin-cash,pivx,firo,dash,litecoin,dogecoin,decred'; const url = `https://api.coingecko.com/api/v3/simple/price?ids=${coins}&vs_currencies=usd&include_24hr_vol=true&include_24hr_change=true`; @@ -280,7 +280,7 @@ const ApiManager = (function() { } }); }, - + fetchCryptoCompareData: function(coin) { return this.rateLimiter.queueRequest('cryptocompare', async () => { try { @@ -290,7 +290,7 @@ const ApiManager = (function() { 'User-Agent': 'Mozilla/5.0', 'Accept': 'application/json' }; - + return await this.makePostRequest(url, headers); } catch (error) { console.error(`CryptoCompare request failed for ${coin}:`, error); @@ -324,7 +324,7 @@ const ApiManager = (function() { try { const apiKey = window.config?.apiKeys?.cryptoCompare || ''; let url; - + if (resolution === 'day') { url = `https://min-api.cryptocompare.com/data/v2/histohour?fsym=${coin}&tsym=USD&limit=24&api_key=${apiKey}`; } else if (resolution === 'year') { @@ -351,7 +351,7 @@ const ApiManager = (function() { await Promise.all(fetchPromises); return results; }, - + dispose: function() { // Clear any pending requests or resources rateLimiter.requestQueue = {}; diff --git a/basicswap/static/js/modules/cache-manager.js b/basicswap/static/js/modules/cache-manager.js index 1dec899..e8fba7c 100644 --- a/basicswap/static/js/modules/cache-manager.js +++ b/basicswap/static/js/modules/cache-manager.js @@ -19,7 +19,7 @@ const CacheManager = (function() { ]; const isCacheKey = (key) => { - return CACHE_KEY_PATTERNS.some(pattern => key.startsWith(pattern)) || + return CACHE_KEY_PATTERNS.some(pattern => key.startsWith(pattern)) || key === 'coinGeckoOneLiner' || key === PRICES_CACHE_KEY; }; @@ -48,7 +48,7 @@ const CacheManager = (function() { const ttlConfig = window.config?.cacheConfig?.ttlSettings || {}; return ttlConfig[resourceType] || window.config?.cacheConfig?.defaultTTL || defaults.defaultTTL; }, - + set: function(key, value, resourceTypeOrCustomTtl = null) { try { this.cleanup(); @@ -119,10 +119,10 @@ const CacheManager = (function() { .filter(k => isCacheKey(k)) .sort((a, b) => memoryCache.get(a).timestamp - memoryCache.get(b).timestamp) .slice(0, Math.floor(memoryCache.size * 0.2)); // Remove oldest 20% - + keysToDelete.forEach(k => memoryCache.delete(k)); } - + return true; } } catch (error) { @@ -194,7 +194,7 @@ const CacheManager = (function() { memoryCache.delete(key); } } - + return null; } catch (error) { console.error("Cache retrieval error:", error); @@ -286,7 +286,7 @@ const CacheManager = (function() { .filter(key => isCacheKey(key)) .sort((a, b) => memoryCache.get(a).timestamp - memoryCache.get(b).timestamp) .slice(0, Math.floor(memoryCache.size * 0.3)); // Remove oldest 30% during aggressive cleanup - + keysToDelete.forEach(key => memoryCache.delete(key)); } @@ -327,7 +327,7 @@ const CacheManager = (function() { Array.from(memoryCache.keys()) .filter(key => isCacheKey(key)) .forEach(key => memoryCache.delete(key)); - + console.log("Cache cleared successfully"); return true; }, @@ -368,7 +368,7 @@ const CacheManager = (function() { let memoryCacheSize = 0; let memoryCacheItems = 0; let memoryCacheExpired = 0; - + memoryCache.forEach((item, key) => { if (isCacheKey(key)) { memoryCacheItems++; @@ -381,7 +381,7 @@ const CacheManager = (function() { } } }); - + return { totalSizeMB: (totalSize / 1024 / 1024).toFixed(2), itemCount, @@ -415,10 +415,10 @@ const CacheManager = (function() { } } }); - + console.log(`Migrated ${migratedCount} items from memory cache to localStorage.`); } - + return { available: storageAvailable, type: storageAvailable ? 'localStorage' : 'memory' @@ -430,7 +430,7 @@ const CacheManager = (function() { ...cacheAPI, setPrices: function(priceData, customTtl = null) { - return this.set(PRICES_CACHE_KEY, priceData, + return this.set(PRICES_CACHE_KEY, priceData, customTtl || (typeof customTtl === 'undefined' ? 'prices' : null)); }, @@ -447,7 +447,7 @@ const CacheManager = (function() { const normalizedSymbol = symbol.toLowerCase(); return prices.value[normalizedSymbol] || null; }, - + getCompatiblePrices: function(format) { const prices = this.getPrices(); if (!prices || !prices.value) { @@ -464,7 +464,7 @@ const CacheManager = (function() { .join(' ') .toLowerCase() .replace(' ', '-'); - + ratesFormat[coinKey] = { usd: data.price || data.usd, btc: data.price_btc || data.btc @@ -493,7 +493,7 @@ const CacheManager = (function() { value: geckoFormat, remainingTime: prices.remainingTime }; - + default: return prices; } diff --git a/basicswap/static/js/modules/cleanup-manager.js b/basicswap/static/js/modules/cleanup-manager.js index 0615fca..d179ee9 100644 --- a/basicswap/static/js/modules/cleanup-manager.js +++ b/basicswap/static/js/modules/cleanup-manager.js @@ -111,8 +111,8 @@ const CleanupManager = (function() { } state.eventListeners = state.eventListeners.filter( - listener => !(listener.element === element && - listener.type === type && + listener => !(listener.element === element && + listener.type === type && listener.handler === handler) ); }, @@ -224,10 +224,10 @@ const CleanupManager = (function() { intervals: state.intervals.length, animationFrames: state.animationFrames.length, resources: state.resources.size, - total: state.eventListeners.length + - state.timeouts.length + - state.intervals.length + - state.animationFrames.length + + total: state.eventListeners.length + + state.timeouts.length + + state.intervals.length + + state.animationFrames.length + state.resources.size }; }, diff --git a/basicswap/static/js/modules/config-manager.js b/basicswap/static/js/modules/config-manager.js index e77c55e..1176cc3 100644 --- a/basicswap/static/js/modules/config-manager.js +++ b/basicswap/static/js/modules/config-manager.js @@ -4,9 +4,9 @@ const ConfigManager = (function() { }; function determineWebSocketPort() { - const wsPort = - window.ws_port || - (typeof getWebSocketConfig === 'function' ? getWebSocketConfig().port : null) || + const wsPort = + window.ws_port || + (typeof getWebSocketConfig === 'function' ? getWebSocketConfig().port : null) || '11700'; return wsPort; } @@ -17,10 +17,10 @@ const ConfigManager = (function() { cacheDuration: 10 * 60 * 1000, requestTimeout: 60000, wsPort: selectedWsPort, - + cacheConfig: { defaultTTL: 10 * 60 * 1000, - + ttlSettings: { prices: 5 * 60 * 1000, chart: 5 * 60 * 1000, @@ -34,7 +34,7 @@ const ConfigManager = (function() { maxSizeBytes: 10 * 1024 * 1024, maxItems: 200 }, - + fallbackTTL: 24 * 60 * 60 * 1000 }, @@ -92,7 +92,7 @@ const ConfigManager = (function() { 'Bitcoin Cash': 'BCH', 'Dogecoin': 'DOGE' }, - + nameToDisplayName: { 'Bitcoin': 'Bitcoin', 'Litecoin': 'Litecoin', @@ -164,13 +164,13 @@ const ConfigManager = (function() { if (options) { Object.assign(this, options); } - + if (window.CleanupManager) { window.CleanupManager.registerResource('configManager', this, (mgr) => mgr.dispose()); } this.utils = utils; - + state.isInitialized = true; console.log('ConfigManager initialized'); return this; @@ -205,7 +205,7 @@ const ConfigManager = (function() { const lowerCoinName = typeof coinName === 'string' ? coinName.toLowerCase() : ''; return nameMap[lowerCoinName] || lowerCoinName; }, - + coinMatches: function(offerCoin, filterCoin) { if (!offerCoin || !filterCoin) return false; @@ -254,7 +254,7 @@ const ConfigManager = (function() { get: function(path, defaultValue = null) { const parts = path.split('.'); let current = this; - + for (let i = 0; i < parts.length; i++) { if (current === undefined || current === null) { return defaultValue; @@ -376,12 +376,12 @@ const ConfigManager = (function() { 'firo': { usd: null, btc: null } }; }, - + getCoinSymbol: function(fullName) { return publicAPI.coinMappings?.nameToSymbol[fullName] || fullName; } }; - + return publicAPI; })(); diff --git a/basicswap/static/js/modules/identity-manager.js b/basicswap/static/js/modules/identity-manager.js index 9ac2859..68359f7 100644 --- a/basicswap/static/js/modules/identity-manager.js +++ b/basicswap/static/js/modules/identity-manager.js @@ -60,7 +60,7 @@ const IdentityManager = (function() { const oldestEntries = [...state.cache.entries()] .sort((a, b) => a[1].timestamp - b[1].timestamp) .slice(0, Math.floor(state.config.maxCacheSize * 0.2)); - + oldestEntries.forEach(([key]) => { state.cache.delete(key); log(`Pruned cache entry for ${key}`); @@ -88,10 +88,10 @@ const IdentityManager = (function() { const entriesToRemove = [...state.cache.entries()] .sort((a, b) => a[1].timestamp - b[1].timestamp) .slice(0, state.cache.size - maxSize); - + entriesToRemove.forEach(([key]) => state.cache.delete(key)); log(`Limited cache size, removed ${entriesToRemove.length} entries`); - + return entriesToRemove.length; }, @@ -138,11 +138,11 @@ const IdentityManager = (function() { if (options) { this.configure(options); } - + if (window.CleanupManager) { window.CleanupManager.registerResource('identityManager', this, (mgr) => mgr.dispose()); } - + log('IdentityManager initialized'); return this; }, diff --git a/basicswap/static/js/modules/memory-manager.js b/basicswap/static/js/modules/memory-manager.js index 4c3cffd..fd5489f 100644 --- a/basicswap/static/js/modules/memory-manager.js +++ b/basicswap/static/js/modules/memory-manager.js @@ -65,7 +65,7 @@ const MemoryManager = (function() { const nodeCount = document.querySelectorAll('*').length; console.log('DOM node count:', nodeCount); - + if (window.CleanupManager) { const counts = CleanupManager.getResourceCounts(); console.log('Managed resources:', counts); @@ -102,11 +102,11 @@ const MemoryManager = (function() { state.cleanupInterval = setInterval(() => { this.forceCleanup(); }, interval); - + log('Auto-cleanup enabled every', interval/1000, 'seconds'); return true; }, - + disableAutoCleanup: function() { if (state.cleanupInterval) { clearInterval(state.cleanupInterval); @@ -155,7 +155,7 @@ const MemoryManager = (function() { return true; }, - + setDebugMode: function(enabled) { config.debug = Boolean(enabled); return `Debug mode ${config.debug ? 'enabled' : 'disabled'}`; diff --git a/basicswap/static/js/modules/network-manager.js b/basicswap/static/js/modules/network-manager.js index c5a1dc4..6a8a932 100644 --- a/basicswap/static/js/modules/network-manager.js +++ b/basicswap/static/js/modules/network-manager.js @@ -30,13 +30,13 @@ const NetworkManager = (function() { const publicAPI = { initialize: function(options = {}) { Object.assign(config, options); - + window.addEventListener('online', this.handleOnlineStatus.bind(this)); window.addEventListener('offline', this.handleOfflineStatus.bind(this)); - + state.isOnline = navigator.onLine; log(`Network status initialized: ${state.isOnline ? 'online' : 'offline'}`); - + if (window.CleanupManager) { window.CleanupManager.registerResource('networkManager', this, (mgr) => mgr.dispose()); } @@ -61,7 +61,7 @@ const NetworkManager = (function() { log('Browser reports online status'); state.isOnline = true; this.notifyHandlers('online'); - + if (state.reconnectTimer) { this.scheduleReconnectRefresh(); } @@ -103,7 +103,7 @@ const NetworkManager = (function() { state.reconnectTimer = null; } - const delay = config.reconnectDelay * Math.pow(config.reconnectBackoff, + const delay = config.reconnectDelay * Math.pow(config.reconnectBackoff, Math.min(state.reconnectAttempts, 5)); log(`Scheduling reconnection attempt in ${delay/1000} seconds`); @@ -145,7 +145,7 @@ const NetworkManager = (function() { this.notifyHandlers('reconnected'); } else { log('Backend still unavailable'); - + if (state.reconnectAttempts < config.maxReconnectAttempts) { this.scheduleReconnectRefresh(); } else { @@ -157,7 +157,7 @@ const NetworkManager = (function() { .catch(error => { state.connectionTestInProgress = false; log('Error during connection test:', error); - + if (state.reconnectAttempts < config.maxReconnectAttempts) { this.scheduleReconnectRefresh(); } else { @@ -210,7 +210,7 @@ const NetworkManager = (function() { const handlerId = generateHandlerId(); state.eventHandlers[event][handlerId] = handler; - + return handlerId; }, @@ -256,9 +256,9 @@ const NetworkManager = (function() { window.removeEventListener('online', this.handleOnlineStatus); window.removeEventListener('offline', this.handleOfflineStatus); - + state.eventHandlers = {}; - + log('NetworkManager disposed'); } }; diff --git a/basicswap/static/js/modules/summary-manager.js b/basicswap/static/js/modules/summary-manager.js index d4f42b4..77317b7 100644 --- a/basicswap/static/js/modules/summary-manager.js +++ b/basicswap/static/js/modules/summary-manager.js @@ -15,9 +15,9 @@ const SummaryManager = (function() { function updateElement(elementId, value) { const element = document.getElementById(elementId); if (!element) return false; - - const safeValue = (value !== undefined && value !== null) - ? value + + const safeValue = (value !== undefined && value !== null) + ? value : (element.dataset.lastValue || 0); element.dataset.lastValue = safeValue; @@ -32,8 +32,8 @@ const SummaryManager = (function() { element.textContent = safeValue; } - if (['offers-counter', 'bid-requests-counter', 'sent-bids-counter', - 'recv-bids-counter', 'swaps-counter', 'network-offers-counter', + if (['offers-counter', 'bid-requests-counter', 'sent-bids-counter', + 'recv-bids-counter', 'swaps-counter', 'network-offers-counter', 'watched-outputs-counter'].includes(elementId)) { element.classList.remove('bg-blue-500', 'bg-gray-400'); element.classList.add(safeValue > 0 ? 'bg-blue-500' : 'bg-gray-400'); @@ -57,7 +57,7 @@ const SummaryManager = (function() { function updateUIFromData(data) { if (!data) return; - + updateElement('network-offers-counter', data.num_network_offers); updateElement('offers-counter', data.num_sent_active_offers); updateElement('sent-bids-counter', data.num_sent_active_bids); @@ -65,7 +65,7 @@ const SummaryManager = (function() { updateElement('bid-requests-counter', data.num_available_bids); updateElement('swaps-counter', data.num_swapping); updateElement('watched-outputs-counter', data.num_watched_outputs); - + const shutdownButtons = document.querySelectorAll('.shutdown-button'); shutdownButtons.forEach(button => { button.setAttribute('data-active-swaps', data.num_swapping); @@ -83,7 +83,7 @@ const SummaryManager = (function() { function cacheSummaryData(data) { if (!data) return; - + localStorage.setItem('summary_data_cache', JSON.stringify({ timestamp: Date.now(), data: data @@ -92,24 +92,24 @@ const SummaryManager = (function() { function getCachedSummaryData() { let cachedData = null; - + cachedData = localStorage.getItem('summary_data_cache'); if (!cachedData) return null; - + const parsedCache = JSON.parse(cachedData); const maxAge = 24 * 60 * 60 * 1000; - + if (Date.now() - parsedCache.timestamp < maxAge) { return parsedCache.data; } - + return null; } function fetchSummaryDataWithTimeout() { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), config.requestTimeout); - + return fetch(config.summaryEndpoint, { signal: controller.signal, headers: { @@ -120,11 +120,11 @@ const SummaryManager = (function() { }) .then(response => { clearTimeout(timeoutId); - + if (!response.ok) { throw new Error(`HTTP error! Status: ${response.status}`); } - + return response.json(); }) .catch(error => { @@ -138,21 +138,21 @@ const SummaryManager = (function() { webSocket.close(); } - const wsPort = window.config?.wsPort || + const wsPort = window.config?.wsPort || (typeof determineWebSocketPort === 'function' ? determineWebSocketPort() : '11700'); - + const wsUrl = "ws://" + window.location.hostname + ":" + wsPort; webSocket = new WebSocket(wsUrl); - + webSocket.onopen = () => { publicAPI.fetchSummaryData() .then(() => {}) .catch(() => {}); }; - + webSocket.onmessage = (event) => { let data; - + try { data = JSON.parse(event.data); } catch (error) { @@ -161,18 +161,18 @@ const SummaryManager = (function() { } return; } - + if (data.event) { publicAPI.fetchSummaryData() .then(() => {}) .catch(() => {}); - + if (window.NotificationManager && typeof window.NotificationManager.handleWebSocketEvent === 'function') { window.NotificationManager.handleWebSocketEvent(data); } } }; - + webSocket.onclose = () => { setTimeout(setupWebSocket, 5000); }; @@ -185,8 +185,8 @@ const SummaryManager = (function() { template.innerHTML = document.querySelector('[id^="swapContainer"]')?.innerHTML || ''; document.body.appendChild(template); } - - if (!document.getElementById('swap-in-progress-green-template') && + + if (!document.getElementById('swap-in-progress-green-template') && document.querySelector('[id^="swapContainer"]')?.innerHTML) { const greenTemplate = document.createElement('template'); greenTemplate.id = 'swap-in-progress-green-template'; @@ -229,7 +229,7 @@ const SummaryManager = (function() { if (window.WebSocketManager && typeof window.WebSocketManager.initialize === 'function') { const wsManager = window.WebSocketManager; - + if (!wsManager.isConnected()) { wsManager.connect(); } @@ -239,7 +239,7 @@ const SummaryManager = (function() { this.fetchSummaryData() .then(() => {}) .catch(() => {}); - + if (window.NotificationManager && typeof window.NotificationManager.handleWebSocketEvent === 'function') { window.NotificationManager.handleWebSocketEvent(data); } @@ -302,7 +302,7 @@ const SummaryManager = (function() { } }); }, - + startRefreshTimer: function() { startRefreshTimer(); }, diff --git a/basicswap/static/js/modules/tooltips-manager.js b/basicswap/static/js/modules/tooltips-manager.js index cb8018f..6af1b6e 100644 --- a/basicswap/static/js/modules/tooltips-manager.js +++ b/basicswap/static/js/modules/tooltips-manager.js @@ -26,14 +26,14 @@ const TooltipManager = (function() { create(element, content, options = {}) { if (!element) return null; - + this.destroy(element); if (this.tooltipElementsMap.size > this.maxTooltips * this.cleanupThreshold) { const oldestEntries = Array.from(this.tooltipElementsMap.entries()) .sort((a, b) => a[1].timestamp - b[1].timestamp) .slice(0, 20); - + oldestEntries.forEach(([el]) => { this.destroy(el); }); @@ -199,7 +199,7 @@ const TooltipManager = (function() { instance[0].destroy(); } catch (e) { console.warn('Error destroying tooltip:', e); - + const tippyRoot = document.querySelector(`[data-for-tooltip-id="${id}"]`); if (tippyRoot && tippyRoot.parentNode) { tippyRoot.parentNode.removeChild(tippyRoot); @@ -209,7 +209,7 @@ const TooltipManager = (function() { this.activeTooltips.delete(element); this.tooltipElementsMap.delete(element); - + element.removeAttribute('data-tooltip-trigger-id'); } @@ -355,7 +355,7 @@ const TooltipManager = (function() { this.handleVisibilityChange = () => { if (document.hidden) { this.cleanup(); - + if (window.MemoryManager) { window.MemoryManager.forceCleanup(); } @@ -365,7 +365,7 @@ const TooltipManager = (function() { window.addEventListener('beforeunload', this.boundCleanup); window.addEventListener('unload', this.boundCleanup); document.addEventListener('visibilitychange', this.handleVisibilityChange); - + if (window.CleanupManager) { window.CleanupManager.registerResource('tooltipManager', this, (tm) => tm.dispose()); } @@ -471,7 +471,7 @@ const TooltipManager = (function() { } }); - this.mutationObserver.observe(document.body, { + this.mutationObserver.observe(document.body, { childList: true, subtree: true }); @@ -497,7 +497,7 @@ const TooltipManager = (function() { cancelAnimationFrame(id); }); this.pendingAnimationFrames.clear(); - + if (this.mutationObserver) { this.mutationObserver.disconnect(); this.mutationObserver = null; diff --git a/basicswap/static/js/modules/wallet-manager.js b/basicswap/static/js/modules/wallet-manager.js index ac81e0d..b97f5b6 100644 --- a/basicswap/static/js/modules/wallet-manager.js +++ b/basicswap/static/js/modules/wallet-manager.js @@ -37,7 +37,7 @@ const WalletManager = (function() { 'Decred': 'DCR', 'Bitcoin Cash': 'BCH' }, - + coingeckoIds: { 'BTC': 'btc', 'PART': 'part', @@ -51,7 +51,7 @@ const WalletManager = (function() { 'DCR': 'dcr', 'BCH': 'bch' }, - + shortNames: { 'Bitcoin': 'BTC', 'Particl': 'PART', @@ -99,9 +99,9 @@ const WalletManager = (function() { try { const processedData = {}; const currentSource = config.priceSource.primary; - + const shouldIncludeWow = currentSource === 'coingecko.com'; - + const coinsToFetch = Object.values(coinData.symbols) .filter(symbol => shouldIncludeWow || symbol !== 'WOW') .map(symbol => coinData.coingeckoIds[symbol] || symbol.toLowerCase()) @@ -171,8 +171,8 @@ const WalletManager = (function() { lastError = error; console.error(`Price fetch attempt ${attempt + 1} failed:`, error); - if (attempt === config.maxRetries - 1 && - config.priceSource.fallback && + if (attempt === config.maxRetries - 1 && + config.priceSource.fallback && config.priceSource.fallback !== config.priceSource.primary) { const temp = config.priceSource.primary; config.priceSource.primary = config.priceSource.fallback; @@ -269,14 +269,14 @@ const WalletManager = (function() { } const coinId = coinName.toLowerCase().replace(' ', '-'); - + if (!prices[coinId]) { return; } const price = prices[coinId]?.usd || parseFloat(localStorage.getItem(`${coinId}-price`) || '0'); if (!price) return; - + const usdValue = (amount * price).toFixed(2); if (coinName === 'Particl') { @@ -300,7 +300,7 @@ const WalletManager = (function() { } let usdEl = null; - + const flexContainer = el.closest('.flex'); if (flexContainer) { const nextFlex = flexContainer.nextElementSibling; @@ -384,7 +384,7 @@ const WalletManager = (function() { return false; } } - + function updateTotalValues(totalUsd, btcPrice) { const totalUsdEl = document.getElementById('total-usd-value'); if (totalUsdEl) { @@ -439,7 +439,7 @@ const WalletManager = (function() { const eyeIcon = document.querySelector("#hide-usd-amount-toggle svg"); if (eyeIcon) { - eyeIcon.innerHTML = isVisible ? + eyeIcon.innerHTML = isVisible ? '' : ''; } @@ -500,7 +500,7 @@ const WalletManager = (function() { document.querySelectorAll('.coinname-value').forEach(el => { el.textContent = '****'; }); - + document.querySelectorAll('.usd-value').forEach(el => { el.textContent = '****'; }); @@ -542,7 +542,7 @@ const WalletManager = (function() { } state.lastUpdateTime = parseInt(localStorage.getItem(stateKeys.lastUpdate) || '0'); - state.isWalletsPage = document.querySelector('.wallet-list') !== null || + state.isWalletsPage = document.querySelector('.wallet-list') !== null || window.location.pathname.includes('/wallets'); document.querySelectorAll('.usd-value').forEach(el => { @@ -553,7 +553,7 @@ const WalletManager = (function() { }); storeOriginalValues(); - + if (localStorage.getItem('balancesVisible') === null) { localStorage.setItem('balancesVisible', 'true'); } @@ -581,7 +581,7 @@ const WalletManager = (function() { state.initialized = true; console.log('WalletManager initialized'); - + return this; }, @@ -606,7 +606,7 @@ const WalletManager = (function() { if (fallbackSource) { config.priceSource.fallback = fallbackSource; } - + return this; }, diff --git a/basicswap/static/js/modules/websocket-manager.js b/basicswap/static/js/modules/websocket-manager.js index 1aae881..e823821 100644 --- a/basicswap/static/js/modules/websocket-manager.js +++ b/basicswap/static/js/modules/websocket-manager.js @@ -43,12 +43,12 @@ const WebSocketManager = (function() { wsPort = window.ws_port.toString(); return wsPort; } - + if (typeof getWebSocketConfig === 'function') { const wsConfig = getWebSocketConfig(); wsPort = (wsConfig.port || wsConfig.fallbackPort || '11700').toString(); return wsPort; - } + } wsPort = '11700'; return wsPort; @@ -62,14 +62,14 @@ const WebSocketManager = (function() { startHealthCheck(); log('WebSocketManager initialized with options:', options); - + if (window.CleanupManager) { window.CleanupManager.registerResource('webSocketManager', this, (mgr) => mgr.dispose()); } return this; }, - + connect: function() { if (state.isConnecting || state.isIntentionallyClosed) { log('Connection attempt blocked - already connecting or intentionally closed'); @@ -87,7 +87,7 @@ const WebSocketManager = (function() { try { const wsPort = determineWebSocketPort(); - + if (!wsPort) { state.isConnecting = false; return false; @@ -129,7 +129,7 @@ const WebSocketManager = (function() { log('Cannot send message - not connected'); return false; } - + try { ws.send(JSON.stringify(message)); return true; @@ -143,13 +143,13 @@ const WebSocketManager = (function() { if (!state.messageHandlers[type]) { state.messageHandlers[type] = {}; } - + const handlerId = generateHandlerId(); state.messageHandlers[type][handlerId] = handler; - + return handlerId; }, - + removeMessageHandler: function(type, handlerId) { if (state.messageHandlers[type] && state.messageHandlers[type][handlerId]) { delete state.messageHandlers[type][handlerId]; @@ -158,7 +158,7 @@ const WebSocketManager = (function() { cleanup: function() { log('Cleaning up WebSocket resources'); - + clearTimeout(state.connectTimeout); stopHealthCheck(); @@ -211,11 +211,11 @@ const WebSocketManager = (function() { resume: function() { log('WebSocketManager resumed'); state.isIntentionallyClosed = false; - + if (!this.isConnected()) { this.connect(); } - + startHealthCheck(); } }; @@ -264,16 +264,16 @@ const WebSocketManager = (function() { log('WebSocket closed:', event); state.isConnecting = false; window.ws = null; - + if (typeof updateConnectionStatus === 'function') { updateConnectionStatus('disconnected'); } - notifyHandlers('disconnect', { - code: event.code, - reason: event.reason + notifyHandlers('disconnect', { + code: event.code, + reason: event.reason }); - + if (!state.isIntentionallyClosed) { handleReconnect(); } @@ -292,12 +292,12 @@ const WebSocketManager = (function() { document.addEventListener('visibilitychange', visibilityChangeHandler); state.listeners.visibilityChange = visibilityChangeHandler; } - + function handlePageHidden() { log('Page hidden'); state.isPageHidden = true; stopHealthCheck(); - + if (ws && ws.readyState === WebSocket.OPEN) { state.isIntentionallyClosed = true; ws.close(1000, 'Page hidden'); @@ -308,7 +308,7 @@ const WebSocketManager = (function() { log('Page visible'); state.isPageHidden = false; state.isIntentionallyClosed = false; - + setTimeout(() => { if (!publicAPI.isConnected()) { publicAPI.connect(); @@ -323,7 +323,7 @@ const WebSocketManager = (function() { performHealthCheck(); }, 30000); } - + function stopHealthCheck() { if (state.healthCheckInterval) { clearInterval(state.healthCheckInterval); @@ -340,7 +340,7 @@ const WebSocketManager = (function() { const now = Date.now(); const lastCheck = state.lastHealthCheck; - + if (lastCheck && (now - lastCheck) > 60000) { log('Health check failed - too long since last check'); handleReconnect(); @@ -401,10 +401,10 @@ const WebSocketManager = (function() { function cleanup() { log('Cleaning up WebSocket resources'); - + clearTimeout(state.connectTimeout); stopHealthCheck(); - + if (state.reconnectTimeout) { clearTimeout(state.reconnectTimeout); state.reconnectTimeout = null; diff --git a/basicswap/static/js/new_offer.js b/basicswap/static/js/new_offer.js index c88df43..beaecab 100644 --- a/basicswap/static/js/new_offer.js +++ b/basicswap/static/js/new_offer.js @@ -61,7 +61,7 @@ const Ajax = { if (xhr.status === 200) { if (onSuccess) { try { - const response = xhr.responseText.startsWith('{') ? + const response = xhr.responseText.startsWith('{') ? JSON.parse(xhr.responseText) : xhr.responseText; onSuccess(response); } catch (e) { @@ -153,10 +153,10 @@ const RateManager = { const params = 'coin_from=' + selectedCoin + '&coin_to=' + coinTo; - Ajax.post('/json/rates', params, + Ajax.post('/json/rates', params, (response) => { if (ratesDisplay) { - ratesDisplay.innerHTML = typeof response === 'string' ? + ratesDisplay.innerHTML = typeof response === 'string' ? response : '
' + JSON.stringify(response, null, '  ') + '
'; } }, @@ -167,7 +167,7 @@ const RateManager = { } ); }, - + getRateInferred: (event) => { if (event) event.preventDefault(); @@ -180,12 +180,12 @@ const RateManager = { return; } - const params = 'coin_from=' + encodeURIComponent(coinFrom) + + const params = 'coin_from=' + encodeURIComponent(coinFrom) + '&coin_to=' + encodeURIComponent(coinTo); DOM.setValue('rate', 'Loading...'); - Ajax.post('/json/rates', params, + Ajax.post('/json/rates', params, (response) => { if (response.coingecko && response.coingecko.rate_inferred) { DOM.setValue('rate', response.coingecko.rate_inferred); @@ -213,7 +213,7 @@ const RateManager = { swapType: DOM.get('swap_type') }; - if (!elements.coinFrom || !elements.coinTo || + if (!elements.coinFrom || !elements.coinTo || !elements.amtFrom || !elements.amtTo || !elements.rate) { console.log('Required elements for setRate not found'); return; @@ -225,7 +225,7 @@ const RateManager = { amtFrom: elements.amtFrom.value, amtTo: elements.amtTo.value, rate: elements.rate.value, - lockRate: elements.rate.value == '' ? false : + lockRate: elements.rate.value == '' ? false : (elements.rateLock ? elements.rateLock.checked : false) }; @@ -236,8 +236,8 @@ const RateManager = { if (elements.swapType) { SwapTypeManager.setSwapTypeEnabled( - values.coinFrom, - values.coinTo, + values.coinFrom, + values.coinTo, elements.swapType ); } @@ -248,8 +248,8 @@ const RateManager = { let params = 'coin_from=' + values.coinFrom + '&coin_to=' + values.coinTo; - if (valueChanged == 'rate' || - (values.lockRate && valueChanged == 'amt_from') || + if (valueChanged == 'rate' || + (values.lockRate && valueChanged == 'amt_from') || (values.amtTo == '' && valueChanged == 'amt_from')) { if (values.rate == '' || (values.amtFrom == '' && values.amtTo == '')) { @@ -274,7 +274,7 @@ const RateManager = { params += '&amt_from=' + values.amtFrom + '&amt_to=' + values.amtTo; } - Ajax.post('/json/rate', params, + Ajax.post('/json/rate', params, (response) => { if (response.hasOwnProperty('rate')) { DOM.setValue('rate', response.rate); @@ -314,13 +314,13 @@ const SwapTypeManager = { coinFrom = String(coinFrom); coinTo = String(coinTo); - if (SwapTypeManager.adaptor_sig_only_coins.includes(coinFrom) || + if (SwapTypeManager.adaptor_sig_only_coins.includes(coinFrom) || SwapTypeManager.adaptor_sig_only_coins.includes(coinTo)) { swapTypeElement.disabled = true; swapTypeElement.value = 'xmr_swap'; makeHidden = true; swapTypeElement.classList.add('select-disabled'); - } else if (SwapTypeManager.secret_hash_only_coins.includes(coinFrom) || + } else if (SwapTypeManager.secret_hash_only_coins.includes(coinFrom) || SwapTypeManager.secret_hash_only_coins.includes(coinTo)) { swapTypeElement.disabled = true; swapTypeElement.value = 'seller_first'; @@ -426,7 +426,7 @@ const UIEnhancer = { const name = selectedOption.textContent.trim(); selectCache[select.id] = { image, name }; } - + function setSelectData(select) { if (!select || !select.options || select.selectedIndex === undefined) return; @@ -457,9 +457,9 @@ const UIEnhancer = { const options = select.querySelectorAll('option'); const selectIcon = select.parentElement?.querySelector('.select-icon'); const selectImage = select.parentElement?.querySelector('.select-image'); - + if (!options || !selectIcon || !selectImage) return; - + options.forEach(option => { const image = option.getAttribute('data-image'); if (image) { @@ -476,7 +476,7 @@ const UIEnhancer = { setSelectData(select); Storage.setRaw(select.name, select.value); }); - + setSelectData(select); selectIcon.style.display = 'none'; selectImage.style.display = 'none'; diff --git a/basicswap/static/js/pricechart.js b/basicswap/static/js/pricechart.js index 05e7147..1e785d1 100644 --- a/basicswap/static/js/pricechart.js +++ b/basicswap/static/js/pricechart.js @@ -145,7 +145,7 @@ const api = { const apiResponse = await Api.fetchCoinGeckoData({ coinGecko: window.config.getAPIKeys().coinGecko }); - + if (!apiResponse || !apiResponse.rates) { if (fallbackData) { return fallbackData; @@ -174,7 +174,7 @@ const api = { const wowResponse = await Api.fetchCoinPrices("wownero", { coinGecko: window.config.getAPIKeys().coinGecko }); - + if (wowResponse && wowResponse.rates && wowResponse.rates.wownero) { transformedData['wow'] = { current_price: wowResponse.rates.wownero, @@ -189,9 +189,9 @@ const api = { console.error('Error fetching WOW price:', wowError); } - const missingCoins = window.config.coins.filter(coin => - !transformedData[coin.symbol.toLowerCase()] && - fallbackData && + const missingCoins = window.config.coins.filter(coin => + !transformedData[coin.symbol.toLowerCase()] && + fallbackData && fallbackData[coin.symbol.toLowerCase()] ); @@ -203,11 +203,11 @@ const api = { }); CacheManager.set(cacheKey, transformedData, 'prices'); - + if (NetworkManager.getReconnectAttempts() > 0) { NetworkManager.resetReconnectAttempts(); } - + return transformedData; } catch (error) { console.error('Error fetching coin data:', error); @@ -250,8 +250,8 @@ const api = { } const historicalData = await Api.fetchHistoricalData( - coinSymbols, - window.config.currentResolution, + coinSymbols, + window.config.currentResolution, { cryptoCompare: window.config.getAPIKeys().cryptoCompare } @@ -260,7 +260,7 @@ const api = { Object.keys(historicalData).forEach(coin => { if (historicalData[coin]) { results[coin] = historicalData[coin]; - + const cacheKey = `historical_${coin}_${window.config.currentResolution}`; CacheManager.set(cacheKey, historicalData[coin], 'historical'); } @@ -279,7 +279,7 @@ const api = { results[coin] = cachedData.value; } } - + return results; } }, @@ -317,7 +317,7 @@ const rateLimiter = { try { await this.requestQueue[apiName]; - + const executeRequest = async () => { const waitTime = this.getWaitTime(apiName); if (waitTime > 0) { @@ -335,7 +335,7 @@ const rateLimiter = { return this.queueRequest(apiName, requestFn, retryCount + 1); } - if ((error.message.includes('timeout') || error.name === 'NetworkError') && + if ((error.message.includes('timeout') || error.name === 'NetworkError') && retryCount < this.retryDelays.length) { const delay = this.retryDelays[retryCount]; logger.warn(`Request failed, retrying in ${delay/1000} seconds...`); @@ -350,12 +350,12 @@ const rateLimiter = { this.requestQueue[apiName] = executeRequest(); return await this.requestQueue[apiName]; } catch (error) { - if (error.message.includes('429') || - error.message.includes('timeout') || + if (error.message.includes('429') || + error.message.includes('timeout') || error.name === 'NetworkError') { - + NetworkManager.handleNetworkError(error); - + const cachedData = CacheManager.get(`coinData_${apiName}`); if (cachedData) { return cachedData.value; @@ -375,7 +375,7 @@ const ui = { 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)}`; } @@ -490,8 +490,8 @@ const ui = { if (priceChange === null || priceChange === undefined) { container.innerHTML = 'N/A'; } else { - container.innerHTML = priceChange >= 0 ? - ui.positivePriceChangeHTML(priceChange) : + container.innerHTML = priceChange >= 0 ? + ui.positivePriceChangeHTML(priceChange) : ui.negativePriceChangeHTML(priceChange); } } @@ -504,7 +504,7 @@ const ui = { lastRefreshedElement.textContent = `Last Refreshed: ${formattedTime}`; } }, - + updateConnectionStatus: () => { const statusElement = document.getElementById('connection-status'); if (statusElement) { @@ -599,12 +599,12 @@ const ui = { NetworkManager.manualReconnect(); }; - const buttonContainer = errorOverlay.querySelector('.button-container') || + const buttonContainer = errorOverlay.querySelector('.button-container') || document.createElement('div'); buttonContainer.className = "button-container mt-4"; buttonContainer.innerHTML = ''; buttonContainer.appendChild(reconnectBtn); - + if (!errorOverlay.querySelector('.button-container')) { errorOverlay.querySelector('div').appendChild(buttonContainer); } @@ -701,7 +701,7 @@ const chartModule = { const gradient = ctx.createLinearGradient(0, 0, 0, 400); gradient.addColorStop(0, 'rgba(77, 132, 240, 0.2)'); gradient.addColorStop(1, 'rgba(77, 132, 240, 0)'); - + chartModule.chart = new Chart(ctx, { type: 'line', data: { @@ -897,7 +897,7 @@ const chartModule = { } rawDataPoints.sort((a, b) => a.time - b.time); - + let preparedData = []; if (window.config.currentResolution === 'day') { @@ -918,7 +918,7 @@ const chartModule = { closestPoint = point; } } - + if (closestPoint) { preparedData.push({ x: hourUnix, @@ -940,7 +940,7 @@ const chartModule = { y: point.close })); } - + if (preparedData.length === 0 && rawDataPoints.length > 0) { preparedData = rawDataPoints.map(point => ({ x: point.time, @@ -965,7 +965,7 @@ const chartModule = { if (data.length > 0) { const closestDataPoint = data.reduce((prev, curr) => - Math.abs(new Date(curr.x).getTime() - targetTime.getTime()) < + Math.abs(new Date(curr.x).getTime() - targetTime.getTime()) < Math.abs(new Date(prev.x).getTime() - targetTime.getTime()) ? curr : prev , data[0]); hourlyData.push({ @@ -989,7 +989,7 @@ const chartModule = { } chartModule.loadStartTime = Date.now(); const cacheKey = `chartData_${coinSymbol}_${window.config.currentResolution}`; - let cachedData = !forceRefresh ? CacheManager.get(cacheKey) : null; + const cachedData = !forceRefresh ? CacheManager.get(cacheKey) : null; let data; if (cachedData && Object.keys(cachedData.value).length > 0) { data = cachedData.value; @@ -1001,7 +1001,7 @@ const chartModule = { const allData = await api.fetchHistoricalDataXHR([coinSymbol]); data = allData[coinSymbol]; - + if (!data || Object.keys(data).length === 0) { throw new Error(`No data returned for ${coinSymbol}`); } @@ -1009,7 +1009,7 @@ const chartModule = { CacheManager.set(cacheKey, data, 'chart'); } catch (error) { NetworkManager.handleNetworkError(error); - + if (error.message.includes('429') && currentChartData.length > 0) { console.warn(`Rate limit hit for ${coinSymbol}, maintaining current chart`); chartModule.hideChartLoader(); @@ -1041,7 +1041,7 @@ const chartModule = { chartModule.chart.options.scales.x.time.unit = 'hour'; } else { const resolution = window.config.chartConfig.resolutions[window.config.currentResolution]; - chartModule.chart.options.scales.x.time.unit = + chartModule.chart.options.scales.x.time.unit = resolution && resolution.interval === 'hourly' ? 'hour' : window.config.currentResolution === 'year' ? 'month' : 'day'; } @@ -1086,7 +1086,7 @@ const chartModule = { loader.classList.add('hidden'); chart.classList.remove('hidden'); }, - + cleanup: function() { this.destroyChart(); this.currentCoin = null; @@ -1181,7 +1181,7 @@ const app = { 0 ); }); - + return app; }, @@ -1223,13 +1223,13 @@ const app = { } } }, - + loadAllCoinData: async function() { try { if (!NetworkManager.isOnline()) { throw new Error('Network is offline'); } - + const allCoinData = await api.fetchCoinGeckoDataXHR(); if (allCoinData.error) { throw new Error(allCoinData.error); @@ -1242,7 +1242,7 @@ const app = { for (const coin of window.config.coins) { const coinData = allCoinData[coin.symbol.toLowerCase()]; - + if (coinData) { coinData.displayName = coin.displayName || coin.symbol; @@ -1409,7 +1409,7 @@ const app = { const lastGeckoRequest = rateLimiter.lastRequestTime['coingecko'] || 0; const timeSinceLastRequest = Date.now() - lastGeckoRequest; const waitTime = Math.max(0, rateLimiter.minRequestInterval.coingecko - timeSinceLastRequest); - + if (waitTime > 0) { const seconds = Math.ceil(waitTime / 1000); ui.displayErrorMessage(`Rate limit: Please wait ${seconds} seconds before refreshing`); @@ -1480,7 +1480,7 @@ const app = { if (cachedData && cachedData.value && cachedData.value.total_volume) { coinData.total_volume = cachedData.value.total_volume; } - if (cachedData && cachedData.value && cachedData.value.price_change_percentage_24h && + if (cachedData && cachedData.value && cachedData.value.price_change_percentage_24h && !coinData.price_change_percentage_24h) { coinData.price_change_percentage_24h = cachedData.value.price_change_percentage_24h; } @@ -1541,7 +1541,7 @@ const app = { let countdown = 10; ui.displayErrorMessage(`Refresh failed: ${error.message}. Please try again later. (${countdown}s)`); - + const countdownInterval = setInterval(() => { countdown--; if (countdown > 0) { @@ -1652,7 +1652,7 @@ const app = { if (!NetworkManager.isOnline()) { throw new Error('Network is offline'); } - + const response = await Api.fetchCoinPrices("bitcoin"); if (response && response.rates && response.rates.bitcoin) { @@ -1715,7 +1715,7 @@ resolutionButtons.forEach(button => { button.addEventListener('click', () => { const resolution = button.id.split('-')[1]; const currentCoin = chartModule.currentCoin; - + if (currentCoin !== 'WOW' || resolution === 'day') { window.config.currentResolution = resolution; chartModule.updateChart(currentCoin, true); @@ -1726,12 +1726,12 @@ resolutionButtons.forEach(button => { function cleanup() { console.log('Starting cleanup process'); - + try { if (window.MemoryManager) { MemoryManager.forceCleanup(); } - + if (chartModule) { CleanupManager.registerResource('chartModule', chartModule, (cm) => { cm.cleanup(); @@ -1752,7 +1752,7 @@ function cleanup() { const cleanupCounts = CleanupManager.clearAll(); console.log('All resources cleaned up:', cleanupCounts); - + } catch (error) { console.error('Error during cleanup:', error); CleanupManager.clearAll(); @@ -1801,7 +1801,7 @@ document.addEventListener('DOMContentLoaded', () => { NetworkManager.initialize({ connectionTestEndpoint: '/json', connectionTestTimeout: 3000, - reconnectDelay: 5000, + reconnectDelay: 5000, maxReconnectAttempts: 5 }); window.networkManagerInitialized = true; diff --git a/basicswap/static/js/swaps_in_progress.js b/basicswap/static/js/swaps_in_progress.js index 6efe3d7..3724bbd 100644 --- a/basicswap/static/js/swaps_in_progress.js +++ b/basicswap/static/js/swaps_in_progress.js @@ -85,7 +85,7 @@ const getStatusClass = (status, tx_a, tx_b) => { const getTxStatusClass = (status) => { if (!status || status === 'None') return 'text-gray-400'; - + if (status.includes('Complete') || status.includes('Confirmed')) { return 'text-green-500'; } @@ -545,12 +545,12 @@ async function updateSwapsTable(options = {}) { const data = await response.json(); //console.log('Received swap data:', data); - state.swapsData = Array.isArray(data) + state.swapsData = Array.isArray(data) ? data.filter(swap => { const isActive = isActiveSwap(swap); //console.log(`Swap ${swap.bid_id}: ${isActive ? 'Active' : 'Inactive'}`, swap.bid_state); return isActive; - }) + }) : []; //console.log('Filtered active swaps:', state.swapsData); @@ -570,7 +570,7 @@ async function updateSwapsTable(options = {}) { } const totalPages = Math.ceil(state.swapsData.length / PAGE_SIZE); - + if (resetPage && state.swapsData.length > 0) { state.currentPage = 1; } @@ -631,18 +631,18 @@ async function updateSwapsTable(options = {}) { function isActiveSwap(swap) { const activeStates = [ - 'InProgress', - 'Accepted', - 'Delaying', + 'InProgress', + 'Accepted', + 'Delaying', 'Auto accept delay', 'Request accepted', //'Received', - 'Script coin locked', + 'Script coin locked', 'Scriptless coin locked', 'Script coin lock released', - - 'SendingInitialTx', + + 'SendingInitialTx', 'SendingPaymentTx', 'Exchanged script lock tx sigs msg', diff --git a/basicswap/static/js/ui/dropdown.js b/basicswap/static/js/ui/dropdown.js index 788382d..ed0ec86 100644 --- a/basicswap/static/js/ui/dropdown.js +++ b/basicswap/static/js/ui/dropdown.js @@ -62,7 +62,7 @@ this._handleOutsideClick = this._handleOutsideClick.bind(this); dropdownInstances.push(this); - + this.init(); } @@ -73,7 +73,7 @@ this._targetEl.style.position = 'fixed'; this._targetEl.style.zIndex = '40'; this._targetEl.classList.add('dropdown-menu'); - + this._setupEventListeners(); this._initialized = true; } @@ -120,8 +120,8 @@ } _handleOutsideClick(e) { - if (this._visible && - !this._targetEl.contains(e.target) && + if (this._visible && + !this._targetEl.contains(e.target) && !this._triggerEl.contains(e.target)) { this.hide(); } diff --git a/basicswap/templates/offer_new_1.html b/basicswap/templates/offer_new_1.html index da9f42c..777f744 100644 --- a/basicswap/templates/offer_new_1.html +++ b/basicswap/templates/offer_new_1.html @@ -69,7 +69,7 @@
-
+
@@ -236,7 +236,7 @@
-
+

Automatically adjusts the You Get value based on the rate you’ve entered. Without it, the rate value is automatically adjusted based on the number of coins you put in You Get.

@@ -248,7 +248,6 @@
-{% if debug_mode == true %}
@@ -258,7 +257,11 @@
+ {% if debug_ui_mode == true %} + {% else %} + + {% endif %}
@@ -267,7 +270,11 @@
+ {% if debug_ui_mode == true %} + {% else %} + + {% endif %}
@@ -278,37 +285,6 @@
-{% else %} -
-
-
-
-

Options

-
-
-
-
- -
-
- -

Allow bids with a different amount to the offer.

-
-
-
-
- -
-
- -

Allow bids with a different rate to the offer.

-
-
-
-
-
-
-{% endif %}