Really-amin commited on
Commit
d362646
·
verified ·
1 Parent(s): 6e1537c

Update static/shared/js/fallback-api-client.js

Browse files
static/shared/js/fallback-api-client.js ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Fallback API Client with Hierarchical Retry System
3
+ * سیستم fallback سلسله مراتبی با 10 پشتیبان
4
+ */
5
+
6
+ class FallbackAPIClient {
7
+ constructor() {
8
+ // لیست 10 endpoint پشتیبان به ترتیب اولویت
9
+ this.endpoints = [
10
+ 'https://Really-amin-crypto-api-clean.hf.space',
11
+ 'https://really-amin-datasourceforcryptocurrency-2.hf.space',
12
+ 'https://really-amin-datasourceforcryptocurrency.hf.space',
13
+ 'http://localhost:7860',
14
+ 'http://localhost:8000',
15
+ 'https://api.coingecko.com/api/v3',
16
+ 'https://api.coincap.io/v2',
17
+ 'https://api.binance.com/api/v3',
18
+ 'https://api.kraken.com/0/public',
19
+ 'https://api.coinbase.com/v2'
20
+ ];
21
+
22
+ // Cache برای نتایج موفق
23
+ this.cache = new Map();
24
+ this.cacheTimeout = 60000; // 1 دقیقه
25
+
26
+ // آمار برای monitoring
27
+ this.stats = {
28
+ totalRequests: 0,
29
+ successfulRequests: 0,
30
+ failedRequests: 0,
31
+ endpointStats: {},
32
+ lastSuccessfulEndpoint: null
33
+ };
34
+
35
+ // Initialize endpoint stats
36
+ this.endpoints.forEach(endpoint => {
37
+ this.stats.endpointStats[endpoint] = {
38
+ requests: 0,
39
+ successes: 0,
40
+ failures: 0,
41
+ avgResponseTime: 0,
42
+ lastUsed: null
43
+ };
44
+ });
45
+ }
46
+
47
+ /**
48
+ * درخواست با fallback سلسله مراتبی
49
+ */
50
+ async request(path, options = {}) {
51
+ const {
52
+ method = 'GET',
53
+ body = null,
54
+ headers = {},
55
+ timeout = 10000,
56
+ retryCount = 3,
57
+ useCache = true
58
+ } = options;
59
+
60
+ // بررسی cache
61
+ const cacheKey = `${method}:${path}:${JSON.stringify(body)}`;
62
+ if (useCache && method === 'GET') {
63
+ const cached = this.getFromCache(cacheKey);
64
+ if (cached) {
65
+ console.log('✅ Cache hit:', path);
66
+ return cached;
67
+ }
68
+ }
69
+
70
+ this.stats.totalRequests++;
71
+ const errors = [];
72
+
73
+ // تلاش با هر endpoint به ترتیب
74
+ for (let i = 0; i < this.endpoints.length; i++) {
75
+ const endpoint = this.endpoints[i];
76
+ const endpointStats = this.stats.endpointStats[endpoint];
77
+
78
+ try {
79
+ console.log(`🔄 Trying endpoint ${i + 1}/${this.endpoints.length}: ${endpoint}`);
80
+
81
+ const startTime = Date.now();
82
+ const result = await this.makeRequest(endpoint, path, {
83
+ method,
84
+ body,
85
+ headers,
86
+ timeout
87
+ });
88
+ const responseTime = Date.now() - startTime;
89
+
90
+ // به‌روزرسانی آمار موفق
91
+ endpointStats.requests++;
92
+ endpointStats.successes++;
93
+ endpointStats.lastUsed = new Date().toISOString();
94
+ endpointStats.avgResponseTime =
95
+ (endpointStats.avgResponseTime * (endpointStats.successes - 1) + responseTime) /
96
+ endpointStats.successes;
97
+
98
+ this.stats.successfulRequests++;
99
+ this.stats.lastSuccessfulEndpoint = endpoint;
100
+
101
+ // ذخیره در cache
102
+ if (useCache && method === 'GET') {
103
+ this.saveToCache(cacheKey, result);
104
+ }
105
+
106
+ console.log(`✅ Success with endpoint ${i + 1}: ${endpoint} (${responseTime}ms)`);
107
+ return result;
108
+
109
+ } catch (error) {
110
+ // به‌روزرسانی آمار خطا
111
+ endpointStats.requests++;
112
+ endpointStats.failures++;
113
+
114
+ errors.push({
115
+ endpoint,
116
+ error: error.message,
117
+ index: i + 1
118
+ });
119
+
120
+ console.warn(`❌ Failed endpoint ${i + 1}/${this.endpoints.length}: ${endpoint}`, error.message);
121
+
122
+ // اگر آخرین endpoint بود، خطا بده
123
+ if (i === this.endpoints.length - 1) {
124
+ this.stats.failedRequests++;
125
+ throw new Error(
126
+ `All ${this.endpoints.length} endpoints failed:\n` +
127
+ errors.map(e => `${e.index}. ${e.endpoint}: ${e.error}`).join('\n')
128
+ );
129
+ }
130
+
131
+ // صبر کوتاه قبل از تلاش بعدی
132
+ await this.sleep(500);
133
+ }
134
+ }
135
+ }
136
+
137
+ /**
138
+ * ساخت درخواست به یک endpoint
139
+ */
140
+ async makeRequest(baseUrl, path, options) {
141
+ const { method, body, headers, timeout } = options;
142
+
143
+ // ساخت URL کامل
144
+ const url = this.buildUrl(baseUrl, path);
145
+
146
+ // ساخت AbortController برای timeout
147
+ const controller = new AbortController();
148
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
149
+
150
+ try {
151
+ const response = await fetch(url, {
152
+ method,
153
+ headers: {
154
+ 'Content-Type': 'application/json',
155
+ ...headers
156
+ },
157
+ body: body ? JSON.stringify(body) : null,
158
+ signal: controller.signal
159
+ });
160
+
161
+ clearTimeout(timeoutId);
162
+
163
+ if (!response.ok) {
164
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
165
+ }
166
+
167
+ const data = await response.json();
168
+ return data;
169
+
170
+ } catch (error) {
171
+ clearTimeout(timeoutId);
172
+
173
+ if (error.name === 'AbortError') {
174
+ throw new Error(`Timeout after ${timeout}ms`);
175
+ }
176
+
177
+ throw error;
178
+ }
179
+ }
180
+
181
+ /**
182
+ * ساخت URL کامل
183
+ */
184
+ buildUrl(baseUrl, path) {
185
+ // حذف slash اضافی
186
+ baseUrl = baseUrl.replace(/\/$/, '');
187
+ path = path.replace(/^\//, '');
188
+
189
+ // تطبیق path با endpoint های مختلف
190
+ if (baseUrl.includes('coingecko')) {
191
+ return this.adaptToCoinGecko(baseUrl, path);
192
+ } else if (baseUrl.includes('coincap')) {
193
+ return this.adaptToCoinCap(baseUrl, path);
194
+ } else if (baseUrl.includes('binance')) {
195
+ return this.adaptToBinance(baseUrl, path);
196
+ }
197
+
198
+ // پیش‌فرض
199
+ return `${baseUrl}/${path}`;
200
+ }
201
+
202
+ /**
203
+ * تطبیق با CoinGecko API
204
+ */
205
+ adaptToCoinGecko(baseUrl, path) {
206
+ if (path.includes('/api/coins/top')) {
207
+ return `${baseUrl}/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=50`;
208
+ }
209
+ if (path.includes('/api/trending')) {
210
+ return `${baseUrl}/search/trending`;
211
+ }
212
+ if (path.includes('/api/market')) {
213
+ return `${baseUrl}/global`;
214
+ }
215
+ return `${baseUrl}/${path}`;
216
+ }
217
+
218
+ /**
219
+ * تطبیق با CoinCap API
220
+ */
221
+ adaptToCoinCap(baseUrl, path) {
222
+ if (path.includes('/api/coins/top')) {
223
+ return `${baseUrl}/assets?limit=50`;
224
+ }
225
+ return `${baseUrl}/${path}`;
226
+ }
227
+
228
+ /**
229
+ * تطبیق با Binance API
230
+ */
231
+ adaptToBinance(baseUrl, path) {
232
+ if (path.includes('/api/coins/top')) {
233
+ return `${baseUrl}/ticker/24hr`;
234
+ }
235
+ return `${baseUrl}/${path}`;
236
+ }
237
+
238
+ /**
239
+ * Cache management
240
+ */
241
+ getFromCache(key) {
242
+ const cached = this.cache.get(key);
243
+ if (!cached) return null;
244
+
245
+ const now = Date.now();
246
+ if (now - cached.timestamp > this.cacheTimeout) {
247
+ this.cache.delete(key);
248
+ return null;
249
+ }
250
+
251
+ return cached.data;
252
+ }
253
+
254
+ saveToCache(key, data) {
255
+ this.cache.set(key, {
256
+ data,
257
+ timestamp: Date.now()
258
+ });
259
+
260
+ // پاکسازی cache قدیمی
261
+ if (this.cache.size > 100) {
262
+ const oldestKey = this.cache.keys().next().value;
263
+ this.cache.delete(oldestKey);
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Helper: sleep
269
+ */
270
+ sleep(ms) {
271
+ return new Promise(resolve => setTimeout(resolve, ms));
272
+ }
273
+
274
+ /**
275
+ * دریافت آمار
276
+ */
277
+ getStats() {
278
+ return {
279
+ ...this.stats,
280
+ successRate: this.stats.totalRequests > 0
281
+ ? (this.stats.successfulRequests / this.stats.totalRequests * 100).toFixed(2) + '%'
282
+ : '0%',
283
+ cacheSize: this.cache.size
284
+ };
285
+ }
286
+
287
+ /**
288
+ * ریست آمار
289
+ */
290
+ resetStats() {
291
+ this.stats.totalRequests = 0;
292
+ this.stats.successfulRequests = 0;
293
+ this.stats.failedRequests = 0;
294
+
295
+ this.endpoints.forEach(endpoint => {
296
+ this.stats.endpointStats[endpoint] = {
297
+ requests: 0,
298
+ successes: 0,
299
+ failures: 0,
300
+ avgResponseTime: 0,
301
+ lastUsed: null
302
+ };
303
+ });
304
+ }
305
+
306
+ /**
307
+ * پاکسازی cache
308
+ */
309
+ clearCache() {
310
+ this.cache.clear();
311
+ }
312
+
313
+ /**
314
+ * تغییر ترتیب endpoints بر اساس عملکرد
315
+ */
316
+ optimizeEndpoints() {
317
+ // مرتب‌سازی بر اساس نرخ موفقیت و سرعت
318
+ this.endpoints.sort((a, b) => {
319
+ const statsA = this.stats.endpointStats[a];
320
+ const statsB = this.stats.endpointStats[b];
321
+
322
+ const successRateA = statsA.requests > 0 ? statsA.successes / statsA.requests : 0;
323
+ const successRateB = statsB.requests > 0 ? statsB.successes / statsB.requests : 0;
324
+
325
+ if (successRateA !== successRateB) {
326
+ return successRateB - successRateA; // بیشترین موفقیت اول
327
+ }
328
+
329
+ return statsA.avgResponseTime - statsB.avgResponseTime; // سریع‌تر اول
330
+ });
331
+
332
+ console.log('✅ Endpoints optimized based on performance');
333
+ }
334
+ }
335
+
336
+ // ============================================================================
337
+ // API Methods با Fallback
338
+ // ============================================================================
339
+
340
+ class CryptoAPI {
341
+ constructor() {
342
+ this.client = new FallbackAPIClient();
343
+ }
344
+
345
+ // Health & Status
346
+ async health() {
347
+ return this.client.request('/api/health');
348
+ }
349
+
350
+ async status() {
351
+ return this.client.request('/api/status');
352
+ }
353
+
354
+ // Market Data
355
+ async getTopCoins(limit = 50) {
356
+ return this.client.request(`/api/coins/top?limit=${limit}`);
357
+ }
358
+
359
+ async getTrending() {
360
+ return this.client.request('/api/trending');
361
+ }
362
+
363
+ async getMarket() {
364
+ return this.client.request('/api/market');
365
+ }
366
+
367
+ // Sentiment
368
+ async getGlobalSentiment(timeframe = '1D') {
369
+ return this.client.request(`/api/sentiment/global?timeframe=${timeframe}`);
370
+ }
371
+
372
+ async getAssetSentiment(symbol) {
373
+ return this.client.request(`/api/sentiment/asset/${symbol}`);
374
+ }
375
+
376
+ // News
377
+ async getNews(limit = 50) {
378
+ return this.client.request(`/api/news?limit=${limit}`);
379
+ }
380
+
381
+ // Resources
382
+ async getResources() {
383
+ return this.client.request('/api/resources/summary');
384
+ }
385
+
386
+ async getCategories() {
387
+ return this.client.request('/api/categories');
388
+ }
389
+
390
+ // Models
391
+ async getModels() {
392
+ return this.client.request('/api/models/list');
393
+ }
394
+
395
+ // Stats
396
+ getStats() {
397
+ return this.client.getStats();
398
+ }
399
+
400
+ optimizeEndpoints() {
401
+ this.client.optimizeEndpoints();
402
+ }
403
+ }
404
+
405
+ // Export
406
+ if (typeof module !== 'undefined' && module.exports) {
407
+ module.exports = { FallbackAPIClient, CryptoAPI };
408
+ }