@icgio/icg-exchanges-wrapper 1.15.1 → 1.15.3

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.
@@ -86,11 +86,7 @@ function reset_ws_failure(failed_pairs_dict, exchange_name, pair) {
86
86
  */
87
87
  function should_skip_websocket(failed_pairs_dict, exchange_name, pair) {
88
88
  const failure_info = failed_pairs_dict[exchange_name]?.[pair]
89
- return (
90
- failure_info &&
91
- failure_info.count >= WS_FAILURE_THRESHOLD &&
92
- (Date.now() - failure_info.last_failed) < WS_RETRY_INTERVAL_MS
93
- )
89
+ return failure_info && failure_info.count >= WS_FAILURE_THRESHOLD && Date.now() - failure_info.last_failed < WS_RETRY_INTERVAL_MS
94
90
  }
95
91
 
96
92
  /**
@@ -127,7 +123,7 @@ const get_exchanges_rates = function (Exchanges, exchange_pair_dict, cb, ws_enab
127
123
  for (let Exchange_name in exchange_instances) {
128
124
  let Exchange = exchange_instances[Exchange_name]
129
125
  let all_pairs = exchange_all_pairs[Exchange_name]
130
-
126
+
131
127
  if (Exchange.ws_market && ws_enabled) {
132
128
  // Check if WebSocket is already opening/opened for this exchange
133
129
  const ws_key = Exchange_name
@@ -173,162 +169,158 @@ const get_exchanges_rates = function (Exchanges, exchange_pair_dict, cb, ws_enab
173
169
  // This ensures only ONE WebSocket per exchange, even with multiple clients/pairs
174
170
  Exchange.ws_market(all_pairs)
175
171
 
176
- const WS_TIMEOUT_MS = 30000 // 30 seconds timeout
177
- const MAX_CHECKS = 300 // 30 seconds / 100ms
178
- const check_timeout = create_ws_timeout_checker(
179
- Exchange_name,
180
- WS_TIMEOUT_MS,
181
- MAX_CHECKS,
182
- (elapsed) => {
172
+ const WS_TIMEOUT_MS = 30000 // 30 seconds timeout
173
+ const MAX_CHECKS = 300 // 30 seconds / 100ms
174
+ const check_timeout = create_ws_timeout_checker(Exchange_name, WS_TIMEOUT_MS, MAX_CHECKS, (elapsed) => {
183
175
  console.error(`WebSocket timeout for ${Exchange_name} after ${elapsed}ms`)
184
176
  reject(new Error(`WebSocket timeout for ${Exchange_name}`))
185
- }
186
- )
177
+ })
187
178
 
188
- const check_status = () => {
189
- if (check_timeout()) {
190
- return
191
- }
179
+ const check_status = () => {
180
+ if (check_timeout()) {
181
+ return
182
+ }
192
183
 
193
- // Safety check for WebSocket object
194
- if (!Exchange.ws || !Exchange.ws.market) {
195
- console.error(`${Exchange_name} WebSocket object missing during check`)
196
- reject(new Error(`WebSocket object missing for ${Exchange_name}`))
197
- return
198
- }
184
+ // Safety check for WebSocket object
185
+ if (!Exchange.ws || !Exchange.ws.market) {
186
+ console.error(`${Exchange_name} WebSocket object missing during check`)
187
+ reject(new Error(`WebSocket object missing for ${Exchange_name}`))
188
+ return
189
+ }
199
190
 
200
- // Check only the pairs we need for this call (not all pairs in connection)
201
- let pairs_to_check = exchange_pair_dict[Exchange_name] || all_pairs
202
-
203
- // Check if WebSocket connection is at least opening/opened (not closed/stopped)
204
- const ws_connection_ready = Exchange.ws.market.status === 'opening' || Exchange.ws.market.status === 'opened'
205
-
206
- if (ws_connection_ready) {
207
- // Try each pair individually - wait a bit for pairs to become ready
208
- // This allows us to detect individual pair failures while giving time for subscription
209
- const PAIR_READY_TIMEOUT_MS = 10000 // Wait up to 10 seconds for pair to become ready
210
- const PAIR_CHECK_INTERVAL_MS = 100 // Check every 100ms
211
-
212
- let rate_promises = pairs_to_check.map((pair) => {
213
- return new Promise((rate_resolve) => {
214
- // Check if this pair should skip WebSocket due to persistent failures
215
- if (should_skip_websocket(ws_failed_pairs, Exchange_name, pair)) {
216
- const failure_info = ws_failed_pairs[Exchange_name][pair]
217
- console.log(`[WebSocket Skip] ${Exchange_name} ${pair}: Skipping WebSocket (${failure_info.count} consecutive failures, last failed ${Math.round((Date.now() - failure_info.last_failed) / 1000)}s ago)`)
218
- rate_resolve({ pair, failed: true, skip_ws: true })
219
- return
220
- }
221
-
222
- // Wait for pair to become ready with retry logic
223
- const pair_start_time = Date.now()
224
- const check_pair_ready = () => {
225
- // Check if this specific pair is ready before calling rate_ws
226
- const pair_ready = (
227
- Exchange.ws.market.pair_status &&
228
- Exchange.ws.market.pair_status[pair] === 'opened' &&
229
- Exchange.ws.market.asks_dict &&
230
- Exchange.ws.market.asks_dict[pair] &&
231
- Exchange.ws.market.asks_dict[pair].length > 0 &&
232
- Exchange.ws.market.bids_dict &&
233
- Exchange.ws.market.bids_dict[pair] &&
234
- Exchange.ws.market.bids_dict[pair].length > 0
235
- )
236
-
237
- if (pair_ready) {
238
- // Pair is ready, try to get rate
239
- Exchange.rate_ws(pair, function (res) {
240
- if (res.success) {
241
- // Success - reset failure count
242
- reset_ws_failure(ws_failed_pairs, Exchange_name, pair)
243
- set_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair, res.body)
244
- rate_resolve({ pair, failed: false })
245
- } else {
246
- console.error(`getting rate ws failed for ${Exchange_name} ${pair}:`, res)
247
- delete_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair)
248
- track_ws_failure(ws_failed_pairs, Exchange_name, pair)
249
- rate_resolve({ pair, failed: true })
250
- }
251
- })
252
- } else {
253
- // Pair not ready yet - check if we've exceeded timeout
254
- const elapsed = Date.now() - pair_start_time
255
- if (elapsed >= PAIR_READY_TIMEOUT_MS) {
256
- // Timeout reached - mark as failed, will fall back to REST
257
- console.log(`[WebSocket] ${Exchange_name} ${pair}: Not ready after ${elapsed}ms (status: ${Exchange.ws.market.pair_status?.[pair] || 'unknown'})`)
258
- rate_resolve({ pair, failed: true })
259
- } else {
260
- // Retry after a short delay
261
- setTimeout(check_pair_ready, PAIR_CHECK_INTERVAL_MS)
262
- }
191
+ // Check only the pairs we need for this call (not all pairs in connection)
192
+ let pairs_to_check = exchange_pair_dict[Exchange_name] || all_pairs
193
+
194
+ // Check if WebSocket connection is at least opening/opened (not closed/stopped)
195
+ const ws_connection_ready = Exchange.ws.market.status === 'opening' || Exchange.ws.market.status === 'opened'
196
+
197
+ if (ws_connection_ready) {
198
+ // Try each pair individually - wait a bit for pairs to become ready
199
+ // This allows us to detect individual pair failures while giving time for subscription
200
+ const PAIR_READY_TIMEOUT_MS = 10000 // Wait up to 10 seconds for pair to become ready
201
+ const PAIR_CHECK_INTERVAL_MS = 100 // Check every 100ms
202
+
203
+ let rate_promises = pairs_to_check.map((pair) => {
204
+ return new Promise((rate_resolve) => {
205
+ // Check if this pair should skip WebSocket due to persistent failures
206
+ if (should_skip_websocket(ws_failed_pairs, Exchange_name, pair)) {
207
+ const failure_info = ws_failed_pairs[Exchange_name][pair]
208
+ console.log(
209
+ `[WebSocket Skip] ${Exchange_name} ${pair}: Skipping WebSocket (${failure_info.count} consecutive failures, last failed ${Math.round((Date.now() - failure_info.last_failed) / 1000)}s ago)`,
210
+ )
211
+ rate_resolve({ pair, failed: true, skip_ws: true })
212
+ return
263
213
  }
264
- }
265
-
266
- // Start checking
267
- check_pair_ready()
268
- })
269
- })
270
- Promise.all(rate_promises)
271
- .then((results) => {
272
- // Check which pairs failed and fall back to REST for those pairs only
273
- const failed_pairs = results.filter((r) => r && r.failed).map((r) => r.pair)
274
- if (failed_pairs.length > 0 && Exchange.rate) {
275
- console.log(`[WebSocket Fallback] ${Exchange_name}: Falling back to REST for ${failed_pairs.length} pair(s): ${failed_pairs.join(', ')}`)
276
- const rest_promises = failed_pairs.map((pair) => {
277
- return new Promise((rest_resolve) => {
278
- Exchange.rate(pair, DEFAULT_ORDERBOOK_LEVEL, function (res) {
214
+
215
+ // Wait for pair to become ready with retry logic
216
+ const pair_start_time = Date.now()
217
+ const check_pair_ready = () => {
218
+ // Check if this specific pair is ready before calling rate_ws
219
+ const pair_ready =
220
+ Exchange.ws.market.pair_status &&
221
+ Exchange.ws.market.pair_status[pair] === 'opened' &&
222
+ Exchange.ws.market.asks_dict &&
223
+ Exchange.ws.market.asks_dict[pair] &&
224
+ Exchange.ws.market.asks_dict[pair].length > 0 &&
225
+ Exchange.ws.market.bids_dict &&
226
+ Exchange.ws.market.bids_dict[pair] &&
227
+ Exchange.ws.market.bids_dict[pair].length > 0
228
+
229
+ if (pair_ready) {
230
+ // Pair is ready, try to get rate
231
+ Exchange.rate_ws(pair, function (res) {
279
232
  if (res.success) {
233
+ // Success - reset failure count
234
+ reset_ws_failure(ws_failed_pairs, Exchange_name, pair)
280
235
  set_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair, res.body)
281
- console.log(`[REST Fallback] ${Exchange_name} ${pair}: Successfully fetched via REST`)
236
+ rate_resolve({ pair, failed: false })
282
237
  } else {
283
- console.error(`[REST Fallback] getting rate failed for ${Exchange_name} ${pair}:`, res)
238
+ console.error(`getting rate ws failed for ${Exchange_name} ${pair}:`, res)
284
239
  delete_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair)
240
+ track_ws_failure(ws_failed_pairs, Exchange_name, pair)
241
+ rate_resolve({ pair, failed: true })
285
242
  }
286
- rest_resolve()
243
+ })
244
+ } else {
245
+ // Pair not ready yet - check if we've exceeded timeout
246
+ const elapsed = Date.now() - pair_start_time
247
+ if (elapsed >= PAIR_READY_TIMEOUT_MS) {
248
+ // Timeout reached - mark as failed, will fall back to REST
249
+ console.log(`[WebSocket] ${Exchange_name} ${pair}: Not ready after ${elapsed}ms (status: ${Exchange.ws.market.pair_status?.[pair] || 'unknown'})`)
250
+ rate_resolve({ pair, failed: true })
251
+ } else {
252
+ // Retry after a short delay
253
+ setTimeout(check_pair_ready, PAIR_CHECK_INTERVAL_MS)
254
+ }
255
+ }
256
+ }
257
+
258
+ // Start checking
259
+ check_pair_ready()
260
+ })
261
+ })
262
+ Promise.all(rate_promises)
263
+ .then((results) => {
264
+ // Check which pairs failed and fall back to REST for those pairs only
265
+ const failed_pairs = results.filter((r) => r && r.failed).map((r) => r.pair)
266
+ if (failed_pairs.length > 0 && Exchange.rate) {
267
+ console.log(`[WebSocket Fallback] ${Exchange_name}: Falling back to REST for ${failed_pairs.length} pair(s): ${failed_pairs.join(', ')}`)
268
+ const rest_promises = failed_pairs.map((pair) => {
269
+ return new Promise((rest_resolve) => {
270
+ Exchange.rate(pair, DEFAULT_ORDERBOOK_LEVEL, function (res) {
271
+ if (res.success) {
272
+ set_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair, res.body)
273
+ console.log(`[REST Fallback] ${Exchange_name} ${pair}: Successfully fetched via REST`)
274
+ } else {
275
+ console.error(`[REST Fallback] getting rate failed for ${Exchange_name} ${pair}:`, res)
276
+ delete_pair_data(exchange_pair_rates, pair_exchange_rates, Exchange_name, pair)
277
+ }
278
+ rest_resolve()
279
+ })
287
280
  })
288
281
  })
289
- })
290
- // Wait for REST fallback to complete before resolving
291
- return Promise.all(rest_promises)
292
- .catch((err) => {
293
- console.error(`[REST Fallback] Error fetching rates for ${Exchange_name}:`, err)
294
- })
295
- .then(() => {
296
- // Resolve after REST fallback completes
297
- resolve()
298
- })
299
- } else {
300
- // if (Exchange.ws.market.end_base) {
301
- // Exchange.ws.market.end_base(); // Close the connection and prevent reconnection
302
- // console.log(`${Exchange_name} WebSocket connection closed.`);
303
- // }
282
+ // Wait for REST fallback to complete before resolving
283
+ return Promise.all(rest_promises)
284
+ .catch((err) => {
285
+ console.error(`[REST Fallback] Error fetching rates for ${Exchange_name}:`, err)
286
+ })
287
+ .then(() => {
288
+ // Resolve after REST fallback completes
289
+ resolve()
290
+ })
291
+ } else {
292
+ // if (Exchange.ws.market.end_base) {
293
+ // Exchange.ws.market.end_base(); // Close the connection and prevent reconnection
294
+ // console.log(`${Exchange_name} WebSocket connection closed.`);
295
+ // }
296
+ resolve()
297
+ }
298
+ })
299
+ .catch((err) => {
300
+ console.error(`Error in rate_ws for ${Exchange_name}:`, err)
304
301
  resolve()
305
- }
306
- })
307
- .catch((err) => {
308
- console.error(`Error in rate_ws for ${Exchange_name}:`, err)
309
- resolve()
310
- })
311
- } else {
312
- // WebSocket connection not ready yet, wait and retry
313
- setTimeout(check_status, 100)
302
+ })
303
+ } else {
304
+ // WebSocket connection not ready yet, wait and retry
305
+ setTimeout(check_status, 100)
306
+ }
314
307
  }
315
- }
316
308
 
317
- check_status()
318
- })
319
- // Clean up promise reference when done (but keep connection open for reuse)
320
- ws_promise.finally(() => {
321
- // Don't delete immediately - keep for a short time to allow reuse
322
- setTimeout(() => {
323
- if (ws_connection_promises[ws_key] === ws_promise) {
324
- delete ws_connection_promises[ws_key]
325
- // Also clean up wrapped promise if it exists
326
- if (ws_connection_promises_with_fallback[ws_key]) {
327
- delete ws_connection_promises_with_fallback[ws_key]
309
+ check_status()
310
+ })
311
+ // Clean up promise reference when done (but keep connection open for reuse)
312
+ ws_promise.finally(() => {
313
+ // Don't delete immediately - keep for a short time to allow reuse
314
+ setTimeout(() => {
315
+ if (ws_connection_promises[ws_key] === ws_promise) {
316
+ delete ws_connection_promises[ws_key]
317
+ // Also clean up wrapped promise if it exists
318
+ if (ws_connection_promises_with_fallback[ws_key]) {
319
+ delete ws_connection_promises_with_fallback[ws_key]
320
+ }
328
321
  }
329
- }
330
- }, 1000)
331
- })
322
+ }, 1000)
323
+ })
332
324
  }
333
325
 
334
326
  // Wrap WebSocket promise to handle rejection and fall back to REST
@@ -352,7 +344,7 @@ const get_exchanges_rates = function (Exchanges, exchange_pair_dict, cb, ws_enab
352
344
  rest_resolve()
353
345
  })
354
346
  })
355
- })
347
+ }),
356
348
  ).then(() => {
357
349
  // Resolve after REST fallback completes
358
350
  return Promise.resolve()
@@ -485,152 +477,150 @@ const get_exchanges_market_history = function (Exchanges, exchange_pair_dict, ti
485
477
  first_time[Exchange_name] = true
486
478
  }
487
479
 
488
- const WS_TIMEOUT_MS = 30000 // 30 seconds timeout
489
- const MAX_CHECKS = 300 // 30 seconds / 100ms
490
- const check_timeout = create_ws_timeout_checker(
491
- Exchange_name,
492
- WS_TIMEOUT_MS,
493
- MAX_CHECKS,
494
- (elapsed) => {
480
+ const WS_TIMEOUT_MS = 30000 // 30 seconds timeout
481
+ const MAX_CHECKS = 300 // 30 seconds / 100ms
482
+ const check_timeout = create_ws_timeout_checker(Exchange_name, WS_TIMEOUT_MS, MAX_CHECKS, (elapsed) => {
495
483
  console.error(`WebSocket timeout for ${Exchange_name} market history after ${elapsed}ms`)
496
484
  reject(new Error(`WebSocket timeout for ${Exchange_name} market history`))
497
- }
498
- )
485
+ })
499
486
 
500
- const check_status = () => {
501
- if (check_timeout()) {
502
- return
503
- }
487
+ const check_status = () => {
488
+ if (check_timeout()) {
489
+ return
490
+ }
504
491
 
505
- // Safety check for WebSocket object
506
- if (!Exchange.ws || !Exchange.ws.market) {
507
- console.error(`${Exchange_name} WebSocket object missing during check`)
508
- reject(new Error(`WebSocket object missing for ${Exchange_name}`))
509
- return
510
- }
492
+ // Safety check for WebSocket object
493
+ if (!Exchange.ws || !Exchange.ws.market) {
494
+ console.error(`${Exchange_name} WebSocket object missing during check`)
495
+ reject(new Error(`WebSocket object missing for ${Exchange_name}`))
496
+ return
497
+ }
511
498
 
512
- // Check only the pairs we need for this call
513
- let pairs_to_check = exchange_pair_dict[Exchange_name] || all_pairs
514
-
515
- // Check if WebSocket connection is at least opening/opened (not closed/stopped)
516
- const ws_connection_ready = Exchange.ws.market.status === 'opening' || Exchange.ws.market.status === 'opened'
517
-
518
- if (ws_connection_ready) {
519
- // Try each pair individually - wait a bit for pairs to become ready
520
- // This allows us to detect individual pair failures while giving time for subscription
521
- const PAIR_READY_TIMEOUT_MS = 10000 // Wait up to 10 seconds for pair to become ready
522
- const PAIR_CHECK_INTERVAL_MS = 100 // Check every 100ms
523
-
524
- let market_history_promises = pairs_to_check.map((pair) => {
525
- return new Promise((rate_resolve) => {
526
- // Check if this pair should skip WebSocket due to persistent failures
527
- if (should_skip_websocket(ws_market_history_failed_pairs, Exchange_name, pair)) {
528
- const failure_info = ws_market_history_failed_pairs[Exchange_name][pair]
529
- console.log(`[WebSocket Skip] ${Exchange_name} ${pair} market history: Skipping WebSocket (${failure_info.count} consecutive failures, last failed ${Math.round((Date.now() - failure_info.last_failed) / 1000)}s ago)`)
530
- rate_resolve({ pair, failed: true, skip_ws: true })
531
- return
532
- }
533
-
534
- // Wait for pair to become ready with retry logic
535
- const pair_start_time = Date.now()
536
- const check_pair_ready = () => {
537
- // Check if this specific pair is ready before calling market_history_ws
538
- const pair_ready = (
539
- Exchange.ws.market.pair_status &&
540
- Exchange.ws.market.pair_status[pair] === 'opened' &&
541
- Exchange.ws.market.market_history_dict &&
542
- Exchange.ws.market.market_history_dict[pair] &&
543
- Exchange.ws.market.market_history_dict[pair].length >= 0
544
- )
545
-
546
- if (pair_ready) {
547
- // Pair is ready, try to get market history
548
- Exchange.market_history_ws(pair, timeframe_in_ms, function (res) {
549
- if (res.success) {
550
- // Success - reset failure count
551
- reset_ws_failure(ws_market_history_failed_pairs, Exchange_name, pair)
552
- set_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair, res.body)
553
- rate_resolve({ pair, failed: false })
554
- } else {
555
- console.error(`getting market history ws failed for ${Exchange_name} ${pair}:`, res)
556
- delete_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair)
557
- track_ws_failure(ws_market_history_failed_pairs, Exchange_name, pair)
558
- rate_resolve({ pair, failed: true })
559
- }
560
- })
561
- } else {
562
- // Pair not ready yet - check if we've exceeded timeout
563
- const elapsed = Date.now() - pair_start_time
564
- if (elapsed >= PAIR_READY_TIMEOUT_MS) {
565
- // Timeout reached - mark as failed, will fall back to REST
566
- console.log(`[WebSocket Market History] ${Exchange_name} ${pair}: Not ready after ${elapsed}ms (status: ${Exchange.ws.market.pair_status?.[pair] || 'unknown'})`)
567
- rate_resolve({ pair, failed: true })
568
- } else {
569
- // Retry after a short delay
570
- setTimeout(check_pair_ready, PAIR_CHECK_INTERVAL_MS)
571
- }
499
+ // Check only the pairs we need for this call
500
+ let pairs_to_check = exchange_pair_dict[Exchange_name] || all_pairs
501
+
502
+ // Check if WebSocket connection is at least opening/opened (not closed/stopped)
503
+ const ws_connection_ready = Exchange.ws.market.status === 'opening' || Exchange.ws.market.status === 'opened'
504
+
505
+ if (ws_connection_ready) {
506
+ // Try each pair individually - wait a bit for pairs to become ready
507
+ // This allows us to detect individual pair failures while giving time for subscription
508
+ const PAIR_READY_TIMEOUT_MS = 10000 // Wait up to 10 seconds for pair to become ready
509
+ const PAIR_CHECK_INTERVAL_MS = 100 // Check every 100ms
510
+
511
+ let market_history_promises = pairs_to_check.map((pair) => {
512
+ return new Promise((rate_resolve) => {
513
+ // Check if this pair should skip WebSocket due to persistent failures
514
+ if (should_skip_websocket(ws_market_history_failed_pairs, Exchange_name, pair)) {
515
+ const failure_info = ws_market_history_failed_pairs[Exchange_name][pair]
516
+ console.log(
517
+ `[WebSocket Skip] ${Exchange_name} ${pair} market history: Skipping WebSocket (${failure_info.count} consecutive failures, last failed ${Math.round((Date.now() - failure_info.last_failed) / 1000)}s ago)`,
518
+ )
519
+ rate_resolve({ pair, failed: true, skip_ws: true })
520
+ return
572
521
  }
573
- }
574
-
575
- // Start checking
576
- check_pair_ready()
577
- })
578
- })
579
- Promise.all(market_history_promises)
580
- .then((results) => {
581
- // Check which pairs failed and fall back to REST for those pairs only
582
- const failed_pairs = results.filter((r) => r && r.failed).map((r) => r.pair)
583
- if (failed_pairs.length > 0 && Exchange.market_history) {
584
- console.log(`[WebSocket Fallback] ${Exchange_name} market history: Falling back to REST for ${failed_pairs.length} pair(s): ${failed_pairs.join(', ')}`)
585
- const rest_promises = failed_pairs.map((pair) => {
586
- return new Promise((rest_resolve) => {
587
- Exchange.market_history(pair, timeframe_in_ms, function (res) {
522
+
523
+ // Wait for pair to become ready with retry logic
524
+ const pair_start_time = Date.now()
525
+ const check_pair_ready = () => {
526
+ // Check if this specific pair is ready before calling market_history_ws
527
+ const pair_ready =
528
+ Exchange.ws.market.pair_status &&
529
+ Exchange.ws.market.pair_status[pair] === 'opened' &&
530
+ Exchange.ws.market.market_history_dict &&
531
+ Exchange.ws.market.market_history_dict[pair] &&
532
+ Exchange.ws.market.market_history_dict[pair].length >= 0
533
+
534
+ if (pair_ready) {
535
+ // Pair is ready, try to get market history
536
+ Exchange.market_history_ws(pair, timeframe_in_ms, function (res) {
588
537
  if (res.success) {
538
+ // Success - reset failure count
539
+ reset_ws_failure(ws_market_history_failed_pairs, Exchange_name, pair)
589
540
  set_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair, res.body)
590
- console.log(`[REST Fallback] ${Exchange_name} ${pair} market history: Successfully fetched via REST`)
541
+ rate_resolve({ pair, failed: false })
591
542
  } else {
592
- console.error(`[REST Fallback] getting market history failed for ${Exchange_name} ${pair}:`, res)
543
+ console.error(`getting market history ws failed for ${Exchange_name} ${pair}:`, res)
593
544
  delete_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair)
545
+ track_ws_failure(ws_market_history_failed_pairs, Exchange_name, pair)
546
+ rate_resolve({ pair, failed: true })
594
547
  }
595
- rest_resolve()
548
+ })
549
+ } else {
550
+ // Pair not ready yet - check if we've exceeded timeout
551
+ const elapsed = Date.now() - pair_start_time
552
+ if (elapsed >= PAIR_READY_TIMEOUT_MS) {
553
+ // Timeout reached - mark as failed, will fall back to REST
554
+ console.log(
555
+ `[WebSocket Market History] ${Exchange_name} ${pair}: Not ready after ${elapsed}ms (status: ${Exchange.ws.market.pair_status?.[pair] || 'unknown'})`,
556
+ )
557
+ rate_resolve({ pair, failed: true })
558
+ } else {
559
+ // Retry after a short delay
560
+ setTimeout(check_pair_ready, PAIR_CHECK_INTERVAL_MS)
561
+ }
562
+ }
563
+ }
564
+
565
+ // Start checking
566
+ check_pair_ready()
567
+ })
568
+ })
569
+ Promise.all(market_history_promises)
570
+ .then((results) => {
571
+ // Check which pairs failed and fall back to REST for those pairs only
572
+ const failed_pairs = results.filter((r) => r && r.failed).map((r) => r.pair)
573
+ if (failed_pairs.length > 0 && Exchange.market_history) {
574
+ console.log(`[WebSocket Fallback] ${Exchange_name} market history: Falling back to REST for ${failed_pairs.length} pair(s): ${failed_pairs.join(', ')}`)
575
+ const rest_promises = failed_pairs.map((pair) => {
576
+ return new Promise((rest_resolve) => {
577
+ Exchange.market_history(pair, timeframe_in_ms, function (res) {
578
+ if (res.success) {
579
+ set_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair, res.body)
580
+ console.log(`[REST Fallback] ${Exchange_name} ${pair} market history: Successfully fetched via REST`)
581
+ } else {
582
+ console.error(`[REST Fallback] getting market history failed for ${Exchange_name} ${pair}:`, res)
583
+ delete_pair_data(exchange_pair_market_history, pair_exchange_market_history, Exchange_name, pair)
584
+ }
585
+ rest_resolve()
586
+ })
596
587
  })
597
588
  })
598
- })
599
- // Wait for REST fallback to complete before resolving
600
- return Promise.all(rest_promises)
601
- .catch((err) => {
602
- console.error(`[REST Fallback] Error fetching market history for ${Exchange_name}:`, err)
603
- })
604
- .then(() => {
605
- // Resolve after REST fallback completes
606
- resolve()
607
- })
608
- } else {
589
+ // Wait for REST fallback to complete before resolving
590
+ return Promise.all(rest_promises)
591
+ .catch((err) => {
592
+ console.error(`[REST Fallback] Error fetching market history for ${Exchange_name}:`, err)
593
+ })
594
+ .then(() => {
595
+ // Resolve after REST fallback completes
596
+ resolve()
597
+ })
598
+ } else {
599
+ resolve()
600
+ }
601
+ })
602
+ .catch((err) => {
603
+ console.error(`Error in market_history_ws for ${Exchange_name}:`, err)
609
604
  resolve()
610
- }
611
- })
612
- .catch((err) => {
613
- console.error(`Error in market_history_ws for ${Exchange_name}:`, err)
614
- resolve()
615
- })
616
- } else {
617
- setTimeout(check_status, 100)
618
- }
619
- }
620
- check_status()
621
- })
622
- // Clean up promise reference when done (but keep connection open for reuse)
623
- ws_promise.finally(() => {
624
- setTimeout(() => {
625
- if (ws_market_history_promises[ws_key] === ws_promise) {
626
- delete ws_market_history_promises[ws_key]
627
- // Also clean up wrapped promise if it exists
628
- if (ws_market_history_promises_with_fallback[ws_key]) {
629
- delete ws_market_history_promises_with_fallback[ws_key]
605
+ })
606
+ } else {
607
+ setTimeout(check_status, 100)
630
608
  }
631
609
  }
632
- }, 1000)
633
- })
610
+ check_status()
611
+ })
612
+ // Clean up promise reference when done (but keep connection open for reuse)
613
+ ws_promise.finally(() => {
614
+ setTimeout(() => {
615
+ if (ws_market_history_promises[ws_key] === ws_promise) {
616
+ delete ws_market_history_promises[ws_key]
617
+ // Also clean up wrapped promise if it exists
618
+ if (ws_market_history_promises_with_fallback[ws_key]) {
619
+ delete ws_market_history_promises_with_fallback[ws_key]
620
+ }
621
+ }
622
+ }, 1000)
623
+ })
634
624
  }
635
625
 
636
626
  // If WebSocket promise rejects (timeout/error), fall back to REST for all pairs
@@ -654,7 +644,7 @@ const get_exchanges_market_history = function (Exchanges, exchange_pair_dict, ti
654
644
  rest_resolve()
655
645
  })
656
646
  })
657
- })
647
+ }),
658
648
  ).then(() => {
659
649
  // Resolve after REST fallback completes
660
650
  return Promise.resolve()
@@ -700,12 +690,13 @@ const get_exchanges_market_history = function (Exchanges, exchange_pair_dict, ti
700
690
 
701
691
  let checked_ohlcv_exchange = {},
702
692
  ohlcv_status = {},
693
+ ohlcv_callback_fired = false, // Separate flag to prevent multiple callback calls
703
694
  exchange_pair_ohlcv = {},
704
695
  pair_exchange_ohlcv = {}
705
696
  const get_exchanges_ohlcv = function (Exchanges, exchange_pair_dict, from_in_ms, to_in_ms, cb) {
706
697
  checked_ohlcv_exchange = {}
707
698
  ohlcv_status = {}
708
- ohlcv_status._callback_fired = false // Reset callback flag
699
+ ohlcv_callback_fired = false // Reset callback flag (separate from ohlcv_status to not pollute _.values())
709
700
  if (!Array.isArray(Exchanges)) {
710
701
  console.error('Exchanges should be an array')
711
702
  return
@@ -731,16 +722,16 @@ const get_exchanges_ohlcv = function (Exchanges, exchange_pair_dict, from_in_ms,
731
722
  }
732
723
  _.set(ohlcv_status, [Exchange_name + pair], true)
733
724
  // Use a flag to prevent multiple callback calls
734
- if (!_.includes(_.values(ohlcv_status), false) && !ohlcv_status._callback_fired) {
735
- ohlcv_status._callback_fired = true
725
+ if (!_.includes(_.values(ohlcv_status), false) && !ohlcv_callback_fired) {
726
+ ohlcv_callback_fired = true
736
727
  cb({ exchange_pair_ohlcv, pair_exchange_ohlcv })
737
728
  }
738
729
  })
739
730
  } else {
740
731
  _.set(ohlcv_status, [Exchange_name + pair], true)
741
732
  // Use a flag to prevent multiple callback calls
742
- if (!_.includes(_.values(ohlcv_status), false) && !ohlcv_status._callback_fired) {
743
- ohlcv_status._callback_fired = true
733
+ if (!_.includes(_.values(ohlcv_status), false) && !ohlcv_callback_fired) {
734
+ ohlcv_callback_fired = true
744
735
  cb({ exchange_pair_ohlcv, pair_exchange_ohlcv })
745
736
  }
746
737
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icgio/icg-exchanges-wrapper",
3
- "version": "1.15.1",
3
+ "version": "1.15.3",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {