UNPKG

rocket.chat.mqtt

Version:

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

302 lines (253 loc) 6.84 kB
'use strict' var Buffer = require('safe-buffer').Buffer var mqtt = require('mqtt-packet') var EE = require('events').EventEmitter var util = require('util') var eos = require('end-of-stream') var empty = Buffer.allocUnsafe(0) var Packet = require('aedes-packet') var write = require('./write') var QoSPacket = require('./qos-packet') var handleSubscribe = require('./handlers/subscribe') var handleUnsubscribe = require('./handlers/unsubscribe') var handle = require('./handlers') module.exports = Client function Client (broker, conn) { var that = this this.broker = broker this.conn = conn this.parser = mqtt.parser() this.connected = false this.errored = false this.clean = true this._handling = 0 this.subscriptions = {} this.id = null conn.client = this this.parser.client = this this.duplicates = {} this._parsingBatch = 1 this._nextId = Math.ceil(Math.random() * 65535) this.disconnected = false this.parser.on('packet', enqueue) function nextBatch (err) { if (err) { return that.emit('error', err) } var buf = empty var client = that if (client._paused) { return } that._parsingBatch-- if (that._parsingBatch <= 0) { that._parsingBatch = 0 buf = client.conn.read(null) if (buf) { client.parser.parse(buf) } } } this._nextBatch = nextBatch this.on('error', onError) nextBatch() conn.on('readable', nextBatch) conn.on('error', this.emit.bind(this, 'error')) this.parser.on('error', this.emit.bind(this, 'error')) this._eos = eos(this.conn, this.close.bind(this)) function dedupe (packet) { var duplicates = that.duplicates var id = packet.brokerId if (!id) { return true } var counter = packet.brokerCounter var result = (duplicates[id] || 0) < counter if (result) { duplicates[id] = counter } return result } this.deliver0 = function deliverQoS0 (_packet, cb) { var toForward = dedupe(_packet) && that.broker.authorizeForward(that, _packet) if (toForward) { var packet = new Packet(toForward, broker) packet.qos = 0 write(that, packet, cb) } else { cb() } } this.deliverQoS = function deliverQoS (_packet, cb) { // downgrade to qos0 if requested by publish if (_packet.qos === 0) { that.deliver0(_packet, cb) } else { var toForward = dedupe(_packet) && that.broker.authorizeForward(that, _packet) if (toForward) { var packet = new QoSPacket(toForward, that) packet.writeCallback = cb if (that.clean) { writeQoS(null, that, packet) } else { broker.persistence.outgoingUpdate(that, packet, writeQoS) } } else if (that.clean === false) { that.broker.persistence.outgoingClearMessageId(that, _packet, nop) // we consider this to be an error, since the packet is undefined // so there's nothing to send cb() } else { cb() } } } this._keepaliveTimer = null this._keepaliveInterval = -1 this._connectTimer = setTimeout(function () { that.emit('error', new Error('connect did not arrive in time')) }, broker.connectTimeout) } function writeQoS (err, client, packet) { if (err) { // is this right, or we should ignore thins? client.emit('error', err) } else { write(client, packet, packet.writeCallback) } } function drainRequest (req) { req.callback() } function onError (err) { this.errored = true this.conn.removeAllListeners('error') this.conn.on('error', nop) if (this.id) { this.broker.emit('clientError', this, err) } else { this.broker.emit('connectionError', this, err) } this.close() } util.inherits(Client, EE) Client.prototype._onError = onError Client.prototype.publish = function (message, done) { var packet = new Packet(message, this.broker) var that = this if (packet.qos === 0) { // skip offline and send it as it is this.deliver0(packet, done || nop) } else if (!this.clean && this.id) { this.broker.persistence.outgoingEnqueue({ clientId: this.id }, packet, function deliver (err) { if (err) { return done(err) } that.deliverQoS(packet, done) }) } else { that.deliverQoS(packet, done) } } Client.prototype.subscribe = function (packet, done) { if (!packet.subscriptions) { if (!Array.isArray(packet)) { packet = [packet] } packet = { subscriptions: packet } } handleSubscribe(this, packet, done) } Client.prototype.unsubscribe = function (packet, done) { if (!packet.unsubscriptions) { if (!Array.isArray(packet)) { packet = [packet] } packet = { unsubscriptions: packet } } handleUnsubscribe(this, packet, done) } Client.prototype.close = function (done) { var that = this var conn = this.conn if (this.connected) { handleUnsubscribe( this, { close: true, unsubscriptions: Object.keys(this.subscriptions) }, finish) } else { finish() } this.parser.removeAllListeners('packet') conn.removeAllListeners('readable') if (this._keepaliveTimer) { this._keepaliveTimer.clear() this._keepaliveInterval = -1 this._keepaliveTimer = null } if (this._connectTimer) { clearTimeout(this._connectTimer) this._connectTimer = null } this._eos() this._eos = nop function finish () { if (!that.disconnected && that.will) { that.broker.publish(that.will, that, function (err) { if (!err) { that.broker.persistence.delWill({ id: that.id, brokerId: that.broker.id }, nop) } }) that.will = null // this function might be called twice } conn.removeAllListeners('error') conn.on('error', nop) if (that.broker.clients[that.id]) { that.broker.unregisterClient(that) } // hack to clean up the write callbacks // supports streams2 & streams3, so node 0.10, 0.11, and iojs var state = that.conn._writableState var list = (state.getBuffer && state.getBuffer()) || state.buffer list.forEach(drainRequest) // clear up the drain event listeners that.conn.emit('drain') that.conn.removeAllListeners('drain') if (conn.destroySoon) { conn.destroySoon() } if (conn.destroy) { conn.destroy() } else { conn.end() } if (typeof done === 'function') { done() } } } Client.prototype.pause = function () { this._paused = true } Client.prototype.resume = function () { this._paused = false this._nextBatch() } function enqueue (packet) { this.client._parsingBatch++ handle(this.client, packet, this.client._nextBatch) } function nop () {}