UNPKG

rocket.chat.mqtt

Version:

It's a MQTT Server, using redis to scale horizontally.

216 lines (184 loc) 4.98 kB
'use strict' var QlobberSub = require('qlobber/aedes/qlobber-sub') var Packet = require('aedes-packet') var EE = require('events').EventEmitter var inherits = require('util').inherits var MultiStream = require('multistream') var parallel = require('fastparallel') var QlobberOpts = { wildcard_one: '+', wildcard_some: '#', separator: '/' } var newSubTopic = '$SYS/sub/add' var rmSubTopic = '$SYS/sub/rm' var subTopic = '$SYS/sub/+' function CachedPersistence (opts) { if (!(this instanceof CachedPersistence)) { return new CachedPersistence(opts) } EE.call(this) this.ready = false this.destroyed = false this._parallel = parallel() this._trie = new QlobberSub(QlobberOpts) this._waiting = {} var that = this this.once('ready', function () { that.ready = true }) this._onMessage = function onSubMessage (packet, cb) { var decoded = JSON.parse(packet.payload) var clientId = decoded.clientId for (var i = 0; i < decoded.subs.length; i++) { var sub = decoded.subs[i] sub.clientId = clientId if (packet.topic === newSubTopic) { if (sub.qos > 0) { that._trie.add(sub.topic, sub) } else { that._trie.remove(sub.topic, sub) } } else if (packet.topic === rmSubTopic) { that._trie.remove(sub.topic, sub) } } var action = packet.topic === newSubTopic ? 'sub' : 'unsub' var waiting = that._waiting[clientId + '-' + action] delete that._waiting[clientId + '-' + action] if (waiting) { process.nextTick(waiting) } cb() } } inherits(CachedPersistence, EE) CachedPersistence.prototype._waitFor = function (client, action, cb) { this._waiting[client.id + '-' + action] = cb } CachedPersistence.prototype._addedSubscriptions = function (client, subs, cb) { if (!this.ready) { this.once('ready', this._addedSubscriptions.bind(this, client, subs, cb)) return } var errored = false this._waitFor(client, 'sub', function (err) { if (!errored && err) { return cb(err) } if (!errored) { cb(null, client) } }) if (subs.length === 0) { return cb(null, client) } var ctx = { cb: cb || noop, client: client, broker: this._broker, topic: newSubTopic, brokerPublish: brokerPublish } ctx.brokerPublish(subs, function (err) { if (err) { errored = true cb(err) } }) } function brokerPublish (subs, cb) { var encoded = JSON.stringify({clientId: this.client.id, subs: subs}) var packet = new Packet({ topic: this.topic, payload: encoded }) this.broker.publish(packet, cb) } function noop () {} CachedPersistence.prototype._removedSubscriptions = function (client, subs, cb) { if (!this.ready) { this.once('ready', this._removedSubscriptions.bind(this, client, subs, cb)) return } var errored = false this._waitFor(client, 'unsub', function (err) { if (!errored && err) { return cb(err) } if (!errored) { cb(null, client) } }) var ctx = { cb: cb || noop, client: client, broker: this._broker, topic: rmSubTopic, brokerPublish: brokerPublish } ctx.brokerPublish(subs, function (err) { if (err) { errored = true cb(err) } }) } CachedPersistence.prototype.subscriptionsByTopic = function (topic, cb) { if (!this.ready) { this.once('ready', this.subscriptionsByTopic.bind(this, topic, cb)) return this } cb(null, this._trie.match(topic)) } CachedPersistence.prototype.cleanSubscriptions = function (client, cb) { var that = this this.subscriptionsByClient(client, function (err, subs, client) { if (err || !subs) { return cb(err, client) } subs = subs.map(subToTopic) that.removeSubscriptions(client, subs, cb) }) } CachedPersistence.prototype.outgoingEnqueueCombi = function (subs, packet, cb) { this._parallel({ persistence: this, packet: packet }, outgoingEnqueue, subs, cb) } function outgoingEnqueue (sub, cb) { this.persistence.outgoingEnqueue(sub, this.packet, cb) } CachedPersistence.prototype.createRetainedStreamCombi = function (patterns) { var that = this var streams = patterns.map(function (p) { return that.createRetainedStream(p) }) return MultiStream.obj(streams) } CachedPersistence.prototype.destroy = function (cb) { this.destroyed = true this.broker.unsubscribe(subTopic, this._onMessage, function () { if (cb) { cb() } }) } // must emit 'ready' CachedPersistence.prototype._setup = function () { this.emit('ready') } function subToTopic (sub) { return sub.topic } Object.defineProperty(CachedPersistence.prototype, 'broker', { enumerable: false, get: function () { return this._broker }, set: function (broker) { this._broker = broker this.broker.subscribe(subTopic, this._onMessage, this._setup.bind(this)) } }) module.exports = CachedPersistence module.exports.Packet = Packet