mirror of
https://github.com/basicswap/basicswap.git
synced 2026-05-09 07:52:13 +02:00
Extra refactor + Various bug/fixes. (#293)
* Refactor + Various Fixes. * WS / LINT * Show also failed status. * Fix sorting market +/- * Simplified swaps in progress * Black * Update basicswap/static/js/modules/coin-manager.js Co-authored-by: nahuhh <50635951+nahuhh@users.noreply.github.com> * Update basicswap/static/js/modules/coin-manager.js Co-authored-by: nahuhh <50635951+nahuhh@users.noreply.github.com> * Fixes + GUI: v3.2.1 * Fixes + AutoRefreshEnabled true as default. * Fix small memory issue since new features added, --------- Co-authored-by: nahuhh <50635951+nahuhh@users.noreply.github.com>
This commit is contained in:
@@ -201,12 +201,23 @@ const ApiManager = (function() {
|
||||
},
|
||||
|
||||
fetchCoinPrices: async function(coins, source = "coingecko.com", ttl = 300) {
|
||||
if (!Array.isArray(coins)) {
|
||||
coins = [coins];
|
||||
if (!coins) {
|
||||
throw new Error('No coins specified for price lookup');
|
||||
}
|
||||
let coinsParam;
|
||||
if (Array.isArray(coins)) {
|
||||
coinsParam = coins.filter(c => c && c.trim() !== '').join(',');
|
||||
} else if (typeof coins === 'object' && coins.coins) {
|
||||
coinsParam = coins.coins;
|
||||
} else {
|
||||
coinsParam = coins;
|
||||
}
|
||||
if (!coinsParam || coinsParam.trim() === '') {
|
||||
throw new Error('No valid coins to fetch prices for');
|
||||
}
|
||||
|
||||
return this.makeRequest('/json/coinprices', 'POST', {}, {
|
||||
coins: Array.isArray(coins) ? coins.join(',') : coins,
|
||||
coins: coinsParam,
|
||||
source: source,
|
||||
ttl: ttl
|
||||
});
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
const CoinManager = (function() {
|
||||
const coinRegistry = [
|
||||
{
|
||||
symbol: 'BTC',
|
||||
name: 'bitcoin',
|
||||
displayName: 'Bitcoin',
|
||||
aliases: ['btc', 'bitcoin'],
|
||||
coingeckoId: 'bitcoin',
|
||||
cryptocompareId: 'BTC',
|
||||
usesCryptoCompare: false,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Bitcoin.png'
|
||||
},
|
||||
{
|
||||
symbol: 'XMR',
|
||||
name: 'monero',
|
||||
displayName: 'Monero',
|
||||
aliases: ['xmr', 'monero'],
|
||||
coingeckoId: 'monero',
|
||||
cryptocompareId: 'XMR',
|
||||
usesCryptoCompare: true,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Monero.png'
|
||||
},
|
||||
{
|
||||
symbol: 'PART',
|
||||
name: 'particl',
|
||||
displayName: 'Particl',
|
||||
aliases: ['part', 'particl', 'particl anon', 'particl blind'],
|
||||
variants: ['Particl', 'Particl Blind', 'Particl Anon'],
|
||||
coingeckoId: 'particl',
|
||||
cryptocompareId: 'PART',
|
||||
usesCryptoCompare: true,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Particl.png'
|
||||
},
|
||||
{
|
||||
symbol: 'BCH',
|
||||
name: 'bitcoin-cash',
|
||||
displayName: 'Bitcoin Cash',
|
||||
aliases: ['bch', 'bitcoincash', 'bitcoin cash'],
|
||||
coingeckoId: 'bitcoin-cash',
|
||||
cryptocompareId: 'BCH',
|
||||
usesCryptoCompare: true,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Bitcoin-Cash.png'
|
||||
},
|
||||
{
|
||||
symbol: 'PIVX',
|
||||
name: 'pivx',
|
||||
displayName: 'PIVX',
|
||||
aliases: ['pivx'],
|
||||
coingeckoId: 'pivx',
|
||||
cryptocompareId: 'PIVX',
|
||||
usesCryptoCompare: true,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'PIVX.png'
|
||||
},
|
||||
{
|
||||
symbol: 'FIRO',
|
||||
name: 'firo',
|
||||
displayName: 'Firo',
|
||||
aliases: ['firo', 'zcoin'],
|
||||
coingeckoId: 'firo',
|
||||
cryptocompareId: 'FIRO',
|
||||
usesCryptoCompare: true,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Firo.png'
|
||||
},
|
||||
{
|
||||
symbol: 'DASH',
|
||||
name: 'dash',
|
||||
displayName: 'Dash',
|
||||
aliases: ['dash'],
|
||||
coingeckoId: 'dash',
|
||||
cryptocompareId: 'DASH',
|
||||
usesCryptoCompare: true,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Dash.png'
|
||||
},
|
||||
{
|
||||
symbol: 'LTC',
|
||||
name: 'litecoin',
|
||||
displayName: 'Litecoin',
|
||||
aliases: ['ltc', 'litecoin'],
|
||||
variants: ['Litecoin', 'Litecoin MWEB'],
|
||||
coingeckoId: 'litecoin',
|
||||
cryptocompareId: 'LTC',
|
||||
usesCryptoCompare: true,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Litecoin.png'
|
||||
},
|
||||
{
|
||||
symbol: 'DOGE',
|
||||
name: 'dogecoin',
|
||||
displayName: 'Dogecoin',
|
||||
aliases: ['doge', 'dogecoin'],
|
||||
coingeckoId: 'dogecoin',
|
||||
cryptocompareId: 'DOGE',
|
||||
usesCryptoCompare: true,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Dogecoin.png'
|
||||
},
|
||||
{
|
||||
symbol: 'DCR',
|
||||
name: 'decred',
|
||||
displayName: 'Decred',
|
||||
aliases: ['dcr', 'decred'],
|
||||
coingeckoId: 'decred',
|
||||
cryptocompareId: 'DCR',
|
||||
usesCryptoCompare: true,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Decred.png'
|
||||
},
|
||||
{
|
||||
symbol: 'NMC',
|
||||
name: 'namecoin',
|
||||
displayName: 'Namecoin',
|
||||
aliases: ['nmc', 'namecoin'],
|
||||
coingeckoId: 'namecoin',
|
||||
cryptocompareId: 'NMC',
|
||||
usesCryptoCompare: true,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Namecoin.png'
|
||||
},
|
||||
{
|
||||
symbol: 'WOW',
|
||||
name: 'wownero',
|
||||
displayName: 'Wownero',
|
||||
aliases: ['wow', 'wownero'],
|
||||
coingeckoId: 'wownero',
|
||||
cryptocompareId: 'WOW',
|
||||
usesCryptoCompare: false,
|
||||
usesCoinGecko: true,
|
||||
historicalDays: 30,
|
||||
icon: 'Wownero.png'
|
||||
}
|
||||
];
|
||||
const symbolToInfo = {};
|
||||
const nameToInfo = {};
|
||||
const displayNameToInfo = {};
|
||||
const coinAliasesMap = {};
|
||||
|
||||
function buildLookupMaps() {
|
||||
coinRegistry.forEach(coin => {
|
||||
symbolToInfo[coin.symbol.toLowerCase()] = coin;
|
||||
nameToInfo[coin.name.toLowerCase()] = coin;
|
||||
displayNameToInfo[coin.displayName.toLowerCase()] = coin;
|
||||
if (coin.aliases && Array.isArray(coin.aliases)) {
|
||||
coin.aliases.forEach(alias => {
|
||||
coinAliasesMap[alias.toLowerCase()] = coin;
|
||||
});
|
||||
}
|
||||
coinAliasesMap[coin.symbol.toLowerCase()] = coin;
|
||||
coinAliasesMap[coin.name.toLowerCase()] = coin;
|
||||
coinAliasesMap[coin.displayName.toLowerCase()] = coin;
|
||||
if (coin.variants && Array.isArray(coin.variants)) {
|
||||
coin.variants.forEach(variant => {
|
||||
coinAliasesMap[variant.toLowerCase()] = coin;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
buildLookupMaps();
|
||||
|
||||
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 {
|
||||
getAllCoins: function() {
|
||||
return [...coinRegistry];
|
||||
},
|
||||
getCoinByAnyIdentifier: getCoinByAnyIdentifier,
|
||||
getSymbol: function(identifier) {
|
||||
const coin = getCoinByAnyIdentifier(identifier);
|
||||
return coin ? coin.symbol : null;
|
||||
},
|
||||
getDisplayName: function(identifier) {
|
||||
const coin = getCoinByAnyIdentifier(identifier);
|
||||
return coin ? coin.displayName : null;
|
||||
},
|
||||
getCoingeckoId: function(identifier) {
|
||||
const coin = getCoinByAnyIdentifier(identifier);
|
||||
return coin ? coin.coingeckoId : null;
|
||||
},
|
||||
coinMatches: function(coinId1, coinId2) {
|
||||
if (!coinId1 || !coinId2) return false;
|
||||
const coin1 = getCoinByAnyIdentifier(coinId1);
|
||||
const coin2 = getCoinByAnyIdentifier(coinId2);
|
||||
if (!coin1 || !coin2) return false;
|
||||
return coin1.symbol === coin2.symbol;
|
||||
},
|
||||
getPriceKey: function(coinIdentifier) {
|
||||
if (!coinIdentifier) return null;
|
||||
const coin = getCoinByAnyIdentifier(coinIdentifier);
|
||||
if (!coin) return coinIdentifier.toLowerCase();
|
||||
return coin.coingeckoId;
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
window.CoinManager = CoinManager;
|
||||
console.log('CoinManager initialized');
|
||||
@@ -17,10 +17,8 @@ const ConfigManager = (function() {
|
||||
cacheDuration: 10 * 60 * 1000,
|
||||
requestTimeout: 60000,
|
||||
wsPort: selectedWsPort,
|
||||
|
||||
cacheConfig: {
|
||||
defaultTTL: 10 * 60 * 1000,
|
||||
|
||||
ttlSettings: {
|
||||
prices: 5 * 60 * 1000,
|
||||
chart: 5 * 60 * 1000,
|
||||
@@ -29,17 +27,13 @@ const ConfigManager = (function() {
|
||||
offers: 2 * 60 * 1000,
|
||||
identity: 15 * 60 * 1000
|
||||
},
|
||||
|
||||
storage: {
|
||||
maxSizeBytes: 10 * 1024 * 1024,
|
||||
maxItems: 200
|
||||
},
|
||||
|
||||
fallbackTTL: 24 * 60 * 60 * 1000
|
||||
},
|
||||
|
||||
itemsPerPage: 50,
|
||||
|
||||
apiEndpoints: {
|
||||
cryptoCompare: 'https://min-api.cryptocompare.com/data/pricemultifull',
|
||||
coinGecko: 'https://api.coingecko.com/api/v3',
|
||||
@@ -47,7 +41,6 @@ const ConfigManager = (function() {
|
||||
cryptoCompareHourly: 'https://min-api.cryptocompare.com/data/v2/histohour',
|
||||
volumeEndpoint: 'https://api.coingecko.com/api/v3/simple/price'
|
||||
},
|
||||
|
||||
rateLimits: {
|
||||
coingecko: {
|
||||
requestsPerMinute: 50,
|
||||
@@ -58,86 +51,23 @@ const ConfigManager = (function() {
|
||||
minInterval: 2000
|
||||
}
|
||||
},
|
||||
|
||||
retryDelays: [5000, 15000, 30000],
|
||||
|
||||
coins: [
|
||||
{ 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 }
|
||||
],
|
||||
|
||||
coinMappings: {
|
||||
nameToSymbol: {
|
||||
'Bitcoin': 'BTC',
|
||||
'Litecoin': 'LTC',
|
||||
'Monero': 'XMR',
|
||||
'Particl': 'PART',
|
||||
'Particl Blind': 'PART',
|
||||
'Particl Anon': 'PART',
|
||||
'PIVX': 'PIVX',
|
||||
'Firo': 'FIRO',
|
||||
'Zcoin': 'FIRO',
|
||||
'Dash': 'DASH',
|
||||
'Decred': 'DCR',
|
||||
'Namecoin': 'NMC',
|
||||
'Wownero': 'WOW',
|
||||
'Bitcoin Cash': 'BCH',
|
||||
'Dogecoin': 'DOGE'
|
||||
},
|
||||
|
||||
nameToDisplayName: {
|
||||
'Bitcoin': 'Bitcoin',
|
||||
'Litecoin': 'Litecoin',
|
||||
'Monero': 'Monero',
|
||||
'Particl': 'Particl',
|
||||
'Particl Blind': 'Particl Blind',
|
||||
'Particl Anon': 'Particl Anon',
|
||||
'PIVX': 'PIVX',
|
||||
'Firo': 'Firo',
|
||||
'Zcoin': 'Firo',
|
||||
'Dash': 'Dash',
|
||||
'Decred': 'Decred',
|
||||
'Namecoin': 'Namecoin',
|
||||
'Wownero': 'Wownero',
|
||||
'Bitcoin Cash': 'Bitcoin Cash',
|
||||
'Dogecoin': 'Dogecoin'
|
||||
},
|
||||
|
||||
idToName: {
|
||||
1: 'particl', 2: 'bitcoin', 3: 'litecoin', 4: 'decred', 5: 'namecoin',
|
||||
6: 'monero', 7: 'particl blind', 8: 'particl anon',
|
||||
9: 'wownero', 11: 'pivx', 13: 'firo', 17: 'bitcoincash',
|
||||
18: 'dogecoin'
|
||||
},
|
||||
|
||||
nameToCoinGecko: {
|
||||
'bitcoin': 'bitcoin',
|
||||
'monero': 'monero',
|
||||
'particl': 'particl',
|
||||
'bitcoin cash': 'bitcoin-cash',
|
||||
'bitcoincash': 'bitcoin-cash',
|
||||
'pivx': 'pivx',
|
||||
'firo': 'firo',
|
||||
'zcoin': 'firo',
|
||||
'dash': 'dash',
|
||||
'litecoin': 'litecoin',
|
||||
'dogecoin': 'dogecoin',
|
||||
'decred': 'decred',
|
||||
'namecoin': 'namecoin',
|
||||
'wownero': 'wownero'
|
||||
}
|
||||
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 }
|
||||
];
|
||||
},
|
||||
|
||||
chartConfig: {
|
||||
colors: {
|
||||
default: {
|
||||
@@ -158,28 +88,22 @@ const ConfigManager = (function() {
|
||||
|
||||
const publicAPI = {
|
||||
...defaultConfig,
|
||||
|
||||
initialize: function(options = {}) {
|
||||
if (state.isInitialized) {
|
||||
console.warn('[ConfigManager] Already initialized');
|
||||
return this;
|
||||
}
|
||||
|
||||
if (options) {
|
||||
Object.assign(this, options);
|
||||
}
|
||||
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.registerResource('configManager', this, (mgr) => mgr.dispose());
|
||||
}
|
||||
|
||||
this.utils = utils;
|
||||
|
||||
state.isInitialized = true;
|
||||
console.log('ConfigManager initialized');
|
||||
return this;
|
||||
},
|
||||
|
||||
getAPIKeys: function() {
|
||||
if (typeof window.getAPIKeys === 'function') {
|
||||
const apiKeys = window.getAPIKeys();
|
||||
@@ -188,16 +112,16 @@ const ConfigManager = (function() {
|
||||
coinGecko: apiKeys.coinGecko || ''
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
cryptoCompare: '',
|
||||
coinGecko: ''
|
||||
};
|
||||
},
|
||||
|
||||
getCoinBackendId: function(coinName) {
|
||||
if (!coinName) return null;
|
||||
|
||||
if (window.CoinManager) {
|
||||
return window.CoinManager.getPriceKey(coinName);
|
||||
}
|
||||
const nameMap = {
|
||||
'bitcoin-cash': 'bitcoincash',
|
||||
'bitcoin cash': 'bitcoincash',
|
||||
@@ -205,70 +129,57 @@ const ConfigManager = (function() {
|
||||
'zcoin': 'firo',
|
||||
'bitcoincash': 'bitcoin-cash'
|
||||
};
|
||||
|
||||
const lowerCoinName = typeof coinName === 'string' ? coinName.toLowerCase() : '';
|
||||
return nameMap[lowerCoinName] || lowerCoinName;
|
||||
},
|
||||
|
||||
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 ((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 (particlVariants.includes(filterCoin)) {
|
||||
return offerCoin === filterCoin;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
update: function(path, value) {
|
||||
const parts = path.split('.');
|
||||
let current = this;
|
||||
|
||||
for (let i = 0; i < parts.length - 1; i++) {
|
||||
if (!current[parts[i]]) {
|
||||
current[parts[i]] = {};
|
||||
}
|
||||
current = current[parts[i]];
|
||||
}
|
||||
|
||||
current[parts[parts.length - 1]] = value;
|
||||
return this;
|
||||
},
|
||||
|
||||
get: function(path, defaultValue = null) {
|
||||
const parts = path.split('.');
|
||||
let current = this;
|
||||
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
if (current === undefined || current === null) {
|
||||
return defaultValue;
|
||||
}
|
||||
current = current[parts[i]];
|
||||
}
|
||||
|
||||
return current !== undefined ? current : defaultValue;
|
||||
},
|
||||
|
||||
dispose: function() {
|
||||
state.isInitialized = false;
|
||||
console.log('ConfigManager disposed');
|
||||
@@ -290,7 +201,6 @@ const ConfigManager = (function() {
|
||||
return '0';
|
||||
}
|
||||
},
|
||||
|
||||
formatDate: function(timestamp, resolution) {
|
||||
const date = new Date(timestamp);
|
||||
const options = {
|
||||
@@ -300,7 +210,6 @@ const ConfigManager = (function() {
|
||||
};
|
||||
return date.toLocaleString('en-US', { ...options[resolution], timeZone: 'UTC' });
|
||||
},
|
||||
|
||||
debounce: function(func, delay) {
|
||||
let timeoutId;
|
||||
return function(...args) {
|
||||
@@ -308,17 +217,14 @@ const ConfigManager = (function() {
|
||||
timeoutId = setTimeout(() => func(...args), delay);
|
||||
};
|
||||
},
|
||||
|
||||
formatTimeLeft: function(timestamp) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
if (timestamp <= now) return "Expired";
|
||||
return this.formatTime(timestamp);
|
||||
},
|
||||
|
||||
formatTime: function(timestamp, addAgoSuffix = false) {
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const diff = Math.abs(now - timestamp);
|
||||
|
||||
let timeString;
|
||||
if (diff < 60) {
|
||||
timeString = `${diff} seconds`;
|
||||
@@ -333,10 +239,8 @@ const ConfigManager = (function() {
|
||||
} else {
|
||||
timeString = `${Math.floor(diff / 31536000)} years`;
|
||||
}
|
||||
|
||||
return addAgoSuffix ? `${timeString} ago` : timeString;
|
||||
},
|
||||
|
||||
escapeHtml: function(unsafe) {
|
||||
if (typeof unsafe !== 'string') {
|
||||
console.warn('escapeHtml received a non-string value:', unsafe);
|
||||
@@ -349,7 +253,6 @@ const ConfigManager = (function() {
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'");
|
||||
},
|
||||
|
||||
formatPrice: function(coin, price) {
|
||||
if (typeof price !== 'number' || isNaN(price)) {
|
||||
console.warn(`Invalid price for ${coin}:`, price);
|
||||
@@ -363,7 +266,6 @@ const ConfigManager = (function() {
|
||||
if (price < 100000) return price.toFixed(1);
|
||||
return price.toFixed(0);
|
||||
},
|
||||
|
||||
getEmptyPriceData: function() {
|
||||
return {
|
||||
'bitcoin': { usd: null, btc: null },
|
||||
@@ -381,12 +283,13 @@ const ConfigManager = (function() {
|
||||
'firo': { usd: null, btc: null }
|
||||
};
|
||||
},
|
||||
|
||||
getCoinSymbol: function(fullName) {
|
||||
return publicAPI.coinMappings?.nameToSymbol[fullName] || fullName;
|
||||
if (window.CoinManager) {
|
||||
return window.CoinManager.getSymbol(fullName) || fullName;
|
||||
}
|
||||
return fullName;
|
||||
}
|
||||
};
|
||||
|
||||
return publicAPI;
|
||||
})();
|
||||
|
||||
@@ -415,5 +318,4 @@ if (typeof module !== 'undefined') {
|
||||
module.exports = ConfigManager;
|
||||
}
|
||||
|
||||
//console.log('ConfigManager initialized with properties:', Object.keys(ConfigManager));
|
||||
console.log('ConfigManager initialized');
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
const PriceManager = (function() {
|
||||
const PRICES_CACHE_KEY = 'prices_unified';
|
||||
let fetchPromise = null;
|
||||
let lastFetchTime = 0;
|
||||
const MIN_FETCH_INTERVAL = 60000;
|
||||
let isInitialized = false;
|
||||
const eventListeners = {
|
||||
'priceUpdate': [],
|
||||
'error': []
|
||||
};
|
||||
|
||||
return {
|
||||
addEventListener: function(event, callback) {
|
||||
if (eventListeners[event]) {
|
||||
eventListeners[event].push(callback);
|
||||
}
|
||||
},
|
||||
|
||||
removeEventListener: function(event, callback) {
|
||||
if (eventListeners[event]) {
|
||||
eventListeners[event] = eventListeners[event].filter(cb => cb !== callback);
|
||||
}
|
||||
},
|
||||
|
||||
triggerEvent: function(event, data) {
|
||||
if (eventListeners[event]) {
|
||||
eventListeners[event].forEach(callback => callback(data));
|
||||
}
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
if (isInitialized) {
|
||||
console.warn('PriceManager: Already initialized');
|
||||
return this;
|
||||
}
|
||||
|
||||
if (window.CleanupManager) {
|
||||
window.CleanupManager.registerResource('priceManager', this, (mgr) => {
|
||||
Object.keys(eventListeners).forEach(event => {
|
||||
eventListeners[event] = [];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setTimeout(() => this.getPrices(), 1500);
|
||||
isInitialized = true;
|
||||
return this;
|
||||
},
|
||||
|
||||
getPrices: async function(forceRefresh = false) {
|
||||
if (!forceRefresh) {
|
||||
const cachedData = CacheManager.get(PRICES_CACHE_KEY);
|
||||
if (cachedData) {
|
||||
return cachedData.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (fetchPromise && Date.now() - lastFetchTime < MIN_FETCH_INTERVAL) {
|
||||
return fetchPromise;
|
||||
}
|
||||
|
||||
console.log('PriceManager: Fetching latest prices.');
|
||||
lastFetchTime = Date.now();
|
||||
fetchPromise = this.fetchPrices()
|
||||
.then(prices => {
|
||||
this.triggerEvent('priceUpdate', prices);
|
||||
return prices;
|
||||
})
|
||||
.catch(error => {
|
||||
this.triggerEvent('error', error);
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
fetchPromise = null;
|
||||
});
|
||||
|
||||
return fetchPromise;
|
||||
},
|
||||
|
||||
fetchPrices: async function() {
|
||||
try {
|
||||
if (!NetworkManager.isOnline()) {
|
||||
throw new Error('Network is offline');
|
||||
}
|
||||
|
||||
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']);
|
||||
|
||||
console.log('PriceManager: lookupFiatRates ' + coinSymbols.join(', '));
|
||||
|
||||
if (!coinSymbols.length) {
|
||||
throw new Error('No valid coins configured');
|
||||
}
|
||||
|
||||
let apiResponse;
|
||||
try {
|
||||
apiResponse = await Api.fetchCoinPrices(
|
||||
coinSymbols,
|
||||
"coingecko.com",
|
||||
300
|
||||
);
|
||||
|
||||
if (!apiResponse) {
|
||||
throw new Error('Empty response received from API');
|
||||
}
|
||||
|
||||
if (apiResponse.error) {
|
||||
throw new Error(`API error: ${apiResponse.error}`);
|
||||
}
|
||||
|
||||
if (!apiResponse.rates) {
|
||||
throw new Error('No rates found in API response');
|
||||
}
|
||||
|
||||
if (typeof apiResponse.rates !== 'object' || Object.keys(apiResponse.rates).length === 0) {
|
||||
throw new Error('Empty rates object in API response');
|
||||
}
|
||||
} catch (apiError) {
|
||||
console.error('API call error:', apiError);
|
||||
throw new Error(`API error: ${apiError.message}`);
|
||||
}
|
||||
|
||||
const processedData = {};
|
||||
|
||||
Object.entries(apiResponse.rates).forEach(([coinId, price]) => {
|
||||
let normalizedCoinId;
|
||||
|
||||
if (window.CoinManager) {
|
||||
const coin = window.CoinManager.getCoinByAnyIdentifier(coinId);
|
||||
if (coin) {
|
||||
normalizedCoinId = window.CoinManager.getPriceKey(coin.name);
|
||||
} else {
|
||||
normalizedCoinId = coinId === 'bitcoincash' ? 'bitcoin-cash' : coinId.toLowerCase();
|
||||
}
|
||||
} else {
|
||||
normalizedCoinId = coinId === 'bitcoincash' ? 'bitcoin-cash' : coinId.toLowerCase();
|
||||
}
|
||||
|
||||
if (coinId.toLowerCase() === 'zcoin') {
|
||||
normalizedCoinId = 'firo';
|
||||
}
|
||||
|
||||
processedData[normalizedCoinId] = {
|
||||
usd: price,
|
||||
btc: normalizedCoinId === 'bitcoin' ? 1 : price / (apiResponse.rates.bitcoin || 1)
|
||||
};
|
||||
});
|
||||
|
||||
CacheManager.set(PRICES_CACHE_KEY, processedData, 'prices');
|
||||
|
||||
Object.entries(processedData).forEach(([coin, prices]) => {
|
||||
if (prices.usd) {
|
||||
if (window.tableRateModule) {
|
||||
window.tableRateModule.setFallbackValue(coin, prices.usd);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return processedData;
|
||||
} catch (error) {
|
||||
console.error('Error fetching prices:', error);
|
||||
NetworkManager.handleNetworkError(error);
|
||||
|
||||
const cachedData = CacheManager.get(PRICES_CACHE_KEY);
|
||||
if (cachedData) {
|
||||
console.log('Using cached price data');
|
||||
return cachedData.value;
|
||||
}
|
||||
|
||||
try {
|
||||
const existingCache = localStorage.getItem(PRICES_CACHE_KEY);
|
||||
if (existingCache) {
|
||||
console.log('Using localStorage cached price data');
|
||||
return JSON.parse(existingCache).value;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse existing cache:', e);
|
||||
}
|
||||
|
||||
const emptyData = {};
|
||||
|
||||
const coinNames = window.CoinManager
|
||||
? window.CoinManager.getAllCoins().map(c => c.name.toLowerCase())
|
||||
: ['bitcoin', 'bitcoin-cash', 'dash', 'dogecoin', 'decred', 'namecoin', 'litecoin', 'particl', 'pivx', 'monero', 'wownero', 'firo'];
|
||||
|
||||
coinNames.forEach(coin => {
|
||||
emptyData[coin] = { usd: null, btc: null };
|
||||
});
|
||||
|
||||
return emptyData;
|
||||
}
|
||||
},
|
||||
|
||||
getCoinPrice: function(coinSymbol) {
|
||||
if (!coinSymbol) return null;
|
||||
const prices = this.getPrices();
|
||||
if (!prices) return null;
|
||||
|
||||
let normalizedSymbol;
|
||||
if (window.CoinManager) {
|
||||
normalizedSymbol = window.CoinManager.getPriceKey(coinSymbol);
|
||||
} else {
|
||||
normalizedSymbol = coinSymbol.toLowerCase();
|
||||
}
|
||||
|
||||
return prices[normalizedSymbol] || null;
|
||||
},
|
||||
|
||||
formatPrice: function(coin, price) {
|
||||
if (window.config && window.config.utils && window.config.utils.formatPrice) {
|
||||
return window.config.utils.formatPrice(coin, price);
|
||||
}
|
||||
if (typeof price !== 'number' || isNaN(price)) return 'N/A';
|
||||
if (price < 0.01) return price.toFixed(8);
|
||||
if (price < 1) return price.toFixed(4);
|
||||
if (price < 1000) return price.toFixed(2);
|
||||
return price.toFixed(0);
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
window.PriceManager = PriceManager;
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (!window.priceManagerInitialized) {
|
||||
window.PriceManager = PriceManager.initialize();
|
||||
window.priceManagerInitialized = true;
|
||||
}
|
||||
});
|
||||
|
||||
console.log('PriceManager initialized');
|
||||
@@ -23,54 +23,6 @@ const WalletManager = (function() {
|
||||
balancesVisible: 'balancesVisible'
|
||||
};
|
||||
|
||||
const coinData = {
|
||||
symbols: {
|
||||
'Bitcoin': 'BTC',
|
||||
'Particl': 'PART',
|
||||
'Monero': 'XMR',
|
||||
'Wownero': 'WOW',
|
||||
'Litecoin': 'LTC',
|
||||
'Dogecoin': 'DOGE',
|
||||
'Firo': 'FIRO',
|
||||
'Dash': 'DASH',
|
||||
'PIVX': 'PIVX',
|
||||
'Decred': 'DCR',
|
||||
'Namecoin': 'NMC',
|
||||
'Bitcoin Cash': 'BCH'
|
||||
},
|
||||
|
||||
coingeckoIds: {
|
||||
'BTC': 'btc',
|
||||
'PART': 'part',
|
||||
'XMR': 'xmr',
|
||||
'WOW': 'wownero',
|
||||
'LTC': 'ltc',
|
||||
'DOGE': 'doge',
|
||||
'FIRO': 'firo',
|
||||
'DASH': 'dash',
|
||||
'PIVX': 'pivx',
|
||||
'DCR': 'dcr',
|
||||
'NMC': 'nmc',
|
||||
'BCH': 'bch'
|
||||
},
|
||||
|
||||
shortNames: {
|
||||
'Bitcoin': 'BTC',
|
||||
'Particl': 'PART',
|
||||
'Monero': 'XMR',
|
||||
'Wownero': 'WOW',
|
||||
'Litecoin': 'LTC',
|
||||
'Litecoin MWEB': 'LTC MWEB',
|
||||
'Firo': 'FIRO',
|
||||
'Dash': 'DASH',
|
||||
'PIVX': 'PIVX',
|
||||
'Decred': 'DCR',
|
||||
'Namecoin': 'NMC',
|
||||
'Bitcoin Cash': 'BCH',
|
||||
'Dogecoin': 'DOGE'
|
||||
}
|
||||
};
|
||||
|
||||
const state = {
|
||||
lastFetchTime: 0,
|
||||
toggleInProgress: false,
|
||||
@@ -83,7 +35,23 @@ const WalletManager = (function() {
|
||||
};
|
||||
|
||||
function getShortName(fullName) {
|
||||
return coinData.shortNames[fullName] || fullName;
|
||||
return window.CoinManager.getSymbol(fullName) || fullName;
|
||||
}
|
||||
|
||||
function getCoingeckoId(coinName) {
|
||||
if (!window.CoinManager) {
|
||||
console.warn('[WalletManager] CoinManager not available');
|
||||
return coinName;
|
||||
}
|
||||
|
||||
const coin = window.CoinManager.getCoinByAnyIdentifier(coinName);
|
||||
|
||||
if (!coin) {
|
||||
console.warn(`[WalletManager] No coin found for: ${coinName}`);
|
||||
return coinName;
|
||||
}
|
||||
|
||||
return coin.symbol;
|
||||
}
|
||||
|
||||
async function fetchPrices(forceUpdate = false) {
|
||||
@@ -105,16 +73,33 @@ const WalletManager = (function() {
|
||||
|
||||
const shouldIncludeWow = currentSource === 'coingecko.com';
|
||||
|
||||
const coinsToFetch = Object.values(coinData.symbols)
|
||||
.filter(symbol => shouldIncludeWow || symbol !== 'WOW')
|
||||
.map(symbol => coinData.coingeckoIds[symbol] || symbol.toLowerCase())
|
||||
.join(',');
|
||||
const coinsToFetch = [];
|
||||
const processedCoins = new Set();
|
||||
|
||||
document.querySelectorAll('.coinname-value').forEach(el => {
|
||||
const coinName = el.getAttribute('data-coinname');
|
||||
|
||||
if (!coinName || processedCoins.has(coinName)) return;
|
||||
|
||||
const adjustedName = coinName === 'Zcoin' ? 'Firo' :
|
||||
coinName.includes('Particl') ? 'Particl' :
|
||||
coinName;
|
||||
|
||||
const coinId = getCoingeckoId(adjustedName);
|
||||
|
||||
if (coinId && (shouldIncludeWow || coinId !== 'WOW')) {
|
||||
coinsToFetch.push(coinId);
|
||||
processedCoins.add(coinName);
|
||||
}
|
||||
});
|
||||
|
||||
const fetchCoinsString = coinsToFetch.join(',');
|
||||
|
||||
const mainResponse = await fetch("/json/coinprices", {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
coins: coinsToFetch,
|
||||
coins: fetchCoinsString,
|
||||
source: currentSource,
|
||||
ttl: config.defaultTTL
|
||||
})
|
||||
@@ -127,46 +112,27 @@ const WalletManager = (function() {
|
||||
const mainData = await mainResponse.json();
|
||||
|
||||
if (mainData && mainData.rates) {
|
||||
Object.entries(mainData.rates).forEach(([coinId, price]) => {
|
||||
const symbol = Object.entries(coinData.coingeckoIds).find(([sym, id]) => id.toLowerCase() === coinId.toLowerCase())?.[0];
|
||||
if (symbol) {
|
||||
const coinKey = Object.keys(coinData.symbols).find(key => coinData.symbols[key] === symbol);
|
||||
if (coinKey) {
|
||||
processedData[coinKey.toLowerCase().replace(' ', '-')] = {
|
||||
usd: price,
|
||||
btc: symbol === 'BTC' ? 1 : price / (mainData.rates.btc || 1)
|
||||
};
|
||||
}
|
||||
document.querySelectorAll('.coinname-value').forEach(el => {
|
||||
const coinName = el.getAttribute('data-coinname');
|
||||
if (!coinName) return;
|
||||
|
||||
const adjustedName = coinName === 'Zcoin' ? 'Firo' :
|
||||
coinName.includes('Particl') ? 'Particl' :
|
||||
coinName;
|
||||
|
||||
const coinId = getCoingeckoId(adjustedName);
|
||||
const price = mainData.rates[coinId];
|
||||
|
||||
if (price) {
|
||||
const coinKey = coinName.toLowerCase().replace(' ', '-');
|
||||
processedData[coinKey] = {
|
||||
usd: price,
|
||||
btc: coinId === 'BTC' ? 1 : price / (mainData.rates.BTC || 1)
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!shouldIncludeWow && !processedData['wownero']) {
|
||||
try {
|
||||
const wowResponse = await fetch("/json/coinprices", {
|
||||
method: "POST",
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
coins: "wownero",
|
||||
source: "coingecko.com",
|
||||
ttl: config.defaultTTL
|
||||
})
|
||||
});
|
||||
|
||||
if (wowResponse.ok) {
|
||||
const wowData = await wowResponse.json();
|
||||
if (wowData && wowData.rates && wowData.rates.wownero) {
|
||||
processedData['wownero'] = {
|
||||
usd: wowData.rates.wownero,
|
||||
btc: processedData.bitcoin ? wowData.rates.wownero / processedData.bitcoin.usd : 0
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (wowError) {
|
||||
console.error('Error fetching WOW price:', wowError);
|
||||
}
|
||||
}
|
||||
|
||||
CacheManager.set(state.cacheKey, processedData, config.cacheExpiration);
|
||||
state.lastFetchTime = now;
|
||||
return processedData;
|
||||
@@ -202,7 +168,6 @@ const WalletManager = (function() {
|
||||
throw lastError || new Error('Failed to fetch prices');
|
||||
}
|
||||
|
||||
// UI Management functions
|
||||
function storeOriginalValues() {
|
||||
document.querySelectorAll('.coinname-value').forEach(el => {
|
||||
const coinName = el.getAttribute('data-coinname');
|
||||
@@ -210,10 +175,10 @@ const WalletManager = (function() {
|
||||
|
||||
if (coinName) {
|
||||
const amount = value ? parseFloat(value.replace(/[^0-9.-]+/g, '')) : 0;
|
||||
const coinId = coinData.symbols[coinName];
|
||||
const coinSymbol = window.CoinManager.getSymbol(coinName);
|
||||
const shortName = getShortName(coinName);
|
||||
|
||||
if (coinId) {
|
||||
if (coinSymbol) {
|
||||
if (coinName === 'Particl') {
|
||||
const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
|
||||
const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
|
||||
@@ -224,7 +189,7 @@ const WalletManager = (function() {
|
||||
const balanceType = isMWEB ? 'mweb' : 'public';
|
||||
localStorage.setItem(`litecoin-${balanceType}-amount`, amount.toString());
|
||||
} else {
|
||||
localStorage.setItem(`${coinId.toLowerCase()}-amount`, amount.toString());
|
||||
localStorage.setItem(`${coinSymbol.toLowerCase()}-amount`, amount.toString());
|
||||
}
|
||||
|
||||
el.setAttribute('data-original-value', `${amount} ${shortName}`);
|
||||
|
||||
@@ -168,6 +168,8 @@ const WebSocketManager = (function() {
|
||||
}
|
||||
|
||||
state.isConnecting = false;
|
||||
state.messageHandlers = {};
|
||||
|
||||
|
||||
if (ws) {
|
||||
ws.onopen = null;
|
||||
|
||||
Reference in New Issue
Block a user