Your Name
feat: UI improvements and error suppression - Enhanced dashboard and market pages with improved header buttons, logo, and currency symbol display - Stopped animated ticker - Removed pie chart legends - Added error suppressor for external service errors (SSE, Permissions-Policy warnings) - Improved header button prominence and icon appearance - Enhanced logo with glow effects and better design - Fixed currency symbol visibility in market tables
8b7b267
raw
history blame
5.07 kB
/**
* API Client with Request Throttling, Caching, and Error Handling
* Prevents excessive API calls and handles security challenges gracefully
*/
class APIClient {
constructor() {
this.cache = new Map();
this.requestQueue = new Map();
this.retryDelays = new Map();
this.maxRetries = 3;
this.defaultCacheTTL = 30000; // 30 seconds
this.requestTimeout = 8000; // 8 seconds
}
/**
* Make a fetch request with throttling, caching, and retry logic
* @param {string} url - Request URL
* @param {Object} options - Fetch options
* @param {number} cacheTTL - Cache TTL in milliseconds
* @returns {Promise<Response>}
*/
async fetch(url, options = {}, cacheTTL = this.defaultCacheTTL) {
const cacheKey = `${url}:${JSON.stringify(options)}`;
// Check cache first
if (cacheTTL > 0 && this.cache.has(cacheKey)) {
const cached = this.cache.get(cacheKey);
if (Date.now() - cached.timestamp < cacheTTL) {
return cached.response.clone();
}
this.cache.delete(cacheKey);
}
// Throttle duplicate requests
if (this.requestQueue.has(cacheKey)) {
return this.requestQueue.get(cacheKey);
}
// Create request promise
const requestPromise = this._makeRequest(url, options, cacheKey, cacheTTL);
this.requestQueue.set(cacheKey, requestPromise);
try {
const response = await requestPromise;
return response;
} finally {
// Clean up queue after a delay to allow concurrent requests to share the promise
setTimeout(() => {
this.requestQueue.delete(cacheKey);
}, 100);
}
}
/**
* Internal method to make the actual request with retry logic
* @private
*/
async _makeRequest(url, options, cacheKey, cacheTTL) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
let lastError;
let retryCount = 0;
while (retryCount <= this.maxRetries) {
try {
const response = await fetch(url, {
...options,
signal: controller.signal,
headers: {
'Accept': 'application/json',
...options.headers
}
});
clearTimeout(timeoutId);
// Handle security challenges (AWS WAF, etc.)
if (response.status === 403 || response.status === 429) {
// Rate limited or blocked - use exponential backoff
const delay = Math.min(1000 * Math.pow(2, retryCount), 10000);
await this._delay(delay);
if (retryCount < this.maxRetries) {
retryCount++;
continue;
}
// Return a fallback response instead of throwing
return this._createFallbackResponse(url);
}
// Cache successful responses
if (response.ok && cacheTTL > 0) {
this.cache.set(cacheKey, {
response: response.clone(),
timestamp: Date.now()
});
}
return response;
} catch (error) {
clearTimeout(timeoutId);
lastError = error;
// Don't retry on abort (timeout)
if (error.name === 'AbortError') {
break;
}
// Retry on network errors
if (retryCount < this.maxRetries) {
const delay = this._getRetryDelay(retryCount);
await this._delay(delay);
retryCount++;
// Create new controller for retry
const newController = new AbortController();
const newTimeoutId = setTimeout(() => newController.abort(), this.requestTimeout);
Object.assign(controller, newController);
timeoutId = newTimeoutId;
} else {
break;
}
}
}
// All retries failed - return fallback
console.warn(`[APIClient] Request failed after ${retryCount} retries:`, url);
return this._createFallbackResponse(url);
}
/**
* Get retry delay with exponential backoff
* @private
*/
_getRetryDelay(retryCount) {
const baseDelay = 500;
return Math.min(baseDelay * Math.pow(2, retryCount), 5000);
}
/**
* Delay helper
* @private
*/
_delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Create a fallback response for failed requests
* @private
*/
_createFallbackResponse(url) {
return new Response(
JSON.stringify({
error: 'Service temporarily unavailable',
fallback: true,
url
}),
{
status: 200,
statusText: 'OK',
headers: { 'Content-Type': 'application/json' }
}
);
}
/**
* Clear cache
*/
clearCache() {
this.cache.clear();
}
/**
* Clear cache for specific URL pattern
*/
clearCacheFor(urlPattern) {
for (const key of this.cache.keys()) {
if (key.includes(urlPattern)) {
this.cache.delete(key);
}
}
}
}
// Export singleton instance
export const apiClient = new APIClient();
export default apiClient;