mirror of
https://github.com/basicswap/basicswap.git
synced 2025-01-20 01:24:31 +00:00
Merge pull request #203 from gerlofvanek/offers-7
Some checks are pending
ci / ci (3.12) (push) Waiting to run
Some checks are pending
ci / ci (3.12) (push) Waiting to run
JS: Fix API and new cleanup (memory) table row function and small fixes.
This commit is contained in:
commit
bf6d07a726
3 changed files with 132 additions and 90 deletions
|
@ -21,6 +21,14 @@ const PRICE_INIT_RETRIES = 3;
|
||||||
const PRICE_INIT_RETRY_DELAY = 2000;
|
const PRICE_INIT_RETRY_DELAY = 2000;
|
||||||
const isSentOffers = window.offersTableConfig.isSentOffers;
|
const isSentOffers = window.offersTableConfig.isSentOffers;
|
||||||
|
|
||||||
|
const offersConfig = {
|
||||||
|
apiEndpoints: {
|
||||||
|
coinGecko: 'https://api.coingecko.com/api/v3',
|
||||||
|
cryptoCompare: 'https://min-api.cryptocompare.com/data'
|
||||||
|
},
|
||||||
|
apiKeys: getAPIKeys()
|
||||||
|
};
|
||||||
|
|
||||||
// MAPPING OBJECTS
|
// MAPPING OBJECTS
|
||||||
const coinNameToSymbol = {
|
const coinNameToSymbol = {
|
||||||
'Bitcoin': 'bitcoin',
|
'Bitcoin': 'bitcoin',
|
||||||
|
@ -251,7 +259,7 @@ const WebSocketManager = {
|
||||||
|
|
||||||
handleMessage(message) {
|
handleMessage(message) {
|
||||||
if (this.messageQueue.length >= this.maxQueueSize) {
|
if (this.messageQueue.length >= this.maxQueueSize) {
|
||||||
console.warn('⚠Message queue full, dropping oldest message');
|
console.warn('Message queue full, dropping oldest message');
|
||||||
this.messageQueue.shift();
|
this.messageQueue.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +268,7 @@ const WebSocketManager = {
|
||||||
|
|
||||||
this.debounceTimeout = setTimeout(() => {
|
this.debounceTimeout = setTimeout(() => {
|
||||||
this.processMessageQueue();
|
this.processMessageQueue();
|
||||||
}, 250);
|
}, 200);
|
||||||
},
|
},
|
||||||
|
|
||||||
async processMessageQueue() {
|
async processMessageQueue() {
|
||||||
|
@ -1011,6 +1019,7 @@ async function getMarketRate(fromCoin, toCoin) {
|
||||||
|
|
||||||
async function fetchLatestPrices() {
|
async function fetchLatestPrices() {
|
||||||
const PRICES_CACHE_KEY = 'prices_coingecko';
|
const PRICES_CACHE_KEY = 'prices_coingecko';
|
||||||
|
const apiKeys = getAPIKeys();
|
||||||
|
|
||||||
const cachedData = CacheManager.get(PRICES_CACHE_KEY);
|
const cachedData = CacheManager.get(PRICES_CACHE_KEY);
|
||||||
if (cachedData && cachedData.remainingTime > 60000) {
|
if (cachedData && cachedData.remainingTime > 60000) {
|
||||||
|
@ -1019,10 +1028,10 @@ async function fetchLatestPrices() {
|
||||||
return cachedData.value;
|
return cachedData.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=bitcoin,bitcoin-cash,dash,dogecoin,decred,litecoin,particl,pivx,monero,zano,wownero,zcoin&vs_currencies=USD,BTC&api_key=${config.apiKeys.coinGecko}`;
|
const url = `${offersConfig.apiEndpoints.coinGecko}/simple/price?ids=bitcoin,bitcoin-cash,dash,dogecoin,decred,litecoin,particl,pivx,monero,zano,wownero,zcoin&vs_currencies=USD,BTC&api_key=${offersConfig.apiKeys.coinGecko}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Fetching fresh price data...');
|
console.log('Initiating fresh price data fetch...');
|
||||||
const response = await fetch('/json/readurl', {
|
const response = await fetch('/json/readurl', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -1039,31 +1048,37 @@ async function fetchLatestPrices() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.Error) {
|
if (data.Error) {
|
||||||
|
console.error('API Error:', data.Error);
|
||||||
throw new Error(data.Error);
|
throw new Error(data.Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data && Object.keys(data).length > 0) {
|
if (data && Object.keys(data).length > 0) {
|
||||||
console.log('Fresh price data received');
|
console.log('Processing fresh price data...');
|
||||||
|
|
||||||
latestPrices = data;
|
latestPrices = data;
|
||||||
|
|
||||||
CacheManager.set(PRICES_CACHE_KEY, data, CACHE_DURATION);
|
CacheManager.set(PRICES_CACHE_KEY, data, CACHE_DURATION);
|
||||||
|
const fallbackLog = {};
|
||||||
Object.entries(data).forEach(([coin, prices]) => {
|
Object.entries(data).forEach(([coin, prices]) => {
|
||||||
tableRateModule.setFallbackValue(coin, prices.usd);
|
tableRateModule.setFallbackValue(coin, prices.usd);
|
||||||
|
fallbackLog[coin] = prices.usd;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//console.log('Fallback Values Set:', fallbackLog);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
} else {
|
} else {
|
||||||
//console.warn('Received empty price data');
|
console.warn('No price data received');
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
//console.error('Error fetching prices:', error);
|
console.error('Price Fetch Error:', {
|
||||||
|
message: error.message,
|
||||||
|
name: error.name,
|
||||||
|
stack: error.stack
|
||||||
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return latestPrices || null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchOffers(manualRefresh = false) {
|
async function fetchOffers(manualRefresh = false) {
|
||||||
|
@ -1312,7 +1327,6 @@ function updateClearFiltersButton() {
|
||||||
clearButton.classList.toggle('opacity-50', !hasFilters);
|
clearButton.classList.toggle('opacity-50', !hasFilters);
|
||||||
clearButton.disabled = !hasFilters;
|
clearButton.disabled = !hasFilters;
|
||||||
|
|
||||||
// Update button styles based on state
|
|
||||||
if (hasFilters) {
|
if (hasFilters) {
|
||||||
clearButton.classList.add('hover:bg-green-600', 'hover:text-white');
|
clearButton.classList.add('hover:bg-green-600', 'hover:text-white');
|
||||||
clearButton.classList.remove('cursor-not-allowed');
|
clearButton.classList.remove('cursor-not-allowed');
|
||||||
|
@ -1323,6 +1337,19 @@ function updateClearFiltersButton() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanupRow(row) {
|
||||||
|
const tooltips = row.querySelectorAll('[data-tooltip-target]');
|
||||||
|
const count = tooltips.length;
|
||||||
|
tooltips.forEach(tooltip => {
|
||||||
|
const tooltipId = tooltip.getAttribute('data-tooltip-target');
|
||||||
|
const tooltipElement = document.getElementById(tooltipId);
|
||||||
|
if (tooltipElement) {
|
||||||
|
tooltipElement.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//console.log(`Cleaned up ${count} tooltips from row`);
|
||||||
|
}
|
||||||
|
|
||||||
function handleNoOffersScenario() {
|
function handleNoOffersScenario() {
|
||||||
const formData = new FormData(filterForm);
|
const formData = new FormData(filterForm);
|
||||||
const filters = Object.fromEntries(formData);
|
const filters = Object.fromEntries(formData);
|
||||||
|
@ -1332,6 +1359,11 @@ function handleNoOffersScenario() {
|
||||||
|
|
||||||
stopRefreshAnimation();
|
stopRefreshAnimation();
|
||||||
|
|
||||||
|
const existingRows = offersBody.querySelectorAll('tr');
|
||||||
|
existingRows.forEach(row => {
|
||||||
|
cleanupRow(row);
|
||||||
|
});
|
||||||
|
|
||||||
if (hasActiveFilters) {
|
if (hasActiveFilters) {
|
||||||
offersBody.innerHTML = `
|
offersBody.innerHTML = `
|
||||||
<tr>
|
<tr>
|
||||||
|
@ -1355,80 +1387,96 @@ function handleNoOffersScenario() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateOffersTable() {
|
async function updateOffersTable() {
|
||||||
try {
|
try {
|
||||||
const PRICES_CACHE_KEY = 'prices_coingecko';
|
const PRICES_CACHE_KEY = 'prices_coingecko';
|
||||||
const cachedPrices = CacheManager.get(PRICES_CACHE_KEY);
|
const cachedPrices = CacheManager.get(PRICES_CACHE_KEY);
|
||||||
|
|
||||||
if (!cachedPrices || !cachedPrices.remainingTime || cachedPrices.remainingTime < 60000) {
|
if (!cachedPrices || !cachedPrices.remainingTime || cachedPrices.remainingTime < 60000) {
|
||||||
console.log('Fetching fresh price data...');
|
console.log('Fetching fresh price data...');
|
||||||
const priceData = await fetchLatestPrices();
|
const priceData = await fetchLatestPrices();
|
||||||
if (priceData) {
|
if (priceData) {
|
||||||
latestPrices = priceData;
|
latestPrices = priceData;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
latestPrices = cachedPrices.value;
|
latestPrices = cachedPrices.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validOffers = getValidOffers();
|
const validOffers = getValidOffers();
|
||||||
|
|
||||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
if (!isSentOffers) {
|
||||||
const endIndex = Math.min(startIndex + itemsPerPage, validOffers.length);
|
const networkOffersSpan = document.querySelector('a[href="/offers"] span.inline-flex.justify-center');
|
||||||
const itemsToDisplay = validOffers.slice(startIndex, endIndex);
|
if (networkOffersSpan) {
|
||||||
|
networkOffersSpan.textContent = validOffers.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const identityPromises = itemsToDisplay.map(offer =>
|
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||||
offer.addr_from ? getIdentityData(offer.addr_from) : Promise.resolve(null)
|
const endIndex = Math.min(startIndex + itemsPerPage, validOffers.length);
|
||||||
);
|
const itemsToDisplay = validOffers.slice(startIndex, endIndex);
|
||||||
|
|
||||||
const identities = await Promise.all(identityPromises);
|
const identityPromises = itemsToDisplay.map(offer =>
|
||||||
|
offer.addr_from ? getIdentityData(offer.addr_from) : Promise.resolve(null)
|
||||||
|
);
|
||||||
|
|
||||||
if (validOffers.length === 0) {
|
const identities = await Promise.all(identityPromises);
|
||||||
handleNoOffersScenario();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalPages = Math.max(1, Math.ceil(validOffers.length / itemsPerPage));
|
if (validOffers.length === 0) {
|
||||||
currentPage = Math.min(currentPage, totalPages);
|
const existingRows = offersBody.querySelectorAll('tr');
|
||||||
|
existingRows.forEach(row => {
|
||||||
|
cleanupRow(row);
|
||||||
|
});
|
||||||
|
handleNoOffersScenario();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
const totalPages = Math.max(1, Math.ceil(validOffers.length / itemsPerPage));
|
||||||
|
currentPage = Math.min(currentPage, totalPages);
|
||||||
|
|
||||||
itemsToDisplay.forEach((offer, index) => {
|
const fragment = document.createDocumentFragment();
|
||||||
const identity = identities[index];
|
|
||||||
const row = createTableRow(offer, identity);
|
|
||||||
if (row) {
|
|
||||||
fragment.appendChild(row);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
offersBody.innerHTML = '';
|
const existingRows = offersBody.querySelectorAll('tr');
|
||||||
offersBody.appendChild(fragment);
|
existingRows.forEach(row => {
|
||||||
|
cleanupRow(row);
|
||||||
|
});
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
itemsToDisplay.forEach((offer, index) => {
|
||||||
initializeFlowbiteTooltips();
|
const identity = identities[index];
|
||||||
updateRowTimes();
|
const row = createTableRow(offer, identity);
|
||||||
updatePaginationControls(totalPages);
|
if (row) {
|
||||||
|
fragment.appendChild(row);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (tableRateModule?.initializeTable) {
|
offersBody.textContent = '';
|
||||||
tableRateModule.initializeTable();
|
offersBody.appendChild(fragment);
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
lastRefreshTime = Date.now();
|
requestAnimationFrame(() => {
|
||||||
if (newEntriesCountSpan) {
|
initializeFlowbiteTooltips();
|
||||||
newEntriesCountSpan.textContent = validOffers.length;
|
updateRowTimes();
|
||||||
}
|
updatePaginationControls(totalPages);
|
||||||
if (lastRefreshTimeSpan) {
|
|
||||||
lastRefreshTimeSpan.textContent = new Date(lastRefreshTime).toLocaleTimeString();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
if (tableRateModule?.initializeTable) {
|
||||||
console.error('[Debug] Error in updateOffersTable:', error);
|
tableRateModule.initializeTable();
|
||||||
offersBody.innerHTML = `
|
}
|
||||||
<tr>
|
});
|
||||||
<td colspan="8" class="text-center py-4 text-red-500">
|
|
||||||
An error occurred while updating the offers table. Please try again later.
|
lastRefreshTime = Date.now();
|
||||||
</td>
|
if (newEntriesCountSpan) {
|
||||||
</tr>`;
|
newEntriesCountSpan.textContent = validOffers.length;
|
||||||
}
|
}
|
||||||
|
if (lastRefreshTimeSpan) {
|
||||||
|
lastRefreshTimeSpan.textContent = new Date(lastRefreshTime).toLocaleTimeString();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Debug] Error in updateOffersTable:', error);
|
||||||
|
offersBody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="8" class="text-center py-4 text-red-500">
|
||||||
|
An error occurred while updating the offers table. Please try again later.
|
||||||
|
</td>
|
||||||
|
</tr>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getIdentityData(address) {
|
async function getIdentityData(address) {
|
||||||
|
|
|
@ -38,13 +38,6 @@ const config = {
|
||||||
currentResolution: 'year'
|
currentResolution: 'year'
|
||||||
};
|
};
|
||||||
|
|
||||||
function getAPIKeys() {
|
|
||||||
return {
|
|
||||||
cryptoCompare: '{{chart_api_key}}',
|
|
||||||
coinGecko: '{{coingecko_api_key}}'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// UTILS
|
// UTILS
|
||||||
const utils = {
|
const utils = {
|
||||||
formatNumber: (number, decimals = 2) =>
|
formatNumber: (number, decimals = 2) =>
|
||||||
|
@ -86,8 +79,9 @@ const logger = {
|
||||||
|
|
||||||
// API
|
// API
|
||||||
const api = {
|
const api = {
|
||||||
makePostRequest: (url, headers = {}) => {
|
makePostRequest: (url, headers = {}) => {
|
||||||
return new Promise((resolve, reject) => {
|
const apiKeys = getAPIKeys();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open('POST', '/json/readurl');
|
xhr.open('POST', '/json/readurl');
|
||||||
xhr.setRequestHeader('Content-Type', 'application/json');
|
xhr.setRequestHeader('Content-Type', 'application/json');
|
||||||
|
@ -148,7 +142,7 @@ const api = {
|
||||||
.join(',');
|
.join(',');
|
||||||
const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=${coinIds}&vs_currencies=usd,btc&include_24hr_vol=true&include_24hr_change=true&api_key=${config.apiKeys.coinGecko}`;
|
const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=${coinIds}&vs_currencies=usd,btc&include_24hr_vol=true&include_24hr_change=true&api_key=${config.apiKeys.coinGecko}`;
|
||||||
|
|
||||||
console.log(`Fetching data for multiple coins from CoinGecko: ${url}`);
|
//console.log(`Fetching data for multiple coins from CoinGecko: ${url}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const data = await api.makePostRequest(url);
|
const data = await api.makePostRequest(url);
|
||||||
|
@ -675,7 +669,7 @@ const chartModule = {
|
||||||
plugins: [chartModule.verticalLinePlugin]
|
plugins: [chartModule.verticalLinePlugin]
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Chart initialized:', chartModule.chart);
|
//console.log('Chart initialized:', chartModule.chart);
|
||||||
},
|
},
|
||||||
|
|
||||||
prepareChartData: (coinSymbol, data) => {
|
prepareChartData: (coinSymbol, data) => {
|
||||||
|
|
|
@ -11,15 +11,15 @@
|
||||||
<script>
|
<script>
|
||||||
function getAPIKeys() {
|
function getAPIKeys() {
|
||||||
return {
|
return {
|
||||||
cryptoCompare: '{{chart_api_key}}',
|
cryptoCompare: "{{ chart_api_key|safe }}",
|
||||||
coinGecko: '{{coingecko_api_key}}'
|
coinGecko: "{{ coingecko_api_key|safe }}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWebSocketConfig() {
|
function getWebSocketConfig() {
|
||||||
return {
|
return {
|
||||||
port: '{{ ws_port }}',
|
port: "{{ ws_port|safe }}",
|
||||||
fallbackPort: '11700'
|
fallbackPort: "11700"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue