├── VERSION ├── config ├── paramErrorConfig.json ├── coreConstant.js └── apiErrorConfig.json ├── lib ├── formatter │ └── response.js ├── util.js ├── logger │ └── customConsoleLogger.js ├── validator │ └── init.js └── rabbitmq │ ├── helper.js │ └── connection.js ├── .prettierrc.json ├── CHANGELOG.md ├── test ├── configStrategy.json └── publish.js ├── services ├── localEmitter.js ├── publishEvent.js ├── rmqSubscribe │ ├── all.js │ ├── topic.js │ └── Base.js ├── subscribeEvent.js └── rmqPublish │ ├── all.js │ └── topic.js ├── LICENSE ├── .gitignore ├── package.json ├── index.js └── README.md /VERSION: -------------------------------------------------------------------------------- 1 | 2.0.0 -------------------------------------------------------------------------------- /config/paramErrorConfig.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /lib/formatter/response.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Response formatter 3 | * 4 | * @module lib/formatter/response 5 | */ 6 | 7 | const Base = require('@truesparrow/base'), 8 | responseHelper = new Base.responseHelper({ moduleName: 'Queue' }); 9 | 10 | module.exports = responseHelper; 11 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "arrowParens": "always", 10 | "parser": "flow", 11 | "proseWrap": "preserve" 12 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Queue v2.0.0 2 | - Republished package under @truesparrow 3 | 4 | ## Queue v1.0.1 5 | - LICENSE changes. 6 | - README changes. 7 | 8 | ## Queue v1.0.0 9 | - True Sparrow Queue is implemented to publish critical events using EventEmitter and RabbitMQ. 10 | 11 | -------------------------------------------------------------------------------- /test/configStrategy.json: -------------------------------------------------------------------------------- 1 | { 2 | "enableRabbitmq":"1", 3 | "rabbitmq": { 4 | "username": "guest", 5 | "password": "guest", 6 | "host": "127.0.0.1", 7 | "port": "5672", 8 | "heartbeats": "30", 9 | "clusterNodes": ["127.0.0.1"], 10 | "switchHostAfterSec": "", 11 | "connectionTimeout": "60" 12 | } 13 | } -------------------------------------------------------------------------------- /services/localEmitter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Singleton class to manage the local emitter. 3 | * 4 | * @module services/localEmitter 5 | */ 6 | 7 | const EventEmitter = require('events'), 8 | emitObj = new EventEmitter(); 9 | 10 | /** 11 | * Constructor for local emitter 12 | * 13 | * @constructor 14 | */ 15 | class LocalEmitter { 16 | constructor() { 17 | this.emitObj = emitObj; 18 | } 19 | } 20 | 21 | module.exports = new LocalEmitter(); 22 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility methods 3 | * 4 | * @module lib/util 5 | */ 6 | 7 | /** 8 | * Utility methods constructor 9 | * 10 | * @constructor 11 | */ 12 | class Util { 13 | constructor() {} 14 | 15 | /** 16 | * check if the value is undefined or null. Empty value is considered as present. 17 | * 18 | * @param {object} val - object to check for present 19 | * 20 | * @returns {boolean} 21 | */ 22 | valPresent(val) { 23 | return !(val === undefined || val === null); 24 | } 25 | } 26 | 27 | module.exports = new Util(); 28 | -------------------------------------------------------------------------------- /config/coreConstant.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Load all the core constants. 4 | * 5 | * @module config/coreConstant 6 | */ 7 | 8 | class CoreConstants { 9 | /** 10 | * Constructor for core constants 11 | * 12 | * @constructor 13 | */ 14 | constructor() {} 15 | 16 | /** 17 | * Get IC Namespace 18 | * 19 | * @returns {string} 20 | */ 21 | get icNameSpace() { 22 | return 'Queue'; 23 | } 24 | 25 | /** 26 | * Debug Enabled 27 | * 28 | * @returns {boolean} 29 | */ 30 | get DEBUG_ENABLED() { 31 | return process.env.DEBUG_ENABLED; 32 | } 33 | } 34 | 35 | module.exports = new CoreConstants(); 36 | -------------------------------------------------------------------------------- /lib/logger/customConsoleLogger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom console log methods. 3 | * 4 | * @module lib/logger/customConsoleLogger 5 | */ 6 | const Base = require('@truesparrow/base'); 7 | 8 | const rootPrefix = '../..', 9 | coreConstants = require(rootPrefix + '/config/coreConstant'); 10 | 11 | const Logger = Base.Logger; 12 | 13 | // Following is to ensure that INFO logs are printed when debug is off. 14 | let loggerLevel; 15 | if (1 === Number(coreConstants.DEBUG_ENABLED)) { 16 | loggerLevel = Logger.LOG_LEVELS.DEBUG; 17 | } else { 18 | loggerLevel = Logger.LOG_LEVELS.INFO; 19 | } 20 | 21 | module.exports = new Logger('truesparrow-queue', loggerLevel); 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2022 True Sparrow Systems Pvt. Ltd. 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | package-lock.json 14 | 15 | # SDK 16 | /.idea 17 | /.idea/* 18 | .DS_Store 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directory 36 | # https://docs.npmjs.com/cli/shrinkwrap#caveats 37 | node_modules 38 | 39 | # Debug log from npm 40 | npm-debug.log 41 | .vscode 42 | 43 | # Typescript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@truesparrow/queue", 3 | "version": "2.0.0", 4 | "description": "Queue helps publish critical events using EventEmitter and RabbitMQ.", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "index.js", 8 | "pre-commit": "lint-staged" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/TrueSparrowSystems/queue.git" 13 | }, 14 | "keywords": [ 15 | "True Sparrow Queue", 16 | "Event Emitter", 17 | "RabbitMQ", 18 | "PubSub" 19 | ], 20 | "author": "True Sparrow", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/TrueSparrowSystems/queue/issues" 24 | }, 25 | "homepage": "https://github.com/TrueSparrowSystems/queue#readme", 26 | "dependencies": { 27 | "@truesparrow/base": "2.0.0", 28 | "amqplib": "0.8.0", 29 | "uuid": "8.3.2" 30 | }, 31 | "devDependencies": { 32 | "chai": "4.3.0", 33 | "ink-docstrap": "1.3.2", 34 | "jsdoc-route-plugin": "0.1.0", 35 | "lint-staged": "9.4.2", 36 | "mocha": "8.4.0", 37 | "pre-commit": "1.2.2", 38 | "prettier": "1.14.3" 39 | }, 40 | "pre-commit": [ 41 | "pre-commit" 42 | ], 43 | "lint-staged": { 44 | "*.js": [ 45 | "prettier --write --config .prettierrc.json", 46 | "git add" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /services/publishEvent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Publish event to RabbitMQ. 3 | * 4 | * @module services/publishEvent 5 | */ 6 | 7 | const Base = require('@truesparrow/base'); 8 | 9 | const rootPrefix = '..', 10 | coreConstant = require(rootPrefix + '/config/coreConstant'); 11 | 12 | const InstanceComposer = Base.InstanceComposer; 13 | 14 | require(rootPrefix + '/services/rmqPublish/topic'); 15 | require(rootPrefix + '/services/rmqPublish/all'); 16 | 17 | /** 18 | * Constructor to publish RMQ event 19 | * 20 | * @constructor 21 | */ 22 | class RmqPublishEvent { 23 | constructor() {} 24 | 25 | /** 26 | * Publish to rabbitMQ and local emitter also. 27 | * 28 | * @param {object} params - event parameters 29 | * @param {array} params.topics - on which topic messages 30 | * @param {string} params.publisher - name of publisher 31 | * @param {object} params.message 32 | * @param {string} params.message.kind - kind of the message 33 | * @param {object} params.message.payload - Payload to identify message and extra info. 34 | * @param {number} [params.broadcast] - boolean to broadcast message to all channels 35 | * @param {number} [params.publishAfter] - message to be sent after miliseconds 36 | * 37 | * @return {Promise} 38 | */ 39 | async perform(params) { 40 | const oThis = this; 41 | 42 | params = params || {}; 43 | 44 | if (params['broadcast']) { 45 | let rmqBroadcastToAll = oThis.ic().getInstanceFor(coreConstant.icNameSpace, 'PublishEventToAll'); 46 | return rmqBroadcastToAll.perform(params); 47 | } else { 48 | let rmqPublishByTopic = oThis.ic().getInstanceFor(coreConstant.icNameSpace, 'RmqPublishByTopic'); 49 | return rmqPublishByTopic.perform(params); 50 | } 51 | } 52 | } 53 | 54 | InstanceComposer.registerAsObject(RmqPublishEvent, coreConstant.icNameSpace, 'publishEvent', true); 55 | 56 | module.exports = {}; 57 | -------------------------------------------------------------------------------- /services/rmqSubscribe/all.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Listening to RabbitMq channels to receive published message. 3 | * 4 | * @module services/rmqSubscribe/all 5 | */ 6 | 7 | const Base = require('@truesparrow/base'); 8 | 9 | const rootPrefix = '../..', 10 | SubscriptionBase = require(rootPrefix + '/services/rmqSubscribe/Base'), 11 | coreConstant = require(rootPrefix + '/config/coreConstant'); 12 | 13 | const InstanceComposer = Base.InstanceComposer; 14 | 15 | require(rootPrefix + '/lib/rabbitmq/connection'); 16 | 17 | /** 18 | * Constructor to subscribe RMQ event 19 | * 20 | * @constructor 21 | */ 22 | class FanoutSubscription extends SubscriptionBase { 23 | constructor() { 24 | super(); 25 | } 26 | 27 | /** 28 | * Subscribe to rabbitMq topics to receive messages. 29 | * 30 | * @param {array} topics - list of topics to receive messages. 31 | * @param {object} options - 32 | * @param {string} [options.queue] - RMQ queue name. 33 | * - Name of the queue on which you want to receive all your subscribed events. 34 | * These queues and events, published in them, have TTL of 6 days. 35 | * If queue name is not passed, a queue with unique name is created and is deleted when subscriber gets disconnected. 36 | * @param {function} readCallback - function to run on message arrived on the channel. 37 | * @param {function} subscribeCallback - function to return consumerTag. 38 | * 39 | */ 40 | async rabbit(topics, options, readCallback, subscribeCallback) { 41 | const oThis = this; 42 | 43 | options.exchangeName = 'fanout_events'; 44 | options.exchangeType = 'fanout'; 45 | return super.rabbit(topics, options, readCallback, subscribeCallback); 46 | } 47 | 48 | /** 49 | * Get Keys to Bind Queue with 50 | * 51 | * @param topics 52 | */ 53 | getQueueBindingKeys(topics) { 54 | // Queue doesn't needs to be bind to any key for Fanout. 55 | return ['']; 56 | } 57 | } 58 | 59 | InstanceComposer.registerAsObject(FanoutSubscription, coreConstant.icNameSpace, 'FanoutSubscription', true); 60 | 61 | module.exports = {}; 62 | -------------------------------------------------------------------------------- /services/rmqSubscribe/topic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Listening to RabbitMq channels to receive published message. 3 | * 4 | * @module services/rmqSubscribe/topic 5 | */ 6 | 7 | const Base = require('@truesparrow/base'); 8 | 9 | const rootPrefix = '../..', 10 | SubscriptionBase = require(rootPrefix + '/services/rmqSubscribe/Base'), 11 | logger = require(rootPrefix + '/lib/logger/customConsoleLogger'), 12 | coreConstant = require(rootPrefix + '/config/coreConstant'); 13 | 14 | const InstanceComposer = Base.InstanceComposer; 15 | 16 | require(rootPrefix + '/lib/rabbitmq/connection'); 17 | 18 | /** 19 | * Constructor to subscribe RMQ event 20 | * 21 | * @constructor 22 | */ 23 | class RmqSubscribeByTopic extends SubscriptionBase { 24 | constructor() { 25 | super(); 26 | } 27 | 28 | /** 29 | * Subscribe to rabbitMq topics to receive messages. 30 | * 31 | * @param {array} topics - list of topics to receive messages. 32 | * @param {object} options - 33 | * @param {string} [options.queue] - RMQ queue name. 34 | * - Name of the queue on which you want to receive all your subscribed events. 35 | * These queues and events, published in them, have TTL of 6 days. 36 | * If queue name is not passed, a queue with unique name is created and is deleted when subscriber gets disconnected. 37 | * @param {function} readCallback - function to run on message arrived on the channel. 38 | * @param {function} subscribeCallback - function to return consumerTag. 39 | * 40 | */ 41 | async rabbit(topics, options, readCallback, subscribeCallback) { 42 | const oThis = this; 43 | 44 | if (topics.length === 0) { 45 | logger.error('Invalid topic parameters.'); 46 | process.emit('rmq_error', 'Invalid topic parameters.'); 47 | return; 48 | } 49 | 50 | oThis.topics = topics; 51 | 52 | options.exchangeName = 'topic_events'; 53 | options.exchangeType = 'topic'; 54 | return super.rabbit(topics, options, readCallback, subscribeCallback); 55 | } 56 | 57 | /** 58 | * Get Keys to Bind Queue with 59 | * 60 | * @param topics 61 | */ 62 | getQueueBindingKeys(topics) { 63 | return topics; 64 | } 65 | } 66 | 67 | InstanceComposer.registerAsObject(RmqSubscribeByTopic, coreConstant.icNameSpace, 'RmqSubscribeByTopic', true); 68 | 69 | module.exports = {}; 70 | -------------------------------------------------------------------------------- /services/subscribeEvent.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Listening to RabbitMq channels to receive published message. 3 | * 4 | * @module services/subscribeEvent 5 | */ 6 | 7 | const Base = require('@truesparrow/base'); 8 | 9 | const rootPrefix = '..', 10 | coreConstant = require(rootPrefix + '/config/coreConstant'); 11 | 12 | const InstanceComposer = Base.InstanceComposer; 13 | 14 | require(rootPrefix + '/services/rmqSubscribe/topic'); 15 | require(rootPrefix + '/services/rmqSubscribe/all'); 16 | 17 | /** 18 | * Constructor to publish RMQ event 19 | * 20 | * @constructor 21 | */ 22 | class SubscribeEvent { 23 | constructor() {} 24 | 25 | /** 26 | * Subscribe to rabbitMq topics to receive messages. 27 | * 28 | * @param {array} topics - list of topics to receive messages. 29 | * @param {object} options - 30 | * @param {string} [options.queue] - RMQ queue name. 31 | * @param {string} [options.broadcastSubscription] - Subscribe to events which is broadcasted. 32 | * - Name of the queue on which you want to receive all your subscribed events. 33 | * These queues and events, published in them, have TTL of 6 days. 34 | * If queue name is not passed, a queue with unique name is created and is deleted when subscriber gets disconnected. 35 | * @param {function} readCallback - function to run on message arrived on the channel. 36 | * @param {function} subscribeCallback - function to return consumerTag. 37 | * 38 | */ 39 | async rabbit(topics, options, readCallback, subscribeCallback) { 40 | const oThis = this; 41 | 42 | let subOptions = JSON.parse(JSON.stringify(options)); 43 | // If Queue needs to be subscribed for broadcast events 44 | if (subOptions.broadcastSubscription == 1) { 45 | let subscribeToAll = oThis.ic().getInstanceFor(coreConstant.icNameSpace, 'FanoutSubscription'); 46 | subscribeToAll.rabbit([], subOptions, readCallback, subscribeCallback); 47 | } 48 | 49 | subOptions = JSON.parse(JSON.stringify(options)); 50 | // If topics are present then subscribe queue to particluar topics 51 | if (topics.length > 0) { 52 | let subscribeToTopics = oThis.ic().getInstanceFor(coreConstant.icNameSpace, 'RmqSubscribeByTopic'); 53 | subscribeToTopics.rabbit(topics, subOptions, readCallback, subscribeCallback); 54 | } 55 | } 56 | } 57 | 58 | InstanceComposer.registerAsObject(SubscribeEvent, coreConstant.icNameSpace, 'subscribeEvent', true); 59 | 60 | module.exports = {}; 61 | -------------------------------------------------------------------------------- /lib/validator/init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Validator service to be called to validate received message to subscriber. 3 | * 4 | * @module lib/validator/init 5 | */ 6 | 7 | const rootPrefix = '../..', 8 | responseHelper = require(rootPrefix + '/lib/formatter/response'), 9 | util = require(rootPrefix + '/lib/util'), 10 | paramErrorConfig = require(rootPrefix + '/config/paramErrorConfig'), 11 | apiErrorConfig = require(rootPrefix + '/config/apiErrorConfig'); 12 | 13 | const errorConfig = { 14 | param_error_config: paramErrorConfig, 15 | api_error_config: apiErrorConfig 16 | }; 17 | 18 | /** 19 | * Validate event parameters constructor 20 | * 21 | * @constructor 22 | */ 23 | class Init { 24 | constructor() {} 25 | 26 | /** 27 | * Perform basic validation for specific event params 28 | * 29 | * @param {object} params - event parameters 30 | * * {array} topics - on which topic messages 31 | * * {object} message - 32 | * ** {string} kind - kind of the message 33 | * ** {object} payload - Payload to identify message and extra info. 34 | * 35 | * @return {Promise} 36 | */ 37 | light(params) { 38 | let validatedParams = {}; 39 | 40 | if ( 41 | !util.valPresent(params) || 42 | !util.valPresent(params['message']) || 43 | !util.valPresent(params['topics']) || 44 | params['topics'].length === 0 || 45 | !util.valPresent(params['publisher']) 46 | ) { 47 | let errorParams = { 48 | internal_error_identifier: 's_v_i_1', 49 | api_error_identifier: 'invalid_notification_params', 50 | error_config: errorConfig, 51 | debug_options: {} 52 | }; 53 | return Promise.resolve(responseHelper.error(errorParams)); 54 | } 55 | 56 | validatedParams['topics'] = params['topics']; 57 | validatedParams['publisher'] = params['publisher']; 58 | 59 | validatedParams['message'] = {}; 60 | 61 | const message = params['message']; 62 | 63 | if (!util.valPresent(message) || !util.valPresent(message['kind']) || !util.valPresent(message['payload'])) { 64 | let errorParams = { 65 | internal_error_identifier: 's_v_i_2', 66 | api_error_identifier: 'invalid_message_params', 67 | error_config: errorConfig, 68 | debug_options: {} 69 | }; 70 | return Promise.resolve(responseHelper.error(errorParams)); 71 | } 72 | 73 | validatedParams['message']['kind'] = message['kind']; 74 | validatedParams['message']['payload'] = message['payload']; 75 | 76 | return Promise.resolve(responseHelper.successWithData(validatedParams)); 77 | } 78 | } 79 | 80 | module.exports = new Init(); 81 | -------------------------------------------------------------------------------- /services/rmqPublish/all.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Publish event to RabbitMQ using fanout exchange. 3 | * 4 | * @module services/rmqPublish/all 5 | */ 6 | 7 | const Base = require('@truesparrow/base'); 8 | 9 | const rootPrefix = '../..', 10 | apiErrorConfig = require(rootPrefix + '/config/apiErrorConfig'), 11 | paramErrorConfig = require(rootPrefix + '/config/paramErrorConfig'), 12 | responseHelper = require(rootPrefix + '/lib/formatter/response'), 13 | coreConstant = require(rootPrefix + '/config/coreConstant'); 14 | 15 | const InstanceComposer = Base.InstanceComposer; 16 | 17 | require(rootPrefix + '/lib/rabbitmq/connection'); 18 | 19 | const errorConfig = { 20 | param_error_config: paramErrorConfig, 21 | api_error_config: apiErrorConfig 22 | }, 23 | exchange = 'fanout_events'; 24 | 25 | class PublishEventToAll { 26 | constructor() {} 27 | 28 | async perform(params) { 29 | const oThis = this; 30 | 31 | if (!params['message']) { 32 | return Promise.resolve( 33 | responseHelper.error({ 34 | internal_error_identifier: 's_rp_a_2', 35 | api_error_identifier: 'invalid_message_params', 36 | error_config: errorConfig, 37 | debug_options: {} 38 | }) 39 | ); 40 | } 41 | 42 | let rabbitMqConnection = oThis.ic().getInstanceFor(coreConstant.icNameSpace, 'rabbitmqConnection'), 43 | msgString = JSON.stringify(params['message']); 44 | 45 | const conn = await rabbitMqConnection.get(); 46 | if (conn) { 47 | conn.createChannel(function(err, ch) { 48 | if (err) { 49 | let errorParams = { 50 | internal_error_identifier: 's_rp_t_3', 51 | api_error_identifier: 'cannot_create_channel', 52 | error_config: errorConfig, 53 | debug_options: { err: err } 54 | }; 55 | logger.error(err.message); 56 | return onResolve(responseHelper.error(errorParams)); 57 | } 58 | 59 | ch.assertExchange(exchange, 'fanout', { 60 | durable: true 61 | }); 62 | ch.publish(exchange, '', new Buffer(msgString), { persistent: true }); 63 | console.log(' [x] Sent message'); 64 | 65 | ch.close(); 66 | }); 67 | } else { 68 | return Promise.resolve( 69 | responseHelper.error({ 70 | internal_error_identifier: 's_rp_a_3', 71 | api_error_identifier: 'no_rmq_connection', 72 | error_config: errorConfig, 73 | debug_options: {} 74 | }) 75 | ); 76 | } 77 | 78 | return Promise.resolve(responseHelper.successWithData({ publishedToRmq: 1 })); 79 | } 80 | } 81 | 82 | InstanceComposer.registerAsObject(PublishEventToAll, coreConstant.icNameSpace, 'PublishEventToAll', true); 83 | 84 | module.exports = {}; 85 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const Base = require('@truesparrow/base'); 2 | 3 | const rootPrefix = '.', 4 | version = require(rootPrefix + '/package.json').version, 5 | rabbitmqHelper = require(rootPrefix + '/lib/rabbitmq/helper'), 6 | coreConstant = require(rootPrefix + '/config/coreConstant'); 7 | 8 | const InstanceComposer = Base.InstanceComposer; 9 | 10 | require(rootPrefix + '/lib/rabbitmq/helper'); 11 | require(rootPrefix + '/lib/rabbitmq/connection'); 12 | require(rootPrefix + '/services/publishEvent'); 13 | require(rootPrefix + '/services/subscribeEvent'); 14 | 15 | /** 16 | * Queue Manager. 17 | * 18 | * @param configStrategy 19 | * @constructor 20 | */ 21 | const QueueManager = function(configStrategy) { 22 | const oThis = this; 23 | 24 | if (!configStrategy) { 25 | throw 'Mandatory argument configStrategy missing.'; 26 | } 27 | 28 | const instanceComposer = (oThis.ic = new InstanceComposer(configStrategy)); 29 | 30 | oThis.version = version; 31 | oThis.connection = instanceComposer.getInstanceFor(coreConstant.icNameSpace, 'rabbitmqConnection'); 32 | oThis.publishEvent = instanceComposer.getInstanceFor(coreConstant.icNameSpace, 'publishEvent'); 33 | oThis.subscribeEvent = instanceComposer.getInstanceFor(coreConstant.icNameSpace, 'subscribeEvent'); 34 | }; 35 | 36 | // Instance Map to ensure that only one object is created per config strategy. 37 | const instanceMap = {}; 38 | 39 | const QueueFactory = function() {}; 40 | 41 | QueueFactory.prototype = { 42 | /** 43 | * Get an instance of QueueManager 44 | * 45 | * @param configStrategy 46 | * @returns {QueueManager} 47 | */ 48 | getInstance: function(configStrategy) { 49 | const oThis = this, 50 | rabbitMqMandatoryParams = ['username', 'password', 'host', 'port', 'heartbeats']; 51 | 52 | if (!configStrategy.hasOwnProperty('rabbitmq')) { 53 | throw 'RabbitMQ one or more mandatory connection parameters missing.'; 54 | } 55 | 56 | // Check if all the mandatory connection parameters for RabbitMQ are available or not. 57 | for (let key = 0; key < rabbitMqMandatoryParams.length; key++) { 58 | if (!configStrategy.rabbitmq.hasOwnProperty(rabbitMqMandatoryParams[key])) { 59 | throw 'RabbitMQ one or more mandatory connection parameters missing.'; 60 | } 61 | } 62 | 63 | // Check if instance already present. 64 | let instanceKey = rabbitmqHelper.getInstanceKey(configStrategy), 65 | _instance = instanceMap[instanceKey]; 66 | 67 | if (!_instance) { 68 | _instance = new QueueManager(configStrategy); 69 | instanceMap[instanceKey] = _instance; 70 | } 71 | _instance.connection.get(); 72 | 73 | return _instance; 74 | } 75 | }; 76 | 77 | const factory = new QueueFactory(); 78 | QueueManager.getInstance = function() { 79 | return factory.getInstance.apply(factory, arguments); 80 | }; 81 | 82 | module.exports = QueueManager; 83 | -------------------------------------------------------------------------------- /config/apiErrorConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "invalid_params": { 3 | "http_code": "422", 4 | "code": "invalid_params", 5 | "message": "At least one or more of the input params are invalid" 6 | }, 7 | "invalid_notification_params": { 8 | "http_code": "422", 9 | "code": "invalid_notification_params", 10 | "message": "At least one or more of the input notification params are invalid" 11 | }, 12 | "invalid_message_params": { 13 | "http_code": "422", 14 | "code": "invalid_message_params", 15 | "message": "At least one or more of the input message params are invalid" 16 | }, 17 | "invalid_payload_for_received_event": { 18 | "http_code": "422", 19 | "code": "invalid_payload_for_received_event", 20 | "message": "Invalid payload for the event received" 21 | }, 22 | "invalid_payload_transaction_init": { 23 | "http_code": "422", 24 | "code": "invalid_payload_transaction_init", 25 | "message": "Invalid payload for kind transaction initiated" 26 | }, 27 | "invalid_payload_for_email": { 28 | "http_code": "422", 29 | "code": "invalid_payload_for_email", 30 | "message": "Invalid payload for kind email" 31 | }, 32 | "invalid_payload_shared_entity": { 33 | "http_code": "422", 34 | "code": "invalid_payload_shared_entity", 35 | "message": "Invalid payload for kind shared entity" 36 | }, 37 | "invalid_payload_exec_transaction": { 38 | "http_code": "422", 39 | "code": "invalid_payload_exec_transaction", 40 | "message": "Invalid payload for kind execute transaction" 41 | }, 42 | "unsupported_message_kind": { 43 | "http_code": "422", 44 | "code": "unsupported_message_kind", 45 | "message": "Unsupported message kind. Supported are event_received,transaction_initiated,transaction_mined" 46 | }, 47 | "cannot_create_channel": { 48 | "http_code": "422", 49 | "code": "cannot_create_channel", 50 | "message": "Can't create channel in the queue" 51 | }, 52 | "no_rmq_connection": { 53 | "http_code": "422", 54 | "code": "no_rmq_connection", 55 | "message": "No rabbitmq connection found" 56 | }, 57 | "invalid_payload_for_holdWorker": { 58 | "http_code": "422", 59 | "code": "invalid_payload_for_holdWorker", 60 | "message": "Invalid payload for kind hold worker" 61 | }, 62 | "invalid_payload_for_redirectToDhq": { 63 | "http_code": "422", 64 | "code": "invalid_payload_for_redirectToDhq", 65 | "message": "Invalid payload for kind redirect to Dhq" 66 | }, 67 | "invalid_payload_for_transactionsDone": { 68 | "http_code": "422", 69 | "code": "invalid_payload_for_transactionsDone", 70 | "message": "Invalid payload for kind transactions done" 71 | }, 72 | "invalid_payload_for_command_message": { 73 | "http_code": "422", 74 | "code": "invalid_payload_for_command_message", 75 | "message": "Invalid payload for the command message" 76 | }, 77 | "unsupported_command_message_kind": { 78 | "http_code": "422", 79 | "code": "unsupported_command_message_kind", 80 | "message": "Unsupported command message kind" 81 | }, 82 | "invalid_topics": { 83 | "http_code": "422", 84 | "code": "invalid_topics_passed", 85 | "message": "invalid topics passed" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/rabbitmq/helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * RabbitMQ helper methods 3 | * 4 | * @module lib/rabbitmq/helper 5 | */ 6 | 7 | /** 8 | * RabbitMQ helper constructor 9 | * 10 | * @constructor 11 | */ 12 | class RabbitmqHelper { 13 | constructor() {} 14 | 15 | /** 16 | * No of max connections attempt in case of failure.

