@autofleet/rabbit 3.3.0-beta.4 → 3.3.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.
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,8 +338,8 @@ 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 (
@@ -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,64 @@ 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
+ logger.error('rabbit: connection connectFailed', { err, advice: 'Check if the vhost exist', vhost: this.vhost });
361
432
  if (!isResolved) {
362
433
  isResolved = true;
363
434
  reject(err);
364
- this.em.emit(connectionFailedEventName, err);
435
+ this.em.emit(CONNECTION_FAILED_CONST, err);
365
436
  }
366
437
  });
367
438
 
368
- newConnection.on('disconnect', ({ err }) => {
439
+ this.connection.on('disconnect', ({ err }) => {
440
+ // this.channel = null;
369
441
  this.consumersTags = [];
370
442
  debug('rabbit: connection closed');
371
443
  if (this.options?.disableReconnect) {
372
444
  logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
373
- this.connectionsMap[connectionPurpose].blockReconnect = true;
445
+ this.blockReconnect = true;
374
446
  } else {
375
447
  logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
376
448
  }
377
449
  });
378
- newConnection.once('connect', async () => {
379
- this.connectionsMap[connectionPurpose].creatingConnection = false;
380
- this.em.emit(connectionCreatedEventName, newConnection);
450
+
451
+ this.connection.once('connect', async () => {
452
+ debug('rabbit: connection established');
453
+ this.creatingConnection = false;
454
+ this.em.emit(CONNECTION_CREATED_CONST, connection);
381
455
  isResolved = true;
382
- resolve(newConnection);
456
+ resolve(connection);
383
457
  });
384
458
  });
385
459
  }
386
460
 
387
- async getNewChannel({
388
- name = rand().toString(), onClose = null, options = {}, connectionPurpose = ConnectionPurpose.Consume,
389
- }: newChannelOpts): Promise<ChannelWrapper> {
390
- let connection!: AmqpConnectionManager | undefined | null;
461
+ async getNewChannel({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
462
+ let connection!: AmqpConnectionManager;
391
463
  try {
392
- connection = await this.getConnection(connectionPurpose);
464
+ connection = await this.getConnection();
393
465
  } catch (e) {
394
466
  logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
395
467
  throw e;
396
468
  }
397
- if (!connection) {
398
- throw new Error(`rabbit: couldnt get connection for new channel ${name}`);
399
- }
400
469
  const channel = connection.createChannel({ ...options });
401
470
  once(channel, 'close').then((args) => {
402
471
  logger.error(`rabbit: channel ${name} closed`);
@@ -412,23 +481,19 @@ class RabbitMq implements IAfRabbitMq {
412
481
  }
413
482
  }
414
483
 
415
- async assertChannel({ force = false, connectionPurpose = ConnectionPurpose.Consume }: assertChannelOpts): Promise<ChannelWrapper> {
416
- debug('rabbit: start assert channel', { connectionPurpose, publishPromise: this.publishChannelSetupPromise, channel: this.publishChannel });
484
+ async assertChannel({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
417
485
  if (!this.publishChannelSetupPromise) {
418
486
  this.publishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
419
- if (this.publishChannel && !force) {
420
- return resolve(this.publishChannel);
487
+ if (this.channel && !force) {
488
+ return resolve(this.channel);
421
489
  }
422
490
 
423
491
  try {
424
- const channel = await this.getNewChannel({ connectionPurpose });
425
- debug('rabbit: new channel got', { connectionPurpose, channel: this.publishChannel });
492
+ const channel = await this.getNewChannel({});
426
493
  channel.on('error', (err) => {
427
494
  logger.error('rabbit: channel error', { err });
428
495
  });
429
- if (connectionPurpose === ConnectionPurpose.Publish) {
430
- this.publishChannel = channel;
431
- }
496
+ this.channel = channel;
432
497
  resolve(channel);
433
498
  } catch (e) {
434
499
  reject(e);
@@ -438,8 +503,9 @@ class RabbitMq implements IAfRabbitMq {
438
503
  return this.publishChannelSetupPromise;
439
504
  }
440
505
 
441
- async assertExchange(exchangeName: string, options: any = { connectionPurpose: ConnectionPurpose.Consume }) {
442
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: options.connectionPurpose });
506
+ async assertExchange(exchangeName: string, options?: any) {
507
+ const channel: ChannelWrapper = await this.assertChannel();
508
+
443
509
  if (this.exchanges[exchangeName]) {
444
510
  delete this.assertExchangePromises[exchangeName];
445
511
  return this.exchanges[exchangeName];
@@ -454,57 +520,58 @@ class RabbitMq implements IAfRabbitMq {
454
520
  return this.exchanges[exchangeName];
455
521
  }
456
522
 
457
- async getQueueLength(queue: string, connectionPurpose: ConnectionPurpose = ConnectionPurpose.Consume): Promise<Replies.AssertQueue> {
523
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
524
+ async getQueueLength(queue: string) {
458
525
  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');
526
+ const { oldChannel: channel } = this;
527
+ if (!channel) {
528
+ throw new RabbitError('channel is not defined');
463
529
  }
464
- debug('rabbit: getting queue length', { queue, connected: connection?.isConnected() });
465
- return publishChannel?.checkQueue(queue);
530
+ debug('rabbit: getting queue length', { queue, connected: this.connection?.isConnected() });
531
+ return channel?.checkQueue(queue);
466
532
  }
467
533
 
468
- private async deleteQueue(queue: string, connectionPurpose: ConnectionPurpose) {
534
+ private async deleteQueue(queue: string) {
469
535
  RabbitMq.validateName('queue', queue);
470
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
536
+ const channel: ChannelWrapper = await this.assertChannel();
471
537
  logger.info('rabbit: deleting queue', { queue });
472
538
  const deleteQueueRes = await channel.deleteQueue(queue);
473
539
  debug('queue deleted', deleteQueueRes);
474
540
  return deleteQueueRes;
475
541
  }
476
542
 
543
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
477
544
  async bindQueue(queue: string, exchange: string) {
478
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
545
+ const channel: ChannelWrapper = await this.assertChannelOld();
479
546
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.bindQueue(queue, exchange, ''));
480
547
  return channel.bindQueue(queue, exchange, '');
481
548
  }
482
549
 
483
550
  async setupQueue(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
484
551
  let queue: Replies.AssertQueue;
485
- const connectionPurpose = ConnectionPurpose.Publish;
486
- const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
487
552
  const localeOptions = {
488
553
  ...options,
489
554
  durable: true,
490
555
  arguments: {
491
556
  ...options?.arguments,
492
557
  'x-consumer-timeout': 1000 * 60 * 60 * 24,
493
- 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
558
+ 'x-queue-type': 'quorum',
494
559
  },
495
560
  };
496
561
  try {
497
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose });
562
+ const channel: ChannelWrapper = await this.assertChannel();
498
563
  debug('assertQueue->channel.addSetup', { queueName });
499
- await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
564
+ await channel.addSetup(async (setupChannel: ConfirmChannel) => {
565
+ await setupChannel.assertQueue(queueName, localeOptions);
566
+ });
500
567
  debug('assertQueue->channel.assertQueue', { queueName });
501
568
  queue = await channel.assertQueue(queueName, localeOptions);
502
569
  } catch (e) {
503
570
  logger.error('rabbit: assertQueue error', { queueName, options, error: e });
504
571
  if (!this.options?.dontRetryAssert) {
505
572
  debug('retrying assertQueue', { queueName });
506
- const channel = await this.assertChannel({ force: true, connectionPurpose });
507
- await this.deleteQueue(queueName, connectionPurpose);
573
+ const channel = await this.assertChannel({ force: true });
574
+ await this.deleteQueue(queueName);
508
575
 
509
576
  debug('retrying assertQueue->channel.addSetup', { queueName });
510
577
  await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
@@ -519,6 +586,7 @@ class RabbitMq implements IAfRabbitMq {
519
586
  return queue;
520
587
  }
521
588
 
589
+ // TODO: [QUORUM-PHASE-3] Can be deleted after deleting the old consumers and publishers because all the queues are created us quorum queues
522
590
  static shouldUseQuorum(queueName: string): boolean {
523
591
  const envQuorumQueuesWhitelist = process.env.QUORUM_QUEUES_WHITELIST;
524
592
 
@@ -534,8 +602,7 @@ class RabbitMq implements IAfRabbitMq {
534
602
  return false;
535
603
  }
536
604
 
537
- async assertQueue(queueName: string, options?: Options.AssertQueue) {
538
- debug('rabbit: start assert queue', { queueName });
605
+ async assertQueue(queueName: string, options?: Options.AssertQueue): Promise<any> {
539
606
  RabbitMq.validateName('queue', queueName);
540
607
  if (this.queues[queueName]) {
541
608
  delete this.queueSetupPromises[queueName];
@@ -547,12 +614,11 @@ class RabbitMq implements IAfRabbitMq {
547
614
  }
548
615
 
549
616
  this.queueSetupPromises[queueName] = this.setupQueue(queueName, options);
550
- debug('rabbit: done assert queue', { queueName });
551
617
  return this.queueSetupPromises[queueName];
552
618
  }
553
619
 
554
620
  private saveConsumer(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
555
- const isConsumerExist: boolean = this.consumers.some((consumer) => consumer.queue === queue);
621
+ const isConsumerExist :boolean = this.consumers.some((consumer) => consumer.queue === queue);
556
622
  if (!isConsumerExist) {
557
623
  logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
558
624
  this.consumers.push({
@@ -563,10 +629,27 @@ class RabbitMq implements IAfRabbitMq {
563
629
  }
564
630
  }
565
631
 
632
+ // Used by the microservices to consume messages from the queue
566
633
  async consume(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
634
+ // TODO: [QUORUM-PHASE-3] Use only the implementation of consumeNew and delete consumeNew and consumeOld
635
+ if (options?.isQuorumQueue !== false) {
636
+ await this.assertVHost();
637
+ await this.consumeNew(queue, callback, options);
638
+ }
639
+
640
+ await this.consumeOld(queue, callback, options);
641
+ }
642
+
643
+ // TODO: [QUORUM-PHASE-3] Delete consumeNew we do not use it anymore
644
+ async consumeNew(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
567
645
  await this.consumeFromRabbit(queue, callback, options);
568
646
  }
569
647
 
648
+ // TODO: [QUORUM-PHASE-3] Delete consumeOld we do not use it anymore
649
+ async consumeOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
650
+ await this.consumeFromRabbitOld(queue, callback, options);
651
+ }
652
+
570
653
  private async lockRedisIfNeeded(msg: any, options: any) {
571
654
  const { properties: { headers } } = msg;
572
655
  const timestamp = headers?.creationTimestamp;
@@ -597,11 +680,11 @@ class RabbitMq implements IAfRabbitMq {
597
680
  } = optionsWithDefaults;
598
681
  if (useConsumeWithLock) {
599
682
  if (!this.redisLock) {
600
- throw new Error('Usage of consumeWithLock requires RedisInstance');
683
+ throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
601
684
  }
602
685
  logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
603
686
  }
604
- const channel = await this.getNewChannel({ connectionPurpose: ConnectionPurpose.Consume });
687
+ const channel = await this.getNewChannel({});
605
688
  return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
606
689
  const q = await this.assertQueue(queue, optionsWithDefaults);
607
690
  await confirmChannel.prefetch(limit, false);
@@ -691,22 +774,45 @@ class RabbitMq implements IAfRabbitMq {
691
774
  });
692
775
  }
693
776
 
694
- async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
777
+ // Used by the microservices to consume messages from the exchange
778
+ async consumeFromExchange(queue: string, exchange: string, callback: CallbackFunction, options?: ConsumeOptions) : Promise<any> {
695
779
  const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
696
780
  RabbitMq.validateName('exchange', exchange);
697
781
  RabbitMq.validateName('queue', queue);
698
782
  const { limit, deadMessageTtl } = optionsWithDefaults;
699
- await this.saveConsumer(queue, callback, options);
700
- const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
783
+ // TODO: [QUORUM-PHASE-3] Delete the if statement after all the queues are created as quorum queues
784
+ if (options?.isQuorumQueue !== false) {
785
+ await this.assertVHost();
786
+ await this.saveConsumer(queue, callback, options);
787
+ const channel: ChannelWrapper = await this.getNewChannel({ name: `consume-exchange-${exchange}-queue-${queue}` });
788
+
789
+ await channel.addSetup(async (c: ConfirmChannel) => {
790
+ const assertExchange = await assertExchangeFanout(c, exchange);
791
+ await c.assertQueue(queue);
792
+ this.exchanges[exchange] = assertExchange;
793
+ await c.prefetch(limit, false);
794
+ return Promise.all([
795
+ c.bindQueue(queue, exchange, ''),
796
+ this.consumeNew(
797
+ queue,
798
+ callback,
799
+ options,
800
+ ),
801
+ ]);
802
+ });
803
+ }
701
804
 
702
- return channel.addSetup(async (c: ConfirmChannel) => {
805
+ // TODO: [QUORUM-PHASE-3] Delete the old implementation
806
+ await this.saveConsumerOld(queue, callback, options);
807
+ const channelOld: ChannelWrapper = await this.getNewChannelOld({ name: `consume-exchange-${exchange}-queue-${queue}-old` });
808
+ await channelOld.addSetup(async (c: ConfirmChannel) => {
703
809
  const assertExchange = await assertExchangeFanout(c, exchange);
704
810
  await c.assertQueue(queue);
705
- this.exchanges[exchange] = assertExchange;
811
+ this.oldExchanges[exchange] = assertExchange;
706
812
  await c.prefetch(limit, false);
707
813
  return Promise.all([
708
814
  c.bindQueue(queue, exchange, ''),
709
- this.consume(
815
+ this.consumeOld(
710
816
  queue,
711
817
  callback,
712
818
  options,
@@ -715,18 +821,21 @@ class RabbitMq implements IAfRabbitMq {
715
821
  });
716
822
  }
717
823
 
718
- async publish(exchange: string, content: any, customHeaders?: any): Promise<boolean> {
719
- debug('rabbit: start publish msg');
824
+ // Used by the microservices to publish messages to the exchange
825
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
826
+ async publish(exchange: string, content: any, customHeaders?: any) : Promise<boolean> {
720
827
  return wrapSetImmediate(async () => {
721
828
  RabbitMq.validateName('exchange', exchange);
722
- const channel: ChannelWrapper = await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
723
- await this.assertExchange(exchange, { connectionPurpose: ConnectionPurpose.Publish });
829
+ const channel: ChannelWrapper = await this.assertChannelOld();
830
+ await this.assertExchangeOld(exchange);
724
831
  await channel.publish(exchange, '',
725
832
  Buffer.from(JSON.stringify(content)),
726
833
  RabbitMq.getPublishOptions(customHeaders));
727
834
  });
728
835
  }
729
836
 
837
+ // Used by the microservices to send messages to the queue
838
+ // TODO: [QUORUM-PHASE-2] Change the implementation to the new one
730
839
  async sendToQueue(
731
840
  queue: string,
732
841
  content: any,
@@ -734,7 +843,7 @@ class RabbitMq implements IAfRabbitMq {
734
843
  customHeaders?: any,
735
844
  ): Promise<boolean | undefined> {
736
845
  try {
737
- await this.assertChannel({ connectionPurpose: ConnectionPurpose.Publish });
846
+ await this.assertChannelOld();
738
847
  } catch (e) {
739
848
  logger.error(`rabbit sendToQueue: failed to send assert channel when sending to queue ${queue}`, { e });
740
849
  throw e;
@@ -742,61 +851,57 @@ class RabbitMq implements IAfRabbitMq {
742
851
 
743
852
  try {
744
853
  RabbitMq.validateName('queue', queue);
745
- await this.assertQueue(queue, options);
854
+ await this.assertQueueOld(queue, options);
746
855
  } catch (e) {
747
856
  logger.error(`rabbit sendToQueue: failed to assert queue ${queue}`, { e });
748
857
  throw e;
749
858
  }
750
859
 
751
860
  try {
752
- const res = await this.publishChannel?.sendToQueue(queue,
861
+ const res = await this.oldChannel?.sendToQueue(queue,
753
862
  Buffer.from(JSON.stringify(content)),
754
863
  RabbitMq.getPublishOptions(customHeaders));
755
864
  debug(`rabbit: sending to queue ${queue}`, { res });
756
865
  return res;
757
866
  } catch (e) {
758
- logger.error(`rabbit sendToQueue: failed to send to queue ${queue}`, { e });
867
+ const isConnected = await this.isConnected();
868
+ logger.error(`rabbit sendToQueue: failed to send to queue ${queue}, isConnected: ${isConnected}`, { e });
759
869
  throw e;
760
870
  }
761
871
  }
762
872
 
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);
873
+ // TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
874
+ async isConnected() : Promise<boolean> {
875
+ const connection = await this.getConnectionOld();
876
+ const isConnected = connection.isConnected();
877
+ if (!isConnected) {
878
+ logger.error('rabbit: isConnected - false');
879
+ return false;
880
+ }
881
+ const channel: any = await this.assertChannelOld();
882
+ try {
883
+ await Promise.all([
884
+ channel.waitForConnect(),
885
+ ...this.oldConsumers.map((c: AfConsumer) => channel.checkQueue(c.queue)),
886
+ ]);
887
+ } catch (e) {
888
+ logger.error('rabbit: isConnected - false');
889
+ return false;
890
+ }
891
+ logger.info('rabbit: isConnected - true');
892
+ return true;
791
893
  }
792
894
 
793
- async gracefulShutdown(signal: string): Promise<void> {
794
- const tagsNumber = this.consumersTags.length;
895
+ async gracefulShutdown(signal: string) : Promise<void> {
896
+ // TODO: [QUORUM-PHASE-2] After changing the publish from old to new change from the old to the new implementation
897
+ const tagsNumber = this.consumersTags.length + this.oldConsumersTags.length;
795
898
  logger.info(`rabbit: [gracefully-shutdown] received ${signal}! canceling #${tagsNumber} tags...`);
796
899
  const cancelTagPromises = this.consumersTags.map(([channel, tag]) => channel.cancel(tag));
900
+ const cancelTagPromisesOld = this.oldConsumersTags.map(([channel, tag]) => channel.cancel(tag));
797
901
  // Clean the array to avoid race
798
902
  this.consumersTags = [];
799
- const results = await Promise.allSettled(cancelTagPromises);
903
+ this.oldConsumersTags = [];
904
+ const results = await Promise.allSettled([...cancelTagPromises, ...cancelTagPromisesOld]);
800
905
  const rejected = results.filter((p) => p.status === 'rejected');
801
906
  if (rejected.length > 0) {
802
907
  logger.warn(`rabbit: [gracefully-shutdown] #${rejected.length}/${tagsNumber} tags failed to cancel: ${rejected}`);
@@ -804,6 +909,332 @@ class RabbitMq implements IAfRabbitMq {
804
909
  logger.info('rabbit: [gracefully-shutdown] all tags successfully canceled.');
805
910
  }
806
911
  }
912
+
913
+ private async consumeFromRabbitOld(queue: string, callback: CallbackFunction, options?: ConsumeOptions): Promise<any> {
914
+ const optionsWithDefaults = { ...DEFAULT_OPTIONS, ...options };
915
+ RabbitMq.validateName('queue', queue);
916
+ this.saveConsumerOld(queue, callback, options);
917
+ const uniqueId = randomUUID();
918
+ const {
919
+ limit, deadMessageTtl, useConsumeWithLock, lockTimeout, auditContext, enableRabbitTrace,
920
+ } = optionsWithDefaults;
921
+ if (useConsumeWithLock) {
922
+ if (!this.redisLock) {
923
+ throw new RabbitError('Usage of consumeWithLock requires RedisInstance');
924
+ }
925
+ logger.info(`rabbit: Consuming with lock from queue ${queue} with lockTimeout: ${lockTimeout}ms`);
926
+ }
927
+ const channel = await this.getNewChannelOld({});
928
+ return channel.addSetup(async (confirmChannel: ConfirmChannel) => {
929
+ const q = await this.assertQueueOld(queue, optionsWithDefaults);
930
+ await confirmChannel.prefetch(limit, false);
931
+ const { consumerTag } = await confirmChannel.consume(
932
+ queue,
933
+ async (msg: ConsumeMessageOrNull) => {
934
+ if (!msg) {
935
+ return null;
936
+ }
937
+
938
+ const traceId = msg.properties.headers[TRACING_HEADER];
939
+ const userId = msg.properties.headers[USER_TRACING_HEADER];
940
+ const automationId = msg.properties.headers[AUTOMATION_ID_HEADER];
941
+ const parsedMessage = RabbitMq.parseMsg(msg);
942
+ const releaseLock = await this.lockRedisIfNeeded(parsedMessage, optionsWithDefaults);
943
+ const trace = newTrace(traceTypes.RABBIT);
944
+ // setting also outbreak trace as part of legacy code
945
+ const outbreakTrace = outbreak.newTrace(traceTypes.RABBIT);
946
+ // enableRabbitTrace is a flag to protect from different flows that doesn't work with permission
947
+ // and we don't want to fail the flow because of it
948
+ if (userId && enableRabbitTrace) {
949
+ try {
950
+ await Promise.all([
951
+ createOrSetRabbitTrace(trace, userId),
952
+ createOrSetRabbitTrace(outbreakTrace, userId),
953
+ ]);
954
+ } catch (e) {
955
+ logger.error('rabbit: failed to setRabbitTrace', { userId, e });
956
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg);
957
+ }
958
+ }
959
+
960
+ if (traceId) {
961
+ (trace as any)?.context?.set(TRACING_HEADER, traceId);
962
+ (outbreakTrace as any)?.context.set(TRACING_HEADER, traceId);
963
+ }
964
+
965
+ if (auditContext) {
966
+ await auditContext(queue, {
967
+ userId,
968
+ automationId,
969
+ });
970
+ }
971
+ const shouldConsume = await this.shouldConsumeMessageByTimestamp(parsedMessage);
972
+ if (!shouldConsume) {
973
+ await this.unlockRedisIfNeeded(releaseLock);
974
+ return this.ack(confirmChannel, msg)(msg);
975
+ }
976
+
977
+ let messageAcked = false;
978
+ // setting the localAck function to be used in the callback
979
+
980
+ const localAck = async () => {
981
+ if (messageAcked) {
982
+ return;
983
+ }
984
+ messageAcked = true;
985
+ return this.ack(confirmChannel, msg, true, releaseLock)(msg);
986
+ };
987
+
988
+ const localNack = async (_: ConsumeMessageOrNull, nackOptions: NackOptions = {}) => {
989
+ if (messageAcked) {
990
+ return;
991
+ }
992
+ debug('rabbit localNack', { messageAcked, uniqueId, deliveryTag: msg.fields.deliveryTag });
993
+ messageAcked = true;
994
+ return this.nack(confirmChannel, queue, optionsWithDefaults, { messageTtl: deadMessageTtl }, msg, releaseLock)(msg, nackOptions);
995
+ };
996
+
997
+ try {
998
+ await callback(
999
+ parsedMessage,
1000
+ localAck,
1001
+ localNack,
1002
+ );
1003
+ } catch (e) {
1004
+ await localNack(msg);
1005
+ }
1006
+ }, CONSUMER_DEFAULT_OPTIONS,
1007
+ );
1008
+ if (!consumerTag) {
1009
+ logger.error(`rabbit: failed to consume from queue ${queue}`);
1010
+ } else {
1011
+ logger.info(`rabbit: adding tag ${consumerTag} to the array.`);
1012
+ this.oldConsumersTags.push([confirmChannel, consumerTag]);
1013
+ }
1014
+ });
1015
+ }
1016
+
1017
+ // TODO: [QUORUM-PHASE-3] Delete all the function under this line (getNewChannelOld, getConnectionOld, assertQueueOld, setupQueueOld, saveConsumerOld)
1018
+ async getNewChannelOld({ name = rand().toString(), onClose = null, options = {} }: newChannelOpts = {}) {
1019
+ let connection!: AmqpConnectionManager;
1020
+ try {
1021
+ connection = await this.getConnectionOld();
1022
+ } catch (e) {
1023
+ logger.error(`rabbit: error on get connection for new channel ${name} `, { e });
1024
+ throw e;
1025
+ }
1026
+ const channel = connection.createChannel({ ...options });
1027
+ once(channel, 'close').then((args) => {
1028
+ logger.error(`rabbit: channel ${name} closed`);
1029
+ onClose?.(args);
1030
+ });
1031
+ try {
1032
+ await once(channel, 'connect');
1033
+ debug(`rabbit: channel ${name} CONNECTED`);
1034
+ return channel;
1035
+ } catch (err) {
1036
+ logger.error(`rabbit: channel error ${name} error`, { err });
1037
+ throw err;
1038
+ }
1039
+ }
1040
+
1041
+ async getConnectionOld() {
1042
+ return new Promise<AmqpConnectionManager>(async (resolve, reject) => {
1043
+ if (this.oldBlockReconnect) {
1044
+ debug('rabbit: block reconnect');
1045
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1046
+ // @ts-ignore
1047
+ return resolve();
1048
+ }
1049
+ if (this.oldConnection !== null) {
1050
+ if (this.options?.disableReconnect || this.oldConnection?.isConnected()) {
1051
+ debug('rabbit: connection - is connected');
1052
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1053
+ // @ts-ignore
1054
+ return resolve(this.oldConnection);
1055
+ }
1056
+ debug('rabbit: connection - reconnecting');
1057
+ }
1058
+ if (this.oldCreatingConnection) {
1059
+ debug('rabbit: creating connection emi');
1060
+ this.oldEm.once(CONNECTION_CREATED_CONST, resolve);
1061
+ this.oldEm.once(CONNECTION_FAILED_CONST, reject);
1062
+ return;
1063
+ }
1064
+ this.oldCreatingConnection = true;
1065
+ let isResolved = false;
1066
+
1067
+ // It is import to use it as a function and not as a variable
1068
+ // because of k8s changes the env variables
1069
+ // and we want to use the new values
1070
+ const findServers = () => {
1071
+ const userName = process.env.RABBITMQ_USERNAME || 'guest';
1072
+ const password = process.env.RABBITMQ_PASSWORD || 'guest';
1073
+ const host = this.options?.rabbitHost || process.env.RABBITMQ_SERVICE_HOST || 'localhost';
1074
+
1075
+ debug('rabbit: creating connection', { host, userName, HEARTBEAT });
1076
+
1077
+ return [`amqp://${userName}:${password}@${host}?heartbeat=${HEARTBEAT}`];
1078
+ };
1079
+
1080
+ const defaultUrls = findServers();
1081
+ const connection: AmqpConnectionManager = await connect(defaultUrls, {
1082
+ findServers,
1083
+ });
1084
+
1085
+ this.oldConnection = connection;
1086
+ this.oldConnection.on('error', (err) => {
1087
+ logger.error('rabbit: connection error', { err });
1088
+ if (!isResolved) {
1089
+ isResolved = true;
1090
+ reject(err);
1091
+ this.oldEm.emit(CONNECTION_FAILED_CONST, err);
1092
+ }
1093
+ });
1094
+
1095
+ this.oldConnection.on('connectFailed', (err) => {
1096
+ this.oldConsumersTags = [];
1097
+ logger.error('rabbit: connection connectFailed', { err });
1098
+ if (!isResolved) {
1099
+ isResolved = true;
1100
+ reject(err);
1101
+ this.oldEm.emit(CONNECTION_FAILED_CONST, err);
1102
+ }
1103
+ });
1104
+
1105
+ this.oldConnection.on('disconnect', ({ err }) => {
1106
+ this.oldConsumersTags = [];
1107
+ debug('rabbit: connection closed');
1108
+ if (this.options?.disableReconnect) {
1109
+ logger.error(`${this.DISCONNECT_MSG}${err && ` - ${err}`}`);
1110
+ this.oldBlockReconnect = true;
1111
+ } else {
1112
+ logger.error(`${this.RECONNECT_MSG}${err && ` - ${err}`}`);
1113
+ }
1114
+ });
1115
+
1116
+ this.oldConnection.once('connect', async () => {
1117
+ debug('rabbit: connection established');
1118
+ this.oldCreatingConnection = false;
1119
+ this.oldEm.emit(CONNECTION_CREATED_CONST, connection);
1120
+ isResolved = true;
1121
+ resolve(connection);
1122
+ });
1123
+ });
1124
+ }
1125
+
1126
+ private saveConsumerOld(queue: string, callback: CallbackFunction, options: ConsumeOptions | undefined) {
1127
+ const isConsumerExist :boolean = this.oldConsumers.some((consumer) => consumer.queue === queue);
1128
+ if (!isConsumerExist) {
1129
+ logger.info(`rabbit: consumer: ${queue} saved in consumer array`);
1130
+ this.oldConsumers.push({
1131
+ queue,
1132
+ callback,
1133
+ options,
1134
+ });
1135
+ }
1136
+ }
1137
+
1138
+ async assertQueueOld(queueName: string, options?: Options.AssertQueue): Promise<any> {
1139
+ RabbitMq.validateName('queue', queueName);
1140
+ if (this.oldQueues[queueName]) {
1141
+ delete this.oldQueueSetupPromises[queueName];
1142
+ return this.oldQueues[queueName];
1143
+ }
1144
+
1145
+ if (this.oldQueueSetupPromises[queueName]) {
1146
+ return this.oldQueueSetupPromises[queueName];
1147
+ }
1148
+
1149
+ this.oldQueueSetupPromises[queueName] = this.setupQueueOld(queueName, options);
1150
+ return this.oldQueueSetupPromises[queueName];
1151
+ }
1152
+
1153
+ async setupQueueOld(queueName: string, options?: Options.AssertQueue): Promise<Replies.AssertQueue> {
1154
+ let queue: Replies.AssertQueue;
1155
+ const shouldUseQuorum = RabbitMq.shouldUseQuorum(queueName);
1156
+ const localeOptions = {
1157
+ ...options,
1158
+ durable: true,
1159
+ arguments: {
1160
+ ...options?.arguments,
1161
+ 'x-consumer-timeout': 1000 * 60 * 60 * 24,
1162
+ 'x-queue-type': shouldUseQuorum ? 'quorum' : 'classic',
1163
+ },
1164
+ };
1165
+ try {
1166
+ const channel: ChannelWrapper = await this.assertChannelOld();
1167
+ debug('assertQueue->channel.addSetup', { queueName });
1168
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
1169
+ debug('assertQueue->channel.assertQueue', { queueName });
1170
+ queue = await channel.assertQueue(queueName, localeOptions);
1171
+ } catch (e) {
1172
+ logger.error('rabbit: assertQueue error', { queueName, options, error: e });
1173
+ if (!this.options?.dontRetryAssert) {
1174
+ debug('retrying assertQueue', { queueName });
1175
+ const channel = await this.assertChannelOld({ force: true });
1176
+ await this.deleteQueueOld(queueName);
1177
+
1178
+ debug('retrying assertQueue->channel.addSetup', { queueName });
1179
+ await channel.addSetup((setupChannel: ConfirmChannel) => setupChannel.assertQueue(queueName, localeOptions));
1180
+ debug('retrying assertQueue->channel.assertQueue', { queueName });
1181
+ queue = await channel.assertQueue(queueName, localeOptions);
1182
+ } else {
1183
+ throw e;
1184
+ }
1185
+ }
1186
+
1187
+ this.oldQueues[queueName] = queue;
1188
+ return queue;
1189
+ }
1190
+
1191
+ async assertChannelOld({ force = false } : assertChannelOpts = {}): Promise<ChannelWrapper> {
1192
+ if (!this.oldPublishChannelSetupPromise) {
1193
+ this.oldPublishChannelSetupPromise = new Promise<ChannelWrapper>(async (resolve, reject) => {
1194
+ if (this.oldChannel && !force) {
1195
+ return resolve(this.oldChannel);
1196
+ }
1197
+
1198
+ try {
1199
+ const channel = await this.getNewChannelOld({});
1200
+ channel.on('error', (err) => {
1201
+ logger.error('rabbit: channel error', { err });
1202
+ });
1203
+ this.oldChannel = channel;
1204
+ resolve(channel);
1205
+ } catch (e) {
1206
+ reject(e);
1207
+ }
1208
+ });
1209
+ }
1210
+ return this.oldPublishChannelSetupPromise;
1211
+ }
1212
+
1213
+ private async deleteQueueOld(queue: string) {
1214
+ RabbitMq.validateName('queue', queue);
1215
+ const channel: ChannelWrapper = await this.assertChannelOld();
1216
+ logger.info('rabbit: deleting queue', { queue });
1217
+ const deleteQueueRes = await channel.deleteQueue(queue);
1218
+ debug('queue deleted', deleteQueueRes);
1219
+ return deleteQueueRes;
1220
+ }
1221
+
1222
+ async assertExchangeOld(exchangeName: string, options?: any) {
1223
+ const channel: ChannelWrapper = await this.assertChannelOld();
1224
+
1225
+ if (this.oldExchanges[exchangeName]) {
1226
+ delete this.oldAssertExchangePromises[exchangeName];
1227
+ return this.oldExchanges[exchangeName];
1228
+ }
1229
+
1230
+ if (this.oldAssertExchangePromises[exchangeName]) {
1231
+ return this.oldAssertExchangePromises[exchangeName];
1232
+ }
1233
+
1234
+ this.oldAssertExchangePromises[exchangeName] = assertExchangeFanout(channel, exchangeName);
1235
+ this.oldExchanges[exchangeName] = await this.oldAssertExchangePromises[exchangeName];
1236
+ return this.oldExchanges[exchangeName];
1237
+ }
807
1238
  }
808
1239
 
809
1240
  export default RabbitMq;