@autofleet/rabbit 3.3.0-beta.4 → 3.3.2

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/src/index.ts CHANGED
@@ -20,7 +20,8 @@ import getRedisInstance, { RedisConfig } from './lib/redis';
20
20
  import { assertExchangeFanout, rand, wrapSetImmediate } from './lib/utils';
21
21
  import {
22
22
  AUTOMATION_ID_HEADER,
23
- ConnectionPurpose,
23
+ CONNECTION_CREATED_CONST,
24
+ CONNECTION_FAILED_CONST,
24
25
  DEFAULT_LOCK_TIMEOUT,
25
26
  DEFAULT_OPTIONS,
26
27
  RETRY_HEADER,
@@ -39,13 +40,13 @@ import {
39
40
  CONSUMER_DEFAULT_OPTIONS,
40
41
  QueueSetupPromisesDictionary,
41
42
  AssertExchangePromisesDictionary,
42
- ConnectionData,
43
43
  } from './lib/types';
44
44
 
45
45
  // const debug = nodeDebug('af-rabbitmq')
46
46
  const debug = logger.debug.bind(logger);
47
47
 
48
48
  const PUBLISH_TIMEOUT = 1000 * 10;
49
+
49
50
  export interface IAfRabbitMq {
50
51
  ack: any;
51
52
  nack: any;
@@ -85,13 +86,11 @@ type newChannelOpts = {
85
86
  name?: string;
86
87
  onClose?: null | ((args: any | null) => void);
87
88
  options?: CreateChannelOpts | undefined;
88
- connectionPurpose?: ConnectionPurpose,
89
89
  };
90
90
 
91
91
  type assertChannelOpts = {
92
92
  channelName?: string;
93
93
  force?: boolean;
94
- connectionPurpose?: ConnectionPurpose;
95
94
  }
96
95
 
97
96
  type AfConsumer = {
@@ -103,7 +102,7 @@ type AfConsumer = {
103
102
  const HEARTBEAT = '60';
104
103
 
105
104
  class RabbitMq implements IAfRabbitMq {
106
- static parseMsg(msg: any): any {
105
+ static parseMsg(msg: any) : any {
107
106
  let { content } = msg;
108
107
  content = content.toString();
109
108
 
@@ -144,17 +143,18 @@ class RabbitMq implements IAfRabbitMq {
144
143
 
145
144
  RECONNECT_MSG = 'rabbit: connection disconnect - reconnecting';
146
145
 
147
- publishChannel: ChannelWrapper | null;
146
+ channel: ChannelWrapper | null;
148
147
 
149
148
  publishChannelSetupPromise: Promise<ChannelWrapper> | null;
150
149
 
151
- connectionsMap: {
152
- [ConnectionPurpose.Consume]: ConnectionData;
153
- [ConnectionPurpose.Publish]: ConnectionData;
154
- };
150
+ blockReconnect: boolean | null | undefined
151
+
152
+ connection: AmqpConnectionManager | null | undefined
155
153
 
156
154
  em: EventEmitter;
157
155
 
156
+ creatingConnection: boolean;
157
+
158
158
  exchanges: ExchangesCache;
159
159
 
160
160
  queues: QueuesCache;
@@ -174,32 +174,48 @@ class RabbitMq implements IAfRabbitMq {
174
174
 
175
175
  private consumers: Array<AfConsumer> = [];
176
176
 
177
- constructor(options?: AfRabbitOptions, redisConfig?: RedisConfig) {
177
+ private doesVHostExist = false;
178
+
179
+ private vhost = 'quorum-vhost';
180
+
181
+ // TODO:[QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
182
+ oldChannel: ChannelWrapper | null;
183
+
184
+ oldPublishChannelSetupPromise: Promise<ChannelWrapper> | null;
185
+
186
+ oldBlockReconnect: boolean | null | undefined
187
+
188
+ oldConnection: AmqpConnectionManager | null | undefined
189
+
190
+ oldEm: EventEmitter;
191
+
192
+ oldCreatingConnection: boolean;
193
+
194
+ oldExchanges: ExchangesCache;
195
+
196
+ oldQueues: QueuesCache;
197
+
198
+ oldQueueSetupPromises: QueueSetupPromisesDictionary;
199
+
200
+ oldAssertExchangePromises: AssertExchangePromisesDictionary;
201
+
202
+ oldConsumersTags: Array<[ConfirmChannel, string]>;
203
+
204
+ private oldConsumers: Array<AfConsumer> = [];
205
+
206
+ constructor(options: AfRabbitOptions = {}, redisConfig?: RedisConfig) {
178
207
  this.em = new EventEmitter();
179
- this.publishChannel = null;
208
+ this.channel = null;
180
209
  this.publishChannelSetupPromise = null;
181
- this.connectionsMap = {
182
- [ConnectionPurpose.Consume]: {
183
- connection: null,
184
- creatingConnection: false,
185
- connectionCreatedEventName: 'consumeConnectionCreated',
186
- connectionFailedEventName: 'consumeConnectionFailed',
187
- blockReconnect: false,
188
- },
189
- [ConnectionPurpose.Publish]: {
190
- connection: null,
191
- creatingConnection: false,
192
- connectionCreatedEventName: 'publishConnectionCreated',
193
- connectionFailedEventName: 'publishConnectionFailed',
194
- blockReconnect: false,
195
- },
196
- };
210
+ this.connection = null;
211
+ this.creatingConnection = false;
197
212
  this.exchanges = {};
198
213
  this.queues = {};
199
214
  this.queueSetupPromises = {};
200
215
  this.assertExchangePromises = {};
201
216
  this.consumers = [];
202
217
  this.options = options;
218
+
203
219
  this.redisClient = redisConfig && getRedisInstance(redisConfig);
204
220
  if (this.redisClient) {
205
221
  this.redisLock = promisify(RedisLock(this.redisClient)) as RedisLockType;
@@ -214,8 +230,74 @@ class RabbitMq implements IAfRabbitMq {
214
230
  await this.gracefulShutdown('SIGINT');
215
231
  });
216
232
  }
233
+
234
+ // TODO: [QUORUM-PHASE-3] Delete the old properties that we use for the old consumers and publishers
235
+ this.oldEm = new EventEmitter();
236
+ this.oldChannel = null;
237
+ this.oldPublishChannelSetupPromise = null;
238
+ this.oldConnection = null;
239
+ this.oldCreatingConnection = false;
240
+ this.oldExchanges = {};
241
+ this.oldQueues = {};
242
+ this.oldQueueSetupPromises = {};
243
+ this.oldAssertExchangePromises = {};
244
+ this.oldConsumers = [];
245
+ this.oldConsumersTags = [];
217
246
  }
218
247
 
248
+ private assertVHost = async () => {
249
+ if (this.doesVHostExist) {
250
+ return;
251
+ }
252
+
253
+ const username = process.env.RABBITMQ_USERNAME || 'guest';
254
+ const password = process.env.RABBITMQ_PASSWORD || 'guest';
255
+ const credentials = Buffer.from(`${username}:${password}`).toString('base64');
256
+ const headers = {
257
+ Authorization: `Basic ${credentials}`,
258
+ 'Content-Type': 'application/json',
259
+ };
260
+
261
+ const rabbitHost = `http://${(this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost').split(':')[0]}:15672`;
262
+
263
+ const url = `${rabbitHost}/api/vhosts/${encodeURIComponent(this.vhost)}`;
264
+
265
+ try {
266
+ const response = await fetch(url, {
267
+ method: 'GET',
268
+ headers,
269
+ });
270
+
271
+ if (response.status === 200) {
272
+ this.doesVHostExist = true;
273
+ logger.info('Vhost exists', { vhost: this.vhost });
274
+ return;
275
+ }
276
+
277
+ if (response.status !== 404) {
278
+ logger.error('Failed to check vhost', { response });
279
+ throw new RabbitError('Failed to check vhost');
280
+ }
281
+
282
+ const createResponse = await fetch(url, {
283
+ method: 'PUT',
284
+ headers,
285
+ body: JSON.stringify({ default_queue_type: 'quorum' }),
286
+ });
287
+
288
+ if (!createResponse.ok) {
289
+ logger.error('Failed to create vhost', { response: createResponse });
290
+ throw new RabbitError('Failed to create vhost');
291
+ }
292
+
293
+ this.doesVHostExist = true;
294
+ logger.info('Vhost created', { vhost: this.vhost });
295
+ } catch (error) {
296
+ logger.error('Failed to check or create vhost', { error });
297
+ throw error;
298
+ }
299
+ };
300
+
219
301
  private shouldConsumeMessageByTimestamp = async (msg: ConsumeMessageOrNull) => {
220
302
  if (msg) {
221
303
  const { properties: { headers } } = msg;
@@ -230,7 +312,7 @@ class RabbitMq implements IAfRabbitMq {
230
312
  return false;
231
313
  }
232
314
 
233
- public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage): Promise<any> => {
315
+ public ack = (channel: ConfirmChannel, msg: ConsumeMessageOrNull, shouldUpdateRedisTimestamp = false, releaseLock = null) => async (userMsg: ConsumeMessage) : Promise<any> => {
234
316
  if (msg) {
235
317
  debug('rabbit acking message', { deliveryTag: msg.fields.deliveryTag });
236
318
  await channel.ack(msg);
@@ -256,20 +338,20 @@ class RabbitMq implements IAfRabbitMq {
256
338
  userMsg: ConsumeMessageOrNull,
257
339
  {
258
340
  skipRetry = false,
259
- }: NackOptions = {},
260
- ): Promise<any> => {
341
+ }: NackOptions = { },
342
+ ) : Promise<any> => {
261
343
  await this.unlockRedisIfNeeded(releaseLock);
262
344
  if (channel && msg) {
263
345
  if (
264
346
  !skipRetry
265
347
  && (
266
- !msg.properties.headers[RETRY_HEADER]
348
+ !msg.properties.headers?.[RETRY_HEADER]
267
349
  || parseInt(msg.properties.headers[RETRY_HEADER], 10) < options.retries
268
350
  )
269
351
  ) {
270
352
  await this.sendToQueue(queue, RabbitMq.parseMsg(msg).content, options, {
271
353
  ...msg.properties.headers,
272
- [RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
354
+ [RETRY_HEADER]: msg.properties.headers?.[RETRY_HEADER]
273
355
  ? msg.properties.headers[RETRY_HEADER] + 1
274
356
  : 1,
275
357
  });
@@ -277,7 +359,7 @@ class RabbitMq implements IAfRabbitMq {
277
359
  const deadQueue = `${queue}-dead`;
278
360
  await this.sendToQueue(deadQueue, RabbitMq.parseMsg(msg).content, deadQueueOptions, {
279
361
  ...msg.properties.headers,
280
- [RETRY_HEADER]: msg.properties.headers[RETRY_HEADER]
362
+ [RETRY_HEADER]: msg.properties.headers?.[RETRY_HEADER]
281
363
  ? msg.properties.headers[RETRY_HEADER] + 1
282
364
  : 1,
283
365
  });
@@ -291,38 +373,30 @@ class RabbitMq implements IAfRabbitMq {
291
373
  }
292
374
  }
293
375
 
294
- async getConnection(connectionPurpose: ConnectionPurpose) {
295
- return new Promise<AmqpConnectionManager | undefined | null>(async (resolve, reject) => {
296
- const {
297
- connection,
298
- creatingConnection,
299
- connectionCreatedEventName,
300
- connectionFailedEventName,
301
- blockReconnect,
302
- } = this.connectionsMap[connectionPurpose];
303
-
304
- if (blockReconnect) {
376
+ async getConnection() {
377
+ return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
378
+ if (this.blockReconnect) {
305
379
  debug('rabbit: block reconnect');
306
380
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
307
381
  // @ts-ignore
308
382
  return resolve();
309
383
  }
310
- if (connection !== null) {
311
- if (this.options?.disableReconnect || connection?.isConnected()) {
384
+ if (this.connection !== null) {
385
+ if (this.options?.disableReconnect || this.connection?.isConnected()) {
312
386
  debug('rabbit: connection - is connected');
313
387
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
314
388
  // @ts-ignore
315
- return resolve(connection);
389
+ return resolve(this.connection);
316
390
  }
317
391
  debug('rabbit: connection - reconnecting');
318
392
  }
319
- if (creatingConnection) {
393
+ if (this.creatingConnection) {
320
394
  debug('rabbit: creating connection emi');
321
- this.em.once(connectionCreatedEventName, resolve);
322
- this.em.once(connectionFailedEventName, reject);
395
+ this.em.once(CONNECTION_CREATED_CONST, resolve);
396
+ this.em.once(CONNECTION_FAILED_CONST, reject);
323
397
  return;
324
398
  }
325
- this.connectionsMap[connectionPurpose].creatingConnection = true;
399
+ this.creatingConnection = true;
326
400
  let isResolved = false;
327
401
 
328
402
  // It is import to use it as a function and not as a variable
@@ -334,69 +408,67 @@ class RabbitMq implements IAfRabbitMq {
334
408
  const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
335
409
 
336
410
  debug('rabbit: creating connection', { host, userName, HEARTBEAT });
337
-
338
- return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
411
+ return [`amqp://${userName}:${password}@${host}/${this.vhost}?heartbeat=${HEARTBEAT}`];
339
412
  };
340
413
 
341
414
  const defaultUrls = findServers();
342
- const newConnection: AmqpConnectionManager = await connect(defaultUrls, {
415
+ const connection: AmqpConnectionManager = await connect(defaultUrls, {
343
416
  findServers,
344
417
  });
345
418
 
346
- this.connectionsMap[connectionPurpose].connection = newConnection;
347
- logger.info(`rabbit: created new connection ${connectionPurpose}`);
348
-
349
- newConnection.on('error', (err) => {
419
+ this.connection = connection;
420
+ this.connection.on('error', (err) => {
350
421
  logger.error('rabbit: connection error', { err });
351
422
  if (!isResolved) {
352
423
  isResolved = true;
353
424
  reject(err);
354
- this.em.emit(connectionFailedEventName, err);
425
+ this.em.emit(CONNECTION_FAILED_CONST, err);
355
426
  }
356
427
  });
357
428
 
358
- newConnection.on('connectFailed', (err) => {
429
+ this.connection.on('connectFailed', (err) => {
359
430
  this.consumersTags = [];
360
- logger.error('rabbit: connection connectFailed', { err });
431
+ if (typeof err.url === 'string') {
432
+ err.url = this.maskURL(err.url);
433
+ }
434
+ logger.error('rabbit: connection connectFailed', { err, advice: 'Check if the vhost exist', vhost: this.vhost });
361
435
  if (!isResolved) {
362
436
  isResolved = true;
363
437
  reject(err);
364
- this.em.emit(connectionFailedEventName, err);
438
+ this.em.emit(CONNECTION_FAILED_CONST, err);
365
439
  }
366
440
  });
367
441
 
368
- newConnection.on('disconnect', ({ err }) => {
442
+ this.connection.on('disconnect', ({ err }) => {
443
+ // this.channel = null;
369
444
  this.consumersTags = [];
370
445
  debug('rabbit: connection closed');
371
446
  if (this.options?.disableReconnect) {
372
447
  logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
373
- this.connectionsMap[connectionPurpose].blockReconnect = true;
448
+ this.blockReconnect = true;
374
449
  } else {
375
450
  logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
376
451
  }
377
452
  });
378
- newConnection.once('connect', async () => {
379
- this.connectionsMap[connectionPurpose].creatingConnection = false;
380
- this.em.emit(connectionCreatedEventName, newConnection);
453
+
454
+ this.connection.once('connect', async () => {
455
+ debug('rabbit: connection established');
456
+ this.creatingConnection = false;
457
+ this.em.emit(CONNECTION_CREATED_CONST, connection);
381
458
  isResolved = true;
382
- resolve(newConnection);
459
+ resolve(connection);
383
460
  });
384
461
  });
385
462
  }
386
463
 
387
- async getNewChannel({
388
- name = rand().toString(), onClose = null, options = {}, connectionPurpose = ConnectionPurpose.Consume,
389
- }: newChannelOpts): Promise<ChannelWrapper> {
390
- let connection!: AmqpConnectionManager | undefined | null;
464
+ async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
465
+ let connection!: AmqpConnectionManager;
391
466
  try {
392
- connection = await this.getConnection(connectionPurpose);
467
+ connection = await this.getConnection();
393
468
  } catch (e) {
394
469
  logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
395
470
  throw e;
396
471
  }
397
- if (!connection) {
398
- throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
399
- }
400
472
  const channel = connection.createChannel({ ...options });
401
473
  once(channel, 'close').then((args) => {
402
474
  logger.error(`rabbit: channel ${name} closed`);
@@ -412,23 +484,19 @@ class RabbitMq implements IAfRabbitMq {
412
484
  }
413
485
  }
414
486
 
415
- async assertChannel({ force = false, connectionPurpose = ConnectionPurpose.Consume }: assertChannelOpts): Promise<ChannelWrapper> {
416
- debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.publishChannel });
487
+ async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
417
488
  if (!this.publishChannelSetupPromise) {
418
489
  this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
419
- if (this.publishChannel && !force) {
420
- return resolve(this.publishChannel);
490
+ if (this.channel && !force) {
491
+ return resolve(this.channel);
421
492
  }
422
493
 
423
494
  try {
424
- const channel = await this.getNewChannel({ connectionPurpose });
425
- debug('rabbit: new channel got', { connectionPurpose, channel: this.publishChannel });
495
+ const channel = await this.getNewChannel({});
426
496
  channel.on('error', (err) => {
427
497
  logger.error('rabbit: channel error', { err });
428
498
  });
429
- if (connectionPurpose === ConnectionPurpose.Publish) {
430
- this.publishChannel = channel;
431
- }
499
+ this.channel = channel;
432
500
  resolve(channel);
433
501
  } catch (e) {
434
502
  reject(e);
@@ -438,8 +506,9 @@ class RabbitMq implements IAfRabbitMq {
438
506
  return this.publishChannelSetupPromise;
439
507
  }
440
508
 
441
- async assertExchange(exchangeName: string, options: any = { connectionPurpose: ConnectionPurpose.Consume }) {
442
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
509
+ async assertExchange(exchangeName: string, options?: any) {
510
+ const channel: ChannelWrapper = await this.assertChannel();
511
+
443
512
  if (this.exchanges[exchangeName]) {
444
513
  delete this.assertExchangePromises[exchangeName];
445
514
  return this.exchanges[exchangeName];
@@ -454,57 +523,58 @@ class RabbitMq implements IAfRabbitMq {
454
523
  return this.exchanges[exchangeName];
455
524
  }
456
525
 
457
- async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
526
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
527
+ async getQueueLength(queue: string) {
458
528
  RabbitMq.validateName('queue', queue);
459
- const { connection } = this.connectionsMap[connectionPurpose];
460
- const { publishChannel } = this;
461
- if (!publishChannel) {
462
- throw new Error('channel is not defined');
529
+ const { oldChannel: channel } = this;
530
+ if (!channel) {
531
+ throw new RabbitError('channel is not defined');
463
532
  }
464
- debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
465
- return publishChannel?.checkQueue(queue);
533
+ debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
534
+ return channel?.checkQueue(queue);
466
535
  }
467
536
 
468
- private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
537
+ private async deleteQueue(queue: string) {
469
538
  RabbitMq.validateName('queue', queue);
470
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
539
+ const channel: ChannelWrapper = await this.assertChannel();
471
540
  logger.info('rabbit: deleting queue', { queue });
472
541
  const deleteQueueRes = await channel.deleteQueue(queue);
473
542
  debug('queue deleted', deleteQueueRes);
474
543
  return deleteQueueRes;
475
544
  }
476
545
 
546
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
477
547
  async bindQueue(queue: string, exchange: string) {
478
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
548
+ const channel: ChannelWrapper = await this.assertChannelOld();
479
549
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
480
550
  return channel.bindQueue(queue, exchange, '');
481
551
  }
482
552
 
483
553
  async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
484
554
  let queue: Replies.AssertQueue;
485
- const connectionPurpose = ConnectionPurpose.Publish;
486
- const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
487
555
  const localeOptions = {
488
556
  ...options,
489
557
  durable: true,
490
558
  arguments: {
491
559
  ...options?.arguments,
492
560
  'x-consumer-timeout': 1000 * 60 * 60 * 24,
493
- 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
561
+ 'x-queue-type': 'quorum',
494
562
  },
495
563
  };
496
564
  try {
497
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
565
+ const channel: ChannelWrapper = await this.assertChannel();
498
566
  debug('assertQueue->channel.addSetup', { queueName });
499
- await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
567
+ await channel.addSetup(async (setupChannel: ConfirmChannel) => {
568
+ await setupChannel.assertQueue(queueName, localeOptions);
569
+ });
500
570
  debug('assertQueue->channel.assertQueue', { queueName });
501
571
  queue = await channel.assertQueue(queueName, localeOptions);
502
572
  } catch (e) {
503
573
  logger.error('rabbit: assertQueue error', { queueName, options, error: e });
504
574
  if (!this.options?.dontRetryAssert) {
505
575
  debug('retrying assertQueue', { queueName });
506
- const channel = await this.assertChannel({ force: true, connectionPurpose });
507
- await this.deleteQueue(queueName, connectionPurpose);
576
+ const channel = await this.assertChannel({ force: true });
577
+ await this.deleteQueue(queueName);
508
578
 
509
579
  debug('retrying assertQueue->channel.addSetup', { queueName });
510
580
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
@@ -519,6 +589,7 @@ class RabbitMq implements IAfRabbitMq {
519
589
  return queue;
520
590
  }
521
591
 
592
+ // TODO: [QUORUM-PHASE-3] Can be deleted after deleting the old consumers and publishers because all the queues are created us quorum queues
522
593
  static shouldUseQuorum(queueName: string): boolean {
523
594
  const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
524
595
 
@@ -534,8 +605,7 @@ class RabbitMq implements IAfRabbitMq {
534
605
  return false;
535
606
  }
536
607
 
537
- async assertQueue(queueName: string, options?: Options.AssertQueue) {
538
- debug('rabbit: start assert queue', { queueName });
608
+ async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any> {
539
609
  RabbitMq.validateName('queue', queueName);
540
610
  if (this.queues[queueName]) {
541
611
  delete this.queueSetupPromises[queueName];
@@ -547,12 +617,11 @@ class RabbitMq implements IAfRabbitMq {
547
617
  }
548
618
 
549
619
  this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
550
- debug('rabbit: done assert queue', { queueName });
551
620
  return this.queueSetupPromises[queueName];
552
621
  }
553
622
 
554
623
  private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
555
- const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
624
+ const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
556
625
  if (!isConsumerExist) {
557
626
  logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
558
627
  this.consumers.push({
@@ -563,10 +632,27 @@ class RabbitMq implements IAfRabbitMq {
563
632
  }
564
633
  }
565
634
 
635
+ // Used by the microservices to consume messages from the queue
566
636
  async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
637
+ // TODO: [QUORUM-PHASE-3] Use only the implementation of consumeNew and delete consumeNew and consumeOld
638
+ if (options?.isQuorumQueue !== false) {
639
+ await this.assertVHost();
640
+ await this.consumeNew(queue, callback, options);
641
+ }
642
+
643
+ await this.consumeOld(queue, callback, options);
644
+ }
645
+
646
+ // TODO: [QUORUM-PHASE-3] Delete consumeNew we do not use it anymore
647
+ async consumeNew(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
567
648
  await this.consumeFromRabbit(queue, callback, options);
568
649
  }
569
650
 
651
+ // TODO: [QUORUM-PHASE-3] Delete consumeOld we do not use it anymore
652
+ async consumeOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
653
+ await this.consumeFromRabbitOld(queue, callback, options);
654
+ }
655
+
570
656
  private async lockRedisIfNeeded(msg: any, options: any) {
571
657
  const { properties: { headers } } = msg;
572
658
  const timestamp = headers?.creationTimestamp;
@@ -597,11 +683,11 @@ class RabbitMq implements IAfRabbitMq {
597
683
  } = optionsWithDefaults;
598
684
  if (useConsumeWithLock) {
599
685
  if (!this.redisLock) {
600
- throw new Error('Usage of consumeWithLock requires RedisInstance');
686
+ throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
601
687
  }
602
688
  logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
603
689
  }
604
- const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
690
+ const channel = await this.getNewChannel({});
605
691
  return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
606
692
  const q = await this.assertQueue(queue, optionsWithDefaults);
607
693
  await confirmChannel.prefetch(limit, false);
@@ -612,9 +698,9 @@ class RabbitMq implements IAfRabbitMq {
612
698
  return null;
613
699
  }
614
700
 
615
- const traceId = msg.properties.headers[TRACING_HEADER];
616
- const userId = msg.properties.headers[USER_TRACING_HEADER];
617
- const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
701
+ const traceId = msg.properties.headers![TRACING_HEADER];
702
+ const userId = msg.properties.headers![USER_TRACING_HEADER];
703
+ const automationId = msg.properties.headers![AUTOMATION_ID_HEADER];
618
704
  const parsedMessage = RabbitMq.parseMsg(msg);
619
705
  const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
620
706
  const trace = newTrace(traceTypes.RABBIT);
@@ -691,22 +777,45 @@ class RabbitMq implements IAfRabbitMq {
691
777
  });
692
778
  }
693
779
 
694
- async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
780
+ // Used by the microservices to consume messages from the exchange
781
+ async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
695
782
  const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
696
783
  RabbitMq.validateName('exchange', exchange);
697
784
  RabbitMq.validateName('queue', queue);
698
785
  const { limit, deadMessageTtl } = optionsWithDefaults;
699
- await this.saveConsumer(queue, callback, options);
700
- const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
786
+ // TODO: [QUORUM-PHASE-3] Delete the if statement after all the queues are created as quorum queues
787
+ if (options?.isQuorumQueue !== false) {
788
+ await this.assertVHost();
789
+ await this.saveConsumer(queue, callback, options);
790
+ const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
791
+
792
+ await channel.addSetup(async (c: ConfirmChannel) => {
793
+ const assertExchange = await assertExchangeFanout(c, exchange);
794
+ await c.assertQueue(queue);
795
+ this.exchanges[exchange] = assertExchange;
796
+ await c.prefetch(limit, false);
797
+ return Promise.all([
798
+ c.bindQueue(queue, exchange, ''),
799
+ this.consumeNew(
800
+ queue,
801
+ callback,
802
+ options,
803
+ ),
804
+ ]);
805
+ });
806
+ }
701
807
 
702
- return channel.addSetup(async (c: ConfirmChannel) => {
808
+ // TODO: [QUORUM-PHASE-3] Delete the old implementation
809
+ await this.saveConsumerOld(queue, callback, options);
810
+ const channelOld: ChannelWrapper = await this.getNewChannelOld({ name: `consume-exchange-${exchange}-queue-${queue}-old` });
811
+ await channelOld.addSetup(async (c: ConfirmChannel) => {
703
812
  const assertExchange = await assertExchangeFanout(c, exchange);
704
813
  await c.assertQueue(queue);
705
- this.exchanges[exchange] = assertExchange;
814
+ this.oldExchanges[exchange] = assertExchange;
706
815
  await c.prefetch(limit, false);
707
816
  return Promise.all([
708
817
  c.bindQueue(queue, exchange, ''),
709
- this.consume(
818
+ this.consumeOld(
710
819
  queue,
711
820
  callback,
712
821
  options,
@@ -715,18 +824,21 @@ class RabbitMq implements IAfRabbitMq {
715
824
  });
716
825
  }
717
826
 
718
- async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
719
- debug('rabbit: start publish msg');
827
+ // Used by the microservices to publish messages to the exchange
828
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
829
+ async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
720
830
  return wrapSetImmediate(async () => {
721
831
  RabbitMq.validateName('exchange', exchange);
722
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
723
- await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
832
+ const channel: ChannelWrapper = await this.assertChannelOld();
833
+ await this.assertExchangeOld(exchange);
724
834
  await channel.publish(exchange, '',
725
835
  Buffer.from(JSON.stringify(content)),
726
836
  RabbitMq.getPublishOptions(customHeaders));
727
837
  });
728
838
  }
729
839
 
840
+ // Used by the microservices to send messages to the queue
841
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
730
842
  async sendToQueue(
731
843
  queue: string,
732
844
  content: any,
@@ -734,7 +846,7 @@ class RabbitMq implements IAfRabbitMq {
734
846
  customHeaders?: any,
735
847
  ): Promise<boolean | undefined> {
736
848
  try {
737
- await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
849
+ await this.assertChannelOld();
738
850
  } catch (e) {
739
851
  logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
740
852
  throw e;
@@ -742,61 +854,57 @@ class RabbitMq implements IAfRabbitMq {
742
854
 
743
855
  try {
744
856
  RabbitMq.validateName('queue', queue);
745
- await this.assertQueue(queue, options);
857
+ await this.assertQueueOld(queue, options);
746
858
  } catch (e) {
747
859
  logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
748
860
  throw e;
749
861
  }
750
862
 
751
863
  try {
752
- const res = await this.publishChannel?.sendToQueue(queue,
864
+ const res = await this.oldChannel?.sendToQueue(queue,
753
865
  Buffer.from(JSON.stringify(content)),
754
866
  RabbitMq.getPublishOptions(customHeaders));
755
867
  debug(`rabbit: sending to queue ${queue}`, { res });
756
868
  return res;
757
869
  } catch (e) {
758
- logger.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
870
+ const isConnected = await this.isConnected();
871
+ logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
759
872
  throw e;
760
873
  }
761
874
  }
762
875
 
763
- async isConnected(): Promise<boolean> {
764
- debug('rabbit: start is connected');
765
- const isEachConnectionConnected = await Promise.all(
766
- Object.keys(this.connectionsMap).map(async (connectionPurpose) => {
767
- const connection = await this.getConnection(connectionPurpose as ConnectionPurpose);
768
- const isConnected = connection?.isConnected();
769
- if (!isConnected) {
770
- logger.error('rabbit: isConnected - false', { connectionPurpose });
771
- return false;
772
- }
773
-
774
- if (connectionPurpose === ConnectionPurpose.Publish) {
775
- const channel: any = await this.assertChannel({ connectionPurpose: connectionPurpose as ConnectionPurpose });
776
- try {
777
- await Promise.all([
778
- channel.waitForConnect(),
779
- ...this.consumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
780
- ]);
781
- } catch (e) {
782
- logger.error('rabbit: isConnected - false');
783
- return false;
784
- }
785
- }
786
- logger.info('rabbit: isConnected - true', { connectionPurpose });
787
- return true;
788
- }),
789
- );
790
- return isEachConnectionConnected.every((isConnected) => isConnected === true);
876
+ // TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
877
+ async isConnected() : Promise<boolean> {
878
+ const connection = await this.getConnectionOld();
879
+ const isConnected = connection.isConnected();
880
+ if (!isConnected) {
881
+ logger.error('rabbit: isConnected - false');
882
+ return false;
883
+ }
884
+ const channel: any = await this.assertChannelOld();
885
+ try {
886
+ await Promise.all([
887
+ channel.waitForConnect(),
888
+ ...this.oldConsumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
889
+ ]);
890
+ } catch (e) {
891
+ logger.error('rabbit: isConnected - false');
892
+ return false;
893
+ }
894
+ logger.info('rabbit: isConnected - true');
895
+ return true;
791
896
  }
792
897
 
793
- async gracefulShutdown(signal: string): Promise<void> {
794
- const tagsNumber = this.consumersTags.length;
898
+ async gracefulShutdown(signal: string) : Promise<void> {
899
+ // TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
900
+ const tagsNumber = this.consumersTags.length + this.oldConsumersTags.length;
795
901
  logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
796
902
  const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
903
+ const cancelTagPromisesOld = this.oldConsumersTags.map(([channel, tag]) => channel.cancel(tag));
797
904
  // Clean the array to avoid race
798
905
  this.consumersTags = [];
799
- const results = await Promise.allSettled(cancelTagPromises);
906
+ this.oldConsumersTags = [];
907
+ const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
800
908
  const rejected = results.filter((p) => p.status === 'rejected');
801
909
  if (rejected.length > 0) {
802
910
  logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
@@ -804,6 +912,346 @@ class RabbitMq implements IAfRabbitMq {
804
912
  logger.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
805
913
  }
806
914
  }
915
+
916
+ private async consumeFromRabbitOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
917
+ const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
918
+ RabbitMq.validateName('queue', queue);
919
+ this.saveConsumerOld(queue, callback, options);
920
+ const uniqueId = randomUUID();
921
+ const {
922
+ limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace,
923
+ } = optionsWithDefaults;
924
+ if (useConsumeWithLock) {
925
+ if (!this.redisLock) {
926
+ throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
927
+ }
928
+ logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
929
+ }
930
+ const channel = await this.getNewChannelOld({});
931
+ return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
932
+ const q = await this.assertQueueOld(queue, optionsWithDefaults);
933
+ await confirmChannel.prefetch(limit, false);
934
+ const { consumerTag } = await confirmChannel.consume(
935
+ queue,
936
+ async (msg: ConsumeMessageOrNull) => {
937
+ if (!msg) {
938
+ return null;
939
+ }
940
+
941
+ const traceId = msg.properties.headers![TRACING_HEADER];
942
+ const userId = msg.properties.headers![USER_TRACING_HEADER];
943
+ const automationId = msg.properties.headers![AUTOMATION_ID_HEADER];
944
+ const parsedMessage = RabbitMq.parseMsg(msg);
945
+ const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
946
+ const trace = newTrace(traceTypes.RABBIT);
947
+ // setting also outbreak trace as part of legacy code
948
+ const outbreakTrace = outbreak.newTrace(traceTypes.RABBIT);
949
+ // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
950
+ // and we don't want to fail the flow because of it
951
+ if (userId && enableRabbitTrace) {
952
+ try {
953
+ await Promise.all([
954
+ createOrSetRabbitTrace(trace, userId),
955
+ createOrSetRabbitTrace(outbreakTrace, userId),
956
+ ]);
957
+ } catch (e) {
958
+ logger.error('rabbit: failed to setRabbitTrace', { userId, e });
959
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
960
+ }
961
+ }
962
+
963
+ if (traceId) {
964
+ (trace as any)?.context?.set(TRACING_HEADER, traceId);
965
+ (outbreakTrace as any)?.context.set(TRACING_HEADER, traceId);
966
+ }
967
+
968
+ if (auditContext) {
969
+ await auditContext(queue, {
970
+ userId,
971
+ automationId,
972
+ });
973
+ }
974
+ const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
975
+ if (!shouldConsume) {
976
+ await this.unlockRedisIfNeeded(releaseLock);
977
+ return this.ack(confirmChannel, msg)(msg);
978
+ }
979
+
980
+ let messageAcked = false;
981
+ // setting the localAck function to be used in the callback
982
+
983
+ const localAck = async () => {
984
+ if (messageAcked) {
985
+ return;
986
+ }
987
+ messageAcked = true;
988
+ return this.ack(confirmChannel, msg, true, releaseLock)(msg);
989
+ };
990
+
991
+ const localNack = async (_: ConsumeMessageOrNull, nackOptions: NackOptions = {}) => {
992
+ if (messageAcked) {
993
+ return;
994
+ }
995
+ debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
996
+ messageAcked = true;
997
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
998
+ };
999
+
1000
+ try {
1001
+ await callback(
1002
+ parsedMessage,
1003
+ localAck,
1004
+ localNack,
1005
+ );
1006
+ } catch (e) {
1007
+ await localNack(msg);
1008
+ }
1009
+ }, CONSUMER_DEFAULT_OPTIONS,
1010
+ );
1011
+ if (!consumerTag) {
1012
+ logger.error(`rabbit: failed to consume from queue ${queue}`);
1013
+ } else {
1014
+ logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
1015
+ this.oldConsumersTags.push([confirmChannel, consumerTag]);
1016
+ }
1017
+ });
1018
+ }
1019
+
1020
+ // TODO: [QUORUM-PHASE-3] Delete all the function under this line (getNewChannelOld, getConnectionOld, assertQueueOld, setupQueueOld, saveConsumerOld)
1021
+ async getNewChannelOld({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
1022
+ let connection!: AmqpConnectionManager;
1023
+ try {
1024
+ connection = await this.getConnectionOld();
1025
+ } catch (e) {
1026
+ logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
1027
+ throw e;
1028
+ }
1029
+ const channel = connection.createChannel({ ...options });
1030
+ once(channel, 'close').then((args) => {
1031
+ logger.error(`rabbit: channel ${name} closed`);
1032
+ onClose?.(args);
1033
+ });
1034
+ try {
1035
+ await once(channel, 'connect');
1036
+ debug(`rabbit: channel ${name} CONNECTED`);
1037
+ return channel;
1038
+ } catch (err) {
1039
+ logger.error(`rabbit: channel error ${name} error`, { err });
1040
+ throw err;
1041
+ }
1042
+ }
1043
+
1044
+ async getConnectionOld() {
1045
+ return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
1046
+ if (this.oldBlockReconnect) {
1047
+ debug('rabbit: block reconnect');
1048
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1049
+ // @ts-ignore
1050
+ return resolve();
1051
+ }
1052
+ if (this.oldConnection !== null) {
1053
+ if (this.options?.disableReconnect || this.oldConnection?.isConnected()) {
1054
+ debug('rabbit: connection - is connected');
1055
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1056
+ // @ts-ignore
1057
+ return resolve(this.oldConnection);
1058
+ }
1059
+ debug('rabbit: connection - reconnecting');
1060
+ }
1061
+ if (this.oldCreatingConnection) {
1062
+ debug('rabbit: creating connection emi');
1063
+ this.oldEm.once(CONNECTION_CREATED_CONST, resolve);
1064
+ this.oldEm.once(CONNECTION_FAILED_CONST, reject);
1065
+ return;
1066
+ }
1067
+ this.oldCreatingConnection = true;
1068
+ let isResolved = false;
1069
+
1070
+ // It is import to use it as a function and not as a variable
1071
+ // because of k8s changes the env variables
1072
+ // and we want to use the new values
1073
+ const findServers = () => {
1074
+ const userName = process.env.RABBITMQ_USERNAME || 'guest';
1075
+ const password = process.env.RABBITMQ_PASSWORD || 'guest';
1076
+ const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
1077
+
1078
+ debug('rabbit: creating connection', { host, userName, HEARTBEAT });
1079
+
1080
+ return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
1081
+ };
1082
+
1083
+ const defaultUrls = findServers();
1084
+ const connection: AmqpConnectionManager = await connect(defaultUrls, {
1085
+ findServers,
1086
+ });
1087
+
1088
+ this.oldConnection = connection;
1089
+ this.oldConnection.on('error', (err) => {
1090
+ logger.error('rabbit: connection error', { err });
1091
+ if (!isResolved) {
1092
+ isResolved = true;
1093
+ reject(err);
1094
+ this.oldEm.emit(CONNECTION_FAILED_CONST, err);
1095
+ }
1096
+ });
1097
+
1098
+ this.oldConnection.on('connectFailed', (err) => {
1099
+ this.oldConsumersTags = [];
1100
+ if (typeof err.url === 'string') {
1101
+ err.url = this.maskURL(err.url);
1102
+ }
1103
+ logger.error('rabbit: connection connectFailed', { err });
1104
+ if (!isResolved) {
1105
+ isResolved = true;
1106
+ reject(err);
1107
+ this.oldEm.emit(CONNECTION_FAILED_CONST, err);
1108
+ }
1109
+ });
1110
+
1111
+ this.oldConnection.on('disconnect', ({ err }) => {
1112
+ this.oldConsumersTags = [];
1113
+ debug('rabbit: connection closed');
1114
+ if (this.options?.disableReconnect) {
1115
+ logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
1116
+ this.oldBlockReconnect = true;
1117
+ } else {
1118
+ logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
1119
+ }
1120
+ });
1121
+
1122
+ this.oldConnection.once('connect', async () => {
1123
+ debug('rabbit: connection established');
1124
+ this.oldCreatingConnection = false;
1125
+ this.oldEm.emit(CONNECTION_CREATED_CONST, connection);
1126
+ isResolved = true;
1127
+ resolve(connection);
1128
+ });
1129
+ });
1130
+ }
1131
+
1132
+ private saveConsumerOld(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
1133
+ const isConsumerExist :boolean = this.oldConsumers.some((consumer) => consumer.queue === queue);
1134
+ if (!isConsumerExist) {
1135
+ logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
1136
+ this.oldConsumers.push({
1137
+ queue,
1138
+ callback,
1139
+ options,
1140
+ });
1141
+ }
1142
+ }
1143
+
1144
+ async assertQueueOld(queueName: string, options?: Options.AssertQueue): Promise<any> {
1145
+ RabbitMq.validateName('queue', queueName);
1146
+ if (this.oldQueues[queueName]) {
1147
+ delete this.oldQueueSetupPromises[queueName];
1148
+ return this.oldQueues[queueName];
1149
+ }
1150
+
1151
+ if (this.oldQueueSetupPromises[queueName]) {
1152
+ return this.oldQueueSetupPromises[queueName];
1153
+ }
1154
+
1155
+ this.oldQueueSetupPromises[queueName] = this.setupQueueOld(queueName, options);
1156
+ return this.oldQueueSetupPromises[queueName];
1157
+ }
1158
+
1159
+ async setupQueueOld(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
1160
+ let queue: Replies.AssertQueue;
1161
+ const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
1162
+ const localeOptions = {
1163
+ ...options,
1164
+ durable: true,
1165
+ arguments: {
1166
+ ...options?.arguments,
1167
+ 'x-consumer-timeout': 1000 * 60 * 60 * 24,
1168
+ 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
1169
+ },
1170
+ };
1171
+ try {
1172
+ const channel: ChannelWrapper = await this.assertChannelOld();
1173
+ debug('assertQueue->channel.addSetup', { queueName });
1174
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
1175
+ debug('assertQueue->channel.assertQueue', { queueName });
1176
+ queue = await channel.assertQueue(queueName, localeOptions);
1177
+ } catch (e) {
1178
+ logger.error('rabbit: assertQueue error', { queueName, options, error: e });
1179
+ if (!this.options?.dontRetryAssert) {
1180
+ debug('retrying assertQueue', { queueName });
1181
+ const channel = await this.assertChannelOld({ force: true });
1182
+ await this.deleteQueueOld(queueName);
1183
+
1184
+ debug('retrying assertQueue->channel.addSetup', { queueName });
1185
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
1186
+ debug('retrying assertQueue->channel.assertQueue', { queueName });
1187
+ queue = await channel.assertQueue(queueName, localeOptions);
1188
+ } else {
1189
+ throw e;
1190
+ }
1191
+ }
1192
+
1193
+ this.oldQueues[queueName] = queue;
1194
+ return queue;
1195
+ }
1196
+
1197
+ async assertChannelOld({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
1198
+ if (!this.oldPublishChannelSetupPromise) {
1199
+ this.oldPublishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
1200
+ if (this.oldChannel && !force) {
1201
+ return resolve(this.oldChannel);
1202
+ }
1203
+
1204
+ try {
1205
+ const channel = await this.getNewChannelOld({});
1206
+ channel.on('error', (err) => {
1207
+ logger.error('rabbit: channel error', { err });
1208
+ });
1209
+ this.oldChannel = channel;
1210
+ resolve(channel);
1211
+ } catch (e) {
1212
+ reject(e);
1213
+ }
1214
+ });
1215
+ }
1216
+ return this.oldPublishChannelSetupPromise;
1217
+ }
1218
+
1219
+ private async deleteQueueOld(queue: string) {
1220
+ RabbitMq.validateName('queue', queue);
1221
+ const channel: ChannelWrapper = await this.assertChannelOld();
1222
+ logger.info('rabbit: deleting queue', { queue });
1223
+ const deleteQueueRes = await channel.deleteQueue(queue);
1224
+ debug('queue deleted', deleteQueueRes);
1225
+ return deleteQueueRes;
1226
+ }
1227
+
1228
+ async assertExchangeOld(exchangeName: string, options?: any) {
1229
+ const channel: ChannelWrapper = await this.assertChannelOld();
1230
+
1231
+ if (this.oldExchanges[exchangeName]) {
1232
+ delete this.oldAssertExchangePromises[exchangeName];
1233
+ return this.oldExchanges[exchangeName];
1234
+ }
1235
+
1236
+ if (this.oldAssertExchangePromises[exchangeName]) {
1237
+ return this.oldAssertExchangePromises[exchangeName];
1238
+ }
1239
+
1240
+ this.oldAssertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
1241
+ this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
1242
+ return this.oldExchanges[exchangeName];
1243
+ }
1244
+
1245
+ private maskURL = (url: string): string => {
1246
+ try {
1247
+ const urlObj = new URL(url);
1248
+ urlObj.username = '***';
1249
+ urlObj.password = '***';
1250
+ return urlObj.toString();
1251
+ } catch {
1252
+ return url;
1253
+ }
1254
+ }
807
1255
  }
808
1256
 
809
1257
  export default RabbitMq;