mirror of
https://github.com/basicswap/basicswap.git
synced 2026-05-09 07:52:13 +02:00
Refactor + Optimizations
This commit is contained in:
@@ -4,34 +4,35 @@ const ApiManager = (function() {
|
||||
isInitialized: false
|
||||
};
|
||||
|
||||
const config = {
|
||||
requestTimeout: 60000,
|
||||
retryDelays: [5000, 15000, 30000],
|
||||
rateLimits: {
|
||||
coingecko: {
|
||||
requestsPerMinute: 50,
|
||||
minInterval: 1200
|
||||
},
|
||||
cryptocompare: {
|
||||
requestsPerMinute: 30,
|
||||
minInterval: 2000
|
||||
function getConfig() {
|
||||
return window.config || window.ConfigManager || {
|
||||
requestTimeout: 60000,
|
||||
retryDelays: [5000, 15000, 30000],
|
||||
rateLimits: {
|
||||
coingecko: { requestsPerMinute: 50, minInterval: 1200 },
|
||||
cryptocompare: { requestsPerMinute: 30, minInterval: 2000 }
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const rateLimiter = {
|
||||
lastRequestTime: {},
|
||||
minRequestInterval: {
|
||||
coingecko: 1200,
|
||||
cryptocompare: 2000
|
||||
},
|
||||
requestQueue: {},
|
||||
retryDelays: [5000, 15000, 30000],
|
||||
|
||||
getMinInterval: function(apiName) {
|
||||
const config = getConfig();
|
||||
return config.rateLimits?.[apiName]?.minInterval || 1200;
|
||||
},
|
||||
|
||||
getRetryDelays: function() {
|
||||
const config = getConfig();
|
||||
return config.retryDelays || [5000, 15000, 30000];
|
||||
},
|
||||
|
||||
canMakeRequest: function(apiName) {
|
||||
const now = Date.now();
|
||||
const lastRequest = this.lastRequestTime[apiName] || 0;
|
||||
return (now - lastRequest) >= this.minRequestInterval[apiName];
|
||||
return (now - lastRequest) >= this.getMinInterval(apiName);
|
||||
},
|
||||
|
||||
updateLastRequestTime: function(apiName) {
|
||||
@@ -41,7 +42,7 @@ const ApiManager = (function() {
|
||||
getWaitTime: function(apiName) {
|
||||
const now = Date.now();
|
||||
const lastRequest = this.lastRequestTime[apiName] || 0;
|
||||
return Math.max(0, this.minRequestInterval[apiName] - (now - lastRequest));
|
||||
return Math.max(0, this.getMinInterval(apiName) - (now - lastRequest));
|
||||
},
|
||||
|
||||
queueRequest: async function(apiName, requestFn, retryCount = 0) {
|
||||
@@ -55,29 +56,30 @@ const ApiManager = (function() {
|
||||
const executeRequest = async () => {
|
||||
const waitTime = this.getWaitTime(apiName);
|
||||
if (waitTime > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, waitTime));
|
||||
await new Promise(resolve => CleanupManager.setTimeout(resolve, waitTime));
|
||||
}
|
||||
|
||||
try {
|
||||
this.updateLastRequestTime(apiName);
|
||||
return await requestFn();
|
||||
} catch (error) {
|
||||
if (error.message.includes('429') && retryCount < this.retryDelays.length) {
|
||||
const delay = this.retryDelays[retryCount];
|
||||
const retryDelays = this.getRetryDelays();
|
||||
if (error.message.includes('429') && retryCount < retryDelays.length) {
|
||||
const delay = retryDelays[retryCount];
|
||||
console.log(`Rate limit hit, retrying in ${delay/1000} seconds...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
await new Promise(resolve => CleanupManager.setTimeout(resolve, delay));
|
||||
return publicAPI.rateLimiter.queueRequest(apiName, requestFn, retryCount + 1);
|
||||
}
|
||||
|
||||
if ((error.message.includes('timeout') || error.name === 'NetworkError') &&
|
||||
retryCount < this.retryDelays.length) {
|
||||
const delay = this.retryDelays[retryCount];
|
||||
retryCount < retryDelays.length) {
|
||||
const delay = retryDelays[retryCount];
|
||||
console.warn(`Request failed, retrying in ${delay/1000} seconds...`, {
|
||||
apiName,
|
||||
retryCount,
|
||||
error: error.message
|
||||
});
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
await new Promise(resolve => CleanupManager.setTimeout(resolve, delay));
|
||||
return publicAPI.rateLimiter.queueRequest(apiName, requestFn, retryCount + 1);
|
||||
}
|
||||
|
||||
@@ -118,19 +120,7 @@ const ApiManager = (function() {
|
||||
}
|
||||
|
||||
if (options.config) {
|
||||
Object.assign(config, options.config);
|
||||
}
|
||||
|
||||
if (config.rateLimits) {
|
||||
Object.keys(config.rateLimits).forEach(api => {
|
||||
if (config.rateLimits[api].minInterval) {
|
||||
rateLimiter.minRequestInterval[api] = config.rateLimits[api].minInterval;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (config.retryDelays) {
|
||||
rateLimiter.retryDelays = [...config.retryDelays];
|
||||
console.log('[ApiManager] Config options provided, but using ConfigManager instead');
|
||||
}
|
||||
|
||||
if (window.CleanupManager) {
|
||||
@@ -143,6 +133,31 @@ const ApiManager = (function() {
|
||||
},
|
||||
|
||||
makeRequest: async function(url, method = 'GET', headers = {}, body = null) {
|
||||
if (window.ErrorHandler) {
|
||||
return window.ErrorHandler.safeExecuteAsync(async () => {
|
||||
const options = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...headers
|
||||
},
|
||||
signal: AbortSignal.timeout(getConfig().requestTimeout || 60000)
|
||||
};
|
||||
|
||||
if (body) {
|
||||
options.body = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const response = await fetch(url, options);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}, `ApiManager.makeRequest(${url})`, null);
|
||||
}
|
||||
|
||||
try {
|
||||
const options = {
|
||||
method: method,
|
||||
@@ -150,7 +165,7 @@ const ApiManager = (function() {
|
||||
'Content-Type': 'application/json',
|
||||
...headers
|
||||
},
|
||||
signal: AbortSignal.timeout(config.requestTimeout)
|
||||
signal: AbortSignal.timeout(getConfig().requestTimeout || 60000)
|
||||
};
|
||||
|
||||
if (body) {
|
||||
@@ -233,11 +248,8 @@ const ApiManager = (function() {
|
||||
.join(',') :
|
||||
'bitcoin,monero,particl,bitcoincash,pivx,firo,dash,litecoin,dogecoin,decred,namecoin';
|
||||
|
||||
//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');
|
||||
}
|
||||
@@ -260,80 +272,38 @@ const ApiManager = (function() {
|
||||
fetchVolumeData: async function() {
|
||||
return this.rateLimiter.queueRequest('coingecko', async () => {
|
||||
try {
|
||||
let coinList = (window.config && window.config.coins) ?
|
||||
window.config.coins
|
||||
.filter(coin => coin.usesCoinGecko)
|
||||
.map(coin => {
|
||||
return window.config.getCoinBackendId ?
|
||||
window.config.getCoinBackendId(coin.name) :
|
||||
(typeof getCoinBackendId === 'function' ?
|
||||
getCoinBackendId(coin.name) : coin.name.toLowerCase());
|
||||
})
|
||||
.join(',') :
|
||||
'bitcoin,monero,particl,bitcoin-cash,pivx,firo,dash,litecoin,dogecoin,decred,namecoin,wownero';
|
||||
const coinSymbols = window.CoinManager
|
||||
? window.CoinManager.getAllCoins().map(c => c.symbol).filter(symbol => symbol && symbol.trim() !== '')
|
||||
: (window.config.coins
|
||||
? window.config.coins.map(c => c.symbol).filter(symbol => symbol && symbol.trim() !== '')
|
||||
: ['BTC', 'XMR', 'PART', 'BCH', 'PIVX', 'FIRO', 'DASH', 'LTC', 'DOGE', 'DCR', 'NMC', 'WOW']);
|
||||
|
||||
if (!coinList.includes('zcoin') && coinList.includes('firo')) {
|
||||
coinList = coinList + ',zcoin';
|
||||
}
|
||||
|
||||
const url = `https://api.coingecko.com/api/v3/simple/price?ids=${coinList}&vs_currencies=usd&include_24hr_vol=true&include_24hr_change=true`;
|
||||
|
||||
const response = await this.makePostRequest(url, {
|
||||
'User-Agent': 'Mozilla/5.0',
|
||||
'Accept': 'application/json'
|
||||
const response = await this.makeRequest('/json/coinvolume', 'POST', {}, {
|
||||
coins: coinSymbols.join(','),
|
||||
source: 'coingecko.com',
|
||||
ttl: 300
|
||||
});
|
||||
|
||||
if (!response || typeof response !== 'object') {
|
||||
throw new Error('Invalid response from CoinGecko API');
|
||||
if (!response) {
|
||||
console.error('No response from backend');
|
||||
throw new Error('Invalid response from backend');
|
||||
}
|
||||
|
||||
if (!response.data) {
|
||||
console.error('Response missing data field:', response);
|
||||
throw new Error('Invalid response from backend');
|
||||
}
|
||||
|
||||
const volumeData = {};
|
||||
|
||||
Object.entries(response).forEach(([coinId, data]) => {
|
||||
if (data && data.usd_24h_vol !== undefined) {
|
||||
volumeData[coinId] = {
|
||||
total_volume: data.usd_24h_vol || 0,
|
||||
price_change_percentage_24h: data.usd_24h_change || 0
|
||||
};
|
||||
}
|
||||
Object.entries(response.data).forEach(([coinSymbol, data]) => {
|
||||
const coinKey = coinSymbol.toLowerCase();
|
||||
volumeData[coinKey] = {
|
||||
total_volume: (data.volume_24h !== undefined && data.volume_24h !== null) ? data.volume_24h : null,
|
||||
price_change_percentage_24h: data.price_change_24h || 0
|
||||
};
|
||||
});
|
||||
|
||||
const coinMappings = {
|
||||
'firo': ['firo', 'zcoin'],
|
||||
'zcoin': ['zcoin', 'firo'],
|
||||
'bitcoin-cash': ['bitcoin-cash', 'bitcoincash', 'bch'],
|
||||
'bitcoincash': ['bitcoincash', 'bitcoin-cash', 'bch'],
|
||||
'particl': ['particl', 'part']
|
||||
};
|
||||
|
||||
if (response['zcoin'] && (!volumeData['firo'] || volumeData['firo'].total_volume === 0)) {
|
||||
volumeData['firo'] = {
|
||||
total_volume: response['zcoin'].usd_24h_vol || 0,
|
||||
price_change_percentage_24h: response['zcoin'].usd_24h_change || 0
|
||||
};
|
||||
}
|
||||
|
||||
if (response['bitcoin-cash'] && (!volumeData['bitcoincash'] || volumeData['bitcoincash'].total_volume === 0)) {
|
||||
volumeData['bitcoincash'] = {
|
||||
total_volume: response['bitcoin-cash'].usd_24h_vol || 0,
|
||||
price_change_percentage_24h: response['bitcoin-cash'].usd_24h_change || 0
|
||||
};
|
||||
}
|
||||
|
||||
for (const [mainCoin, alternativeIds] of Object.entries(coinMappings)) {
|
||||
if (!volumeData[mainCoin] || volumeData[mainCoin].total_volume === 0) {
|
||||
for (const altId of alternativeIds) {
|
||||
if (response[altId] && response[altId].usd_24h_vol) {
|
||||
volumeData[mainCoin] = {
|
||||
total_volume: response[altId].usd_24h_vol,
|
||||
price_change_percentage_24h: response[altId].usd_24h_change || 0
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return volumeData;
|
||||
} catch (error) {
|
||||
console.error("Error fetching volume data:", error);
|
||||
@@ -342,104 +312,45 @@ const ApiManager = (function() {
|
||||
});
|
||||
},
|
||||
|
||||
fetchCryptoCompareData: function(coin) {
|
||||
return this.rateLimiter.queueRequest('cryptocompare', async () => {
|
||||
try {
|
||||
const apiKey = window.config?.apiKeys?.cryptoCompare || '';
|
||||
const url = `https://min-api.cryptocompare.com/data/pricemultifull?fsyms=${coin}&tsyms=USD,BTC&api_key=${apiKey}`;
|
||||
const headers = {
|
||||
'User-Agent': 'Mozilla/5.0',
|
||||
'Accept': 'application/json'
|
||||
};
|
||||
|
||||
return await this.makePostRequest(url, headers);
|
||||
} catch (error) {
|
||||
console.error(`CryptoCompare request failed for ${coin}:`, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
fetchHistoricalData: async function(coinSymbols, resolution = 'day') {
|
||||
if (!Array.isArray(coinSymbols)) {
|
||||
coinSymbols = [coinSymbols];
|
||||
}
|
||||
|
||||
const results = {};
|
||||
const fetchPromises = coinSymbols.map(async coin => {
|
||||
let useCoinGecko = false;
|
||||
let coingeckoId = null;
|
||||
|
||||
if (window.CoinManager) {
|
||||
const coinConfig = window.CoinManager.getCoinByAnyIdentifier(coin);
|
||||
if (coinConfig) {
|
||||
useCoinGecko = !coinConfig.usesCryptoCompare || coin === 'PART';
|
||||
coingeckoId = coinConfig.coingeckoId;
|
||||
return this.rateLimiter.queueRequest('coingecko', async () => {
|
||||
try {
|
||||
let days;
|
||||
if (resolution === 'day') {
|
||||
days = 1;
|
||||
} else if (resolution === 'year') {
|
||||
days = 365;
|
||||
} else {
|
||||
days = 180;
|
||||
}
|
||||
} else {
|
||||
const coinGeckoCoins = {
|
||||
'WOW': 'wownero',
|
||||
'PART': 'particl',
|
||||
'BTC': 'bitcoin'
|
||||
};
|
||||
if (coinGeckoCoins[coin]) {
|
||||
useCoinGecko = true;
|
||||
coingeckoId = coinGeckoCoins[coin];
|
||||
|
||||
const response = await this.makeRequest('/json/coinhistory', 'POST', {}, {
|
||||
coins: coinSymbols.join(','),
|
||||
days: days,
|
||||
source: 'coingecko.com',
|
||||
ttl: 3600
|
||||
});
|
||||
|
||||
if (!response) {
|
||||
console.error('No response from backend');
|
||||
throw new Error('Invalid response from backend');
|
||||
}
|
||||
}
|
||||
|
||||
if (useCoinGecko && coingeckoId) {
|
||||
return this.rateLimiter.queueRequest('coingecko', async () => {
|
||||
let days;
|
||||
if (resolution === 'day') {
|
||||
days = 1;
|
||||
} else if (resolution === 'year') {
|
||||
days = 365;
|
||||
} else {
|
||||
days = 180;
|
||||
}
|
||||
const url = `https://api.coingecko.com/api/v3/coins/${coingeckoId}/market_chart?vs_currency=usd&days=${days}`;
|
||||
try {
|
||||
const response = await this.makePostRequest(url);
|
||||
if (response && response.prices) {
|
||||
results[coin] = response.prices;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching CoinGecko data for ${coin}:`, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return this.rateLimiter.queueRequest('cryptocompare', async () => {
|
||||
try {
|
||||
const apiKey = window.config?.apiKeys?.cryptoCompare || '';
|
||||
let url;
|
||||
if (!response.data) {
|
||||
console.error('Response missing data field:', response);
|
||||
throw new Error('Invalid response from backend');
|
||||
}
|
||||
|
||||
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') {
|
||||
url = `https://min-api.cryptocompare.com/data/v2/histoday?fsym=${coin}&tsym=USD&limit=365&api_key=${apiKey}`;
|
||||
} else {
|
||||
url = `https://min-api.cryptocompare.com/data/v2/histoday?fsym=${coin}&tsym=USD&limit=180&api_key=${apiKey}`;
|
||||
}
|
||||
|
||||
const response = await this.makePostRequest(url);
|
||||
if (response.Response === "Error") {
|
||||
console.error(`API Error for ${coin}:`, response.Message);
|
||||
throw new Error(response.Message);
|
||||
} else if (response.Data && response.Data.Data) {
|
||||
results[coin] = response.Data;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching CryptoCompare data for ${coin}:`, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching historical data:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(fetchPromises);
|
||||
return results;
|
||||
},
|
||||
|
||||
dispose: function() {
|
||||
@@ -453,17 +364,6 @@ const ApiManager = (function() {
|
||||
return publicAPI;
|
||||
})();
|
||||
|
||||
function getCoinBackendId(coinName) {
|
||||
const nameMap = {
|
||||
'bitcoin-cash': 'bitcoincash',
|
||||
'bitcoin cash': 'bitcoincash',
|
||||
'firo': 'zcoin',
|
||||
'zcoin': 'zcoin',
|
||||
'bitcoincash': 'bitcoin-cash'
|
||||
};
|
||||
return nameMap[coinName.toLowerCase()] || coinName.toLowerCase();
|
||||
}
|
||||
|
||||
window.Api = ApiManager;
|
||||
window.ApiManager = ApiManager;
|
||||
|
||||
@@ -474,5 +374,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
//console.log('ApiManager initialized with methods:', Object.keys(ApiManager));
|
||||
console.log('ApiManager initialized');
|
||||
|
||||
@@ -15,7 +15,21 @@ const BalanceUpdatesManager = (function() {
|
||||
initialized: false
|
||||
};
|
||||
|
||||
function fetchBalanceData() {
|
||||
async function fetchBalanceData() {
|
||||
if (window.ApiManager) {
|
||||
const data = await window.ApiManager.makeRequest('/json/walletbalances', 'GET');
|
||||
|
||||
if (data && data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error('Invalid response format');
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return fetch('/json/walletbalances', {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
@@ -43,27 +57,41 @@ const BalanceUpdatesManager = (function() {
|
||||
|
||||
function clearTimeoutByKey(key) {
|
||||
if (state.timeouts.has(key)) {
|
||||
clearTimeout(state.timeouts.get(key));
|
||||
const timeoutId = state.timeouts.get(key);
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.clearTimeout(timeoutId);
|
||||
} else {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
state.timeouts.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
function setTimeoutByKey(key, callback, delay) {
|
||||
clearTimeoutByKey(key);
|
||||
const timeoutId = setTimeout(callback, delay);
|
||||
const timeoutId = window.CleanupManager
|
||||
? window.CleanupManager.setTimeout(callback, delay)
|
||||
: setTimeout(callback, delay);
|
||||
state.timeouts.set(key, timeoutId);
|
||||
}
|
||||
|
||||
function clearIntervalByKey(key) {
|
||||
if (state.intervals.has(key)) {
|
||||
clearInterval(state.intervals.get(key));
|
||||
const intervalId = state.intervals.get(key);
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.clearInterval(intervalId);
|
||||
} else {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
state.intervals.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
function setIntervalByKey(key, callback, interval) {
|
||||
clearIntervalByKey(key);
|
||||
const intervalId = setInterval(callback, interval);
|
||||
const intervalId = window.CleanupManager
|
||||
? window.CleanupManager.setInterval(callback, interval)
|
||||
: setInterval(callback, interval);
|
||||
state.intervals.set(key, intervalId);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
const CacheManager = (function() {
|
||||
const defaults = window.config?.cacheConfig?.storage || {
|
||||
maxSizeBytes: 10 * 1024 * 1024,
|
||||
maxItems: 200,
|
||||
defaultTTL: 5 * 60 * 1000
|
||||
};
|
||||
function getDefaults() {
|
||||
if (window.config?.cacheConfig?.storage) {
|
||||
return window.config.cacheConfig.storage;
|
||||
}
|
||||
if (window.ConfigManager?.cacheConfig?.storage) {
|
||||
return window.ConfigManager.cacheConfig.storage;
|
||||
}
|
||||
return {
|
||||
maxSizeBytes: 10 * 1024 * 1024,
|
||||
maxItems: 200,
|
||||
defaultTTL: 5 * 60 * 1000
|
||||
};
|
||||
}
|
||||
|
||||
const defaults = getDefaults();
|
||||
|
||||
const PRICES_CACHE_KEY = 'crypto_prices_unified';
|
||||
|
||||
@@ -45,8 +55,12 @@ const CacheManager = (function() {
|
||||
|
||||
const cacheAPI = {
|
||||
getTTL: function(resourceType) {
|
||||
const ttlConfig = window.config?.cacheConfig?.ttlSettings || {};
|
||||
return ttlConfig[resourceType] || window.config?.cacheConfig?.defaultTTL || defaults.defaultTTL;
|
||||
const ttlConfig = window.config?.cacheConfig?.ttlSettings ||
|
||||
window.ConfigManager?.cacheConfig?.ttlSettings || {};
|
||||
const defaultTTL = window.config?.cacheConfig?.defaultTTL ||
|
||||
window.ConfigManager?.cacheConfig?.defaultTTL ||
|
||||
defaults.defaultTTL;
|
||||
return ttlConfig[resourceType] || defaultTTL;
|
||||
},
|
||||
|
||||
set: function(key, value, resourceTypeOrCustomTtl = null) {
|
||||
@@ -73,13 +87,18 @@ const CacheManager = (function() {
|
||||
expiresAt: Date.now() + ttl
|
||||
};
|
||||
|
||||
let serializedItem;
|
||||
try {
|
||||
serializedItem = JSON.stringify(item);
|
||||
} catch (e) {
|
||||
console.error('Failed to serialize cache item:', e);
|
||||
return false;
|
||||
}
|
||||
const serializedItem = window.ErrorHandler
|
||||
? window.ErrorHandler.safeExecute(() => JSON.stringify(item), 'CacheManager.set.serialize', null)
|
||||
: (() => {
|
||||
try {
|
||||
return JSON.stringify(item);
|
||||
} catch (e) {
|
||||
console.error('Failed to serialize cache item:', e);
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
if (!serializedItem) return false;
|
||||
|
||||
const itemSize = new Blob([serializedItem]).size;
|
||||
if (itemSize > defaults.maxSizeBytes) {
|
||||
@@ -118,7 +137,7 @@ const CacheManager = (function() {
|
||||
const keysToDelete = Array.from(memoryCache.keys())
|
||||
.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%
|
||||
.slice(0, Math.floor(memoryCache.size * 0.2));
|
||||
|
||||
keysToDelete.forEach(k => memoryCache.delete(k));
|
||||
}
|
||||
@@ -285,7 +304,7 @@ const CacheManager = (function() {
|
||||
const keysToDelete = Array.from(memoryCache.keys())
|
||||
.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
|
||||
.slice(0, Math.floor(memoryCache.size * 0.3));
|
||||
|
||||
keysToDelete.forEach(key => memoryCache.delete(key));
|
||||
}
|
||||
@@ -328,7 +347,6 @@ const CacheManager = (function() {
|
||||
.filter(key => isCacheKey(key))
|
||||
.forEach(key => memoryCache.delete(key));
|
||||
|
||||
//console.log("Cache cleared successfully");
|
||||
return true;
|
||||
},
|
||||
|
||||
@@ -531,6 +549,4 @@ const CacheManager = (function() {
|
||||
|
||||
window.CacheManager = CacheManager;
|
||||
|
||||
|
||||
//console.log('CacheManager initialized with methods:', Object.keys(CacheManager));
|
||||
console.log('CacheManager initialized');
|
||||
|
||||
@@ -233,7 +233,7 @@ const CleanupManager = (function() {
|
||||
},
|
||||
|
||||
setupMemoryOptimization: function(options = {}) {
|
||||
const memoryCheckInterval = options.interval || 2 * 60 * 1000; // Default: 2 minutes
|
||||
const memoryCheckInterval = options.interval || 2 * 60 * 1000;
|
||||
const maxCacheSize = options.maxCacheSize || 100;
|
||||
const maxDataSize = options.maxDataSize || 1000;
|
||||
|
||||
|
||||
@@ -178,19 +178,7 @@ const CoinManager = (function() {
|
||||
function getCoinByAnyIdentifier(identifier) {
|
||||
if (!identifier) return null;
|
||||
const normalizedId = identifier.toString().toLowerCase().trim();
|
||||
const coin = coinAliasesMap[normalizedId];
|
||||
if (coin) return coin;
|
||||
if (normalizedId.includes('bitcoin') && normalizedId.includes('cash') ||
|
||||
normalizedId === 'bch') {
|
||||
return symbolToInfo['bch'];
|
||||
}
|
||||
if (normalizedId === 'zcoin' || normalizedId.includes('firo')) {
|
||||
return symbolToInfo['firo'];
|
||||
}
|
||||
if (normalizedId.includes('particl')) {
|
||||
return symbolToInfo['part'];
|
||||
}
|
||||
return null;
|
||||
return coinAliasesMap[normalizedId] || null;
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
const CoinUtils = (function() {
|
||||
function buildAliasesFromCoinManager() {
|
||||
const aliases = {};
|
||||
const symbolMap = {};
|
||||
|
||||
if (window.CoinManager) {
|
||||
const coins = window.CoinManager.getAllCoins();
|
||||
coins.forEach(coin => {
|
||||
const canonical = coin.name.toLowerCase();
|
||||
aliases[canonical] = coin.aliases || [coin.name.toLowerCase()];
|
||||
symbolMap[canonical] = coin.symbol;
|
||||
});
|
||||
}
|
||||
|
||||
return { aliases, symbolMap };
|
||||
}
|
||||
|
||||
let COIN_ALIASES = {};
|
||||
let CANONICAL_TO_SYMBOL = {};
|
||||
|
||||
function initializeAliases() {
|
||||
const { aliases, symbolMap } = buildAliasesFromCoinManager();
|
||||
COIN_ALIASES = aliases;
|
||||
CANONICAL_TO_SYMBOL = symbolMap;
|
||||
}
|
||||
|
||||
if (window.CoinManager) {
|
||||
initializeAliases();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (window.CoinManager) {
|
||||
initializeAliases();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getCanonicalName(coin) {
|
||||
if (!coin) return null;
|
||||
const lower = coin.toString().toLowerCase().trim();
|
||||
|
||||
for (const [canonical, aliases] of Object.entries(COIN_ALIASES)) {
|
||||
if (aliases.includes(lower)) {
|
||||
return canonical;
|
||||
}
|
||||
}
|
||||
return lower;
|
||||
}
|
||||
|
||||
return {
|
||||
normalizeCoinName: function(coin, priceData = null) {
|
||||
const canonical = getCanonicalName(coin);
|
||||
if (!canonical) return null;
|
||||
|
||||
if (priceData) {
|
||||
if (canonical === 'bitcoin-cash') {
|
||||
if (priceData['bitcoin-cash']) return 'bitcoin-cash';
|
||||
if (priceData['bch']) return 'bch';
|
||||
if (priceData['bitcoincash']) return 'bitcoincash';
|
||||
return 'bitcoin-cash';
|
||||
}
|
||||
|
||||
if (canonical === 'particl') {
|
||||
if (priceData['part']) return 'part';
|
||||
if (priceData['particl']) return 'particl';
|
||||
return 'part';
|
||||
}
|
||||
}
|
||||
|
||||
return canonical;
|
||||
},
|
||||
|
||||
isSameCoin: function(coin1, coin2) {
|
||||
if (!coin1 || !coin2) return false;
|
||||
|
||||
if (window.CoinManager) {
|
||||
return window.CoinManager.coinMatches(coin1, coin2);
|
||||
}
|
||||
|
||||
const canonical1 = getCanonicalName(coin1);
|
||||
const canonical2 = getCanonicalName(coin2);
|
||||
if (canonical1 === canonical2) return true;
|
||||
|
||||
const lower1 = coin1.toString().toLowerCase().trim();
|
||||
const lower2 = coin2.toString().toLowerCase().trim();
|
||||
|
||||
const particlVariants = ['particl', 'particl anon', 'particl blind', 'part', 'part_anon', 'part_blind'];
|
||||
if (particlVariants.includes(lower1) && particlVariants.includes(lower2)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lower1.includes(' ') || lower2.includes(' ')) {
|
||||
const word1 = lower1.split(' ')[0];
|
||||
const word2 = lower2.split(' ')[0];
|
||||
if (word1 === word2 && word1.length > 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
getCoinSymbol: function(identifier) {
|
||||
if (!identifier) return null;
|
||||
|
||||
if (window.CoinManager) {
|
||||
const coin = window.CoinManager.getCoinByAnyIdentifier(identifier);
|
||||
if (coin) return coin.symbol;
|
||||
}
|
||||
|
||||
const canonical = getCanonicalName(identifier);
|
||||
if (canonical && CANONICAL_TO_SYMBOL[canonical]) {
|
||||
return CANONICAL_TO_SYMBOL[canonical];
|
||||
}
|
||||
|
||||
return identifier.toString().toUpperCase();
|
||||
},
|
||||
|
||||
getDisplayName: function(identifier) {
|
||||
if (!identifier) return null;
|
||||
|
||||
if (window.CoinManager) {
|
||||
const coin = window.CoinManager.getCoinByAnyIdentifier(identifier);
|
||||
if (coin) return coin.displayName || coin.name;
|
||||
}
|
||||
|
||||
const symbol = this.getCoinSymbol(identifier);
|
||||
return symbol || identifier;
|
||||
},
|
||||
|
||||
getCoinImage: function(coinName) {
|
||||
if (!coinName) return null;
|
||||
|
||||
const canonical = getCanonicalName(coinName);
|
||||
const symbol = this.getCoinSymbol(canonical);
|
||||
|
||||
if (!symbol) return null;
|
||||
|
||||
const imagePath = `/static/images/coins/${symbol.toLowerCase()}.png`;
|
||||
return imagePath;
|
||||
},
|
||||
|
||||
getPriceKey: function(coin, priceData = null) {
|
||||
return this.normalizeCoinName(coin, priceData);
|
||||
},
|
||||
|
||||
getCoingeckoId: function(coinName) {
|
||||
if (!coinName) return null;
|
||||
|
||||
if (window.CoinManager) {
|
||||
const coin = window.CoinManager.getCoinByAnyIdentifier(coinName);
|
||||
if (coin && coin.coingeckoId) {
|
||||
return coin.coingeckoId;
|
||||
}
|
||||
}
|
||||
|
||||
const canonical = getCanonicalName(coinName);
|
||||
return canonical;
|
||||
},
|
||||
|
||||
formatCoinAmount: function(amount, decimals = 8) {
|
||||
if (amount === null || amount === undefined) return '0';
|
||||
|
||||
const numAmount = parseFloat(amount);
|
||||
if (isNaN(numAmount)) return '0';
|
||||
|
||||
return numAmount.toFixed(decimals).replace(/\.?0+$/, '');
|
||||
},
|
||||
|
||||
getAllAliases: function(coin) {
|
||||
const canonical = getCanonicalName(coin);
|
||||
return COIN_ALIASES[canonical] || [canonical];
|
||||
},
|
||||
|
||||
isValidCoin: function(coin) {
|
||||
if (!coin) return false;
|
||||
const canonical = getCanonicalName(coin);
|
||||
return canonical !== null && COIN_ALIASES.hasOwnProperty(canonical);
|
||||
},
|
||||
|
||||
refreshAliases: function() {
|
||||
initializeAliases();
|
||||
return Object.keys(COIN_ALIASES).length;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.CoinUtils = CoinUtils;
|
||||
}
|
||||
|
||||
console.log('CoinUtils module loaded');
|
||||
@@ -53,20 +53,11 @@ const ConfigManager = (function() {
|
||||
},
|
||||
retryDelays: [5000, 15000, 30000],
|
||||
get coins() {
|
||||
return window.CoinManager ? window.CoinManager.getAllCoins() : [
|
||||
{ symbol: 'BTC', name: 'bitcoin', usesCryptoCompare: false, usesCoinGecko: true, historicalDays: 30 },
|
||||
{ symbol: 'XMR', name: 'monero', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
|
||||
{ symbol: 'PART', name: 'particl', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
|
||||
{ symbol: 'BCH', name: 'bitcoincash', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
|
||||
{ symbol: 'PIVX', name: 'pivx', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
|
||||
{ symbol: 'FIRO', name: 'firo', displayName: 'Firo', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
|
||||
{ symbol: 'DASH', name: 'dash', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
|
||||
{ symbol: 'LTC', name: 'litecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
|
||||
{ symbol: 'DOGE', name: 'dogecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
|
||||
{ symbol: 'DCR', name: 'decred', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
|
||||
{ symbol: 'NMC', name: 'namecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
|
||||
{ symbol: 'WOW', name: 'wownero', usesCryptoCompare: false, usesCoinGecko: true, historicalDays: 30 }
|
||||
];
|
||||
if (window.CoinManager) {
|
||||
return window.CoinManager.getAllCoins();
|
||||
}
|
||||
console.warn('[ConfigManager] CoinManager not available, returning empty array');
|
||||
return [];
|
||||
},
|
||||
chartConfig: {
|
||||
colors: {
|
||||
@@ -122,55 +113,20 @@ const ConfigManager = (function() {
|
||||
if (window.CoinManager) {
|
||||
return window.CoinManager.getPriceKey(coinName);
|
||||
}
|
||||
const nameMap = {
|
||||
'bitcoin-cash': 'bitcoincash',
|
||||
'bitcoin cash': 'bitcoincash',
|
||||
'firo': 'firo',
|
||||
'zcoin': 'firo',
|
||||
'bitcoincash': 'bitcoin-cash'
|
||||
};
|
||||
const lowerCoinName = typeof coinName === 'string' ? coinName.toLowerCase() : '';
|
||||
return nameMap[lowerCoinName] || lowerCoinName;
|
||||
if (window.CoinUtils) {
|
||||
return window.CoinUtils.normalizeCoinName(coinName);
|
||||
}
|
||||
return typeof coinName === 'string' ? coinName.toLowerCase() : '';
|
||||
},
|
||||
coinMatches: function(offerCoin, filterCoin) {
|
||||
if (!offerCoin || !filterCoin) return false;
|
||||
if (window.CoinManager) {
|
||||
return window.CoinManager.coinMatches(offerCoin, filterCoin);
|
||||
}
|
||||
offerCoin = offerCoin.toLowerCase();
|
||||
filterCoin = filterCoin.toLowerCase();
|
||||
if (offerCoin === filterCoin) return true;
|
||||
if ((offerCoin === 'firo' || offerCoin === 'zcoin') &&
|
||||
(filterCoin === 'firo' || filterCoin === 'zcoin')) {
|
||||
return true;
|
||||
if (window.CoinUtils) {
|
||||
return window.CoinUtils.isSameCoin(offerCoin, filterCoin);
|
||||
}
|
||||
if ((offerCoin === 'bitcoincash' && filterCoin === 'bitcoin cash') ||
|
||||
(offerCoin === 'bitcoin cash' && filterCoin === 'bitcoincash')) {
|
||||
return true;
|
||||
}
|
||||
const particlVariants = ['particl', 'particl anon', 'particl blind'];
|
||||
if (filterCoin === 'particl' && particlVariants.includes(offerCoin)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (filterCoin.includes(' ') || offerCoin.includes(' ')) {
|
||||
const filterFirstWord = filterCoin.split(' ')[0];
|
||||
const offerFirstWord = offerCoin.split(' ')[0];
|
||||
|
||||
if (filterFirstWord === 'bitcoin' && offerFirstWord === 'bitcoin') {
|
||||
const filterHasCash = filterCoin.includes('cash');
|
||||
const offerHasCash = offerCoin.includes('cash');
|
||||
return filterHasCash === offerHasCash;
|
||||
}
|
||||
|
||||
if (filterFirstWord === offerFirstWord && filterFirstWord.length > 4) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (particlVariants.includes(filterCoin)) {
|
||||
return offerCoin === filterCoin;
|
||||
}
|
||||
return false;
|
||||
return offerCoin.toLowerCase() === filterCoin.toLowerCase();
|
||||
},
|
||||
update: function(path, value) {
|
||||
const parts = path.split('.');
|
||||
@@ -229,7 +185,7 @@ const ConfigManager = (function() {
|
||||
let timeoutId;
|
||||
return function(...args) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = setTimeout(() => func(...args), delay);
|
||||
timeoutId = CleanupManager.setTimeout(() => func(...args), delay);
|
||||
};
|
||||
},
|
||||
formatTimeLeft: function(timestamp) {
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const originalGetElementById = document.getElementById.bind(document);
|
||||
|
||||
const DOMCache = {
|
||||
|
||||
cache: {},
|
||||
|
||||
get: function(id, forceRefresh = false) {
|
||||
if (!id) {
|
||||
console.warn('DOMCache: No ID provided');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!forceRefresh && this.cache[id]) {
|
||||
|
||||
if (document.body.contains(this.cache[id])) {
|
||||
return this.cache[id];
|
||||
} else {
|
||||
|
||||
delete this.cache[id];
|
||||
}
|
||||
}
|
||||
|
||||
const element = originalGetElementById(id);
|
||||
if (element) {
|
||||
this.cache[id] = element;
|
||||
}
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
getMultiple: function(ids) {
|
||||
const elements = {};
|
||||
ids.forEach(id => {
|
||||
elements[id] = this.get(id);
|
||||
});
|
||||
return elements;
|
||||
},
|
||||
|
||||
setValue: function(id, value) {
|
||||
const element = this.get(id);
|
||||
if (element) {
|
||||
element.value = value;
|
||||
return true;
|
||||
}
|
||||
console.warn(`DOMCache: Element not found: ${id}`);
|
||||
return false;
|
||||
},
|
||||
|
||||
getValue: function(id, defaultValue = '') {
|
||||
const element = this.get(id);
|
||||
return element ? element.value : defaultValue;
|
||||
},
|
||||
|
||||
setText: function(id, text) {
|
||||
const element = this.get(id);
|
||||
if (element) {
|
||||
element.textContent = text;
|
||||
return true;
|
||||
}
|
||||
console.warn(`DOMCache: Element not found: ${id}`);
|
||||
return false;
|
||||
},
|
||||
|
||||
getText: function(id, defaultValue = '') {
|
||||
const element = this.get(id);
|
||||
return element ? element.textContent : defaultValue;
|
||||
},
|
||||
|
||||
addClass: function(id, className) {
|
||||
const element = this.get(id);
|
||||
if (element) {
|
||||
element.classList.add(className);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
removeClass: function(id, className) {
|
||||
const element = this.get(id);
|
||||
if (element) {
|
||||
element.classList.remove(className);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
toggleClass: function(id, className) {
|
||||
const element = this.get(id);
|
||||
if (element) {
|
||||
element.classList.toggle(className);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
show: function(id) {
|
||||
const element = this.get(id);
|
||||
if (element) {
|
||||
element.style.display = '';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
hide: function(id) {
|
||||
const element = this.get(id);
|
||||
if (element) {
|
||||
element.style.display = 'none';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
exists: function(id) {
|
||||
return this.get(id) !== null;
|
||||
},
|
||||
|
||||
clear: function(id) {
|
||||
if (id) {
|
||||
delete this.cache[id];
|
||||
} else {
|
||||
this.cache = {};
|
||||
}
|
||||
},
|
||||
|
||||
size: function() {
|
||||
return Object.keys(this.cache).length;
|
||||
},
|
||||
|
||||
validate: function() {
|
||||
const ids = Object.keys(this.cache);
|
||||
let removed = 0;
|
||||
|
||||
ids.forEach(id => {
|
||||
const element = this.cache[id];
|
||||
if (!document.body.contains(element)) {
|
||||
delete this.cache[id];
|
||||
removed++;
|
||||
}
|
||||
});
|
||||
|
||||
return removed;
|
||||
},
|
||||
|
||||
createScope: function(elementIds) {
|
||||
const scope = {};
|
||||
|
||||
elementIds.forEach(id => {
|
||||
Object.defineProperty(scope, id, {
|
||||
get: () => this.get(id),
|
||||
enumerable: true
|
||||
});
|
||||
});
|
||||
|
||||
return scope;
|
||||
},
|
||||
|
||||
batch: function(operations) {
|
||||
Object.keys(operations).forEach(id => {
|
||||
const ops = operations[id];
|
||||
const element = this.get(id);
|
||||
|
||||
if (!element) {
|
||||
console.warn(`DOMCache: Element not found in batch operation: ${id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ops.value !== undefined) element.value = ops.value;
|
||||
if (ops.text !== undefined) element.textContent = ops.text;
|
||||
if (ops.html !== undefined) element.innerHTML = ops.html;
|
||||
if (ops.class) element.classList.add(ops.class);
|
||||
if (ops.removeClass) element.classList.remove(ops.removeClass);
|
||||
if (ops.hide) element.style.display = 'none';
|
||||
if (ops.show) element.style.display = '';
|
||||
if (ops.disabled !== undefined) element.disabled = ops.disabled;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.DOMCache = DOMCache;
|
||||
|
||||
if (!window.$) {
|
||||
window.$ = function(id) {
|
||||
return DOMCache.get(id);
|
||||
};
|
||||
}
|
||||
|
||||
document.getElementById = function(id) {
|
||||
return DOMCache.get(id);
|
||||
};
|
||||
|
||||
document.getElementByIdOriginal = originalGetElementById;
|
||||
|
||||
if (window.CleanupManager) {
|
||||
const validationInterval = CleanupManager.setInterval(() => {
|
||||
DOMCache.validate();
|
||||
}, 30000);
|
||||
|
||||
CleanupManager.registerResource('domCacheValidation', validationInterval, () => {
|
||||
clearInterval(validationInterval);
|
||||
});
|
||||
}
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,215 @@
|
||||
const ErrorHandler = (function() {
|
||||
const config = {
|
||||
logErrors: true,
|
||||
throwErrors: false,
|
||||
errorCallbacks: []
|
||||
};
|
||||
|
||||
function formatError(error, context) {
|
||||
const timestamp = new Date().toISOString();
|
||||
const contextStr = context ? ` [${context}]` : '';
|
||||
|
||||
if (error instanceof Error) {
|
||||
return `${timestamp}${contextStr} ${error.name}: ${error.message}`;
|
||||
}
|
||||
|
||||
return `${timestamp}${contextStr} ${String(error)}`;
|
||||
}
|
||||
|
||||
function notifyCallbacks(error, context) {
|
||||
config.errorCallbacks.forEach(callback => {
|
||||
try {
|
||||
callback(error, context);
|
||||
} catch (e) {
|
||||
console.error('[ErrorHandler] Error in callback:', e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
configure: function(options = {}) {
|
||||
Object.assign(config, options);
|
||||
return this;
|
||||
},
|
||||
|
||||
addCallback: function(callback) {
|
||||
if (typeof callback === 'function') {
|
||||
config.errorCallbacks.push(callback);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
removeCallback: function(callback) {
|
||||
const index = config.errorCallbacks.indexOf(callback);
|
||||
if (index > -1) {
|
||||
config.errorCallbacks.splice(index, 1);
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
||||
safeExecute: function(fn, context = null, fallbackValue = null) {
|
||||
try {
|
||||
return fn();
|
||||
} catch (error) {
|
||||
if (config.logErrors) {
|
||||
console.error(formatError(error, context));
|
||||
}
|
||||
|
||||
notifyCallbacks(error, context);
|
||||
|
||||
if (config.throwErrors) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
}
|
||||
},
|
||||
|
||||
safeExecuteAsync: async function(fn, context = null, fallbackValue = null) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
if (config.logErrors) {
|
||||
console.error(formatError(error, context));
|
||||
}
|
||||
|
||||
notifyCallbacks(error, context);
|
||||
|
||||
if (config.throwErrors) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
}
|
||||
},
|
||||
|
||||
wrap: function(fn, context = null, fallbackValue = null) {
|
||||
return (...args) => {
|
||||
try {
|
||||
return fn(...args);
|
||||
} catch (error) {
|
||||
if (config.logErrors) {
|
||||
console.error(formatError(error, context));
|
||||
}
|
||||
|
||||
notifyCallbacks(error, context);
|
||||
|
||||
if (config.throwErrors) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
wrapAsync: function(fn, context = null, fallbackValue = null) {
|
||||
return async (...args) => {
|
||||
try {
|
||||
return await fn(...args);
|
||||
} catch (error) {
|
||||
if (config.logErrors) {
|
||||
console.error(formatError(error, context));
|
||||
}
|
||||
|
||||
notifyCallbacks(error, context);
|
||||
|
||||
if (config.throwErrors) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
handleError: function(error, context = null, fallbackValue = null) {
|
||||
if (config.logErrors) {
|
||||
console.error(formatError(error, context));
|
||||
}
|
||||
|
||||
notifyCallbacks(error, context);
|
||||
|
||||
if (config.throwErrors) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return fallbackValue;
|
||||
},
|
||||
|
||||
try: function(fn, catchFn = null, finallyFn = null) {
|
||||
try {
|
||||
return fn();
|
||||
} catch (error) {
|
||||
if (config.logErrors) {
|
||||
console.error(formatError(error, 'ErrorHandler.try'));
|
||||
}
|
||||
|
||||
notifyCallbacks(error, 'ErrorHandler.try');
|
||||
|
||||
if (catchFn) {
|
||||
return catchFn(error);
|
||||
}
|
||||
|
||||
if (config.throwErrors) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return null;
|
||||
} finally {
|
||||
if (finallyFn) {
|
||||
finallyFn();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
tryAsync: async function(fn, catchFn = null, finallyFn = null) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (error) {
|
||||
if (config.logErrors) {
|
||||
console.error(formatError(error, 'ErrorHandler.tryAsync'));
|
||||
}
|
||||
|
||||
notifyCallbacks(error, 'ErrorHandler.tryAsync');
|
||||
|
||||
if (catchFn) {
|
||||
return await catchFn(error);
|
||||
}
|
||||
|
||||
if (config.throwErrors) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return null;
|
||||
} finally {
|
||||
if (finallyFn) {
|
||||
await finallyFn();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
createBoundary: function(context) {
|
||||
return {
|
||||
execute: (fn, fallbackValue = null) => {
|
||||
return ErrorHandler.safeExecute(fn, context, fallbackValue);
|
||||
},
|
||||
executeAsync: (fn, fallbackValue = null) => {
|
||||
return ErrorHandler.safeExecuteAsync(fn, context, fallbackValue);
|
||||
},
|
||||
wrap: (fn, fallbackValue = null) => {
|
||||
return ErrorHandler.wrap(fn, context, fallbackValue);
|
||||
},
|
||||
wrapAsync: (fn, fallbackValue = null) => {
|
||||
return ErrorHandler.wrapAsync(fn, context, fallbackValue);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.ErrorHandler = ErrorHandler;
|
||||
}
|
||||
|
||||
console.log('ErrorHandler module loaded');
|
||||
@@ -0,0 +1,342 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const EventHandlers = {
|
||||
|
||||
confirmPopup: function(action = 'proceed', coinName = '') {
|
||||
const message = action === 'Accept'
|
||||
? 'Are you sure you want to accept this bid?'
|
||||
: coinName
|
||||
? `Are you sure you want to ${action} ${coinName}?`
|
||||
: 'Are you sure you want to proceed?';
|
||||
|
||||
return confirm(message);
|
||||
},
|
||||
|
||||
confirmReseed: function() {
|
||||
return confirm('Are you sure you want to reseed the wallet? This will generate new addresses.');
|
||||
},
|
||||
|
||||
confirmWithdrawal: function() {
|
||||
|
||||
if (window.WalletPage && typeof window.WalletPage.confirmWithdrawal === 'function') {
|
||||
return window.WalletPage.confirmWithdrawal();
|
||||
}
|
||||
return confirm('Are you sure you want to withdraw? Please verify the address and amount.');
|
||||
},
|
||||
|
||||
confirmUTXOResize: function() {
|
||||
return confirm('Are you sure you want to create a UTXO? This will split your balance.');
|
||||
},
|
||||
|
||||
confirmRemoveExpired: function() {
|
||||
return confirm('Are you sure you want to remove all expired offers and bids?');
|
||||
},
|
||||
|
||||
fillDonationAddress: function(address, coinType) {
|
||||
|
||||
let addressInput = null;
|
||||
|
||||
addressInput = window.DOMCache
|
||||
? window.DOMCache.get('address_to')
|
||||
: document.getElementById('address_to');
|
||||
|
||||
if (!addressInput) {
|
||||
addressInput = document.querySelector('input[name^="to_"]');
|
||||
}
|
||||
|
||||
if (!addressInput) {
|
||||
addressInput = document.querySelector('input[placeholder*="Address"]');
|
||||
}
|
||||
|
||||
if (addressInput) {
|
||||
addressInput.value = address;
|
||||
console.log(`Filled donation address for ${coinType}: ${address}`);
|
||||
} else {
|
||||
console.error('EventHandlers: Address input not found');
|
||||
}
|
||||
},
|
||||
|
||||
setAmmAmount: function(percent, inputId) {
|
||||
const amountInput = window.DOMCache
|
||||
? window.DOMCache.get(inputId)
|
||||
: document.getElementById(inputId);
|
||||
|
||||
if (!amountInput) {
|
||||
console.error('EventHandlers: AMM amount input not found:', inputId);
|
||||
return;
|
||||
}
|
||||
|
||||
const balanceElement = amountInput.closest('form')?.querySelector('[data-balance]');
|
||||
const balance = balanceElement ? parseFloat(balanceElement.getAttribute('data-balance')) : 0;
|
||||
|
||||
if (balance > 0) {
|
||||
const calculatedAmount = balance * percent;
|
||||
amountInput.value = calculatedAmount.toFixed(8);
|
||||
} else {
|
||||
console.warn('EventHandlers: No balance found for AMM amount calculation');
|
||||
}
|
||||
},
|
||||
|
||||
setOfferAmount: function(percent, inputId) {
|
||||
const amountInput = window.DOMCache
|
||||
? window.DOMCache.get(inputId)
|
||||
: document.getElementById(inputId);
|
||||
|
||||
if (!amountInput) {
|
||||
console.error('EventHandlers: Offer amount input not found:', inputId);
|
||||
return;
|
||||
}
|
||||
|
||||
const coinFromSelect = document.getElementById('coin_from');
|
||||
if (!coinFromSelect) {
|
||||
console.error('EventHandlers: coin_from select not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedOption = coinFromSelect.options[coinFromSelect.selectedIndex];
|
||||
if (!selectedOption || selectedOption.value === '-1') {
|
||||
if (window.showErrorModal) {
|
||||
window.showErrorModal('Validation Error', 'Please select a coin first');
|
||||
} else {
|
||||
alert('Please select a coin first');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const balance = selectedOption.getAttribute('data-balance');
|
||||
if (!balance) {
|
||||
console.error('EventHandlers: Balance not found for selected coin');
|
||||
return;
|
||||
}
|
||||
|
||||
const floatBalance = parseFloat(balance);
|
||||
if (isNaN(floatBalance) || floatBalance <= 0) {
|
||||
if (window.showErrorModal) {
|
||||
window.showErrorModal('Invalid Balance', 'The selected coin has no available balance. Please select a coin with a positive balance.');
|
||||
} else {
|
||||
alert('Invalid balance for selected coin');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const calculatedAmount = floatBalance * percent;
|
||||
amountInput.value = calculatedAmount.toFixed(8);
|
||||
},
|
||||
|
||||
resetForm: function() {
|
||||
const form = document.querySelector('form[name="offer_form"]') || document.querySelector('form');
|
||||
if (form) {
|
||||
form.reset();
|
||||
}
|
||||
},
|
||||
|
||||
hideConfirmModal: function() {
|
||||
if (window.DOMCache) {
|
||||
window.DOMCache.hide('confirmModal');
|
||||
} else {
|
||||
const modal = document.getElementById('confirmModal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
lookup_rates: function() {
|
||||
|
||||
if (window.lookup_rates && typeof window.lookup_rates === 'function') {
|
||||
window.lookup_rates();
|
||||
} else {
|
||||
console.error('EventHandlers: lookup_rates function not found');
|
||||
}
|
||||
},
|
||||
|
||||
checkForUpdatesNow: function() {
|
||||
if (window.checkForUpdatesNow && typeof window.checkForUpdatesNow === 'function') {
|
||||
window.checkForUpdatesNow();
|
||||
} else {
|
||||
console.error('EventHandlers: checkForUpdatesNow function not found');
|
||||
}
|
||||
},
|
||||
|
||||
testUpdateNotification: function() {
|
||||
if (window.testUpdateNotification && typeof window.testUpdateNotification === 'function') {
|
||||
window.testUpdateNotification();
|
||||
} else {
|
||||
console.error('EventHandlers: testUpdateNotification function not found');
|
||||
}
|
||||
},
|
||||
|
||||
toggleNotificationDropdown: function(event) {
|
||||
if (window.toggleNotificationDropdown && typeof window.toggleNotificationDropdown === 'function') {
|
||||
window.toggleNotificationDropdown(event);
|
||||
} else {
|
||||
console.error('EventHandlers: toggleNotificationDropdown function not found');
|
||||
}
|
||||
},
|
||||
|
||||
closeMessage: function(messageId) {
|
||||
if (window.DOMCache) {
|
||||
window.DOMCache.hide(messageId);
|
||||
} else {
|
||||
const messageElement = document.getElementById(messageId);
|
||||
if (messageElement) {
|
||||
messageElement.style.display = 'none';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-confirm]');
|
||||
if (target) {
|
||||
const action = target.getAttribute('data-confirm-action') || 'proceed';
|
||||
const coinName = target.getAttribute('data-confirm-coin') || '';
|
||||
|
||||
if (!this.confirmPopup(action, coinName)) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-confirm-reseed]');
|
||||
if (target) {
|
||||
if (!this.confirmReseed()) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-confirm-utxo]');
|
||||
if (target) {
|
||||
if (!this.confirmUTXOResize()) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-confirm-remove-expired]');
|
||||
if (target) {
|
||||
if (!this.confirmRemoveExpired()) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-fill-donation]');
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
const address = target.getAttribute('data-address');
|
||||
const coinType = target.getAttribute('data-coin-type');
|
||||
this.fillDonationAddress(address, coinType);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-set-amm-amount]');
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
const percent = parseFloat(target.getAttribute('data-set-amm-amount'));
|
||||
const inputId = target.getAttribute('data-input-id');
|
||||
this.setAmmAmount(percent, inputId);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-set-offer-amount]');
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
const percent = parseFloat(target.getAttribute('data-set-offer-amount'));
|
||||
const inputId = target.getAttribute('data-input-id');
|
||||
this.setOfferAmount(percent, inputId);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-reset-form]');
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
this.resetForm();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-hide-modal]');
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
this.hideConfirmModal();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-lookup-rates]');
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
this.lookup_rates();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-check-updates]');
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
this.checkForUpdatesNow();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-test-notification]');
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
const type = target.getAttribute('data-test-notification');
|
||||
if (type === 'update') {
|
||||
this.testUpdateNotification();
|
||||
} else {
|
||||
window.NotificationManager && window.NotificationManager.testToasts();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const target = e.target.closest('[data-close-message]');
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
const messageId = target.getAttribute('data-close-message');
|
||||
this.closeMessage(messageId);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
EventHandlers.initialize();
|
||||
});
|
||||
} else {
|
||||
EventHandlers.initialize();
|
||||
}
|
||||
|
||||
window.EventHandlers = EventHandlers;
|
||||
|
||||
window.confirmPopup = EventHandlers.confirmPopup.bind(EventHandlers);
|
||||
window.confirmReseed = EventHandlers.confirmReseed.bind(EventHandlers);
|
||||
window.confirmWithdrawal = EventHandlers.confirmWithdrawal.bind(EventHandlers);
|
||||
window.confirmUTXOResize = EventHandlers.confirmUTXOResize.bind(EventHandlers);
|
||||
window.confirmRemoveExpired = EventHandlers.confirmRemoveExpired.bind(EventHandlers);
|
||||
window.fillDonationAddress = EventHandlers.fillDonationAddress.bind(EventHandlers);
|
||||
window.setAmmAmount = EventHandlers.setAmmAmount.bind(EventHandlers);
|
||||
window.setOfferAmount = EventHandlers.setOfferAmount.bind(EventHandlers);
|
||||
window.resetForm = EventHandlers.resetForm.bind(EventHandlers);
|
||||
window.hideConfirmModal = EventHandlers.hideConfirmModal.bind(EventHandlers);
|
||||
window.toggleNotificationDropdown = EventHandlers.toggleNotificationDropdown.bind(EventHandlers);
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,225 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const FormValidator = {
|
||||
|
||||
checkPasswordStrength: function(password) {
|
||||
const requirements = {
|
||||
length: password.length >= 8,
|
||||
uppercase: /[A-Z]/.test(password),
|
||||
lowercase: /[a-z]/.test(password),
|
||||
number: /[0-9]/.test(password)
|
||||
};
|
||||
|
||||
let score = 0;
|
||||
if (requirements.length) score += 25;
|
||||
if (requirements.uppercase) score += 25;
|
||||
if (requirements.lowercase) score += 25;
|
||||
if (requirements.number) score += 25;
|
||||
|
||||
return {
|
||||
score: score,
|
||||
requirements: requirements,
|
||||
isStrong: score >= 60
|
||||
};
|
||||
},
|
||||
|
||||
updatePasswordStrengthUI: function(password, elements) {
|
||||
const result = this.checkPasswordStrength(password);
|
||||
const { score, requirements } = result;
|
||||
|
||||
if (!elements.bar || !elements.text) {
|
||||
console.warn('FormValidator: Missing strength UI elements');
|
||||
return result.isStrong;
|
||||
}
|
||||
|
||||
elements.bar.style.width = `${score}%`;
|
||||
|
||||
if (score === 0) {
|
||||
elements.bar.className = 'h-2 rounded-full transition-all duration-300 bg-gray-300 dark:bg-gray-500';
|
||||
elements.text.textContent = 'Enter password';
|
||||
elements.text.className = 'text-sm font-medium text-gray-500 dark:text-gray-400';
|
||||
} else if (score < 40) {
|
||||
elements.bar.className = 'h-2 rounded-full transition-all duration-300 bg-red-500';
|
||||
elements.text.textContent = 'Weak';
|
||||
elements.text.className = 'text-sm font-medium text-red-600 dark:text-red-400';
|
||||
} else if (score < 70) {
|
||||
elements.bar.className = 'h-2 rounded-full transition-all duration-300 bg-yellow-500';
|
||||
elements.text.textContent = 'Fair';
|
||||
elements.text.className = 'text-sm font-medium text-yellow-600 dark:text-yellow-400';
|
||||
} else if (score < 90) {
|
||||
elements.bar.className = 'h-2 rounded-full transition-all duration-300 bg-blue-500';
|
||||
elements.text.textContent = 'Good';
|
||||
elements.text.className = 'text-sm font-medium text-blue-600 dark:text-blue-400';
|
||||
} else {
|
||||
elements.bar.className = 'h-2 rounded-full transition-all duration-300 bg-green-500';
|
||||
elements.text.textContent = 'Strong';
|
||||
elements.text.className = 'text-sm font-medium text-green-600 dark:text-green-400';
|
||||
}
|
||||
|
||||
if (elements.requirements) {
|
||||
this.updateRequirement(elements.requirements.length, requirements.length);
|
||||
this.updateRequirement(elements.requirements.uppercase, requirements.uppercase);
|
||||
this.updateRequirement(elements.requirements.lowercase, requirements.lowercase);
|
||||
this.updateRequirement(elements.requirements.number, requirements.number);
|
||||
}
|
||||
|
||||
return result.isStrong;
|
||||
},
|
||||
|
||||
updateRequirement: function(element, met) {
|
||||
if (!element) return;
|
||||
|
||||
if (met) {
|
||||
element.className = 'flex items-center text-green-600 dark:text-green-400';
|
||||
} else {
|
||||
element.className = 'flex items-center text-gray-500 dark:text-gray-400';
|
||||
}
|
||||
},
|
||||
|
||||
checkPasswordMatch: function(password1, password2, elements) {
|
||||
if (!elements) {
|
||||
return password1 === password2;
|
||||
}
|
||||
|
||||
const { container, success, error } = elements;
|
||||
|
||||
if (password2.length === 0) {
|
||||
if (container) container.classList.add('hidden');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (container) container.classList.remove('hidden');
|
||||
|
||||
if (password1 === password2) {
|
||||
if (success) success.classList.remove('hidden');
|
||||
if (error) error.classList.add('hidden');
|
||||
return true;
|
||||
} else {
|
||||
if (success) success.classList.add('hidden');
|
||||
if (error) error.classList.remove('hidden');
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
validateEmail: function(email) {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
return emailRegex.test(email);
|
||||
},
|
||||
|
||||
validateRequired: function(value) {
|
||||
return value && value.trim().length > 0;
|
||||
},
|
||||
|
||||
validateMinLength: function(value, minLength) {
|
||||
return value && value.length >= minLength;
|
||||
},
|
||||
|
||||
validateMaxLength: function(value, maxLength) {
|
||||
return value && value.length <= maxLength;
|
||||
},
|
||||
|
||||
validateNumeric: function(value) {
|
||||
return !isNaN(value) && !isNaN(parseFloat(value));
|
||||
},
|
||||
|
||||
validateRange: function(value, min, max) {
|
||||
const num = parseFloat(value);
|
||||
return !isNaN(num) && num >= min && num <= max;
|
||||
},
|
||||
|
||||
showError: function(element, message) {
|
||||
if (!element) return;
|
||||
|
||||
element.classList.add('border-red-500', 'focus:border-red-500', 'focus:ring-red-500');
|
||||
element.classList.remove('border-gray-300', 'focus:border-blue-500', 'focus:ring-blue-500');
|
||||
|
||||
let errorElement = element.parentElement.querySelector('.validation-error');
|
||||
if (!errorElement) {
|
||||
errorElement = document.createElement('p');
|
||||
errorElement.className = 'validation-error text-red-600 dark:text-red-400 text-sm mt-1';
|
||||
element.parentElement.appendChild(errorElement);
|
||||
}
|
||||
|
||||
errorElement.textContent = message;
|
||||
errorElement.classList.remove('hidden');
|
||||
},
|
||||
|
||||
clearError: function(element) {
|
||||
if (!element) return;
|
||||
|
||||
element.classList.remove('border-red-500', 'focus:border-red-500', 'focus:ring-red-500');
|
||||
element.classList.add('border-gray-300', 'focus:border-blue-500', 'focus:ring-blue-500');
|
||||
|
||||
const errorElement = element.parentElement.querySelector('.validation-error');
|
||||
if (errorElement) {
|
||||
errorElement.classList.add('hidden');
|
||||
}
|
||||
},
|
||||
|
||||
validateForm: function(form, rules) {
|
||||
if (!form || !rules) return false;
|
||||
|
||||
let isValid = true;
|
||||
|
||||
Object.keys(rules).forEach(fieldName => {
|
||||
const field = form.querySelector(`[name="${fieldName}"]`);
|
||||
if (!field) return;
|
||||
|
||||
const fieldRules = rules[fieldName];
|
||||
let fieldValid = true;
|
||||
let errorMessage = '';
|
||||
|
||||
if (fieldRules.required && !this.validateRequired(field.value)) {
|
||||
fieldValid = false;
|
||||
errorMessage = fieldRules.requiredMessage || 'This field is required';
|
||||
}
|
||||
|
||||
if (fieldValid && fieldRules.minLength && !this.validateMinLength(field.value, fieldRules.minLength)) {
|
||||
fieldValid = false;
|
||||
errorMessage = fieldRules.minLengthMessage || `Minimum ${fieldRules.minLength} characters required`;
|
||||
}
|
||||
|
||||
if (fieldValid && fieldRules.maxLength && !this.validateMaxLength(field.value, fieldRules.maxLength)) {
|
||||
fieldValid = false;
|
||||
errorMessage = fieldRules.maxLengthMessage || `Maximum ${fieldRules.maxLength} characters allowed`;
|
||||
}
|
||||
|
||||
if (fieldValid && fieldRules.email && !this.validateEmail(field.value)) {
|
||||
fieldValid = false;
|
||||
errorMessage = fieldRules.emailMessage || 'Invalid email format';
|
||||
}
|
||||
|
||||
if (fieldValid && fieldRules.numeric && !this.validateNumeric(field.value)) {
|
||||
fieldValid = false;
|
||||
errorMessage = fieldRules.numericMessage || 'Must be a number';
|
||||
}
|
||||
|
||||
if (fieldValid && fieldRules.range && !this.validateRange(field.value, fieldRules.range.min, fieldRules.range.max)) {
|
||||
fieldValid = false;
|
||||
errorMessage = fieldRules.rangeMessage || `Must be between ${fieldRules.range.min} and ${fieldRules.range.max}`;
|
||||
}
|
||||
|
||||
if (fieldValid && fieldRules.custom) {
|
||||
const customResult = fieldRules.custom(field.value, form);
|
||||
if (!customResult.valid) {
|
||||
fieldValid = false;
|
||||
errorMessage = customResult.message || 'Invalid value';
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldValid) {
|
||||
this.clearError(field);
|
||||
} else {
|
||||
this.showError(field, errorMessage);
|
||||
isValid = false;
|
||||
}
|
||||
});
|
||||
|
||||
return isValid;
|
||||
}
|
||||
};
|
||||
|
||||
window.FormValidator = FormValidator;
|
||||
|
||||
})();
|
||||
@@ -23,10 +23,24 @@ const IdentityManager = (function() {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cachedData = this.getCachedIdentity(address);
|
||||
if (cachedData) {
|
||||
log(`Cache hit for ${address}`);
|
||||
return cachedData;
|
||||
const cached = state.cache.get(address);
|
||||
const now = Date.now();
|
||||
|
||||
if (cached && (now - cached.timestamp) < state.config.cacheTimeout) {
|
||||
log(`Cache hit (fresh) for ${address}`);
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
if (cached && (now - cached.timestamp) < state.config.cacheTimeout * 2) {
|
||||
log(`Cache hit (stale) for ${address}, refreshing in background`);
|
||||
|
||||
const staleData = cached.data;
|
||||
|
||||
if (!state.pendingRequests.has(address)) {
|
||||
this.refreshIdentityInBackground(address);
|
||||
}
|
||||
|
||||
return staleData;
|
||||
}
|
||||
|
||||
if (state.pendingRequests.has(address)) {
|
||||
@@ -47,6 +61,20 @@ const IdentityManager = (function() {
|
||||
}
|
||||
},
|
||||
|
||||
refreshIdentityInBackground: function(address) {
|
||||
const request = fetchWithRetry(address);
|
||||
state.pendingRequests.set(address, request);
|
||||
|
||||
request.then(data => {
|
||||
this.setCachedIdentity(address, data);
|
||||
log(`Background refresh completed for ${address}`);
|
||||
}).catch(error => {
|
||||
log(`Background refresh failed for ${address}:`, error);
|
||||
}).finally(() => {
|
||||
state.pendingRequests.delete(address);
|
||||
});
|
||||
},
|
||||
|
||||
getCachedIdentity: function(address) {
|
||||
const cached = state.cache.get(address);
|
||||
if (cached && (Date.now() - cached.timestamp) < state.config.cacheTimeout) {
|
||||
@@ -155,15 +183,23 @@ const IdentityManager = (function() {
|
||||
|
||||
async function fetchWithRetry(address, attempt = 1) {
|
||||
try {
|
||||
const response = await fetch(`/json/identities/${address}`, {
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
let data;
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
if (window.ApiManager) {
|
||||
data = await window.ApiManager.makeRequest(`/json/identities/${address}`, 'GET');
|
||||
} else {
|
||||
const response = await fetch(`/json/identities/${address}`, {
|
||||
signal: AbortSignal.timeout(5000)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
data = await response.json();
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
if (attempt >= state.config.maxRetries) {
|
||||
console.error(`[IdentityManager] Error:`, error.message);
|
||||
@@ -171,7 +207,10 @@ const IdentityManager = (function() {
|
||||
return null;
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, state.config.retryDelay * attempt));
|
||||
const delay = state.config.retryDelay * attempt;
|
||||
await new Promise(resolve => {
|
||||
CleanupManager.setTimeout(resolve, delay);
|
||||
});
|
||||
return fetchWithRetry(address, attempt + 1);
|
||||
}
|
||||
}
|
||||
@@ -188,5 +227,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
//console.log('IdentityManager initialized with methods:', Object.keys(IdentityManager));
|
||||
console.log('IdentityManager initialized');
|
||||
|
||||
@@ -108,7 +108,7 @@ const NetworkManager = (function() {
|
||||
|
||||
log(`Scheduling reconnection attempt in ${delay/1000} seconds`);
|
||||
|
||||
state.reconnectTimer = setTimeout(() => {
|
||||
state.reconnectTimer = CleanupManager.setTimeout(() => {
|
||||
state.reconnectTimer = null;
|
||||
this.attemptReconnect();
|
||||
}, delay);
|
||||
@@ -167,7 +167,20 @@ const NetworkManager = (function() {
|
||||
});
|
||||
},
|
||||
|
||||
testBackendConnection: function() {
|
||||
testBackendConnection: async function() {
|
||||
if (window.ApiManager) {
|
||||
try {
|
||||
await window.ApiManager.makeRequest(config.connectionTestEndpoint, 'HEAD', {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache'
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
log('Backend connection test failed:', error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(config.connectionTestEndpoint, {
|
||||
method: 'HEAD',
|
||||
headers: {
|
||||
@@ -275,6 +288,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
//console.log('NetworkManager initialized with methods:', Object.keys(NetworkManager));
|
||||
console.log('NetworkManager initialized');
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const NotificationManager = (function() {
|
||||
|
||||
|
||||
const defaultConfig = {
|
||||
showNewOffers: false,
|
||||
showNewBids: true,
|
||||
@@ -12,7 +11,6 @@ const NotificationManager = (function() {
|
||||
notificationDuration: 20000
|
||||
};
|
||||
|
||||
|
||||
function loadConfig() {
|
||||
const saved = localStorage.getItem('notification_settings');
|
||||
if (saved) {
|
||||
@@ -25,7 +23,6 @@ const NotificationManager = (function() {
|
||||
return { ...defaultConfig };
|
||||
}
|
||||
|
||||
|
||||
function saveConfig(newConfig) {
|
||||
try {
|
||||
localStorage.setItem('notification_settings', JSON.stringify(newConfig));
|
||||
@@ -269,7 +266,6 @@ function ensureToastContainer() {
|
||||
return colors[type] || colors['success'];
|
||||
}
|
||||
|
||||
// Todo: Remove later and use global.
|
||||
function getCoinDisplayName(coinId) {
|
||||
const coinMap = {
|
||||
1: 'PART',
|
||||
@@ -292,25 +288,24 @@ function ensureToastContainer() {
|
||||
return coinMap[coinId] || `Coin ${coinId}`;
|
||||
}
|
||||
|
||||
// Todo: Remove later.
|
||||
function formatCoinAmount(amount, coinId) {
|
||||
const divisors = {
|
||||
1: 100000000, // PART - 8 decimals
|
||||
2: 100000000, // BTC - 8 decimals
|
||||
3: 100000000, // LTC - 8 decimals
|
||||
4: 100000000, // DCR - 8 decimals
|
||||
5: 100000000, // NMC - 8 decimals
|
||||
1: 100000000, // PART - 8 decimals
|
||||
2: 100000000, // BTC - 8 decimals
|
||||
3: 100000000, // LTC - 8 decimals
|
||||
4: 100000000, // DCR - 8 decimals
|
||||
5: 100000000, // NMC - 8 decimals
|
||||
6: 1000000000000, // XMR - 12 decimals
|
||||
7: 100000000, // PART (Blind) - 8 decimals
|
||||
8: 100000000, // PART (Anon) - 8 decimals
|
||||
9: 100000000000, // WOW - 11 decimals
|
||||
11: 100000000, // PIVX - 8 decimals
|
||||
12: 100000000, // DASH - 8 decimals
|
||||
13: 100000000, // FIRO - 8 decimals
|
||||
14: 100000000, // NAV - 8 decimals
|
||||
15: 100000000, // LTC (MWEB) - 8 decimals
|
||||
17: 100000000, // BCH - 8 decimals
|
||||
18: 100000000 // DOGE - 8 decimals
|
||||
7: 100000000, // PART (Blind) - 8 decimals
|
||||
8: 100000000, // PART (Anon) - 8 decimals
|
||||
9: 100000000000, // WOW - 11 decimals
|
||||
11: 100000000, // PIVX - 8 decimals
|
||||
12: 100000000, // DASH - 8 decimals
|
||||
13: 100000000, // FIRO - 8 decimals
|
||||
14: 100000000, // NAV - 8 decimals
|
||||
15: 100000000, // LTC (MWEB) - 8 decimals
|
||||
17: 100000000, // BCH - 8 decimals
|
||||
18: 100000000 // DOGE - 8 decimals
|
||||
};
|
||||
|
||||
const divisor = divisors[coinId] || 100000000;
|
||||
@@ -358,7 +353,7 @@ function ensureToastContainer() {
|
||||
testToasts: function() {
|
||||
if (!this.createToast) return;
|
||||
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
this.createToast(
|
||||
'+0.05000000 PART',
|
||||
'balance_change',
|
||||
@@ -366,7 +361,7 @@ function ensureToastContainer() {
|
||||
);
|
||||
}, 500);
|
||||
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
this.createToast(
|
||||
'+0.00123456 XMR',
|
||||
'balance_change',
|
||||
@@ -374,7 +369,7 @@ function ensureToastContainer() {
|
||||
);
|
||||
}, 1000);
|
||||
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
this.createToast(
|
||||
'-29.86277595 PART',
|
||||
'balance_change',
|
||||
@@ -382,7 +377,7 @@ function ensureToastContainer() {
|
||||
);
|
||||
}, 1500);
|
||||
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
this.createToast(
|
||||
'-0.05000000 PART (Anon)',
|
||||
'balance_change',
|
||||
@@ -390,7 +385,7 @@ function ensureToastContainer() {
|
||||
);
|
||||
}, 2000);
|
||||
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
this.createToast(
|
||||
'+1.23456789 PART (Anon)',
|
||||
'balance_change',
|
||||
@@ -398,33 +393,37 @@ function ensureToastContainer() {
|
||||
);
|
||||
}, 2500);
|
||||
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
const btcIcon = getCoinIcon('BTC');
|
||||
const xmrIcon = getCoinIcon('XMR');
|
||||
this.createToast(
|
||||
'<img src="/static/images/coins/bitcoin.svg" class="w-4 h-4 inline mr-1" alt="BTC" onerror="this.style.display=\'none\'">1.00000000 BTC → <img src="/static/images/coins/monero.svg" class="w-4 h-4 inline mr-1" alt="XMR" onerror="this.style.display=\'none\'">15.50000000 XMR',
|
||||
'New Network Offer',
|
||||
'new_offer',
|
||||
{
|
||||
offerId: '000000006873f4ef17d4f220730400f4fdd57157492289c5d414ea66',
|
||||
subtitle: 'New offer • Rate: 1 BTC = 15.50000000 XMR',
|
||||
subtitle: `<img src="/static/images/coins/${btcIcon}" class="w-4 h-4 inline mr-1" alt="BTC" onerror="this.style.display='none'">1.00000000 BTC → <img src="/static/images/coins/${xmrIcon}" class="w-4 h-4 inline mr-1" alt="XMR" onerror="this.style.display='none'">15.50000000 XMR<br>Rate: 1 BTC = 15.50000000 XMR`,
|
||||
coinFrom: 2,
|
||||
coinTo: 6
|
||||
}
|
||||
);
|
||||
}, 3000);
|
||||
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
const btcIcon = getCoinIcon('BTC');
|
||||
const xmrIcon = getCoinIcon('XMR');
|
||||
this.createToast(
|
||||
'<img src="/static/images/coins/bitcoin.svg" class="w-4 h-4 inline mr-1" alt="BTC" onerror="this.style.display=\'none\'">0.50000000 BTC → <img src="/static/images/coins/monero.svg" class="w-4 h-4 inline mr-1" alt="XMR" onerror="this.style.display=\'none\'">7.75000000 XMR',
|
||||
'New Bid Received',
|
||||
'new_bid',
|
||||
{
|
||||
bidId: '000000006873f4ef17d4f220730400f4fdd57157492289c5d414ea66',
|
||||
subtitle: 'New bid • Rate: 1 BTC = 15.50000000 XMR',
|
||||
subtitle: `<img src="/static/images/coins/${btcIcon}" class="w-4 h-4 inline mr-1" alt="BTC" onerror="this.style.display='none'">0.50000000 BTC → <img src="/static/images/coins/${xmrIcon}" class="w-4 h-4 inline mr-1" alt="XMR" onerror="this.style.display='none'">7.75000000 XMR<br>Rate: 1 BTC = 15.50000000 XMR`,
|
||||
coinFrom: 2,
|
||||
coinTo: 6
|
||||
}
|
||||
);
|
||||
}, 3500);
|
||||
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
this.createToast(
|
||||
'Swap completed successfully',
|
||||
'swap_completed',
|
||||
@@ -435,25 +434,68 @@ function ensureToastContainer() {
|
||||
);
|
||||
}, 4000);
|
||||
|
||||
setTimeout(() => {
|
||||
this.createToast(
|
||||
'Update Available: v0.15.0',
|
||||
'update_available',
|
||||
{
|
||||
subtitle: 'Current: v0.14.6 • Click to view release',
|
||||
releaseUrl: 'https://github.com/basicswap/basicswap/releases/tag/v0.15.0',
|
||||
releaseNotes: 'New version v0.15.0 is available. Click to view details on GitHub.'
|
||||
CleanupManager.setTimeout(async () => {
|
||||
try {
|
||||
const response = await fetch('/json/checkupdates', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
console.warn('Test notification - API returned error, using fallback:', data.error);
|
||||
this.createToast(
|
||||
'Update Available: v0.15.0',
|
||||
'update_available',
|
||||
{
|
||||
subtitle: 'Current: v0.14.6 • Click to view release',
|
||||
releaseUrl: 'https://github.com/basicswap/basicswap/releases/tag/v0.15.0',
|
||||
releaseNotes: 'New version v0.15.0 is available. Click to view details on GitHub.'
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
);
|
||||
|
||||
const currentVer = (data.current_version && String(data.current_version) !== 'null' && String(data.current_version) !== 'None')
|
||||
? String(data.current_version)
|
||||
: '0.14.6';
|
||||
const latestVer = (data.latest_version && String(data.latest_version) !== 'null' && String(data.latest_version) !== 'None')
|
||||
? String(data.latest_version)
|
||||
: currentVer;
|
||||
|
||||
this.createToast(
|
||||
`Update Available: v${latestVer}`,
|
||||
'update_available',
|
||||
{
|
||||
subtitle: `Current: v${currentVer} • Click to view release`,
|
||||
releaseUrl: `https://github.com/basicswap/basicswap/releases/tag/v${latestVer}`,
|
||||
releaseNotes: `New version v${latestVer} is available. Click to view details on GitHub.`
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Test notification - API error:', error);
|
||||
this.createToast(
|
||||
'Update Available: v0.15.0',
|
||||
'update_available',
|
||||
{
|
||||
subtitle: 'Current: v0.14.6 • Click to view release',
|
||||
releaseUrl: 'https://github.com/basicswap/basicswap/releases/tag/v0.15.0',
|
||||
releaseNotes: 'New version v0.15.0 is available. Click to view details on GitHub.'
|
||||
}
|
||||
);
|
||||
}
|
||||
}, 4500);
|
||||
|
||||
},
|
||||
|
||||
initializeBalanceTracking: function() {
|
||||
initializeBalanceTracking: async function() {
|
||||
this.checkAndResetStaleBalanceTracking();
|
||||
|
||||
fetch('/json/walletbalances')
|
||||
.then(response => response.json())
|
||||
const fetchBalances = window.ApiManager
|
||||
? window.ApiManager.makeRequest('/json/walletbalances', 'GET')
|
||||
: fetch('/json/walletbalances').then(response => response.json());
|
||||
|
||||
fetchBalances
|
||||
.then(balanceData => {
|
||||
if (Array.isArray(balanceData)) {
|
||||
balanceData.forEach(coin => {
|
||||
@@ -533,7 +575,6 @@ function ensureToastContainer() {
|
||||
coinIconHtml = `<img src="/static/images/coins/${coinIcon}" class="w-5 h-5 mr-2" alt="${options.coinSymbol}" onerror="this.style.display='none'">`;
|
||||
}
|
||||
|
||||
|
||||
let clickAction = '';
|
||||
let cursorStyle = 'cursor-default';
|
||||
|
||||
@@ -585,10 +626,10 @@ function ensureToastContainer() {
|
||||
messages.appendChild(message);
|
||||
|
||||
if (!isPersistent) {
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
if (message.parentNode) {
|
||||
message.classList.add('toast-slide-out');
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
if (message.parentNode) {
|
||||
message.parentNode.removeChild(message);
|
||||
}
|
||||
@@ -613,12 +654,12 @@ function ensureToastContainer() {
|
||||
const amountTo = formatCoinAmount(data.amount_to, data.coin_to);
|
||||
const coinFromIcon = getCoinIcon(coinFromName);
|
||||
const coinToIcon = getCoinIcon(coinToName);
|
||||
toastTitle = `<img src="/static/images/coins/${coinFromIcon}" class="w-4 h-4 inline mr-1" alt="${coinFromName}" onerror="this.style.display='none'">${amountFrom} ${coinFromName} → <img src="/static/images/coins/${coinToIcon}" class="w-4 h-4 inline mr-1" alt="${coinToName}" onerror="this.style.display='none'">${amountTo} ${coinToName}`;
|
||||
toastOptions.subtitle = `New offer • Rate: 1 ${coinFromName} = ${(data.amount_to / data.amount_from).toFixed(8)} ${coinToName}`;
|
||||
toastTitle = `New Network Offer`;
|
||||
toastOptions.subtitle = `<img src="/static/images/coins/${coinFromIcon}" class="w-4 h-4 inline mr-1" alt="${coinFromName}" onerror="this.style.display='none'">${amountFrom} ${coinFromName} → <img src="/static/images/coins/${coinToIcon}" class="w-4 h-4 inline mr-1" alt="${coinToName}" onerror="this.style.display='none'">${amountTo} ${coinToName}<br>Rate: 1 ${coinFromName} = ${(data.amount_to / data.amount_from).toFixed(8)} ${coinToName}`;
|
||||
toastOptions.coinFrom = data.coin_from;
|
||||
toastOptions.coinTo = data.coin_to;
|
||||
} else {
|
||||
toastTitle = `New network offer`;
|
||||
toastTitle = `New Network Offer`;
|
||||
toastOptions.subtitle = 'Click to view offer';
|
||||
}
|
||||
toastType = 'new_offer';
|
||||
@@ -633,12 +674,12 @@ function ensureToastContainer() {
|
||||
const bidAmountTo = formatCoinAmount(data.bid_amount_to, data.coin_to);
|
||||
const coinFromIcon = getCoinIcon(coinFromName);
|
||||
const coinToIcon = getCoinIcon(coinToName);
|
||||
toastTitle = `<img src="/static/images/coins/${coinFromIcon}" class="w-4 h-4 inline mr-1" alt="${coinFromName}" onerror="this.style.display='none'">${bidAmountFrom} ${coinFromName} → <img src="/static/images/coins/${coinToIcon}" class="w-4 h-4 inline mr-1" alt="${coinToName}" onerror="this.style.display='none'">${bidAmountTo} ${coinToName}`;
|
||||
toastOptions.subtitle = `New bid • Rate: 1 ${coinFromName} = ${(data.bid_amount_to / data.bid_amount).toFixed(8)} ${coinToName}`;
|
||||
toastTitle = `New Bid Received`;
|
||||
toastOptions.subtitle = `<img src="/static/images/coins/${coinFromIcon}" class="w-4 h-4 inline mr-1" alt="${coinFromName}" onerror="this.style.display='none'">${bidAmountFrom} ${coinFromName} → <img src="/static/images/coins/${coinToIcon}" class="w-4 h-4 inline mr-1" alt="${coinToName}" onerror="this.style.display='none'">${bidAmountTo} ${coinToName}<br>Rate: 1 ${coinFromName} = ${(data.bid_amount_to / data.bid_amount).toFixed(8)} ${coinToName}`;
|
||||
toastOptions.coinFrom = data.coin_from;
|
||||
toastOptions.coinTo = data.coin_to;
|
||||
} else {
|
||||
toastTitle = `New bid received`;
|
||||
toastTitle = `New Bid Received`;
|
||||
toastOptions.subtitle = 'Click to view bid';
|
||||
}
|
||||
toastOptions.bidId = data.bid_id;
|
||||
@@ -696,14 +737,17 @@ function ensureToastContainer() {
|
||||
this.balanceTimeouts = {};
|
||||
}
|
||||
|
||||
this.balanceTimeouts[balanceKey] = setTimeout(() => {
|
||||
this.balanceTimeouts[balanceKey] = CleanupManager.setTimeout(() => {
|
||||
this.fetchAndShowBalanceChange(data.coin);
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
fetchAndShowBalanceChange: function(coinSymbol) {
|
||||
fetch('/json/walletbalances')
|
||||
.then(response => response.json())
|
||||
const fetchBalances = window.ApiManager
|
||||
? window.ApiManager.makeRequest('/json/walletbalances', 'GET')
|
||||
: fetch('/json/walletbalances').then(response => response.json());
|
||||
|
||||
fetchBalances
|
||||
.then(balanceData => {
|
||||
if (Array.isArray(balanceData)) {
|
||||
|
||||
@@ -748,13 +792,10 @@ function ensureToastContainer() {
|
||||
const pendingIncrease = currentPending - prevPending;
|
||||
const pendingDecrease = prevPending - currentPending;
|
||||
|
||||
|
||||
|
||||
const totalChange = Math.abs(balanceIncrease) + Math.abs(pendingIncrease);
|
||||
const maxReasonableChange = Math.max(currentBalance, prevBalance) * 0.5;
|
||||
|
||||
if (totalChange > maxReasonableChange && totalChange > 1.0) {
|
||||
console.log(`Detected potentially stale balance data for ${coinData.name}, resetting tracking`);
|
||||
localStorage.setItem(storageKey, currentBalance.toString());
|
||||
localStorage.setItem(pendingStorageKey, currentPending.toString());
|
||||
return;
|
||||
@@ -782,7 +823,6 @@ function ensureToastContainer() {
|
||||
|
||||
const isPendingToConfirmed = pendingDecrease > 0.00000001 && balanceIncrease > 0.00000001;
|
||||
|
||||
|
||||
const displaySymbol = originalCoinSymbol;
|
||||
let variantInfo = '';
|
||||
|
||||
@@ -871,8 +911,6 @@ function ensureToastContainer() {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
updateConfig: function(newConfig) {
|
||||
Object.assign(config, newConfig);
|
||||
return this;
|
||||
|
||||
@@ -42,7 +42,7 @@ const PriceManager = (function() {
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => this.getPrices(), 1500);
|
||||
CleanupManager.setTimeout(() => this.getPrices(), 1500);
|
||||
isInitialized = true;
|
||||
console.log('PriceManager initialized');
|
||||
return this;
|
||||
@@ -60,7 +60,6 @@ const PriceManager = (function() {
|
||||
return fetchPromise;
|
||||
}
|
||||
|
||||
|
||||
lastFetchTime = Date.now();
|
||||
fetchPromise = this.fetchPrices()
|
||||
.then(prices => {
|
||||
@@ -90,8 +89,6 @@ const PriceManager = (function() {
|
||||
? window.config.coins.map(c => c.symbol).filter(symbol => symbol && symbol.trim() !== '')
|
||||
: ['BTC', 'XMR', 'PART', 'BCH', 'PIVX', 'FIRO', 'DASH', 'LTC', 'DOGE', 'DCR', 'NMC', 'WOW']);
|
||||
|
||||
//console.log('PriceManager: lookupFiatRates ' + coinSymbols.join(', '));
|
||||
|
||||
if (!coinSymbols.length) {
|
||||
throw new Error('No valid coins configured');
|
||||
}
|
||||
@@ -133,15 +130,15 @@ const PriceManager = (function() {
|
||||
const coin = window.CoinManager.getCoinByAnyIdentifier(coinId);
|
||||
if (coin) {
|
||||
normalizedCoinId = window.CoinManager.getPriceKey(coin.name);
|
||||
} else if (window.CoinUtils) {
|
||||
normalizedCoinId = window.CoinUtils.normalizeCoinName(coinId);
|
||||
} else {
|
||||
normalizedCoinId = coinId === 'bitcoincash' ? 'bitcoin-cash' : coinId.toLowerCase();
|
||||
normalizedCoinId = coinId.toLowerCase();
|
||||
}
|
||||
} else if (window.CoinUtils) {
|
||||
normalizedCoinId = window.CoinUtils.normalizeCoinName(coinId);
|
||||
} else {
|
||||
normalizedCoinId = coinId === 'bitcoincash' ? 'bitcoin-cash' : coinId.toLowerCase();
|
||||
}
|
||||
|
||||
if (coinId.toLowerCase() === 'zcoin') {
|
||||
normalizedCoinId = 'firo';
|
||||
normalizedCoinId = coinId.toLowerCase();
|
||||
}
|
||||
|
||||
processedData[normalizedCoinId] = {
|
||||
@@ -230,5 +227,3 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
window.priceManagerInitialized = true;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const QRCodeManager = {
|
||||
|
||||
defaultOptions: {
|
||||
width: 200,
|
||||
height: 200,
|
||||
colorDark: "#000000",
|
||||
colorLight: "#ffffff",
|
||||
correctLevel: QRCode.CorrectLevel.L
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
const qrElements = document.querySelectorAll('[data-qrcode]');
|
||||
|
||||
qrElements.forEach(element => {
|
||||
this.generateQRCode(element);
|
||||
});
|
||||
},
|
||||
|
||||
generateQRCode: function(element) {
|
||||
const address = element.getAttribute('data-address');
|
||||
const width = parseInt(element.getAttribute('data-width')) || this.defaultOptions.width;
|
||||
const height = parseInt(element.getAttribute('data-height')) || this.defaultOptions.height;
|
||||
|
||||
if (!address) {
|
||||
console.error('QRCodeManager: No address provided for element', element);
|
||||
return;
|
||||
}
|
||||
|
||||
element.innerHTML = '';
|
||||
|
||||
try {
|
||||
new QRCode(element, {
|
||||
text: address,
|
||||
width: width,
|
||||
height: height,
|
||||
colorDark: this.defaultOptions.colorDark,
|
||||
colorLight: this.defaultOptions.colorLight,
|
||||
correctLevel: this.defaultOptions.correctLevel
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('QRCodeManager: Failed to generate QR code', error);
|
||||
}
|
||||
},
|
||||
|
||||
generateById: function(elementId, address, options = {}) {
|
||||
|
||||
const element = window.DOMCache
|
||||
? window.DOMCache.get(elementId)
|
||||
: document.getElementById(elementId);
|
||||
|
||||
if (!element) {
|
||||
console.error('QRCodeManager: Element not found:', elementId);
|
||||
return;
|
||||
}
|
||||
|
||||
element.setAttribute('data-address', address);
|
||||
|
||||
if (options.width) element.setAttribute('data-width', options.width);
|
||||
if (options.height) element.setAttribute('data-height', options.height);
|
||||
|
||||
this.generateQRCode(element);
|
||||
}
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
QRCodeManager.initialize();
|
||||
});
|
||||
} else {
|
||||
QRCodeManager.initialize();
|
||||
}
|
||||
|
||||
window.QRCodeManager = QRCodeManager;
|
||||
|
||||
})();
|
||||
@@ -166,7 +166,7 @@ const SummaryManager = (function() {
|
||||
}
|
||||
|
||||
if (window.TooltipManager && typeof window.TooltipManager.initializeTooltips === 'function') {
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
window.TooltipManager.initializeTooltips(`[data-tooltip-target="${tooltipId}"]`);
|
||||
debugLog(`Re-initialized tooltips for ${tooltipId}`);
|
||||
}, 50);
|
||||
@@ -205,8 +205,16 @@ const SummaryManager = (function() {
|
||||
}
|
||||
|
||||
function fetchSummaryDataWithTimeout() {
|
||||
if (window.ApiManager) {
|
||||
return window.ApiManager.makeRequest(config.summaryEndpoint, 'GET', {
|
||||
'Accept': 'application/json',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache'
|
||||
});
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), config.requestTimeout);
|
||||
const timeoutId = CleanupManager.setTimeout(() => controller.abort(), config.requestTimeout);
|
||||
|
||||
return fetch(config.summaryEndpoint, {
|
||||
signal: controller.signal,
|
||||
@@ -217,7 +225,11 @@ const SummaryManager = (function() {
|
||||
}
|
||||
})
|
||||
.then(response => {
|
||||
clearTimeout(timeoutId);
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.clearTimeout(timeoutId);
|
||||
} else {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
@@ -226,7 +238,11 @@ const SummaryManager = (function() {
|
||||
return response.json();
|
||||
})
|
||||
.catch(error => {
|
||||
clearTimeout(timeoutId);
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.clearTimeout(timeoutId);
|
||||
} else {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
@@ -275,7 +291,7 @@ const SummaryManager = (function() {
|
||||
};
|
||||
|
||||
webSocket.onclose = () => {
|
||||
setTimeout(setupWebSocket, 5000);
|
||||
CleanupManager.setTimeout(setupWebSocket, 5000);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -303,7 +319,7 @@ const SummaryManager = (function() {
|
||||
.then(() => {})
|
||||
.catch(() => {});
|
||||
|
||||
refreshTimer = setInterval(() => {
|
||||
refreshTimer = CleanupManager.setInterval(() => {
|
||||
publicAPI.fetchSummaryData()
|
||||
.then(() => {})
|
||||
.catch(() => {});
|
||||
@@ -386,7 +402,7 @@ const SummaryManager = (function() {
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
CleanupManager.setTimeout(() => {
|
||||
resolve(this.fetchSummaryData());
|
||||
}, config.retryDelay);
|
||||
});
|
||||
@@ -446,5 +462,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
//console.log('SummaryManager initialized with methods:', Object.keys(SummaryManager));
|
||||
console.log('SummaryManager initialized');
|
||||
|
||||
@@ -14,6 +14,9 @@ const TooltipManager = (function() {
|
||||
this.debug = false;
|
||||
this.tooltipData = new WeakMap();
|
||||
this.resources = {};
|
||||
this.creationQueue = [];
|
||||
this.batchSize = 5;
|
||||
this.isProcessingQueue = false;
|
||||
|
||||
if (window.CleanupManager) {
|
||||
CleanupManager.registerResource(
|
||||
@@ -48,40 +51,69 @@ const TooltipManager = (function() {
|
||||
this.performPeriodicCleanup(true);
|
||||
}
|
||||
|
||||
const createTooltip = () => {
|
||||
if (!document.body.contains(element)) return;
|
||||
this.creationQueue.push({ element, content, options });
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (rect.width > 0 && rect.height > 0) {
|
||||
this.createTooltipInstance(element, content, options);
|
||||
} else {
|
||||
let retryCount = 0;
|
||||
const maxRetries = 3;
|
||||
if (!this.isProcessingQueue) {
|
||||
this.processCreationQueue();
|
||||
}
|
||||
|
||||
const retryCreate = () => {
|
||||
const newRect = element.getBoundingClientRect();
|
||||
if ((newRect.width > 0 && newRect.height > 0) || retryCount >= maxRetries) {
|
||||
if (newRect.width > 0 && newRect.height > 0) {
|
||||
this.createTooltipInstance(element, content, options);
|
||||
}
|
||||
} else {
|
||||
retryCount++;
|
||||
CleanupManager.setTimeout(() => {
|
||||
CleanupManager.requestAnimationFrame(retryCreate);
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
CleanupManager.setTimeout(() => {
|
||||
CleanupManager.requestAnimationFrame(retryCreate);
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
CleanupManager.requestAnimationFrame(createTooltip);
|
||||
return null;
|
||||
}
|
||||
|
||||
processCreationQueue() {
|
||||
if (this.creationQueue.length === 0) {
|
||||
this.isProcessingQueue = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.isProcessingQueue = true;
|
||||
const batch = this.creationQueue.splice(0, this.batchSize);
|
||||
|
||||
CleanupManager.requestAnimationFrame(() => {
|
||||
batch.forEach(({ element, content, options }) => {
|
||||
this.createTooltipSync(element, content, options);
|
||||
});
|
||||
|
||||
if (this.creationQueue.length > 0) {
|
||||
CleanupManager.setTimeout(() => {
|
||||
this.processCreationQueue();
|
||||
}, 0);
|
||||
} else {
|
||||
this.isProcessingQueue = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
createTooltipSync(element, content, options) {
|
||||
if (!document.body.contains(element)) return;
|
||||
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (rect.width > 0 && rect.height > 0) {
|
||||
this.createTooltipInstance(element, content, options);
|
||||
} else {
|
||||
let retryCount = 0;
|
||||
const maxRetries = 3;
|
||||
|
||||
const retryCreate = () => {
|
||||
const newRect = element.getBoundingClientRect();
|
||||
if ((newRect.width > 0 && newRect.height > 0) || retryCount >= maxRetries) {
|
||||
if (newRect.width > 0 && newRect.height > 0) {
|
||||
this.createTooltipInstance(element, content, options);
|
||||
}
|
||||
} else {
|
||||
retryCount++;
|
||||
CleanupManager.setTimeout(() => {
|
||||
CleanupManager.requestAnimationFrame(retryCreate);
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
CleanupManager.setTimeout(() => {
|
||||
CleanupManager.requestAnimationFrame(retryCreate);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
createTooltipInstance(element, content, options = {}) {
|
||||
if (!element || !document.body.contains(element)) {
|
||||
return null;
|
||||
@@ -191,6 +223,9 @@ const TooltipManager = (function() {
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
if (window.ErrorHandler) {
|
||||
return window.ErrorHandler.handleError(error, 'TooltipManager.createTooltipInstance', null);
|
||||
}
|
||||
console.error('Error creating tooltip:', error);
|
||||
return null;
|
||||
}
|
||||
@@ -199,7 +234,7 @@ const TooltipManager = (function() {
|
||||
destroy(element) {
|
||||
if (!element) return;
|
||||
|
||||
try {
|
||||
const destroyFn = () => {
|
||||
const tooltipId = element.getAttribute('data-tooltip-trigger-id');
|
||||
if (!tooltipId) return;
|
||||
|
||||
@@ -224,8 +259,16 @@ const TooltipManager = (function() {
|
||||
|
||||
this.tooltipData.delete(element);
|
||||
tooltipInstanceMap.delete(element);
|
||||
} catch (error) {
|
||||
console.error('Error destroying tooltip:', error);
|
||||
};
|
||||
|
||||
if (window.ErrorHandler) {
|
||||
window.ErrorHandler.safeExecute(destroyFn, 'TooltipManager.destroy', null);
|
||||
} else {
|
||||
try {
|
||||
destroyFn();
|
||||
} catch (error) {
|
||||
console.error('Error destroying tooltip:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,10 +781,40 @@ const TooltipManager = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
initializeLazyTooltips(selector = '[data-tooltip-target]') {
|
||||
|
||||
const initializedTooltips = new Set();
|
||||
|
||||
const initializeTooltip = (element) => {
|
||||
const targetId = element.getAttribute('data-tooltip-target');
|
||||
if (!targetId || initializedTooltips.has(targetId)) return;
|
||||
|
||||
const tooltipContent = document.getElementById(targetId);
|
||||
if (tooltipContent) {
|
||||
this.create(element, tooltipContent.innerHTML, {
|
||||
placement: element.getAttribute('data-tooltip-placement') || 'top'
|
||||
});
|
||||
initializedTooltips.add(targetId);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mouseover', (e) => {
|
||||
const target = e.target.closest(selector);
|
||||
if (target) {
|
||||
initializeTooltip(target);
|
||||
}
|
||||
}, { passive: true, capture: true });
|
||||
|
||||
this.log('Lazy tooltip initialization enabled');
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.log('Disposing TooltipManager');
|
||||
|
||||
try {
|
||||
this.creationQueue = [];
|
||||
this.isProcessingQueue = false;
|
||||
|
||||
this.cleanup();
|
||||
|
||||
Object.values(this.resources).forEach(resourceId => {
|
||||
@@ -830,6 +903,11 @@ const TooltipManager = (function() {
|
||||
return manager.initializeTooltips(...args);
|
||||
},
|
||||
|
||||
initializeLazyTooltips: function(...args) {
|
||||
const manager = this.getInstance();
|
||||
return manager.initializeLazyTooltips(...args);
|
||||
},
|
||||
|
||||
setDebugMode: function(enabled) {
|
||||
const manager = this.getInstance();
|
||||
return manager.setDebugMode(enabled);
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const WalletAmountManager = {
|
||||
|
||||
coinConfigs: {
|
||||
1: {
|
||||
types: ['plain', 'blind', 'anon'],
|
||||
hasSubfee: true,
|
||||
hasSweepAll: false
|
||||
},
|
||||
3: {
|
||||
types: ['plain', 'mweb'],
|
||||
hasSubfee: true,
|
||||
hasSweepAll: false
|
||||
},
|
||||
6: {
|
||||
types: ['default'],
|
||||
hasSubfee: false,
|
||||
hasSweepAll: true
|
||||
},
|
||||
9: {
|
||||
types: ['default'],
|
||||
hasSubfee: false,
|
||||
hasSweepAll: true
|
||||
}
|
||||
},
|
||||
|
||||
safeParseFloat: function(value) {
|
||||
const numValue = Number(value);
|
||||
|
||||
if (!isNaN(numValue) && numValue > 0) {
|
||||
return numValue;
|
||||
}
|
||||
|
||||
console.warn('WalletAmountManager: Invalid balance value:', value);
|
||||
return 0;
|
||||
},
|
||||
|
||||
getBalance: function(coinId, balances, selectedType) {
|
||||
const cid = parseInt(coinId);
|
||||
|
||||
if (cid === 1) {
|
||||
switch(selectedType) {
|
||||
case 'plain':
|
||||
return this.safeParseFloat(balances.main || balances.balance);
|
||||
case 'blind':
|
||||
return this.safeParseFloat(balances.blind);
|
||||
case 'anon':
|
||||
return this.safeParseFloat(balances.anon);
|
||||
default:
|
||||
return this.safeParseFloat(balances.main || balances.balance);
|
||||
}
|
||||
}
|
||||
|
||||
if (cid === 3) {
|
||||
switch(selectedType) {
|
||||
case 'plain':
|
||||
return this.safeParseFloat(balances.main || balances.balance);
|
||||
case 'mweb':
|
||||
return this.safeParseFloat(balances.mweb);
|
||||
default:
|
||||
return this.safeParseFloat(balances.main || balances.balance);
|
||||
}
|
||||
}
|
||||
|
||||
return this.safeParseFloat(balances.main || balances.balance);
|
||||
},
|
||||
|
||||
calculateAmount: function(balance, percent, coinId) {
|
||||
const cid = parseInt(coinId);
|
||||
|
||||
if (percent === 1) {
|
||||
return balance;
|
||||
}
|
||||
|
||||
if (cid === 1) {
|
||||
return Math.max(0, Math.floor(balance * percent * 100000000) / 100000000);
|
||||
}
|
||||
|
||||
const calculatedAmount = balance * percent;
|
||||
|
||||
if (calculatedAmount < 0.00000001) {
|
||||
console.warn('WalletAmountManager: Calculated amount too small, setting to zero');
|
||||
return 0;
|
||||
}
|
||||
|
||||
return calculatedAmount;
|
||||
},
|
||||
|
||||
setAmount: function(percent, balances, coinId) {
|
||||
|
||||
const amountInput = window.DOMCache
|
||||
? window.DOMCache.get('amount')
|
||||
: document.getElementById('amount');
|
||||
const typeSelect = window.DOMCache
|
||||
? window.DOMCache.get('withdraw_type')
|
||||
: document.getElementById('withdraw_type');
|
||||
|
||||
if (!amountInput) {
|
||||
console.error('WalletAmountManager: Amount input not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const cid = parseInt(coinId);
|
||||
const selectedType = typeSelect ? typeSelect.value : 'plain';
|
||||
|
||||
const balance = this.getBalance(cid, balances, selectedType);
|
||||
|
||||
const calculatedAmount = this.calculateAmount(balance, percent, cid);
|
||||
|
||||
const specialCids = [6, 9];
|
||||
if (specialCids.includes(cid) && percent === 1) {
|
||||
amountInput.setAttribute('data-hidden', 'true');
|
||||
amountInput.placeholder = 'Sweep All';
|
||||
amountInput.value = '';
|
||||
amountInput.disabled = true;
|
||||
|
||||
const sweepAllCheckbox = window.DOMCache
|
||||
? window.DOMCache.get('sweepall')
|
||||
: document.getElementById('sweepall');
|
||||
if (sweepAllCheckbox) {
|
||||
sweepAllCheckbox.checked = true;
|
||||
}
|
||||
} else {
|
||||
|
||||
amountInput.value = calculatedAmount.toFixed(8);
|
||||
amountInput.setAttribute('data-hidden', 'false');
|
||||
amountInput.placeholder = '';
|
||||
amountInput.disabled = false;
|
||||
|
||||
const sweepAllCheckbox = window.DOMCache
|
||||
? window.DOMCache.get('sweepall')
|
||||
: document.getElementById('sweepall');
|
||||
if (sweepAllCheckbox) {
|
||||
sweepAllCheckbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
const subfeeCheckbox = document.querySelector(`[name="subfee_${cid}"]`);
|
||||
if (subfeeCheckbox) {
|
||||
subfeeCheckbox.checked = (percent === 1);
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
const amountButtons = document.querySelectorAll('[data-set-amount]');
|
||||
|
||||
amountButtons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const percent = parseFloat(button.getAttribute('data-set-amount'));
|
||||
const balancesJson = button.getAttribute('data-balances');
|
||||
const coinId = button.getAttribute('data-coin-id');
|
||||
|
||||
if (!balancesJson || !coinId) {
|
||||
console.error('WalletAmountManager: Missing data attributes on button', button);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const balances = JSON.parse(balancesJson);
|
||||
this.setAmount(percent, balances, coinId);
|
||||
} catch (error) {
|
||||
console.error('WalletAmountManager: Failed to parse balances', error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
WalletAmountManager.initialize();
|
||||
});
|
||||
} else {
|
||||
WalletAmountManager.initialize();
|
||||
}
|
||||
|
||||
window.WalletAmountManager = WalletAmountManager;
|
||||
|
||||
window.setAmount = function(percent, balance, coinId, balance2, balance3) {
|
||||
|
||||
const balances = {
|
||||
main: balance || balance,
|
||||
balance: balance,
|
||||
blind: balance2,
|
||||
anon: balance3,
|
||||
mweb: balance2
|
||||
};
|
||||
WalletAmountManager.setAmount(percent, balances, coinId);
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -95,22 +95,32 @@ const WalletManager = (function() {
|
||||
|
||||
const fetchCoinsString = coinsToFetch.join(',');
|
||||
|
||||
const mainResponse = await fetch("/json/coinprices", {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
let mainData;
|
||||
|
||||
if (window.ApiManager) {
|
||||
mainData = await window.ApiManager.makeRequest("/json/coinprices", "POST", {}, {
|
||||
coins: fetchCoinsString,
|
||||
source: currentSource,
|
||||
ttl: config.defaultTTL
|
||||
})
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const mainResponse = await fetch("/json/coinprices", {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
coins: fetchCoinsString,
|
||||
source: currentSource,
|
||||
ttl: config.defaultTTL
|
||||
})
|
||||
});
|
||||
|
||||
if (!mainResponse.ok) {
|
||||
throw new Error(`HTTP error: ${mainResponse.status}`);
|
||||
if (!mainResponse.ok) {
|
||||
throw new Error(`HTTP error: ${mainResponse.status}`);
|
||||
}
|
||||
|
||||
mainData = await mainResponse.json();
|
||||
}
|
||||
|
||||
const mainData = await mainResponse.json();
|
||||
|
||||
if (mainData && mainData.rates) {
|
||||
document.querySelectorAll('.coinname-value').forEach(el => {
|
||||
const coinName = el.getAttribute('data-coinname');
|
||||
@@ -154,7 +164,7 @@ const WalletManager = (function() {
|
||||
|
||||
if (attempt < config.maxRetries - 1) {
|
||||
const delay = Math.min(config.baseDelay * Math.pow(2, attempt), 10000);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
await new Promise(resolve => CleanupManager.setTimeout(resolve, delay));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,7 +438,7 @@ const WalletManager = (function() {
|
||||
clearTimeout(state.toggleDebounceTimer);
|
||||
}
|
||||
|
||||
state.toggleDebounceTimer = window.setTimeout(async () => {
|
||||
state.toggleDebounceTimer = CleanupManager.setTimeout(async () => {
|
||||
state.toggleInProgress = false;
|
||||
if (newVisibility) {
|
||||
await updatePrices(true);
|
||||
@@ -539,7 +549,6 @@ const WalletManager = (function() {
|
||||
}
|
||||
}
|
||||
|
||||
// Public API
|
||||
const publicAPI = {
|
||||
initialize: async function(options) {
|
||||
if (state.initialized) {
|
||||
@@ -579,7 +588,7 @@ const WalletManager = (function() {
|
||||
clearInterval(state.priceUpdateInterval);
|
||||
}
|
||||
|
||||
state.priceUpdateInterval = setInterval(() => {
|
||||
state.priceUpdateInterval = CleanupManager.setInterval(() => {
|
||||
if (localStorage.getItem('balancesVisible') === 'true' && !state.toggleInProgress) {
|
||||
updatePrices(false);
|
||||
}
|
||||
@@ -661,5 +670,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
//console.log('WalletManager initialized with methods:', Object.keys(WalletManager));
|
||||
console.log('WalletManager initialized');
|
||||
|
||||
@@ -32,26 +32,24 @@ const WebSocketManager = (function() {
|
||||
}
|
||||
|
||||
function determineWebSocketPort() {
|
||||
let wsPort;
|
||||
if (window.ConfigManager && window.ConfigManager.wsPort) {
|
||||
return window.ConfigManager.wsPort.toString();
|
||||
}
|
||||
|
||||
if (window.config && window.config.wsPort) {
|
||||
wsPort = window.config.wsPort;
|
||||
return wsPort;
|
||||
return window.config.wsPort.toString();
|
||||
}
|
||||
|
||||
if (window.ws_port) {
|
||||
wsPort = window.ws_port.toString();
|
||||
return wsPort;
|
||||
return window.ws_port.toString();
|
||||
}
|
||||
|
||||
if (typeof getWebSocketConfig === 'function') {
|
||||
const wsConfig = getWebSocketConfig();
|
||||
wsPort = (wsConfig.port || wsConfig.fallbackPort || '11700').toString();
|
||||
return wsPort;
|
||||
return (wsConfig.port || wsConfig.fallbackPort || '11700').toString();
|
||||
}
|
||||
|
||||
wsPort = '11700';
|
||||
return wsPort;
|
||||
return '11700';
|
||||
}
|
||||
|
||||
const publicAPI = {
|
||||
@@ -77,7 +75,11 @@ const WebSocketManager = (function() {
|
||||
}
|
||||
|
||||
if (state.reconnectTimeout) {
|
||||
clearTimeout(state.reconnectTimeout);
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.clearTimeout(state.reconnectTimeout);
|
||||
} else {
|
||||
clearTimeout(state.reconnectTimeout);
|
||||
}
|
||||
state.reconnectTimeout = null;
|
||||
}
|
||||
|
||||
@@ -96,13 +98,17 @@ const WebSocketManager = (function() {
|
||||
ws = new WebSocket(`ws://${window.location.hostname}:${wsPort}`);
|
||||
setupEventHandlers();
|
||||
|
||||
state.connectTimeout = setTimeout(() => {
|
||||
const timeoutFn = () => {
|
||||
if (state.isConnecting) {
|
||||
log('Connection timeout, cleaning up');
|
||||
cleanup();
|
||||
handleReconnect();
|
||||
}
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
state.connectTimeout = window.CleanupManager
|
||||
? window.CleanupManager.setTimeout(timeoutFn, 5000)
|
||||
: setTimeout(timeoutFn, 5000);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -159,18 +165,25 @@ const WebSocketManager = (function() {
|
||||
cleanup: function() {
|
||||
log('Cleaning up WebSocket resources');
|
||||
|
||||
clearTimeout(state.connectTimeout);
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.clearTimeout(state.connectTimeout);
|
||||
} else {
|
||||
clearTimeout(state.connectTimeout);
|
||||
}
|
||||
stopHealthCheck();
|
||||
|
||||
if (state.reconnectTimeout) {
|
||||
clearTimeout(state.reconnectTimeout);
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.clearTimeout(state.reconnectTimeout);
|
||||
} else {
|
||||
clearTimeout(state.reconnectTimeout);
|
||||
}
|
||||
state.reconnectTimeout = null;
|
||||
}
|
||||
|
||||
state.isConnecting = false;
|
||||
state.messageHandlers = {};
|
||||
|
||||
|
||||
if (ws) {
|
||||
ws.onopen = null;
|
||||
ws.onmessage = null;
|
||||
@@ -228,7 +241,11 @@ const WebSocketManager = (function() {
|
||||
ws.onopen = () => {
|
||||
state.isConnecting = false;
|
||||
config.reconnectAttempts = 0;
|
||||
clearTimeout(state.connectTimeout);
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.clearTimeout(state.connectTimeout);
|
||||
} else {
|
||||
clearTimeout(state.connectTimeout);
|
||||
}
|
||||
state.lastHealthCheck = Date.now();
|
||||
window.ws = ws;
|
||||
|
||||
@@ -311,24 +328,37 @@ const WebSocketManager = (function() {
|
||||
state.isPageHidden = false;
|
||||
state.isIntentionallyClosed = false;
|
||||
|
||||
setTimeout(() => {
|
||||
const resumeFn = () => {
|
||||
if (!publicAPI.isConnected()) {
|
||||
publicAPI.connect();
|
||||
}
|
||||
startHealthCheck();
|
||||
}, 0);
|
||||
};
|
||||
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.setTimeout(resumeFn, 0);
|
||||
} else {
|
||||
setTimeout(resumeFn, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function startHealthCheck() {
|
||||
stopHealthCheck();
|
||||
state.healthCheckInterval = setInterval(() => {
|
||||
const healthCheckFn = () => {
|
||||
performHealthCheck();
|
||||
}, 30000);
|
||||
};
|
||||
state.healthCheckInterval = window.CleanupManager
|
||||
? window.CleanupManager.setInterval(healthCheckFn, 30000)
|
||||
: setInterval(healthCheckFn, 30000);
|
||||
}
|
||||
|
||||
function stopHealthCheck() {
|
||||
if (state.healthCheckInterval) {
|
||||
clearInterval(state.healthCheckInterval);
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.clearInterval(state.healthCheckInterval);
|
||||
} else {
|
||||
clearInterval(state.healthCheckInterval);
|
||||
}
|
||||
state.healthCheckInterval = null;
|
||||
}
|
||||
}
|
||||
@@ -356,7 +386,11 @@ const WebSocketManager = (function() {
|
||||
function handleReconnect() {
|
||||
|
||||
if (state.reconnectTimeout) {
|
||||
clearTimeout(state.reconnectTimeout);
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.clearTimeout(state.reconnectTimeout);
|
||||
} else {
|
||||
clearTimeout(state.reconnectTimeout);
|
||||
}
|
||||
state.reconnectTimeout = null;
|
||||
}
|
||||
|
||||
@@ -369,23 +403,31 @@ const WebSocketManager = (function() {
|
||||
|
||||
log(`Scheduling reconnect in ${delay}ms (attempt ${config.reconnectAttempts})`);
|
||||
|
||||
state.reconnectTimeout = setTimeout(() => {
|
||||
const reconnectFn = () => {
|
||||
state.reconnectTimeout = null;
|
||||
if (!state.isIntentionallyClosed) {
|
||||
publicAPI.connect();
|
||||
}
|
||||
}, delay);
|
||||
};
|
||||
|
||||
state.reconnectTimeout = window.CleanupManager
|
||||
? window.CleanupManager.setTimeout(reconnectFn, delay)
|
||||
: setTimeout(reconnectFn, delay);
|
||||
} else {
|
||||
log('Max reconnect attempts reached');
|
||||
if (typeof updateConnectionStatus === 'function') {
|
||||
updateConnectionStatus('error');
|
||||
}
|
||||
|
||||
state.reconnectTimeout = setTimeout(() => {
|
||||
const resetFn = () => {
|
||||
state.reconnectTimeout = null;
|
||||
config.reconnectAttempts = 0;
|
||||
publicAPI.connect();
|
||||
}, 60000);
|
||||
};
|
||||
|
||||
state.reconnectTimeout = window.CleanupManager
|
||||
? window.CleanupManager.setTimeout(resetFn, 60000)
|
||||
: setTimeout(resetFn, 60000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,5 +484,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
//console.log('WebSocketManager initialized with methods:', Object.keys(WebSocketManager));
|
||||
console.log('WebSocketManager initialized');
|
||||
|
||||
Reference in New Issue
Block a user