@onlineapps/mq-client-core 1.0.78 → 1.0.79

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onlineapps/mq-client-core",
3
- "version": "1.0.78",
3
+ "version": "1.0.79",
4
4
  "description": "Core MQ client library for RabbitMQ - shared by infrastructure services and connectors",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -80,6 +80,7 @@ class RabbitMQClient extends EventEmitter {
80
80
  this._reconnecting = false;
81
81
  this._disconnecting = false;
82
82
  this._reconnectAttempts = 0;
83
+ this._activeTimers = new Set();
83
84
  this._maxReconnectAttempts = this._config.maxReconnectAttempts || 10; // Max 10 attempts
84
85
  this._reconnectBaseDelay = this._config.reconnectBaseDelay || 1000; // Start with 1 second
85
86
  this._reconnectMaxDelay = this._config.reconnectMaxDelay || 30000; // Max 30 seconds
@@ -418,6 +419,41 @@ class RabbitMQClient extends EventEmitter {
418
419
  * @returns {Promise<void>}
419
420
  * @throws {Error} If connection or channel creation fails.
420
421
  */
422
+ /**
423
+ * Helper to set a trackable timeout
424
+ * @private
425
+ */
426
+ _setTimeout(callback, ms) {
427
+ const timer = setTimeout(() => {
428
+ this._activeTimers.delete(timer);
429
+ callback();
430
+ }, ms);
431
+ this._activeTimers.add(timer);
432
+ return timer;
433
+ }
434
+
435
+ /**
436
+ * Helper to clear a trackable timeout
437
+ * @private
438
+ */
439
+ _clearTimeout(timer) {
440
+ if (timer) {
441
+ clearTimeout(timer);
442
+ this._activeTimers.delete(timer);
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Clear all active timers
448
+ * @private
449
+ */
450
+ _clearAllTimers() {
451
+ for (const timer of this._activeTimers) {
452
+ clearTimeout(timer);
453
+ }
454
+ this._activeTimers.clear();
455
+ }
456
+
421
457
  async connect() {
422
458
  let connectTimeoutTimer = null;
423
459
  try {
@@ -435,7 +471,7 @@ class RabbitMQClient extends EventEmitter {
435
471
 
436
472
  const connectPromise = amqp.connect(...connectArgs);
437
473
  const timeoutPromise = new Promise((_, reject) => {
438
- connectTimeoutTimer = setTimeout(() => reject(new Error('Connection timeout after 10 seconds')), 10000);
474
+ connectTimeoutTimer = this._setTimeout(() => reject(new Error('Connection timeout after 10 seconds')), 10000);
439
475
  if (connectTimeoutTimer && typeof connectTimeoutTimer.unref === 'function') {
440
476
  connectTimeoutTimer.unref();
441
477
  }
@@ -443,7 +479,7 @@ class RabbitMQClient extends EventEmitter {
443
479
  console.log('[RabbitMQClient] Starting connection race...');
444
480
  this._connection = await Promise.race([connectPromise, timeoutPromise]);
445
481
  if (connectTimeoutTimer) {
446
- clearTimeout(connectTimeoutTimer);
482
+ this._clearTimeout(connectTimeoutTimer);
447
483
  connectTimeoutTimer = null;
448
484
  }
449
485
  console.log('[RabbitMQClient] Connection established');
@@ -562,18 +598,18 @@ class RabbitMQClient extends EventEmitter {
562
598
 
563
599
  // Wait for 'reconnected' event
564
600
  return new Promise((resolve, reject) => {
565
- const timeout = setTimeout(() => {
601
+ const timeout = this._setTimeout(() => {
566
602
  this.removeListener('reconnected', onReconnected);
567
603
  this.removeListener('error', onError);
568
604
  reject(new Error(`Reconnection timeout after ${this._reconnectWaitTimeout}ms`));
569
605
  }, this._reconnectWaitTimeout);
570
606
 
571
607
  const onReconnected = () => {
572
- clearTimeout(timeout);
608
+ this._clearTimeout(timeout);
573
609
  this.removeListener('reconnected', onReconnected);
574
610
  this.removeListener('error', onError);
575
611
  // Wait a bit for channels to be fully recreated
576
- setTimeout(resolve, 500);
612
+ this._setTimeout(resolve, 500);
577
613
  };
578
614
 
579
615
  const onError = (error) => {
@@ -581,7 +617,7 @@ class RabbitMQClient extends EventEmitter {
581
617
  if (error.message && error.message.includes('Connection closed unexpectedly')) {
582
618
  return; // Expected during reconnection
583
619
  }
584
- clearTimeout(timeout);
620
+ this._clearTimeout(timeout);
585
621
  this.removeListener('reconnected', onReconnected);
586
622
  this.removeListener('error', onError);
587
623
  reject(error);
@@ -1052,6 +1088,10 @@ class RabbitMQClient extends EventEmitter {
1052
1088
 
1053
1089
  async disconnect() {
1054
1090
  this._disconnecting = true;
1091
+
1092
+ // Clear all active timers (reconnection, waits, etc.)
1093
+ this._clearAllTimers();
1094
+
1055
1095
  // Stop health monitoring
1056
1096
  this._stopHealthMonitoring();
1057
1097
 
@@ -1324,13 +1364,13 @@ class RabbitMQClient extends EventEmitter {
1324
1364
  // Use callback-based confirmation - kanály jsou spolehlivé, takže callback vždy dorazí
1325
1365
  const confirmPromise = new Promise((resolve, reject) => {
1326
1366
  // Set timeout for publish confirmation (configurable)
1327
- const timeout = setTimeout(() => {
1367
+ const timeout = this._setTimeout(() => {
1328
1368
  reject(new Error(`Publish confirmation timeout for queue "${queue}" after ${this._publishConfirmationTimeout}ms`));
1329
1369
  }, this._publishConfirmationTimeout);
1330
1370
 
1331
1371
  // Check if channel is still valid before sending
1332
1372
  if (!this._channel || this._channel.closed) {
1333
- clearTimeout(timeout);
1373
+ this._clearTimeout(timeout);
1334
1374
  reject(new Error(`Cannot publish: channel is closed for queue "${queue}"`));
1335
1375
  return;
1336
1376
  }
@@ -1344,7 +1384,7 @@ class RabbitMQClient extends EventEmitter {
1344
1384
  try {
1345
1385
  originalChannel.sendToQueue(queue, buffer, { persistent, headers }, (err, ok) => {
1346
1386
  callbackInvoked = true;
1347
- clearTimeout(timeout);
1387
+ this._clearTimeout(timeout);
1348
1388
 
1349
1389
  // CRITICAL: Check if channel was closed or recreated during publish
1350
1390
  // If channel was recreated, the delivery tag is invalid - ignore this callback
@@ -1398,11 +1438,11 @@ class RabbitMQClient extends EventEmitter {
1398
1438
  });
1399
1439
 
1400
1440
  // Set a safety timeout - if callback wasn't invoked and channel closed, retry
1401
- setTimeout(() => {
1441
+ this._setTimeout(() => {
1402
1442
  if (!callbackInvoked && (!this._channel || this._channel.closed || this._channel !== originalChannel)) {
1403
1443
  console.warn(`[RabbitMQClient] [mq-client-core] [PUBLISH] Callback timeout and channel closed for queue "${queue}", will retry after reconnection`);
1404
1444
  if (this._reconnecting) {
1405
- clearTimeout(timeout);
1445
+ this._clearTimeout(timeout);
1406
1446
  this._waitForReconnection().then(() => {
1407
1447
  return this.publish(queue, buffer, options);
1408
1448
  }).then(resolve).catch(reject);
@@ -1438,12 +1478,12 @@ class RabbitMQClient extends EventEmitter {
1438
1478
  // Use callback-based confirmation - kanály jsou spolehlivé
1439
1479
  const confirmPromise = new Promise((resolve, reject) => {
1440
1480
  // Set timeout for exchange publish confirmation
1441
- const timeout = setTimeout(() => {
1481
+ const timeout = this._setTimeout(() => {
1442
1482
  reject(new Error(`Exchange publish confirmation timeout for exchange "${exchange}" after ${this._publishConfirmationTimeout}ms`));
1443
1483
  }, this._publishConfirmationTimeout);
1444
1484
 
1445
1485
  this._channel.publish(exchange, routingKey, buffer, { persistent, headers }, (err, ok) => {
1446
- clearTimeout(timeout);
1486
+ this._clearTimeout(timeout);
1447
1487
  if (err) {
1448
1488
  console.error(`[RabbitMQClient] [mq-client-core] [PUBLISH] Exchange publish callback error:`, err.message);
1449
1489
  reject(err);
@@ -1731,7 +1771,13 @@ class RabbitMQClient extends EventEmitter {
1731
1771
  );
1732
1772
 
1733
1773
  console.log(`[RabbitMQClient] Waiting ${delay}ms before reconnection attempt ${this._reconnectAttempts + 1}...`);
1734
- await new Promise(resolve => setTimeout(resolve, delay));
1774
+ await new Promise(resolve => this._setTimeout(resolve, delay));
1775
+
1776
+ // Check if we started disconnecting during the wait
1777
+ if (this._disconnecting) {
1778
+ console.log('[RabbitMQClient] Disconnecting during reconnection wait, aborting');
1779
+ return;
1780
+ }
1735
1781
 
1736
1782
  // Attempt to reconnect
1737
1783
  console.log(`[RabbitMQClient] Reconnection attempt ${this._reconnectAttempts + 1}...`);
@@ -1760,7 +1806,7 @@ class RabbitMQClient extends EventEmitter {
1760
1806
 
1761
1807
  const connectPromise = amqp.connect(...connectArgs);
1762
1808
  const timeoutPromise = new Promise((_, reject) => {
1763
- setTimeout(() => reject(new Error('Connection timeout after 10 seconds')), 10000);
1809
+ this._setTimeout(() => reject(new Error('Connection timeout after 10 seconds')), 10000);
1764
1810
  });
1765
1811
 
1766
1812
  this._connection = await Promise.race([connectPromise, timeoutPromise]);