| |
| |
| |
|
|
|
|
| class FallbackAPIClient {
|
| constructor() {
|
|
|
| this.endpoints = [
|
| 'https://Really-amin-crypto-api-clean.hf.space',
|
| 'https://really-amin-datasourceforcryptocurrency-2.hf.space',
|
| 'https://really-amin-datasourceforcryptocurrency.hf.space',
|
| 'http://localhost:7860',
|
| 'http://localhost:8000',
|
| 'https://api.coingecko.com/api/v3',
|
| 'https://api.coincap.io/v2',
|
| 'https://api.binance.com/api/v3',
|
| 'https://api.kraken.com/0/public',
|
| 'https://api.coinbase.com/v2'
|
| ];
|
|
|
|
|
| this.cache = new Map();
|
| this.cacheTimeout = 60000;
|
|
|
|
|
| this.stats = {
|
| totalRequests: 0,
|
| successfulRequests: 0,
|
| failedRequests: 0,
|
| endpointStats: {},
|
| lastSuccessfulEndpoint: null
|
| };
|
|
|
|
|
| this.endpoints.forEach(endpoint => {
|
| this.stats.endpointStats[endpoint] = {
|
| requests: 0,
|
| successes: 0,
|
| failures: 0,
|
| avgResponseTime: 0,
|
| lastUsed: null
|
| };
|
| });
|
| }
|
|
|
| |
| |
|
|
| async request(path, options = {}) {
|
| const {
|
| method = 'GET',
|
| body = null,
|
| headers = {},
|
| timeout = 10000,
|
| retryCount = 3,
|
| useCache = true
|
| } = options;
|
|
|
|
|
| const cacheKey = `${method}:${path}:${JSON.stringify(body)}`;
|
| if (useCache && method === 'GET') {
|
| const cached = this.getFromCache(cacheKey);
|
| if (cached) {
|
| console.log('✅ Cache hit:', path);
|
| return cached;
|
| }
|
| }
|
|
|
| this.stats.totalRequests++;
|
| const errors = [];
|
|
|
|
|
| for (let i = 0; i < this.endpoints.length; i++) {
|
| const endpoint = this.endpoints[i];
|
| const endpointStats = this.stats.endpointStats[endpoint];
|
|
|
| try {
|
| console.log(`🔄 Trying endpoint ${i + 1}/${this.endpoints.length}: ${endpoint}`);
|
|
|
| const startTime = Date.now();
|
| const result = await this.makeRequest(endpoint, path, {
|
| method,
|
| body,
|
| headers,
|
| timeout
|
| });
|
| const responseTime = Date.now() - startTime;
|
|
|
|
|
| endpointStats.requests++;
|
| endpointStats.successes++;
|
| endpointStats.lastUsed = new Date().toISOString();
|
| endpointStats.avgResponseTime =
|
| (endpointStats.avgResponseTime * (endpointStats.successes - 1) + responseTime) /
|
| endpointStats.successes;
|
|
|
| this.stats.successfulRequests++;
|
| this.stats.lastSuccessfulEndpoint = endpoint;
|
|
|
|
|
| if (useCache && method === 'GET') {
|
| this.saveToCache(cacheKey, result);
|
| }
|
|
|
| console.log(`✅ Success with endpoint ${i + 1}: ${endpoint} (${responseTime}ms)`);
|
| return result;
|
|
|
| } catch (error) {
|
|
|
| endpointStats.requests++;
|
| endpointStats.failures++;
|
|
|
| errors.push({
|
| endpoint,
|
| error: error.message,
|
| index: i + 1
|
| });
|
|
|
| console.warn(`❌ Failed endpoint ${i + 1}/${this.endpoints.length}: ${endpoint}`, error.message);
|
|
|
|
|
| if (i === this.endpoints.length - 1) {
|
| this.stats.failedRequests++;
|
| throw new Error(
|
| `All ${this.endpoints.length} endpoints failed:\n` +
|
| errors.map(e => `${e.index}. ${e.endpoint}: ${e.error}`).join('\n')
|
| );
|
| }
|
|
|
|
|
| await this.sleep(500);
|
| }
|
| }
|
| }
|
|
|
| |
| |
|
|
| async makeRequest(baseUrl, path, options) {
|
| const { method, body, headers, timeout } = options;
|
|
|
|
|
| const url = this.buildUrl(baseUrl, path);
|
|
|
|
|
| const controller = new AbortController();
|
| const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
|
| try {
|
| const response = await fetch(url, {
|
| method,
|
| headers: {
|
| 'Content-Type': 'application/json',
|
| ...headers
|
| },
|
| body: body ? JSON.stringify(body) : null,
|
| signal: controller.signal
|
| });
|
|
|
| clearTimeout(timeoutId);
|
|
|
| if (!response.ok) {
|
| throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
| }
|
|
|
| const data = await response.json();
|
| return data;
|
|
|
| } catch (error) {
|
| clearTimeout(timeoutId);
|
|
|
| if (error.name === 'AbortError') {
|
| throw new Error(`Timeout after ${timeout}ms`);
|
| }
|
|
|
| throw error;
|
| }
|
| }
|
|
|
| |
| |
|
|
| buildUrl(baseUrl, path) {
|
|
|
| baseUrl = baseUrl.replace(/\/$/, '');
|
| path = path.replace(/^\//, '');
|
|
|
|
|
| if (baseUrl.includes('coingecko')) {
|
| return this.adaptToCoinGecko(baseUrl, path);
|
| } else if (baseUrl.includes('coincap')) {
|
| return this.adaptToCoinCap(baseUrl, path);
|
| } else if (baseUrl.includes('binance')) {
|
| return this.adaptToBinance(baseUrl, path);
|
| }
|
|
|
|
|
| return `${baseUrl}/${path}`;
|
| }
|
|
|
| |
| |
|
|
| adaptToCoinGecko(baseUrl, path) {
|
| if (path.includes('/api/coins/top')) {
|
| return `${baseUrl}/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=50`;
|
| }
|
| if (path.includes('/api/trending')) {
|
| return `${baseUrl}/search/trending`;
|
| }
|
| if (path.includes('/api/market')) {
|
| return `${baseUrl}/global`;
|
| }
|
| return `${baseUrl}/${path}`;
|
| }
|
|
|
| |
| |
|
|
| adaptToCoinCap(baseUrl, path) {
|
| if (path.includes('/api/coins/top')) {
|
| return `${baseUrl}/assets?limit=50`;
|
| }
|
| return `${baseUrl}/${path}`;
|
| }
|
|
|
| |
| |
|
|
| adaptToBinance(baseUrl, path) {
|
| if (path.includes('/api/coins/top')) {
|
| return `${baseUrl}/ticker/24hr`;
|
| }
|
| return `${baseUrl}/${path}`;
|
| }
|
|
|
| |
| |
|
|
| getFromCache(key) {
|
| const cached = this.cache.get(key);
|
| if (!cached) return null;
|
|
|
| const now = Date.now();
|
| if (now - cached.timestamp > this.cacheTimeout) {
|
| this.cache.delete(key);
|
| return null;
|
| }
|
|
|
| return cached.data;
|
| }
|
|
|
| saveToCache(key, data) {
|
| this.cache.set(key, {
|
| data,
|
| timestamp: Date.now()
|
| });
|
|
|
|
|
| if (this.cache.size > 100) {
|
| const oldestKey = this.cache.keys().next().value;
|
| this.cache.delete(oldestKey);
|
| }
|
| }
|
|
|
| |
| |
|
|
| sleep(ms) {
|
| return new Promise(resolve => setTimeout(resolve, ms));
|
| }
|
|
|
| |
| |
|
|
| getStats() {
|
| return {
|
| ...this.stats,
|
| successRate: this.stats.totalRequests > 0
|
| ? (this.stats.successfulRequests / this.stats.totalRequests * 100).toFixed(2) + '%'
|
| : '0%',
|
| cacheSize: this.cache.size
|
| };
|
| }
|
|
|
| |
| |
|
|
| resetStats() {
|
| this.stats.totalRequests = 0;
|
| this.stats.successfulRequests = 0;
|
| this.stats.failedRequests = 0;
|
|
|
| this.endpoints.forEach(endpoint => {
|
| this.stats.endpointStats[endpoint] = {
|
| requests: 0,
|
| successes: 0,
|
| failures: 0,
|
| avgResponseTime: 0,
|
| lastUsed: null
|
| };
|
| });
|
| }
|
|
|
| |
| |
|
|
| clearCache() {
|
| this.cache.clear();
|
| }
|
|
|
| |
| |
|
|
| optimizeEndpoints() {
|
|
|
| this.endpoints.sort((a, b) => {
|
| const statsA = this.stats.endpointStats[a];
|
| const statsB = this.stats.endpointStats[b];
|
|
|
| const successRateA = statsA.requests > 0 ? statsA.successes / statsA.requests : 0;
|
| const successRateB = statsB.requests > 0 ? statsB.successes / statsB.requests : 0;
|
|
|
| if (successRateA !== successRateB) {
|
| return successRateB - successRateA;
|
| }
|
|
|
| return statsA.avgResponseTime - statsB.avgResponseTime;
|
| });
|
|
|
| console.log('✅ Endpoints optimized based on performance');
|
| }
|
| }
|
|
|
|
|
|
|
|
|
|
|
| class CryptoAPI {
|
| constructor() {
|
| this.client = new FallbackAPIClient();
|
| }
|
|
|
|
|
| async health() {
|
| return this.client.request('/api/health');
|
| }
|
|
|
| async status() {
|
| return this.client.request('/api/status');
|
| }
|
|
|
|
|
| async getTopCoins(limit = 50) {
|
| return this.client.request(`/api/coins/top?limit=${limit}`);
|
| }
|
|
|
| async getTrending() {
|
| return this.client.request('/api/trending');
|
| }
|
|
|
| async getMarket() {
|
| return this.client.request('/api/market');
|
| }
|
|
|
|
|
| async getGlobalSentiment(timeframe = '1D') {
|
| return this.client.request(`/api/sentiment/global?timeframe=${timeframe}`);
|
| }
|
|
|
| async getAssetSentiment(symbol) {
|
| return this.client.request(`/api/sentiment/asset/${symbol}`);
|
| }
|
|
|
|
|
| async getNews(limit = 50) {
|
| return this.client.request(`/api/news?limit=${limit}`);
|
| }
|
|
|
|
|
| async getResources() {
|
| return this.client.request('/api/resources/summary');
|
| }
|
|
|
| async getCategories() {
|
| return this.client.request('/api/categories');
|
| }
|
|
|
|
|
| async getModels() {
|
| return this.client.request('/api/models/list');
|
| }
|
|
|
|
|
| getStats() {
|
| return this.client.getStats();
|
| }
|
|
|
| optimizeEndpoints() {
|
| this.client.optimizeEndpoints();
|
| }
|
| }
|
|
|
|
|
| if (typeof module !== 'undefined' && module.exports) {
|
| module.exports = { FallbackAPIClient, CryptoAPI };
|
| }
|
|
|