From ce4ec57fe4a16411402f484f32b51ea92458581e Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 10 Jul 2023 16:53:49 +0200 Subject: [PATCH 1/8] chore!: remove unused deps and make `new` required BREAKING CHANGE: when creating an `MqttClient` instance `new` is now required --- lib/client.js | 2885 ++++++++++++++++++++++++------------------------ test/client.js | 6 +- 2 files changed, 1461 insertions(+), 1430 deletions(-) diff --git a/lib/client.js b/lib/client.js index 13306a0c6..7613db2be 100644 --- a/lib/client.js +++ b/lib/client.js @@ -10,7 +10,6 @@ const TopicAliasSend = require('./topic-alias-send') const mqttPacket = require('mqtt-packet') const DefaultMessageIdProvider = require('./default-message-id-provider') const Writable = require('readable-stream').Writable -const inherits = require('inherits') const reInterval = require('reinterval') const clone = require('rfdc/default') const validations = require('./validations') @@ -247,1672 +246,1704 @@ function nop (error) { * @param {Object} [options] - connection options * (see Connection#connect) */ -function MqttClient (streamBuilder, options) { - let k - const that = this +class MqttClient extends EventEmitter { + + constructor(streamBuilder, options) { + super() - if (!(this instanceof MqttClient)) { - return new MqttClient(streamBuilder, options) - } + let k + const that = this - this.options = options || {} + this.options = options || {} - // Defaults - for (k in defaultConnectOptions) { - if (typeof this.options[k] === 'undefined') { - this.options[k] = defaultConnectOptions[k] - } else { - this.options[k] = options[k] + // Defaults + for (k in defaultConnectOptions) { + if (typeof this.options[k] === 'undefined') { + this.options[k] = defaultConnectOptions[k] + } else { + this.options[k] = options[k] + } } - } - - debug('MqttClient :: options.protocol', options.protocol) - debug('MqttClient :: options.protocolVersion', options.protocolVersion) - debug('MqttClient :: options.username', options.username) - debug('MqttClient :: options.keepalive', options.keepalive) - debug('MqttClient :: options.reconnectPeriod', options.reconnectPeriod) - debug('MqttClient :: options.rejectUnauthorized', options.rejectUnauthorized) - debug('MqttClient :: options.properties.topicAliasMaximum', options.properties ? options.properties.topicAliasMaximum : undefined) - this.options.clientId = (typeof options.clientId === 'string') ? options.clientId : defaultId() + debug('MqttClient :: options.protocol', options.protocol) + debug('MqttClient :: options.protocolVersion', options.protocolVersion) + debug('MqttClient :: options.username', options.username) + debug('MqttClient :: options.keepalive', options.keepalive) + debug('MqttClient :: options.reconnectPeriod', options.reconnectPeriod) + debug('MqttClient :: options.rejectUnauthorized', options.rejectUnauthorized) + debug('MqttClient :: options.properties.topicAliasMaximum', options.properties ? options.properties.topicAliasMaximum : undefined) - debug('MqttClient :: clientId', this.options.clientId) + this.options.clientId = (typeof options.clientId === 'string') ? options.clientId : defaultId() - this.options.customHandleAcks = (options.protocolVersion === 5 && options.customHandleAcks) ? options.customHandleAcks : function () { arguments[3](0) } + debug('MqttClient :: clientId', this.options.clientId) - // Disable pre-generated write cache if requested. Will allocate buffers on-the-fly instead. WARNING: This can affect write performance - if (!this.options.writeCache) { - mqttPacket.writeToStream.cacheNumbers = false - } + this.options.customHandleAcks = (options.protocolVersion === 5 && options.customHandleAcks) ? options.customHandleAcks : function () { arguments[3](0) } - this.streamBuilder = streamBuilder - - this.messageIdProvider = (typeof this.options.messageIdProvider === 'undefined') ? new DefaultMessageIdProvider() : this.options.messageIdProvider - - // Inflight message storages - this.outgoingStore = options.outgoingStore || new Store() - this.incomingStore = options.incomingStore || new Store() - - // Should QoS zero messages be queued when the connection is broken? - this.queueQoSZero = options.queueQoSZero === undefined ? true : options.queueQoSZero - - // 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 - // Is the client connected? - this.connected = false - // Are we disconnecting? - this.disconnecting = false - // Packet queue - this.queue = [] - // connack timer - this.connackTimer = null - // Reconnect timer - this.reconnectTimer = null - // Is processing store? - this._storeProcessing = false - // Packet Ids are put into the store during store processing - this._packetIdsDuringStoreProcessing = {} - // Store processing queue - this._storeProcessingQueue = [] - - // Inflight callbacks - this.outgoing = {} - - // True if connection is first time. - this._firstConnection = true - - if (options.properties && options.properties.topicAliasMaximum > 0) { - if (options.properties.topicAliasMaximum > 0xffff) { - debug('MqttClient :: options.properties.topicAliasMaximum is out of range') - } else { - this.topicAliasRecv = new TopicAliasRecv(options.properties.topicAliasMaximum) + // Disable pre-generated write cache if requested. Will allocate buffers on-the-fly instead. WARNING: This can affect write performance + if (!this.options.writeCache) { + mqttPacket.writeToStream.cacheNumbers = false } - } - // Send queued packets - this.on('connect', function () { - const queue = that.queue + this.streamBuilder = streamBuilder - function deliver () { - const entry = queue.shift() - debug('deliver :: entry %o', entry) - let packet = null + this.messageIdProvider = (typeof this.options.messageIdProvider === 'undefined') ? new DefaultMessageIdProvider() : this.options.messageIdProvider - if (!entry) { - that._resubscribe() - return + // Inflight message storages + this.outgoingStore = options.outgoingStore || new Store() + this.incomingStore = options.incomingStore || new Store() + + // Should QoS zero messages be queued when the connection is broken? + this.queueQoSZero = options.queueQoSZero === undefined ? true : options.queueQoSZero + + // 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 + // Is the client connected? + this.connected = false + // Are we disconnecting? + this.disconnecting = false + // Packet queue + this.queue = [] + // connack timer + this.connackTimer = null + // Reconnect timer + this.reconnectTimer = null + // Is processing store? + this._storeProcessing = false + // Packet Ids are put into the store during store processing + this._packetIdsDuringStoreProcessing = {} + // Store processing queue + this._storeProcessingQueue = [] + + // Inflight callbacks + this.outgoing = {} + + // True if connection is first time. + this._firstConnection = true + + if (options.properties && options.properties.topicAliasMaximum > 0) { + if (options.properties.topicAliasMaximum > 0xffff) { + debug('MqttClient :: options.properties.topicAliasMaximum is out of range') + } else { + this.topicAliasRecv = new TopicAliasRecv(options.properties.topicAliasMaximum) } + } + + // Send queued packets + this.on('connect', function () { + const queue = that.queue + + function deliver() { + const entry = queue.shift() + debug('deliver :: entry %o', entry) + let packet = null - packet = entry.packet - debug('deliver :: call _sendPacket for %o', packet) - let send = true - if (packet.messageId && packet.messageId !== 0) { - if (!that.messageIdProvider.register(packet.messageId)) { - send = false + if (!entry) { + that._resubscribe() + return } - } - if (send) { - that._sendPacket( - packet, - function (err) { - if (entry.cb) { - entry.cb(err) - } - deliver() + + packet = entry.packet + debug('deliver :: call _sendPacket for %o', packet) + let send = true + if (packet.messageId && packet.messageId !== 0) { + if (!that.messageIdProvider.register(packet.messageId)) { + send = false } - ) - } else { - debug('messageId: %d has already used. The message is skipped and removed.', packet.messageId) - deliver() + } + if (send) { + that._sendPacket( + packet, + function (err) { + if (entry.cb) { + entry.cb(err) + } + deliver() + } + ) + } else { + debug('messageId: %d has already used. The message is skipped and removed.', packet.messageId) + deliver() + } } - } - debug('connect :: sending queued packets') - deliver() - }) + debug('connect :: sending queued packets') + deliver() + }) - this.on('close', function () { - debug('close :: connected set to `false`') - that.connected = false + this.on('close', function () { + debug('close :: connected set to `false`') + that.connected = false - debug('close :: clearing connackTimer') - clearTimeout(that.connackTimer) + debug('close :: clearing connackTimer') + clearTimeout(that.connackTimer) - debug('close :: clearing ping timer') - if (that.pingTimer !== null) { - that.pingTimer.clear() - that.pingTimer = null - } + debug('close :: clearing ping timer') + if (that.pingTimer !== null) { + that.pingTimer.clear() + that.pingTimer = null + } - if (that.topicAliasRecv) { - that.topicAliasRecv.clear() - } + if (that.topicAliasRecv) { + that.topicAliasRecv.clear() + } - debug('close :: calling _setupReconnect') - that._setupReconnect() - }) - EventEmitter.call(this) + debug('close :: calling _setupReconnect') + that._setupReconnect() + }) + EventEmitter.call(this) - debug('MqttClient :: setting up stream') - this._setupStream() -} -inherits(MqttClient, EventEmitter) + debug('MqttClient :: setting up stream') + this._setupStream() + } + + /** + * setup the event handlers in the inner stream. + * + * @api private + */ + _setupStream() { + const that = this + const writable = new Writable() + const parser = mqttPacket.parser(this.options) + let completeParse = null + const packets = [] + + debug('_setupStream :: calling method to clear reconnect') + this._clearReconnect() -/** - * setup the event handlers in the inner stream. - * - * @api private - */ -MqttClient.prototype._setupStream = function () { - const that = this - const writable = new Writable() - const parser = mqttPacket.parser(this.options) - let completeParse = null - const packets = [] - - debug('_setupStream :: calling method to clear reconnect') - this._clearReconnect() - - debug('_setupStream :: using streamBuilder provided to client to create stream') - this.stream = this.streamBuilder(this) - - parser.on('packet', function (packet) { - debug('parser :: on packet push to packets array.') - packets.push(packet) - }) + debug('_setupStream :: using streamBuilder provided to client to create stream') + this.stream = this.streamBuilder(this) - function nextTickWork () { - if (packets.length) { - nextTick(work) - } else { - const done = completeParse - completeParse = null - done() + parser.on('packet', function (packet) { + debug('parser :: on packet push to packets array.') + packets.push(packet) + }) + + function nextTickWork() { + if (packets.length) { + nextTick(work) + } else { + const done = completeParse + completeParse = null + done() + } } - } - function work () { - debug('work :: getting next packet in queue') - const packet = packets.shift() + function work() { + debug('work :: getting next packet in queue') + const packet = packets.shift() - if (packet) { - debug('work :: packet pulled from queue') - that._handlePacket(packet, nextTickWork) - } else { - debug('work :: no packets in queue') - const done = completeParse - completeParse = null - debug('work :: done flag is %s', !!(done)) - if (done) done() + if (packet) { + debug('work :: packet pulled from queue') + that._handlePacket(packet, nextTickWork) + } else { + debug('work :: no packets in queue') + const done = completeParse + completeParse = null + debug('work :: done flag is %s', !!(done)) + if (done) done() + } } - } - writable._write = function (buf, enc, done) { - completeParse = done - debug('writable stream :: parsing buffer') - parser.parse(buf) - work() - } + writable._write = function (buf, enc, done) { + completeParse = done + debug('writable stream :: parsing buffer') + parser.parse(buf) + work() + } - function streamErrorHandler (error) { - debug('streamErrorHandler :: error', error.message) - if (socketErrors.includes(error.code)) { - // handle error - debug('streamErrorHandler :: emitting error') - that.emit('error', error) - } else { - nop(error) + function streamErrorHandler(error) { + debug('streamErrorHandler :: error', error.message) + if (socketErrors.includes(error.code)) { + // handle error + debug('streamErrorHandler :: emitting error') + that.emit('error', error) + } else { + nop(error) + } } - } - debug('_setupStream :: pipe stream to writable stream') - this.stream.pipe(writable) + debug('_setupStream :: pipe stream to writable stream') + this.stream.pipe(writable) - // Suppress connection errors - this.stream.on('error', streamErrorHandler) + // Suppress connection errors + this.stream.on('error', streamErrorHandler) - // Echo stream close - this.stream.on('close', function () { - debug('(%s)stream :: on close', that.options.clientId) - flushVolatile(that.outgoing) - debug('stream: emit close to MqttClient') - that.emit('close') - }) + // Echo stream close + this.stream.on('close', function () { + debug('(%s)stream :: on close', that.options.clientId) + flushVolatile(that.outgoing) + debug('stream: emit close to MqttClient') + that.emit('close') + }) - // Send a connect packet - debug('_setupStream: sending packet `connect`') - const connectPacket = Object.create(this.options) - connectPacket.cmd = 'connect' - if (this.topicAliasRecv) { - if (!connectPacket.properties) { - connectPacket.properties = {} - } + // Send a connect packet + debug('_setupStream: sending packet `connect`') + const connectPacket = Object.create(this.options) + connectPacket.cmd = 'connect' if (this.topicAliasRecv) { - connectPacket.properties.topicAliasMaximum = this.topicAliasRecv.max + if (!connectPacket.properties) { + connectPacket.properties = {} + } + if (this.topicAliasRecv) { + connectPacket.properties.topicAliasMaximum = this.topicAliasRecv.max + } } - } - // avoid message queue - sendPacket(this, connectPacket) + // avoid message queue + sendPacket(this, connectPacket) - // Echo connection errors - parser.on('error', this.emit.bind(this, 'error')) + // Echo connection errors + parser.on('error', this.emit.bind(this, 'error')) - // auth - if (this.options.properties) { - if (!this.options.properties.authenticationMethod && this.options.properties.authenticationData) { - that.end(() => - this.emit('error', new Error('Packet has no Authentication Method') + // auth + if (this.options.properties) { + if (!this.options.properties.authenticationMethod && this.options.properties.authenticationData) { + that.end(() => this.emit('error', new Error('Packet has no Authentication Method') )) - return this - } - if (this.options.properties.authenticationMethod && this.options.authPacket && typeof this.options.authPacket === 'object') { - const authPacket = xtend({ cmd: 'auth', reasonCode: 0 }, this.options.authPacket) - sendPacket(this, authPacket) + return this + } + if (this.options.properties.authenticationMethod && this.options.authPacket && typeof this.options.authPacket === 'object') { + const authPacket = xtend({ cmd: 'auth', reasonCode: 0 }, this.options.authPacket) + sendPacket(this, authPacket) + } } + + // many drain listeners are needed for qos 1 callbacks if the connection is intermittent + this.stream.setMaxListeners(1000) + + clearTimeout(this.connackTimer) + this.connackTimer = setTimeout(function () { + debug('!!connectTimeout hit!! Calling _cleanUp with force `true`') + that._cleanUp(true) + }, this.options.connectTimeout) } - // many drain listeners are needed for qos 1 callbacks if the connection is intermittent - this.stream.setMaxListeners(1000) - clearTimeout(this.connackTimer) - this.connackTimer = setTimeout(function () { - debug('!!connectTimeout hit!! Calling _cleanUp with force `true`') - that._cleanUp(true) - }, this.options.connectTimeout) -} + _handlePacket(packet, done) { + const options = this.options -MqttClient.prototype._handlePacket = function (packet, done) { - const options = this.options + if (options.protocolVersion === 5 && options.properties && options.properties.maximumPacketSize && options.properties.maximumPacketSize < packet.length) { + this.emit('error', new Error('exceeding packets size ' + packet.cmd)) + this.end({ reasonCode: 149, properties: { reasonString: 'Maximum packet size was exceeded' } }) + return this + } + debug('_handlePacket :: emitting packetreceive') + this.emit('packetreceive', packet) - if (options.protocolVersion === 5 && options.properties && options.properties.maximumPacketSize && options.properties.maximumPacketSize < packet.length) { - this.emit('error', new Error('exceeding packets size ' + packet.cmd)) - this.end({ reasonCode: 149, properties: { reasonString: 'Maximum packet size was exceeded' } }) - return this - } - debug('_handlePacket :: emitting packetreceive') - 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 'auth': - this._handleAuth(packet) - done() - break - case 'pingresp': - this._handlePingresp(packet) - done() - break - case 'disconnect': - this._handleDisconnect(packet) - done() - break - default: - // do nothing - // maybe we should do an error handling - // or just log it - break + 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 'auth': + this._handleAuth(packet) + done() + break + case 'pingresp': + this._handlePingresp(packet) + done() + break + case 'disconnect': + this._handleDisconnect(packet) + done() + break + default: + // do nothing + // maybe we should do an error handling + // or just log it + break + } } -} -MqttClient.prototype._checkDisconnecting = function (callback) { - if (this.disconnecting) { - if (callback && callback !== nop) { - callback(new Error('client disconnecting')) - } else { - this.emit('error', new Error('client disconnecting')) + _checkDisconnecting(callback) { + if (this.disconnecting) { + if (callback && callback !== nop) { + callback(new Error('client disconnecting')) + } else { + this.emit('error', new Error('client disconnecting')) + } + } + return this.disconnecting + } + + /** + * publish - publish to + * + * @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 + * {Function} cbStorePut - function(){} called when message is put into `outgoingStore` + * @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) { + debug('publish :: message `%s` to topic `%s`', message, topic) + const options = this.options + + // .publish(topic, payload, cb); + if (typeof opts === 'function') { + callback = opts + opts = null } - } - return this.disconnecting -} -/** - * publish - publish to - * - * @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 - * {Function} cbStorePut - function(){} called when message is put into `outgoingStore` - * @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); - */ -MqttClient.prototype.publish = function (topic, message, opts, callback) { - debug('publish :: message `%s` to topic `%s`', message, topic) - const options = this.options - - // .publish(topic, payload, cb); - if (typeof opts === 'function') { - callback = opts - opts = null - } + // default opts + const defaultOpts = { qos: 0, retain: false, dup: false } + opts = xtend(defaultOpts, opts) - // default opts - const defaultOpts = { qos: 0, retain: false, dup: false } - opts = xtend(defaultOpts, opts) + if (this._checkDisconnecting(callback)) { + return this + } - if (this._checkDisconnecting(callback)) { - return this - } + const that = this + const publishProc = function () { + let messageId = 0 + if (opts.qos === 1 || opts.qos === 2) { + messageId = that._nextId() + if (messageId === null) { + debug('No messageId left') + return false + } + } + const packet = { + cmd: 'publish', + topic, + payload: message, + qos: opts.qos, + retain: opts.retain, + messageId, + dup: opts.dup + } - const that = this - const publishProc = function () { - let messageId = 0 - if (opts.qos === 1 || opts.qos === 2) { - messageId = that._nextId() - if (messageId === null) { - debug('No messageId left') - return false + if (options.protocolVersion === 5) { + packet.properties = opts.properties } - } - const packet = { - cmd: 'publish', - topic, - payload: message, - qos: opts.qos, - retain: opts.retain, - messageId, - dup: opts.dup - } - if (options.protocolVersion === 5) { - packet.properties = opts.properties + debug('publish :: qos', opts.qos) + switch (opts.qos) { + case 1: + case 2: + // Add to callbacks + that.outgoing[packet.messageId] = { + volatile: false, + cb: callback || nop + } + debug('MqttClient:publish: packet cmd: %s', packet.cmd) + that._sendPacket(packet, undefined, opts.cbStorePut) + break + default: + debug('MqttClient:publish: packet cmd: %s', packet.cmd) + that._sendPacket(packet, callback, opts.cbStorePut) + break + } + return true } - debug('publish :: qos', opts.qos) - switch (opts.qos) { - case 1: - case 2: - // Add to callbacks - that.outgoing[packet.messageId] = { - volatile: false, - cb: callback || nop + if (this._storeProcessing || this._storeProcessingQueue.length > 0 || !publishProc()) { + this._storeProcessingQueue.push( + { + invoke: publishProc, + cbStorePut: opts.cbStorePut, + callback } - debug('MqttClient:publish: packet cmd: %s', packet.cmd) - that._sendPacket(packet, undefined, opts.cbStorePut) - break - default: - debug('MqttClient:publish: packet cmd: %s', packet.cmd) - that._sendPacket(packet, callback, opts.cbStorePut) - break + ) } - return true + return this } - if (this._storeProcessing || this._storeProcessingQueue.length > 0 || !publishProc()) { - this._storeProcessingQueue.push( - { - invoke: publishProc, - cbStorePut: opts.cbStorePut, - callback - } - ) - } - return this -} + /** + * subscribe - subscribe to + * + * @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': {qos: 0}, 'topic2': {qos: 1}}, console.log); + * @example client.subscribe('topic', console.log); + */ + subscribe() { + const that = this + const args = new Array(arguments.length) + for (let i = 0; i < arguments.length; i++) { + args[i] = arguments[i] + } + const subs = [] + let obj = args.shift() + const resubscribe = obj.resubscribe + let callback = args.pop() || nop + let opts = args.pop() + const version = this.options.protocolVersion -/** - * subscribe - subscribe to - * - * @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': {qos: 0}, 'topic2': {qos: 1}}, console.log); - * @example client.subscribe('topic', console.log); - */ -MqttClient.prototype.subscribe = function () { - const that = this - const args = new Array(arguments.length) - for (let i = 0; i < arguments.length; i++) { - args[i] = arguments[i] - } - const subs = [] - let obj = args.shift() - const resubscribe = obj.resubscribe - let callback = args.pop() || nop - let opts = args.pop() - const version = this.options.protocolVersion + delete obj.resubscribe - delete obj.resubscribe + if (typeof obj === 'string') { + obj = [obj] + } - if (typeof obj === 'string') { - obj = [obj] - } + if (typeof callback !== 'function') { + opts = callback + callback = nop + } - if (typeof callback !== 'function') { - opts = callback - callback = nop - } + const invalidTopic = validations.validateTopics(obj) + if (invalidTopic !== null) { + setImmediate(callback, new Error('Invalid topic ' + invalidTopic)) + return this + } - const invalidTopic = validations.validateTopics(obj) - if (invalidTopic !== null) { - setImmediate(callback, new Error('Invalid topic ' + invalidTopic)) - return this - } + if (this._checkDisconnecting(callback)) { + debug('subscribe: discconecting true') + return this + } - if (this._checkDisconnecting(callback)) { - debug('subscribe: discconecting true') - return this - } + const defaultOpts = { + qos: 0 + } + if (version === 5) { + defaultOpts.nl = false + defaultOpts.rap = false + defaultOpts.rh = 0 + } + opts = xtend(defaultOpts, opts) - const defaultOpts = { - qos: 0 - } - if (version === 5) { - defaultOpts.nl = false - defaultOpts.rap = false - defaultOpts.rh = 0 - } - opts = xtend(defaultOpts, opts) - - if (Array.isArray(obj)) { - obj.forEach(function (topic) { - debug('subscribe: array topic %s', topic) - if (!Object.prototype.hasOwnProperty.call(that._resubscribeTopics, topic) || - that._resubscribeTopics[topic].qos < opts.qos || - resubscribe) { - const currentOpts = { - topic, - qos: opts.qos - } - if (version === 5) { - currentOpts.nl = opts.nl - currentOpts.rap = opts.rap - currentOpts.rh = opts.rh - currentOpts.properties = opts.properties - } - debug('subscribe: pushing topic `%s` and qos `%s` to subs list', currentOpts.topic, currentOpts.qos) - subs.push(currentOpts) - } - }) - } else { - Object - .keys(obj) - .forEach(function (k) { - debug('subscribe: object topic %s', k) - if (!Object.prototype.hasOwnProperty.call(that._resubscribeTopics, k) || - that._resubscribeTopics[k].qos < obj[k].qos || + if (Array.isArray(obj)) { + obj.forEach(function (topic) { + debug('subscribe: array topic %s', topic) + if (!Object.prototype.hasOwnProperty.call(that._resubscribeTopics, topic) || + that._resubscribeTopics[topic].qos < opts.qos || resubscribe) { const currentOpts = { - topic: k, - qos: obj[k].qos + topic, + qos: opts.qos } if (version === 5) { - currentOpts.nl = obj[k].nl - currentOpts.rap = obj[k].rap - currentOpts.rh = obj[k].rh + currentOpts.nl = opts.nl + currentOpts.rap = opts.rap + currentOpts.rh = opts.rh currentOpts.properties = opts.properties } - debug('subscribe: pushing `%s` to subs list', currentOpts) + debug('subscribe: pushing topic `%s` and qos `%s` to subs list', currentOpts.topic, currentOpts.qos) subs.push(currentOpts) } }) - } - - if (!subs.length) { - callback(null, []) - return this - } - - const subscribeProc = function () { - const messageId = that._nextId() - if (messageId === null) { - debug('No messageId left') - return false - } - - const packet = { - cmd: 'subscribe', - subscriptions: subs, - qos: 1, - retain: false, - dup: false, - messageId - } - - if (opts.properties) { - packet.properties = opts.properties + } else { + Object + .keys(obj) + .forEach(function (k) { + debug('subscribe: object topic %s', k) + if (!Object.prototype.hasOwnProperty.call(that._resubscribeTopics, k) || + that._resubscribeTopics[k].qos < obj[k].qos || + resubscribe) { + const currentOpts = { + topic: k, + qos: obj[k].qos + } + if (version === 5) { + currentOpts.nl = obj[k].nl + currentOpts.rap = obj[k].rap + currentOpts.rh = obj[k].rh + currentOpts.properties = opts.properties + } + debug('subscribe: pushing `%s` to subs list', currentOpts) + subs.push(currentOpts) + } + }) } - // subscriptions to resubscribe to in case of disconnect - if (that.options.resubscribe) { - debug('subscribe :: resubscribe true') - const topics = [] - subs.forEach(function (sub) { - if (that.options.reconnectPeriod > 0) { - const topic = { qos: sub.qos } - if (version === 5) { - topic.nl = sub.nl || false - topic.rap = sub.rap || false - topic.rh = sub.rh || 0 - topic.properties = sub.properties - } - that._resubscribeTopics[sub.topic] = topic - topics.push(sub.topic) - } - }) - that.messageIdToTopic[packet.messageId] = topics + if (!subs.length) { + callback(null, []) + return this } - that.outgoing[packet.messageId] = { - volatile: true, - cb: function (err, packet) { - if (!err) { - const granted = packet.granted - for (let i = 0; i < granted.length; i += 1) { - subs[i].qos = granted[i] - } - } + const subscribeProc = function () { + const messageId = that._nextId() + if (messageId === null) { + debug('No messageId left') + return false + } - callback(err, subs) + const packet = { + cmd: 'subscribe', + subscriptions: subs, + qos: 1, + retain: false, + dup: false, + messageId } - } - debug('subscribe :: call _sendPacket') - that._sendPacket(packet) - return true - } - if (this._storeProcessing || this._storeProcessingQueue.length > 0 || !subscribeProc()) { - this._storeProcessingQueue.push( - { - invoke: subscribeProc, - callback + if (opts.properties) { + packet.properties = opts.properties } - ) - } - return this -} + // subscriptions to resubscribe to in case of disconnect + if (that.options.resubscribe) { + debug('subscribe :: resubscribe true') + const topics = [] + subs.forEach(function (sub) { + if (that.options.reconnectPeriod > 0) { + const topic = { qos: sub.qos } + if (version === 5) { + topic.nl = sub.nl || false + topic.rap = sub.rap || false + topic.rh = sub.rh || 0 + topic.properties = sub.properties + } + that._resubscribeTopics[sub.topic] = topic + topics.push(sub.topic) + } + }) + that.messageIdToTopic[packet.messageId] = topics + } -/** - * unsubscribe - unsubscribe from topic(s) - * - * @param {String, Array} topic - topics to unsubscribe from - * @param {Object} [opts] - optional subscription options, includes: - * {Object} properties - properties of unsubscribe packet - * @param {Function} [callback] - callback fired on unsuback - * @returns {MqttClient} this - for chaining - * @api public - * @example client.unsubscribe('topic'); - * @example client.unsubscribe('topic', console.log); - */ -MqttClient.prototype.unsubscribe = function () { - const that = this - const args = new Array(arguments.length) - for (let i = 0; i < arguments.length; i++) { - args[i] = arguments[i] - } - let topic = args.shift() - let callback = args.pop() || nop - let opts = args.pop() - if (typeof topic === 'string') { - topic = [topic] - } + that.outgoing[packet.messageId] = { + volatile: true, + cb: function (err, packet) { + if (!err) { + const granted = packet.granted + for (let i = 0; i < granted.length; i += 1) { + subs[i].qos = granted[i] + } + } - if (typeof callback !== 'function') { - opts = callback - callback = nop - } + callback(err, subs) + } + } + debug('subscribe :: call _sendPacket') + that._sendPacket(packet) + return true + } - const invalidTopic = validations.validateTopics(topic) - if (invalidTopic !== null) { - setImmediate(callback, new Error('Invalid topic ' + invalidTopic)) - return this - } + if (this._storeProcessing || this._storeProcessingQueue.length > 0 || !subscribeProc()) { + this._storeProcessingQueue.push( + { + invoke: subscribeProc, + callback + } + ) + } - if (that._checkDisconnecting(callback)) { return this } - const unsubscribeProc = function () { - const messageId = that._nextId() - if (messageId === null) { - debug('No messageId left') - return false + /** + * unsubscribe - unsubscribe from topic(s) + * + * @param {String, Array} topic - topics to unsubscribe from + * @param {Object} [opts] - optional subscription options, includes: + * {Object} properties - properties of unsubscribe packet + * @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() { + const that = this + const args = new Array(arguments.length) + for (let i = 0; i < arguments.length; i++) { + args[i] = arguments[i] } - const packet = { - cmd: 'unsubscribe', - qos: 1, - messageId - } - + let topic = args.shift() + let callback = args.pop() || nop + let opts = args.pop() if (typeof topic === 'string') { - packet.unsubscriptions = [topic] - } else if (Array.isArray(topic)) { - packet.unsubscriptions = topic + topic = [topic] } - if (that.options.resubscribe) { - packet.unsubscriptions.forEach(function (topic) { - delete that._resubscribeTopics[topic] - }) + if (typeof callback !== 'function') { + opts = callback + callback = nop } - if (typeof opts === 'object' && opts.properties) { - packet.properties = opts.properties + const invalidTopic = validations.validateTopics(topic) + if (invalidTopic !== null) { + setImmediate(callback, new Error('Invalid topic ' + invalidTopic)) + return this } - that.outgoing[packet.messageId] = { - volatile: true, - cb: callback + if (that._checkDisconnecting(callback)) { + return this } - debug('unsubscribe: call _sendPacket') - that._sendPacket(packet) + const unsubscribeProc = function () { + const messageId = that._nextId() + if (messageId === null) { + debug('No messageId left') + return false + } + const packet = { + cmd: 'unsubscribe', + qos: 1, + messageId + } - return true - } + if (typeof topic === 'string') { + packet.unsubscriptions = [topic] + } else if (Array.isArray(topic)) { + packet.unsubscriptions = topic + } - if (this._storeProcessing || this._storeProcessingQueue.length > 0 || !unsubscribeProc()) { - this._storeProcessingQueue.push( - { - invoke: unsubscribeProc, - callback + if (that.options.resubscribe) { + packet.unsubscriptions.forEach(function (topic) { + delete that._resubscribeTopics[topic] + }) } - ) - } - return this -} + if (typeof opts === 'object' && opts.properties) { + packet.properties = opts.properties + } -/** - * end - close connection - * - * @returns {MqttClient} this - for chaining - * @param {Boolean} force - do not wait for all in-flight messages to be acked - * @param {Object} opts - added to the disconnect packet - * @param {Function} cb - called when the client has been closed - * - * @api public - */ -MqttClient.prototype.end = function (force, opts, cb) { - const that = this + that.outgoing[packet.messageId] = { + volatile: true, + cb: callback + } - debug('end :: (%s)', this.options.clientId) + debug('unsubscribe: call _sendPacket') + that._sendPacket(packet) - if (force == null || typeof force !== 'boolean') { - cb = opts || nop - opts = force - force = false - if (typeof opts !== 'object') { - cb = opts - opts = null - if (typeof cb !== 'function') { - cb = nop - } + return true } - } - - if (typeof opts !== 'object') { - cb = opts - opts = null - } - debug('end :: cb? %s', !!cb) - cb = cb || nop - - function closeStores () { - debug('end :: closeStores: closing incoming and outgoing stores') - that.disconnected = true - that.incomingStore.close(function (e1) { - that.outgoingStore.close(function (e2) { - debug('end :: closeStores: emitting end') - that.emit('end') - if (cb) { - const err = e1 || e2 - debug('end :: closeStores: invoking callback with args') - cb(err) + if (this._storeProcessing || this._storeProcessingQueue.length > 0 || !unsubscribeProc()) { + this._storeProcessingQueue.push( + { + invoke: unsubscribeProc, + callback } - }) - }) - if (that._deferredReconnect) { - that._deferredReconnect() + ) } - } - - function finish () { - // defer closesStores of an I/O cycle, - // just to make sure things are - // ok for websockets - debug('end :: (%s) :: finish :: calling _cleanUp with force %s', that.options.clientId, force) - that._cleanUp(force, () => { - debug('end :: finish :: calling process.nextTick on closeStores') - // const boundProcess = nextTick.bind(null, closeStores) - nextTick(closeStores.bind(that)) - }, opts) - } - if (this.disconnecting) { - cb() return this } - this._clearReconnect() - - this.disconnecting = true + /** + * end - close connection + * + * @returns {MqttClient} this - for chaining + * @param {Boolean} force - do not wait for all in-flight messages to be acked + * @param {Object} opts - added to the disconnect packet + * @param {Function} cb - called when the client has been closed + * + * @api public + */ + end(force, opts, cb) { + const that = this + + debug('end :: (%s)', this.options.clientId) + + if (force == null || typeof force !== 'boolean') { + cb = opts || nop + opts = force + force = false + if (typeof opts !== 'object') { + cb = opts + opts = null + if (typeof cb !== 'function') { + cb = nop + } + } + } - if (!force && Object.keys(this.outgoing).length > 0) { - // wait 10ms, just to be sure we received all of it - debug('end :: (%s) :: calling finish in 10ms once outgoing is empty', that.options.clientId) - this.once('outgoingEmpty', setTimeout.bind(null, finish, 10)) - } else { - debug('end :: (%s) :: immediately calling finish', that.options.clientId) - finish() - } + if (typeof opts !== 'object') { + cb = opts + opts = null + } - return this -} + debug('end :: cb? %s', !!cb) + cb = cb || nop + + function closeStores() { + debug('end :: closeStores: closing incoming and outgoing stores') + that.disconnected = true + that.incomingStore.close(function (e1) { + that.outgoingStore.close(function (e2) { + debug('end :: closeStores: emitting end') + that.emit('end') + if (cb) { + const err = e1 || e2 + debug('end :: closeStores: invoking callback with args') + cb(err) + } + }) + }) + if (that._deferredReconnect) { + that._deferredReconnect() + } + } -/** - * removeOutgoingMessage - remove a message in outgoing store - * the outgoing callback will be called withe Error('Message removed') if the message is removed - * - * @param {Number} messageId - messageId to remove message - * @returns {MqttClient} this - for chaining - * @api public - * - * @example client.removeOutgoingMessage(client.getLastAllocated()); - */ -MqttClient.prototype.removeOutgoingMessage = function (messageId) { - const cb = this.outgoing[messageId] ? this.outgoing[messageId].cb : null - delete this.outgoing[messageId] - this.outgoingStore.del({ messageId }, function () { - cb(new Error('Message removed')) - }) - return this -} + function finish() { + // defer closesStores of an I/O cycle, + // just to make sure things are + // ok for websockets + debug('end :: (%s) :: finish :: calling _cleanUp with force %s', that.options.clientId, force) + that._cleanUp(force, () => { + debug('end :: finish :: calling process.nextTick on closeStores') + // const boundProcess = nextTick.bind(null, closeStores) + nextTick(closeStores.bind(that)) + }, opts) + } -/** - * 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 - */ -MqttClient.prototype.reconnect = function (opts) { - debug('client reconnect') - const that = this - const f = function () { - if (opts) { - that.options.incomingStore = opts.incomingStore - that.options.outgoingStore = opts.outgoingStore + if (this.disconnecting) { + cb() + 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 + debug('end :: (%s) :: calling finish in 10ms once outgoing is empty', that.options.clientId) + this.once('outgoingEmpty', setTimeout.bind(null, finish, 10)) } else { - that.options.incomingStore = null - that.options.outgoingStore = null - } - that.incomingStore = that.options.incomingStore || new Store() - that.outgoingStore = that.options.outgoingStore || new Store() - that.disconnecting = false - that.disconnected = false - that._deferredReconnect = null - that._reconnect() - } + debug('end :: (%s) :: immediately calling finish', that.options.clientId) + finish() + } - if (this.disconnecting && !this.disconnected) { - this._deferredReconnect = f - } else { - f() + return this } - return this -} -/** - * _reconnect - implement reconnection - * @api privateish - */ -MqttClient.prototype._reconnect = function () { - debug('_reconnect: emitting reconnect to client') - this.emit('reconnect') - if (this.connected) { - this.end(() => { this._setupStream() }) - debug('client already connected. disconnecting first.') - } else { - debug('_reconnect: calling _setupStream') - this._setupStream() + /** + * removeOutgoingMessage - remove a message in outgoing store + * the outgoing callback will be called withe Error('Message removed') if the message is removed + * + * @param {Number} messageId - messageId to remove message + * @returns {MqttClient} this - for chaining + * @api public + * + * @example client.removeOutgoingMessage(client.getLastAllocated()); + */ + removeOutgoingMessage(messageId) { + const cb = this.outgoing[messageId] ? this.outgoing[messageId].cb : null + delete this.outgoing[messageId] + this.outgoingStore.del({ messageId }, function () { + cb(new Error('Message removed')) + }) + return this } -} -/** - * _setupReconnect - setup reconnect timer - */ -MqttClient.prototype._setupReconnect = function () { - const that = this - - if (!that.disconnecting && !that.reconnectTimer && (that.options.reconnectPeriod > 0)) { - if (!this.reconnecting) { - debug('_setupReconnect :: emit `offline` state') - this.emit('offline') - debug('_setupReconnect :: set `reconnecting` to `true`') - this.reconnecting = true - } - debug('_setupReconnect :: setting reconnectTimer for %d ms', that.options.reconnectPeriod) - that.reconnectTimer = setInterval(function () { - debug('reconnectTimer :: reconnect triggered!') + /** + * 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) { + debug('client reconnect') + const that = this + const f = function () { + if (opts) { + that.options.incomingStore = opts.incomingStore + that.options.outgoingStore = opts.outgoingStore + } else { + that.options.incomingStore = null + that.options.outgoingStore = null + } + that.incomingStore = that.options.incomingStore || new Store() + that.outgoingStore = that.options.outgoingStore || new Store() + that.disconnecting = false + that.disconnected = false + that._deferredReconnect = null that._reconnect() - }, that.options.reconnectPeriod) - } else { - debug('_setupReconnect :: doing nothing...') - } -} + } -/** - * _clearReconnect - clear the reconnect timer - */ -MqttClient.prototype._clearReconnect = function () { - debug('_clearReconnect : clearing reconnect timer') - if (this.reconnectTimer) { - clearInterval(this.reconnectTimer) - this.reconnectTimer = null + if (this.disconnecting && !this.disconnected) { + this._deferredReconnect = f + } else { + f() + } + return this } -} -/** - * _cleanUp - clean up on connection end - * @api private - */ -MqttClient.prototype._cleanUp = function (forced, done) { - const opts = arguments[2] - if (done) { - debug('_cleanUp :: done callback provided for on stream close') - this.stream.on('close', done) + /** + * _reconnect - implement reconnection + * @api privateish + */ + _reconnect() { + debug('_reconnect: emitting reconnect to client') + this.emit('reconnect') + if (this.connected) { + this.end(() => { this._setupStream() }) + debug('client already connected. disconnecting first.') + } else { + debug('_reconnect: calling _setupStream') + this._setupStream() + } } - debug('_cleanUp :: forced? %s', forced) - if (forced) { - if ((this.options.reconnectPeriod === 0) && this.options.clean) { - flush(this.outgoing) - } - debug('_cleanUp :: (%s) :: destroying stream', this.options.clientId) - this.stream.destroy() - } else { - const packet = xtend({ cmd: 'disconnect' }, opts) - debug('_cleanUp :: (%s) :: call _sendPacket with disconnect packet', this.options.clientId) - this._sendPacket( - packet, - setImmediate.bind( - null, - this.stream.end.bind(this.stream) - ) - ) - } + /** + * _setupReconnect - setup reconnect timer + */ + _setupReconnect() { + const that = this - if (!this.disconnecting) { - debug('_cleanUp :: client not disconnecting. Clearing and resetting reconnect.') - this._clearReconnect() - this._setupReconnect() + if (!that.disconnecting && !that.reconnectTimer && (that.options.reconnectPeriod > 0)) { + if (!this.reconnecting) { + debug('_setupReconnect :: emit `offline` state') + this.emit('offline') + debug('_setupReconnect :: set `reconnecting` to `true`') + this.reconnecting = true + } + debug('_setupReconnect :: setting reconnectTimer for %d ms', that.options.reconnectPeriod) + that.reconnectTimer = setInterval(function () { + debug('reconnectTimer :: reconnect triggered!') + that._reconnect() + }, that.options.reconnectPeriod) + } else { + debug('_setupReconnect :: doing nothing...') + } } - if (this.pingTimer !== null) { - debug('_cleanUp :: clearing pingTimer') - this.pingTimer.clear() - this.pingTimer = null + /** + * _clearReconnect - clear the reconnect timer + */ + _clearReconnect() { + debug('_clearReconnect : clearing reconnect timer') + if (this.reconnectTimer) { + clearInterval(this.reconnectTimer) + this.reconnectTimer = null + } } - if (done && !this.connected) { - debug('_cleanUp :: (%s) :: removing stream `done` callback `close` listener', this.options.clientId) - this.stream.removeListener('close', done) - done() - } -} + /** + * _cleanUp - clean up on connection end + * @api private + */ + _cleanUp(forced, done) { + const opts = arguments[2] + if (done) { + debug('_cleanUp :: done callback provided for on stream close') + this.stream.on('close', done) + } -/** - * _sendPacket - send or queue a packet - * @param {Object} packet - packet options - * @param {Function} cb - callback when the packet is sent - * @param {Function} cbStorePut - called when message is put into outgoingStore - * @param {Boolean} noStore - send without put to the store - * @api private - */ -MqttClient.prototype._sendPacket = function (packet, cb, cbStorePut, noStore) { - debug('_sendPacket :: (%s) :: start', this.options.clientId) - cbStorePut = cbStorePut || nop - cb = cb || nop - - const err = applyTopicAlias(this, packet) - if (err) { - cb(err) - return - } + debug('_cleanUp :: forced? %s', forced) + if (forced) { + if ((this.options.reconnectPeriod === 0) && this.options.clean) { + flush(this.outgoing) + } + debug('_cleanUp :: (%s) :: destroying stream', this.options.clientId) + this.stream.destroy() + } else { + const packet = xtend({ cmd: 'disconnect' }, opts) + debug('_cleanUp :: (%s) :: call _sendPacket with disconnect packet', this.options.clientId) + this._sendPacket( + packet, + setImmediate.bind( + null, + this.stream.end.bind(this.stream) + ) + ) + } - if (!this.connected) { - // allow auth packets to be sent while authenticating with the broker (mqtt5 enhanced auth) - if (packet.cmd === 'auth') { - this._shiftPingInterval() - sendPacket(this, packet, cb) - return + if (!this.disconnecting) { + debug('_cleanUp :: client not disconnecting. Clearing and resetting reconnect.') + this._clearReconnect() + this._setupReconnect() } - debug('_sendPacket :: client not connected. Storing packet offline.') - this._storePacket(packet, cb, cbStorePut) - return - } + if (this.pingTimer !== null) { + debug('_cleanUp :: clearing pingTimer') + this.pingTimer.clear() + this.pingTimer = null + } - // When sending a packet, reschedule the ping timer - this._shiftPingInterval() - - // If "noStore" is true, the message is sent without being recorded in the store. - // Messages that have not received puback or pubcomp remain in the store after disconnection - // and are resent from the store upon reconnection. - // For resend upon reconnection, "noStore" is set to true. This is because the message is already stored in the store. - // This is to avoid interrupting other processes while recording to the store. - if (noStore) { - sendPacket(this, packet, cb) - return + if (done && !this.connected) { + debug('_cleanUp :: (%s) :: removing stream `done` callback `close` listener', this.options.clientId) + this.stream.removeListener('close', done) + done() + } } - switch (packet.cmd) { - case 'publish': - break - case 'pubrel': - storeAndSend(this, packet, cb, cbStorePut) + /** + * _sendPacket - send or queue a packet + * @param {Object} packet - packet options + * @param {Function} cb - callback when the packet is sent + * @param {Function} cbStorePut - called when message is put into outgoingStore + * @param {Boolean} noStore - send without put to the store + * @api private + */ + _sendPacket(packet, cb, cbStorePut, noStore) { + debug('_sendPacket :: (%s) :: start', this.options.clientId) + cbStorePut = cbStorePut || nop + cb = cb || nop + + const err = applyTopicAlias(this, packet) + if (err) { + cb(err) return - default: - sendPacket(this, packet, cb) + } + + if (!this.connected) { + // allow auth packets to be sent while authenticating with the broker (mqtt5 enhanced auth) + if (packet.cmd === 'auth') { + this._shiftPingInterval() + sendPacket(this, packet, cb) + return + } + + debug('_sendPacket :: client not connected. Storing packet offline.') + this._storePacket(packet, cb, cbStorePut) return - } + } - switch (packet.qos) { - case 2: - case 1: - storeAndSend(this, packet, cb, cbStorePut) - 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: + // When sending a packet, reschedule the ping timer + this._shiftPingInterval() + + // If "noStore" is true, the message is sent without being recorded in the store. + // Messages that have not received puback or pubcomp remain in the store after disconnection + // and are resent from the store upon reconnection. + // For resend upon reconnection, "noStore" is set to true. This is because the message is already stored in the store. + // This is to avoid interrupting other processes while recording to the store. + if (noStore) { sendPacket(this, packet, cb) - break - } - debug('_sendPacket :: (%s) :: end', this.options.clientId) -} + return + } -/** - * _storePacket - queue a packet - * @param {Object} packet - packet options - * @param {Function} cb - callback when the packet is sent - * @param {Function} cbStorePut - called when message is put into outgoingStore - * @api private - */ -MqttClient.prototype._storePacket = function (packet, cb, cbStorePut) { - debug('_storePacket :: packet: %o', packet) - debug('_storePacket :: cb? %s', !!cb) - cbStorePut = cbStorePut || nop + switch (packet.cmd) { + case 'publish': + break + case 'pubrel': + storeAndSend(this, packet, cb, cbStorePut) + return + default: + sendPacket(this, packet, cb) + return + } - let storePacket = packet - if (storePacket.cmd === 'publish') { - // The original packet is for sending. - // The cloned storePacket is for storing to resend on reconnect. - // Topic Alias must not be used after disconnected. - storePacket = clone(packet) - const err = removeTopicAliasAndRecoverTopicName(this, storePacket) - if (err) { - return cb && cb(err) + switch (packet.qos) { + case 2: + case 1: + storeAndSend(this, packet, cb, cbStorePut) + 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: + sendPacket(this, packet, cb) + break } - } - // check that the packet is not a qos of 0, or that the command is not a publish - if (((storePacket.qos || 0) === 0 && this.queueQoSZero) || storePacket.cmd !== 'publish') { - this.queue.push({ packet: storePacket, cb }) - } else if (storePacket.qos > 0) { - cb = this.outgoing[storePacket.messageId] ? this.outgoing[storePacket.messageId].cb : null - this.outgoingStore.put(storePacket, function (err) { + debug('_sendPacket :: (%s) :: end', this.options.clientId) + } + + /** + * _storePacket - queue a packet + * @param {Object} packet - packet options + * @param {Function} cb - callback when the packet is sent + * @param {Function} cbStorePut - called when message is put into outgoingStore + * @api private + */ + _storePacket(packet, cb, cbStorePut) { + debug('_storePacket :: packet: %o', packet) + debug('_storePacket :: cb? %s', !!cb) + cbStorePut = cbStorePut || nop + + let storePacket = packet + if (storePacket.cmd === 'publish') { + // The original packet is for sending. + // The cloned storePacket is for storing to resend on reconnect. + // Topic Alias must not be used after disconnected. + storePacket = clone(packet) + const err = removeTopicAliasAndRecoverTopicName(this, storePacket) if (err) { return cb && cb(err) } - cbStorePut() - }) - } else if (cb) { - cb(new Error('No connection to broker')) + } + // check that the packet is not a qos of 0, or that the command is not a publish + if (((storePacket.qos || 0) === 0 && this.queueQoSZero) || storePacket.cmd !== 'publish') { + this.queue.push({ packet: storePacket, cb }) + } else if (storePacket.qos > 0) { + cb = this.outgoing[storePacket.messageId] ? this.outgoing[storePacket.messageId].cb : null + this.outgoingStore.put(storePacket, function (err) { + if (err) { + return cb && cb(err) + } + cbStorePut() + }) + } else if (cb) { + cb(new Error('No connection to broker')) + } } -} - -/** - * _setupPingTimer - setup the ping timer - * - * @api private - */ -MqttClient.prototype._setupPingTimer = function () { - debug('_setupPingTimer :: keepalive %d (seconds)', this.options.keepalive) - const that = this - if (!this.pingTimer && this.options.keepalive) { - this.pingResp = true - this.pingTimer = reInterval(function () { - that._checkPing() - }, this.options.keepalive * 1000) + /** + * _setupPingTimer - setup the ping timer + * + * @api private + */ + _setupPingTimer() { + debug('_setupPingTimer :: keepalive %d (seconds)', this.options.keepalive) + const that = this + + if (!this.pingTimer && this.options.keepalive) { + this.pingResp = true + this.pingTimer = reInterval(function () { + that._checkPing() + }, this.options.keepalive * 1000) + } } -} -/** - * _shiftPingInterval - reschedule the ping interval - * - * @api private - */ -MqttClient.prototype._shiftPingInterval = function () { - if (this.pingTimer && this.options.keepalive && this.options.reschedulePings) { - this.pingTimer.reschedule(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 - */ -MqttClient.prototype._checkPing = function () { - debug('_checkPing :: checking ping...') - if (this.pingResp) { - debug('_checkPing :: ping response received. Clearing flag and sending `pingreq`') - this.pingResp = false - this._sendPacket({ cmd: 'pingreq' }) - } else { - // do a forced cleanup since socket will be in bad shape - debug('_checkPing :: calling _cleanUp with force true') - this._cleanUp(true) + + /** + * _checkPing - check if a pingresp has come back, and ping the server again + * + * @api private + */ + _checkPing() { + debug('_checkPing :: checking ping...') + if (this.pingResp) { + debug('_checkPing :: ping response received. Clearing flag and sending `pingreq`') + this.pingResp = false + this._sendPacket({ cmd: 'pingreq' }) + } else { + // do a forced cleanup since socket will be in bad shape + debug('_checkPing :: calling _cleanUp with force true') + this._cleanUp(true) + } } -} -/** - * _handlePingresp - handle a pingresp - * - * @api private - */ -MqttClient.prototype._handlePingresp = function () { - this.pingResp = true -} + /** + * _handlePingresp - handle a pingresp + * + * @api private + */ + _handlePingresp() { + this.pingResp = true + } -/** - * _handleConnack - * - * @param {Object} packet - * @api private - */ -MqttClient.prototype._handleConnack = function (packet) { - debug('_handleConnack') - const options = this.options - const version = options.protocolVersion - const rc = version === 5 ? packet.reasonCode : packet.returnCode + /** + * _handleConnack + * + * @param {Object} packet + * @api private + */ + _handleConnack(packet) { + debug('_handleConnack') + const options = this.options + const version = options.protocolVersion + const rc = version === 5 ? packet.reasonCode : packet.returnCode - clearTimeout(this.connackTimer) - delete this.topicAliasSend + clearTimeout(this.connackTimer) + delete this.topicAliasSend - if (packet.properties) { - if (packet.properties.topicAliasMaximum) { - if (packet.properties.topicAliasMaximum > 0xffff) { - this.emit('error', new Error('topicAliasMaximum from broker is out of range')) - return + if (packet.properties) { + if (packet.properties.topicAliasMaximum) { + if (packet.properties.topicAliasMaximum > 0xffff) { + this.emit('error', new Error('topicAliasMaximum from broker is out of range')) + return + } + if (packet.properties.topicAliasMaximum > 0) { + this.topicAliasSend = new TopicAliasSend(packet.properties.topicAliasMaximum) + } } - if (packet.properties.topicAliasMaximum > 0) { - this.topicAliasSend = new TopicAliasSend(packet.properties.topicAliasMaximum) + if (packet.properties.serverKeepAlive && options.keepalive) { + options.keepalive = packet.properties.serverKeepAlive + this._shiftPingInterval() + } + if (packet.properties.maximumPacketSize) { + if (!options.properties) { options.properties = {}} + options.properties.maximumPacketSize = packet.properties.maximumPacketSize } } - if (packet.properties.serverKeepAlive && options.keepalive) { - options.keepalive = packet.properties.serverKeepAlive - this._shiftPingInterval() - } - if (packet.properties.maximumPacketSize) { - if (!options.properties) { options.properties = {} } - options.properties.maximumPacketSize = packet.properties.maximumPacketSize - } - } - if (rc === 0) { - this.reconnecting = false - this._onConnect(packet) - } else if (rc > 0) { - const err = new Error('Connection refused: ' + errors[rc]) - err.code = rc - this.emit('error', err) - } -} - -MqttClient.prototype._handleAuth = function (packet) { - const options = this.options - const version = options.protocolVersion - const rc = version === 5 ? packet.reasonCode : packet.returnCode - - if (version !== 5) { - const err = new Error('Protocol error: Auth packets are only supported in MQTT 5. Your version:' + version) - err.code = rc - this.emit('error', err) - return + if (rc === 0) { + this.reconnecting = false + this._onConnect(packet) + } else if (rc > 0) { + const err = new Error('Connection refused: ' + errors[rc]) + err.code = rc + this.emit('error', err) + } } - const that = this - this.handleAuth(packet, function (err, packet) { - if (err) { - that.emit('error', err) - return - } + _handleAuth(packet) { + const options = this.options + const version = options.protocolVersion + const rc = version === 5 ? packet.reasonCode : packet.returnCode - if (rc === 24) { - that.reconnecting = false - that._sendPacket(packet) - } else { - const error = new Error('Connection refused: ' + errors[rc]) + if (version !== 5) { + const err = new Error('Protocol error: Auth packets are only supported in MQTT 5. Your version:' + version) err.code = rc - that.emit('error', error) + this.emit('error', err) + return } - }) -} -/** - * @param packet the packet received by the broker - * @return the auth packet to be returned to the broker - * @api public - */ -MqttClient.prototype.handleAuth = function (packet, callback) { - callback() -} + const that = this + this.handleAuth(packet, function (err, packet) { + if (err) { + that.emit('error', err) + return + } -/** - * _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: messageId - }); + if (rc === 24) { + that.reconnecting = false + that._sendPacket(packet) + } else { + const error = new Error('Connection refused: ' + errors[rc]) + err.code = rc + that.emit('error', error) + } + }) } - // 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 -*/ -MqttClient.prototype._handlePublish = function (packet, done) { - debug('_handlePublish: packet %o', packet) - done = typeof done !== 'undefined' ? done : nop - let topic = packet.topic.toString() - const message = packet.payload - const qos = packet.qos - const messageId = packet.messageId - const that = this - const options = this.options - const validReasonCodes = [0, 16, 128, 131, 135, 144, 145, 151, 153] - if (this.options.protocolVersion === 5) { - let alias - if (packet.properties) { - alias = packet.properties.topicAlias - } - if (typeof alias !== 'undefined') { - if (topic.length === 0) { - if (alias > 0 && alias <= 0xffff) { - const gotTopic = this.topicAliasRecv.getTopicByAlias(alias) - if (gotTopic) { - topic = gotTopic - debug('_handlePublish :: topic complemented by alias. topic: %s - alias: %d', topic, alias) + + /** + * @param packet the packet received by the broker + * @return the auth packet to be returned to the broker + * @api public + */ + handleAuth(packet, callback) { + callback() + } + /** + * _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: messageId + }); + } + // 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) { + debug('_handlePublish: packet %o', packet) + done = typeof done !== 'undefined' ? done : nop + let topic = packet.topic.toString() + const message = packet.payload + const qos = packet.qos + const messageId = packet.messageId + const that = this + const options = this.options + const validReasonCodes = [0, 16, 128, 131, 135, 144, 145, 151, 153] + if (this.options.protocolVersion === 5) { + let alias + if (packet.properties) { + alias = packet.properties.topicAlias + } + if (typeof alias !== 'undefined') { + if (topic.length === 0) { + if (alias > 0 && alias <= 0xffff) { + const gotTopic = this.topicAliasRecv.getTopicByAlias(alias) + if (gotTopic) { + topic = gotTopic + debug('_handlePublish :: topic complemented by alias. topic: %s - alias: %d', topic, alias) + } else { + debug('_handlePublish :: unregistered topic alias. alias: %d', alias) + this.emit('error', new Error('Received unregistered Topic Alias')) + return + } } else { - debug('_handlePublish :: unregistered topic alias. alias: %d', alias) - this.emit('error', new Error('Received unregistered Topic Alias')) + debug('_handlePublish :: topic alias out of range. alias: %d', alias) + this.emit('error', new Error('Received Topic Alias is out of range')) return } } else { - debug('_handlePublish :: topic alias out of range. alias: %d', alias) - this.emit('error', new Error('Received Topic Alias is out of range')) - return - } - } else { - if (this.topicAliasRecv.put(topic, alias)) { - debug('_handlePublish :: registered topic: %s - alias: %d', topic, alias) - } else { - debug('_handlePublish :: topic alias out of range. alias: %d', alias) - this.emit('error', new Error('Received Topic Alias is out of range')) - return + if (this.topicAliasRecv.put(topic, alias)) { + debug('_handlePublish :: registered topic: %s - alias: %d', topic, alias) + } else { + debug('_handlePublish :: topic alias out of range. alias: %d', alias) + this.emit('error', new Error('Received Topic Alias is out of range')) + return + } } } } - } - debug('_handlePublish: qos %d', qos) - switch (qos) { - case 2: { - options.customHandleAcks(topic, message, packet, function (error, code) { - if (!(error instanceof Error)) { - code = error - error = null - } - if (error) { return that.emit('error', error) } - if (validReasonCodes.indexOf(code) === -1) { return that.emit('error', new Error('Wrong reason code for pubrec')) } - if (code) { - that._sendPacket({ cmd: 'pubrec', messageId, reasonCode: code }, done) - } else { - that.incomingStore.put(packet, function () { - that._sendPacket({ cmd: 'pubrec', messageId }, done) - }) - } - }) - break - } - case 1: { - // emit the message event - options.customHandleAcks(topic, message, packet, function (error, code) { - if (!(error instanceof Error)) { - code = error - error = null - } - if (error) { return that.emit('error', error) } - if (validReasonCodes.indexOf(code) === -1) { return that.emit('error', new Error('Wrong reason code for puback')) } - if (!code) { that.emit('message', topic, message, packet) } - that.handleMessage(packet, function (err) { - if (err) { - return done && done(err) + debug('_handlePublish: qos %d', qos) + switch (qos) { + case 2: { + options.customHandleAcks(topic, message, packet, function (error, code) { + if (!(error instanceof Error)) { + code = error + error = null + } + if (error) { return that.emit('error', error)} + if (validReasonCodes.indexOf(code) === -1) { return that.emit('error', new Error('Wrong reason code for pubrec'))} + if (code) { + that._sendPacket({ cmd: 'pubrec', messageId, reasonCode: code }, done) + } else { + that.incomingStore.put(packet, function () { + that._sendPacket({ cmd: 'pubrec', messageId }, done) + }) } - that._sendPacket({ cmd: 'puback', messageId, reasonCode: code }, done) }) - }) - break - } - case 0: - // emit the message event - this.emit('message', topic, message, packet) - this.handleMessage(packet, done) - break - default: - // do nothing - debug('_handlePublish: unknown QoS. Doing nothing.') - // log or throw an error about unknown qos - break + break + } + case 1: { + // emit the message event + options.customHandleAcks(topic, message, packet, function (error, code) { + if (!(error instanceof Error)) { + code = error + error = null + } + if (error) { return that.emit('error', error)} + if (validReasonCodes.indexOf(code) === -1) { return that.emit('error', new Error('Wrong reason code for puback'))} + if (!code) { that.emit('message', topic, message, packet)} + that.handleMessage(packet, function (err) { + if (err) { + return done && done(err) + } + that._sendPacket({ cmd: 'puback', messageId, reasonCode: code }, done) + }) + }) + break + } + case 0: + // emit the message event + this.emit('message', topic, message, packet) + this.handleMessage(packet, done) + break + default: + // do nothing + debug('_handlePublish: unknown QoS. Doing 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 - */ -MqttClient.prototype.handleMessage = function (packet, callback) { - callback() -} - -/** - * _handleAck - * - * @param {Object} packet - * @api private - */ - -MqttClient.prototype._handleAck = function (packet) { - /* eslint no-fallthrough: "off" */ - const messageId = packet.messageId - const type = packet.cmd - let response = null - const cb = this.outgoing[messageId] ? this.outgoing[messageId].cb : null - const that = this - let err - // Checking `!cb` happens to work, but it's not technically "correct". - // - // Why? This code assumes that "no callback" is the same as that "we're not - // waiting for responses" (puback, pubrec, pubcomp, suback, or unsuback). - // - // It would be better to check `if (!this.outgoing[messageId])` here, but - // there's no reason to change it and risk (another) regression. - // - // The only reason this code works is becaues code in MqttClient.publish, - // MqttClinet.subscribe, and MqttClient.unsubscribe ensures that we will - // have a callback even if the user doesn't pass one in.) - if (!cb) { - debug('_handleAck :: Server sent an ack in error. Ignoring.') - // Server sent an ack in error, ignore it. - return - } + /** + * 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() + } + + /** + * _handleAck + * + * @param {Object} packet + * @api private + */ + _handleAck(packet) { + /* eslint no-fallthrough: "off" */ + const messageId = packet.messageId + const type = packet.cmd + let response = null + const cb = this.outgoing[messageId] ? this.outgoing[messageId].cb : null + const that = this + let err + + // Checking `!cb` happens to work, but it's not technically "correct". + // + // Why? This code assumes that "no callback" is the same as that "we're not + // waiting for responses" (puback, pubrec, pubcomp, suback, or unsuback). + // + // It would be better to check `if (!this.outgoing[messageId])` here, but + // there's no reason to change it and risk (another) regression. + // + // The only reason this code works is becaues code in MqttClient.publish, + // MqttClinet.subscribe, and MqttClient.unsubscribe ensures that we will + // have a callback even if the user doesn't pass one in.) + if (!cb) { + debug('_handleAck :: Server sent an ack in error. Ignoring.') + // Server sent an ack in error, ignore it. + return + } - // Process - debug('_handleAck :: packet type', type) - switch (type) { - case 'pubcomp': - // same thing as puback for QoS 2 - case 'puback': { - const pubackRC = packet.reasonCode - // Callback - we're done - if (pubackRC && pubackRC > 0 && pubackRC !== 16) { - err = new Error('Publish error: ' + errors[pubackRC]) - err.code = pubackRC - cb(err, packet) - } - delete this.outgoing[messageId] - this.outgoingStore.del(packet, cb) - this.messageIdProvider.deallocate(messageId) - this._invokeStoreProcessingQueue() - break - } - case 'pubrec': { - response = { - cmd: 'pubrel', - qos: 2, - messageId + // Process + debug('_handleAck :: packet type', type) + switch (type) { + case 'pubcomp': + // same thing as puback for QoS 2 + case 'puback': { + const pubackRC = packet.reasonCode + // Callback - we're done + if (pubackRC && pubackRC > 0 && pubackRC !== 16) { + err = new Error('Publish error: ' + errors[pubackRC]) + err.code = pubackRC + cb(err, packet) + } + delete this.outgoing[messageId] + this.outgoingStore.del(packet, cb) + this.messageIdProvider.deallocate(messageId) + this._invokeStoreProcessingQueue() + break } - const pubrecRC = packet.reasonCode + case 'pubrec': { + response = { + cmd: 'pubrel', + qos: 2, + messageId + } + const pubrecRC = packet.reasonCode - if (pubrecRC && pubrecRC > 0 && pubrecRC !== 16) { - err = new Error('Publish error: ' + errors[pubrecRC]) - err.code = pubrecRC - cb(err, packet) - } else { - this._sendPacket(response) + if (pubrecRC && pubrecRC > 0 && pubrecRC !== 16) { + err = new Error('Publish error: ' + errors[pubrecRC]) + err.code = pubrecRC + cb(err, packet) + } else { + this._sendPacket(response) + } + break } - break - } - case 'suback': { - delete this.outgoing[messageId] - this.messageIdProvider.deallocate(messageId) - for (let grantedI = 0; grantedI < packet.granted.length; grantedI++) { - if ((packet.granted[grantedI] & 0x80) !== 0) { - // suback with Failure status - const topics = this.messageIdToTopic[messageId] - if (topics) { - topics.forEach(function (topic) { - delete that._resubscribeTopics[topic] - }) + case 'suback': { + delete this.outgoing[messageId] + this.messageIdProvider.deallocate(messageId) + for (let grantedI = 0; grantedI < packet.granted.length; grantedI++) { + if ((packet.granted[grantedI] & 0x80) !== 0) { + // suback with Failure status + const topics = this.messageIdToTopic[messageId] + if (topics) { + topics.forEach(function (topic) { + delete that._resubscribeTopics[topic] + }) + } } } + delete this.messageIdToTopic[messageId] + this._invokeStoreProcessingQueue() + cb(null, packet) + break } - delete this.messageIdToTopic[messageId] - this._invokeStoreProcessingQueue() - cb(null, packet) - break - } - case 'unsuback': { - delete this.outgoing[messageId] - this.messageIdProvider.deallocate(messageId) - this._invokeStoreProcessingQueue() - cb(null) - break - } - default: - that.emit('error', new Error('unrecognized packet type')) - } + case 'unsuback': { + delete this.outgoing[messageId] + this.messageIdProvider.deallocate(messageId) + this._invokeStoreProcessingQueue() + cb(null) + break + } + default: + that.emit('error', new Error('unrecognized packet type')) + } - if (this.disconnecting && - Object.keys(this.outgoing).length === 0) { - this.emit('outgoingEmpty') + if (this.disconnecting && + Object.keys(this.outgoing).length === 0) { + this.emit('outgoingEmpty') + } } -} -/** - * _handlePubrel - * - * @param {Object} packet - * @api private - */ -MqttClient.prototype._handlePubrel = function (packet, callback) { - debug('handling pubrel packet') - callback = typeof callback !== 'undefined' ? callback : nop - const messageId = packet.messageId - const that = this - - const comp = { cmd: 'pubcomp', messageId } - - that.incomingStore.get(packet, function (err, pub) { - if (!err) { - that.emit('message', pub.topic, pub.payload, pub) - that.handleMessage(pub, function (err) { - if (err) { - return callback(err) - } - that.incomingStore.del(pub, nop) + /** + * _handlePubrel + * + * @param {Object} packet + * @api private + */ + _handlePubrel(packet, callback) { + debug('handling pubrel packet') + callback = typeof callback !== 'undefined' ? callback : nop + const messageId = packet.messageId + const that = this + + const comp = { cmd: 'pubcomp', messageId } + + that.incomingStore.get(packet, function (err, pub) { + if (!err) { + that.emit('message', pub.topic, pub.payload, pub) + that.handleMessage(pub, function (err) { + if (err) { + return callback(err) + } + that.incomingStore.del(pub, nop) + that._sendPacket(comp, callback) + }) + } else { that._sendPacket(comp, callback) - }) - } else { - that._sendPacket(comp, callback) - } - }) -} - -/** - * _handleDisconnect - * - * @param {Object} packet - * @api private - */ -MqttClient.prototype._handleDisconnect = function (packet) { - this.emit('disconnect', packet) -} - -/** - * _nextId - * @return unsigned int - */ -MqttClient.prototype._nextId = function () { - return this.messageIdProvider.allocate() -} - -/** - * getLastMessageId - * @return unsigned int - */ -MqttClient.prototype.getLastMessageId = function () { - return this.messageIdProvider.getLastAllocated() -} + } + }) + } -/** - * _resubscribe - * @api private - */ -MqttClient.prototype._resubscribe = function () { - debug('_resubscribe') - const _resubscribeTopicsKeys = Object.keys(this._resubscribeTopics) - if (!this._firstConnection && - (this.options.clean || (this.options.protocolVersion === 5 && !this.connackPacket.sessionPresent)) && - _resubscribeTopicsKeys.length > 0) { - if (this.options.resubscribe) { - if (this.options.protocolVersion === 5) { - debug('_resubscribe: protocolVersion 5') - for (let topicI = 0; topicI < _resubscribeTopicsKeys.length; topicI++) { - const resubscribeTopic = {} - resubscribeTopic[_resubscribeTopicsKeys[topicI]] = this._resubscribeTopics[_resubscribeTopicsKeys[topicI]] - resubscribeTopic.resubscribe = true - this.subscribe(resubscribeTopic, { properties: resubscribeTopic[_resubscribeTopicsKeys[topicI]].properties }) + /** + * _handleDisconnect + * + * @param {Object} packet + * @api private + */ + _handleDisconnect(packet) { + this.emit('disconnect', packet) + } + + /** + * _nextId + * @return unsigned int + */ + _nextId() { + return this.messageIdProvider.allocate() + } + + /** + * getLastMessageId + * @return unsigned int + */ + getLastMessageId() { + return this.messageIdProvider.getLastAllocated() + } + + /** + * _resubscribe + * @api private + */ + _resubscribe() { + debug('_resubscribe') + const _resubscribeTopicsKeys = Object.keys(this._resubscribeTopics) + if (!this._firstConnection && + (this.options.clean || (this.options.protocolVersion === 5 && !this.connackPacket.sessionPresent)) && + _resubscribeTopicsKeys.length > 0) { + if (this.options.resubscribe) { + if (this.options.protocolVersion === 5) { + debug('_resubscribe: protocolVersion 5') + for (let topicI = 0; topicI < _resubscribeTopicsKeys.length; topicI++) { + const resubscribeTopic = {} + resubscribeTopic[_resubscribeTopicsKeys[topicI]] = this._resubscribeTopics[_resubscribeTopicsKeys[topicI]] + resubscribeTopic.resubscribe = true + this.subscribe(resubscribeTopic, { properties: resubscribeTopic[_resubscribeTopicsKeys[topicI]].properties }) + } + } else { + this._resubscribeTopics.resubscribe = true + this.subscribe(this._resubscribeTopics) } } else { - this._resubscribeTopics.resubscribe = true - this.subscribe(this._resubscribeTopics) + this._resubscribeTopics = {} } - } else { - this._resubscribeTopics = {} } - } - - this._firstConnection = false -} -/** - * _onConnect - * - * @api private - */ -MqttClient.prototype._onConnect = function (packet) { - if (this.disconnected) { - this.emit('connect', packet) - return + this._firstConnection = false } - const that = this + /** + * _onConnect + * + * @api private + */ + _onConnect(packet) { + if (this.disconnected) { + this.emit('connect', packet) + return + } - this.connackPacket = packet - this.messageIdProvider.clear() - this._setupPingTimer() + const that = this - this.connected = true + this.connackPacket = packet + this.messageIdProvider.clear() + this._setupPingTimer() - function startStreamProcess () { - let outStore = that.outgoingStore.createStream() + this.connected = true - function clearStoreProcessing () { - that._storeProcessing = false - that._packetIdsDuringStoreProcessing = {} - } + function startStreamProcess() { + let outStore = that.outgoingStore.createStream() - that.once('close', remove) - outStore.on('error', function (err) { - clearStoreProcessing() - that._flushStoreProcessingQueue() - that.removeListener('close', remove) - that.emit('error', err) - }) + function clearStoreProcessing() { + that._storeProcessing = false + that._packetIdsDuringStoreProcessing = {} + } - function remove () { - outStore.destroy() - outStore = null - that._flushStoreProcessingQueue() - clearStoreProcessing() - } + that.once('close', remove) + outStore.on('error', function (err) { + clearStoreProcessing() + that._flushStoreProcessingQueue() + that.removeListener('close', remove) + that.emit('error', err) + }) - function storeDeliver () { - // edge case, we wrapped this twice - if (!outStore) { - return + function remove() { + outStore.destroy() + outStore = null + that._flushStoreProcessingQueue() + clearStoreProcessing() } - const packet = outStore.read(1) + function storeDeliver() { + // edge case, we wrapped this twice + if (!outStore) { + return + } - let cb + const packet = outStore.read(1) - if (!packet) { - // read when data is available in the future - outStore.once('readable', storeDeliver) - return - } + let cb - that._storeProcessing = true + if (!packet) { + // read when data is available in the future + outStore.once('readable', storeDeliver) + return + } - // Skip already processed store packets - if (that._packetIdsDuringStoreProcessing[packet.messageId]) { - storeDeliver() - return - } + that._storeProcessing = true - // Avoid unnecessary stream read operations when disconnected - if (!that.disconnecting && !that.reconnectTimer) { - cb = that.outgoing[packet.messageId] ? that.outgoing[packet.messageId].cb : null - that.outgoing[packet.messageId] = { - volatile: false, - cb: function (err, status) { - // Ensure that the original callback passed in to publish gets invoked - if (cb) { - cb(err, status) + // Skip already processed store packets + if (that._packetIdsDuringStoreProcessing[packet.messageId]) { + storeDeliver() + return + } + + // Avoid unnecessary stream read operations when disconnected + if (!that.disconnecting && !that.reconnectTimer) { + cb = that.outgoing[packet.messageId] ? that.outgoing[packet.messageId].cb : null + that.outgoing[packet.messageId] = { + volatile: false, + cb: function (err, status) { + // Ensure that the original callback passed in to publish gets invoked + if (cb) { + cb(err, status) + } + + storeDeliver() } + } + that._packetIdsDuringStoreProcessing[packet.messageId] = true + if (that.messageIdProvider.register(packet.messageId)) { + that._sendPacket(packet, undefined, undefined, true) + } else { + debug('messageId: %d has already used.', packet.messageId) + } + } else if (outStore.destroy) { + outStore.destroy() + } + } - storeDeliver() + outStore.on('end', function () { + let allProcessed = true + for (const id in that._packetIdsDuringStoreProcessing) { + if (!that._packetIdsDuringStoreProcessing[id]) { + allProcessed = false + break } } - that._packetIdsDuringStoreProcessing[packet.messageId] = true - if (that.messageIdProvider.register(packet.messageId)) { - that._sendPacket(packet, undefined, undefined, true) + if (allProcessed) { + clearStoreProcessing() + that.removeListener('close', remove) + that._invokeAllStoreProcessingQueue() + that.emit('connect', packet) } else { - debug('messageId: %d has already used.', packet.messageId) + startStreamProcess() } - } else if (outStore.destroy) { - outStore.destroy() - } + }) + storeDeliver() } + // start flowing + startStreamProcess() + } - outStore.on('end', function () { - let allProcessed = true - for (const id in that._packetIdsDuringStoreProcessing) { - if (!that._packetIdsDuringStoreProcessing[id]) { - allProcessed = false - break - } - } - if (allProcessed) { - clearStoreProcessing() - that.removeListener('close', remove) - that._invokeAllStoreProcessingQueue() - that.emit('connect', packet) - } else { - startStreamProcess() + _invokeStoreProcessingQueue() { + if (this._storeProcessingQueue.length > 0) { + const f = this._storeProcessingQueue[0] + if (f && f.invoke()) { + this._storeProcessingQueue.shift() + return true } - }) - storeDeliver() + } + return false } - // start flowing - startStreamProcess() -} -MqttClient.prototype._invokeStoreProcessingQueue = function () { - if (this._storeProcessingQueue.length > 0) { - const f = this._storeProcessingQueue[0] - if (f && f.invoke()) { - this._storeProcessingQueue.shift() - return true + _invokeAllStoreProcessingQueue() { + while (this._invokeStoreProcessingQueue()) { /* empty */ } + } + + _flushStoreProcessingQueue() { + for (const f of this._storeProcessingQueue) { + if (f.cbStorePut) f.cbStorePut(new Error('Connection closed')) + if (f.callback) f.callback(new Error('Connection closed')) } + this._storeProcessingQueue.splice(0) } - return false } -MqttClient.prototype._invokeAllStoreProcessingQueue = function () { - while (this._invokeStoreProcessingQueue()) { /* empty */ } -} -MqttClient.prototype._flushStoreProcessingQueue = function () { - for (const f of this._storeProcessingQueue) { - if (f.cbStorePut) f.cbStorePut(new Error('Connection closed')) - if (f.callback) f.callback(new Error('Connection closed')) - } - this._storeProcessingQueue.splice(0) -} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + module.exports = MqttClient diff --git a/test/client.js b/test/client.js index deba79f80..2d78a81d4 100644 --- a/test/client.js +++ b/test/client.js @@ -32,9 +32,9 @@ describe('MqttClient', function () { abstractClientTests(server, config) describe('creating', function () { - it('should allow instantiation of MqttClient without the \'new\' operator', function (done) { + it('should allow instantiation of MqttClient', function (done) { try { - client = mqtt.MqttClient(function () { + client = new mqtt.MqttClient(function () { throw Error('break') }, {}) client.end() @@ -47,7 +47,7 @@ describe('MqttClient', function () { it('should disable number cache if specified in options', function (done) { try { assert.isTrue(mqttPacket.writeToStream.cacheNumbers) - client = mqtt.MqttClient(function () { + client = new mqtt.MqttClient(function () { throw Error('break') }, { writeCache: false }) client.end() From a2d27ad8176208514db51160734a488d6883fdb1 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 10 Jul 2023 16:57:20 +0200 Subject: [PATCH 2/8] refactor: convert to es2015 class --- lib/default-message-id-provider.js | 105 ++++++++---------- lib/store.js | 172 ++++++++++++++--------------- lib/topic-alias-recv.js | 66 ++++++----- lib/topic-alias-send.js | 136 +++++++++++------------ lib/unique-message-id-provider.js | 103 +++++++++-------- 5 files changed, 278 insertions(+), 304 deletions(-) diff --git a/lib/default-message-id-provider.js b/lib/default-message-id-provider.js index 3655983a0..6cc8b3e8b 100644 --- a/lib/default-message-id-provider.js +++ b/lib/default-message-id-provider.js @@ -4,66 +4,59 @@ * DefaultMessageAllocator constructor * @constructor */ -function DefaultMessageIdProvider () { - if (!(this instanceof DefaultMessageIdProvider)) { - return new DefaultMessageIdProvider() +class DefaultMessageIdProvider { + constructor() { + /** + * 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)) } - /** - * MessageIDs starting with 1 - * ensure that nextId is min. 1, see https://github.com/mqttjs/MQTT.js/issues/810 + * allocate + * + * Get the next messageId. + * @return unsigned int */ - this.nextId = Math.max(1, Math.floor(Math.random() * 65535)) -} - -/** - * allocate - * - * Get the next messageId. - * @return unsigned int - */ -DefaultMessageIdProvider.prototype.allocate = function () { - // id becomes current state of this.nextId and increments afterwards - const id = this.nextId++ - // Ensure 16 bit unsigned int (max 65535, nextId got one higher) - if (this.nextId === 65536) { - this.nextId = 1 + allocate() { + // id becomes current state of this.nextId and increments afterwards + const id = this.nextId++ + // Ensure 16 bit unsigned int (max 65535, nextId got one higher) + if (this.nextId === 65536) { + this.nextId = 1 + } + return id + } + /** + * getLastAllocated + * Get the last allocated messageId. + * @return unsigned int + */ + getLastAllocated() { + return (this.nextId === 1) ? 65535 : (this.nextId - 1) + } + /** + * register + * Register messageId. If success return true, otherwise return false. + * @param { unsigned int } - messageId to register, + * @return boolean + */ + register(messageId) { + return true + } + /** + * deallocate + * Deallocate messageId. + * @param { unsigned int } - messageId to deallocate, + */ + deallocate(messageId) { + } + /** + * clear + * Deallocate all messageIds. + */ + clear() { } - return id -} - -/** - * getLastAllocated - * Get the last allocated messageId. - * @return unsigned int - */ -DefaultMessageIdProvider.prototype.getLastAllocated = function () { - return (this.nextId === 1) ? 65535 : (this.nextId - 1) -} - -/** - * register - * Register messageId. If success return true, otherwise return false. - * @param { unsigned int } - messageId to register, - * @return boolean - */ -DefaultMessageIdProvider.prototype.register = function (messageId) { - return true -} - -/** - * deallocate - * Deallocate messageId. - * @param { unsigned int } - messageId to deallocate, - */ -DefaultMessageIdProvider.prototype.deallocate = function (messageId) { -} - -/** - * clear - * Deallocate all messageIds. - */ -DefaultMessageIdProvider.prototype.clear = function () { } module.exports = DefaultMessageIdProvider diff --git a/lib/store.js b/lib/store.js index 4f6b1a237..c1385f050 100644 --- a/lib/store.js +++ b/lib/store.js @@ -17,111 +17,105 @@ const defaultStoreOptions = { * * @param {Object} [options] - store options */ -function Store (options) { - if (!(this instanceof Store)) { - return new Store(options) - } - - this.options = options || {} +class Store { + + constructor(options) { + this.options = options || {} - // Defaults - this.options = xtend(defaultStoreOptions, options) + // Defaults + this.options = xtend(defaultStoreOptions, options) - this._inflights = new Map() -} - -/** - * Adds a packet to the store, a packet is - * anything that has a messageId property. - * - */ -Store.prototype.put = function (packet, cb) { - this._inflights.set(packet.messageId, packet) - - if (cb) { - cb() + this._inflights = new Map() } - - return this -} - -/** - * Creates a stream with all the packets in the store - * - */ -Store.prototype.createStream = function () { - const stream = new Readable(streamsOpts) - const values = [] - let destroyed = false - let i = 0 - - this._inflights.forEach(function (value, key) { - values.push(value) - }) - - stream._read = function () { - if (!destroyed && i < values.length) { - this.push(values[i++]) - } else { - this.push(null) + /** + * Adds a packet to the store, a packet is + * anything that has a messageId property. + * + */ + put(packet, cb) { + this._inflights.set(packet.messageId, packet) + + if (cb) { + cb() } - } - stream.destroy = function () { - if (destroyed) { - return + return this + } + /** + * Creates a stream with all the packets in the store + * + */ + createStream() { + const stream = new Readable(streamsOpts) + const values = [] + let destroyed = false + let i = 0 + + this._inflights.forEach(function (value, key) { + values.push(value) + }) + + stream._read = function () { + if (!destroyed && i < values.length) { + this.push(values[i++]) + } else { + this.push(null) + } } - const self = this + stream.destroy = function () { + if (destroyed) { + return + } - destroyed = true + const self = this - setTimeout(function () { - self.emit('close') - }, 0) - } + destroyed = true - return stream -} + setTimeout(function () { + self.emit('close') + }, 0) + } -/** - * deletes a packet from the store. - */ -Store.prototype.del = function (packet, cb) { - packet = this._inflights.get(packet.messageId) - if (packet) { - this._inflights.delete(packet.messageId) - cb(null, packet) - } else if (cb) { - cb(new Error('missing packet')) + return stream } + /** + * deletes a packet from the store. + */ + del(packet, cb) { + packet = this._inflights.get(packet.messageId) + if (packet) { + this._inflights.delete(packet.messageId) + cb(null, packet) + } else if (cb) { + cb(new Error('missing packet')) + } - return this -} - -/** - * get a packet from the store. - */ -Store.prototype.get = function (packet, cb) { - packet = this._inflights.get(packet.messageId) - if (packet) { - cb(null, packet) - } else if (cb) { - cb(new Error('missing packet')) + return this } + /** + * get a packet from the store. + */ + get(packet, cb) { + packet = this._inflights.get(packet.messageId) + if (packet) { + cb(null, packet) + } else if (cb) { + cb(new Error('missing packet')) + } - return this -} - -/** - * Close the store - */ -Store.prototype.close = function (cb) { - if (this.options.clean) { - this._inflights = null + return this } - if (cb) { - cb() + /** + * Close the store + */ + close(cb) { + if (this.options.clean) { + this._inflights = null + } + if (cb) { + cb() + } } } diff --git a/lib/topic-alias-recv.js b/lib/topic-alias-recv.js index 553341100..1456758ef 100644 --- a/lib/topic-alias-recv.js +++ b/lib/topic-alias-recv.js @@ -5,43 +5,39 @@ * This holds alias to topic map * @param {Number} [max] - topic alias maximum entries */ -function TopicAliasRecv (max) { - if (!(this instanceof TopicAliasRecv)) { - return new TopicAliasRecv(max) +class TopicAliasRecv { + constructor(max) { + this.aliasToTopic = {} + this.max = max } - this.aliasToTopic = {} - this.max = max -} - -/** - * Insert or update topic - alias entry. - * @param {String} [topic] - topic - * @param {Number} [alias] - topic alias - * @returns {Boolean} - if success return true otherwise false - */ -TopicAliasRecv.prototype.put = function (topic, alias) { - if (alias === 0 || alias > this.max) { - return false + /** + * Insert or update topic - alias entry. + * @param {String} [topic] - topic + * @param {Number} [alias] - topic alias + * @returns {Boolean} - if success return true otherwise false + */ + put(topic, alias) { + if (alias === 0 || alias > this.max) { + return false + } + this.aliasToTopic[alias] = topic + this.length = Object.keys(this.aliasToTopic).length + return true + } + /** + * Get topic by alias + * @param {String} [topic] - topic + * @returns {Number} - if mapped topic exists return topic alias, otherwise return undefined + */ + getTopicByAlias(alias) { + return this.aliasToTopic[alias] + } + /** + * Clear all entries + */ + clear() { + this.aliasToTopic = {} } - this.aliasToTopic[alias] = topic - this.length = Object.keys(this.aliasToTopic).length - return true -} - -/** - * Get topic by alias - * @param {String} [topic] - topic - * @returns {Number} - if mapped topic exists return topic alias, otherwise return undefined - */ -TopicAliasRecv.prototype.getTopicByAlias = function (alias) { - return this.aliasToTopic[alias] -} - -/** - * Clear all entries - */ -TopicAliasRecv.prototype.clear = function () { - this.aliasToTopic = {} } module.exports = TopicAliasRecv diff --git a/lib/topic-alias-send.js b/lib/topic-alias-send.js index 0c7292dc0..a2cbb6628 100644 --- a/lib/topic-alias-send.js +++ b/lib/topic-alias-send.js @@ -11,82 +11,76 @@ const NumberAllocator = require('number-allocator').NumberAllocator * This holds both topic to alias and alias to topic map * @param {Number} [max] - topic alias maximum entries */ -function TopicAliasSend (max) { - if (!(this instanceof TopicAliasSend)) { - return new TopicAliasSend(max) - } +class TopicAliasSend { - if (max > 0) { - this.aliasToTopic = new LRUCache({ max }) - this.topicToAlias = {} - this.numberAllocator = new NumberAllocator(1, max) - this.max = max - this.length = 0 + constructor(max) { + if (max > 0) { + this.aliasToTopic = new LRUCache({ max }) + this.topicToAlias = {} + this.numberAllocator = new NumberAllocator(1, max) + this.max = max + this.length = 0 + } } -} - -/** - * Insert or update topic - alias entry. - * @param {String} [topic] - topic - * @param {Number} [alias] - topic alias - * @returns {Boolean} - if success return true otherwise false - */ -TopicAliasSend.prototype.put = function (topic, alias) { - if (alias === 0 || alias > this.max) { - return false + /** + * Insert or update topic - alias entry. + * @param {String} [topic] - topic + * @param {Number} [alias] - topic alias + * @returns {Boolean} - if success return true otherwise false + */ + put(topic, alias) { + if (alias === 0 || alias > this.max) { + return false + } + const entry = this.aliasToTopic.get(alias) + if (entry) { + delete this.topicToAlias[entry] + } + this.aliasToTopic.set(alias, topic) + this.topicToAlias[topic] = alias + this.numberAllocator.use(alias) + this.length = this.aliasToTopic.size + return true } - const entry = this.aliasToTopic.get(alias) - if (entry) { - delete this.topicToAlias[entry] + /** + * Get topic by alias + * @param {Number} [alias] - topic alias + * @returns {String} - if mapped topic exists return topic, otherwise return undefined + */ + getTopicByAlias(alias) { + return this.aliasToTopic.get(alias) } - this.aliasToTopic.set(alias, topic) - this.topicToAlias[topic] = alias - this.numberAllocator.use(alias) - this.length = this.aliasToTopic.size - return true -} - -/** - * Get topic by alias - * @param {Number} [alias] - topic alias - * @returns {String} - if mapped topic exists return topic, otherwise return undefined - */ -TopicAliasSend.prototype.getTopicByAlias = function (alias) { - return this.aliasToTopic.get(alias) -} - -/** - * Get topic by alias - * @param {String} [topic] - topic - * @returns {Number} - if mapped topic exists return topic alias, otherwise return undefined - */ -TopicAliasSend.prototype.getAliasByTopic = function (topic) { - const alias = this.topicToAlias[topic] - if (typeof alias !== 'undefined') { - this.aliasToTopic.get(alias) // LRU update + /** + * Get topic by alias + * @param {String} [topic] - topic + * @returns {Number} - if mapped topic exists return topic alias, otherwise return undefined + */ + getAliasByTopic(topic) { + const alias = this.topicToAlias[topic] + if (typeof alias !== 'undefined') { + this.aliasToTopic.get(alias) // LRU update + } + return alias + } + /** + * Clear all entries + */ + clear() { + this.aliasToTopic.clear() + this.topicToAlias = {} + this.numberAllocator.clear() + this.length = 0 + } + /** + * Get Least Recently Used (LRU) topic alias + * @returns {Number} - if vacant alias exists then return it, otherwise then return LRU alias + */ + getLruAlias() { + const alias = this.numberAllocator.firstVacant() + if (alias) return alias + // get last alias (key) from LRU cache + return [...this.aliasToTopic.keys()][this.aliasToTopic.size - 1] } - return alias -} - -/** - * Clear all entries - */ -TopicAliasSend.prototype.clear = function () { - this.aliasToTopic.clear() - this.topicToAlias = {} - this.numberAllocator.clear() - this.length = 0 -} - -/** - * Get Least Recently Used (LRU) topic alias - * @returns {Number} - if vacant alias exists then return it, otherwise then return LRU alias - */ -TopicAliasSend.prototype.getLruAlias = function () { - const alias = this.numberAllocator.firstVacant() - if (alias) return alias - // get last alias (key) from LRU cache - return [...this.aliasToTopic.keys()][this.aliasToTopic.size - 1] } module.exports = TopicAliasSend diff --git a/lib/unique-message-id-provider.js b/lib/unique-message-id-provider.js index 5026253a9..d16272c55 100644 --- a/lib/unique-message-id-provider.js +++ b/lib/unique-message-id-provider.js @@ -6,60 +6,57 @@ const NumberAllocator = require('number-allocator').NumberAllocator * UniqueMessageAllocator constructor * @constructor */ -function UniqueMessageIdProvider () { - if (!(this instanceof UniqueMessageIdProvider)) { - return new UniqueMessageIdProvider() - } - - this.numberAllocator = new NumberAllocator(1, 65535) -} - -/** - * allocate - * - * Get the next messageId. - * @return if messageId is fully allocated then return null, - * otherwise return the smallest usable unsigned int messageId. - */ -UniqueMessageIdProvider.prototype.allocate = function () { - this.lastId = this.numberAllocator.alloc() - return this.lastId -} +class UniqueMessageIdProvider { + constructor() { + if (!(this instanceof UniqueMessageIdProvider)) { + return new UniqueMessageIdProvider() + } -/** - * getLastAllocated - * Get the last allocated messageId. - * @return unsigned int - */ -UniqueMessageIdProvider.prototype.getLastAllocated = function () { - return this.lastId -} - -/** - * register - * Register messageId. If success return true, otherwise return false. - * @param { unsigned int } - messageId to register, - * @return boolean - */ -UniqueMessageIdProvider.prototype.register = function (messageId) { - return this.numberAllocator.use(messageId) -} - -/** - * deallocate - * Deallocate messageId. - * @param { unsigned int } - messageId to deallocate, - */ -UniqueMessageIdProvider.prototype.deallocate = function (messageId) { - this.numberAllocator.free(messageId) -} - -/** - * clear - * Deallocate all messageIds. - */ -UniqueMessageIdProvider.prototype.clear = function () { - this.numberAllocator.clear() + this.numberAllocator = new NumberAllocator(1, 65535) + } + /** + * allocate + * + * Get the next messageId. + * @return if messageId is fully allocated then return null, + * otherwise return the smallest usable unsigned int messageId. + */ + allocate() { + this.lastId = this.numberAllocator.alloc() + return this.lastId + } + /** + * getLastAllocated + * Get the last allocated messageId. + * @return unsigned int + */ + getLastAllocated() { + return this.lastId + } + /** + * register + * Register messageId. If success return true, otherwise return false. + * @param { unsigned int } - messageId to register, + * @return boolean + */ + register(messageId) { + return this.numberAllocator.use(messageId) + } + /** + * deallocate + * Deallocate messageId. + * @param { unsigned int } - messageId to deallocate, + */ + deallocate(messageId) { + this.numberAllocator.free(messageId) + } + /** + * clear + * Deallocate all messageIds. + */ + clear() { + this.numberAllocator.clear() + } } module.exports = UniqueMessageIdProvider From 488f03f9962b473e8e2aab62594c53214f07e0f0 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 10 Jul 2023 17:03:28 +0200 Subject: [PATCH 3/8] refactor: remove `pump`, use `pipeline` --- bin/pub.js | 5 +- package-lock.json | 283 ---------------------------------------------- package.json | 2 - 3 files changed, 2 insertions(+), 288 deletions(-) diff --git a/bin/pub.js b/bin/pub.js index 2ea11f747..6309bca3e 100755 --- a/bin/pub.js +++ b/bin/pub.js @@ -3,11 +3,10 @@ 'use strict' const mqtt = require('../') -const pump = require('pump') +const { pipeline, Writable } = require('readable-stream') const path = require('path') const fs = require('fs') const concat = require('concat-stream') -const Writable = require('readable-stream').Writable const helpMe = require('help-me')({ dir: path.join(__dirname, '..', 'doc') }) @@ -40,7 +39,7 @@ function multisend (args) { } client.on('connect', function () { - pump(process.stdin, split2(), sender, function (err) { + pipeline(process.stdin, split2(), sender, function (err) { client.end() if (err) { throw err diff --git a/package-lock.json b/package-lock.json index 3fc7a738b..6322b2762 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,6 @@ "minimist": "^1.2.8", "mqtt-packet": "^8.2.0", "number-allocator": "^1.0.14", - "pump": "^3.0.0", "readable-stream": "^4.4.2", "reinterval": "^1.1.0", "rfdc": "^1.3.0", @@ -37,7 +36,6 @@ "@types/node": "^20.4.0", "@types/tape": "^5.6.0", "@types/ws": "^8.5.5", - "aedes": "^0.49.0", "airtap": "^4.0.4", "airtap-playwright": "^1.0.1", "browserify": "^17.0.0", @@ -1368,74 +1366,6 @@ "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", "dev": true }, - "node_modules/aedes": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/aedes/-/aedes-0.49.0.tgz", - "integrity": "sha512-RJK/ZggcCwXE9MW2mJ6+kLDa732DdkIbZcMP4sQkl8T8Gb9HSYKneuRub1kKgKQDvne8EQLOOo36PXLpbr3BOg==", - "dev": true, - "dependencies": { - "aedes-packet": "^3.0.0", - "aedes-persistence": "^9.1.2", - "end-of-stream": "^1.4.4", - "fastfall": "^1.5.1", - "fastparallel": "^2.4.1", - "fastseries": "^2.0.0", - "hyperid": "^3.1.1", - "mqemitter": "^5.0.0", - "mqtt-packet": "^8.1.2", - "retimer": "^3.0.0", - "reusify": "^1.0.4", - "uuid": "^9.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/aedes-packet": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aedes-packet/-/aedes-packet-3.0.0.tgz", - "integrity": "sha512-swASey0BxGs4/npZGWoiVDmnEyPvVFIRY6l2LVKL4rbiW8IhcIGDLfnb20Qo8U20itXlitAKPQ3MVTEbOGG5ZA==", - "dev": true, - "dependencies": { - "mqtt-packet": "^7.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/aedes-packet/node_modules/mqtt-packet": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-7.1.2.tgz", - "integrity": "sha512-FFZbcZ2omsf4c5TxEQfcX9hI+JzDpDKPT46OmeIBpVA7+t32ey25UNqlqNXTmeZOr5BLsSIERpQQLsFWJS94SQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.2", - "debug": "^4.1.1", - "process-nextick-args": "^2.0.1" - } - }, - "node_modules/aedes-persistence": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/aedes-persistence/-/aedes-persistence-9.1.2.tgz", - "integrity": "sha512-2Wlr5pwIK0eQOkiTwb8ZF6C20s8UPUlnsJ4kXYePZ3JlQl0NbBA176mzM8wY294BJ5wybpNc9P5XEQxqadRNcQ==", - "dev": true, - "dependencies": { - "aedes-packet": "^3.0.0", - "qlobber": "^7.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/aedes/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -5966,28 +5896,6 @@ "punycode": "^1.3.2" } }, - "node_modules/fastfall": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/fastfall/-/fastfall-1.5.1.tgz", - "integrity": "sha1-P+4DMxpJ0dObPN96XpzWb0dee5Q=", - "dev": true, - "dependencies": { - "reusify": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fastparallel": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/fastparallel/-/fastparallel-2.4.1.tgz", - "integrity": "sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4", - "xtend": "^4.0.2" - } - }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -5997,12 +5905,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fastseries": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fastseries/-/fastseries-2.0.0.tgz", - "integrity": "sha512-XBU9RXeoYc2/VnvMhplAxEmZLfIk7cvTBu+xwoBuTI8pL19E03cmca17QQycKIdxgwCeFA/a4u27gv1h3ya5LQ==", - "dev": true - }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -7224,16 +7126,6 @@ "integrity": "sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA==", "dev": true }, - "node_modules/hyperid": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/hyperid/-/hyperid-3.1.1.tgz", - "integrity": "sha512-RveV33kIksycSf7HLkq1sHB5wW0OwuX8ot8MYnY++gaaPXGFfKpBncHrAWxdpuEeRlazUMGWefwP1w6o6GaumA==", - "dev": true, - "dependencies": { - "uuid": "^8.3.2", - "uuid-parse": "^1.1.0" - } - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -9916,19 +9808,6 @@ "xtend": "~4.0.1" } }, - "node_modules/mqemitter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mqemitter/-/mqemitter-5.0.0.tgz", - "integrity": "sha512-rqNRQhGgl0W/NV+Zrx0rpAUTZcSlAtivCVUmXBUPcFYt+AeDEpoJgy5eKlFWJP6xnatONL59WIFdV0W6niOMhw==", - "dev": true, - "dependencies": { - "fastparallel": "^2.3.0", - "qlobber": "^7.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mqtt-connection": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mqtt-connection/-/mqtt-connection-4.1.0.tgz", @@ -12024,15 +11903,6 @@ "looper": "^2.0.0" } }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -12054,15 +11924,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/qlobber": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/qlobber/-/qlobber-7.0.1.tgz", - "integrity": "sha512-FsFg9lMuMEFNKmTO9nV7tlyPhx8BmskPPjH2akWycuYVTtWaVwhW5yCHLJQ6Q+3mvw5cFX2vMfW2l9z2SiYAbg==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -12766,12 +12627,6 @@ "through": "~2.3.4" } }, - "node_modules/retimer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", - "integrity": "sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==", - "dev": true - }, "node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -15138,12 +14993,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/uuid-parse": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", - "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==", - "dev": true - }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -16840,66 +16689,6 @@ "integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==", "dev": true }, - "aedes": { - "version": "0.49.0", - "resolved": "https://registry.npmjs.org/aedes/-/aedes-0.49.0.tgz", - "integrity": "sha512-RJK/ZggcCwXE9MW2mJ6+kLDa732DdkIbZcMP4sQkl8T8Gb9HSYKneuRub1kKgKQDvne8EQLOOo36PXLpbr3BOg==", - "dev": true, - "requires": { - "aedes-packet": "^3.0.0", - "aedes-persistence": "^9.1.2", - "end-of-stream": "^1.4.4", - "fastfall": "^1.5.1", - "fastparallel": "^2.4.1", - "fastseries": "^2.0.0", - "hyperid": "^3.1.1", - "mqemitter": "^5.0.0", - "mqtt-packet": "^8.1.2", - "retimer": "^3.0.0", - "reusify": "^1.0.4", - "uuid": "^9.0.0" - }, - "dependencies": { - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "dev": true - } - } - }, - "aedes-packet": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aedes-packet/-/aedes-packet-3.0.0.tgz", - "integrity": "sha512-swASey0BxGs4/npZGWoiVDmnEyPvVFIRY6l2LVKL4rbiW8IhcIGDLfnb20Qo8U20itXlitAKPQ3MVTEbOGG5ZA==", - "dev": true, - "requires": { - "mqtt-packet": "^7.0.0" - }, - "dependencies": { - "mqtt-packet": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-7.1.2.tgz", - "integrity": "sha512-FFZbcZ2omsf4c5TxEQfcX9hI+JzDpDKPT46OmeIBpVA7+t32ey25UNqlqNXTmeZOr5BLsSIERpQQLsFWJS94SQ==", - "dev": true, - "requires": { - "bl": "^4.0.2", - "debug": "^4.1.1", - "process-nextick-args": "^2.0.1" - } - } - } - }, - "aedes-persistence": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/aedes-persistence/-/aedes-persistence-9.1.2.tgz", - "integrity": "sha512-2Wlr5pwIK0eQOkiTwb8ZF6C20s8UPUlnsJ4kXYePZ3JlQl0NbBA176mzM8wY294BJ5wybpNc9P5XEQxqadRNcQ==", - "dev": true, - "requires": { - "aedes-packet": "^3.0.0", - "qlobber": "^7.0.0" - } - }, "agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -20515,25 +20304,6 @@ "punycode": "^1.3.2" } }, - "fastfall": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/fastfall/-/fastfall-1.5.1.tgz", - "integrity": "sha1-P+4DMxpJ0dObPN96XpzWb0dee5Q=", - "dev": true, - "requires": { - "reusify": "^1.0.0" - } - }, - "fastparallel": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/fastparallel/-/fastparallel-2.4.1.tgz", - "integrity": "sha512-qUmhxPgNHmvRjZKBFUNI0oZuuH9OlSIOXmJ98lhKPxMZZ7zS/Fi0wRHOihDSz0R1YiIOjxzOY4bq65YTcdBi2Q==", - "dev": true, - "requires": { - "reusify": "^1.0.4", - "xtend": "^4.0.2" - } - }, "fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -20543,12 +20313,6 @@ "reusify": "^1.0.4" } }, - "fastseries": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fastseries/-/fastseries-2.0.0.tgz", - "integrity": "sha512-XBU9RXeoYc2/VnvMhplAxEmZLfIk7cvTBu+xwoBuTI8pL19E03cmca17QQycKIdxgwCeFA/a4u27gv1h3ya5LQ==", - "dev": true - }, "fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -21497,16 +21261,6 @@ "integrity": "sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA==", "dev": true }, - "hyperid": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/hyperid/-/hyperid-3.1.1.tgz", - "integrity": "sha512-RveV33kIksycSf7HLkq1sHB5wW0OwuX8ot8MYnY++gaaPXGFfKpBncHrAWxdpuEeRlazUMGWefwP1w6o6GaumA==", - "dev": true, - "requires": { - "uuid": "^8.3.2", - "uuid-parse": "^1.1.0" - } - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -23605,16 +23359,6 @@ } } }, - "mqemitter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mqemitter/-/mqemitter-5.0.0.tgz", - "integrity": "sha512-rqNRQhGgl0W/NV+Zrx0rpAUTZcSlAtivCVUmXBUPcFYt+AeDEpoJgy5eKlFWJP6xnatONL59WIFdV0W6niOMhw==", - "dev": true, - "requires": { - "fastparallel": "^2.3.0", - "qlobber": "^7.0.0" - } - }, "mqtt-connection": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/mqtt-connection/-/mqtt-connection-4.1.0.tgz", @@ -25275,15 +25019,6 @@ "looper": "^2.0.0" } }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -25299,12 +25034,6 @@ "escape-goat": "^4.0.0" } }, - "qlobber": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/qlobber/-/qlobber-7.0.1.tgz", - "integrity": "sha512-FsFg9lMuMEFNKmTO9nV7tlyPhx8BmskPPjH2akWycuYVTtWaVwhW5yCHLJQ6Q+3mvw5cFX2vMfW2l9z2SiYAbg==", - "dev": true - }, "qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -25836,12 +25565,6 @@ "through": "~2.3.4" } }, - "retimer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/retimer/-/retimer-3.0.0.tgz", - "integrity": "sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==", - "dev": true - }, "retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", @@ -27721,12 +27444,6 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, - "uuid-parse": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/uuid-parse/-/uuid-parse-1.1.0.tgz", - "integrity": "sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==", - "dev": true - }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index ba71a5800..55cb1e1fd 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,6 @@ "minimist": "^1.2.8", "mqtt-packet": "^8.2.0", "number-allocator": "^1.0.14", - "pump": "^3.0.0", "readable-stream": "^4.4.2", "reinterval": "^1.1.0", "rfdc": "^1.3.0", @@ -111,7 +110,6 @@ "@types/node": "^20.4.0", "@types/tape": "^5.6.0", "@types/ws": "^8.5.5", - "aedes": "^0.49.0", "airtap": "^4.0.4", "airtap-playwright": "^1.0.1", "browserify": "^17.0.0", From 81c32db13cd9d4dfef7ed50e3e2e849d5a176baa Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 10 Jul 2023 17:06:40 +0200 Subject: [PATCH 4/8] chore: remove `inherits` --- package-lock.json | 1 - package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6322b2762..0f7f9f681 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "debug": "^4.3.4", "duplexify": "^4.1.2", "help-me": "^4.2.0", - "inherits": "^2.0.4", "lru-cache": "^7.18.3", "minimist": "^1.2.8", "mqtt-packet": "^8.2.0", diff --git a/package.json b/package.json index 55cb1e1fd..cae64af9f 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,6 @@ "debug": "^4.3.4", "duplexify": "^4.1.2", "help-me": "^4.2.0", - "inherits": "^2.0.4", "lru-cache": "^7.18.3", "minimist": "^1.2.8", "mqtt-packet": "^8.2.0", From 7a699361e9cd8244fd00476953261542228c94ea Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Mon, 10 Jul 2023 17:14:22 +0200 Subject: [PATCH 5/8] chore: run browser tests only on latest --- .github/workflows/mqttjs-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/mqttjs-test.yml b/.github/workflows/mqttjs-test.yml index 9511d07c8..eb1c47d6c 100644 --- a/.github/workflows/mqttjs-test.yml +++ b/.github/workflows/mqttjs-test.yml @@ -44,6 +44,8 @@ jobs: DEBUG: "mqttjs" - name: Test Browser + if: matrix.node-version == '20.x' + # only run on latest node version, no reason to run on all timeout-minutes: 2 run: | npm run browser-build From a871cec4aa11122443aa3bebadf44891955c2079 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 11 Jul 2023 08:27:02 +0200 Subject: [PATCH 6/8] fix: remove `xtend` --- lib/client.js | 37 ++++--- lib/connect/index.js | 9 +- lib/store.js | 6 +- package-lock.json | 7 +- package.json | 3 +- test/abstract_client.js | 203 +++++++++++++++++++-------------------- test/websocket_client.js | 180 +++++++++++++++++----------------- 7 files changed, 219 insertions(+), 226 deletions(-) diff --git a/lib/client.js b/lib/client.js index 7613db2be..09d9ce8ca 100644 --- a/lib/client.js +++ b/lib/client.js @@ -13,7 +13,6 @@ const Writable = require('readable-stream').Writable const reInterval = require('reinterval') const clone = require('rfdc/default') const validations = require('./validations') -const xtend = require('xtend') const debug = require('debug')('mqttjs:client') const nextTick = process ? process.nextTick : function (callback) { setTimeout(callback, 0) } const setImmediate = global.setImmediate || function (...args) { @@ -89,11 +88,11 @@ const errors = { 162: 'Wildcard Subscriptions not supported' } -function defaultId () { +function defaultId() { return 'mqttjs_' + Math.random().toString(16).substr(2, 8) } -function applyTopicAlias (client, packet) { +function applyTopicAlias(client, packet) { if (client.options.protocolVersion === 5) { if (packet.cmd === 'publish') { let alias @@ -143,7 +142,7 @@ function applyTopicAlias (client, packet) { } } -function removeTopicAliasAndRecoverTopicName (client, packet) { +function removeTopicAliasAndRecoverTopicName(client, packet) { let alias if (packet.properties) { alias = packet.properties.topicAlias @@ -168,7 +167,7 @@ function removeTopicAliasAndRecoverTopicName (client, packet) { } } -function sendPacket (client, packet, cb) { +function sendPacket(client, packet, cb) { debug('sendPacket :: packet: %O', packet) debug('sendPacket :: emitting `packetsend`') @@ -186,7 +185,7 @@ function sendPacket (client, packet, cb) { } } -function flush (queue) { +function flush(queue) { if (queue) { debug('flush: queue exists? %b', !!(queue)) Object.keys(queue).forEach(function (messageId) { @@ -200,7 +199,7 @@ function flush (queue) { } } -function flushVolatile (queue) { +function flushVolatile(queue) { if (queue) { debug('flushVolatile :: deleting volatile messages from the queue and setting their callbacks as error function') Object.keys(queue).forEach(function (messageId) { @@ -212,7 +211,7 @@ function flushVolatile (queue) { } } -function storeAndSend (client, packet, cb, cbStorePut) { +function storeAndSend(client, packet, cb, cbStorePut) { debug('storeAndSend :: store packet with cmd %s to outgoingStore', packet.cmd) let storePacket = packet let err @@ -226,7 +225,7 @@ function storeAndSend (client, packet, cb, cbStorePut) { return cb && cb(err) } } - client.outgoingStore.put(storePacket, function storedPacket (err) { + client.outgoingStore.put(storePacket, function storedPacket(err) { if (err) { return cb && cb(err) } @@ -235,7 +234,7 @@ function storeAndSend (client, packet, cb, cbStorePut) { }) } -function nop (error) { +function nop(error) { debug('nop ::', error) } @@ -247,7 +246,7 @@ function nop (error) { * (see Connection#connect) */ class MqttClient extends EventEmitter { - + constructor(streamBuilder, options) { super() @@ -401,7 +400,7 @@ class MqttClient extends EventEmitter { debug('MqttClient :: setting up stream') this._setupStream() } - + /** * setup the event handlers in the inner stream. * @@ -509,7 +508,7 @@ class MqttClient extends EventEmitter { return this } if (this.options.properties.authenticationMethod && this.options.authPacket && typeof this.options.authPacket === 'object') { - const authPacket = xtend({ cmd: 'auth', reasonCode: 0 }, this.options.authPacket) + const authPacket = { cmd: 'auth', reasonCode: 0, ...this.options.authPacket } sendPacket(this, authPacket) } } @@ -1394,7 +1393,7 @@ class MqttClient extends EventEmitter { this._shiftPingInterval() } if (packet.properties.maximumPacketSize) { - if (!options.properties) { options.properties = {}} + if (!options.properties) { options.properties = {} } options.properties.maximumPacketSize = packet.properties.maximumPacketSize } } @@ -1528,8 +1527,8 @@ class MqttClient extends EventEmitter { code = error error = null } - if (error) { return that.emit('error', error)} - if (validReasonCodes.indexOf(code) === -1) { return that.emit('error', new Error('Wrong reason code for pubrec'))} + if (error) { return that.emit('error', error) } + if (validReasonCodes.indexOf(code) === -1) { return that.emit('error', new Error('Wrong reason code for pubrec')) } if (code) { that._sendPacket({ cmd: 'pubrec', messageId, reasonCode: code }, done) } else { @@ -1547,9 +1546,9 @@ class MqttClient extends EventEmitter { code = error error = null } - if (error) { return that.emit('error', error)} - if (validReasonCodes.indexOf(code) === -1) { return that.emit('error', new Error('Wrong reason code for puback'))} - if (!code) { that.emit('message', topic, message, packet)} + if (error) { return that.emit('error', error) } + if (validReasonCodes.indexOf(code) === -1) { return that.emit('error', new Error('Wrong reason code for puback')) } + if (!code) { that.emit('message', topic, message, packet) } that.handleMessage(packet, function (err) { if (err) { return done && done(err) diff --git a/lib/connect/index.js b/lib/connect/index.js index 707ba0d2d..d44e194d7 100644 --- a/lib/connect/index.js +++ b/lib/connect/index.js @@ -6,7 +6,6 @@ const DefaultMessageIdProvider = require('../default-message-id-provider') const UniqueMessageIdProvider = require('../unique-message-id-provider') const { IS_BROWSER } = require('../is-browser') const url = require('url') -const xtend = require('xtend') const debug = require('debug')('mqttjs') const protocols = {} @@ -33,7 +32,7 @@ protocols.wss = require('./ws') * * @param {Object} [opts] option object */ -function parseAuthOptions (opts) { +function parseAuthOptions(opts) { let matches if (opts.auth) { matches = opts.auth.match(/^(.+):(.+)$/) @@ -52,7 +51,7 @@ function parseAuthOptions (opts) { * @param {String} [brokerUrl] - url of the broker, optional * @param {Object} opts - see MqttClient#constructor */ -function connect (brokerUrl, opts) { +function connect(brokerUrl, opts) { debug('connecting to an MQTT broker...') if ((typeof brokerUrl === 'object') && !opts) { opts = brokerUrl @@ -68,7 +67,7 @@ function connect (brokerUrl, opts) { parsed.port = Number(parsed.port) } - opts = xtend(parsed, opts) + opts = { ...parsed, ...opts } if (opts.protocol === null) { throw new Error('Missing protocol') @@ -139,7 +138,7 @@ function connect (brokerUrl, opts) { opts.defaultProtocol = opts.protocol } - function wrapper (client) { + function wrapper(client) { if (opts.servers) { if (!client._reconnectCount || client._reconnectCount === opts.servers.length) { client._reconnectCount = 0 diff --git a/lib/store.js b/lib/store.js index c1385f050..8b4f2d2e8 100644 --- a/lib/store.js +++ b/lib/store.js @@ -3,8 +3,6 @@ /** * Module dependencies */ -const xtend = require('xtend') - const Readable = require('readable-stream').Readable const streamsOpts = { objectMode: true } const defaultStoreOptions = { @@ -18,12 +16,12 @@ const defaultStoreOptions = { * @param {Object} [options] - store options */ class Store { - + constructor(options) { this.options = options || {} // Defaults - this.options = xtend(defaultStoreOptions, options) + this.options = { ...defaultStoreOptions, ...options } this._inflights = new Map() } diff --git a/package-lock.json b/package-lock.json index 0f7f9f681..568540f45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,8 +22,7 @@ "reinterval": "^1.1.0", "rfdc": "^1.3.0", "split2": "^4.2.0", - "ws": "^8.13.0", - "xtend": "^4.0.2" + "ws": "^8.13.0" }, "bin": { "mqtt": "bin/mqtt.js", @@ -15560,6 +15559,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, "engines": { "node": ">=0.4" } @@ -27875,7 +27875,8 @@ "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true }, "y18n": { "version": "5.0.8", diff --git a/package.json b/package.json index cae64af9f..8947b686e 100644 --- a/package.json +++ b/package.json @@ -101,8 +101,7 @@ "reinterval": "^1.1.0", "rfdc": "^1.3.0", "split2": "^4.2.0", - "ws": "^8.13.0", - "xtend": "^4.0.2" + "ws": "^8.13.0" }, "devDependencies": { "@release-it/conventional-changelog": "^6.0.0", diff --git a/test/abstract_client.js b/test/abstract_client.js index d37a1bde7..5fe44c4d2 100644 --- a/test/abstract_client.js +++ b/test/abstract_client.js @@ -6,7 +6,6 @@ const should = require('chai').should const sinon = require('sinon') const mqtt = require('../') -const xtend = require('xtend') const Store = require('./../lib/store') const assert = require('chai').assert const ports = require('./helpers/port_list') @@ -38,8 +37,8 @@ const levelStore = require('mqtt-level-store') module.exports = function (server, config) { const version = config.protocolVersion || 4 - function connect (opts) { - opts = xtend(config, opts) + function connect(opts) { + opts = { ...config, ...opts } return mqtt.connect(opts) } @@ -439,7 +438,7 @@ module.exports = function (server, config) { // fake a port const client = connect({ reconnectPeriod: 20, port: 4557 }) - client.on('error', function () {}) + client.on('error', function () { }) client.on('offline', function () { client.end(true, done) @@ -826,7 +825,7 @@ module.exports = function (server, config) { server.on('client', onClient) - function onClient (serverClient) { + function onClient(serverClient) { serverClient.once('connect', function () { server.removeListener('client', onClient) }) @@ -852,7 +851,7 @@ module.exports = function (server, config) { server.on('client', onClient) - function onClient (serverClient) { + function onClient(serverClient) { serverClient.once('connect', function () { server.removeListener('client', onClient) }) @@ -1051,7 +1050,7 @@ module.exports = function (server, config) { let countSent = 0 let countReceived = 0 - function publishNext () { + function publishNext() { client.publish('test', 'test', { qos: 2 }, function (err) { assert.ifError(err) countSent++ @@ -1088,7 +1087,7 @@ module.exports = function (server, config) { }) }) - function testQosHandleMessage (qos, done) { + function testQosHandleMessage(qos, done) { const client = connect() let messageEventCount = 0 @@ -1168,34 +1167,34 @@ module.exports = function (server, config) { it('should silently ignore errors thrown by `handleMessage` and return when no callback is passed ' + 'into `handlePublish` method', function (done) { - const client = connect() + const client = connect() - client.handleMessage = function (packet, callback) { - callback(new Error('Error thrown by the application')) - } + client.handleMessage = function (packet, callback) { + callback(new Error('Error thrown by the application')) + } - try { - client._handlePublish({ - messageId: Math.floor(65535 * Math.random()), - topic: 'test', - payload: 'test', - qos: 1 - }) - client.end(true, done) - } catch (err) { - client.end(true, () => { done(err) }) - } - }) + try { + client._handlePublish({ + messageId: Math.floor(65535 * Math.random()), + topic: 'test', + payload: 'test', + qos: 1 + }) + client.end(true, done) + } catch (err) { + client.end(true, () => { done(err) }) + } + }) it('should handle error with async incoming store in QoS 1 `handlePublish` method', function (done) { class AsyncStore { - put (packet, cb) { + put(packet, cb) { process.nextTick(function () { cb(null, 'Error') }) } - close (cb) { + close(cb) { cb() } } @@ -1215,25 +1214,25 @@ module.exports = function (server, config) { it('should handle error with async incoming store in QoS 2 `handlePublish` method', function (done) { class AsyncStore { - put (packet, cb) { + put(packet, cb) { process.nextTick(function () { cb(null, 'Error') }) } - del (packet, cb) { + del(packet, cb) { process.nextTick(function () { cb(new Error('Error')) }) } - get (packet, cb) { + get(packet, cb) { process.nextTick(function () { cb(null, { cmd: 'publish' }) }) } - close (cb) { + close(cb) { cb() } } @@ -1253,25 +1252,25 @@ module.exports = function (server, config) { it('should handle error with async incoming store in QoS 2 `handlePubrel` method', function (done) { class AsyncStore { - put (packet, cb) { + put(packet, cb) { process.nextTick(function () { cb(null, 'Error') }) } - del (packet, cb) { + del(packet, cb) { process.nextTick(function () { cb(new Error('Error')) }) } - get (packet, cb) { + get(packet, cb) { process.nextTick(function () { cb(null, { cmd: 'publish' }) }) } - close (cb) { + close(cb) { cb() } } @@ -1290,26 +1289,26 @@ module.exports = function (server, config) { it('should handle success with async incoming store in QoS 2 `handlePubrel` method', function (done) { let delComplete = false class AsyncStore { - put (packet, cb) { + put(packet, cb) { process.nextTick(function () { cb(null, 'Error') }) } - del (packet, cb) { + del(packet, cb) { process.nextTick(function () { delComplete = true cb(null) }) } - get (packet, cb) { + get(packet, cb) { process.nextTick(function () { cb(null, { cmd: 'publish' }) }) } - close (cb) { + close(cb) { cb() } } @@ -1362,37 +1361,37 @@ module.exports = function (server, config) { it('should silently ignore errors thrown by `handleMessage` and return when no callback is passed ' + 'into `handlePubrel` method', function (done) { - const store = new Store() - const client = connect({ incomingStore: store }) + const store = new Store() + const client = connect({ incomingStore: store }) - const messageId = Math.floor(65535 * Math.random()) - const topic = 'test' - const payload = 'test' - const qos = 2 + const messageId = Math.floor(65535 * Math.random()) + const topic = 'test' + const payload = 'test' + const qos = 2 - client.handleMessage = function (packet, callback) { - callback(new Error('Error thrown by the application')) - } + client.handleMessage = function (packet, callback) { + callback(new Error('Error thrown by the application')) + } - client.once('connect', function () { - client.subscribe(topic, { qos: 2 }) + client.once('connect', function () { + client.subscribe(topic, { qos: 2 }) - store.put({ - messageId, - topic, - payload, - qos, - cmd: 'publish' - }, function () { - try { - client._handlePubrel({ cmd: 'pubrel', messageId }) - client.end(true, done) - } catch (err) { - client.end(true, () => { done(err) }) - } + store.put({ + messageId, + topic, + payload, + qos, + cmd: 'publish' + }, function () { + try { + client._handlePubrel({ cmd: 'pubrel', messageId }) + client.end(true, done) + } catch (err) { + client.end(true, () => { done(err) }) + } + }) }) }) - }) it('should keep message order', function (done) { let publishCount = 0 @@ -1403,7 +1402,7 @@ module.exports = function (server, config) { const server2 = serverBuilder(config.protocol, function (serverClient) { // errors are not interesting for this test // but they might happen on some platforms - serverClient.on('error', function () {}) + serverClient.on('error', function () { }) serverClient.on('connect', function (packet) { const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 } @@ -1463,7 +1462,7 @@ module.exports = function (server, config) { }) }) - function testCallbackStorePutByQoS (qos, clean, expected, done) { + function testCallbackStorePutByQoS(qos, clean, expected, done) { const client = connect({ clean, clientId: 'testId' @@ -1471,7 +1470,7 @@ module.exports = function (server, config) { const callbacks = [] - function cbStorePut () { + function cbStorePut() { callbacks.push('storeput') } @@ -1727,7 +1726,7 @@ module.exports = function (server, config) { const client = connect({ keepalive: 1, reconnectPeriod: 100 }) // Fake no pingresp being send by stubbing the _handlePingresp function - client._handlePingresp = function () {} + client._handlePingresp = function () { } client.once('connect', function () { client.once('connect', function () { @@ -2353,7 +2352,7 @@ module.exports = function (server, config) { }) }) - function testMultiplePubrel (shouldSendPubcompFail, done) { + function testMultiplePubrel(shouldSendPubcompFail, done) { const client = connect() const testTopic = 'test' const testMessage = 'message' @@ -2391,34 +2390,34 @@ module.exports = function (server, config) { } case 'pubrec': case 'pubcomp': - { - // for both pubrec and pubcomp, reply with pubrel, simulating the server not receiving the pubcomp - if (packet.cmd === 'pubcomp') { - pubcompCount++ - if (pubcompCount === 2) { - // end the test once the client has gone through two rounds of replying to pubrel messages - assert.strictEqual(pubrelCount, 2) - assert.strictEqual(handleMessageCount, 1) - assert.strictEqual(emitMessageCount, 1) - client._sendPacket = origSendPacket - client.end(true, done) - break + { + // for both pubrec and pubcomp, reply with pubrel, simulating the server not receiving the pubcomp + if (packet.cmd === 'pubcomp') { + pubcompCount++ + if (pubcompCount === 2) { + // end the test once the client has gone through two rounds of replying to pubrel messages + assert.strictEqual(pubrelCount, 2) + assert.strictEqual(handleMessageCount, 1) + assert.strictEqual(emitMessageCount, 1) + client._sendPacket = origSendPacket + client.end(true, done) + break + } } - } - // simulate the pubrel message, either in response to pubrec or to mock pubcomp failing to be received - const pubrel = { cmd: 'pubrel', messageId: mid } - pubrelCount++ - client._handlePacket(pubrel, function (err) { - if (shouldSendFail) { - assert.exists(err) - assert.instanceOf(err, Error) - } else { - assert.notExists(err) - } - }) - break - } + // simulate the pubrel message, either in response to pubrec or to mock pubcomp failing to be received + const pubrel = { cmd: 'pubrel', messageId: mid } + pubrelCount++ + client._handlePacket(pubrel, function (err) { + if (shouldSendFail) { + assert.exists(err) + assert.instanceOf(err, Error) + } else { + assert.notExists(err) + } + }) + break + } } } @@ -2594,7 +2593,7 @@ module.exports = function (server, config) { check() }) - function check () { + function check() { if (serverPublished && clientCalledBack) { client.end(true, done) } @@ -2634,7 +2633,7 @@ module.exports = function (server, config) { server.once('client', function (serverClient) { // ignore errors - serverClient.on('error', function () {}) + serverClient.on('error', function () { }) serverClient.on('publish', function () { setImmediate(function () { serverClient.stream.destroy() @@ -2654,7 +2653,7 @@ module.exports = function (server, config) { check() }) - function check () { + function check() { if (serverPublished && clientCalledBack) { client.end(true, done) } @@ -2996,7 +2995,7 @@ module.exports = function (server, config) { client.publish('topic', 'payload', { qos: 1 }) } }) - client.on('error', function () {}) + client.on('error', function () { }) }) }) @@ -3043,7 +3042,7 @@ module.exports = function (server, config) { client.publish('topic', 'payload', { qos: 2 }) } }) - client.on('error', function () {}) + client.on('error', function () { }) }) }) @@ -3099,7 +3098,7 @@ module.exports = function (server, config) { }) } }) - client.on('error', function () {}) + client.on('error', function () { }) }) }) @@ -3113,7 +3112,7 @@ module.exports = function (server, config) { const server2 = serverBuilder(config.protocol, function (serverClient) { // errors are not interesting for this test // but they might happen on some platforms - serverClient.on('error', function () {}) + serverClient.on('error', function () { }) serverClient.on('connect', function (packet) { const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 } @@ -3171,7 +3170,7 @@ module.exports = function (server, config) { client.publish('topic', 'payload3', { qos: 1 }) } }) - client.on('error', function () {}) + client.on('error', function () { }) }) }) diff --git a/test/websocket_client.js b/test/websocket_client.js index 84da2a9f4..76aa868ad 100644 --- a/test/websocket_client.js +++ b/test/websocket_client.js @@ -7,12 +7,11 @@ const abstractClientTests = require('./abstract_client') const ports = require('./helpers/port_list') const MqttServerNoWait = require('./server').MqttServerNoWait const mqtt = require('../') -const xtend = require('xtend') const assert = require('assert') const port = 9999 const httpServer = http.createServer() -function attachWebsocketServer (httpServer) { +function attachWebsocketServer(httpServer) { const webSocketServer = new WebSocket.Server({ server: httpServer, perMessageDeflate: false }) webSocketServer.on('connection', function (ws) { @@ -20,14 +19,14 @@ function attachWebsocketServer (httpServer) { const connection = new MQTTConnection(stream) connection.protocol = ws.protocol httpServer.emit('client', connection) - stream.on('error', function () {}) - connection.on('error', function () {}) + stream.on('error', function () { }) + connection.on('error', function () { }) }) return httpServer } -function attachClientEventHandlers (client) { +function attachClientEventHandlers(client) { client.on('connect', function (packet) { if (packet.clientId === 'invalid') { client.connack({ returnCode: 2 }) @@ -89,107 +88,106 @@ httpServer.on('client', attachClientEventHandlers).listen(port) describe('Websocket Client', function () { const baseConfig = { protocol: 'ws', port } - function makeOptions (custom) { - // xtend returns a new object. Does not mutate arguments - return xtend(baseConfig, custom || {}) + function makeOptions(custom) { + return { ...baseConfig, ...(custom || {}) } } - it('should use mqtt as the protocol by default', function (done) { - httpServer.once('client', function (client) { - assert.strictEqual(client.protocol, 'mqtt') - }) - mqtt.connect(makeOptions()).on('connect', function () { - this.end(true, (err) => done(err)) - }) +it('should use mqtt as the protocol by default', function (done) { + httpServer.once('client', function (client) { + assert.strictEqual(client.protocol, 'mqtt') }) - - it('should be able to transform the url (for e.g. to sign it)', function (done) { - const baseUrl = 'ws://localhost:9999/mqtt' - const sig = '?AUTH=token' - const expected = baseUrl + sig - let actual - const opts = makeOptions({ - path: '/mqtt', - transformWsUrl: function (url, opt, client) { - assert.equal(url, baseUrl) - assert.strictEqual(opt, opts) - assert.strictEqual(client.options, opts) - assert.strictEqual(typeof opt.transformWsUrl, 'function') - assert(client instanceof mqtt.MqttClient) - url += sig - actual = url - return url - } - }) - mqtt.connect(opts) - .on('connect', function () { - assert.equal(this.stream.url, expected) - assert.equal(actual, expected) - this.end(true, (err) => done(err)) - }) + mqtt.connect(makeOptions()).on('connect', function () { + this.end(true, (err) => done(err)) }) +}) - it('should use mqttv3.1 as the protocol if using v3.1', function (done) { - httpServer.once('client', function (client) { - assert.strictEqual(client.protocol, 'mqttv3.1') +it('should be able to transform the url (for e.g. to sign it)', function (done) { + const baseUrl = 'ws://localhost:9999/mqtt' + const sig = '?AUTH=token' + const expected = baseUrl + sig + let actual + const opts = makeOptions({ + path: '/mqtt', + transformWsUrl: function (url, opt, client) { + assert.equal(url, baseUrl) + assert.strictEqual(opt, opts) + assert.strictEqual(client.options, opts) + assert.strictEqual(typeof opt.transformWsUrl, 'function') + assert(client instanceof mqtt.MqttClient) + url += sig + actual = url + return url + } + }) + mqtt.connect(opts) + .on('connect', function () { + assert.equal(this.stream.url, expected) + assert.equal(actual, expected) + this.end(true, (err) => done(err)) }) +}) - const opts = makeOptions({ - protocolId: 'MQIsdp', - protocolVersion: 3 - }) +it('should use mqttv3.1 as the protocol if using v3.1', function (done) { + httpServer.once('client', function (client) { + assert.strictEqual(client.protocol, 'mqttv3.1') + }) - mqtt.connect(opts).on('connect', function () { - this.end(true, (err) => done(err)) - }) + const opts = makeOptions({ + protocolId: 'MQIsdp', + protocolVersion: 3 }) - describe('reconnecting', () => { - it('should reconnect to multiple host-ports-protocol combinations if servers is passed', function (done) { - let serverPort42Connected = false - const handler = function (serverClient) { - serverClient.on('connect', function (packet) { - serverClient.connack({ returnCode: 0 }) - }) - } - this.timeout(15000) - const actualURL41 = 'wss://localhost:9917/' - const actualURL42 = 'ws://localhost:9918/' - const serverPort41 = new MqttServerNoWait(handler).listen(ports.PORTAND41) - const serverPort42 = new MqttServerNoWait(handler).listen(ports.PORTAND42) - - serverPort42.on('listening', function () { - const client = mqtt.connect({ - protocol: 'wss', - servers: [ - { port: ports.PORTAND42, host: 'localhost', protocol: 'ws' }, - { port: ports.PORTAND41, host: 'localhost' } - ], - keepalive: 50 - }) - serverPort41.once('client', function (c) { - assert.equal(client.stream.url, actualURL41, 'Protocol for second client should use the default protocol: wss, on port: port + 41.') - assert(serverPort42Connected) - c.stream.destroy() - client.end(true, (err1) => { - serverPort41.close((err2) => { - done(err1 || err2) - }) + mqtt.connect(opts).on('connect', function () { + this.end(true, (err) => done(err)) + }) +}) + +describe('reconnecting', () => { + it('should reconnect to multiple host-ports-protocol combinations if servers is passed', function (done) { + let serverPort42Connected = false + const handler = function (serverClient) { + serverClient.on('connect', function (packet) { + serverClient.connack({ returnCode: 0 }) + }) + } + this.timeout(15000) + const actualURL41 = 'wss://localhost:9917/' + const actualURL42 = 'ws://localhost:9918/' + const serverPort41 = new MqttServerNoWait(handler).listen(ports.PORTAND41) + const serverPort42 = new MqttServerNoWait(handler).listen(ports.PORTAND42) + + serverPort42.on('listening', function () { + const client = mqtt.connect({ + protocol: 'wss', + servers: [ + { port: ports.PORTAND42, host: 'localhost', protocol: 'ws' }, + { port: ports.PORTAND41, host: 'localhost' } + ], + keepalive: 50 + }) + serverPort41.once('client', function (c) { + assert.equal(client.stream.url, actualURL41, 'Protocol for second client should use the default protocol: wss, on port: port + 41.') + assert(serverPort42Connected) + c.stream.destroy() + client.end(true, (err1) => { + serverPort41.close((err2) => { + done(err1 || err2) }) }) - serverPort42.once('client', function (c) { - serverPort42Connected = true - assert.equal(client.stream.url, actualURL42, 'Protocol for connection should use ws, on port: port + 42.') - c.stream.destroy() - serverPort42.close() - }) + }) + serverPort42.once('client', function (c) { + serverPort42Connected = true + assert.equal(client.stream.url, actualURL42, 'Protocol for connection should use ws, on port: port + 42.') + c.stream.destroy() + serverPort42.close() + }) - client.once('connect', function () { - client.stream.destroy() - }) + client.once('connect', function () { + client.stream.destroy() }) }) }) +}) - abstractClientTests(httpServer, makeOptions()) +abstractClientTests(httpServer, makeOptions()) }) From 19b120b29ed9523d703dc9f74d3f9d3298183bad Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 11 Jul 2023 08:28:58 +0200 Subject: [PATCH 7/8] chore: audit fix --- package-lock.json | 146 +++++++++++++++++++++++----------------------- 1 file changed, 72 insertions(+), 74 deletions(-) diff --git a/package-lock.json b/package-lock.json index 568540f45..4d37e7172 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4621,9 +4621,9 @@ } }, "node_modules/engine.io": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.2.tgz", - "integrity": "sha512-v/7eGHxPvO2AWsksyx2PUsQvBafuvqs0jJJQ0FdmJG1b9qIvgSbqDRGwNhfk2XHaTTbTXiC4quRE8Q9nRjsrQQ==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.1.tgz", + "integrity": "sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==", "dev": true, "dependencies": { "@types/cookie": "^0.4.1", @@ -4634,8 +4634,8 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "ws": "~8.2.3" + "engine.io-parser": "~5.1.0", + "ws": "~8.11.0" }, "engines": { "node": ">=10.0.0" @@ -4679,13 +4679,34 @@ "node": ">=10.0.0" } }, + "node_modules/engine.io/node_modules/engine.io-parser": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz", + "integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/engine.io/node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", "dev": true, "engines": { "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, "node_modules/errno": { @@ -9310,15 +9331,6 @@ "validate-npm-package-license": "^3.0.1" } }, - "node_modules/meow/node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, "node_modules/meow/node_modules/read-pkg/node_modules/type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", @@ -9328,6 +9340,15 @@ "node": ">=8" } }, + "node_modules/meow/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/meow/node_modules/type-fest": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", @@ -12224,9 +12245,9 @@ } }, "node_modules/read-pkg/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -12940,9 +12961,9 @@ "dev": true }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -15097,21 +15118,6 @@ "node": ">= 6" } }, - "node_modules/watchify/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/watchify/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/watchify/node_modules/through2": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", @@ -19352,9 +19358,9 @@ } }, "engine.io": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.1.2.tgz", - "integrity": "sha512-v/7eGHxPvO2AWsksyx2PUsQvBafuvqs0jJJQ0FdmJG1b9qIvgSbqDRGwNhfk2XHaTTbTXiC4quRE8Q9nRjsrQQ==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.1.tgz", + "integrity": "sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==", "dev": true, "requires": { "@types/cookie": "^0.4.1", @@ -19365,15 +19371,22 @@ "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", - "engine.io-parser": "~5.0.0", - "ws": "~8.2.3" + "engine.io-parser": "~5.1.0", + "ws": "~8.11.0" }, "dependencies": { - "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "engine.io-parser": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz", + "integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==", "dev": true + }, + "ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "requires": {} } } }, @@ -22951,12 +22964,6 @@ "validate-npm-package-license": "^3.0.1" } }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, "type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", @@ -22984,6 +22991,12 @@ } } }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + }, "type-fest": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", @@ -25214,9 +25227,9 @@ } }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -25781,9 +25794,9 @@ "dev": true }, "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true }, "semver-diff": { @@ -27521,21 +27534,6 @@ "util-deprecate": "^1.0.1" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "through2": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", From 8dcd727e87b865627afedf9e79c5a4a7b477a155 Mon Sep 17 00:00:00 2001 From: Daniel Lando Date: Tue, 11 Jul 2023 08:32:51 +0200 Subject: [PATCH 8/8] fix: lint and missing `xtend` occurances --- lib/client.js | 154 ++++++++++-------------- lib/connect/index.js | 6 +- lib/default-message-id-provider.js | 17 ++- lib/store.js | 18 +-- lib/topic-alias-recv.js | 11 +- lib/topic-alias-send.js | 18 +-- lib/unique-message-id-provider.js | 17 ++- test/abstract_client.js | 182 ++++++++++++++--------------- test/websocket_client.js | 172 +++++++++++++-------------- 9 files changed, 291 insertions(+), 304 deletions(-) diff --git a/lib/client.js b/lib/client.js index 09d9ce8ca..b39ab63db 100644 --- a/lib/client.js +++ b/lib/client.js @@ -88,11 +88,11 @@ const errors = { 162: 'Wildcard Subscriptions not supported' } -function defaultId() { +function defaultId () { return 'mqttjs_' + Math.random().toString(16).substr(2, 8) } -function applyTopicAlias(client, packet) { +function applyTopicAlias (client, packet) { if (client.options.protocolVersion === 5) { if (packet.cmd === 'publish') { let alias @@ -142,7 +142,7 @@ function applyTopicAlias(client, packet) { } } -function removeTopicAliasAndRecoverTopicName(client, packet) { +function removeTopicAliasAndRecoverTopicName (client, packet) { let alias if (packet.properties) { alias = packet.properties.topicAlias @@ -167,7 +167,7 @@ function removeTopicAliasAndRecoverTopicName(client, packet) { } } -function sendPacket(client, packet, cb) { +function sendPacket (client, packet, cb) { debug('sendPacket :: packet: %O', packet) debug('sendPacket :: emitting `packetsend`') @@ -185,7 +185,7 @@ function sendPacket(client, packet, cb) { } } -function flush(queue) { +function flush (queue) { if (queue) { debug('flush: queue exists? %b', !!(queue)) Object.keys(queue).forEach(function (messageId) { @@ -199,7 +199,7 @@ function flush(queue) { } } -function flushVolatile(queue) { +function flushVolatile (queue) { if (queue) { debug('flushVolatile :: deleting volatile messages from the queue and setting their callbacks as error function') Object.keys(queue).forEach(function (messageId) { @@ -211,7 +211,7 @@ function flushVolatile(queue) { } } -function storeAndSend(client, packet, cb, cbStorePut) { +function storeAndSend (client, packet, cb, cbStorePut) { debug('storeAndSend :: store packet with cmd %s to outgoingStore', packet.cmd) let storePacket = packet let err @@ -225,7 +225,7 @@ function storeAndSend(client, packet, cb, cbStorePut) { return cb && cb(err) } } - client.outgoingStore.put(storePacket, function storedPacket(err) { + client.outgoingStore.put(storePacket, function storedPacket (err) { if (err) { return cb && cb(err) } @@ -234,7 +234,7 @@ function storeAndSend(client, packet, cb, cbStorePut) { }) } -function nop(error) { +function nop (error) { debug('nop ::', error) } @@ -246,8 +246,7 @@ function nop(error) { * (see Connection#connect) */ class MqttClient extends EventEmitter { - - constructor(streamBuilder, options) { + constructor (streamBuilder, options) { super() let k @@ -337,7 +336,7 @@ class MqttClient extends EventEmitter { this.on('connect', function () { const queue = that.queue - function deliver() { + function deliver () { const entry = queue.shift() debug('deliver :: entry %o', entry) let packet = null @@ -406,7 +405,7 @@ class MqttClient extends EventEmitter { * * @api private */ - _setupStream() { + _setupStream () { const that = this const writable = new Writable() const parser = mqttPacket.parser(this.options) @@ -424,7 +423,7 @@ class MqttClient extends EventEmitter { packets.push(packet) }) - function nextTickWork() { + function nextTickWork () { if (packets.length) { nextTick(work) } else { @@ -434,7 +433,7 @@ class MqttClient extends EventEmitter { } } - function work() { + function work () { debug('work :: getting next packet in queue') const packet = packets.shift() @@ -457,7 +456,7 @@ class MqttClient extends EventEmitter { work() } - function streamErrorHandler(error) { + function streamErrorHandler (error) { debug('streamErrorHandler :: error', error.message) if (socketErrors.includes(error.code)) { // handle error @@ -523,8 +522,7 @@ class MqttClient extends EventEmitter { }, this.options.connectTimeout) } - - _handlePacket(packet, done) { + _handlePacket (packet, done) { const options = this.options if (options.protocolVersion === 5 && options.properties && options.properties.maximumPacketSize && options.properties.maximumPacketSize < packet.length) { @@ -574,7 +572,7 @@ class MqttClient extends EventEmitter { } } - _checkDisconnecting(callback) { + _checkDisconnecting (callback) { if (this.disconnecting) { if (callback && callback !== nop) { callback(new Error('client disconnecting')) @@ -605,7 +603,7 @@ class MqttClient extends EventEmitter { * client.publish('topic', 'message', {qos: 1, retain: true, dup: true}); * @example client.publish('topic', 'message', console.log); */ - publish(topic, message, opts, callback) { + publish (topic, message, opts, callback) { debug('publish :: message `%s` to topic `%s`', message, topic) const options = this.options @@ -617,7 +615,7 @@ class MqttClient extends EventEmitter { // default opts const defaultOpts = { qos: 0, retain: false, dup: false } - opts = xtend(defaultOpts, opts) + opts = { ...defaultOpts, ...opts } if (this._checkDisconnecting(callback)) { return this @@ -695,7 +693,7 @@ class MqttClient extends EventEmitter { * @example client.subscribe({'topic': {qos: 0}, 'topic2': {qos: 1}}, console.log); * @example client.subscribe('topic', console.log); */ - subscribe() { + subscribe () { const that = this const args = new Array(arguments.length) for (let i = 0; i < arguments.length; i++) { @@ -738,7 +736,7 @@ class MqttClient extends EventEmitter { defaultOpts.rap = false defaultOpts.rh = 0 } - opts = xtend(defaultOpts, opts) + opts = { ...defaultOpts, ...opts } if (Array.isArray(obj)) { obj.forEach(function (topic) { @@ -871,7 +869,7 @@ class MqttClient extends EventEmitter { * @example client.unsubscribe('topic'); * @example client.unsubscribe('topic', console.log); */ - unsubscribe() { + unsubscribe () { const that = this const args = new Array(arguments.length) for (let i = 0; i < arguments.length; i++) { @@ -960,7 +958,7 @@ class MqttClient extends EventEmitter { * * @api public */ - end(force, opts, cb) { + end (force, opts, cb) { const that = this debug('end :: (%s)', this.options.clientId) @@ -986,7 +984,7 @@ class MqttClient extends EventEmitter { debug('end :: cb? %s', !!cb) cb = cb || nop - function closeStores() { + function closeStores () { debug('end :: closeStores: closing incoming and outgoing stores') that.disconnected = true that.incomingStore.close(function (e1) { @@ -1005,7 +1003,7 @@ class MqttClient extends EventEmitter { } } - function finish() { + function finish () { // defer closesStores of an I/O cycle, // just to make sure things are // ok for websockets @@ -1048,7 +1046,7 @@ class MqttClient extends EventEmitter { * * @example client.removeOutgoingMessage(client.getLastAllocated()); */ - removeOutgoingMessage(messageId) { + removeOutgoingMessage (messageId) { const cb = this.outgoing[messageId] ? this.outgoing[messageId].cb : null delete this.outgoing[messageId] this.outgoingStore.del({ messageId }, function () { @@ -1068,7 +1066,7 @@ class MqttClient extends EventEmitter { * * @api public */ - reconnect(opts) { + reconnect (opts) { debug('client reconnect') const that = this const f = function () { @@ -1099,7 +1097,7 @@ class MqttClient extends EventEmitter { * _reconnect - implement reconnection * @api privateish */ - _reconnect() { + _reconnect () { debug('_reconnect: emitting reconnect to client') this.emit('reconnect') if (this.connected) { @@ -1114,7 +1112,7 @@ class MqttClient extends EventEmitter { /** * _setupReconnect - setup reconnect timer */ - _setupReconnect() { + _setupReconnect () { const that = this if (!that.disconnecting && !that.reconnectTimer && (that.options.reconnectPeriod > 0)) { @@ -1137,7 +1135,7 @@ class MqttClient extends EventEmitter { /** * _clearReconnect - clear the reconnect timer */ - _clearReconnect() { + _clearReconnect () { debug('_clearReconnect : clearing reconnect timer') if (this.reconnectTimer) { clearInterval(this.reconnectTimer) @@ -1149,7 +1147,7 @@ class MqttClient extends EventEmitter { * _cleanUp - clean up on connection end * @api private */ - _cleanUp(forced, done) { + _cleanUp (forced, done) { const opts = arguments[2] if (done) { debug('_cleanUp :: done callback provided for on stream close') @@ -1164,7 +1162,7 @@ class MqttClient extends EventEmitter { debug('_cleanUp :: (%s) :: destroying stream', this.options.clientId) this.stream.destroy() } else { - const packet = xtend({ cmd: 'disconnect' }, opts) + const packet = { cmd: 'disconnect', ...opts } debug('_cleanUp :: (%s) :: call _sendPacket with disconnect packet', this.options.clientId) this._sendPacket( packet, @@ -1202,7 +1200,7 @@ class MqttClient extends EventEmitter { * @param {Boolean} noStore - send without put to the store * @api private */ - _sendPacket(packet, cb, cbStorePut, noStore) { + _sendPacket (packet, cb, cbStorePut, noStore) { debug('_sendPacket :: (%s) :: start', this.options.clientId) cbStorePut = cbStorePut || nop cb = cb || nop @@ -1276,7 +1274,7 @@ class MqttClient extends EventEmitter { * @param {Function} cbStorePut - called when message is put into outgoingStore * @api private */ - _storePacket(packet, cb, cbStorePut) { + _storePacket (packet, cb, cbStorePut) { debug('_storePacket :: packet: %o', packet) debug('_storePacket :: cb? %s', !!cb) cbStorePut = cbStorePut || nop @@ -1313,7 +1311,7 @@ class MqttClient extends EventEmitter { * * @api private */ - _setupPingTimer() { + _setupPingTimer () { debug('_setupPingTimer :: keepalive %d (seconds)', this.options.keepalive) const that = this @@ -1330,7 +1328,7 @@ class MqttClient extends EventEmitter { * * @api private */ - _shiftPingInterval() { + _shiftPingInterval () { if (this.pingTimer && this.options.keepalive && this.options.reschedulePings) { this.pingTimer.reschedule(this.options.keepalive * 1000) } @@ -1341,7 +1339,7 @@ class MqttClient extends EventEmitter { * * @api private */ - _checkPing() { + _checkPing () { debug('_checkPing :: checking ping...') if (this.pingResp) { debug('_checkPing :: ping response received. Clearing flag and sending `pingreq`') @@ -1359,7 +1357,7 @@ class MqttClient extends EventEmitter { * * @api private */ - _handlePingresp() { + _handlePingresp () { this.pingResp = true } @@ -1369,7 +1367,7 @@ class MqttClient extends EventEmitter { * @param {Object} packet * @api private */ - _handleConnack(packet) { + _handleConnack (packet) { debug('_handleConnack') const options = this.options const version = options.protocolVersion @@ -1408,7 +1406,7 @@ class MqttClient extends EventEmitter { } } - _handleAuth(packet) { + _handleAuth (packet) { const options = this.options const version = options.protocolVersion const rc = version === 5 ? packet.reasonCode : packet.returnCode @@ -1443,9 +1441,10 @@ class MqttClient extends EventEmitter { * @return the auth packet to be returned to the broker * @api public */ - handleAuth(packet, callback) { + handleAuth (packet, callback) { callback() } + /** * _handlePublish * @@ -1454,7 +1453,7 @@ class MqttClient extends EventEmitter { */ /* those late 2 case should be rewrite to comply with coding style: - + case 1: case 0: // do not wait sending a puback @@ -1473,10 +1472,10 @@ class MqttClient extends EventEmitter { // 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) { + _handlePublish (packet, done) { debug('_handlePublish: packet %o', packet) done = typeof done !== 'undefined' ? done : nop let topic = packet.topic.toString() @@ -1579,7 +1578,7 @@ class MqttClient extends EventEmitter { * @param Function callback call when finished * @api public */ - handleMessage(packet, callback) { + handleMessage (packet, callback) { callback() } @@ -1589,7 +1588,7 @@ class MqttClient extends EventEmitter { * @param {Object} packet * @api private */ - _handleAck(packet) { + _handleAck (packet) { /* eslint no-fallthrough: "off" */ const messageId = packet.messageId const type = packet.cmd @@ -1693,7 +1692,7 @@ class MqttClient extends EventEmitter { * @param {Object} packet * @api private */ - _handlePubrel(packet, callback) { + _handlePubrel (packet, callback) { debug('handling pubrel packet') callback = typeof callback !== 'undefined' ? callback : nop const messageId = packet.messageId @@ -1723,7 +1722,7 @@ class MqttClient extends EventEmitter { * @param {Object} packet * @api private */ - _handleDisconnect(packet) { + _handleDisconnect (packet) { this.emit('disconnect', packet) } @@ -1731,7 +1730,7 @@ class MqttClient extends EventEmitter { * _nextId * @return unsigned int */ - _nextId() { + _nextId () { return this.messageIdProvider.allocate() } @@ -1739,7 +1738,7 @@ class MqttClient extends EventEmitter { * getLastMessageId * @return unsigned int */ - getLastMessageId() { + getLastMessageId () { return this.messageIdProvider.getLastAllocated() } @@ -1747,7 +1746,7 @@ class MqttClient extends EventEmitter { * _resubscribe * @api private */ - _resubscribe() { + _resubscribe () { debug('_resubscribe') const _resubscribeTopicsKeys = Object.keys(this._resubscribeTopics) if (!this._firstConnection && @@ -1779,7 +1778,7 @@ class MqttClient extends EventEmitter { * * @api private */ - _onConnect(packet) { + _onConnect (packet) { if (this.disconnected) { this.emit('connect', packet) return @@ -1793,10 +1792,10 @@ class MqttClient extends EventEmitter { this.connected = true - function startStreamProcess() { + function startStreamProcess () { let outStore = that.outgoingStore.createStream() - function clearStoreProcessing() { + function clearStoreProcessing () { that._storeProcessing = false that._packetIdsDuringStoreProcessing = {} } @@ -1809,14 +1808,14 @@ class MqttClient extends EventEmitter { that.emit('error', err) }) - function remove() { + function remove () { outStore.destroy() outStore = null that._flushStoreProcessingQueue() clearStoreProcessing() } - function storeDeliver() { + function storeDeliver () { // edge case, we wrapped this twice if (!outStore) { return @@ -1888,7 +1887,7 @@ class MqttClient extends EventEmitter { startStreamProcess() } - _invokeStoreProcessingQueue() { + _invokeStoreProcessingQueue () { if (this._storeProcessingQueue.length > 0) { const f = this._storeProcessingQueue[0] if (f && f.invoke()) { @@ -1899,11 +1898,11 @@ class MqttClient extends EventEmitter { return false } - _invokeAllStoreProcessingQueue() { + _invokeAllStoreProcessingQueue () { while (this._invokeStoreProcessingQueue()) { /* empty */ } } - _flushStoreProcessingQueue() { + _flushStoreProcessingQueue () { for (const f of this._storeProcessingQueue) { if (f.cbStorePut) f.cbStorePut(new Error('Connection closed')) if (f.callback) f.callback(new Error('Connection closed')) @@ -1912,37 +1911,4 @@ class MqttClient extends EventEmitter { } } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - module.exports = MqttClient diff --git a/lib/connect/index.js b/lib/connect/index.js index d44e194d7..cff123633 100644 --- a/lib/connect/index.js +++ b/lib/connect/index.js @@ -32,7 +32,7 @@ protocols.wss = require('./ws') * * @param {Object} [opts] option object */ -function parseAuthOptions(opts) { +function parseAuthOptions (opts) { let matches if (opts.auth) { matches = opts.auth.match(/^(.+):(.+)$/) @@ -51,7 +51,7 @@ function parseAuthOptions(opts) { * @param {String} [brokerUrl] - url of the broker, optional * @param {Object} opts - see MqttClient#constructor */ -function connect(brokerUrl, opts) { +function connect (brokerUrl, opts) { debug('connecting to an MQTT broker...') if ((typeof brokerUrl === 'object') && !opts) { opts = brokerUrl @@ -138,7 +138,7 @@ function connect(brokerUrl, opts) { opts.defaultProtocol = opts.protocol } - function wrapper(client) { + function wrapper (client) { if (opts.servers) { if (!client._reconnectCount || client._reconnectCount === opts.servers.length) { client._reconnectCount = 0 diff --git a/lib/default-message-id-provider.js b/lib/default-message-id-provider.js index 6cc8b3e8b..d7a1da87f 100644 --- a/lib/default-message-id-provider.js +++ b/lib/default-message-id-provider.js @@ -5,20 +5,21 @@ * @constructor */ class DefaultMessageIdProvider { - constructor() { + constructor () { /** * 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)) } + /** * allocate * * Get the next messageId. * @return unsigned int */ - allocate() { + allocate () { // id becomes current state of this.nextId and increments afterwards const id = this.nextId++ // Ensure 16 bit unsigned int (max 65535, nextId got one higher) @@ -27,35 +28,39 @@ class DefaultMessageIdProvider { } return id } + /** * getLastAllocated * Get the last allocated messageId. * @return unsigned int */ - getLastAllocated() { + getLastAllocated () { return (this.nextId === 1) ? 65535 : (this.nextId - 1) } + /** * register * Register messageId. If success return true, otherwise return false. * @param { unsigned int } - messageId to register, * @return boolean */ - register(messageId) { + register (messageId) { return true } + /** * deallocate * Deallocate messageId. * @param { unsigned int } - messageId to deallocate, */ - deallocate(messageId) { + deallocate (messageId) { } + /** * clear * Deallocate all messageIds. */ - clear() { + clear () { } } diff --git a/lib/store.js b/lib/store.js index 8b4f2d2e8..1412967eb 100644 --- a/lib/store.js +++ b/lib/store.js @@ -16,8 +16,7 @@ const defaultStoreOptions = { * @param {Object} [options] - store options */ class Store { - - constructor(options) { + constructor (options) { this.options = options || {} // Defaults @@ -25,12 +24,13 @@ class Store { this._inflights = new Map() } + /** * Adds a packet to the store, a packet is * anything that has a messageId property. * */ - put(packet, cb) { + put (packet, cb) { this._inflights.set(packet.messageId, packet) if (cb) { @@ -39,11 +39,12 @@ class Store { return this } + /** * Creates a stream with all the packets in the store * */ - createStream() { + createStream () { const stream = new Readable(streamsOpts) const values = [] let destroyed = false @@ -77,10 +78,11 @@ class Store { return stream } + /** * deletes a packet from the store. */ - del(packet, cb) { + del (packet, cb) { packet = this._inflights.get(packet.messageId) if (packet) { this._inflights.delete(packet.messageId) @@ -91,10 +93,11 @@ class Store { return this } + /** * get a packet from the store. */ - get(packet, cb) { + get (packet, cb) { packet = this._inflights.get(packet.messageId) if (packet) { cb(null, packet) @@ -104,10 +107,11 @@ class Store { return this } + /** * Close the store */ - close(cb) { + close (cb) { if (this.options.clean) { this._inflights = null } diff --git a/lib/topic-alias-recv.js b/lib/topic-alias-recv.js index 1456758ef..e6412d68d 100644 --- a/lib/topic-alias-recv.js +++ b/lib/topic-alias-recv.js @@ -6,17 +6,18 @@ * @param {Number} [max] - topic alias maximum entries */ class TopicAliasRecv { - constructor(max) { + constructor (max) { this.aliasToTopic = {} this.max = max } + /** * Insert or update topic - alias entry. * @param {String} [topic] - topic * @param {Number} [alias] - topic alias * @returns {Boolean} - if success return true otherwise false */ - put(topic, alias) { + put (topic, alias) { if (alias === 0 || alias > this.max) { return false } @@ -24,18 +25,20 @@ class TopicAliasRecv { this.length = Object.keys(this.aliasToTopic).length return true } + /** * Get topic by alias * @param {String} [topic] - topic * @returns {Number} - if mapped topic exists return topic alias, otherwise return undefined */ - getTopicByAlias(alias) { + getTopicByAlias (alias) { return this.aliasToTopic[alias] } + /** * Clear all entries */ - clear() { + clear () { this.aliasToTopic = {} } } diff --git a/lib/topic-alias-send.js b/lib/topic-alias-send.js index a2cbb6628..f6efcc822 100644 --- a/lib/topic-alias-send.js +++ b/lib/topic-alias-send.js @@ -12,8 +12,7 @@ const NumberAllocator = require('number-allocator').NumberAllocator * @param {Number} [max] - topic alias maximum entries */ class TopicAliasSend { - - constructor(max) { + constructor (max) { if (max > 0) { this.aliasToTopic = new LRUCache({ max }) this.topicToAlias = {} @@ -22,13 +21,14 @@ class TopicAliasSend { this.length = 0 } } + /** * Insert or update topic - alias entry. * @param {String} [topic] - topic * @param {Number} [alias] - topic alias * @returns {Boolean} - if success return true otherwise false */ - put(topic, alias) { + put (topic, alias) { if (alias === 0 || alias > this.max) { return false } @@ -42,40 +42,44 @@ class TopicAliasSend { this.length = this.aliasToTopic.size return true } + /** * Get topic by alias * @param {Number} [alias] - topic alias * @returns {String} - if mapped topic exists return topic, otherwise return undefined */ - getTopicByAlias(alias) { + getTopicByAlias (alias) { return this.aliasToTopic.get(alias) } + /** * Get topic by alias * @param {String} [topic] - topic * @returns {Number} - if mapped topic exists return topic alias, otherwise return undefined */ - getAliasByTopic(topic) { + getAliasByTopic (topic) { const alias = this.topicToAlias[topic] if (typeof alias !== 'undefined') { this.aliasToTopic.get(alias) // LRU update } return alias } + /** * Clear all entries */ - clear() { + clear () { this.aliasToTopic.clear() this.topicToAlias = {} this.numberAllocator.clear() this.length = 0 } + /** * Get Least Recently Used (LRU) topic alias * @returns {Number} - if vacant alias exists then return it, otherwise then return LRU alias */ - getLruAlias() { + getLruAlias () { const alias = this.numberAllocator.firstVacant() if (alias) return alias // get last alias (key) from LRU cache diff --git a/lib/unique-message-id-provider.js b/lib/unique-message-id-provider.js index d16272c55..882bdf7d7 100644 --- a/lib/unique-message-id-provider.js +++ b/lib/unique-message-id-provider.js @@ -7,13 +7,14 @@ const NumberAllocator = require('number-allocator').NumberAllocator * @constructor */ class UniqueMessageIdProvider { - constructor() { + constructor () { if (!(this instanceof UniqueMessageIdProvider)) { return new UniqueMessageIdProvider() } this.numberAllocator = new NumberAllocator(1, 65535) } + /** * allocate * @@ -21,40 +22,44 @@ class UniqueMessageIdProvider { * @return if messageId is fully allocated then return null, * otherwise return the smallest usable unsigned int messageId. */ - allocate() { + allocate () { this.lastId = this.numberAllocator.alloc() return this.lastId } + /** * getLastAllocated * Get the last allocated messageId. * @return unsigned int */ - getLastAllocated() { + getLastAllocated () { return this.lastId } + /** * register * Register messageId. If success return true, otherwise return false. * @param { unsigned int } - messageId to register, * @return boolean */ - register(messageId) { + register (messageId) { return this.numberAllocator.use(messageId) } + /** * deallocate * Deallocate messageId. * @param { unsigned int } - messageId to deallocate, */ - deallocate(messageId) { + deallocate (messageId) { this.numberAllocator.free(messageId) } + /** * clear * Deallocate all messageIds. */ - clear() { + clear () { this.numberAllocator.clear() } } diff --git a/test/abstract_client.js b/test/abstract_client.js index 5fe44c4d2..985651192 100644 --- a/test/abstract_client.js +++ b/test/abstract_client.js @@ -37,7 +37,7 @@ const levelStore = require('mqtt-level-store') module.exports = function (server, config) { const version = config.protocolVersion || 4 - function connect(opts) { + function connect (opts) { opts = { ...config, ...opts } return mqtt.connect(opts) } @@ -825,7 +825,7 @@ module.exports = function (server, config) { server.on('client', onClient) - function onClient(serverClient) { + function onClient (serverClient) { serverClient.once('connect', function () { server.removeListener('client', onClient) }) @@ -851,7 +851,7 @@ module.exports = function (server, config) { server.on('client', onClient) - function onClient(serverClient) { + function onClient (serverClient) { serverClient.once('connect', function () { server.removeListener('client', onClient) }) @@ -1050,7 +1050,7 @@ module.exports = function (server, config) { let countSent = 0 let countReceived = 0 - function publishNext() { + function publishNext () { client.publish('test', 'test', { qos: 2 }, function (err) { assert.ifError(err) countSent++ @@ -1087,7 +1087,7 @@ module.exports = function (server, config) { }) }) - function testQosHandleMessage(qos, done) { + function testQosHandleMessage (qos, done) { const client = connect() let messageEventCount = 0 @@ -1167,34 +1167,34 @@ module.exports = function (server, config) { it('should silently ignore errors thrown by `handleMessage` and return when no callback is passed ' + 'into `handlePublish` method', function (done) { - const client = connect() + const client = connect() - client.handleMessage = function (packet, callback) { - callback(new Error('Error thrown by the application')) - } + client.handleMessage = function (packet, callback) { + callback(new Error('Error thrown by the application')) + } - try { - client._handlePublish({ - messageId: Math.floor(65535 * Math.random()), - topic: 'test', - payload: 'test', - qos: 1 - }) - client.end(true, done) - } catch (err) { - client.end(true, () => { done(err) }) - } - }) + try { + client._handlePublish({ + messageId: Math.floor(65535 * Math.random()), + topic: 'test', + payload: 'test', + qos: 1 + }) + client.end(true, done) + } catch (err) { + client.end(true, () => { done(err) }) + } + }) it('should handle error with async incoming store in QoS 1 `handlePublish` method', function (done) { class AsyncStore { - put(packet, cb) { + put (packet, cb) { process.nextTick(function () { cb(null, 'Error') }) } - close(cb) { + close (cb) { cb() } } @@ -1214,25 +1214,25 @@ module.exports = function (server, config) { it('should handle error with async incoming store in QoS 2 `handlePublish` method', function (done) { class AsyncStore { - put(packet, cb) { + put (packet, cb) { process.nextTick(function () { cb(null, 'Error') }) } - del(packet, cb) { + del (packet, cb) { process.nextTick(function () { cb(new Error('Error')) }) } - get(packet, cb) { + get (packet, cb) { process.nextTick(function () { cb(null, { cmd: 'publish' }) }) } - close(cb) { + close (cb) { cb() } } @@ -1252,25 +1252,25 @@ module.exports = function (server, config) { it('should handle error with async incoming store in QoS 2 `handlePubrel` method', function (done) { class AsyncStore { - put(packet, cb) { + put (packet, cb) { process.nextTick(function () { cb(null, 'Error') }) } - del(packet, cb) { + del (packet, cb) { process.nextTick(function () { cb(new Error('Error')) }) } - get(packet, cb) { + get (packet, cb) { process.nextTick(function () { cb(null, { cmd: 'publish' }) }) } - close(cb) { + close (cb) { cb() } } @@ -1289,26 +1289,26 @@ module.exports = function (server, config) { it('should handle success with async incoming store in QoS 2 `handlePubrel` method', function (done) { let delComplete = false class AsyncStore { - put(packet, cb) { + put (packet, cb) { process.nextTick(function () { cb(null, 'Error') }) } - del(packet, cb) { + del (packet, cb) { process.nextTick(function () { delComplete = true cb(null) }) } - get(packet, cb) { + get (packet, cb) { process.nextTick(function () { cb(null, { cmd: 'publish' }) }) } - close(cb) { + close (cb) { cb() } } @@ -1361,37 +1361,37 @@ module.exports = function (server, config) { it('should silently ignore errors thrown by `handleMessage` and return when no callback is passed ' + 'into `handlePubrel` method', function (done) { - const store = new Store() - const client = connect({ incomingStore: store }) + const store = new Store() + const client = connect({ incomingStore: store }) - const messageId = Math.floor(65535 * Math.random()) - const topic = 'test' - const payload = 'test' - const qos = 2 + const messageId = Math.floor(65535 * Math.random()) + const topic = 'test' + const payload = 'test' + const qos = 2 - client.handleMessage = function (packet, callback) { - callback(new Error('Error thrown by the application')) - } + client.handleMessage = function (packet, callback) { + callback(new Error('Error thrown by the application')) + } - client.once('connect', function () { - client.subscribe(topic, { qos: 2 }) + client.once('connect', function () { + client.subscribe(topic, { qos: 2 }) - store.put({ - messageId, - topic, - payload, - qos, - cmd: 'publish' - }, function () { - try { - client._handlePubrel({ cmd: 'pubrel', messageId }) - client.end(true, done) - } catch (err) { - client.end(true, () => { done(err) }) - } - }) + store.put({ + messageId, + topic, + payload, + qos, + cmd: 'publish' + }, function () { + try { + client._handlePubrel({ cmd: 'pubrel', messageId }) + client.end(true, done) + } catch (err) { + client.end(true, () => { done(err) }) + } }) }) + }) it('should keep message order', function (done) { let publishCount = 0 @@ -1462,7 +1462,7 @@ module.exports = function (server, config) { }) }) - function testCallbackStorePutByQoS(qos, clean, expected, done) { + function testCallbackStorePutByQoS (qos, clean, expected, done) { const client = connect({ clean, clientId: 'testId' @@ -1470,7 +1470,7 @@ module.exports = function (server, config) { const callbacks = [] - function cbStorePut() { + function cbStorePut () { callbacks.push('storeput') } @@ -2352,7 +2352,7 @@ module.exports = function (server, config) { }) }) - function testMultiplePubrel(shouldSendPubcompFail, done) { + function testMultiplePubrel (shouldSendPubcompFail, done) { const client = connect() const testTopic = 'test' const testMessage = 'message' @@ -2390,34 +2390,34 @@ module.exports = function (server, config) { } case 'pubrec': case 'pubcomp': - { - // for both pubrec and pubcomp, reply with pubrel, simulating the server not receiving the pubcomp - if (packet.cmd === 'pubcomp') { - pubcompCount++ - if (pubcompCount === 2) { - // end the test once the client has gone through two rounds of replying to pubrel messages - assert.strictEqual(pubrelCount, 2) - assert.strictEqual(handleMessageCount, 1) - assert.strictEqual(emitMessageCount, 1) - client._sendPacket = origSendPacket - client.end(true, done) - break - } + { + // for both pubrec and pubcomp, reply with pubrel, simulating the server not receiving the pubcomp + if (packet.cmd === 'pubcomp') { + pubcompCount++ + if (pubcompCount === 2) { + // end the test once the client has gone through two rounds of replying to pubrel messages + assert.strictEqual(pubrelCount, 2) + assert.strictEqual(handleMessageCount, 1) + assert.strictEqual(emitMessageCount, 1) + client._sendPacket = origSendPacket + client.end(true, done) + break } - - // simulate the pubrel message, either in response to pubrec or to mock pubcomp failing to be received - const pubrel = { cmd: 'pubrel', messageId: mid } - pubrelCount++ - client._handlePacket(pubrel, function (err) { - if (shouldSendFail) { - assert.exists(err) - assert.instanceOf(err, Error) - } else { - assert.notExists(err) - } - }) - break } + + // simulate the pubrel message, either in response to pubrec or to mock pubcomp failing to be received + const pubrel = { cmd: 'pubrel', messageId: mid } + pubrelCount++ + client._handlePacket(pubrel, function (err) { + if (shouldSendFail) { + assert.exists(err) + assert.instanceOf(err, Error) + } else { + assert.notExists(err) + } + }) + break + } } } @@ -2593,7 +2593,7 @@ module.exports = function (server, config) { check() }) - function check() { + function check () { if (serverPublished && clientCalledBack) { client.end(true, done) } @@ -2653,7 +2653,7 @@ module.exports = function (server, config) { check() }) - function check() { + function check () { if (serverPublished && clientCalledBack) { client.end(true, done) } diff --git a/test/websocket_client.js b/test/websocket_client.js index 76aa868ad..0ff253fb4 100644 --- a/test/websocket_client.js +++ b/test/websocket_client.js @@ -11,7 +11,7 @@ const assert = require('assert') const port = 9999 const httpServer = http.createServer() -function attachWebsocketServer(httpServer) { +function attachWebsocketServer (httpServer) { const webSocketServer = new WebSocket.Server({ server: httpServer, perMessageDeflate: false }) webSocketServer.on('connection', function (ws) { @@ -26,7 +26,7 @@ function attachWebsocketServer(httpServer) { return httpServer } -function attachClientEventHandlers(client) { +function attachClientEventHandlers (client) { client.on('connect', function (packet) { if (packet.clientId === 'invalid') { client.connack({ returnCode: 2 }) @@ -88,106 +88,106 @@ httpServer.on('client', attachClientEventHandlers).listen(port) describe('Websocket Client', function () { const baseConfig = { protocol: 'ws', port } - function makeOptions(custom) { + function makeOptions (custom) { return { ...baseConfig, ...(custom || {}) } } -it('should use mqtt as the protocol by default', function (done) { - httpServer.once('client', function (client) { - assert.strictEqual(client.protocol, 'mqtt') - }) - mqtt.connect(makeOptions()).on('connect', function () { - this.end(true, (err) => done(err)) - }) -}) - -it('should be able to transform the url (for e.g. to sign it)', function (done) { - const baseUrl = 'ws://localhost:9999/mqtt' - const sig = '?AUTH=token' - const expected = baseUrl + sig - let actual - const opts = makeOptions({ - path: '/mqtt', - transformWsUrl: function (url, opt, client) { - assert.equal(url, baseUrl) - assert.strictEqual(opt, opts) - assert.strictEqual(client.options, opts) - assert.strictEqual(typeof opt.transformWsUrl, 'function') - assert(client instanceof mqtt.MqttClient) - url += sig - actual = url - return url - } - }) - mqtt.connect(opts) - .on('connect', function () { - assert.equal(this.stream.url, expected) - assert.equal(actual, expected) + it('should use mqtt as the protocol by default', function (done) { + httpServer.once('client', function (client) { + assert.strictEqual(client.protocol, 'mqtt') + }) + mqtt.connect(makeOptions()).on('connect', function () { this.end(true, (err) => done(err)) }) -}) - -it('should use mqttv3.1 as the protocol if using v3.1', function (done) { - httpServer.once('client', function (client) { - assert.strictEqual(client.protocol, 'mqttv3.1') }) - const opts = makeOptions({ - protocolId: 'MQIsdp', - protocolVersion: 3 + it('should be able to transform the url (for e.g. to sign it)', function (done) { + const baseUrl = 'ws://localhost:9999/mqtt' + const sig = '?AUTH=token' + const expected = baseUrl + sig + let actual + const opts = makeOptions({ + path: '/mqtt', + transformWsUrl: function (url, opt, client) { + assert.equal(url, baseUrl) + assert.strictEqual(opt, opts) + assert.strictEqual(client.options, opts) + assert.strictEqual(typeof opt.transformWsUrl, 'function') + assert(client instanceof mqtt.MqttClient) + url += sig + actual = url + return url + } + }) + mqtt.connect(opts) + .on('connect', function () { + assert.equal(this.stream.url, expected) + assert.equal(actual, expected) + this.end(true, (err) => done(err)) + }) }) - mqtt.connect(opts).on('connect', function () { - this.end(true, (err) => done(err)) + it('should use mqttv3.1 as the protocol if using v3.1', function (done) { + httpServer.once('client', function (client) { + assert.strictEqual(client.protocol, 'mqttv3.1') + }) + + const opts = makeOptions({ + protocolId: 'MQIsdp', + protocolVersion: 3 + }) + + mqtt.connect(opts).on('connect', function () { + this.end(true, (err) => done(err)) + }) }) -}) -describe('reconnecting', () => { - it('should reconnect to multiple host-ports-protocol combinations if servers is passed', function (done) { - let serverPort42Connected = false - const handler = function (serverClient) { - serverClient.on('connect', function (packet) { - serverClient.connack({ returnCode: 0 }) - }) - } - this.timeout(15000) - const actualURL41 = 'wss://localhost:9917/' - const actualURL42 = 'ws://localhost:9918/' - const serverPort41 = new MqttServerNoWait(handler).listen(ports.PORTAND41) - const serverPort42 = new MqttServerNoWait(handler).listen(ports.PORTAND42) - - serverPort42.on('listening', function () { - const client = mqtt.connect({ - protocol: 'wss', - servers: [ - { port: ports.PORTAND42, host: 'localhost', protocol: 'ws' }, - { port: ports.PORTAND41, host: 'localhost' } - ], - keepalive: 50 - }) - serverPort41.once('client', function (c) { - assert.equal(client.stream.url, actualURL41, 'Protocol for second client should use the default protocol: wss, on port: port + 41.') - assert(serverPort42Connected) - c.stream.destroy() - client.end(true, (err1) => { - serverPort41.close((err2) => { - done(err1 || err2) + describe('reconnecting', () => { + it('should reconnect to multiple host-ports-protocol combinations if servers is passed', function (done) { + let serverPort42Connected = false + const handler = function (serverClient) { + serverClient.on('connect', function (packet) { + serverClient.connack({ returnCode: 0 }) + }) + } + this.timeout(15000) + const actualURL41 = 'wss://localhost:9917/' + const actualURL42 = 'ws://localhost:9918/' + const serverPort41 = new MqttServerNoWait(handler).listen(ports.PORTAND41) + const serverPort42 = new MqttServerNoWait(handler).listen(ports.PORTAND42) + + serverPort42.on('listening', function () { + const client = mqtt.connect({ + protocol: 'wss', + servers: [ + { port: ports.PORTAND42, host: 'localhost', protocol: 'ws' }, + { port: ports.PORTAND41, host: 'localhost' } + ], + keepalive: 50 + }) + serverPort41.once('client', function (c) { + assert.equal(client.stream.url, actualURL41, 'Protocol for second client should use the default protocol: wss, on port: port + 41.') + assert(serverPort42Connected) + c.stream.destroy() + client.end(true, (err1) => { + serverPort41.close((err2) => { + done(err1 || err2) + }) }) }) - }) - serverPort42.once('client', function (c) { - serverPort42Connected = true - assert.equal(client.stream.url, actualURL42, 'Protocol for connection should use ws, on port: port + 42.') - c.stream.destroy() - serverPort42.close() - }) + serverPort42.once('client', function (c) { + serverPort42Connected = true + assert.equal(client.stream.url, actualURL42, 'Protocol for connection should use ws, on port: port + 42.') + c.stream.destroy() + serverPort42.close() + }) - client.once('connect', function () { - client.stream.destroy() + client.once('connect', function () { + client.stream.destroy() + }) }) }) }) -}) -abstractClientTests(httpServer, makeOptions()) + abstractClientTests(httpServer, makeOptions()) })