Really-amin's picture
Upload 577 files
b190b45 verified
/**
* API Client for Crypto Monitor ULTIMATE
*
* Features:
* - Pure HTTP/Fetch API (NO WEBSOCKET)
* - Simple caching mechanism
* - Automatic retry logic
* - Request/error logging
* - ES6 module exports
*/
import { CONFIG, API_ENDPOINTS, buildApiUrl, getCacheKey } from './config.js';
/**
* Base API Client with caching and retry
*/
class APIClient {
constructor(baseURL = CONFIG.API_BASE_URL) {
this.baseURL = baseURL;
this.cache = new Map();
this.cacheTTL = CONFIG.CACHE_TTL;
this.maxRetries = CONFIG.MAX_RETRIES;
this.retryDelay = CONFIG.RETRY_DELAY;
this.requestLog = [];
this.errorLog = [];
this.maxLogSize = 100;
}
/**
* Core request method with retry logic
*/
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const method = options.method || 'GET';
const startTime = performance.now();
// Check cache for GET requests (but skip cache for models/status to get fresh data)
if (method === 'GET' && !options.skipCache) {
// Don't cache models status/summary - always get fresh data
const shouldSkipCache = endpoint.includes('/models/status') ||
endpoint.includes('/models/summary') ||
options.forceRefresh;
if (!shouldSkipCache) {
const cached = this._getFromCache(endpoint);
if (cached) {
console.log(`[APIClient] Cache hit: ${endpoint}`);
return cached;
}
}
}
// Retry logic
let lastError;
for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
try {
const response = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
body: options.body ? JSON.stringify(options.body) : undefined,
signal: options.signal,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
const duration = performance.now() - startTime;
// Cache successful GET responses (but not models status/summary)
if (method === 'GET' && !endpoint.includes('/models/status') && !endpoint.includes('/models/summary')) {
this._saveToCache(endpoint, data);
}
// Log successful request
this._logRequest({
method,
endpoint,
status: response.status,
duration: Math.round(duration),
timestamp: Date.now(),
});
return data;
} catch (error) {
lastError = error;
const errorDetails = {
attempt,
maxRetries: this.maxRetries,
endpoint,
message: error.message,
name: error.name,
stack: error.stack
};
console.warn(`[APIClient] Attempt ${attempt}/${this.maxRetries} failed for ${endpoint}:`, error.message);
// Log detailed error info for debugging
if (attempt === this.maxRetries) {
console.error('[APIClient] All retries exhausted. Error details:', errorDetails);
}
if (attempt < this.maxRetries) {
await this._sleep(this.retryDelay);
}
}
}
// All retries failed - return fallback data instead of throwing
const duration = performance.now() - startTime;
this._logError({
method,
endpoint,
message: lastError?.message || lastError?.toString() || 'Unknown error',
duration: Math.round(duration),
timestamp: Date.now(),
});
// Return fallback data based on endpoint type
return this._getFallbackData(endpoint, lastError);
}
/**
* GET request
*/
async get(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'GET' });
}
/**
* POST request
*/
async post(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'POST',
body: data,
});
}
/**
* PUT request
*/
async put(endpoint, data, options = {}) {
return this.request(endpoint, {
...options,
method: 'PUT',
body: data,
});
}
/**
* DELETE request
*/
async delete(endpoint, options = {}) {
return this.request(endpoint, { ...options, method: 'DELETE' });
}
// ========================================================================
// CACHE MANAGEMENT
// ========================================================================
/**
* Get data from cache if not expired
*/
_getFromCache(key) {
const cacheKey = getCacheKey(key);
const cached = this.cache.get(cacheKey);
if (!cached) return null;
const now = Date.now();
if (now - cached.timestamp > this.cacheTTL) {
this.cache.delete(cacheKey);
return null;
}
return cached.data;
}
/**
* Save data to cache with timestamp
*/
_saveToCache(key, data) {
const cacheKey = getCacheKey(key);
this.cache.set(cacheKey, {
data,
timestamp: Date.now(),
});
}
/**
* Clear all cache
*/
clearCache() {
this.cache.clear();
console.log('[APIClient] Cache cleared');
}
/**
* Clear specific cache entry
*/
clearCacheEntry(key) {
const cacheKey = getCacheKey(key);
this.cache.delete(cacheKey);
}
// ========================================================================
// LOGGING
// ========================================================================
/**
* Log successful request
*/
_logRequest(entry) {
this.requestLog.unshift(entry);
if (this.requestLog.length > this.maxLogSize) {
this.requestLog.pop();
}
}
/**
* Log error with enhanced details
*/
_logError(entry) {
// Add timestamp if not present
if (!entry.timestamp) {
entry.timestamp = Date.now();
}
// Add formatted time for readability
entry.time = new Date(entry.timestamp).toISOString();
this.errorLog.unshift(entry);
if (this.errorLog.length > this.maxLogSize) {
this.errorLog.pop();
}
// Also log to console for immediate visibility
console.error('[APIClient] Error logged:', {
endpoint: entry.endpoint,
method: entry.method,
message: entry.message,
duration: entry.duration
});
}
/**
* Get request logs
*/
getRequestLogs(limit = 20) {
return this.requestLog.slice(0, limit);
}
/**
* Get error logs
*/
getErrorLogs(limit = 20) {
return this.errorLog.slice(0, limit);
}
// ========================================================================
// UTILITY
// ========================================================================
/**
* Sleep utility for retry delays
*/
_sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Get fallback data for failed requests
* @private
*/
_getFallbackData(endpoint, error) {
// Return appropriate fallback based on endpoint
if (endpoint.includes('/resources/summary')) {
return {
success: false,
error: error.message,
summary: {
total_resources: 0,
free_resources: 0,
models_available: 0,
local_routes_count: 0,
total_api_keys: 0,
categories: {}
},
fallback: true,
timestamp: new Date().toISOString()
};
}
if (endpoint.includes('/models/status')) {
return {
success: false,
error: error.message,
status: 'error',
status_message: `Error: ${error.message}`,
models_loaded: 0,
models_failed: 0,
hf_mode: 'unknown',
transformers_available: false,
fallback: true,
timestamp: new Date().toISOString()
};
}
if (endpoint.includes('/models/summary')) {
return {
ok: false,
error: error.message,
summary: {
total_models: 0,
loaded_models: 0,
failed_models: 0,
hf_mode: 'error',
transformers_available: false
},
categories: {},
health_registry: [],
fallback: true,
timestamp: new Date().toISOString()
};
}
if (endpoint.includes('/health') || endpoint.includes('/status')) {
return {
status: 'offline',
healthy: false,
error: error.message,
fallback: true,
timestamp: new Date().toISOString()
};
}
// Generic fallback
return {
error: error.message,
fallback: true,
data: null,
timestamp: new Date().toISOString()
};
}
}
/**
* Crypto Monitor API Client with pre-configured endpoints
*/
export class CryptoMonitorAPI extends APIClient {
// ========================================================================
// HEALTH & STATUS
// ========================================================================
async getHealth() {
return this.get(API_ENDPOINTS.HEALTH);
}
async getStatus() {
return this.get(API_ENDPOINTS.STATUS);
}
async getStats() {
return this.get(API_ENDPOINTS.STATS);
}
async getResources() {
return this.get(API_ENDPOINTS.RESOURCES);
}
// ========================================================================
// MARKET DATA
// ========================================================================
async getMarket() {
return this.get(API_ENDPOINTS.MARKET);
}
async getTrending() {
return this.get(API_ENDPOINTS.TRENDING);
}
async getSentiment() {
return this.get(API_ENDPOINTS.SENTIMENT);
}
async getDefi() {
return this.get(API_ENDPOINTS.DEFI);
}
async getTopCoins(limit = 50) {
return this.get(`${API_ENDPOINTS.COINS_TOP}?limit=${limit}`);
}
async getCoinDetails(symbol) {
return this.get(API_ENDPOINTS.COIN_DETAILS(symbol));
}
// ========================================================================
// CHARTS
// ========================================================================
async getPriceChart(symbol, timeframe = '7D') {
return this.get(`${API_ENDPOINTS.PRICE_CHART(symbol)}?timeframe=${timeframe}`);
}
async analyzeChart(symbol, timeframe, indicators) {
return this.post(API_ENDPOINTS.ANALYZE_CHART, {
symbol,
timeframe,
indicators,
});
}
// ========================================================================
// NEWS
// ========================================================================
async getLatestNews(limit = 40) {
return this.get(`${API_ENDPOINTS.NEWS_LATEST}?limit=${limit}`);
}
async analyzeNews(title, content) {
return this.post(API_ENDPOINTS.NEWS_ANALYZE, { title, content });
}
async summarizeNews(title, content) {
return this.post(API_ENDPOINTS.NEWS_SUMMARIZE, { title, content });
}
// ========================================================================
// AI/ML MODELS
// ========================================================================
async getModelsList() {
return this.get(API_ENDPOINTS.MODELS_LIST);
}
async getModelsStatus() {
return this.get(API_ENDPOINTS.MODELS_STATUS);
}
async getModelsStats() {
return this.get(API_ENDPOINTS.MODELS_STATS);
}
async testModel(modelName, input) {
return this.post(API_ENDPOINTS.MODELS_TEST, {
model: modelName,
input,
});
}
// ========================================================================
// SENTIMENT ANALYSIS
// ========================================================================
async analyzeSentiment(text, mode = 'crypto', model = null) {
return this.post(API_ENDPOINTS.SENTIMENT_ANALYZE, {
text,
mode,
model,
});
}
async getGlobalSentiment() {
return this.get(API_ENDPOINTS.SENTIMENT_GLOBAL);
}
// ========================================================================
// AI ADVISOR
// ========================================================================
async getAIDecision(symbol, horizon, riskTolerance, context, model) {
return this.post(API_ENDPOINTS.AI_DECISION, {
symbol,
horizon,
risk_tolerance: riskTolerance,
context,
model,
});
}
async getAISignals(symbol) {
return this.get(`${API_ENDPOINTS.AI_SIGNALS}?symbol=${symbol}`);
}
// ========================================================================
// DATASETS
// ========================================================================
async getDatasetsList() {
return this.get(API_ENDPOINTS.DATASETS_LIST);
}
async previewDataset(name, limit = 10) {
return this.get(`${API_ENDPOINTS.DATASET_PREVIEW(name)}?limit=${limit}`);
}
// ========================================================================
// PROVIDERS
// ========================================================================
async getProviders() {
return this.get(API_ENDPOINTS.PROVIDERS);
}
async getProviderDetails(id) {
return this.get(API_ENDPOINTS.PROVIDER_DETAILS(id));
}
async checkProviderHealth(id) {
return this.get(API_ENDPOINTS.PROVIDER_HEALTH(id));
}
async getProvidersConfig() {
return this.get(API_ENDPOINTS.PROVIDERS_CONFIG);
}
// ========================================================================
// LOGS & DIAGNOSTICS
// ========================================================================
async getLogs() {
return this.get(API_ENDPOINTS.LOGS);
}
async getRecentLogs(limit = 50) {
return this.get(`${API_ENDPOINTS.LOGS_RECENT}?limit=${limit}`);
}
async getErrorLogs(limit = 50) {
return this.get(`${API_ENDPOINTS.LOGS_ERRORS}?limit=${limit}`);
}
async clearLogs() {
return this.delete(API_ENDPOINTS.LOGS_CLEAR);
}
// ========================================================================
// RESOURCES
// ========================================================================
async runResourceDiscovery() {
return this.post(API_ENDPOINTS.RESOURCES_DISCOVERY);
}
// ========================================================================
// HUGGINGFACE INTEGRATION
// ========================================================================
async getHFHealth() {
return this.get(API_ENDPOINTS.HF_HEALTH);
}
async runHFSentiment(text) {
return this.post(API_ENDPOINTS.HF_RUN_SENTIMENT, { text });
}
// ========================================================================
// FEATURE FLAGS
// ========================================================================
async getFeatureFlags() {
return this.get(API_ENDPOINTS.FEATURE_FLAGS);
}
async updateFeatureFlag(name, value) {
return this.put(API_ENDPOINTS.FEATURE_FLAG_UPDATE(name), { value });
}
async resetFeatureFlags() {
return this.post(API_ENDPOINTS.FEATURE_FLAGS_RESET);
}
// ========================================================================
// SETTINGS
// ========================================================================
async getSettings() {
return this.get(API_ENDPOINTS.SETTINGS);
}
async saveTokens(tokens) {
return this.post(API_ENDPOINTS.SETTINGS_TOKENS, tokens);
}
async saveTelegramSettings(settings) {
return this.post(API_ENDPOINTS.SETTINGS_TELEGRAM, settings);
}
async saveSignalSettings(settings) {
return this.post(API_ENDPOINTS.SETTINGS_SIGNALS, settings);
}
async saveSchedulingSettings(settings) {
return this.post(API_ENDPOINTS.SETTINGS_SCHEDULING, settings);
}
async saveNotificationSettings(settings) {
return this.post(API_ENDPOINTS.SETTINGS_NOTIFICATIONS, settings);
}
async saveAppearanceSettings(settings) {
return this.post(API_ENDPOINTS.SETTINGS_APPEARANCE, settings);
}
}
// ============================================================================
// EXPORT SINGLETON INSTANCE
// ============================================================================
export const api = new CryptoMonitorAPI();
export default api;
/**
* Export apiClient alias with fetch method for compatibility
* This allows files to use apiClient.fetch() pattern
*/
export const apiClient = {
async fetch(url, options = {}) {
// Convert fetch-style call to api method
const method = (options.method || 'GET').toUpperCase();
const endpoint = url.replace(/^.*\/api/, '/api');
try {
let data;
if (method === 'GET') {
data = await api.get(endpoint, { skipCache: options.skipCache, forceRefresh: options.forceRefresh });
} else if (method === 'POST') {
const body = options.body ? (typeof options.body === 'string' ? JSON.parse(options.body) : options.body) : {};
data = await api.post(endpoint, body);
} else if (method === 'PUT') {
const body = options.body ? (typeof options.body === 'string' ? JSON.parse(options.body) : options.body) : {};
data = await api.put(endpoint, body);
} else if (method === 'DELETE') {
data = await api.delete(endpoint);
} else {
data = await api.get(endpoint);
}
// Return a Response-like object
return new Response(JSON.stringify(data), {
status: 200,
statusText: 'OK',
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
// Return error response
return new Response(JSON.stringify({
error: error.message || 'Request failed',
success: false
}), {
status: error.status || 500,
statusText: error.statusText || 'Internal Server Error',
headers: { 'Content-Type': 'application/json' }
});
}
}
};
console.log('[APIClient] Initialized (HTTP-only, no WebSocket)');