UNPKG

tyo-mq

Version:

Distributed Message Pub/Sub Service with socket.io

263 lines (218 loc) 9.02 kB
/** * @file subscriber.js */ const crypto = require('crypto'), util = require('util'), events = require('./events'), Constants = require('./constants'); var Socket = require('./socket'); const { constants } = require('buffer'); /** * */ function Subscriber (name, options) { options = options || {}; this.context = null; Socket.call(this); // When no name is supplied, mint a unique anonymous identity so two unnamed // producers/consumers never collide on the shared 'ANONYMOUS' name. The // ANONYMOUS- prefix keeps them recognisable to the server's realm policy. this.name = name || (Constants.ANONYMOUS + '-' + crypto.randomUUID()); this.consumer_id = options.consumer_id || options.consumerId || this.name; var subscriber = this; /** * @override * */ this.sendIdentificationInfo = function () { this.sendMessage.call(subscriber, 'CONSUMER', { name: subscriber.name, id: subscriber.consumer_id, consumer_id: subscriber.consumer_id }); } /** * Resend the subscription message after a connection is lost (particularily when server is gone) and reconnected */ this.resubscribeWhenReconnect = function (context, who, event, onConsumeCallback, reSubscribe) { var self = this; var subscribeOptions = {}; if (reSubscribe === null || reSubscribe === undefined) reSubscribe = true; if (typeof who === 'function' && event && typeof event === 'object' && !Array.isArray(event)) { subscribeOptions = event; onConsumeCallback = who; event = context; who = Constants.ALL_PUBLISHERS; context = self; } else if (typeof event === 'function') { var optionArg = onConsumeCallback; onConsumeCallback = event; event = who; who = context; context = self; if (optionArg && typeof optionArg === 'object') subscribeOptions = optionArg; else if (typeof optionArg === 'boolean') reSubscribe = optionArg; } else if (!onConsumeCallback) { onConsumeCallback = event; event = who; who = context; context = self; } else if (reSubscribe && typeof reSubscribe === 'object') { subscribeOptions = reSubscribe; reSubscribe = subscribeOptions.reconnect; } function resubscribeListener() { subscribeInternal(); } function subscribeInternal() { if ((typeof event) !== "string") { onConsumeCallback = event; event = null; } var eventStr; var scope; var scope_all = false; if (event) eventStr = events.toEventString(event); else { eventStr = constants.EVENT_ALL; // who + "-ALL"; scope = Constants.SCOPE_ALL; scope_all = true; } /** * @todo * * deal with the ALL events later */ function sendSubscriptionMessage () { var ackEnabled = !!(subscribeOptions.ack || subscribeOptions.require_ack || subscribeOptions.requireAck || subscribeOptions.manual_ack || subscribeOptions.manualAck); self.sendMessage('SUBSCRIBE', { event:eventStr, producer: who, consumer:self.name, scope: scope, durable: !!subscribeOptions.durable, consumer_id: subscribeOptions.consumer_id || subscribeOptions.consumerId || self.consumer_id, ack: ackEnabled, manual_ack: !!(subscribeOptions.manual_ack || subscribeOptions.manualAck), ack_timeout: subscribeOptions.ack_timeout || subscribeOptions.ackTimeout, retry: subscribeOptions.retry || null, mode: subscribeOptions.mode || null, group: subscribeOptions.group || null }); } // On Connect Message will be trigger by system sendSubscriptionMessage(); // the connection should be ready before we subscribe the message // this.on('connect', function () { // sendSubscritionMessage(); // }); if (!self.consumes) self.consumes = {}; var consumerEventStr = events.toConsumerEvent(eventStr, who, scope_all); // var targetEventStr = events.toEventString(event, who).toLowerCase(); self.consumes[consumerEventStr] = function (obj) { //var intendedEvent = obj.event; // if the message is encrypted, then it needs to be decrypted first var message = obj.message; var from = obj.from || message.from; var msgId = obj.msgId || obj.msg_id; var manualAck = !!(subscribeOptions.manual_ack || subscribeOptions.manualAck); var acked = false; function ack(callback) { if (!msgId || acked) { if (callback) callback(); return; } acked = true; self.sendMessage('ACK', {msgId: msgId}, null, callback); } //if (intendedEvent === targetEventStr) { var result = onConsumeCallback(message, from, ack, obj); if (msgId && !manualAck) { Promise.resolve(result).then(function () { ack(); }).catch(function (err) { if (self.logger && self.logger.warn) self.logger.warn('Message handler rejected before ACK: ' + err.message); }); } //} }; var consumeEventStr = events.toConsumeEvent(consumerEventStr); // remove the old listener, we only need one listener for each event self.off(consumeEventStr); self.on(consumeEventStr, function (obj) { if (context) self.consumes[consumerEventStr].call(context, obj); else self.consumes[consumerEventStr](obj); }); } subscribeInternal(); if (reSubscribe) self.addConnectionListener(resubscribeListener); } /** * Subscribe message * * If an event name is not provided, then we subscribe all the messages from the producer */ this.subscribe = function (context, who, event, onConsumeCallback, reconnect) { this.resubscribeWhenReconnect(context, who, event, onConsumeCallback, reconnect); }; /** * Subscribe only once, if the connection is gone, let it be */ this.subscribeOnce = function (context, who, event, onConsumeCallback) { this.subscribe(context, who, event, onConsumeCallback, false); }; /** * Subscribe all events with this name whatever providers are publishing */ this.subscribeAll = function (context, event, onConsumeCallback) { this.subscribe(context, Constants.ALL_PUBLISHERS, event, onConsumeCallback); } this.unsubscribe = function (event, who) { var eventStr = events.toConsumerEvent(event, who); // this.sendMessage('UNSUBSCRIBE', {event:eventStr}); this.off(eventStr); } this.unsubscribeAll = function () { this.socket.removeAllListeners(); } /** * Remove only the consume-event handlers that this subscriber registered, * leaving the socket.io system handlers (connect / disconnect / CONSUME_CHUNK) * intact so reconnection and chunk reassembly keep working. */ this.clearSubscriptions = function () { if (!subscriber.consumes) return; Object.keys(subscriber.consumes).forEach(function (consumerEventStr) { var consumeEventStr = events.toConsumeEvent(consumerEventStr); subscriber.off(consumeEventStr); }); subscriber.consumes = {}; }; this.setOnProducerOnlineListener = function (producer, callback) { var eventStr = events.toEventString(producer, null, "ONLINE"); this.on(eventStr, callback); } this.whenProducerOnline = this.setOnProducerOnline = this.setOnProducerOnlineListener; } /** * Inherits from Socket */ util.inherits(Subscriber, Socket); module.exports = Subscriber;