UNPKG

trade360-nodejs-sdk

Version:

LSports Trade360 SDK for Node.js

178 lines 7.25 kB
"use strict"; const amqplib_1 = require("amqplib"); const lodash_1 = require("lodash"); const adapters_1 = require("../../logger/adapters"); const errors_1 = require("../../entities/errors"); const _utilities_1 = require("../../utilities"); const message_consumer_1 = require("./message-consumer"); /** * Class that represent all the abilities of rabbitmq instance * to consume messages and handle them with all the configured * desired handlers for entities types and process them with * the entityHandler call-back function for the entityConstructor * class type entity type and process them with the entityHandler * call-back function for the entityConstructor class type entity type * @implements IFeed interface that represent Feed implementation for * the feed service to consume messages, process connect creation, * attach listeners for reconnection and consumption process and * handle them with all the configured desired handlers for entities * types */ class RabbitMQFeed { constructor(mqSettings, logger = new adapters_1.ConsoleAdapter()) { this.mqSettings = mqSettings; this.logger = logger; this.requestQueue = 'ReqQueue'; this.isConnected = false; this.stopTryReconnect = false; this.reconnectionLock = new _utilities_1.AsyncLock(); this.isReconnecting = false; this.requestQueue = `_${this.mqSettings.packageId}_`; this.stopTryReconnect = false; this.consumer = new message_consumer_1.MessageConsumer(this.logger); } setLogger(logger) { this.logger = logger; } getLogger() { return this.logger; } async start() { await this.connect(); await this.attachListeners(); const { prefetchCount, autoAck, consumptionLatencyThreshold } = this.mqSettings; // config and define queue prefetch await this.channel.prefetch(prefetchCount, false); const isAutoAck = autoAck; const { consumerTag } = await this.channel.consume(this.requestQueue, async (msg) => { if (!(0, lodash_1.isNil)(msg) && !(0, lodash_1.isNil)(msg.content)) { try { const { content, properties } = msg; await this.consumer.handleBasicMessage(content, { messageMqTimestamp: this.getMessageMqTimestamp(properties), consumptionLatencyThreshold, }); // Manually acknowledge the processed message if (!isAutoAck) await this.channel.ack(msg); } catch (err) { if (!isAutoAck) await this.channel.nack(msg, false, true); throw new errors_1.ConsumptionMessageError(`Error processing message, Error: ${err}`); } } }, { noAck: isAutoAck, }); this.consumerTag = consumerTag; } /** * establish connection to rabbitmq */ async connect() { if (this.isConnected && this.channel) return; const { hostname, port, username, password, vhost } = this.mqSettings; try { this.logger.info('initialize connection to RabbitMQ'); // Establish connection to RabbitMQ server const connectionString = encodeURI(`amqp://${username}:${password}@${hostname}:${port}/${vhost}`); this.connection = await (0, amqplib_1.connect)(connectionString); if (!this.connection) { throw new Error('failed initializing connection!'); } this.logger.info(`connection initialized successfully!\nconnectionString: ${connectionString}\nListen to ${this.requestQueue} queue`); // create channel through the connection this.channel = await this.connection.createChannel(); this.isConnected = true; } catch (err) { this.logger.error(`failed initialize connection to RabbitMQ, Error: ${err}`); throw err; } } /** * attach listeners for desire invoked events */ async attachListeners() { this.connection.on('error', await this.connectionErrorHandler.bind(this)); this.connection.on('close', await this.connectionClosedHandler.bind(this)); } /** * handle close event invoke for rabbitmq instance * handle reconnection option */ async connectionClosedHandler() { this.logger.debug('RabbitMQ connection was closed!'); this.isConnected = false; const { automaticRecoveryEnabled, networkRecoveryIntervalInMs, maxRetryAttempts } = this.mqSettings; const options = { maxAttempts: maxRetryAttempts, delayMs: networkRecoveryIntervalInMs, backoffFactor: 1, }; if (this.stopTryReconnect || this.isConnected || !automaticRecoveryEnabled) { return; } // Retry establish connection after a delay await this.reconnectionLock.acquire(); try { if (this.isReconnecting) return; // Already reconnecting, exit to prevent multiple attempts this.isReconnecting = true; return await (0, _utilities_1.withRetry)(async () => { await this.start(); }, options, 'Retry establish connection after a delay', this.logger); } finally { this.isReconnecting = false; this.reconnectionLock.release(); } } /** * handle error event invoke for rabbitmq instance * connection error * @param err error been thrown from the connection * instance of rabbitmq instance connection error * event invoke handler method call back function * @returns void */ connectionErrorHandler(err) { this.logger.error(err.message); } /** * get the message timestamp from the message properties * @param msgProperties the message properties * @returns the message timestamp in milliseconds */ getMessageMqTimestamp(msgProperties) { if ((0, lodash_1.isNil)(msgProperties)) return; const { timestamp, headers } = msgProperties; const timestampInMs = headers?.timestamp_in_ms; if (!(0, lodash_1.isNil)(timestampInMs)) return timestampInMs; if (typeof timestamp === 'number') { // RabbitMQ timestamps are in seconds, so convert to milliseconds return timestamp * 1000; } else if (timestamp instanceof Date) { return timestamp.getTime(); } return; } async stop() { this.stopTryReconnect = true; if (this.isConnected) { await this.channel?.cancel(this.consumerTag); await this.connection?.close(); } this.logger.info('closed gracefully channel and connection to rabbitMQ!'); } async addEntityHandler(entityHandler, entityConstructor) { this.consumer.RegisterEntityHandler(entityHandler, entityConstructor); } } module.exports = RabbitMQFeed; //# sourceMappingURL=rabbitmq-feed.js.map