nxkit
Version:
This is a collection of tools, independent of any other libraries
1,106 lines (1,103 loc) • 37.4 kB
JavaScript
"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;