17 | * 18 | * @constant {number} 19 | */ 20 | get maxConnectionAttempts() { 21 | return 1 * 60 * 60; // 1 hour (in seconds) 22 | } 23 | 24 | /** 25 | * Time in milliseconds that the unused queue should be deleted after 26 | * Note: Unused means the queue has no consumers, the queue has not been re-declared, or no get message tried. 27 | * 28 | * @constant {number} 29 | */ 30 | get dedicatedQueueTtl() { 31 | return 6 * 24 * 60 * 60 * 1000; // 6 days (in miliseconds) 32 | } 33 | 34 | /** 35 | * Messages TTL (in milliseconds) in dedicated queues 36 | * 37 | * @constant {number} 38 | */ 39 | get dedicatedQueueMsgTtl() { 40 | return 6 * 24 * 60 * 60 * 1000; // 6 days (in miliseconds) 41 | } 42 | 43 | /** 44 | * Socket connection timeout (in milliseconds) 45 | * 46 | * @constant {number} 47 | */ 48 | get socketConnectionTimeout() { 49 | return 15 * 1000; //15 seconds (in miliseconds) 50 | } 51 | 52 | /** 53 | * Make connection string using configStrategy parameters. 54 | * 55 | * @returns {string} 56 | */ 57 | connectionString(configStrategy) { 58 | const oThis = this; 59 | 60 | return ( 61 | 'amqp://' + 62 | configStrategy.rabbitmq.username + 63 | ':' + 64 | configStrategy.rabbitmq.password + 65 | '@' + 66 | configStrategy.rabbitmq.host + 67 | ':' + 68 | configStrategy.rabbitmq.port + 69 | '/?heartbeat=' + 70 | configStrategy.rabbitmq.heartbeats 71 | ); 72 | } 73 | 74 | /** 75 | * 76 | * Get instance key from the configStrategy 77 | * 78 | * @returns {string} 79 | */ 80 | getRmqId(configStrategy) { 81 | const oThis = this; 82 | return [ 83 | configStrategy.rabbitmq.username, 84 | configStrategy.rabbitmq.host.toLowerCase(), 85 | configStrategy.rabbitmq.port, 86 | configStrategy.rabbitmq.heartbeats 87 | ].join('-'); 88 | } 89 | 90 | /** 91 | * 92 | * Get instance key from the configStrategy. 93 | * 94 | * 95 | * Two different instance key can have same connection. Different instance key is generated so that 96 | * for all those who want short connection timeout can get timed out on the same connection. 97 | * For example, app servers connection timeout will be very short. 98 | * 99 | * @returns {string} 100 | */ 101 | getInstanceKey(configStrategy) { 102 | const oThis = this; 103 | return oThis.getRmqId(configStrategy) + '-' + configStrategy.rabbitmq.connectionTimeoutSec; 104 | } 105 | 106 | /** 107 | * 108 | * Get RMQ host passed into configStrategy. 109 | * 110 | * @returns {string} 111 | */ 112 | getConfigRmqHost(configStrategy) { 113 | return configStrategy.rabbitmq.host.toLowerCase(); 114 | } 115 | 116 | /** 117 | * 118 | * Get RMQ host passed into configStrategy. 119 | * 120 | * @returns {string} 121 | */ 122 | getConfigRmqClusterNodes(configStrategy) { 123 | return configStrategy.rabbitmq.clusterNodes; 124 | } 125 | } 126 | 127 | module.exports = new RabbitmqHelper(); 128 | -------------------------------------------------------------------------------- /test/publish.js: -------------------------------------------------------------------------------- 1 | // Load external packages 2 | const chai = require('chai'), 3 | assert = chai.assert; 4 | 5 | // Load queue manager service 6 | const rootPrefix = '..', 7 | Queue = require(rootPrefix + '/index'), 8 | configStrategy = require(rootPrefix + '/test/configStrategy.json'); 9 | 10 | require(rootPrefix + '/lib/rabbitmq/connection'); 11 | require(rootPrefix + '/services/publishEvent'); 12 | 13 | const getParams = function() { 14 | return { 15 | topics: ['events.transfer'], 16 | message: { 17 | kind: 'event_received', 18 | payload: { 19 | event_name: 'one event of st m', 20 | params: { id: 'hello...' }, 21 | contract_address: 'address' 22 | } 23 | } 24 | }; 25 | }; 26 | 27 | // Create connection. 28 | const queueManagerInstance = Queue.getInstance(configStrategy); 29 | 30 | describe('Publishing to rabbitMq', async function() { 31 | it('should return promise', async function() { 32 | let params = getParams(), 33 | response = queueManagerInstance.publishEvent.perform(params); 34 | 35 | assert.typeOf(response, 'Promise'); 36 | }); 37 | 38 | it('should fail when empty params are passed', async function() { 39 | let params = {}, 40 | response = await queueManagerInstance.publishEvent.perform(params); 41 | 42 | assert.equal(response.isSuccess(), false); 43 | }); 44 | 45 | it('should fail when no params are passed', async function() { 46 | let response = await queueManagerInstance.publishEvent.perform(); 47 | 48 | assert.equal(response.isSuccess(), false); 49 | }); 50 | 51 | it('should fail when params dont have topics', async function() { 52 | let params = getParams(); 53 | delete params['topics']; 54 | 55 | let response = await queueManagerInstance.publishEvent.perform(params); 56 | 57 | assert.equal(response.isSuccess(), false); 58 | }); 59 | 60 | it('should fail when params dont have message', async function() { 61 | let params = getParams(); 62 | delete params['message']; 63 | 64 | let response = await queueManagerInstance.publishEvent.perform(params); 65 | assert.equal(response.isSuccess(), false); 66 | }); 67 | 68 | it('should fail when params message dont have kind', async function() { 69 | let params = getParams(); 70 | delete params['message']['kind']; 71 | 72 | let response = await queueManagerInstance.publishEvent.perform(params); 73 | assert.equal(response.isSuccess(), false); 74 | }); 75 | 76 | it('should fail when params message dont have payload', async function() { 77 | let params = getParams(); 78 | delete params['message']['payload']; 79 | 80 | let response = await queueManagerInstance.publishEvent.perform(params); 81 | assert.equal(response.isSuccess(), false); 82 | }); 83 | 84 | it('should fail when params message payload dont have event_name', async function() { 85 | let params = getParams(); 86 | delete params['message']['payload']['event_name']; 87 | 88 | let response = await queueManagerInstance.publishEvent.perform(params); 89 | assert.equal(response.isSuccess(), false); 90 | }); 91 | 92 | it('should fail when params message payload dont have params', async function() { 93 | let params = getParams(); 94 | delete params['message']['payload']['params']; 95 | 96 | let response = await queueManagerInstance.publishEvent.perform(params); 97 | assert.equal(response.isSuccess(), false); 98 | }); 99 | 100 | it('should fail when params message payload dont have contract_address', async function() { 101 | let params = getParams(); 102 | delete params['message']['payload']['contract_address']; 103 | 104 | let response = await queueManagerInstance.publishEvent.perform(params); 105 | assert.equal(response.isSuccess(), false); 106 | }); 107 | 108 | it('should fail when unsupported kind is passed', async function() { 109 | let params = getParams(); 110 | params['message']['kind'] = 'abcd'; 111 | 112 | let response = await queueManagerInstance.publishEvent.perform(params); 113 | assert.equal(response.isSuccess(), false); 114 | }); 115 | }); 116 | -------------------------------------------------------------------------------- /services/rmqPublish/topic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Publish event to RabbitMQ. 3 | * 4 | * @module services/rmqPublish/topic 5 | */ 6 | 7 | const Base = require('@truesparrow/base'); 8 | 9 | const rootPrefix = '../..', 10 | validator = require(rootPrefix + '/lib/validator/init'), 11 | localEmitter = require(rootPrefix + '/services/localEmitter'), 12 | responseHelper = require(rootPrefix + '/lib/formatter/response'), 13 | apiErrorConfig = require(rootPrefix + '/config/apiErrorConfig'), 14 | logger = require(rootPrefix + '/lib/logger/customConsoleLogger'), 15 | paramErrorConfig = require(rootPrefix + '/config/paramErrorConfig'), 16 | coreConstant = require(rootPrefix + '/config/coreConstant'); 17 | 18 | const InstanceComposer = Base.InstanceComposer; 19 | 20 | require(rootPrefix + '/lib/rabbitmq/connection'); 21 | 22 | const errorConfig = { 23 | param_error_config: paramErrorConfig, 24 | api_error_config: apiErrorConfig 25 | }; 26 | 27 | /** 28 | * Constructor to publish RMQ event 29 | * 30 | * @constructor 31 | */ 32 | class RmqPublishByTopic { 33 | constructor() {} 34 | 35 | /** 36 | * Publish to rabbitMQ and local emitter also. 37 | * 38 | * @param {object} params - event parameters 39 | * @param {array} params.topics - on which topic messages 40 | * @param {object} params.message - 41 | * @param {string} params.message.kind - kind of the message 42 | * @param {object} params.message.payload - Payload to identify message and extra info. 43 | * 44 | * @return {Promise} 45 | */ 46 | async perform(params) { 47 | const oThis = this; 48 | 49 | // Validations. 50 | const r = await validator.light(params); 51 | if (r.isFailure()) { 52 | logger.error(r); 53 | return Promise.resolve(r); 54 | } 55 | 56 | let validatedParams = r.data, 57 | topics = validatedParams['topics'], 58 | msgString = JSON.stringify(validatedParams), 59 | publishAfter = params['publishAfter']; 60 | 61 | let publishedInRmq = 0; 62 | 63 | // Publish local events. 64 | topics.forEach(function(key) { 65 | localEmitter.emitObj.emit(key, msgString); 66 | }); 67 | 68 | if (oThis.ic().configStrategy.rabbitmq.enableRabbitmq == '1') { 69 | let rabbitMqConnection = oThis.ic().getInstanceFor(coreConstant.icNameSpace, 'rabbitmqConnection'); 70 | 71 | // Publish RMQ events. 72 | const conn = await rabbitMqConnection.get(); 73 | 74 | if (conn) { 75 | publishedInRmq = 1; 76 | let r = await oThis.publishNow(conn, topics, msgString, publishAfter); 77 | if (r.isFailure()) { 78 | return Promise.resolve(r); 79 | } 80 | 81 | if (publishAfter) { 82 | r = await oThis.publishDelayed(conn, topics, msgString, publishAfter); 83 | if (r.isFailure()) { 84 | return Promise.resolve(r); 85 | } 86 | } 87 | } else { 88 | let errorParams = { 89 | internal_error_identifier: 's_rp_t_1', 90 | api_error_identifier: 'no_rmq_connection', 91 | error_config: errorConfig, 92 | debug_options: {} 93 | }; 94 | return Promise.resolve(responseHelper.error(errorParams)); 95 | } 96 | } 97 | 98 | return Promise.resolve(responseHelper.successWithData({ publishedToRmq: publishedInRmq })); 99 | } 100 | 101 | async publishNow(conn, topics, msgString, publishAfter) { 102 | const oThis = this; 103 | 104 | return new Promise(function(onResolve, onReject) { 105 | conn.createChannel(function(err, ch) { 106 | if (err) { 107 | let errorParams = { 108 | internal_error_identifier: 's_rp_t_2', 109 | api_error_identifier: 'cannot_create_channel', 110 | error_config: errorConfig, 111 | debug_options: { err: err } 112 | }; 113 | logger.error(err.message); 114 | return onResolve(responseHelper.error(errorParams)); 115 | } 116 | 117 | ch.assertExchange(oThis.exchangeName, 'topic', { durable: true }); 118 | 119 | if (!publishAfter) { 120 | for (let index = 0; index < topics.length; index++) { 121 | let currTopic = topics[index]; 122 | ch.publish(oThis.exchangeName, currTopic, new Buffer(msgString), { persistent: true }); 123 | } 124 | } 125 | ch.close(); 126 | 127 | return onResolve(responseHelper.successWithData({})); 128 | }); 129 | }); 130 | } 131 | 132 | get exchangeName() { 133 | return 'topic_events'; 134 | } 135 | 136 | async publishDelayed(conn, topics, msgString, publishAfter) { 137 | const oThis = this; 138 | 139 | return new Promise(function(onResolve, onReject) { 140 | conn.createChannel(function(err, ch) { 141 | if (err) { 142 | let errorParams = { 143 | internal_error_identifier: 's_rp_t_3', 144 | api_error_identifier: 'cannot_create_channel', 145 | error_config: errorConfig, 146 | debug_options: { err: err } 147 | }; 148 | logger.error(err.message); 149 | return onResolve(responseHelper.error(errorParams)); 150 | } 151 | 152 | ch.assertExchange(oThis.delayedExchangeName, 'topic', { durable: true }); 153 | 154 | for (let index = 0; index < topics.length; index++) { 155 | let currTopic = topics[index], 156 | delayedQueueName = `${oThis.delayedExchangeName}_${currTopic}_queue_${publishAfter}`; 157 | 158 | ch.assertQueue( 159 | delayedQueueName, 160 | { 161 | exclusive: false, 162 | durable: true, 163 | arguments: { 164 | 'x-dead-letter-exchange': oThis.exchangeName, 165 | 'x-dead-letter-routing-key': currTopic, 166 | 'x-message-ttl': publishAfter, 167 | 'x-expires': publishAfter * 10 168 | } 169 | }, 170 | function(error2, q) { 171 | if (error2) { 172 | throw error2; 173 | } 174 | console.log(' [*] Waiting for messages in %s. To exit press CTRL+C', q.queue); 175 | ch.bindQueue(q.queue, oThis.delayedExchangeName, q.queue); 176 | 177 | ch.publish(oThis.delayedExchangeName, q.queue, new Buffer(msgString), { persistent: true }); 178 | console.log(' [x] Sent to DLX "', oThis.delayedExchangeName, '" with routing key', currTopic); 179 | 180 | ch.close(); 181 | } 182 | ); 183 | } 184 | 185 | return onResolve(responseHelper.successWithData({})); 186 | }); 187 | }); 188 | } 189 | 190 | get delayedExchangeName() { 191 | return 'delayed_topic_events'; 192 | } 193 | } 194 | 195 | InstanceComposer.registerAsObject(RmqPublishByTopic, coreConstant.icNameSpace, 'RmqPublishByTopic', true); 196 | 197 | module.exports = {}; 198 | -------------------------------------------------------------------------------- /services/rmqSubscribe/Base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Listening to RabbitMq channels to receive published message. 3 | * 4 | * @module services/rmqSubscribe/Base 5 | */ 6 | 7 | const Base = require('@truesparrow/base'); 8 | const { v4: uuidV4 } = require('uuid'); 9 | 10 | const rootPrefix = '../..', 11 | rabbitmqHelper = require(rootPrefix + '/lib/rabbitmq/helper'), 12 | localEmitter = require(rootPrefix + '/services/localEmitter'), 13 | logger = require(rootPrefix + '/lib/logger/customConsoleLogger'), 14 | coreConstant = require(rootPrefix + '/config/coreConstant'); 15 | 16 | const InstanceComposer = Base.InstanceComposer; 17 | 18 | require(rootPrefix + '/lib/rabbitmq/connection'); 19 | 20 | /** 21 | * Constructor to subscribe RMQ event 22 | * 23 | * @constructor 24 | */ 25 | class SubscribeEventBase { 26 | constructor() {} 27 | 28 | /** 29 | * Subscribe to rabbitMq topics to receive messages. 30 | * 31 | * @param {array} topics - list of topics to receive messages. 32 | * @param {object} options - 33 | * @param {string} [options.queue] - RMQ queue name. 34 | * - Name of the queue on which you want to receive all your subscribed events. 35 | * These queues and events, published in them, have TTL of 6 days. 36 | * If queue name is not passed, a queue with unique name is created and is deleted when subscriber gets disconnected. 37 | * @param {function} readCallback - function to run on message arrived on the channel. 38 | * @param {function} subscribeCallback - function to return consumerTag. 39 | * 40 | */ 41 | async rabbit(topics, options, readCallback, subscribeCallback) { 42 | const oThis = this; 43 | 44 | if (oThis.ic().configStrategy.rabbitmq.enableRabbitmq != '1') { 45 | logger.error('There is no rmq support. Error. '); 46 | process.emit('rmq_error', 'There is no rmq support.'); 47 | return; 48 | } 49 | 50 | let rabbitMqConnection = oThis.ic().getInstanceFor(coreConstant.icNameSpace, 'rabbitmqConnection'); 51 | 52 | options.prefetch = options.prefetch || 1; 53 | options.noAck = options.ackRequired !== 1; 54 | 55 | const conn = await rabbitMqConnection.get(); 56 | 57 | if (!conn) { 58 | logger.error('Not able to establish rabbitMQ connection for now. Please try after sometime.'); 59 | process.emit('rmq_error', 'Not able to establish rabbitMQ connection for now. Please try after sometime.'); 60 | return; 61 | } 62 | 63 | conn.createChannel(function(err, ch) { 64 | const consumerTag = uuidV4(); 65 | 66 | if (err) { 67 | logger.error('channel could be not created: Error: ', err); 68 | process.emit('rmq_error', 'channel could be not created: Error:' + err); 69 | return; 70 | } 71 | 72 | ch.once('error', function(err) { 73 | logger.error('[AMQP] Channel error', err); 74 | process.emit('rmq_error', '[AMQP] Channel error: Error:' + err); 75 | }); 76 | ch.once('close', function() { 77 | logger.error('[AMQP] Channel Closed'); 78 | }); 79 | 80 | const ex = options.exchangeName; 81 | 82 | // Call only if subscribeCallback is passed. 83 | subscribeCallback && subscribeCallback(consumerTag); 84 | 85 | ch.assertExchange(ex, options.exchangeType, { durable: true }); 86 | 87 | const assertQueueCallback = function(err, q) { 88 | if (err) { 89 | logger.error('subscriber could not assert queue: ' + err); 90 | process.emit('rmq_error', 'subscriber could not assert queue:' + err); 91 | return; 92 | } 93 | 94 | logger.info(' [*] Waiting for logs. To exit press CTRL+C', q.queue); 95 | 96 | oThis.getQueueBindingKeys(topics).forEach(function(key) { 97 | ch.bindQueue(q.queue, ex, key); 98 | }); 99 | 100 | ch.prefetch(options.prefetch); 101 | 102 | const startConsumption = function() { 103 | ch.consume( 104 | q.queue, 105 | function(msg) { 106 | const msgContent = msg.content.toString(); 107 | if (options.noAck) { 108 | readCallback(msgContent); 109 | } else { 110 | let successCallback = function() { 111 | logger.debug('done with ack'); 112 | ch.ack(msg); 113 | }; 114 | let rejectCallback = function() { 115 | logger.debug('requeue message'); 116 | ch.nack(msg); 117 | }; 118 | readCallback(msgContent).then(successCallback, rejectCallback); 119 | } 120 | }, 121 | { noAck: options.noAck, consumerTag: consumerTag } 122 | ); 123 | }; 124 | // If queue should start consuming as well. 125 | if (!options.onlyAssert) { 126 | startConsumption(); 127 | 128 | process.on('CANCEL_CONSUME', function(ct) { 129 | if (ct === consumerTag) { 130 | logger.info('Received CANCEL_CONSUME, cancelling consumption of', ct); 131 | ch.cancel(consumerTag); 132 | } 133 | }); 134 | process.on('RESUME_CONSUME', function(ct) { 135 | if (ct === consumerTag) { 136 | logger.info('Received RESUME_CONSUME, Resuming consumption of', ct); 137 | startConsumption(); 138 | } 139 | }); 140 | } else { 141 | logger.info('Closing the channel as only assert queue was required.'); 142 | ch.close(); 143 | } 144 | }; 145 | 146 | if (options['queue']) { 147 | ch.assertQueue( 148 | options['queue'], 149 | { 150 | autoDelete: false, 151 | durable: true, 152 | arguments: { 153 | 'x-expires': rabbitmqHelper.dedicatedQueueTtl, 154 | 'x-message-ttl': rabbitmqHelper.dedicatedQueueMsgTtl 155 | } 156 | }, 157 | assertQueueCallback 158 | ); 159 | } else { 160 | ch.assertQueue('', { exclusive: true }, assertQueueCallback); 161 | } 162 | }); 163 | 164 | localEmitter.emitObj.once('rmq_fail', function(err) { 165 | logger.error('RMQ Failed event received. Error: ', err); 166 | setTimeout(function() { 167 | logger.info('trying consume again......'); //Following catch will specifically catch connection timeout error. Thus will emit proper event 168 | oThis.rabbit(topics, options, readCallback, subscribeCallback).catch(function(err) { 169 | logger.error('Error in subscription. ', err); 170 | process.emit('rmq_error', 'Error in subscription:' + err); 171 | }); 172 | }, 2000); 173 | }); 174 | } 175 | 176 | /** 177 | * Subscribe local emitters by topics to receive messages. 178 | * Note: messages could be received only on the same object(thus, same process) where the message was emitted. 179 | * 180 | * @param {array} topics - list of topics to receive messages. 181 | * @param {function} readCallback - function to run on message arrived on the channel. 182 | * 183 | */ 184 | local(topics, readCallback) { 185 | if (topics.length === 0) { 186 | logger.error('Invalid parameters Error: topics are mandatory'); 187 | return; 188 | } 189 | 190 | topics.forEach(function(key) { 191 | localEmitter.emitObj.on(key, readCallback); 192 | }); 193 | } 194 | 195 | /** 196 | * Get Keys to Bind Queue with 197 | * 198 | * @param topics 199 | */ 200 | getQueueBindingKeys(topics) { 201 | throw 'Sub-class to implement'; 202 | } 203 | } 204 | 205 | module.exports = SubscribeEventBase; 206 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Queue 2 | 3 | ![npm version](https://img.shields.io/npm/v/@truesparrow/queue.svg?style=flat) 4 | 5 | True Sparrow Queue helps in publishing and subscribing tasks over [RabbitMQ](https://www.rabbitmq.com/). Internally it uses topic-based exchange. 6 | 7 | One use-case is to publish tasks for asynchronous processing. For example, **API worker process** can publish tasks which will be taken up by **asynchronous worker processes** which have subscribed for such tasks. 8 | 9 | ## Prerequisites 10 | - [RabbitMQ](https://www.rabbitmq.com/tutorials/amqp-concepts.html) 11 | 12 | ## Install 13 | 14 | ```bash 15 | npm install @truesparrow/queue --save 16 | ``` 17 | 18 | ## Initialize 19 | `configStrategy` is passed to initialize True Sparrow Queue. `configStrategy` is an object with `rabbitmq` as a key. The value of `rabbitmq` is an object with following keys: 20 | - **username** [string] (mandatory) RabbitMQ connection username 21 | - **password** [string] (mandatory) RabbitMQ connection password 22 | - **host** [string] (mandatory) RabbitMQ host 23 | - **port** [string] (mandatory) RabbitMQ port 24 | - **heartbeats** [string] (mandatory) [heartbeats](https://www.rabbitmq.com/heartbeats.html) defines after what period of time the peer TCP connection should be considered unreachable. 25 | - **clusterNodes** [Array] (mandatory) - List of [RMQ cluster](https://www.rabbitmq.com/clustering.html#node-names) hosts. 26 | - **enableRabbitmq** [integer] (optional) 0 if local usage. 27 | - **switchHostAfterSec** [integer] (optional) Wait time before switching RMQ host. 28 | - **connectionTimeoutSec** [integer] (optional) Wait time for connection to establish. 29 | 30 | Following snippet initializes True Sparrow Queue Manager: 31 | 32 | ```js 33 | const Queue = require('@truesparrow/queue'); 34 | 35 | // Config Strategy for True Sparrow Queue. 36 | configStrategy = { 37 | 'rabbitmq': { 38 | 'username': 'guest', 39 | 'password': 'guest', 40 | 'host': '127.0.0.1', 41 | 'port': '5672', 42 | 'heartbeats': '30', 43 | 'enableRabbitmq': 1 44 | } 45 | }; 46 | 47 | // Create instance 48 | const queueManager = await Queue.getInstance(configStrategy); 49 | ``` 50 | 51 | ## queueManager object methods 52 | - `queueManager.subscribeEvent.rabbit(topics, options, readCallback, subscribeCallback)` 53 |
Description: Subscribe to multiple topics over a queue. 54 |
Parameters: 55 | - **topics** [Array] (mandatory) - Array of topics to subscribe to. 56 | - **options** [object] (mandatory) Object with following keys: 57 | - **queue** [string] (optional) - Name of the queue on which messages with relevant topics will be published. If not passed, a queue with a unique name is created and is deleted when the subscriber gets disconnected. 58 | - **ackRequired** [integer] (optional) - The delivered message needs ack if passed 1 ( default 0 ). if 1 passed and ack not done, message will redeliver. 59 | - **broadcastSubscription** [integer] (optional) - Set to 1, when queue needs to be subscribed to broadcasting events. Default 0. 60 | - **prefetch** [integer] (optional) - The number of messages released from queue in parallel. In case of ackRequired=1, queue will pause unless delivered messages are acknowledged. Default 1. 61 | - **readCallback** [function] (mandatory) - Callback method will be invoked whenever there is a new notification. 62 | - **subscribeCallback** [function] (optional) - Callback method to get consumerTag. 63 | 64 | - `queueManager.publishEvent.perform(params)` 65 |
Description: Publish event to topics. 66 |
Parameters: 67 | - **params** [object] (mandatory) Object with following keys: 68 | - **topics** [Array] (mandatory) Array of topics to which message to be publish. 69 | - **broadcast** [integer] (optional) Set to 1 for broadcasting. Default 0. 70 | - **publishAfter** [integer] (optional) Delay in milli-seconds between publish and being available for consumption. Default 0. 71 | - **publisher** [string] (mandatory) Name of publisher 72 | - **message** [object] (mandatory) Object with following keys: 73 | - **kind** [string] (mandatory) Kind of the message. 74 | - **payload** [object] (mandatory) Payload to identify message and extra info. 75 | 76 | ## Examples 77 | 78 | ### Subscribe 79 | Following snippet subscribes to specific topics over a queue. 80 | 81 | ```js 82 | const Queue = require('@truesparrow/queue'); 83 | 84 | // Config Strategy for True Sparrow Queue. 85 | configStrategy = { 86 | 'rabbitmq': { 87 | 'username': 'guest', 88 | 'password': 'guest', 89 | 'host': '127.0.0.1', 90 | 'port': '5672', 91 | 'heartbeats': '30', 92 | 'enableRabbitmq': 1 93 | } 94 | }; 95 | 96 | let unAckCount = 0; // Number of unacknowledged messages. 97 | 98 | const topics = ["topic.testTopic"]; 99 | 100 | const options = { 101 | queue: 'testQueue', 102 | ackRequired: 1, // When set to 1, all delivered messages MUST get acknowledge. 103 | broadcastSubscription: 1, // When set to 1, it will subscribe to broadcast channel and receive all broadcast messages. 104 | prefetch:10 105 | }; 106 | 107 | const processMessage = function(msgContent) { 108 | // Process message code here. 109 | // msgContent is the message string, which needs to be JSON parsed to get message object. 110 | }; 111 | 112 | const readCallback = function(msgContent) { 113 | // Please make sure to return promise in callback function. 114 | // On resolving the promise, the message will get acknowledged. 115 | // On rejecting the promise, the message will be re-queued (noAck) 116 | return new Promise(async function(onResolve, onReject) { 117 | // Incrementing unacknowledged message count. 118 | unAckCount++; 119 | 120 | // Process the message. Following is a 121 | response = await processMessage(msgContent); 122 | 123 | // Complete the task and in the end of all tasks done 124 | if(response == success){ 125 | // The message MUST be acknowledged here. 126 | // To acknowledge the message, call onResolve 127 | // Decrementing unacknowledged message count. 128 | unAckCount--; 129 | onResolve(); 130 | } else { 131 | //in case of failure to requeue same message. 132 | onReject(); 133 | } 134 | }) 135 | }; 136 | 137 | const subscription = {}; // object to store the consumer tag 138 | const subscribeCallback = function(consumerTag) { 139 | subscription.consumerTag = consumerTag; 140 | }; 141 | 142 | const subscribe = async function() { 143 | const queueManager = await Queue.getInstance(configStrategy); 144 | queueManager.subscribeEvent.rabbit( 145 | topics, // List of topics 146 | options, 147 | readCallback, 148 | subscribeCallback 149 | ); 150 | }; 151 | 152 | // Gracefully handle SIGINT, SIGTERM signals. 153 | // Once SIGINT/SIGTERM signal is received, programme will stop consuming new messages. 154 | // But, the current process MUST handle unacknowledged queued messages. 155 | process.on('SIGINT', function () { 156 | // Stop the consumption of messages 157 | process.emit('CANCEL_CONSUME', subscription.consumerTag); 158 | 159 | console.log('Received SIGINT, checking unAckCount.'); 160 | const f = function(){ 161 | if (unAckCount === 0) { 162 | process.exit(1); 163 | } else { 164 | console.log('waiting for open tasks to be done.'); 165 | setTimeout(f, 1000); 166 | } 167 | }; 168 | // Wait for open tasks to be done. 169 | setTimeout(f, 1000); 170 | }); 171 | 172 | function rmqError(err) { 173 | console.log('rmqError occured.', err); 174 | process.emit('SIGINT'); 175 | } 176 | // Event published from package in case of internal error. 177 | process.on('rmq_error', rmqError); 178 | 179 | subscribe(); 180 | ``` 181 | 182 | ### Publish 183 | 184 | Following snippet publishes a task for specific topics. 185 | 186 | ```js 187 | // Config Strategy for True Sparrow Queue. 188 | configStrategy = { 189 | 'rabbitmq': { 190 | 'username': 'guest', 191 | 'password': 'guest', 192 | 'host': '127.0.0.1', 193 | 'port': '5672', 194 | 'heartbeats': '30', 195 | 'enableRabbitmq': 1 196 | } 197 | }; 198 | 199 | const topics = ["topic.testTopic"]; 200 | 201 | const message = { 202 | kind: 'testMessageKind', 203 | payload: { 204 | // Custom payload for message 205 | } 206 | }; 207 | 208 | // Import the Queue module. 209 | const Queue = require('@truesparrow/queue'); 210 | const publish = async function() { 211 | const queueManager = await Queue.getInstance(configStrategy); 212 | queueManager.publishEvent.perform( 213 | { 214 | topics: topics, 215 | publisher: 'MyPublisher', 216 | publishAfter: 30*1000, // delay in milli-seconds 217 | message: message 218 | }); 219 | }; 220 | 221 | publish(); 222 | ``` 223 | 224 | ### Publish with delay 225 | 226 | In some use cases, it is required to process certain task with a delay. For example, after one hour of user sign-up, we need to send an email. 227 | Such tasks can be published by using the `publishAfter` parameter. Internally, we use [dead letter exchange](https://www.rabbitmq.com/dlx.html) for achieving this functionality. 228 | 229 | **Important Note**: Do not use arbitrary values of delays. Internally, the message is stored in a delay specific queue for the waiting duration. As the number of allowed delays increases, so do the number of waiting queues. Having too many queues, can hamper RabbitMQ performance. 230 | 231 | ```js 232 | // Config Strategy for True Sparrow Queue. 233 | configStrategy = { 234 | 'rabbitmq': { 235 | 'username': 'guest', 236 | 'password': 'guest', 237 | 'host': '127.0.0.1', 238 | 'port': '5672', 239 | 'heartbeats': '30', 240 | 'enableRabbitmq': 1 241 | } 242 | }; 243 | 244 | const topics = ["topic.testTopic"]; 245 | 246 | const message = { 247 | kind: 'testMessageKind', 248 | payload: { 249 | // Custom payload for message 250 | } 251 | }; 252 | 253 | // Import the Queue module. 254 | const Queue = require('@truesparrow/queue'); 255 | const publish = async function() { 256 | const queueManager = await Queue.getInstance(configStrategy); 257 | queueManager.publishEvent.perform( 258 | { 259 | topics: topics, 260 | publisher: 'MyPublisher', 261 | message: message 262 | }); 263 | }; 264 | 265 | publish(); 266 | ``` 267 | 268 | ### Cancel and Resume message consumption 269 | 270 | As seen in the subscribe snippet, cancelling consumption is the first step in SIGINT handling. For cancelling the consumption, 271 | consumerTag is needed, which is obtained in subscribeCallback. See subscribe snippet above for more details. 272 | 273 | For cancelling the consumption, emit `CANCEL_CONSUME` event with consumerTag info. 274 | ```js 275 | process.emit('CANCEL_CONSUME', consumerTag); 276 | ``` 277 | 278 | For resuming the consumption, emit `RESUME_CONSUME` event with consumerTag info. 279 | ```js 280 | process.emit('RESUME_CONSUME', consumerTag); 281 | ``` 282 | 283 | ## Running test cases 284 | Run following command to execute test cases. 285 | ```shell script 286 | ./node_modules/.bin/mocha --recursive "./test/**/*.js" 287 | ``` 288 | -------------------------------------------------------------------------------- /lib/rabbitmq/connection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pseudo Singleton Object (in the context of a config strategy) to manage and establish RabbitMq connections 3 | * 4 | * @module lib/rabbitmq/connection 5 | */ 6 | 7 | const Base = require('@truesparrow/base'); 8 | 9 | const rootPrefix = '../..', 10 | amqp = require('amqplib/callback_api'), 11 | rabbitmqHelper = require(rootPrefix + '/lib/rabbitmq/helper'), 12 | localEvents = require(rootPrefix + '/services/localEmitter'), 13 | logger = require(rootPrefix + '/lib/logger/customConsoleLogger'), 14 | coreConstant = require(rootPrefix + '/config/coreConstant'); 15 | 16 | const InstanceComposer = Base.InstanceComposer; 17 | 18 | const rmqIdToConnectionMap = {}, 19 | rmqIdToInProcessRequestsMap = {}; 20 | 21 | let lastAttemptTimeMS = null, 22 | d = 0; 23 | 24 | class RabbitmqConnection { 25 | /** 26 | * 27 | * @param {object} configStrategy - config strategy 28 | * @param {object} instanceComposer - Instance Composer object 29 | * 30 | * @param configStrategy 31 | * @param instanceComposer 32 | * @constructor 33 | */ 34 | constructor(configStrategy, instanceComposer) { 35 | const oThis = this; 36 | 37 | oThis.rmqId = rabbitmqHelper.getRmqId(configStrategy); 38 | oThis.instanceKey = rabbitmqHelper.getInstanceKey(configStrategy); 39 | oThis.rmqHost = rabbitmqHelper.getConfigRmqHost(configStrategy); 40 | oThis.configRmqClusterNodes = rabbitmqHelper.getConfigRmqClusterNodes(configStrategy); 41 | 42 | // Wait in seconds for connection to establish. 43 | oThis.getConnectionTimeout = configStrategy.rabbitmq.connectionTimeoutSec; 44 | oThis.switchHostAfterTime = configStrategy.rabbitmq.switchHostAfterSec || 30; 45 | } 46 | 47 | /** 48 | * Get the established connection and return. If connection is not present set new connection in required mode. 49 | * 50 | * @return {Promise} 51 | */ 52 | get() { 53 | const oThis = this; 54 | 55 | if (oThis.ic().configStrategy.rabbitmq.enableRabbitmq != '1') { 56 | return Promise.resolve(null); 57 | } else if (rmqIdToConnectionMap[oThis.rmqId]) { 58 | // If connection is present in map, return the same connection. 59 | return Promise.resolve(rmqIdToConnectionMap[oThis.rmqId]); 60 | } else { 61 | let promiseContext = { 62 | promiseObj: null, 63 | resolve: null, 64 | reject: null 65 | }, 66 | promiseObj = new Promise(function(resolve, reject) { 67 | promiseContext.resolve = resolve; 68 | promiseContext.reject = reject; 69 | }); 70 | 71 | promiseContext.promiseObj = promiseObj; 72 | 73 | //Timeout is part of instancekey. Higher timeout connection attempt should not block lower timeout connection attempts. 74 | //Two different instanceKey can have same rmqId. This will happen when only connection timeout parameter is different. 75 | rmqIdToInProcessRequestsMap[oThis.instanceKey] = rmqIdToInProcessRequestsMap[oThis.instanceKey] || []; 76 | 77 | if (rmqIdToInProcessRequestsMap[oThis.instanceKey].length === 0) { 78 | // if this is the first request, initiate connection creation 79 | rmqIdToInProcessRequestsMap[oThis.instanceKey].push(promiseContext); 80 | 81 | oThis 82 | .set() 83 | .then( 84 | function(conn) { 85 | oThis.resolveAll(conn); 86 | }, 87 | function(err) { 88 | oThis.rejectAll(err); 89 | } 90 | ) 91 | .catch(function(err) { 92 | oThis.rejectAll('unhandled error in lib/rabbitmq/connection.js while calling set. error:', err); 93 | }); 94 | } else { 95 | // else push into the list of in process request and wait for connection to be made by the head of the array 96 | rmqIdToInProcessRequestsMap[oThis.instanceKey].push(promiseContext); 97 | } 98 | 99 | return promiseObj; 100 | } 101 | } 102 | 103 | /** 104 | * Resolve all the in process requests 105 | * 106 | * @param {object} conn - connection object 107 | */ 108 | resolveAll(conn) { 109 | const oThis = this; 110 | 111 | while (rmqIdToInProcessRequestsMap[oThis.instanceKey][0]) { 112 | let promiseContext = rmqIdToInProcessRequestsMap[oThis.instanceKey].shift(); 113 | 114 | let resolve = promiseContext.resolve; 115 | resolve(conn); 116 | } 117 | } 118 | 119 | /** 120 | * Reject all the in process requests 121 | * 122 | * @param {string} reason - error string to reject with 123 | */ 124 | rejectAll(reason) { 125 | const oThis = this; 126 | 127 | while (rmqIdToInProcessRequestsMap[oThis.instanceKey][0]) { 128 | let promiseContext = rmqIdToInProcessRequestsMap[oThis.instanceKey].shift(); 129 | 130 | let reject = promiseContext.reject; 131 | reject(reason); 132 | } 133 | } 134 | 135 | /** 136 | * Establishing new connection to RabbitMq. 137 | * 138 | * @returns {Promise} 139 | */ 140 | set() { 141 | // Declare variables. 142 | const oThis = this, 143 | retryIntervalInMs = 1000; 144 | 145 | let connectionAttempts = 0, 146 | timedOutReason = null, 147 | connectionTimeout = null, 148 | rmqId = oThis.rmqId; 149 | 150 | if (oThis.ic().configStrategy.rabbitmq.enableRabbitmq != '1') { 151 | return Promise.resolve(null); 152 | } 153 | 154 | return new Promise(function(onResolve, onReject) { 155 | let connectRmqInstance = function() { 156 | if (!lastAttemptTimeMS) { 157 | lastAttemptTimeMS = Date.now(); 158 | } 159 | 160 | if (rmqIdToConnectionMap[rmqId]) { 161 | return onResolve(rmqIdToConnectionMap[rmqId]); 162 | } else if (timedOutReason) { 163 | return onReject(new Error(timedOutReason)); 164 | } 165 | 166 | logger.log('Connection will attempted on: ', oThis.ic().configStrategy.rabbitmq.host); 167 | amqp.connect( 168 | rabbitmqHelper.connectionString(oThis.ic().configStrategy), 169 | { timeout: rabbitmqHelper.socketConnectionTimeout }, 170 | function(err, conn) { 171 | if (err || !conn) { 172 | rmqIdToConnectionMap[rmqId] = null; 173 | logger.error('Error from rmq connection attempt : ' + err); 174 | connectionAttempts++; 175 | logger.info('Trying connect after(ms) ', retryIntervalInMs); 176 | setTimeout(function() { 177 | if (connectionAttempts >= rabbitmqHelper.maxConnectionAttempts) { 178 | logger.error('Maximum retry attempts for connection reached'); 179 | timedOutReason = 'Maximum retry connects failed for rmqId:' + rmqId; 180 | process.emit('connectionTimedOut', { failedHost: oThis.rmqHost }); 181 | } 182 | switchConnectionParams(); 183 | connectRmqInstance(); 184 | }, retryIntervalInMs); //Retry every 1 second 185 | } else { 186 | conn.once('error', function(err) { 187 | logger.error('[AMQP] conn error', err.message); 188 | 189 | if (err.message !== 'Connection closing') { 190 | logger.error('[AMQP] conn error in closing'); 191 | } 192 | delete rmqIdToConnectionMap[rmqId]; 193 | 194 | // NOTE: Don't reconnect here, because all error will also trigger 'close' event. 195 | // localEvents.emitObj.emit('rmq_fail', err); 196 | // connectRmqInstance(); 197 | }); 198 | 199 | conn.once('close', function(c_msg) { 200 | logger.info('[AMQP] reconnecting', c_msg); 201 | delete rmqIdToConnectionMap[rmqId]; 202 | localEvents.emitObj.emit('rmq_fail', c_msg); 203 | connectRmqInstance(); 204 | }); 205 | 206 | logger.info('RMQ Connection Established..'); 207 | //Connections should be saved under rmqId. So that one connection of one rmqId is assured. 208 | rmqIdToConnectionMap[rmqId] = conn; 209 | timedOutReason = null; 210 | 211 | if (connectionTimeout) { 212 | clearTimeout(connectionTimeout); 213 | } 214 | 215 | lastAttemptTimeMS = null; 216 | connectionAttempts = 0; //ConnectionAttempt reset here. So that at next failure, connectionAttempts counter starts from 0. 217 | return onResolve(conn); 218 | } 219 | } 220 | ); 221 | }; 222 | 223 | let switchConnectionParams = function() { 224 | //variable d is used to ensure locking 225 | if ( 226 | ++d == 1 && 227 | oThis.switchHostAfterTime && 228 | lastAttemptTimeMS && 229 | (Date.now() - lastAttemptTimeMS) / 1000 > oThis.switchHostAfterTime 230 | ) { 231 | lastAttemptTimeMS = Date.now(); 232 | if (!rmqIdToConnectionMap[rmqId] && oThis.configRmqClusterNodes.length > 1) { 233 | logger.error('SwitchHostAfterTime Connection not established for rmqId:' + rmqId, connectionAttempts); 234 | timedOutReason = null; 235 | let configStrategy = oThis.ic().configStrategy; 236 | for (let i = 0; i < oThis.configRmqClusterNodes.length; i++) { 237 | let newHost = oThis.configRmqClusterNodes[i]; 238 | if (newHost != oThis.rmqHost) { 239 | logger.step( 240 | 'Will try to establish connection on Host ' + newHost + ' - rmqId:' + rmqId, 241 | connectionAttempts 242 | ); 243 | Object.assign(configStrategy.rabbitmq, { host: newHost }); 244 | rmqId = rabbitmqHelper.getRmqId(configStrategy); 245 | process.emit('switchConnectionHost', { newHost: newHost, failedHost: oThis.rmqHost }); 246 | oThis.rmqHost = newHost; 247 | break; 248 | } 249 | } 250 | } 251 | d = 0; 252 | } else { 253 | d--; 254 | } 255 | }; 256 | 257 | /** 258 | * Function to set connection timeout. Specifically used to quit attempting any more connections. 259 | */ 260 | let setConnectionTimeout = function() { 261 | if (connectionTimeout) { 262 | clearTimeout(connectionTimeout); 263 | } 264 | if (oThis.getConnectionTimeout) { 265 | logger.info('Connection Timeout :', oThis.getConnectionTimeout); 266 | connectionTimeout = setTimeout(function() { 267 | if (!rmqIdToConnectionMap[rmqId]) { 268 | logger.error('Connection timed out for rmqId:' + rmqId); 269 | timedOutReason = 'Connection timed out for rmqId:' + rmqId; 270 | process.emit('connectionTimedOut', { failedHost: oThis.rmqHost }); 271 | } 272 | }, oThis.getConnectionTimeout * 1000); 273 | } 274 | }; 275 | 276 | setConnectionTimeout(); 277 | connectRmqInstance(); 278 | }); 279 | } 280 | } 281 | 282 | InstanceComposer.registerAsObject(RabbitmqConnection, coreConstant.icNameSpace, 'rabbitmqConnection', true); 283 | 284 | module.exports = {}; 285 | --------------------------------------------------------------------------------