@fin.cx/opendata 3.2.2 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,596 @@
1
+ import * as plugins from '../../plugins.js';
2
+ /**
3
+ * Custom error for rate limit exceeded responses
4
+ */
5
+ class RateLimitError extends Error {
6
+ constructor(message, waitTime, retryAfter) {
7
+ super(message);
8
+ this.waitTime = waitTime;
9
+ this.retryAfter = retryAfter;
10
+ this.name = 'RateLimitError';
11
+ }
12
+ }
13
+ /**
14
+ * Rate limiter for CoinGecko API
15
+ * Free tier (Demo): 30 requests per minute
16
+ * Without registration: 5-15 requests per minute
17
+ */
18
+ class RateLimiter {
19
+ constructor(maxRequestsPerMinute = 30) {
20
+ this.requestTimes = [];
21
+ this.consecutiveRateLimitErrors = 0;
22
+ this.maxRequestsPerMinute = maxRequestsPerMinute;
23
+ }
24
+ async waitForSlot() {
25
+ const now = Date.now();
26
+ const oneMinuteAgo = now - 60000;
27
+ // Remove requests older than 1 minute
28
+ this.requestTimes = this.requestTimes.filter(time => time > oneMinuteAgo);
29
+ // If we've hit the limit, wait
30
+ if (this.requestTimes.length >= this.maxRequestsPerMinute) {
31
+ const oldestRequest = this.requestTimes[0];
32
+ const waitTime = 60000 - (now - oldestRequest) + 100; // +100ms buffer
33
+ await plugins.smartdelay.delayFor(waitTime);
34
+ return this.waitForSlot(); // Recursively check again
35
+ }
36
+ // Record this request
37
+ this.requestTimes.push(now);
38
+ }
39
+ /**
40
+ * Get time in milliseconds until next request slot is available
41
+ */
42
+ getTimeUntilNextSlot() {
43
+ const now = Date.now();
44
+ const oneMinuteAgo = now - 60000;
45
+ // Clean old requests
46
+ const recentRequests = this.requestTimes.filter(time => time > oneMinuteAgo);
47
+ if (recentRequests.length < this.maxRequestsPerMinute) {
48
+ return 0; // Slot available now
49
+ }
50
+ // Calculate wait time until oldest request expires
51
+ const oldestRequest = recentRequests[0];
52
+ return Math.max(0, 60000 - (now - oldestRequest) + 100);
53
+ }
54
+ /**
55
+ * Handle rate limit error with exponential backoff
56
+ * Returns wait time in milliseconds
57
+ */
58
+ handleRateLimitError() {
59
+ this.consecutiveRateLimitErrors++;
60
+ // Exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s, 60s (max)
61
+ const baseWait = 1000; // 1 second
62
+ const exponent = this.consecutiveRateLimitErrors - 1;
63
+ const backoff = Math.min(baseWait * Math.pow(2, exponent), 60000 // max 60 seconds
64
+ );
65
+ // After 3 consecutive 429s, reduce rate limit to 80% as safety measure
66
+ if (this.consecutiveRateLimitErrors >= 3) {
67
+ const newLimit = Math.floor(this.maxRequestsPerMinute * 0.8);
68
+ if (newLimit < this.maxRequestsPerMinute) {
69
+ console.warn(`Adjusting rate limit from ${this.maxRequestsPerMinute} to ${newLimit} requests/min due to repeated 429 errors`);
70
+ this.maxRequestsPerMinute = newLimit;
71
+ }
72
+ }
73
+ return backoff;
74
+ }
75
+ /**
76
+ * Reset consecutive error count on successful request
77
+ */
78
+ resetErrors() {
79
+ if (this.consecutiveRateLimitErrors > 0) {
80
+ this.consecutiveRateLimitErrors = 0;
81
+ }
82
+ }
83
+ }
84
+ /**
85
+ * CoinGecko Crypto Price Provider
86
+ *
87
+ * Documentation: https://docs.coingecko.com/v3.0.1/reference/endpoint-overview
88
+ *
89
+ * Features:
90
+ * - Current crypto prices (single and batch)
91
+ * - Historical price data with OHLCV
92
+ * - 13M+ tokens, 240+ networks, 1600+ exchanges
93
+ * - Accepts both ticker symbols (BTC, ETH) and CoinGecko IDs (bitcoin, ethereum)
94
+ * - 24/7 market data (crypto never closes)
95
+ *
96
+ * Rate Limits:
97
+ * - Free tier (no key): 5-15 requests/minute
98
+ * - Demo plan (free with registration): ~30 requests/minute, 10,000/month
99
+ * - Paid plans: Higher limits
100
+ *
101
+ * API Authentication:
102
+ * - Optional API key for Demo/paid plans
103
+ * - Header: x-cg-demo-api-key (Demo) or x-cg-pro-api-key (paid)
104
+ */
105
+ export class CoinGeckoProvider {
106
+ constructor(apiKey, config) {
107
+ this.config = config;
108
+ this.name = 'CoinGecko';
109
+ this.priority = 90; // High priority for crypto, between Yahoo (100) and Marketstack (80)
110
+ this.requiresAuth = false; // API key is optional
111
+ this.rateLimit = {
112
+ requestsPerMinute: 30, // Demo plan default
113
+ requestsPerDay: 10000 // Demo plan monthly quota / 30
114
+ };
115
+ this.logger = console;
116
+ this.baseUrl = 'https://api.coingecko.com/api/v3';
117
+ // Coin mapping cache
118
+ this.coinMapCache = new Map(); // ticker/id -> coingecko id
119
+ this.coinListLoadedAt = null;
120
+ this.coinListCacheTTL = 24 * 60 * 60 * 1000; // 24 hours
121
+ // Priority map for common crypto tickers (to avoid conflicts)
122
+ this.priorityTickerMap = new Map([
123
+ ['btc', 'bitcoin'],
124
+ ['eth', 'ethereum'],
125
+ ['usdt', 'tether'],
126
+ ['bnb', 'binancecoin'],
127
+ ['sol', 'solana'],
128
+ ['usdc', 'usd-coin'],
129
+ ['xrp', 'ripple'],
130
+ ['ada', 'cardano'],
131
+ ['doge', 'dogecoin'],
132
+ ['trx', 'tron'],
133
+ ['dot', 'polkadot'],
134
+ ['matic', 'matic-network'],
135
+ ['ltc', 'litecoin'],
136
+ ['shib', 'shiba-inu'],
137
+ ['avax', 'avalanche-2'],
138
+ ['link', 'chainlink'],
139
+ ['atom', 'cosmos'],
140
+ ['uni', 'uniswap'],
141
+ ['etc', 'ethereum-classic'],
142
+ ['xlm', 'stellar']
143
+ ]);
144
+ this.apiKey = apiKey;
145
+ this.rateLimiter = new RateLimiter(this.rateLimit.requestsPerMinute);
146
+ }
147
+ /**
148
+ * Unified data fetching method supporting all request types
149
+ */
150
+ async fetchData(request) {
151
+ switch (request.type) {
152
+ case 'current':
153
+ return this.fetchCurrentPrice(request);
154
+ case 'batch':
155
+ return this.fetchBatchCurrentPrices(request);
156
+ case 'historical':
157
+ return this.fetchHistoricalPrices(request);
158
+ case 'intraday':
159
+ return this.fetchIntradayPrices(request);
160
+ default:
161
+ throw new Error(`Unsupported request type: ${request.type}`);
162
+ }
163
+ }
164
+ /**
165
+ * Fetch current price for a single crypto
166
+ */
167
+ async fetchCurrentPrice(request) {
168
+ return this.fetchWithRateLimitRetry(async () => {
169
+ // Resolve ticker to CoinGecko ID
170
+ const coinId = await this.resolveCoinId(request.ticker);
171
+ // Build URL
172
+ const params = new URLSearchParams({
173
+ ids: coinId,
174
+ vs_currencies: 'usd',
175
+ include_market_cap: 'true',
176
+ include_24hr_vol: 'true',
177
+ include_24hr_change: 'true',
178
+ include_last_updated_at: 'true'
179
+ });
180
+ const url = `${this.baseUrl}/simple/price?${params}`;
181
+ // Wait for rate limit slot
182
+ await this.rateLimiter.waitForSlot();
183
+ // Make request
184
+ const response = await plugins.smartrequest.SmartRequest.create()
185
+ .url(url)
186
+ .headers(this.buildHeaders())
187
+ .timeout(this.config?.timeout || 10000)
188
+ .get();
189
+ const responseData = await response.json();
190
+ // Check for rate limit error
191
+ if (this.isRateLimitError(responseData)) {
192
+ const waitTime = this.rateLimiter.handleRateLimitError();
193
+ throw new RateLimitError(`Rate limit exceeded for ${request.ticker}`, waitTime);
194
+ }
195
+ if (!responseData[coinId]) {
196
+ throw new Error(`No data found for ${request.ticker} (${coinId})`);
197
+ }
198
+ return this.mapToStockPrice(request.ticker, coinId, responseData[coinId], 'live');
199
+ }, `current price for ${request.ticker}`);
200
+ }
201
+ /**
202
+ * Fetch batch current prices for multiple cryptos
203
+ */
204
+ async fetchBatchCurrentPrices(request) {
205
+ return this.fetchWithRateLimitRetry(async () => {
206
+ // Resolve all tickers to CoinGecko IDs
207
+ const coinIds = await Promise.all(request.tickers.map(ticker => this.resolveCoinId(ticker)));
208
+ // Build URL with comma-separated IDs
209
+ const params = new URLSearchParams({
210
+ ids: coinIds.join(','),
211
+ vs_currencies: 'usd',
212
+ include_market_cap: 'true',
213
+ include_24hr_vol: 'true',
214
+ include_24hr_change: 'true',
215
+ include_last_updated_at: 'true'
216
+ });
217
+ const url = `${this.baseUrl}/simple/price?${params}`;
218
+ // Wait for rate limit slot
219
+ await this.rateLimiter.waitForSlot();
220
+ // Make request
221
+ const response = await plugins.smartrequest.SmartRequest.create()
222
+ .url(url)
223
+ .headers(this.buildHeaders())
224
+ .timeout(this.config?.timeout || 15000)
225
+ .get();
226
+ const responseData = await response.json();
227
+ // Check for rate limit error
228
+ if (this.isRateLimitError(responseData)) {
229
+ const waitTime = this.rateLimiter.handleRateLimitError();
230
+ throw new RateLimitError(`Rate limit exceeded for batch request`, waitTime);
231
+ }
232
+ const prices = [];
233
+ // Map responses back to original tickers
234
+ for (let i = 0; i < request.tickers.length; i++) {
235
+ const ticker = request.tickers[i];
236
+ const coinId = coinIds[i];
237
+ if (responseData[coinId]) {
238
+ try {
239
+ prices.push(this.mapToStockPrice(ticker, coinId, responseData[coinId], 'live'));
240
+ }
241
+ catch (error) {
242
+ this.logger.warn(`Failed to parse data for ${ticker}:`, error);
243
+ }
244
+ }
245
+ else {
246
+ this.logger.warn(`No data returned for ${ticker} (${coinId})`);
247
+ }
248
+ }
249
+ if (prices.length === 0) {
250
+ throw new Error('No valid price data received from batch request');
251
+ }
252
+ return prices;
253
+ }, `batch prices for ${request.tickers.length} tickers`);
254
+ }
255
+ /**
256
+ * Fetch historical prices with OHLCV data
257
+ */
258
+ async fetchHistoricalPrices(request) {
259
+ return this.fetchWithRateLimitRetry(async () => {
260
+ const coinId = await this.resolveCoinId(request.ticker);
261
+ // Calculate days between dates
262
+ const days = Math.ceil((request.to.getTime() - request.from.getTime()) / (1000 * 60 * 60 * 24));
263
+ // Build URL
264
+ const params = new URLSearchParams({
265
+ vs_currency: 'usd',
266
+ days: days.toString(),
267
+ interval: 'daily' // Explicit daily granularity for historical data
268
+ });
269
+ const url = `${this.baseUrl}/coins/${coinId}/market_chart?${params}`;
270
+ // Wait for rate limit slot
271
+ await this.rateLimiter.waitForSlot();
272
+ // Make request
273
+ const response = await plugins.smartrequest.SmartRequest.create()
274
+ .url(url)
275
+ .headers(this.buildHeaders())
276
+ .timeout(this.config?.timeout || 20000)
277
+ .get();
278
+ const responseData = await response.json();
279
+ // Check for rate limit error
280
+ if (this.isRateLimitError(responseData)) {
281
+ const waitTime = this.rateLimiter.handleRateLimitError();
282
+ throw new RateLimitError(`Rate limit exceeded for historical ${request.ticker}`, waitTime);
283
+ }
284
+ if (!responseData.prices || !Array.isArray(responseData.prices)) {
285
+ this.logger.error(`Invalid API response for ${request.ticker}:`, JSON.stringify(responseData).substring(0, 500));
286
+ throw new Error(`Invalid response format for ${request.ticker}: ${JSON.stringify(responseData).substring(0, 200)}`);
287
+ }
288
+ const prices = [];
289
+ const priceData = responseData.prices;
290
+ const marketCapData = responseData.market_caps || [];
291
+ const volumeData = responseData.total_volumes || [];
292
+ // Process each data point
293
+ for (let i = 0; i < priceData.length; i++) {
294
+ const [timestamp, price] = priceData[i];
295
+ const date = new Date(timestamp);
296
+ // Filter by date range
297
+ if (date < request.from || date > request.to)
298
+ continue;
299
+ const marketCap = marketCapData[i]?.[1];
300
+ const volume = volumeData[i]?.[1];
301
+ // Calculate previous close for change calculation
302
+ const previousClose = i > 0 ? priceData[i - 1][1] : price;
303
+ const change = price - previousClose;
304
+ const changePercent = previousClose !== 0 ? (change / previousClose) * 100 : 0;
305
+ prices.push({
306
+ ticker: request.ticker.toUpperCase(),
307
+ price: price,
308
+ currency: 'USD',
309
+ change: change,
310
+ changePercent: changePercent,
311
+ previousClose: previousClose,
312
+ timestamp: date,
313
+ provider: this.name,
314
+ marketState: 'REGULAR', // Crypto markets are always open
315
+ // OHLCV data (note: market_chart doesn't provide OHLC, only close prices)
316
+ volume: volume,
317
+ dataType: 'eod',
318
+ fetchedAt: new Date(),
319
+ companyName: coinId.charAt(0).toUpperCase() + coinId.slice(1)
320
+ });
321
+ }
322
+ return prices;
323
+ }, `historical prices for ${request.ticker}`);
324
+ }
325
+ /**
326
+ * Fetch intraday prices with hourly intervals
327
+ */
328
+ async fetchIntradayPrices(request) {
329
+ return this.fetchWithRateLimitRetry(async () => {
330
+ const coinId = await this.resolveCoinId(request.ticker);
331
+ // Map interval to days parameter (CoinGecko auto-granularity)
332
+ // For hourly data, request 1-7 days
333
+ let days = 1;
334
+ switch (request.interval) {
335
+ case '1min':
336
+ case '5min':
337
+ case '10min':
338
+ case '15min':
339
+ case '30min':
340
+ throw new Error('CoinGecko only supports hourly intervals in market_chart. Use interval: "1hour"');
341
+ case '1hour':
342
+ days = 1; // Last 24 hours with hourly granularity
343
+ break;
344
+ }
345
+ // Build URL (omit interval param for automatic granularity based on days)
346
+ const params = new URLSearchParams({
347
+ vs_currency: 'usd',
348
+ days: days.toString()
349
+ });
350
+ const url = `${this.baseUrl}/coins/${coinId}/market_chart?${params}`;
351
+ // Wait for rate limit slot
352
+ await this.rateLimiter.waitForSlot();
353
+ // Make request
354
+ const response = await plugins.smartrequest.SmartRequest.create()
355
+ .url(url)
356
+ .headers(this.buildHeaders())
357
+ .timeout(this.config?.timeout || 15000)
358
+ .get();
359
+ const responseData = await response.json();
360
+ // Check for rate limit error
361
+ if (this.isRateLimitError(responseData)) {
362
+ const waitTime = this.rateLimiter.handleRateLimitError();
363
+ throw new RateLimitError(`Rate limit exceeded for intraday ${request.ticker}`, waitTime);
364
+ }
365
+ if (!responseData.prices || !Array.isArray(responseData.prices)) {
366
+ this.logger.error(`Invalid API response for ${request.ticker}:`, JSON.stringify(responseData).substring(0, 500));
367
+ throw new Error(`Invalid response format for ${request.ticker}: ${JSON.stringify(responseData).substring(0, 200)}`);
368
+ }
369
+ const prices = [];
370
+ const priceData = responseData.prices;
371
+ const marketCapData = responseData.market_caps || [];
372
+ const volumeData = responseData.total_volumes || [];
373
+ // Apply limit if specified
374
+ const limit = request.limit || priceData.length;
375
+ const dataToProcess = priceData.slice(-limit);
376
+ for (let i = 0; i < dataToProcess.length; i++) {
377
+ const actualIndex = priceData.length - limit + i;
378
+ const [timestamp, price] = dataToProcess[i];
379
+ const date = new Date(timestamp);
380
+ const marketCap = marketCapData[actualIndex]?.[1];
381
+ const volume = volumeData[actualIndex]?.[1];
382
+ const previousClose = i > 0 ? dataToProcess[i - 1][1] : price;
383
+ const change = price - previousClose;
384
+ const changePercent = previousClose !== 0 ? (change / previousClose) * 100 : 0;
385
+ prices.push({
386
+ ticker: request.ticker.toUpperCase(),
387
+ price: price,
388
+ currency: 'USD',
389
+ change: change,
390
+ changePercent: changePercent,
391
+ previousClose: previousClose,
392
+ timestamp: date,
393
+ provider: this.name,
394
+ marketState: 'REGULAR',
395
+ volume: volume,
396
+ dataType: 'intraday',
397
+ fetchedAt: new Date(),
398
+ companyName: coinId.charAt(0).toUpperCase() + coinId.slice(1)
399
+ });
400
+ }
401
+ return prices;
402
+ }, `intraday prices for ${request.ticker}`);
403
+ }
404
+ /**
405
+ * Check if CoinGecko API is available
406
+ */
407
+ async isAvailable() {
408
+ try {
409
+ const url = `${this.baseUrl}/simple/price?ids=bitcoin&vs_currencies=usd`;
410
+ await this.rateLimiter.waitForSlot();
411
+ const response = await plugins.smartrequest.SmartRequest.create()
412
+ .url(url)
413
+ .headers(this.buildHeaders())
414
+ .timeout(5000)
415
+ .get();
416
+ const responseData = await response.json();
417
+ return responseData.bitcoin?.usd !== undefined;
418
+ }
419
+ catch (error) {
420
+ this.logger.warn('CoinGecko provider is not available:', error);
421
+ return false;
422
+ }
423
+ }
424
+ /**
425
+ * Check if a market/network is supported
426
+ * CoinGecko supports 240+ networks
427
+ */
428
+ supportsMarket(market) {
429
+ // CoinGecko has extensive crypto network coverage
430
+ const supportedNetworks = [
431
+ 'CRYPTO', 'BTC', 'ETH', 'BSC', 'POLYGON', 'AVALANCHE',
432
+ 'SOLANA', 'ARBITRUM', 'OPTIMISM', 'BASE'
433
+ ];
434
+ return supportedNetworks.includes(market.toUpperCase());
435
+ }
436
+ /**
437
+ * Check if a ticker format is supported
438
+ * Supports both ticker symbols (BTC) and CoinGecko IDs (bitcoin)
439
+ */
440
+ supportsTicker(ticker) {
441
+ // Accept alphanumeric with hyphens (for coin IDs like 'wrapped-bitcoin')
442
+ return /^[A-Za-z0-9\-]{1,50}$/.test(ticker);
443
+ }
444
+ /**
445
+ * Resolve ticker symbol or CoinGecko ID to canonical CoinGecko ID
446
+ * Supports both formats: "BTC" -> "bitcoin", "bitcoin" -> "bitcoin"
447
+ */
448
+ async resolveCoinId(tickerOrId) {
449
+ const normalized = tickerOrId.toLowerCase();
450
+ // Check priority map first (for common cryptos)
451
+ if (this.priorityTickerMap.has(normalized)) {
452
+ const coinId = this.priorityTickerMap.get(normalized);
453
+ this.coinMapCache.set(normalized, coinId);
454
+ return coinId;
455
+ }
456
+ // Check cache
457
+ if (this.coinMapCache.has(normalized)) {
458
+ return this.coinMapCache.get(normalized);
459
+ }
460
+ // Check if it's already a valid CoinGecko ID (contains hyphens or is all lowercase with original case)
461
+ if (normalized.includes('-') || normalized === tickerOrId) {
462
+ // Assume it's a CoinGecko ID, cache it
463
+ this.coinMapCache.set(normalized, normalized);
464
+ return normalized;
465
+ }
466
+ // Load coin list if needed
467
+ if (!this.coinListLoadedAt ||
468
+ Date.now() - this.coinListLoadedAt.getTime() > this.coinListCacheTTL) {
469
+ await this.loadCoinList();
470
+ }
471
+ // Try to find in cache after loading
472
+ if (this.coinMapCache.has(normalized)) {
473
+ return this.coinMapCache.get(normalized);
474
+ }
475
+ // Not found - return as-is and let API handle the error
476
+ this.logger.warn(`Could not resolve ticker ${tickerOrId} to CoinGecko ID, using as-is`);
477
+ return normalized;
478
+ }
479
+ /**
480
+ * Load complete coin list from CoinGecko API
481
+ */
482
+ async loadCoinList() {
483
+ try {
484
+ const url = `${this.baseUrl}/coins/list`;
485
+ await this.rateLimiter.waitForSlot();
486
+ const response = await plugins.smartrequest.SmartRequest.create()
487
+ .url(url)
488
+ .headers(this.buildHeaders())
489
+ .timeout(10000)
490
+ .get();
491
+ const coinList = await response.json();
492
+ // Build mapping: symbol -> id
493
+ for (const coin of coinList) {
494
+ const symbol = coin.symbol.toLowerCase();
495
+ const id = coin.id.toLowerCase();
496
+ // Don't overwrite priority mappings or existing cache entries
497
+ if (!this.priorityTickerMap.has(symbol) && !this.coinMapCache.has(symbol)) {
498
+ this.coinMapCache.set(symbol, id);
499
+ }
500
+ // Always cache the ID mapping
501
+ this.coinMapCache.set(id, id);
502
+ }
503
+ this.coinListLoadedAt = new Date();
504
+ this.logger.info(`Loaded ${coinList.length} coins from CoinGecko`);
505
+ }
506
+ catch (error) {
507
+ this.logger.error('Failed to load coin list from CoinGecko:', error);
508
+ // Don't throw - we can still work with direct IDs
509
+ }
510
+ }
511
+ /**
512
+ * Map CoinGecko simple/price response to IStockPrice
513
+ */
514
+ mapToStockPrice(ticker, coinId, data, dataType) {
515
+ const price = data.usd;
516
+ const change24h = data.usd_24h_change || 0;
517
+ // Calculate previous close from 24h change
518
+ const changePercent = change24h;
519
+ const change = (price * changePercent) / 100;
520
+ const previousClose = price - change;
521
+ // Parse last updated timestamp
522
+ const timestamp = data.last_updated_at
523
+ ? new Date(data.last_updated_at * 1000)
524
+ : new Date();
525
+ return {
526
+ ticker: ticker.toUpperCase(),
527
+ price: price,
528
+ currency: 'USD',
529
+ change: change,
530
+ changePercent: changePercent,
531
+ previousClose: previousClose,
532
+ timestamp: timestamp,
533
+ provider: this.name,
534
+ marketState: 'REGULAR', // Crypto markets are 24/7
535
+ // Volume and market cap
536
+ volume: data.usd_24h_vol,
537
+ dataType: dataType,
538
+ fetchedAt: new Date(),
539
+ // Company identification (use coin name)
540
+ companyName: coinId.charAt(0).toUpperCase() + coinId.slice(1),
541
+ companyFullName: `${coinId.charAt(0).toUpperCase() + coinId.slice(1)} (${ticker.toUpperCase()})`
542
+ };
543
+ }
544
+ /**
545
+ * Build HTTP headers with optional API key
546
+ */
547
+ buildHeaders() {
548
+ const headers = {
549
+ 'Accept': 'application/json'
550
+ };
551
+ if (this.apiKey) {
552
+ // Use Demo or Pro API key header
553
+ // CoinGecko accepts both x-cg-demo-api-key and x-cg-pro-api-key
554
+ headers['x-cg-demo-api-key'] = this.apiKey;
555
+ }
556
+ return headers;
557
+ }
558
+ /**
559
+ * Check if response indicates a rate limit error (429)
560
+ */
561
+ isRateLimitError(responseData) {
562
+ return responseData?.status?.error_code === 429;
563
+ }
564
+ /**
565
+ * Wrapper for fetch operations with automatic rate limit retry and exponential backoff
566
+ */
567
+ async fetchWithRateLimitRetry(fetchFn, operationName, maxRetries = 3) {
568
+ let lastError;
569
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
570
+ try {
571
+ const result = await fetchFn();
572
+ this.rateLimiter.resetErrors();
573
+ return result;
574
+ }
575
+ catch (error) {
576
+ lastError = error;
577
+ if (error instanceof RateLimitError) {
578
+ const attemptInfo = `${attempt + 1}/${maxRetries}`;
579
+ this.logger.warn(`Rate limit hit for ${operationName}, waiting ${error.waitTime}ms before retry ${attemptInfo}`);
580
+ if (attempt < maxRetries - 1) {
581
+ await plugins.smartdelay.delayFor(error.waitTime);
582
+ continue;
583
+ }
584
+ else {
585
+ this.logger.error(`Max retries (${maxRetries}) exceeded for ${operationName} due to rate limiting`);
586
+ throw error;
587
+ }
588
+ }
589
+ // Non-rate-limit errors: throw immediately
590
+ throw error;
591
+ }
592
+ }
593
+ throw lastError;
594
+ }
595
+ }
596
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvdmlkZXIuY29pbmdlY2tvLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvc3RvY2tzL3Byb3ZpZGVycy9wcm92aWRlci5jb2luZ2Vja28udHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQVc1Qzs7R0FFRztBQUNILE1BQU0sY0FBZSxTQUFRLEtBQUs7SUFDaEMsWUFDRSxPQUFlLEVBQ1IsUUFBZ0IsRUFDaEIsVUFBbUI7UUFFMUIsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBSFIsYUFBUSxHQUFSLFFBQVEsQ0FBUTtRQUNoQixlQUFVLEdBQVYsVUFBVSxDQUFTO1FBRzFCLElBQUksQ0FBQyxJQUFJLEdBQUcsZ0JBQWdCLENBQUM7SUFDL0IsQ0FBQztDQUNGO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sV0FBVztJQUtmLFlBQVksdUJBQStCLEVBQUU7UUFKckMsaUJBQVksR0FBYSxFQUFFLENBQUM7UUFFNUIsK0JBQTBCLEdBQVcsQ0FBQyxDQUFDO1FBRzdDLElBQUksQ0FBQyxvQkFBb0IsR0FBRyxvQkFBb0IsQ0FBQztJQUNuRCxDQUFDO0lBRU0sS0FBSyxDQUFDLFdBQVc7UUFDdEIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sWUFBWSxHQUFHLEdBQUcsR0FBRyxLQUFLLENBQUM7UUFFakMsc0NBQXNDO1FBQ3RDLElBQUksQ0FBQyxZQUFZLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxJQUFJLEdBQUcsWUFBWSxDQUFDLENBQUM7UUFFMUUsK0JBQStCO1FBQy9CLElBQUksSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLElBQUksSUFBSSxDQUFDLG9CQUFvQixFQUFFLENBQUM7WUFDMUQsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFD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