tyo-mq
Version:
Distributed Message Pub/Sub Service with socket.io
209 lines (170 loc) • 6.69 kB
JavaScript
/**
* @file producer.js
*/
const util = require('util'),
events = require('./events'),
Constants = require('./constants');
var Subscriber = require('./subscriber');
function SubscriberInfo (name) {
this.name = name;
}
function Publisher (name, event, options) {
if (event && typeof event === 'object' && !Array.isArray(event)) {
options = event;
event = null;
}
options = options || {};
// call parent constructor
Subscriber.call(this, name);
var producer = this;
this.eventDefault = event || Constants.EVENT_DEFAULT;
if (options.default_ttl !== undefined)
this.default_ttl = options.default_ttl;
else if (options.defaultTtl !== undefined)
this.default_ttl = options.defaultTtl;
else if (options.ttl !== undefined)
this.default_ttl = options.ttl;
else
this.default_ttl = null;
this.subscribers = {};
this.onSubscriptionListener = null;
/**
* @override
*
*/
this.sendIdentificationInfo = function () {
var payload = {name: name};
if (producer.default_ttl !== null && producer.default_ttl !== undefined)
payload.default_ttl = producer.default_ttl;
this.sendMessage.call(producer, 'PRODUCER', payload);
};
/**
* Chunk size threshold: messages serialised above this byte length are split
* into multiple PRODUCE_CHUNK frames instead of one PRODUCE frame, keeping
* each frame safely under the engine.io maxHttpBufferSize limit.
*/
Publisher.CHUNK_SIZE = Publisher.CHUNK_SIZE || 256 * 1024; // 256 KB
/**
* Event produce function
* @param event
* @param data
* @param validFor, the valid time (lifespan of the message)
*/
this.produce = function (event, data, validFor, to) {
var self = this;
if (data === undefined) {
data = event;
event = this.eventDefault;
if (!event)
throw new Error('Default event name is not set.');
}
var options = {};
if (validFor && typeof validFor === 'object' && !Array.isArray(validFor))
options = validFor;
var message = {event:event, message:data, from:self.name};
if (options.guaranteed || options.guaranteed_delivery || options.guaranteedDelivery || options.delivery === 'guaranteed' || options.delivery_mode === 'guaranteed')
message.guaranteed = true;
if (options.ttl !== undefined)
message.ttl = options.ttl;
else if (self.default_ttl !== null && self.default_ttl !== undefined)
message.ttl = self.default_ttl;
else if (validFor !== undefined && !(validFor && typeof validFor === 'object'))
message.lifespan = validFor;
if (options.broadcast) {
message.method = 'broadcast';
message.broadcast = options.broadcast === 'group' ? 'group' : 'realm';
if (options.group)
message.group = options.group;
}
if (to)
message.to = to;
var str = JSON.stringify(message);
var chunkSize = Publisher.CHUNK_SIZE;
if (str.length <= chunkSize) {
self.sendMessage.call(self, 'PRODUCE', message, self.name);
return;
}
// Large message — split into chunks
var total = Math.ceil(str.length / chunkSize);
var transferId = Date.now().toString(36) + '-' + Math.random().toString(36).slice(2);
for (var i = 0; i < total; i++) {
self.sendMessage.call(self, 'PRODUCE_CHUNK', {
transferId: transferId,
index: i,
total: total,
data: str.slice(i * chunkSize, (i + 1) * chunkSize)
}, self.name);
}
};
/**
* On Subscribe
*/
this.setOnSubscriptionListener = function (callback) {
var event = events.toOnSubscribeEvent(this.getId());
this.off(event); // clear all previous listeners
this.on(event, function (data) {
producer.logger.log("Received subscription information: " + JSON.stringify(data));
producer.subscribers[data.id] = data;
// further listener
if (producer.onSubscriptionListener)
producer.onSubscriptionListener.call(producer, data);
if (callback)
callback(data);
});
};
this.onSubscribed = this.setOnSubscriptionListener;
/**
* On Lost connections with subscriber(s)
*/
this.setOnSubscriberLostListener = function (callback) {
var event = events.toOnDisconnectEvent(this.getId());
this.off(event); // clear all previous listeners
this.on(event, function(data) {
producer.logger.log("Lost subscriber's connection");
if (callback)
callback(data);
});
};
this.onSubscriberLost = this.setOnSubscriberLostListener;
/**
* On Unsubsribe
*/
this.setOnUnsubscribedListener = function (callback) {
var event = events.toOnUnsubscribeEvent(this.getId());
this.on(event, function() {
if (callback)
callback();
});
};
this.onUnsubscribed = this.setOnUnsubscribedListener;
// Initialisation
this.addConnectionListener(function () {
var producerName = producer.name || Constants.ANONYMOUS;
// producer.sendMessage.call(producer, 'PRODUCER', {name: producerName});
producer.setOnSubscriptionListener();
});
// this.setOnSubscriberLostListener = function (callback) {
// var event = events.toOnDisconnectEvent(this.getId());
// this.on(event, function(data) {
// producer.logger.log("Lost subscriber's connection");
// if (callback)
// callback(data);
// });
// };
// this.setOnSubscriberLost = this.setOnSubscriberLostListener;
this.setOnSubscriberOnlineListener = function (callback) {
var event = events.toOnConnectEvent(this.getId());
this.off(event); // clear all previous listeners
this.on(event, function(data) {
producer.logger.log("Subscriber is online: name='" + (data && data.consumer) + "' id=" + (data && data.id) + " socket=" + (data && data.socket));
if (callback)
callback(data);
});
}
this.whenSubscriberOnline = this.setOnSubscriberOnline = this.setOnSubscriberOnlineListener;
}
/**
* Inherits from Socket
*/
util.inherits(Publisher, Subscriber);
module.exports = Publisher;