coffee-core
Version:
Coffee IT API core library
178 lines • 6.86 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.RabbitMQSubscriber = void 0;
const common_1 = require("@nestjs/common");
const amqplib_1 = require("amqplib");
const consume_handler_1 = require("./consume-handler");
class RabbitMQSubscriber {
config;
static instance;
channel;
server;
createdQueue;
consumeHandlers = [];
usingRandomQueueName;
queueName;
logger = new common_1.Logger(RabbitMQSubscriber.name);
eventRetryTimeoutSeconds = 2 * 1000;
consumeTimeoutSeconds = 5 * 1000;
autoAcknowledge;
isExclusive;
autoDeleteQueue;
isConsuming = false;
constructor(config) {
this.config = config;
this.queueName = config.QUEUE_NAME;
this.usingRandomQueueName = this.queueName === '';
this.autoDeleteQueue = this.queueName === '';
this.autoAcknowledge =
config.AUTO_ACKNOWLEDGE === undefined ? false : config.AUTO_ACKNOWLEDGE;
this.isExclusive =
config.EXCLUSIVE_QUEUES === undefined ? true : config.EXCLUSIVE_QUEUES;
}
async subscribe(bindingKey, onConsume, messageClass, enableLogging = true) {
let channel;
try {
channel = await this.getChannel();
}
catch (err) {
this.logger.error(`Got an error when subscribing for key ${bindingKey}`);
setTimeout(() => {
this.logger.debug(`Retrying subscribe after channel error for key ${bindingKey}`);
this.subscribe(bindingKey, onConsume, messageClass);
}, this.eventRetryTimeoutSeconds);
return;
}
channel.on('close', () => {
this.logger.debug(`Channel closed for key ${bindingKey}`);
this.subscribe(bindingKey, onConsume, messageClass);
});
try {
await this.startEventSubscribe(channel, bindingKey, onConsume, messageClass, enableLogging);
}
catch (err) {
this.logger.error(`Got an error when consuming for key ${bindingKey}`);
setTimeout(() => {
this.logger.debug(`Retrying subscribe after consume error for key ${bindingKey}`);
this.subscribe(bindingKey, onConsume, messageClass, enableLogging);
}, this.eventRetryTimeoutSeconds);
}
}
async startEventSubscribe(channel, bindingKey, onConsume, messageClass, enableLogging) {
this.consumeHandlers.push(new consume_handler_1.ConsumeHandler(bindingKey, onConsume, messageClass, enableLogging));
const exchangeName = this.config.EXCHANGE_NAME;
await channel.assertExchange(exchangeName, this.config.EXCHANGE_TYPE, {
durable: this.config.DURABLE_EXCHANGE,
});
const queueOptions = {
durable: this.config.DURABLE_QUEUES,
autoDelete: this.autoDeleteQueue,
};
if (this.usingRandomQueueName && !this.createdQueue) {
this.createdQueue = await channel.assertQueue(this.queueName, queueOptions);
}
if (!this.usingRandomQueueName) {
this.createdQueue = await channel.assertQueue(this.queueName, queueOptions);
}
await channel.bindQueue(this.createdQueue.queue, exchangeName, bindingKey);
this.logger.debug(`Subscriber is now consuming on ${bindingKey}`);
}
async startReceivingMessages() {
if (this.isConsuming) {
return;
}
let channel;
try {
channel = await this.getChannel();
}
catch (err) {
this.logger.error('Got an error while getting the channel to receive messages.');
setTimeout(() => {
this.startReceivingMessages();
}, this.consumeTimeoutSeconds);
return;
}
this.isConsuming = true;
await channel.consume(this.queueName, async (msg) => {
const { content } = msg;
if (!content) {
return;
}
try {
const { routingKey } = msg.fields;
const consumeHandler = this.findConsumeHandler(routingKey);
if (!consumeHandler) {
this.logger.debug(`Could not find consume handler for routingKey: ${routingKey}. Message lost.`);
const requeue = false;
channel.nack(msg, false, requeue);
return;
}
const messageObject = JSON.parse(content.toString());
await consumeHandler.consume(messageObject);
channel.ack(msg);
}
catch (err) {
this.logger.error(`Got an error when consuming event from queue ${this.queueName}: ${err}`);
}
}, { noAck: this.autoAcknowledge, exclusive: this.isExclusive });
}
findConsumeHandler(routingKey) {
return this.consumeHandlers.find((handler) => handler.bindingKey === routingKey);
}
async close() {
if (this.channel) {
await this.channel.close();
}
}
setValidationMiddleware(middleware) {
this.consumeHandlers.forEach((handler) => handler.setValidationMiddleware(middleware));
}
async getChannel() {
if (this.channel) {
return this.channel;
}
const server = await this.getServer();
this.channel = await server.createChannel();
this.channel.on('close', () => {
this.logger.debug('Channel closed');
this.channel = undefined;
if (this.isConsuming) {
this.isConsuming = false;
this.startReceivingMessages();
}
this.isConsuming = false;
});
this.channel.on('err', () => {
this.logger.debug('Channel got an error');
this.channel = undefined;
this.isConsuming = false;
});
return this.channel;
}
async getServer() {
if (this.server) {
return this.server;
}
this.server = await this.connect();
this.server.on('close', () => {
this.logger.debug('RabbitMQ server closed');
this.server = undefined;
});
this.server.on('err', () => {
this.logger.debug('RabbitMQ server got an error');
this.server = undefined;
});
return this.server;
}
async connect() {
return (0, amqplib_1.connect)(this.config.AMQP_CONNECTION_URI);
}
static getInstance(config) {
if (!RabbitMQSubscriber.instance) {
RabbitMQSubscriber.instance = new RabbitMQSubscriber(config);
}
return RabbitMQSubscriber.instance;
}
}
exports.RabbitMQSubscriber = RabbitMQSubscriber;
//# sourceMappingURL=rabbitmq-subscriber.js.map