UNPKG

nxkit

Version:

This is a collection of tools, independent of any other libraries

1,106 lines (1,103 loc) 37.4 kB
"use strict"; /* ***** BEGIN LICENSE BLOCK ***** * Distributed under the BSD license: * * Copyright (c) 2015, xuewen.chu * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of xuewen.chu nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL xuewen.chu BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * ***** END LICENSE BLOCK ***** */ Object.defineProperty(exports, "__esModule", { value: true }); const events = require("events"); const store_1 = require("./store"); const end_of_stream_1 = require("./end-of-stream"); const writeToStream_1 = require("./writeToStream"); const parser_1 = require("./parser"); const stream_1 = require("stream"); const reinterval_1 = require("./reinterval"); const validations_1 = require("./validations"); const url = require("url"); const net = require("net"); const tls = require("tls"); var protocols = { mqtt: stream_builder_tcp, tcp: stream_builder_tcp, mqtts: stream_builder_ssl, ssl: stream_builder_ssl, tls: stream_builder_ssl, }; function defaultId() { return 'mqttjs_' + Math.random().toString(16).substr(2, 8); } function nop() { } /* variables port and host can be removed since you have all required information in opts object */ function stream_builder_tcp(mqttClient, opts) { opts.port = opts.port || 1883; opts.hostname = opts.hostname || opts.host || '127.0.0.1'; return net.createConnection(opts.port, opts.hostname); } function stream_builder_ssl(mqttClient, opts) { opts.port = opts.port || 8883; opts.host = opts.hostname || opts.host || '127.0.0.1'; opts.rejectUnauthorized = opts.rejectUnauthorized !== false; // delete opts.path; var stream = tls.connect(opts); /* eslint no-use-before-define: [2, "nofunc"] */ stream.on('secureConnect', function () { if (opts.rejectUnauthorized && !stream.authorized) { stream.emit('error', new Error('TLS not authorized')); } else { stream.removeListener('error', handleTLSerrors); } }); function handleTLSerrors(err) { // How can I get verify this error is a tls error? if (opts.rejectUnauthorized) { mqttClient.emit('error', err); } // close this connection to match the behaviour of net // otherwise all we get is an error from the connection // and close event doesn't fire. This is a work around // to enable the reconnect code to work the same as with // net.createConnection stream.end(); } stream.on('error', handleTLSerrors); return stream; } /** * Parse the auth attribute and merge username and password in the options object. * * @param {Object} [opts] option object */ function parseAuthOptions(opts) { if (opts.auth) { var matches = opts.auth.match(/^(.+):(.+)$/); if (matches) { opts.username = matches[1]; opts.password = matches[2]; } else { opts.username = opts.auth; } } } /** * @param {Object} opts */ function resolveOptions(opts) { // Default options var options = Object.assign({ url: 'mqtt://127.0.0.1:1883', keepalive: 60, reschedulePings: true, protocolId: 'MQTT', protocolVersion: 4, protocol: 'mqtt', reconnectPeriod: 1000, connectTimeout: 30 * 1000, clean: true, resubscribe: true, clientId: defaultId(), outgoingStore: new store_1.default(), incomingStore: new store_1.default(), queueQoSZero: true, }, typeof opts == 'string' ? { url: opts } : opts); if (options.url) { opts = Object.assign(url.parse(options.url, true), opts); if (!options.protocol) { throw new Error('Missing protocol'); } options.protocol = options.protocol.replace(/:$/, ''); } options.port = Number(options.port) || 1883; // merge in the auth options if supplied parseAuthOptions(options); // support clientId passed in the query string of the url if (options.query && typeof options.query.clientId === 'string') { options.clientId = options.query.clientId; } if (options.cert && options.key) { options.protocol = 'mqtts'; } if (!protocols[options.protocol]) { options.protocol = 'mqtt'; } if (options.clean === false && !options.clientId) { throw new Error('Missing clientId for unclean clients'); } return options; } /** * @class MqttClient */ class MqttClient extends events.EventEmitter { /** * MqttClient constructor * * @param {Object} [options] - connection options * (see Connection#connect) */ constructor(options) { super(); this._reconnectCount = 0; // map of subscribed topics to support reconnection this._resubscribeTopics = {}; // map of a subscribe messageId and a topic this._messageIdToTopic = {}; // Ping timer, setup in _setupPingTimer this._pingTimer = null; // Packet queue this._queue = []; /** * MessageIDs starting with 1 * ensure that nextId is min. 1, see https://github.com/mqttjs/MQTT.js/issues/810 */ this._nextId = Math.max(1, Math.floor(Math.random() * 65535)); // Inflight callbacks this._outgoing = new Map(); this._stream = null; this._deferredReconnect = null; this._pingResp = false; this._connected = false; this._disconnecting = false; this._disconnected = false; this._reconnecting = false; // resolve options options = resolveOptions(options); this._protocol = options.protocol; this.options = options; // Inflight message storages this._outgoingStore = this.options.outgoingStore; this._incomingStore = this.options.incomingStore; // Should QoS zero messages be queued when the connection is broken? this._queueQoSZero = this.options.queueQoSZero; var that = this; // Mark connected on connect this.on('connect', () => { if (this.disconnected) { return; } this._connected = true; var outStore = this._outgoingStore.createStream(); this.once('close', remove); outStore.on('end', function () { that.removeListener('close', remove); }); outStore.on('error', function (err) { that.removeListener('close', remove); that.emit('error', err); }); function remove() { var _a; (_a = outStore) === null || _a === void 0 ? void 0 : _a.destroy(); outStore = null; } function storeDeliver() { // edge case, we wrapped this twice if (!outStore) { return; } var packet = outStore.read(1); var cb; if (!packet) { // read when data is available in the future outStore.once('readable', storeDeliver); return; } // Avoid unnecessary stream read operations when disconnected if (!that.disconnecting && !that._reconnectTimer) { cb = that._outgoing.get(packet.messageId); that._outgoing.set(packet.messageId, function (err, status) { // Ensure that the original callback passed in to publish gets invoked if (cb) { cb(err, status); } storeDeliver(); }); that._sendPacket(packet); } else if (outStore.destroy) { outStore.destroy(); } } // start flowing storeDeliver(); }); // Mark disconnected on stream close this.on('close', () => { this._connected = false; clearTimeout(this._connackTimer); }); // Setup ping timer this.on('connect', this._setupPingTimer); // Send queued packets this.on('connect', () => { var queue = this._queue; function deliver() { var entry = queue.shift(); var packet = null; if (!entry) return; packet = entry.packet; that._sendPacket(packet, function (err) { var _a; if ((_a = entry) === null || _a === void 0 ? void 0 : _a.cb) { entry.cb(err); } deliver(); }); } deliver(); }); var firstConnection = true; this.on('connect', e => { if (!firstConnection && this.options.clean) { if (Object.keys(this._resubscribeTopics).length > 0) { if (this.options.resubscribe) { this.subscribe(this._resubscribeTopics, { resubscribe: true }); } else { this._resubscribeTopics = {}; } } } firstConnection = false; }); // Clear ping timer this.on('close', () => { if (that._pingTimer) { that._pingTimer.clear(); that._pingTimer = null; } }); // Setup reconnect timer on disconnect this.on('close', this._setupReconnect); this._setupStream(); } // Is the client connected? get connected() { return this._connected; } // Are we disconnecting? get disconnecting() { return this._disconnecting; } get disconnected() { return this._disconnected; } get reconnecting() { return this._reconnecting; } get nextId() { return this._nextId; } _flush() { var self = this; var queue = self._outgoing; if (queue) { for (var [key, value] of queue) { if (typeof value === 'function') { value(new Error('Connection closed')); queue.delete(key); } } } } __sendPacket(packet, cb) { var self = this; self.emit('packetsend', packet); var stream = self._stream; var result = writeToStream_1.default(packet, stream); if (!result && cb) { stream.once('drain', cb); } else if (cb) { cb(); } } _storeAndSend(packet, cb) { var self = this; self._outgoingStore.put(packet, (err) => { if (err) { return cb && cb(err); } this.__sendPacket(packet, cb); }); } /** * @func stream_builder() */ stream_builder() { var self = this; var opts = self.options; if (opts.servers) { if (!self._reconnectCount || self._reconnectCount === opts.servers.length) { self._reconnectCount = 0; } opts.host = opts.servers[self._reconnectCount].host; opts.port = opts.servers[self._reconnectCount].port; opts.protocol = opts.servers[self._reconnectCount].protocol || this._protocol; opts.hostname = opts.host; self._reconnectCount++; } return protocols[opts.protocol](self, opts); } /** * setup the event handlers in the inner stream. * * @api private */ _setupStream() { var that = this; var writable = new stream_1.Writable(); var parser = new parser_1.default( /*this.options*/); var completeParse = null; var packets = []; this._clearReconnect(); this._stream = this.stream_builder(); parser.on('packet', function (packet) { packets.push(packet); }); function nextTickWork() { process.nextTick(work); } function work() { var packet = packets.shift(); var done = completeParse; if (packet) { that._handlePacket(packet, nextTickWork); } else { completeParse = null; done(); } } writable._write = function (buf, enc, done) { completeParse = done; parser.parse(buf); work(); }; this._stream.pipe(writable); // Suppress connection errors this._stream.on('error', nop); // Echo stream close end_of_stream_1.default(this._stream, {}, () => this.emit('close')); // Send a connect packet var connectPacket = Object.assign(Object.create(this.options), { cmd: 'connect' }); // avoid message queue this.__sendPacket(connectPacket); // Echo connection errors parser.on('error', this.emit.bind(this, 'error')); // many drain listeners are needed for qos 1 callbacks if the connection is intermittent this._stream.setMaxListeners(1000); clearTimeout(this._connackTimer); this._connackTimer = setTimeout(() => { that._cleanUp(true); }, this.options.connectTimeout); } _handlePacket(packet, done) { this.emit('packetreceive', packet); switch (packet.cmd) { case 'publish': this._handlePublish(packet, done); break; case 'puback': case 'pubrec': case 'pubcomp': case 'suback': case 'unsuback': this._handleAck(packet); done(); break; case 'pubrel': this._handlePubrel(packet, done); break; case 'connack': this._handleConnack(packet); done(); break; case 'pingresp': this._handlePingresp(); done(); break; default: // do nothing // maybe we should do an error handling // or just log it break; } } _checkDisconnecting(callback) { if (this.disconnecting) { if (callback) { callback(new Error('client disconnecting')); } else { this.emit('error', new Error('client disconnecting')); } } return this.disconnecting; } /** * publish - publish <message> to <topic> * * @param {String} topic - topic to publish to * @param {String, Buffer} message - message to publish * @param {Object} [opts] - publish options, includes: * {Number} qos - qos level to publish on * {Boolean} retain - whether or not to retain the message * {Boolean} dup - whether or not mark a message as duplicate * @param {Function} [callback] - function(err){} * called when publish succeeds or fails * @returns {MqttClient} this - for chaining * @api public * * @example client.publish('topic', 'message'); * @example * client.publish('topic', 'message', {qos: 1, retain: true, dup: true}); * @example client.publish('topic', 'message', console.log); */ publish(topic, message, opts, callback) { var packet; // .publish(topic, payload, cb); if (typeof opts === 'function') { callback = opts; opts = {}; } // default opts var options = Object.assign({ qos: 0, retain: false, dup: false }, opts); if (this._checkDisconnecting(callback)) { return this; } packet = { cmd: 'publish', topic: topic, payload: message, qos: options.qos, retain: options.retain, messageId: this.__nextId(), dup: options.dup, length: 0 }; switch (options.qos) { case 1: case 2: // Add to callbacks this._outgoing.set(packet.messageId, callback || nop); this._sendPacket(packet); break; default: this._sendPacket(packet, callback); break; } return this; } /** * subscribe - subscribe to <topic> * * @param {String, Array, Object} topic - topic(s) to subscribe to, supports objects in the form {'topic': qos} * @param {Object} [opts] - optional subscription options, includes: * {Number} qos - subscribe qos level * @param {Function} [callback] - function(err, granted){} where: * {Error} err - subscription error (none at the moment!) * {Array} granted - array of {topic: 't', qos: 0} * @returns {MqttClient} this - for chaining * @api public * @example client.subscribe('topic'); * @example client.subscribe('topic', {qos: 1}); * @example client.subscribe({'topic': 0, 'topic2': 1}, console.log); * @example client.subscribe('topic', console.log); */ subscribe(topics, opts = {}, callback = nop) { if (typeof topics === 'string') { topics = [topics]; } if (typeof opts == 'function') { callback = opts; opts = {}; } var qos = opts.qos || 0; var _topics = Array.isArray(topics) ? topics.map(e => [e, qos]) : Object.entries(topics); var invalidTopic = validations_1.default.validateTopics(_topics); if (invalidTopic) { setImmediate(callback, new Error('Invalid topic ' + invalidTopic)); return this; } if (this._checkDisconnecting(callback)) { return this; } var subs = []; var resubscribe = opts.resubscribe; for (var [k, _qos] of _topics) { if (!this._resubscribeTopics.hasOwnProperty(k) || this._resubscribeTopics[k] < _qos || resubscribe) { subs.push({ topic: k, qos: _qos, }); } } var packet = { cmd: 'subscribe', subscriptions: subs, qos: 1, retain: false, dup: false, messageId: this.__nextId(), }; if (!subs.length) { callback(undefined, []); return; } // subscriptions to resubscribe to in case of disconnect if (this.options.resubscribe) { this._messageIdToTopic[packet.messageId] = topics = []; for (var sub of subs) { if (this.options.reconnectPeriod > 0) { this._resubscribeTopics[sub.topic] = sub.qos; topics.push(sub.topic); } } } this._outgoing.set(packet.messageId, function (err, packet) { if (!err) { var granted = packet.granted; for (var i = 0; i < granted.length; i++) { subs[i].qos = granted[i]; } } callback(err, subs); }); this._sendPacket(packet); return this; } /** * unsubscribe - unsubscribe from topic(s) * * @param {String, Array} topic - topics to unsubscribe from * @param {Function} [callback] - callback fired on unsuback * @returns {MqttClient} this - for chaining * @api public * @example client.unsubscribe('topic'); * @example client.unsubscribe('topic', console.log); */ unsubscribe(topic, callback = nop) { var packet = { cmd: 'unsubscribe', qos: 1, messageId: this.__nextId(), }; if (this._checkDisconnecting(callback)) { return this; } if (typeof topic === 'string') { packet.unsubscriptions = [topic]; } else if (typeof topic === 'object' && topic.length) { packet.unsubscriptions = topic; } if (this.options.resubscribe) { packet.unsubscriptions.forEach(topic => delete this._resubscribeTopics[topic]); } this._outgoing.set(packet.messageId, callback); this._sendPacket(packet); return this; } /** * end - close connection * * @returns {MqttClient} this - for chaining * @param {Boolean} force - do not wait for all in-flight messages to be acked * @param {Function} cb - called when the client has been closed * * @api public */ end(force, cb) { var that = this; if (typeof force === 'function') { cb = force; force = false; } function closeStores() { that._disconnected = true; that._incomingStore.close(function () { that._outgoingStore.close(function (...args) { if (cb) { cb(...args); } that.emit('end'); }); }); if (that._deferredReconnect) { that._deferredReconnect(); } } function finish() { // defer closesStores of an I/O cycle, // just to make sure things are // ok for websockets that._cleanUp(force, setImmediate.bind(null, closeStores)); } if (this.disconnecting) { return this; } this._clearReconnect(); this._disconnecting = true; if (!force && Object.keys(this._outgoing).length > 0) { // wait 10ms, just to be sure we received all of it this.once('outgoingEmpty', setTimeout.bind(null, finish, 10)); } else { finish(); } return this; } /** * removeOutgoingMessage - remove a message in outgoing store * the outgoing callback will be called withe Error('Message removed') if the message is removed * * @param {Number} mid - messageId to remove message * @returns {MqttClient} this - for chaining * @api public * * @example client.removeOutgoingMessage(client.getLastMessageId()); */ removeOutgoingMessage(mid) { var cb = this._outgoing.get(mid); if (cb) { this._outgoing.delete(mid); this._outgoingStore.del({ messageId: mid }, function () { cb(new Error('Message removed')); }); } return this; } /** * reconnect - connect again using the same options as connect() * * @param {Object} [opts] - optional reconnect options, includes: * {Store} incomingStore - a store for the incoming packets * {Store} outgoingStore - a store for the outgoing packets * if opts is not given, current stores are used * @returns {MqttClient} this - for chaining * * @api public */ reconnect(opts = {}) { var that = this; var f = function () { if (opts) { that.options.incomingStore = opts.incomingStore; that.options.outgoingStore = opts.outgoingStore; } else { that.options.incomingStore = undefined; that.options.outgoingStore = undefined; } that._incomingStore = that.options.incomingStore || new store_1.default(); that._outgoingStore = that.options.outgoingStore || new store_1.default(); that._disconnecting = false; that._disconnected = false; that._deferredReconnect = null; that._reconnect(); }; if (this.disconnecting && !this.disconnected) { this._deferredReconnect = f; } else { f(); } return this; } /** * _reconnect - implement reconnection * @api privateish */ _reconnect() { this.emit('reconnect'); this._setupStream(); } /** * _setupReconnect - setup reconnect timer */ _setupReconnect() { var that = this; if (!that.disconnecting && !that._reconnectTimer && (that.options.reconnectPeriod > 0)) { if (!this.reconnecting) { this.emit('offline'); this._reconnecting = true; } that._reconnectTimer = setInterval(function () { that._reconnect(); }, that.options.reconnectPeriod); } } /** * _clearReconnect - clear the reconnect timer */ _clearReconnect() { if (this._reconnectTimer) { clearInterval(this._reconnectTimer); this._reconnectTimer = null; } } /** * _cleanUp - clean up on connection end * @api private */ _cleanUp(forced, done) { var _a, _b, _c; if (done) { this._stream.on('close', done); } if (forced) { if ((this.options.reconnectPeriod === 0) && this.options.clean) { this._flush(); } (_a = this._stream) === null || _a === void 0 ? void 0 : _a.destroy(); } else { this._sendPacket({ cmd: 'disconnect' }, e => { setImmediate(e => { var _a; return (_a = this._stream) === null || _a === void 0 ? void 0 : _a.end(); }); }); } if (!this.disconnecting) { this._clearReconnect(); this._setupReconnect(); } if (this._pingTimer !== null) { (_b = this._pingTimer) === null || _b === void 0 ? void 0 : _b.clear(); this._pingTimer = null; } if (done && !this.connected) { (_c = this._stream) === null || _c === void 0 ? void 0 : _c.removeListener('close', done); done(); } } /** * _sendPacket - send or queue a packet * @param {String} type - packet type (see `protocol`) * @param {Object} packet - packet options * @param {Function} cb - callback when the packet is sent * @api private */ _sendPacket(packet, cb) { if (!this.connected) { var qos = packet.qos; if ((qos === 0 && this._queueQoSZero) || packet.cmd !== 'publish') { this._queue.push({ packet: packet, cb: cb }); } else if (qos > 0) { cb = this._outgoing.get(packet.messageId); this._outgoingStore.put(packet, function (err) { if (err) { return cb && cb(err); } }); } else if (cb) { cb(new Error('No connection to broker')); } return; } // When sending a packet, reschedule the ping timer this._shiftPingInterval(); switch (packet.cmd) { case 'publish': break; case 'pubrel': this._storeAndSend(packet, cb); return; default: this.__sendPacket(packet, cb); return; } switch (packet.qos) { case 2: case 1: this._storeAndSend(packet, cb); break; /** * no need of case here since it will be caught by default * and jshint comply that before default it must be a break * anyway it will result in -1 evaluation */ case 0: /* falls through */ default: this.__sendPacket(packet, cb); break; } } /** * _setupPingTimer - setup the ping timer * * @api private */ _setupPingTimer() { var that = this; if (!this._pingTimer && this.options.keepalive) { this._pingResp = true; this._pingTimer = reinterval_1.default(function () { that._checkPing(); }, this.options.keepalive * 1000); } } /** * _shiftPingInterval - reschedule the ping interval * * @api private */ _shiftPingInterval() { if (this._pingTimer && this.options.keepalive && this.options.reschedulePings) { this._pingTimer.reschedule(this.options.keepalive * 1000); } } /** * _checkPing - check if a pingresp has come back, and ping the server again * * @api private */ _checkPing() { if (this._pingResp) { this._pingResp = false; this._sendPacket({ cmd: 'pingreq' }); } else { // do a forced cleanup since socket will be in bad shape this._cleanUp(true); } } /** * _handlePingresp - handle a pingresp * * @api private */ _handlePingresp() { this._pingResp = true; } /** * _handleConnack * * @param {Object} packet * @api private */ _handleConnack(packet) { var rc = packet.returnCode; var errors = [ '', 'Unacceptable protocol version', 'Identifier rejected', 'Server unavailable', 'Bad username or password', 'Not authorized', ]; clearTimeout(this._connackTimer); if (rc === 0) { this._reconnecting = false; this.emit('connect', packet); } else if (rc > 0) { var err = new Error('Connection refused: ' + errors[rc]); err.code = rc; this.emit('error', err); } } /** * _handlePublish * * @param {Object} packet * @api private */ /* those late 2 case should be rewrite to comply with coding style: case 1: case 0: // do not wait sending a puback // no callback passed if (1 === qos) { this._sendPacket({ cmd: 'puback', messageId: mid }); } // emit the message event for both qos 1 and 0 this.emit('message', topic, message, packet); this.handleMessage(packet, done); break; default: // do nothing but every switch mus have a default // log or throw an error about unknown qos break; for now i just suppressed the warnings */ _handlePublish(packet, done = nop) { var _a; var topic = (_a = packet.topic) === null || _a === void 0 ? void 0 : _a.toString(); var message = packet.payload; var qos = packet.qos; var mid = packet.messageId; var that = this; switch (qos) { case 2: this._incomingStore.put(packet, function (err) { if (err) { return done(err); } that._sendPacket({ cmd: 'pubrec', messageId: mid }, done); }); break; case 1: // emit the message event this.emit('message', topic, message, packet); this.handleMessage(packet, function (err) { if (err) { return done(err); } // send 'puback' if the above 'handleMessage' method executed // successfully. that._sendPacket({ cmd: 'puback', messageId: mid }, done); }); break; case 0: // emit the message event this.emit('message', topic, message, packet); this.handleMessage(packet, done); break; default: // do nothing // log or throw an error about unknown qos break; } } /** * Handle messages with backpressure support, one at a time. * Override at will. * * @param Packet packet the packet * @param Function callback call when finished * @api public */ handleMessage(packet, callback) { callback && callback(); } /** * _handleAck * * @param {Object} packet * @api private */ _handleAck(packet) { var _a; /* eslint no-fallthrough: "off" */ var mid = packet.messageId; var cb = this._outgoing.get(mid); if (!cb) { // Server sent an ack in error, ignore it. return; } // Process switch (packet.cmd) { case 'pubcomp': // same thing as puback for QoS 2 case 'puback': // Callback - we're done this._outgoing.delete(mid); this._outgoingStore.del(packet, cb); break; case 'pubrec': this._sendPacket({ cmd: 'pubrel', qos: 2, messageId: mid }); break; case 'suback': this._outgoing.delete(mid); if (((_a = packet.granted) === null || _a === void 0 ? void 0 : _a.length) === 1 && (packet.granted[0] & 0x80) !== 0) { // suback with Failure status var topics = this._messageIdToTopic[mid]; if (topics) { topics.forEach(topic => delete this._resubscribeTopics[topic]); } } cb(undefined, packet); break; case 'unsuback': this._outgoing.delete(mid); cb(undefined); break; default: this.emit('error', new Error('unrecognized packet type')); } if (this.disconnecting && Object.keys(this._outgoing).length === 0) { this.emit('outgoingEmpty'); } } /** * _handlePubrel * * @param {Object} packet * @api private */ _handlePubrel(packet, callback) { callback = typeof callback !== 'undefined' ? callback : nop; var mid = packet.messageId; var that = this; var comp = { cmd: 'pubcomp', messageId: mid }; that._incomingStore.get(packet, function (err, pub) { if (!err && pub.cmd !== 'pubrel') { that.emit('message', pub.topic, pub.payload, pub); that._incomingStore.put(packet, function (err) { if (err) { return callback(err); } that.handleMessage(pub, function (err) { if (err) { return callback(err); } that._sendPacket(comp, callback); }); }); } else { that._sendPacket(comp, callback); } }); } /** * _nextId * @return unsigned int */ __nextId() { // id becomes current state of this.nextId and increments afterwards var id = this._nextId++; // Ensure 16 bit unsigned int (max 65535, nextId got one higher) if (this.nextId === 65536) { this._nextId = 1; } return id; } /** * getLastMessageId * @return unsigned int */ getLastMessageId() { return (this.nextId === 1) ? 65535 : (this.nextId - 1); } } exports.MqttClient = MqttClient;