diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 000000000..4e53d5816
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,5 @@
+/types/
+/examples/
+/doc/
+/dist/
+/test/typescript/
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 000000000..51e23bd0c
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,33 @@
+module.exports = {
+ env: {
+ browser: true,
+ commonjs: true,
+ es2021: true,
+ node: true,
+ mocha: true,
+ },
+ extends: ['airbnb-base', 'plugin:prettier/recommended'],
+ parserOptions: {
+ ecmaVersion: 'latest',
+ },
+ rules: {
+ 'global-require': 'off',
+ 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+ 'no-unused-vars': 'off',
+ 'no-underscore-dangle': 'off',
+ 'no-param-reassign': 'off',
+ 'no-restricted-syntax': 'off',
+ camelcase: 'off',
+ 'default-case': 'off',
+ 'consistent-return': 'off',
+ 'import/order': 'off',
+ 'max-classes-per-file': 'off',
+ 'no-plusplus': 'off',
+ 'guard-for-in': 'off',
+ 'no-bitwise': 'off',
+ 'class-methods-use-this': 'off',
+ 'no-continue': 'off',
+ 'prefer-destructuring': 'off',
+ 'no-use-before-define': 'off',
+ },
+}
diff --git a/.github/workflows/mqttjs-test.yml b/.github/workflows/mqttjs-test.yml
index eb1c47d6c..05d67813d 100644
--- a/.github/workflows/mqttjs-test.yml
+++ b/.github/workflows/mqttjs-test.yml
@@ -21,27 +21,33 @@ jobs:
fail-fast: false
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install Dependencies
run: npm ci
+
+ - name: Lint
+ if: matrix.node-version == '20.x'
+ # only run on latest node version, no reason to run on all
+ run: |
+ npm run lint
- name: Test NodeJS
- run: npm run test:node
+ run: npm run test:node # npx mocha test/unique_message_id_provider_client.js --exit
env:
CI: true
- DEBUG: "mqttjs"
+ DEBUG: "${{ runner.debug == '1' && 'mqttjs:*' || '' }}"
- name: Test Typescript
run: npm run test:typescript
env:
CI: true
- DEBUG: "mqttjs"
+ DEBUG: "${{ runner.debug == '1' && 'mqttjs:*' || '' }}"
- name: Test Browser
if: matrix.node-version == '20.x'
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 000000000..a19041c4d
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,6 @@
+*.md
+README.md
+/types/
+/examples/
+/doc/
+/dist/
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 000000000..3d8d73d66
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,7 @@
+module.exports = {
+ semi: false,
+ singleQuote: true,
+ useTabs: true,
+ tabWidth: 4,
+ endOfLine: "lf",
+};
diff --git a/.vscode/settings.json b/.vscode/settings.json
index b788d76e6..820ddab8c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,9 @@
{
- "standard.enable": true,
- "standard.autoFixOnSave": true,
- "editor.defaultFormatter": "standard.vscode-standard"
+ "eslint.format.enable": true,
+ "eslint.lintTask.enable": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": true,
+ "source.fixAll.markdownlint": true
+ },
}
\ No newline at end of file
diff --git a/README.md b/README.md
index 69b9609f8..5c0aac788 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
MQTT.js is a client library for the [MQTT](http://mqtt.org/) protocol, written
in JavaScript for node.js and the browser.
-> MQTT [5.0.0-beta.0](https://github.com/mqttjs/MQTT.js/releases/tag/v5.0.0-beta.0) is now available! Try it out and give us feedback! `npm i mqtt@5.0.0-beta.0`
+> MQTT [5.0.0 BETA](https://www.npmjs.com/package/mqtt/v/beta) is now available! Try it out and give us [feedback](https://github.com/mqttjs/MQTT.js/issues/1639): `npm i mqtt@beta`
## Table of Contents
@@ -101,15 +101,15 @@ For the sake of simplicity, let's put the subscriber and the publisher in the sa
const mqtt = require("mqtt");
const client = mqtt.connect("mqtt://test.mosquitto.org");
-client.on("connect", function () {
- client.subscribe("presence", function (err) {
+client.on("connect", () => {
+ client.subscribe("presence", (err) => {
if (!err) {
client.publish("presence", "Hello mqtt");
}
});
});
-client.on("message", function (topic, message) {
+client.on("message", (topic, message) => {
// message is Buffer
console.log(message.toString());
client.end();
@@ -305,23 +305,24 @@ Also user can manually register topic-alias pair using PUBLISH topic:'some', ta:
## API
-- mqtt.connect()
-- mqtt.Client()
-- mqtt.Client#publish()
-- mqtt.Client#subscribe()
-- mqtt.Client#unsubscribe()
-- mqtt.Client#end()
-- mqtt.Client#removeOutgoingMessage()
-- mqtt.Client#reconnect()
-- mqtt.Client#handleMessage()
-- mqtt.Client#connected
-- mqtt.Client#reconnecting
-- mqtt.Client#getLastMessageId()
-- mqtt.Store()
-- mqtt.Store#put()
-- mqtt.Store#del()
-- mqtt.Store#createStream()
-- mqtt.Store#close()
+- [`mqtt.connect()`](#connect)
+- [`mqtt.Client()`](#client)
+- [`mqtt.Client#connect()`](#client-connect)
+- [`mqtt.Client#publish()`](#publish)
+- [`mqtt.Client#subscribe()`](#subscribe)
+- [`mqtt.Client#unsubscribe()`](#unsubscribe)
+- [`mqtt.Client#end()`](#end)
+- [`mqtt.Client#removeOutgoingMessage()`](#removeOutgoingMessage)
+- [`mqtt.Client#reconnect()`](#reconnect)
+- [`mqtt.Client#handleMessage()`](#handleMessage)
+- [`mqtt.Client#connected`](#connected)
+- [`mqtt.Client#reconnecting`](#reconnecting)
+- [`mqtt.Client#getLastMessageId()`](#getLastMessageId)
+- [`mqtt.Store()`](#store)
+- [`mqtt.Store#put()`](#put)
+- [`mqtt.Store#del()`](#del)
+- [`mqtt.Store#createStream()`](#createStream)
+- [`mqtt.Store#close()`](#close)
---
@@ -368,7 +369,7 @@ The arguments are:
the `connect` event. Typically a `net.Socket`.
- `options` is the client connection options (see: the [connect packet](https://github.com/mcollina/mqtt-packet#connect)). Defaults:
- `wsOptions`: is the WebSocket connection options. Default is `{}`.
- It's specific for WebSockets. For possible options have a look at: https://github.com/websockets/ws/blob/master/doc/ws.md.
+ It's specific for WebSockets. For possible options have a look at: .
- `keepalive`: `60` seconds, set to `0` to disable
- `reschedulePings`: reschedule ping messages after sending packets (default `true`)
- `clientId`: `'mqttjs_' + Math.random().toString(16).substr(2, 8)`
@@ -386,9 +387,11 @@ The arguments are:
- `outgoingStore`: a [Store](#store) for the outgoing packets
- `queueQoSZero`: if connection is broken, queue outgoing QoS zero messages (default `true`)
- `customHandleAcks`: MQTT 5 feature of custom handling puback and pubrec packets. Its callback:
+
```js
customHandleAcks: function(topic, message, packet, done) {/*some logic wit colling done(error, reasonCode)*/}
```
+
- `autoUseTopicAlias`: enabling automatic Topic Alias using functionality
- `autoAssignTopicAlias`: enabling automatic Topic Alias assign functionality
- `properties`: properties MQTT 5.0.
@@ -424,6 +427,7 @@ The arguments are:
subscribed topics are automatically subscribed again (default `true`)
- `messageIdProvider`: custom messageId provider. when `new UniqueMessageIdProvider()` is set, then non conflict messageId is provided.
- `log`: custom log function. Default uses [debug](https://www.npmjs.com/package/debug) package.
+ - `manualConnect`: prevents the constructor to call `connect`. In this case after the `mqtt.connect` is called you should call `client.connect` manually.
In case mqtts (mqtt over tls) is required, the `options` object is
passed through to
@@ -497,7 +501,7 @@ The following TLS errors will be emitted as an `error` event:
`function () {}`
-Emitted when mqtt.Client#end()
is called.
+Emitted when [`mqtt.Client#end()`](#end) is called.
If a callback was passed to `mqtt.Client#end()`, this event is emitted once the
callback returns.
@@ -535,6 +539,12 @@ and connections
---
+
+
+### mqtt.Client#connect()
+
+By default client connects when constructor is called. To prevent this you can set `manualConnect` option to `true` and call `client.connect()` manually.
+
### mqtt.Client#publish(topic, message, [options], [callback])
@@ -746,9 +756,9 @@ Closes the Store.
### Via CDN
-The MQTT.js bundle is available through http://unpkg.com, specifically
-at https://unpkg.com/mqtt/dist/mqtt.min.js.
-See http://unpkg.com for the full documentation on version ranges.
+The MQTT.js bundle is available through , specifically
+at .
+See for the full documentation on version ranges.
diff --git a/benchmarks/bombing.js b/benchmarks/bombing.js
index 4d158c2b4..bdfa36c73 100755
--- a/benchmarks/bombing.js
+++ b/benchmarks/bombing.js
@@ -1,26 +1,32 @@
#! /usr/bin/env node
-const mqtt = require('../')
-const client = mqtt.connect({ port: 1883, host: 'localhost', clean: true, keepalive: 0 })
+const mqtt = require('..')
+
+const client = mqtt.connect({
+ port: 1883,
+ host: 'localhost',
+ clean: true,
+ keepalive: 0,
+})
let sent = 0
const interval = 5000
-function count () {
- console.log('sent/s', sent / interval * 1000)
- sent = 0
+function count() {
+ console.log('sent/s', (sent / interval) * 1000)
+ sent = 0
}
setInterval(count, interval)
-function publish () {
- sent++
- client.publish('test', 'payload', publish)
+function publish() {
+ sent++
+ client.publish('test', 'payload', publish)
}
client.on('connect', publish)
-client.on('error', function () {
- console.log('reconnect!')
- client.stream.end()
+client.on('error', () => {
+ console.log('reconnect!')
+ client.stream.end()
})
diff --git a/benchmarks/throughputCounter.js b/benchmarks/throughputCounter.js
index c2dbabfc5..fab0ed402 100755
--- a/benchmarks/throughputCounter.js
+++ b/benchmarks/throughputCounter.js
@@ -1,22 +1,28 @@
#! /usr/bin/env node
-const mqtt = require('../')
+const mqtt = require('..')
-const client = mqtt.connect({ port: 1883, host: 'localhost', clean: true, encoding: 'binary', keepalive: 0 })
+const client = mqtt.connect({
+ port: 1883,
+ host: 'localhost',
+ clean: true,
+ encoding: 'binary',
+ keepalive: 0,
+})
let counter = 0
const interval = 5000
-function count () {
- console.log('received/s', counter / interval * 1000)
- counter = 0
+function count() {
+ console.log('received/s', (counter / interval) * 1000)
+ counter = 0
}
setInterval(count, interval)
-client.on('connect', function () {
- count()
- this.subscribe('test')
- this.on('message', function () {
- counter++
- })
+client.on('connect', () => {
+ count()
+ this.subscribe('test')
+ this.on('message', () => {
+ counter++
+ })
})
diff --git a/bin/mqtt.js b/bin/mqtt.js
index edba1609d..ee9417288 100755
--- a/bin/mqtt.js
+++ b/bin/mqtt.js
@@ -1,5 +1,4 @@
#!/usr/bin/env node
-'use strict'
/*
* Copyright (c) 2015-2015 MQTT.js contributors.
@@ -10,18 +9,19 @@
const path = require('path')
const commist = require('commist')()
const helpMe = require('help-me')({
- dir: path.join(path.dirname(require.main.filename), '/../doc'),
- ext: '.txt'
+ dir: path.join(path.dirname(require.main.filename), '/../doc'),
+ ext: '.txt',
})
commist.register('publish', require('./pub'))
commist.register('subscribe', require('./sub'))
-commist.register('version', function () {
- console.log('MQTT.js version:', require('./../package.json').version)
+
+commist.register('version', () => {
+ console.log('MQTT.js version:', require('../package.json').version)
})
commist.register('help', helpMe.toStdout)
if (commist.parse(process.argv.slice(2)) !== null) {
- console.log('No such command:', process.argv[2], '\n')
- helpMe.toStdout()
+ console.log('No such command:', process.argv[2], '\n')
+ helpMe.toStdout()
}
diff --git a/bin/pub.js b/bin/pub.js
index 6309bca3e..d9e3ec8bb 100755
--- a/bin/pub.js
+++ b/bin/pub.js
@@ -1,145 +1,159 @@
#!/usr/bin/env node
-'use strict'
-
-const mqtt = require('../')
+const mqtt = require('..')
const { pipeline, Writable } = require('readable-stream')
const path = require('path')
const fs = require('fs')
const concat = require('concat-stream')
const helpMe = require('help-me')({
- dir: path.join(__dirname, '..', 'doc')
+ dir: path.join(__dirname, '..', 'doc'),
})
const minimist = require('minimist')
const split2 = require('split2')
-function send (args) {
- const client = mqtt.connect(args)
- client.on('connect', function () {
- client.publish(args.topic, args.message, args, function (err) {
- if (err) {
- console.warn(err)
- }
- client.end()
- })
- })
- client.on('error', function (err) {
- console.warn(err)
- client.end()
- })
+function send(args) {
+ const client = mqtt.connect(args)
+ client.on('connect', () => {
+ client.publish(args.topic, args.message, args, (err) => {
+ if (err) {
+ console.warn(err)
+ }
+ client.end()
+ })
+ })
+ client.on('error', (err) => {
+ console.warn(err)
+ client.end()
+ })
}
-function multisend (args) {
- const client = mqtt.connect(args)
- const sender = new Writable({
- objectMode: true
- })
- sender._write = function (line, enc, cb) {
- client.publish(args.topic, line.trim(), args, cb)
- }
-
- client.on('connect', function () {
- pipeline(process.stdin, split2(), sender, function (err) {
- client.end()
- if (err) {
- throw err
- }
- })
- })
+function multisend(args) {
+ const client = mqtt.connect(args)
+ const sender = new Writable({
+ objectMode: true,
+ })
+ sender._write = (line, enc, cb) => {
+ client.publish(args.topic, line.trim(), args, cb)
+ }
+
+ client.on('connect', () => {
+ pipeline(process.stdin, split2(), sender, (err) => {
+ client.end()
+ if (err) {
+ throw err
+ }
+ })
+ })
}
-function start (args) {
- args = minimist(args, {
- string: ['hostname', 'username', 'password', 'key', 'cert', 'ca', 'message', 'clientId', 'i', 'id'],
- boolean: ['stdin', 'retain', 'help', 'insecure', 'multiline'],
- alias: {
- port: 'p',
- hostname: ['h', 'host'],
- topic: 't',
- message: 'm',
- qos: 'q',
- clientId: ['i', 'id'],
- retain: 'r',
- username: 'u',
- password: 'P',
- stdin: 's',
- multiline: 'M',
- protocol: ['C', 'l'],
- help: 'H',
- ca: 'cafile'
- },
- default: {
- host: 'localhost',
- qos: 0,
- retain: false,
- topic: '',
- message: ''
- }
- })
-
- if (args.help) {
- return helpMe.toStdout('publish')
- }
-
- if (args.key) {
- args.key = fs.readFileSync(args.key)
- }
-
- if (args.cert) {
- args.cert = fs.readFileSync(args.cert)
- }
-
- if (args.ca) {
- args.ca = fs.readFileSync(args.ca)
- }
-
- if (args.key && args.cert && !args.protocol) {
- args.protocol = 'mqtts'
- }
-
- if (args.port) {
- if (typeof args.port !== 'number') {
- console.warn('# Port: number expected, \'%s\' was given.', typeof args.port)
- return
- }
- }
-
- if (args['will-topic']) {
- args.will = {}
- args.will.topic = args['will-topic']
- args.will.payload = args['will-message']
- args.will.qos = args['will-qos']
- args.will.retain = args['will-retain']
- }
-
- if (args.insecure) {
- args.rejectUnauthorized = false
- }
-
- args.topic = (args.topic || args._.shift()).toString()
- args.message = (args.message || args._.shift()).toString()
-
- if (!args.topic) {
- console.error('missing topic\n')
- return helpMe.toStdout('publish')
- }
-
- if (args.stdin) {
- if (args.multiline) {
- multisend(args)
- } else {
- process.stdin.pipe(concat(function (data) {
- args.message = data
- send(args)
- }))
- }
- } else {
- send(args)
- }
+function start(args) {
+ args = minimist(args, {
+ string: [
+ 'hostname',
+ 'username',
+ 'password',
+ 'key',
+ 'cert',
+ 'ca',
+ 'message',
+ 'clientId',
+ 'i',
+ 'id',
+ ],
+ boolean: ['stdin', 'retain', 'help', 'insecure', 'multiline'],
+ alias: {
+ port: 'p',
+ hostname: ['h', 'host'],
+ topic: 't',
+ message: 'm',
+ qos: 'q',
+ clientId: ['i', 'id'],
+ retain: 'r',
+ username: 'u',
+ password: 'P',
+ stdin: 's',
+ multiline: 'M',
+ protocol: ['C', 'l'],
+ help: 'H',
+ ca: 'cafile',
+ },
+ default: {
+ host: 'localhost',
+ qos: 0,
+ retain: false,
+ topic: '',
+ message: '',
+ },
+ })
+
+ if (args.help) {
+ return helpMe.toStdout('publish')
+ }
+
+ if (args.key) {
+ args.key = fs.readFileSync(args.key)
+ }
+
+ if (args.cert) {
+ args.cert = fs.readFileSync(args.cert)
+ }
+
+ if (args.ca) {
+ args.ca = fs.readFileSync(args.ca)
+ }
+
+ if (args.key && args.cert && !args.protocol) {
+ args.protocol = 'mqtts'
+ }
+
+ if (args.port) {
+ if (typeof args.port !== 'number') {
+ console.warn(
+ "# Port: number expected, '%s' was given.",
+ typeof args.port,
+ )
+ return
+ }
+ }
+
+ if (args['will-topic']) {
+ args.will = {}
+ args.will.topic = args['will-topic']
+ args.will.payload = args['will-message']
+ args.will.qos = args['will-qos']
+ args.will.retain = args['will-retain']
+ }
+
+ if (args.insecure) {
+ args.rejectUnauthorized = false
+ }
+
+ args.topic = (args.topic || args._.shift()).toString()
+ args.message = (args.message || args._.shift()).toString()
+
+ if (!args.topic) {
+ console.error('missing topic\n')
+ return helpMe.toStdout('publish')
+ }
+
+ if (args.stdin) {
+ if (args.multiline) {
+ multisend(args)
+ } else {
+ process.stdin.pipe(
+ concat((data) => {
+ args.message = data
+ send(args)
+ }),
+ )
+ }
+ } else {
+ send(args)
+ }
}
module.exports = start
if (require.main === module) {
- start(process.argv.slice(2))
+ start(process.argv.slice(2))
}
diff --git a/bin/sub.js b/bin/sub.js
index cb113a779..d2e52d5f2 100755
--- a/bin/sub.js
+++ b/bin/sub.js
@@ -1,123 +1,141 @@
#!/usr/bin/env node
-const mqtt = require('../')
+const mqtt = require('..')
const path = require('path')
const fs = require('fs')
const helpMe = require('help-me')({
- dir: path.join(__dirname, '..', 'doc')
+ dir: path.join(__dirname, '..', 'doc'),
})
const minimist = require('minimist')
-function start (args) {
- args = minimist(args, {
- string: ['hostname', 'username', 'password', 'key', 'cert', 'ca', 'clientId', 'i', 'id'],
- boolean: ['stdin', 'help', 'clean', 'insecure'],
- alias: {
- port: 'p',
- hostname: ['h', 'host'],
- topic: 't',
- qos: 'q',
- clean: 'c',
- keepalive: 'k',
- clientId: ['i', 'id'],
- username: 'u',
- password: 'P',
- protocol: ['C', 'l'],
- verbose: 'v',
- help: '-H',
- ca: 'cafile'
- },
- default: {
- host: 'localhost',
- qos: 0,
- retain: false,
- clean: true,
- keepAlive: 30 // 30 sec
- }
- })
-
- if (args.help) {
- return helpMe.toStdout('subscribe')
- }
-
- args.topic = args.topic || args._.shift()
-
- if (!args.topic) {
- console.error('missing topic\n')
- return helpMe.toStdout('subscribe')
- }
-
- if (args.key) {
- args.key = fs.readFileSync(args.key)
- }
-
- if (args.cert) {
- args.cert = fs.readFileSync(args.cert)
- }
-
- if (args.ca) {
- args.ca = fs.readFileSync(args.ca)
- }
-
- if (args.key && args.cert && !args.protocol) {
- args.protocol = 'mqtts'
- }
-
- if (args.insecure) {
- args.rejectUnauthorized = false
- }
-
- if (args.port) {
- if (typeof args.port !== 'number') {
- console.warn('# Port: number expected, \'%s\' was given.', typeof args.port)
- return
- }
- }
-
- if (args['will-topic']) {
- args.will = {}
- args.will.topic = args['will-topic']
- args.will.payload = args['will-message']
- args.will.qos = args['will-qos']
- args.will.retain = args['will-retain']
- }
-
- args.keepAlive = args['keep-alive']
-
- const client = mqtt.connect(args)
-
- client.on('connect', function () {
- client.subscribe(args.topic, { qos: args.qos }, function (err, result) {
- if (err) {
- console.error(err)
- process.exit(1)
- }
-
- result.forEach(function (sub) {
- if (sub.qos > 2) {
- console.error('subscription negated to', sub.topic, 'with code', sub.qos)
- process.exit(1)
- }
- })
- })
- })
-
- client.on('message', function (topic, payload) {
- if (args.verbose) {
- console.log(topic, payload.toString())
- } else {
- console.log(payload.toString())
- }
- })
-
- client.on('error', function (err) {
- console.warn(err)
- client.end()
- })
+function start(args) {
+ args = minimist(args, {
+ string: [
+ 'hostname',
+ 'username',
+ 'password',
+ 'key',
+ 'cert',
+ 'ca',
+ 'clientId',
+ 'i',
+ 'id',
+ ],
+ boolean: ['stdin', 'help', 'clean', 'insecure'],
+ alias: {
+ port: 'p',
+ hostname: ['h', 'host'],
+ topic: 't',
+ qos: 'q',
+ clean: 'c',
+ keepalive: 'k',
+ clientId: ['i', 'id'],
+ username: 'u',
+ password: 'P',
+ protocol: ['C', 'l'],
+ verbose: 'v',
+ help: '-H',
+ ca: 'cafile',
+ },
+ default: {
+ host: 'localhost',
+ qos: 0,
+ retain: false,
+ clean: true,
+ keepAlive: 30, // 30 sec
+ },
+ })
+
+ if (args.help) {
+ return helpMe.toStdout('subscribe')
+ }
+
+ args.topic = args.topic || args._.shift()
+
+ if (!args.topic) {
+ console.error('missing topic\n')
+ return helpMe.toStdout('subscribe')
+ }
+
+ if (args.key) {
+ args.key = fs.readFileSync(args.key)
+ }
+
+ if (args.cert) {
+ args.cert = fs.readFileSync(args.cert)
+ }
+
+ if (args.ca) {
+ args.ca = fs.readFileSync(args.ca)
+ }
+
+ if (args.key && args.cert && !args.protocol) {
+ args.protocol = 'mqtts'
+ }
+
+ if (args.insecure) {
+ args.rejectUnauthorized = false
+ }
+
+ if (args.port) {
+ if (typeof args.port !== 'number') {
+ console.warn(
+ "# Port: number expected, '%s' was given.",
+ typeof args.port,
+ )
+ return
+ }
+ }
+
+ if (args['will-topic']) {
+ args.will = {}
+ args.will.topic = args['will-topic']
+ args.will.payload = args['will-message']
+ args.will.qos = args['will-qos']
+ args.will.retain = args['will-retain']
+ }
+
+ args.keepAlive = args['keep-alive']
+
+ const client = mqtt.connect(args)
+
+ client.on('connect', () => {
+ client.subscribe(args.topic, { qos: args.qos }, (err, result) => {
+ if (err) {
+ console.error(err)
+ process.exit(1)
+ }
+
+ result.forEach((sub) => {
+ if (sub.qos > 2) {
+ console.error(
+ 'subscription negated to',
+ sub.topic,
+ 'with code',
+ sub.qos,
+ )
+ process.exit(1)
+ }
+ })
+ })
+ })
+
+ client.on('message', (topic, payload) => {
+ if (args.verbose) {
+ console.log(topic, payload.toString())
+ } else {
+ console.log(payload.toString())
+ }
+ })
+
+ client.on('error', (err) => {
+ console.warn(err)
+ client.end()
+ })
}
module.exports = start
if (require.main === module) {
- start(process.argv.slice(2))
+ start(process.argv.slice(2))
}
diff --git a/example.js b/example.js
index c5c8568a2..7aa831230 100644
--- a/example.js
+++ b/example.js
@@ -1,11 +1,12 @@
-const mqtt = require('./')
+const mqtt = require('.')
+
const client = mqtt.connect('mqtt://test.mosquitto.org')
client.subscribe('presence')
client.publish('presence', 'Hello mqtt')
-client.on('message', function (topic, message) {
- console.log(message.toString())
+client.on('message', (topic, message) => {
+ console.log(message.toString())
})
client.end()
diff --git a/examples/tls client/mqttclient.js b/examples/tls client/mqttclient.js
index dff75d725..9581eccbe 100644
--- a/examples/tls client/mqttclient.js
+++ b/examples/tls client/mqttclient.js
@@ -39,10 +39,10 @@ const client = mqtt.connect(options)
client.subscribe('messages')
client.publish('messages', 'Current time is: ' + new Date())
-client.on('message', function (topic, message) {
+client.on('message', (topic, message) => {
console.log(message)
})
-client.on('connect', function () {
+client.on('connect', () => {
console.log('Connected')
})
diff --git a/examples/ws/aedes_server.js b/examples/ws/aedes_server.js
index e29032ff4..7ee48a7c0 100644
--- a/examples/ws/aedes_server.js
+++ b/examples/ws/aedes_server.js
@@ -10,19 +10,19 @@ wss.on('connection', function connection (ws) {
aedes.handle(duplex)
})
-httpServer.listen(wsPort, function () {
+httpServer.listen(wsPort, () => {
console.log('websocket server listening on port', wsPort)
})
-aedes.on('clientError', function (client, err) {
+aedes.on('clientError', (client, err) => {
console.log('client error', client.id, err.message, err.stack)
})
-aedes.on('connectionError', function (client, err) {
+aedes.on('connectionError', (client, err) => {
console.log('client error', client, err.message, err.stack)
})
-aedes.on('publish', function (packet, client) {
+aedes.on('publish', (packet, client) => {
if (packet && packet.payload) {
console.log('publish packet:', packet.payload.toString())
}
@@ -31,12 +31,12 @@ aedes.on('publish', function (packet, client) {
}
})
-aedes.on('subscribe', function (subscriptions, client) {
+aedes.on('subscribe', (subscriptions, client) => {
if (client) {
console.log('subscribe from client', subscriptions, client.id)
}
})
-aedes.on('client', function (client) {
+aedes.on('client', (client) => {
console.log('new client', client.id)
})
diff --git a/examples/ws/client.js b/examples/ws/client.js
index cc258677d..d2c4bf53d 100644
--- a/examples/ws/client.js
+++ b/examples/ws/client.js
@@ -33,21 +33,21 @@ const options = {
console.log('connecting mqtt client')
const client = mqtt.connect(host, options)
-client.on('error', function (err) {
+client.on('error', (err) => {
console.log(err)
client.end()
})
-client.on('connect', function () {
+client.on('connect', () => {
console.log('client connected:' + clientId)
client.subscribe('topic', { qos: 0 })
client.publish('topic', 'wss secure connection demo...!', { qos: 0, retain: false })
})
-client.on('message', function (topic, message, packet) {
+client.on('message', (topic, message, packet) => {
console.log('Received Message:= ' + message.toString() + '\nOn topic:= ' + topic)
})
-client.on('close', function () {
+client.on('close', () => {
console.log(clientId + ' disconnected')
})
diff --git a/examples/wss/client_with_proxy.js b/examples/wss/client_with_proxy.js
index 2337f660b..021964bfc 100644
--- a/examples/wss/client_with_proxy.js
+++ b/examples/wss/client_with_proxy.js
@@ -39,22 +39,22 @@ const mqttOptions = {
const client = mqtt.connect(parsed, mqttOptions)
-client.on('connect', function () {
+client.on('connect', () => {
console.log('connected')
})
-client.on('error', function (a) {
+client.on('error', (a) => {
console.log('error!' + a)
})
-client.on('offline', function (a) {
+client.on('offline', (a) => {
console.log('lost connection!' + a)
})
-client.on('close', function (a) {
+client.on('close', (a) => {
console.log('connection closed!' + a)
})
-client.on('message', function (topic, message) {
+client.on('message', (topic, message) => {
console.log(message.toString())
})
diff --git a/lib/client.js b/lib/client.js
index e938a06bc..361b4b9cf 100644
--- a/lib/client.js
+++ b/lib/client.js
@@ -1,47 +1,53 @@
-'use strict'
-
/**
* Module dependencies
*/
-const EventEmitter = require('events').EventEmitter
-const Store = require('./store')
+const { EventEmitter } = require('events')
const TopicAliasRecv = require('./topic-alias-recv')
const mqttPacket = require('mqtt-packet')
const DefaultMessageIdProvider = require('./default-message-id-provider')
-const Writable = require('readable-stream').Writable
+const { Writable } = require('readable-stream')
const reInterval = require('reinterval')
const clone = require('rfdc/default')
const validations = require('./validations')
const debug = require('debug')('mqttjs:client')
+const Store = require('./store')
const handlePacket = require('./handlers')
-const nextTick = process ? process.nextTick : function (callback) { setTimeout(callback, 0) }
-const setImmediate = global.setImmediate || function (...args) {
- const callback = args.shift()
- nextTick(callback.bind(null, ...args))
-}
+const nextTick = process
+ ? process.nextTick
+ : (callback) => {
+ setTimeout(callback, 0)
+ }
+
+const setImmediate =
+ global.setImmediate ||
+ ((...args) => {
+ const callback = args.shift()
+ nextTick(() => {
+ callback(...args)
+ })
+ })
const defaultConnectOptions = {
- keepalive: 60,
- reschedulePings: true,
- protocolId: 'MQTT',
- protocolVersion: 4,
- reconnectPeriod: 1000,
- connectTimeout: 30 * 1000,
- clean: true,
- resubscribe: true,
- writeCache: true
+ keepalive: 60,
+ reschedulePings: true,
+ protocolId: 'MQTT',
+ protocolVersion: 4,
+ reconnectPeriod: 1000,
+ connectTimeout: 30 * 1000,
+ clean: true,
+ resubscribe: true,
+ writeCache: true,
}
const socketErrors = [
- 'ECONNREFUSED',
- 'EADDRINUSE',
- 'ECONNRESET',
- 'ENOTFOUND',
- 'ETIMEDOUT'
+ 'ECONNREFUSED',
+ 'EADDRINUSE',
+ 'ECONNRESET',
+ 'ENOTFOUND',
+ 'ETIMEDOUT',
]
-
/**
* MqttClient constructor
*
@@ -50,1444 +56,1613 @@ const socketErrors = [
* (see Connection#connect)
*/
class MqttClient extends EventEmitter {
-
- static defaultId() {
- return 'mqttjs_' + Math.random().toString(16).substr(2, 8)
- }
-
- constructor(streamBuilder, options) {
- super()
-
- let k
- const that = this
-
- 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]
- }
- }
-
- this.log = this.options.log || debug
- this.nop = this._nop.bind(this)
-
- this.log('MqttClient :: options.protocol', options.protocol)
- this.log('MqttClient :: options.protocolVersion', options.protocolVersion)
- this.log('MqttClient :: options.username', options.username)
- this.log('MqttClient :: options.keepalive', options.keepalive)
- this.log('MqttClient :: options.reconnectPeriod', options.reconnectPeriod)
- this.log('MqttClient :: options.rejectUnauthorized', options.rejectUnauthorized)
- this.log('MqttClient :: options.properties.topicAliasMaximum', options.properties ? options.properties.topicAliasMaximum : undefined)
-
- this.options.clientId = (typeof options.clientId === 'string') ? options.clientId : MqttClient.defaultId()
-
- this.log('MqttClient :: clientId', this.options.clientId)
-
- this.options.customHandleAcks = (options.protocolVersion === 5 && options.customHandleAcks) ? options.customHandleAcks : function () { arguments[3](0) }
-
- // 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.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) {
- this.log('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()
- that.log('deliver :: entry %o', entry)
- let packet = null
-
- if (!entry) {
- that._resubscribe()
- return
- }
-
- packet = entry.packet
- that.log('deliver :: call _sendPacket for %o', packet)
- let send = true
- if (packet.messageId && packet.messageId !== 0) {
- if (!that.messageIdProvider.register(packet.messageId)) {
- send = false
- }
- }
- if (send) {
- that._sendPacket(
- packet,
- function (err) {
- if (entry.cb) {
- entry.cb(err)
- }
- deliver()
- }
- )
- } else {
- that.log('messageId: %d has already used. The message is skipped and removed.', packet.messageId)
- deliver()
- }
- }
-
- that.log('connect :: sending queued packets')
- deliver()
- })
-
- this.on('close', function () {
- that.log('close :: connected set to `false`')
- that.connected = false
-
- that.log('close :: clearing connackTimer')
- clearTimeout(that.connackTimer)
-
- that.log('close :: clearing ping timer')
- if (that.pingTimer !== null) {
- that.pingTimer.clear()
- that.pingTimer = null
- }
-
- if (that.topicAliasRecv) {
- that.topicAliasRecv.clear()
- }
-
- that.log('close :: calling _setupReconnect')
- that._setupReconnect()
- })
-
- this.log('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 = []
-
- this.log('_setupStream :: calling method to clear reconnect')
- this._clearReconnect()
-
- this.log('_setupStream :: using streamBuilder provided to client to create stream')
- this.stream = this.streamBuilder(this)
-
- parser.on('packet', function (packet) {
- that.log('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() {
- that.log('work :: getting next packet in queue')
- const packet = packets.shift()
-
- if (packet) {
- that.log('work :: packet pulled from queue')
- handlePacket(that, packet, nextTickWork)
- } else {
- that.log('work :: no packets in queue')
- const done = completeParse
- completeParse = null
- that.log('work :: done flag is %s', !!(done))
- if (done) done()
- }
- }
-
- writable._write = function (buf, enc, done) {
- completeParse = done
- that.log('writable stream :: parsing buffer')
- parser.parse(buf)
- work()
- }
-
- function streamErrorHandler(error) {
- that.log('streamErrorHandler :: error', error.message)
- if (socketErrors.includes(error.code)) {
- // handle error
- that.log('streamErrorHandler :: emitting error')
- that.emit('error', error)
- } else {
- that.nop(error)
- }
- }
-
- this.log('_setupStream :: pipe stream to writable stream')
- this.stream.pipe(writable)
-
- // Suppress connection errors
- this.stream.on('error', streamErrorHandler)
-
- // Echo stream close
- this.stream.on('close', function () {
- that.log('(%s)stream :: on close', that.options.clientId)
- that._flushVolatile(that.outgoing)
- that.log('stream: emit close to MqttClient')
- that.emit('close')
- })
-
- // Send a connect packet
- this.log('_setupStream: sending packet `connect`')
- const connectPacket = Object.create(this.options)
- connectPacket.cmd = 'connect'
- if (this.topicAliasRecv) {
- if (!connectPacket.properties) {
- connectPacket.properties = {}
- }
- if (this.topicAliasRecv) {
- connectPacket.properties.topicAliasMaximum = this.topicAliasRecv.max
- }
- }
- // avoid message queue
- this._writePacket(connectPacket)
-
- // 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')
- ))
- return this
- }
- if (this.options.properties.authenticationMethod && this.options.authPacket && typeof this.options.authPacket === 'object') {
- const authPacket = { cmd: 'auth', reasonCode: 0, ...this.options.authPacket }
- this._writePacket(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 () {
- that.log('!!connectTimeout hit!! Calling _cleanUp with force `true`')
- that._cleanUp(true)
- }, this.options.connectTimeout)
- }
-
- _flushVolatile(queue) {
- if (queue) {
- this.log('_flushVolatile :: deleting volatile messages from the queue and setting their callbacks as error function')
- Object.keys(queue).forEach(function (messageId) {
- if (queue[messageId].volatile && typeof queue[messageId].cb === 'function') {
- queue[messageId].cb(new Error('Connection closed'))
- delete queue[messageId]
- }
- })
- }
- }
-
- _flush(queue) {
- if (queue) {
- this.log('_flush: queue exists? %b', !!(queue))
- Object.keys(queue).forEach(function (messageId) {
- if (typeof queue[messageId].cb === 'function') {
- queue[messageId].cb(new Error('Connection closed'))
- // This is suspicious. Why do we only delete this if we have a callback?
- // If this is by-design, then adding no as callback would cause this to get deleted unintentionally.
- delete queue[messageId]
- }
- })
- }
- }
-
- _removeTopicAliasAndRecoverTopicName(packet) {
- let alias
- if (packet.properties) {
- alias = packet.properties.topicAlias
- }
-
- let topic = packet.topic.toString()
-
- this.log('_removeTopicAliasAndRecoverTopicName :: alias %d, topic %o', alias, topic)
-
- if (topic.length === 0) {
- // restore topic from alias
- if (typeof alias === 'undefined') {
- return new Error('Unregistered Topic Alias')
- } else {
- topic = this.topicAliasSend.getTopicByAlias(alias)
- if (typeof topic === 'undefined') {
- return new Error('Unregistered Topic Alias')
- } else {
- packet.topic = topic
- }
- }
- }
- if (alias) {
- delete packet.properties.topicAlias
- }
- }
-
- _checkDisconnecting(callback) {
- if (this.disconnecting) {
- if (callback && callback !== this.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) {
- this.log('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 = { ...defaultOpts, ...opts }
-
- 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) {
- that.log('No messageId left')
- return false
- }
- }
- 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
- }
-
- that.log('publish :: qos', opts.qos)
- switch (opts.qos) {
- case 1:
- case 2:
- // Add to callbacks
- that.outgoing[packet.messageId] = {
- volatile: false,
- cb: callback || that.nop
- }
- that.log('MqttClient:publish: packet cmd: %s', packet.cmd)
- that._sendPacket(packet, undefined, opts.cbStorePut)
- break
- default:
- that.log('MqttClient:publish: packet cmd: %s', packet.cmd)
- that._sendPacket(packet, callback, opts.cbStorePut)
- break
- }
- return true
- }
-
- 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() || this.nop
- let opts = args.pop()
- const version = this.options.protocolVersion
-
- delete obj.resubscribe
-
- if (typeof obj === 'string') {
- obj = [obj]
- }
-
- if (typeof callback !== 'function') {
- opts = callback
- callback = this.nop
- }
-
- const invalidTopic = validations.validateTopics(obj)
- if (invalidTopic !== null) {
- setImmediate(callback, new Error('Invalid topic ' + invalidTopic))
- return this
- }
-
- if (this._checkDisconnecting(callback)) {
- this.log('subscribe: discconecting true')
- return this
- }
-
- const defaultOpts = {
- qos: 0
- }
- if (version === 5) {
- defaultOpts.nl = false
- defaultOpts.rap = false
- defaultOpts.rh = 0
- }
- opts = { ...defaultOpts, ...opts }
-
- function parseSub(topic, subOptions) {
- // subOptions is defined only when providing a subs map, use opts otherwise
- subOptions = subOptions || opts
- if (!Object.prototype.hasOwnProperty.call(that._resubscribeTopics, topic) ||
- that._resubscribeTopics[topic].qos < subOptions.qos ||
- resubscribe) {
- const currentOpts = {
- topic,
- qos: subOptions.qos
- }
- if (version === 5) {
- currentOpts.nl = subOptions.nl
- currentOpts.rap = subOptions.rap
- currentOpts.rh = subOptions.rh
- // use opts.properties
- currentOpts.properties = opts.properties
- }
- that.log('subscribe: pushing topic `%s` and qos `%s` to subs list', currentOpts.topic, currentOpts.qos)
- subs.push(currentOpts)
- }
- }
-
- if (Array.isArray(obj)) {
- // array of topics
- obj.forEach(function (topic) {
- that.log('subscribe: array topic %s', topic)
- parseSub(topic)
- })
- } else {
- // object topic --> subOptions (no properties)
- Object
- .keys(obj)
- .forEach(function (topic) {
- that.log('subscribe: object topic %s, %o', topic, obj[topic])
- parseSub(topic, obj[topic])
- })
- }
-
- if (!subs.length) {
- callback(null, [])
- return this
- }
-
- const subscribeProc = function () {
- const messageId = that._nextId()
- if (messageId === null) {
- that.log('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
- }
-
- // subscriptions to resubscribe to in case of disconnect
- if (that.options.resubscribe) {
- that.log('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
- }
-
- 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]
- }
- }
-
- callback(err, subs)
- }
- }
- that.log('subscribe :: call _sendPacket')
- that._sendPacket(packet)
- return true
- }
-
- if (this._storeProcessing || this._storeProcessingQueue.length > 0 || !subscribeProc()) {
- this._storeProcessingQueue.push(
- {
- invoke: subscribeProc,
- callback
- }
- )
- }
-
- return this
- }
-
- /**
- * 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]
- }
- let topic = args.shift()
- let callback = args.pop() || this.nop
- let opts = args.pop()
- if (typeof topic === 'string') {
- topic = [topic]
- }
-
- if (typeof callback !== 'function') {
- opts = callback
- callback = this.nop
- }
-
- const invalidTopic = validations.validateTopics(topic)
- if (invalidTopic !== null) {
- setImmediate(callback, new Error('Invalid topic ' + invalidTopic))
- return this
- }
-
- if (that._checkDisconnecting(callback)) {
- return this
- }
-
- const unsubscribeProc = function () {
- const messageId = that._nextId()
- if (messageId === null) {
- that.log('No messageId left')
- return false
- }
- const packet = {
- cmd: 'unsubscribe',
- qos: 1,
- messageId
- }
-
- if (typeof topic === 'string') {
- packet.unsubscriptions = [topic]
- } else if (Array.isArray(topic)) {
- packet.unsubscriptions = topic
- }
-
- if (that.options.resubscribe) {
- packet.unsubscriptions.forEach(function (topic) {
- delete that._resubscribeTopics[topic]
- })
- }
-
- if (typeof opts === 'object' && opts.properties) {
- packet.properties = opts.properties
- }
-
- that.outgoing[packet.messageId] = {
- volatile: true,
- cb: callback
- }
-
- that.log('unsubscribe: call _sendPacket')
- that._sendPacket(packet)
-
- return true
- }
-
- if (this._storeProcessing || this._storeProcessingQueue.length > 0 || !unsubscribeProc()) {
- this._storeProcessingQueue.push(
- {
- invoke: unsubscribeProc,
- callback
- }
- )
- }
-
- return this
- }
-
- /**
- * end - close connection
- *
- * @returns {MqttClient} this - for chaining
- * @param {Boolean} force - do not wait for all in-flight messages to be acked
- * @param {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
-
- this.log('end :: (%s)', this.options.clientId)
-
- if (force == null || typeof force !== 'boolean') {
- cb = opts || this.nop
- opts = force
- force = false
- if (typeof opts !== 'object') {
- cb = opts
- opts = null
- if (typeof cb !== 'function') {
- cb = this.nop
- }
- }
- }
-
- if (typeof opts !== 'object') {
- cb = opts
- opts = null
- }
-
- this.log('end :: cb? %s', !!cb)
- cb = cb || this.nop
-
- function closeStores() {
- that.log('end :: closeStores: closing incoming and outgoing stores')
- that.disconnected = true
- that.incomingStore.close(function (e1) {
- that.outgoingStore.close(function (e2) {
- that.log('end :: closeStores: emitting end')
- that.emit('end')
- if (cb) {
- const err = e1 || e2
- that.log('end :: closeStores: invoking callback with args')
- cb(err)
- }
- })
- })
- if (that._deferredReconnect) {
- that._deferredReconnect()
- }
- }
-
- function finish() {
- // defer closesStores of an I/O cycle,
- // just to make sure things are
- // ok for websockets
- that.log('end :: (%s) :: finish :: calling _cleanUp with force %s', that.options.clientId, force)
- that._cleanUp(force, () => {
- that.log('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
-
- if (!force && Object.keys(this.outgoing).length > 0) {
- // wait 10ms, just to be sure we received all of it
- this.log('end :: (%s) :: calling finish in 10ms once outgoing is empty', that.options.clientId)
- this.once('outgoingEmpty', setTimeout.bind(null, finish, 10))
- } else {
- this.log('end :: (%s) :: immediately calling finish', that.options.clientId)
- finish()
- }
-
- return this
- }
-
- /**
- * removeOutgoingMessage - remove a message in outgoing store
- * the outgoing callback will be called withe Error('Message removed') if the message is removed
- *
- * @param {Number} messageId - messageId to remove message
- * @returns {MqttClient} this - for chaining
- * @api public
- *
- * @example client.removeOutgoingMessage(client.getLastAllocated());
- */
- removeOutgoingMessage(messageId) {
- if (this.outgoing[messageId]) {
- const cb = this.outgoing[messageId].cb
- this._removeOutgoingAndStoreMessage(messageId, function () {
- cb(new Error('Message removed'))
- })
- }
- return this
- }
-
- /**
- * reconnect - connect again using the same options as connect()
- *
- * @param {Object} [opts] - optional reconnect options, includes:
- * {Store} incomingStore - a store for the incoming packets
- * {Store} outgoingStore - a store for the outgoing packets
- * if opts is not given, current stores are used
- * @returns {MqttClient} this - for chaining
- *
- * @api public
- */
- reconnect(opts) {
- this.log('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()
- }
-
- if (this.disconnecting && !this.disconnected) {
- this._deferredReconnect = f
- } else {
- f()
- }
- return this
- }
-
- /**
- * _reconnect - implement reconnection
- * @api privateish
- */
- _reconnect() {
- this.log('_reconnect: emitting reconnect to client')
- this.emit('reconnect')
- if (this.connected) {
- this.end(() => { this._setupStream() })
- this.log('client already connected. disconnecting first.')
- } else {
- this.log('_reconnect: calling _setupStream')
- this._setupStream()
- }
- }
-
- /**
- * _setupReconnect - setup reconnect timer
- */
- _setupReconnect() {
- const that = this
-
- if (!that.disconnecting && !that.reconnectTimer && (that.options.reconnectPeriod > 0)) {
- if (!this.reconnecting) {
- this.log('_setupReconnect :: emit `offline` state')
- this.emit('offline')
- this.log('_setupReconnect :: set `reconnecting` to `true`')
- this.reconnecting = true
- }
- this.log('_setupReconnect :: setting reconnectTimer for %d ms', that.options.reconnectPeriod)
- that.reconnectTimer = setInterval(function () {
- that.log('reconnectTimer :: reconnect triggered!')
- that._reconnect()
- }, that.options.reconnectPeriod)
- } else {
- this.log('_setupReconnect :: doing nothing...')
- }
- }
-
- /**
- * _clearReconnect - clear the reconnect timer
- */
- _clearReconnect() {
- this.log('_clearReconnect : clearing reconnect timer')
- if (this.reconnectTimer) {
- clearInterval(this.reconnectTimer)
- this.reconnectTimer = null
- }
- }
-
- /**
- * _cleanUp - clean up on connection end
- * @api private
- */
- _cleanUp(forced, done) {
- const opts = arguments[2] || {}
- if (done) {
- this.log('_cleanUp :: done callback provided for on stream close')
- this.stream.on('close', done)
- }
-
- this.log('_cleanUp :: forced? %s', forced)
- if (forced) {
- if ((this.options.reconnectPeriod === 0) && this.options.clean) {
- this._flush(this.outgoing)
- }
- this.log('_cleanUp :: (%s) :: destroying stream', this.options.clientId)
- this.stream.destroy()
- } else {
- const packet = { cmd: 'disconnect', ...opts }
- this.log('_cleanUp :: (%s) :: call _sendPacket with disconnect packet', this.options.clientId)
- this._sendPacket(
- packet,
- () => {
- this.log('_cleanUp :: (%s) :: destroying stream', this.options.clientId)
- setImmediate(
- () => {
- this.stream.end(() => {
- this.log('_cleanUp :: (%s) :: stream destroyed', this.options.clientId)
- // once stream is closed the 'close' event will fire and that will
- // emit client `close` event and call `done` callback if done is provided
- })
- }
- )
- }
- )
- }
-
- if (!this.disconnecting) {
- this.log('_cleanUp :: client not disconnecting. Clearing and resetting reconnect.')
- this._clearReconnect()
- this._setupReconnect()
- }
-
- if (this.pingTimer !== null) {
- this.log('_cleanUp :: clearing pingTimer')
- this.pingTimer.clear()
- this.pingTimer = null
- }
-
- if (done && !this.connected) {
- this.log('_cleanUp :: (%s) :: removing stream `done` callback `close` listener', this.options.clientId)
- this.stream.removeListener('close', done)
- done()
- }
- }
-
- _storeAndSend(packet, cb, cbStorePut) {
- this.log('storeAndSend :: store packet with cmd %s to outgoingStore', packet.cmd)
- let storePacket = packet
- let err
- 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)
- err = this._removeTopicAliasAndRecoverTopicName(storePacket)
- if (err) {
- return cb && cb(err)
- }
- }
- const that = this
- this.outgoingStore.put(storePacket, function storedPacket(err) {
- if (err) {
- return cb && cb(err)
- }
- cbStorePut()
- that._writePacket(packet, cb)
- })
- }
-
- _applyTopicAlias(packet) {
- if (this.options.protocolVersion === 5) {
- if (packet.cmd === 'publish') {
- let alias
- if (packet.properties) {
- alias = packet.properties.topicAlias
- }
- const topic = packet.topic.toString()
- if (this.topicAliasSend) {
- if (alias) {
- if (topic.length !== 0) {
- // register topic alias
- this.log('applyTopicAlias :: register topic: %s - alias: %d', topic, alias)
- if (!this.topicAliasSend.put(topic, alias)) {
- this.log('applyTopicAlias :: error out of range. topic: %s - alias: %d', topic, alias)
- return new Error('Sending Topic Alias out of range')
- }
- }
- } else {
- if (topic.length !== 0) {
- if (this.options.autoAssignTopicAlias) {
- alias = this.topicAliasSend.getAliasByTopic(topic)
- if (alias) {
- packet.topic = ''
- packet.properties = { ...(packet.properties), topicAlias: alias }
- this.log('applyTopicAlias :: auto assign(use) topic: %s - alias: %d', topic, alias)
- } else {
- alias = this.topicAliasSend.getLruAlias()
- this.topicAliasSend.put(topic, alias)
- packet.properties = { ...(packet.properties), topicAlias: alias }
- this.log('applyTopicAlias :: auto assign topic: %s - alias: %d', topic, alias)
- }
- } else if (this.options.autoUseTopicAlias) {
- alias = this.topicAliasSend.getAliasByTopic(topic)
- if (alias) {
- packet.topic = ''
- packet.properties = { ...(packet.properties), topicAlias: alias }
- this.log('applyTopicAlias :: auto use topic: %s - alias: %d', topic, alias)
- }
- }
- }
- }
- } else if (alias) {
- this.log('applyTopicAlias :: error out of range. topic: %s - alias: %d', topic, alias)
- return new Error('Sending Topic Alias out of range')
- }
- }
- }
- }
-
- _nop(err) {
- this.log('nop ::', err)
- }
-
- /** Writes the packet to stream and emits events */
- _writePacket(packet, cb) {
- this.log('_writePacket :: packet: %O', packet)
- this.log('_writePacket :: emitting `packetsend`')
-
- this.emit('packetsend', packet)
-
- // When writing a packet, reschedule the ping timer
- this._shiftPingInterval()
-
- this.log('_writePacket :: writing to stream')
- const result = mqttPacket.writeToStream(packet, this.stream, this.options)
- this.log('_writePacket :: writeToStream result %s', result)
- if (!result && cb && cb !== this.nop) {
- this.log('_writePacket :: handle events on `drain` once through callback.')
- this.stream.once('drain', cb)
- } else if (cb) {
- this.log('_writePacket :: invoking cb')
- cb()
- }
- }
-
- /**
- * _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) {
- this.log('_sendPacket :: (%s) :: start', this.options.clientId)
- cbStorePut = cbStorePut || this.nop
- cb = cb || this.nop
-
- const err = this._applyTopicAlias(packet)
- if (err) {
- cb(err)
- return
- }
-
- if (!this.connected) {
- // allow auth packets to be sent while authenticating with the broker (mqtt5 enhanced auth)
- if (packet.cmd === 'auth') {
- this._writePacket(this, packet, cb)
- return
- }
-
- this.log('_sendPacket :: client not connected. Storing packet offline.')
- this._storePacket(packet, cb, cbStorePut)
- return
- }
-
- // 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) {
- this._writePacket(packet, cb)
- return
- }
-
- switch (packet.cmd) {
- case 'publish':
- break
- case 'pubrel':
- this._storeAndSend(packet, cb, cbStorePut)
- return
- default:
- this._writePacket(packet, cb)
- return
- }
-
- switch (packet.qos) {
- case 2:
- case 1:
- this._storeAndSend(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:
- this._writePacket(packet, cb)
- break
- }
- this.log('_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) {
- this.log('_storePacket :: packet: %o', packet)
- this.log('_storePacket :: cb? %s', !!cb)
- cbStorePut = cbStorePut || this.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 = this._removeTopicAliasAndRecoverTopicName(storePacket)
- if (err) {
- return cb && cb(err)
- }
- }
- // 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
- */
- _setupPingTimer() {
- this.log('_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
- */
- _shiftPingInterval() {
- if (this.pingTimer && this.options.keepalive && this.options.reschedulePings) {
- this.pingTimer.reschedule(this.options.keepalive * 1000)
- }
- }
-
- /**
- * _checkPing - check if a pingresp has come back, and ping the server again
- *
- * @api private
- */
- _checkPing() {
- this.log('_checkPing :: checking ping...')
- if (this.pingResp) {
- this.log('_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
- this.log('_checkPing :: calling _cleanUp with force true')
- this._cleanUp(true)
- }
- }
-
- /**
- * @param packet the packet received by the broker
- * @return the auth packet to be returned to the broker
- * @api public
- */
- handleAuth(packet, callback) {
- callback()
- }
-
- /**
- * 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()
- }
-
- /**
- * _nextId
- * @return unsigned int
- */
- _nextId() {
- return this.messageIdProvider.allocate()
- }
-
- /**
- * getLastMessageId
- * @return unsigned int
- */
- getLastMessageId() {
- return this.messageIdProvider.getLastAllocated()
- }
-
- /**
- * _resubscribe
- * @api private
- */
- _resubscribe() {
- this.log('_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) {
- this.log('_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 = {}
- }
- }
-
- this._firstConnection = false
- }
-
- /**
- * _onConnect
- *
- * @api private
- */
- _onConnect(packet) {
- if (this.disconnected) {
- this.emit('connect', packet)
- return
- }
-
- const that = this
-
- this.connackPacket = packet
- this.messageIdProvider.clear()
- this._setupPingTimer()
-
- this.connected = true
-
- function startStreamProcess() {
- let outStore = that.outgoingStore.createStream()
-
- function clearStoreProcessing() {
- that._storeProcessing = false
- that._packetIdsDuringStoreProcessing = {}
- }
-
- that.once('close', remove)
- outStore.on('error', function (err) {
- clearStoreProcessing()
- that._flushStoreProcessingQueue()
- that.removeListener('close', remove)
- that.emit('error', err)
- })
-
- function remove() {
- outStore.destroy()
- outStore = null
- that._flushStoreProcessingQueue()
- clearStoreProcessing()
- }
-
- function storeDeliver() {
- // edge case, we wrapped this twice
- if (!outStore) {
- return
- }
-
- const packet = outStore.read(1)
-
- let cb
-
- if (!packet) {
- // read when data is available in the future
- outStore.once('readable', storeDeliver)
- return
- }
-
- that._storeProcessing = true
-
- // 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 {
- that.log('messageId: %d has already used.', packet.messageId)
- }
- } else if (outStore.destroy) {
- outStore.destroy()
- }
- }
-
- 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()
- }
- })
- storeDeliver()
- }
- // start flowing
- startStreamProcess()
- }
-
- _invokeStoreProcessingQueue() {
- // If _storeProcessing is true, the message is resending.
- // During resend, processing is skipped to prevent new messages from interrupting. #1635
- if (!this._storeProcessing && this._storeProcessingQueue.length > 0) {
- const f = this._storeProcessingQueue[0]
- if (f && f.invoke()) {
- this._storeProcessingQueue.shift()
- return true
- }
- }
- return false
- }
-
- _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)
- }
-
- /**
- * _removeOutgoingAndStoreMessage
- * @param {Number} messageId - messageId to remove message
- * @param {Function} cb - called when the message removed
- * @api private
- */
- _removeOutgoingAndStoreMessage(messageId, cb) {
- const self = this
- delete this.outgoing[messageId]
- self.outgoingStore.del({ messageId }, function (err, packet) {
- cb(err, packet)
- self.messageIdProvider.deallocate(messageId)
- self._invokeStoreProcessingQueue()
- })
- }
+ static defaultId() {
+ return `mqttjs_${Math.random().toString(16).substr(2, 8)}`
+ }
+
+ constructor(streamBuilder, options) {
+ super()
+
+ let k
+
+ 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]
+ }
+ }
+
+ this.log = this.options.log || debug
+ this.noop = this._noop.bind(this)
+
+ this.log('MqttClient :: options.protocol', options.protocol)
+ this.log(
+ 'MqttClient :: options.protocolVersion',
+ options.protocolVersion,
+ )
+ this.log('MqttClient :: options.username', options.username)
+ this.log('MqttClient :: options.keepalive', options.keepalive)
+ this.log(
+ 'MqttClient :: options.reconnectPeriod',
+ options.reconnectPeriod,
+ )
+ this.log(
+ 'MqttClient :: options.rejectUnauthorized',
+ options.rejectUnauthorized,
+ )
+ this.log(
+ 'MqttClient :: options.properties.topicAliasMaximum',
+ options.properties
+ ? options.properties.topicAliasMaximum
+ : undefined,
+ )
+
+ this.options.clientId =
+ typeof options.clientId === 'string'
+ ? options.clientId
+ : MqttClient.defaultId()
+
+ this.log('MqttClient :: clientId', this.options.clientId)
+
+ this.options.customHandleAcks =
+ options.protocolVersion === 5 && options.customHandleAcks
+ ? options.customHandleAcks
+ : (...args) => {
+ args[3](0)
+ }
+
+ // 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.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) {
+ this.log(
+ 'MqttClient :: options.properties.topicAliasMaximum is out of range',
+ )
+ } else {
+ this.topicAliasRecv = new TopicAliasRecv(
+ options.properties.topicAliasMaximum,
+ )
+ }
+ }
+
+ // Send queued packets
+ this.on('connect', () => {
+ const { queue } = this
+
+ const deliver = () => {
+ const entry = queue.shift()
+ this.log('deliver :: entry %o', entry)
+ let packet = null
+
+ if (!entry) {
+ this._resubscribe()
+ return
+ }
+
+ packet = entry.packet
+ this.log('deliver :: call _sendPacket for %o', packet)
+ let send = true
+ if (packet.messageId && packet.messageId !== 0) {
+ if (!this.messageIdProvider.register(packet.messageId)) {
+ send = false
+ }
+ }
+ if (send) {
+ this._sendPacket(packet, (err) => {
+ if (entry.cb) {
+ entry.cb(err)
+ }
+ deliver()
+ })
+ } else {
+ this.log(
+ 'messageId: %d has already used. The message is skipped and removed.',
+ packet.messageId,
+ )
+ deliver()
+ }
+ }
+
+ this.log('connect :: sending queued packets')
+ deliver()
+ })
+
+ this.on('close', () => {
+ this.log('close :: connected set to `false`')
+ this.connected = false
+
+ this.log('close :: clearing connackTimer')
+ clearTimeout(this.connackTimer)
+
+ this.log('close :: clearing ping timer')
+ if (this.pingTimer !== null) {
+ this.pingTimer.clear()
+ this.pingTimer = null
+ }
+
+ if (this.topicAliasRecv) {
+ this.topicAliasRecv.clear()
+ }
+
+ this.log('close :: calling _setupReconnect')
+ this._setupReconnect()
+ })
+
+ if (!this.options.manualConnect) {
+ this.log('MqttClient :: setting up stream')
+ this.connect()
+ }
+ }
+
+ /**
+ * Setup the event handlers in the inner stream, sends `connect` and `auth` packets
+ */
+ connect() {
+ const writable = new Writable()
+ const parser = mqttPacket.parser(this.options)
+ let completeParse = null
+ const packets = []
+
+ this.log('connect :: calling method to clear reconnect')
+ this._clearReconnect()
+
+ this.log(
+ 'connect :: using streamBuilder provided to client to create stream',
+ )
+ this.stream = this.streamBuilder(this)
+
+ parser.on('packet', (packet) => {
+ this.log('parser :: on packet push to packets array.')
+ packets.push(packet)
+ })
+
+ const work = () => {
+ this.log('work :: getting next packet in queue')
+ const packet = packets.shift()
+
+ if (packet) {
+ this.log('work :: packet pulled from queue')
+ handlePacket(this, packet, nextTickWork)
+ } else {
+ this.log('work :: no packets in queue')
+ const done = completeParse
+ completeParse = null
+ this.log('work :: done flag is %s', !!done)
+ if (done) done()
+ }
+ }
+
+ const nextTickWork = () => {
+ if (packets.length) {
+ nextTick(work)
+ } else {
+ const done = completeParse
+ completeParse = null
+ done()
+ }
+ }
+
+ writable._write = (buf, enc, done) => {
+ completeParse = done
+ this.log('writable stream :: parsing buffer')
+ parser.parse(buf)
+ work()
+ }
+
+ const streamErrorHandler = (error) => {
+ this.log('streamErrorHandler :: error', error.message)
+ if (socketErrors.includes(error.code)) {
+ // handle error
+ this.log('streamErrorHandler :: emitting error')
+ this.emit('error', error)
+ } else {
+ this.noop(error)
+ }
+ }
+
+ this.log('connect :: pipe stream to writable stream')
+ this.stream.pipe(writable)
+
+ // Suppress connection errors
+ this.stream.on('error', streamErrorHandler)
+
+ // Echo stream close
+ this.stream.on('close', () => {
+ this.log('(%s)stream :: on close', this.options.clientId)
+ this._flushVolatile(this.outgoing)
+ this.log('stream: emit close to MqttClient')
+ this.emit('close')
+ })
+
+ // Send a connect packet
+ this.log('connect: sending packet `connect`')
+ const connectPacket = Object.create(this.options)
+ connectPacket.cmd = 'connect'
+ if (this.topicAliasRecv) {
+ if (!connectPacket.properties) {
+ connectPacket.properties = {}
+ }
+ if (this.topicAliasRecv) {
+ connectPacket.properties.topicAliasMaximum =
+ this.topicAliasRecv.max
+ }
+ }
+ // avoid message queue
+ this._writePacket(connectPacket)
+
+ // 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
+ ) {
+ this.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 = {
+ cmd: 'auth',
+ reasonCode: 0,
+ ...this.options.authPacket,
+ }
+ this._writePacket(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(() => {
+ this.log(
+ '!!connectTimeout hit!! Calling _cleanUp with force `true`',
+ )
+ this._cleanUp(true)
+ }, this.options.connectTimeout)
+
+ return this
+ }
+
+ _flushVolatile(queue) {
+ if (queue) {
+ this.log(
+ '_flushVolatile :: deleting volatile messages from the queue and setting their callbacks as error function',
+ )
+ Object.keys(queue).forEach((messageId) => {
+ if (
+ queue[messageId].volatile &&
+ typeof queue[messageId].cb === 'function'
+ ) {
+ queue[messageId].cb(new Error('Connection closed'))
+ delete queue[messageId]
+ }
+ })
+ }
+ }
+
+ _flush(queue) {
+ if (queue) {
+ this.log('_flush: queue exists? %b', !!queue)
+ Object.keys(queue).forEach((messageId) => {
+ if (typeof queue[messageId].cb === 'function') {
+ queue[messageId].cb(new Error('Connection closed'))
+ // This is suspicious. Why do we only delete this if we have a callback?
+ // If this is by-design, then adding no as callback would cause this to get deleted unintentionally.
+ delete queue[messageId]
+ }
+ })
+ }
+ }
+
+ _removeTopicAliasAndRecoverTopicName(packet) {
+ let alias
+ if (packet.properties) {
+ alias = packet.properties.topicAlias
+ }
+
+ let topic = packet.topic.toString()
+
+ this.log(
+ '_removeTopicAliasAndRecoverTopicName :: alias %d, topic %o',
+ alias,
+ topic,
+ )
+
+ if (topic.length === 0) {
+ // restore topic from alias
+ if (typeof alias === 'undefined') {
+ return new Error('Unregistered Topic Alias')
+ }
+ topic = this.topicAliasSend.getTopicByAlias(alias)
+ if (typeof topic === 'undefined') {
+ return new Error('Unregistered Topic Alias')
+ }
+ packet.topic = topic
+ }
+ if (alias) {
+ delete packet.properties.topicAlias
+ }
+ }
+
+ _checkDisconnecting(callback) {
+ if (this.disconnecting) {
+ if (callback && callback !== this.noop) {
+ 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) {
+ this.log('publish :: message `%s` to topic `%s`', message, topic)
+ const { options } = this
+
+ // .publish(topic, payload, cb);
+ if (typeof opts === 'function') {
+ callback = opts
+ opts = null
+ }
+
+ // default opts
+ const defaultOpts = { qos: 0, retain: false, dup: false }
+ opts = { ...defaultOpts, ...opts }
+
+ if (this._checkDisconnecting(callback)) {
+ return this
+ }
+
+ const publishProc = () => {
+ let messageId = 0
+ if (opts.qos === 1 || opts.qos === 2) {
+ messageId = this._nextId()
+ if (messageId === null) {
+ this.log('No messageId left')
+ return false
+ }
+ }
+ 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
+ }
+
+ this.log('publish :: qos', opts.qos)
+ switch (opts.qos) {
+ case 1:
+ case 2:
+ // Add to callbacks
+ this.outgoing[packet.messageId] = {
+ volatile: false,
+ cb: callback || this.noop,
+ }
+ this.log('MqttClient:publish: packet cmd: %s', packet.cmd)
+ this._sendPacket(packet, undefined, opts.cbStorePut)
+ break
+ default:
+ this.log('MqttClient:publish: packet cmd: %s', packet.cmd)
+ this._sendPacket(packet, callback, opts.cbStorePut)
+ break
+ }
+ return true
+ }
+
+ 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(...args) {
+ const subs = []
+ let obj = args.shift()
+ const { resubscribe } = obj
+ let callback = args.pop() || this.noop
+ let opts = args.pop()
+ const version = this.options.protocolVersion
+
+ delete obj.resubscribe
+
+ if (typeof obj === 'string') {
+ obj = [obj]
+ }
+
+ if (typeof callback !== 'function') {
+ opts = callback
+ callback = this.noop
+ }
+
+ const invalidTopic = validations.validateTopics(obj)
+ if (invalidTopic !== null) {
+ setImmediate(callback, new Error(`Invalid topic ${invalidTopic}`))
+ return this
+ }
+
+ if (this._checkDisconnecting(callback)) {
+ this.log('subscribe: discconecting true')
+ return this
+ }
+
+ const defaultOpts = {
+ qos: 0,
+ }
+ if (version === 5) {
+ defaultOpts.nl = false
+ defaultOpts.rap = false
+ defaultOpts.rh = 0
+ }
+ opts = { ...defaultOpts, ...opts }
+
+ const parseSub = (topic, subOptions) => {
+ // subOptions is defined only when providing a subs map, use opts otherwise
+ subOptions = subOptions || opts
+ if (
+ !Object.prototype.hasOwnProperty.call(
+ this._resubscribeTopics,
+ topic,
+ ) ||
+ this._resubscribeTopics[topic].qos < subOptions.qos ||
+ resubscribe
+ ) {
+ const currentOpts = {
+ topic,
+ qos: subOptions.qos,
+ }
+ if (version === 5) {
+ currentOpts.nl = subOptions.nl
+ currentOpts.rap = subOptions.rap
+ currentOpts.rh = subOptions.rh
+ // use opts.properties
+ currentOpts.properties = opts.properties
+ }
+ this.log(
+ 'subscribe: pushing topic `%s` and qos `%s` to subs list',
+ currentOpts.topic,
+ currentOpts.qos,
+ )
+ subs.push(currentOpts)
+ }
+ }
+
+ if (Array.isArray(obj)) {
+ // array of topics
+ obj.forEach((topic) => {
+ this.log('subscribe: array topic %s', topic)
+ parseSub(topic)
+ })
+ } else {
+ // object topic --> subOptions (no properties)
+ Object.keys(obj).forEach((topic) => {
+ this.log('subscribe: object topic %s, %o', topic, obj[topic])
+ parseSub(topic, obj[topic])
+ })
+ }
+
+ if (!subs.length) {
+ callback(null, [])
+ return this
+ }
+
+ const subscribeProc = () => {
+ const messageId = this._nextId()
+ if (messageId === null) {
+ this.log('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
+ }
+
+ // subscriptions to resubscribe to in case of disconnect
+ if (this.options.resubscribe) {
+ this.log('subscribe :: resubscribe true')
+ const topics = []
+ subs.forEach((sub) => {
+ if (this.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
+ }
+ this._resubscribeTopics[sub.topic] = topic
+ topics.push(sub.topic)
+ }
+ })
+ this.messageIdToTopic[packet.messageId] = topics
+ }
+
+ this.outgoing[packet.messageId] = {
+ volatile: true,
+ cb(err, packet2) {
+ if (!err) {
+ const { granted } = packet2
+ for (let i = 0; i < granted.length; i += 1) {
+ subs[i].qos = granted[i]
+ }
+ }
+
+ callback(err, subs)
+ },
+ }
+ this.log('subscribe :: call _sendPacket')
+ this._sendPacket(packet)
+ return true
+ }
+
+ if (
+ this._storeProcessing ||
+ this._storeProcessingQueue.length > 0 ||
+ !subscribeProc()
+ ) {
+ this._storeProcessingQueue.push({
+ invoke: subscribeProc,
+ callback,
+ })
+ }
+
+ return this
+ }
+
+ /**
+ * 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(...args) {
+ let topic = args.shift()
+ let callback = args.pop() || this.noop
+ let opts = args.pop()
+ if (typeof topic === 'string') {
+ topic = [topic]
+ }
+
+ if (typeof callback !== 'function') {
+ opts = callback
+ callback = this.noop
+ }
+
+ const invalidTopic = validations.validateTopics(topic)
+ if (invalidTopic !== null) {
+ setImmediate(callback, new Error(`Invalid topic ${invalidTopic}`))
+ return this
+ }
+
+ if (this._checkDisconnecting(callback)) {
+ return this
+ }
+
+ const unsubscribeProc = () => {
+ const messageId = this._nextId()
+ if (messageId === null) {
+ this.log('No messageId left')
+ return false
+ }
+ const packet = {
+ cmd: 'unsubscribe',
+ qos: 1,
+ messageId,
+ }
+
+ if (typeof topic === 'string') {
+ packet.unsubscriptions = [topic]
+ } else if (Array.isArray(topic)) {
+ packet.unsubscriptions = topic
+ }
+
+ if (this.options.resubscribe) {
+ packet.unsubscriptions.forEach((topic2) => {
+ delete this._resubscribeTopics[topic2]
+ })
+ }
+
+ if (typeof opts === 'object' && opts.properties) {
+ packet.properties = opts.properties
+ }
+
+ this.outgoing[packet.messageId] = {
+ volatile: true,
+ cb: callback,
+ }
+
+ this.log('unsubscribe: call _sendPacket')
+ this._sendPacket(packet)
+
+ return true
+ }
+
+ if (
+ this._storeProcessing ||
+ this._storeProcessingQueue.length > 0 ||
+ !unsubscribeProc()
+ ) {
+ this._storeProcessingQueue.push({
+ invoke: unsubscribeProc,
+ callback,
+ })
+ }
+
+ return this
+ }
+
+ /**
+ * end - close connection
+ *
+ * @returns {MqttClient} this - for chaining
+ * @param {Boolean} force - do not wait for all in-flight messages to be acked
+ * @param {Object} opts - added to the disconnect packet
+ * @param {Function} cb - called when the client has been closed
+ *
+ * @api public
+ */
+ end(force, opts, cb) {
+ this.log('end :: (%s)', this.options.clientId)
+
+ if (force == null || typeof force !== 'boolean') {
+ cb = opts || this.noop
+ opts = force
+ force = false
+ if (typeof opts !== 'object') {
+ cb = opts
+ opts = null
+ if (typeof cb !== 'function') {
+ cb = this.noop
+ }
+ }
+ }
+
+ if (typeof opts !== 'object') {
+ cb = opts
+ opts = null
+ }
+
+ this.log('end :: cb? %s', !!cb)
+ cb = cb || this.noop
+
+ const closeStores = () => {
+ this.log('end :: closeStores: closing incoming and outgoing stores')
+ this.disconnected = true
+ this.incomingStore.close((e1) => {
+ this.outgoingStore.close((e2) => {
+ this.log('end :: closeStores: emitting end')
+ this.emit('end')
+ if (cb) {
+ const err = e1 || e2
+ this.log(
+ 'end :: closeStores: invoking callback with args',
+ )
+ cb(err)
+ }
+ })
+ })
+ if (this._deferredReconnect) {
+ this._deferredReconnect()
+ }
+ }
+
+ const finish = () => {
+ // defer closesStores of an I/O cycle,
+ // just to make sure things are
+ // ok for websockets
+ this.log(
+ 'end :: (%s) :: finish :: calling _cleanUp with force %s',
+ this.options.clientId,
+ force,
+ )
+ this._cleanUp(
+ force,
+ () => {
+ this.log(
+ 'end :: finish :: calling process.nextTick on closeStores',
+ )
+ // const boundProcess = nextTick.bind(null, closeStores)
+ nextTick(closeStores)
+ },
+ opts,
+ )
+ }
+
+ 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
+ this.log(
+ 'end :: (%s) :: calling finish in 10ms once outgoing is empty',
+ this.options.clientId,
+ )
+ this.once('outgoingEmpty', setTimeout.bind(null, finish, 10))
+ } else {
+ this.log(
+ 'end :: (%s) :: immediately calling finish',
+ this.options.clientId,
+ )
+ finish()
+ }
+
+ return this
+ }
+
+ /**
+ * removeOutgoingMessage - remove a message in outgoing store
+ * the outgoing callback will be called withe Error('Message removed') if the message is removed
+ *
+ * @param {Number} messageId - messageId to remove message
+ * @returns {MqttClient} this - for chaining
+ * @api public
+ *
+ * @example client.removeOutgoingMessage(client.getLastAllocated());
+ */
+ removeOutgoingMessage(messageId) {
+ if (this.outgoing[messageId]) {
+ const { cb } = this.outgoing[messageId]
+ this._removeOutgoingAndStoreMessage(messageId, () => {
+ cb(new Error('Message removed'))
+ })
+ }
+ return this
+ }
+
+ /**
+ * reconnect - connect again using the same options as connect()
+ *
+ * @param {Object} [opts] - optional reconnect options, includes:
+ * {Store} incomingStore - a store for the incoming packets
+ * {Store} outgoingStore - a store for the outgoing packets
+ * if opts is not given, current stores are used
+ * @returns {MqttClient} this - for chaining
+ *
+ * @api public
+ */
+ reconnect(opts) {
+ this.log('client reconnect')
+ const f = () => {
+ if (opts) {
+ this.options.incomingStore = opts.incomingStore
+ this.options.outgoingStore = opts.outgoingStore
+ } else {
+ this.options.incomingStore = null
+ this.options.outgoingStore = null
+ }
+ this.incomingStore = this.options.incomingStore || new Store()
+ this.outgoingStore = this.options.outgoingStore || new Store()
+ this.disconnecting = false
+ this.disconnected = false
+ this._deferredReconnect = null
+ this._reconnect()
+ }
+
+ if (this.disconnecting && !this.disconnected) {
+ this._deferredReconnect = f
+ } else {
+ f()
+ }
+ return this
+ }
+
+ /**
+ * _reconnect - implement reconnection
+ * @api privateish
+ */
+ _reconnect() {
+ this.log('_reconnect: emitting reconnect to client')
+ this.emit('reconnect')
+ if (this.connected) {
+ this.end(() => {
+ this.connect()
+ })
+ this.log('client already connected. disconnecting first.')
+ } else {
+ this.log('_reconnect: calling connect')
+ this.connect()
+ }
+ }
+
+ /**
+ * _setupReconnect - setup reconnect timer
+ */
+ _setupReconnect() {
+ if (
+ !this.disconnecting &&
+ !this.reconnectTimer &&
+ this.options.reconnectPeriod > 0
+ ) {
+ if (!this.reconnecting) {
+ this.log('_setupReconnect :: emit `offline` state')
+ this.emit('offline')
+ this.log('_setupReconnect :: set `reconnecting` to `true`')
+ this.reconnecting = true
+ }
+ this.log(
+ '_setupReconnect :: setting reconnectTimer for %d ms',
+ this.options.reconnectPeriod,
+ )
+ this.reconnectTimer = setInterval(() => {
+ this.log('reconnectTimer :: reconnect triggered!')
+ this._reconnect()
+ }, this.options.reconnectPeriod)
+ } else {
+ this.log('_setupReconnect :: doing nothing...')
+ }
+ }
+
+ /**
+ * _clearReconnect - clear the reconnect timer
+ */
+ _clearReconnect() {
+ this.log('_clearReconnect : clearing reconnect timer')
+ if (this.reconnectTimer) {
+ clearInterval(this.reconnectTimer)
+ this.reconnectTimer = null
+ }
+ }
+
+ /**
+ * _cleanUp - clean up on connection end
+ * @api private
+ */
+ _cleanUp(forced, done, opts = {}) {
+ if (done) {
+ this.log('_cleanUp :: done callback provided for on stream close')
+ this.stream.on('close', done)
+ }
+
+ this.log('_cleanUp :: forced? %s', forced)
+ if (forced) {
+ if (this.options.reconnectPeriod === 0 && this.options.clean) {
+ this._flush(this.outgoing)
+ }
+ this.log(
+ '_cleanUp :: (%s) :: destroying stream',
+ this.options.clientId,
+ )
+ this.stream.destroy()
+ } else {
+ const packet = { cmd: 'disconnect', ...opts }
+ this.log(
+ '_cleanUp :: (%s) :: call _sendPacket with disconnect packet',
+ this.options.clientId,
+ )
+ this._sendPacket(packet, () => {
+ this.log(
+ '_cleanUp :: (%s) :: destroying stream',
+ this.options.clientId,
+ )
+ setImmediate(() => {
+ this.stream.end(() => {
+ this.log(
+ '_cleanUp :: (%s) :: stream destroyed',
+ this.options.clientId,
+ )
+ // once stream is closed the 'close' event will fire and that will
+ // emit client `close` event and call `done` callback if done is provided
+ })
+ })
+ })
+ }
+
+ if (!this.disconnecting) {
+ this.log(
+ '_cleanUp :: client not disconnecting. Clearing and resetting reconnect.',
+ )
+ this._clearReconnect()
+ this._setupReconnect()
+ }
+
+ if (this.pingTimer !== null) {
+ this.log('_cleanUp :: clearing pingTimer')
+ this.pingTimer.clear()
+ this.pingTimer = null
+ }
+
+ if (done && !this.connected) {
+ this.log(
+ '_cleanUp :: (%s) :: removing stream `done` callback `close` listener',
+ this.options.clientId,
+ )
+ this.stream.removeListener('close', done)
+ done()
+ }
+ }
+
+ _storeAndSend(packet, cb, cbStorePut) {
+ this.log(
+ 'storeAndSend :: store packet with cmd %s to outgoingStore',
+ packet.cmd,
+ )
+ let storePacket = packet
+ let err
+ 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)
+ err = this._removeTopicAliasAndRecoverTopicName(storePacket)
+ if (err) {
+ return cb && cb(err)
+ }
+ }
+ this.outgoingStore.put(storePacket, (err2) => {
+ if (err2) {
+ return cb && cb(err2)
+ }
+ cbStorePut()
+ this._writePacket(packet, cb)
+ })
+ }
+
+ _applyTopicAlias(packet) {
+ if (this.options.protocolVersion === 5) {
+ if (packet.cmd === 'publish') {
+ let alias
+ if (packet.properties) {
+ alias = packet.properties.topicAlias
+ }
+ const topic = packet.topic.toString()
+ if (this.topicAliasSend) {
+ if (alias) {
+ if (topic.length !== 0) {
+ // register topic alias
+ this.log(
+ 'applyTopicAlias :: register topic: %s - alias: %d',
+ topic,
+ alias,
+ )
+ if (!this.topicAliasSend.put(topic, alias)) {
+ this.log(
+ 'applyTopicAlias :: error out of range. topic: %s - alias: %d',
+ topic,
+ alias,
+ )
+ return new Error(
+ 'Sending Topic Alias out of range',
+ )
+ }
+ }
+ } else if (topic.length !== 0) {
+ if (this.options.autoAssignTopicAlias) {
+ alias = this.topicAliasSend.getAliasByTopic(topic)
+ if (alias) {
+ packet.topic = ''
+ packet.properties = {
+ ...packet.properties,
+ topicAlias: alias,
+ }
+ this.log(
+ 'applyTopicAlias :: auto assign(use) topic: %s - alias: %d',
+ topic,
+ alias,
+ )
+ } else {
+ alias = this.topicAliasSend.getLruAlias()
+ this.topicAliasSend.put(topic, alias)
+ packet.properties = {
+ ...packet.properties,
+ topicAlias: alias,
+ }
+ this.log(
+ 'applyTopicAlias :: auto assign topic: %s - alias: %d',
+ topic,
+ alias,
+ )
+ }
+ } else if (this.options.autoUseTopicAlias) {
+ alias = this.topicAliasSend.getAliasByTopic(topic)
+ if (alias) {
+ packet.topic = ''
+ packet.properties = {
+ ...packet.properties,
+ topicAlias: alias,
+ }
+ this.log(
+ 'applyTopicAlias :: auto use topic: %s - alias: %d',
+ topic,
+ alias,
+ )
+ }
+ }
+ }
+ } else if (alias) {
+ this.log(
+ 'applyTopicAlias :: error out of range. topic: %s - alias: %d',
+ topic,
+ alias,
+ )
+ return new Error('Sending Topic Alias out of range')
+ }
+ }
+ }
+ }
+
+ _noop(err) {
+ this.log('noop ::', err)
+ }
+
+ /** Writes the packet to stream and emits events */
+ _writePacket(packet, cb) {
+ this.log('_writePacket :: packet: %O', packet)
+ this.log('_writePacket :: emitting `packetsend`')
+
+ this.emit('packetsend', packet)
+
+ // When writing a packet, reschedule the ping timer
+ this._shiftPingInterval()
+
+ this.log('_writePacket :: writing to stream')
+ const result = mqttPacket.writeToStream(
+ packet,
+ this.stream,
+ this.options,
+ )
+ this.log('_writePacket :: writeToStream result %s', result)
+ if (!result && cb && cb !== this.noop) {
+ this.log(
+ '_writePacket :: handle events on `drain` once through callback.',
+ )
+ this.stream.once('drain', cb)
+ } else if (cb) {
+ this.log('_writePacket :: invoking cb')
+ cb()
+ }
+ }
+
+ /**
+ * _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) {
+ this.log('_sendPacket :: (%s) :: start', this.options.clientId)
+ cbStorePut = cbStorePut || this.noop
+ cb = cb || this.noop
+
+ const err = this._applyTopicAlias(packet)
+ if (err) {
+ cb(err)
+ return
+ }
+
+ if (!this.connected) {
+ // allow auth packets to be sent while authenticating with the broker (mqtt5 enhanced auth)
+ if (packet.cmd === 'auth') {
+ this._writePacket(this, packet, cb)
+ return
+ }
+
+ this.log(
+ '_sendPacket :: client not connected. Storing packet offline.',
+ )
+ this._storePacket(packet, cb, cbStorePut)
+ return
+ }
+
+ // 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) {
+ this._writePacket(packet, cb)
+ return
+ }
+
+ switch (packet.cmd) {
+ case 'publish':
+ break
+ case 'pubrel':
+ this._storeAndSend(packet, cb, cbStorePut)
+ return
+ default:
+ this._writePacket(packet, cb)
+ return
+ }
+
+ switch (packet.qos) {
+ case 2:
+ case 1:
+ this._storeAndSend(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:
+ this._writePacket(packet, cb)
+ break
+ }
+ this.log('_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) {
+ this.log('_storePacket :: packet: %o', packet)
+ this.log('_storePacket :: cb? %s', !!cb)
+ cbStorePut = cbStorePut || this.noop
+
+ 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 = this._removeTopicAliasAndRecoverTopicName(storePacket)
+ if (err) {
+ return cb && cb(err)
+ }
+ }
+ // 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, (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
+ */
+ _setupPingTimer() {
+ this.log(
+ '_setupPingTimer :: keepalive %d (seconds)',
+ this.options.keepalive,
+ )
+
+ if (!this.pingTimer && this.options.keepalive) {
+ this.pingResp = true
+ this.pingTimer = reInterval(() => {
+ this._checkPing()
+ }, this.options.keepalive * 1000)
+ }
+ }
+
+ /**
+ * _shiftPingInterval - reschedule the ping interval
+ *
+ * @api private
+ */
+ _shiftPingInterval() {
+ if (
+ this.pingTimer &&
+ this.options.keepalive &&
+ this.options.reschedulePings
+ ) {
+ this.pingTimer.reschedule(this.options.keepalive * 1000)
+ }
+ }
+
+ /**
+ * _checkPing - check if a pingresp has come back, and ping the server again
+ *
+ * @api private
+ */
+ _checkPing() {
+ this.log('_checkPing :: checking ping...')
+ if (this.pingResp) {
+ this.log(
+ '_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
+ this.log('_checkPing :: calling _cleanUp with force true')
+ this._cleanUp(true)
+ }
+ }
+
+ /**
+ * @param packet the packet received by the broker
+ * @return the auth packet to be returned to the broker
+ * @api public
+ */
+ handleAuth(packet, callback) {
+ callback()
+ }
+
+ /**
+ * 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()
+ }
+
+ /**
+ * _nextId
+ * @return unsigned int
+ */
+ _nextId() {
+ return this.messageIdProvider.allocate()
+ }
+
+ /**
+ * getLastMessageId
+ * @return unsigned int
+ */
+ getLastMessageId() {
+ return this.messageIdProvider.getLastAllocated()
+ }
+
+ /**
+ * _resubscribe
+ * @api private
+ */
+ _resubscribe() {
+ this.log('_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) {
+ this.log('_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 = {}
+ }
+ }
+
+ this._firstConnection = false
+ }
+
+ /**
+ * _onConnect
+ *
+ * @api private
+ */
+ _onConnect(packet) {
+ if (this.disconnected) {
+ this.emit('connect', packet)
+ return
+ }
+
+ this.connackPacket = packet
+ this.messageIdProvider.clear()
+ this._setupPingTimer()
+
+ this.connected = true
+
+ const startStreamProcess = () => {
+ let outStore = this.outgoingStore.createStream()
+
+ const remove = () => {
+ outStore.destroy()
+ outStore = null
+ this._flushStoreProcessingQueue()
+ clearStoreProcessing()
+ }
+
+ const clearStoreProcessing = () => {
+ this._storeProcessing = false
+ this._packetIdsDuringStoreProcessing = {}
+ }
+
+ this.once('close', remove)
+ outStore.on('error', (err) => {
+ clearStoreProcessing()
+ this._flushStoreProcessingQueue()
+ this.removeListener('close', remove)
+ this.emit('error', err)
+ })
+
+ const storeDeliver = () => {
+ // edge case, we wrapped this twice
+ if (!outStore) {
+ return
+ }
+
+ const packet2 = outStore.read(1)
+
+ let cb
+
+ if (!packet2) {
+ // read when data is available in the future
+ outStore.once('readable', storeDeliver)
+ return
+ }
+
+ this._storeProcessing = true
+
+ // Skip already processed store packets
+ if (this._packetIdsDuringStoreProcessing[packet2.messageId]) {
+ storeDeliver()
+ return
+ }
+
+ // Avoid unnecessary stream read operations when disconnected
+ if (!this.disconnecting && !this.reconnectTimer) {
+ cb = this.outgoing[packet2.messageId]
+ ? this.outgoing[packet2.messageId].cb
+ : null
+ this.outgoing[packet2.messageId] = {
+ volatile: false,
+ cb(err, status) {
+ // Ensure that the original callback passed in to publish gets invoked
+ if (cb) {
+ cb(err, status)
+ }
+
+ storeDeliver()
+ },
+ }
+ this._packetIdsDuringStoreProcessing[
+ packet2.messageId
+ ] = true
+ if (this.messageIdProvider.register(packet2.messageId)) {
+ this._sendPacket(packet2, undefined, undefined, true)
+ } else {
+ this.log(
+ 'messageId: %d has already used.',
+ packet2.messageId,
+ )
+ }
+ } else if (outStore.destroy) {
+ outStore.destroy()
+ }
+ }
+
+ outStore.on('end', () => {
+ let allProcessed = true
+ for (const id in this._packetIdsDuringStoreProcessing) {
+ if (!this._packetIdsDuringStoreProcessing[id]) {
+ allProcessed = false
+ break
+ }
+ }
+ if (allProcessed) {
+ clearStoreProcessing()
+ this.removeListener('close', remove)
+ this._invokeAllStoreProcessingQueue()
+ this.emit('connect', packet)
+ } else {
+ startStreamProcess()
+ }
+ })
+ storeDeliver()
+ }
+ // start flowing
+ startStreamProcess()
+ }
+
+ _invokeStoreProcessingQueue() {
+ // If _storeProcessing is true, the message is resending.
+ // During resend, processing is skipped to prevent new messages from interrupting. #1635
+ if (!this._storeProcessing && this._storeProcessingQueue.length > 0) {
+ const f = this._storeProcessingQueue[0]
+ if (f && f.invoke()) {
+ this._storeProcessingQueue.shift()
+ return true
+ }
+ }
+ return false
+ }
+
+ _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)
+ }
+
+ /**
+ * _removeOutgoingAndStoreMessage
+ * @param {Number} messageId - messageId to remove message
+ * @param {Function} cb - called when the message removed
+ * @api private
+ */
+ _removeOutgoingAndStoreMessage(messageId, cb) {
+ const self = this
+ delete this.outgoing[messageId]
+ self.outgoingStore.del({ messageId }, (err, packet) => {
+ cb(err, packet)
+ self.messageIdProvider.deallocate(messageId)
+ self._invokeStoreProcessingQueue()
+ })
+ }
}
module.exports = MqttClient
diff --git a/lib/connect/ali.js b/lib/connect/ali.js
index 7001e7712..fb6dc3483 100644
--- a/lib/connect/ali.js
+++ b/lib/connect/ali.js
@@ -1,129 +1,126 @@
-'use strict'
-
const { Buffer } = require('buffer')
-const Transform = require('readable-stream').Transform
+const { Transform } = require('readable-stream')
const duplexify = require('duplexify')
-/* global FileReader */
let my
let proxy
let stream
let isInitialized = false
-function buildProxy () {
- const proxy = new Transform()
- proxy._write = function (chunk, encoding, next) {
- my.sendSocketMessage({
- data: chunk.buffer,
- success: function () {
- next()
- },
- fail: function () {
- next(new Error())
- }
- })
- }
- proxy._flush = function socketEnd (done) {
- my.closeSocket({
- success: function () {
- done()
- }
- })
- }
-
- return proxy
+function buildProxy() {
+ const _proxy = new Transform()
+ _proxy._write = (chunk, encoding, next) => {
+ my.sendSocketMessage({
+ data: chunk.buffer,
+ success() {
+ next()
+ },
+ fail() {
+ next(new Error())
+ },
+ })
+ }
+ _proxy._flush = (done) => {
+ my.closeSocket({
+ success() {
+ done()
+ },
+ })
+ }
+
+ return _proxy
}
-function setDefaultOpts (opts) {
- if (!opts.hostname) {
- opts.hostname = 'localhost'
- }
- if (!opts.path) {
- opts.path = '/'
- }
-
- if (!opts.wsOptions) {
- opts.wsOptions = {}
- }
+function setDefaultOpts(opts) {
+ if (!opts.hostname) {
+ opts.hostname = 'localhost'
+ }
+ if (!opts.path) {
+ opts.path = '/'
+ }
+
+ if (!opts.wsOptions) {
+ opts.wsOptions = {}
+ }
}
-function buildUrl (opts, client) {
- const protocol = opts.protocol === 'alis' ? 'wss' : 'ws'
- let url = protocol + '://' + opts.hostname + opts.path
- if (opts.port && opts.port !== 80 && opts.port !== 443) {
- url = protocol + '://' + opts.hostname + ':' + opts.port + opts.path
- }
- if (typeof (opts.transformWsUrl) === 'function') {
- url = opts.transformWsUrl(url, opts, client)
- }
- return url
+function buildUrl(opts, client) {
+ const protocol = opts.protocol === 'alis' ? 'wss' : 'ws'
+ let url = `${protocol}://${opts.hostname}${opts.path}`
+ if (opts.port && opts.port !== 80 && opts.port !== 443) {
+ url = `${protocol}://${opts.hostname}:${opts.port}${opts.path}`
+ }
+ if (typeof opts.transformWsUrl === 'function') {
+ url = opts.transformWsUrl(url, opts, client)
+ }
+ return url
}
-function bindEventHandler () {
- if (isInitialized) return
-
- isInitialized = true
-
- my.onSocketOpen(function () {
- stream.setReadable(proxy)
- stream.setWritable(proxy)
- stream.emit('connect')
- })
-
- my.onSocketMessage(function (res) {
- if (typeof res.data === 'string') {
- const buffer = Buffer.from(res.data, 'base64')
- proxy.push(buffer)
- } else {
- const reader = new FileReader()
- reader.addEventListener('load', function () {
- let data = reader.result
-
- if (data instanceof ArrayBuffer) data = Buffer.from(data)
- else data = Buffer.from(data, 'utf8')
- proxy.push(data)
- })
- reader.readAsArrayBuffer(res.data)
- }
- })
-
- my.onSocketClose(function () {
- stream.end()
- stream.destroy()
- })
-
- my.onSocketError(function (res) {
- stream.destroy(res)
- })
+function bindEventHandler() {
+ if (isInitialized) return
+
+ isInitialized = true
+
+ my.onSocketOpen(() => {
+ stream.setReadable(proxy)
+ stream.setWritable(proxy)
+ stream.emit('connect')
+ })
+
+ my.onSocketMessage((res) => {
+ if (typeof res.data === 'string') {
+ const buffer = Buffer.from(res.data, 'base64')
+ proxy.push(buffer)
+ } else {
+ const reader = new FileReader()
+ reader.addEventListener('load', () => {
+ let data = reader.result
+
+ if (data instanceof ArrayBuffer) data = Buffer.from(data)
+ else data = Buffer.from(data, 'utf8')
+ proxy.push(data)
+ })
+ reader.readAsArrayBuffer(res.data)
+ }
+ })
+
+ my.onSocketClose(() => {
+ stream.end()
+ stream.destroy()
+ })
+
+ my.onSocketError((res) => {
+ stream.destroy(res)
+ })
}
-function buildStream (client, opts) {
- opts.hostname = opts.hostname || opts.host
+function buildStream(client, opts) {
+ opts.hostname = opts.hostname || opts.host
- if (!opts.hostname) {
- throw new Error('Could not determine host. Specify host manually.')
- }
+ if (!opts.hostname) {
+ throw new Error('Could not determine host. Specify host manually.')
+ }
- const websocketSubProtocol =
- (opts.protocolId === 'MQIsdp') && (opts.protocolVersion === 3)
- ? 'mqttv3.1'
- : 'mqtt'
+ const websocketSubProtocol =
+ opts.protocolId === 'MQIsdp' && opts.protocolVersion === 3
+ ? 'mqttv3.1'
+ : 'mqtt'
- setDefaultOpts(opts)
+ setDefaultOpts(opts)
- const url = buildUrl(opts, client)
- my = opts.my
- my.connectSocket({
- url,
- protocols: websocketSubProtocol
- })
+ const url = buildUrl(opts, client)
+ my = opts.my
+ my.connectSocket({
+ url,
+ protocols: websocketSubProtocol,
+ })
- proxy = buildProxy()
- stream = duplexify.obj()
+ proxy = buildProxy()
+ stream = duplexify.obj()
- bindEventHandler()
+ bindEventHandler()
- return stream
+ return stream
}
module.exports = buildStream
diff --git a/lib/connect/index.js b/lib/connect/index.js
index cff123633..e50b31122 100644
--- a/lib/connect/index.js
+++ b/lib/connect/index.js
@@ -1,27 +1,25 @@
-'use strict'
-
+const url = require('url')
const MqttClient = require('../client')
const Store = require('../store')
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 debug = require('debug')('mqttjs')
const protocols = {}
if (!IS_BROWSER) {
- protocols.mqtt = require('./tcp')
- protocols.tcp = require('./tcp')
- protocols.ssl = require('./tls')
- protocols.tls = require('./tls')
- protocols.mqtts = require('./tls')
+ protocols.mqtt = require('./tcp')
+ protocols.tcp = require('./tcp')
+ protocols.ssl = require('./tls')
+ protocols.tls = require('./tls')
+ protocols.mqtts = require('./tls')
} else {
- protocols.wx = require('./wx')
- protocols.wxs = require('./wx')
+ protocols.wx = require('./wx')
+ protocols.wxs = require('./wx')
- protocols.ali = require('./ali')
- protocols.alis = require('./ali')
+ protocols.ali = require('./ali')
+ protocols.alis = require('./ali')
}
protocols.ws = require('./ws')
@@ -32,17 +30,17 @@ protocols.wss = require('./ws')
*
* @param {Object} [opts] option object
*/
-function parseAuthOptions (opts) {
- let matches
- if (opts.auth) {
- matches = opts.auth.match(/^(.+):(.+)$/)
- if (matches) {
- opts.username = matches[1]
- opts.password = matches[2]
- } else {
- opts.username = opts.auth
- }
- }
+function parseAuthOptions(opts) {
+ let matches
+ if (opts.auth) {
+ matches = opts.auth.match(/^(.+):(.+)$/)
+ if (matches) {
+ opts.username = matches[1]
+ opts.password = matches[2]
+ } else {
+ opts.username = opts.auth
+ }
+ }
}
/**
@@ -51,113 +49,122 @@ function parseAuthOptions (opts) {
* @param {String} [brokerUrl] - url of the broker, optional
* @param {Object} opts - see MqttClient#constructor
*/
-function connect (brokerUrl, opts) {
- debug('connecting to an MQTT broker...')
- if ((typeof brokerUrl === 'object') && !opts) {
- opts = brokerUrl
- brokerUrl = null
- }
+function connect(brokerUrl, opts) {
+ debug('connecting to an MQTT broker...')
+ if (typeof brokerUrl === 'object' && !opts) {
+ opts = brokerUrl
+ brokerUrl = null
+ }
- opts = opts || {}
+ opts = opts || {}
- if (brokerUrl) {
- // eslint-disable-next-line
+ if (brokerUrl) {
+ // eslint-disable-next-line
const parsed = url.parse(brokerUrl, true)
- if (parsed.port != null) {
- parsed.port = Number(parsed.port)
- }
-
- opts = { ...parsed, ...opts }
-
- if (opts.protocol === null) {
- throw new Error('Missing protocol')
- }
-
- opts.protocol = opts.protocol.replace(/:$/, '')
- }
-
- // merge in the auth options if supplied
- parseAuthOptions(opts)
-
- // support clientId passed in the query string of the url
- if (opts.query && typeof opts.query.clientId === 'string') {
- opts.clientId = opts.query.clientId
- }
-
- if (opts.cert && opts.key) {
- if (opts.protocol) {
- if (['mqtts', 'wss', 'wxs', 'alis'].indexOf(opts.protocol) === -1) {
- switch (opts.protocol) {
- case 'mqtt':
- opts.protocol = 'mqtts'
- break
- case 'ws':
- opts.protocol = 'wss'
- break
- case 'wx':
- opts.protocol = 'wxs'
- break
- case 'ali':
- opts.protocol = 'alis'
- break
- default:
- throw new Error('Unknown protocol for secure connection: "' + opts.protocol + '"!')
- }
- }
- } else {
- // A cert and key was provided, however no protocol was specified, so we will throw an error.
- throw new Error('Missing secure protocol key')
- }
- }
-
- if (!protocols[opts.protocol]) {
- const isSecure = ['mqtts', 'wss'].indexOf(opts.protocol) !== -1
- opts.protocol = [
- 'mqtt',
- 'mqtts',
- 'ws',
- 'wss',
- 'wx',
- 'wxs',
- 'ali',
- 'alis'
- ].filter(function (key, index) {
- if (isSecure && index % 2 === 0) {
- // Skip insecure protocols when requesting a secure one.
- return false
- }
- return (typeof protocols[key] === 'function')
- })[0]
- }
-
- if (opts.clean === false && !opts.clientId) {
- throw new Error('Missing clientId for unclean clients')
- }
-
- if (opts.protocol) {
- opts.defaultProtocol = opts.protocol
- }
-
- function wrapper (client) {
- if (opts.servers) {
- if (!client._reconnectCount || client._reconnectCount === opts.servers.length) {
- client._reconnectCount = 0
- }
-
- opts.host = opts.servers[client._reconnectCount].host
- opts.port = opts.servers[client._reconnectCount].port
- opts.protocol = (!opts.servers[client._reconnectCount].protocol ? opts.defaultProtocol : opts.servers[client._reconnectCount].protocol)
- opts.hostname = opts.host
-
- client._reconnectCount++
- }
-
- debug('calling streambuilder for', opts.protocol)
- return protocols[opts.protocol](client, opts)
- }
- const client = new MqttClient(wrapper, opts)
- client.on('error', function () { /* Automatically set up client error handling */ })
- return client
+ if (parsed.port != null) {
+ parsed.port = Number(parsed.port)
+ }
+
+ opts = { ...parsed, ...opts }
+
+ if (opts.protocol === null) {
+ throw new Error('Missing protocol')
+ }
+
+ opts.protocol = opts.protocol.replace(/:$/, '')
+ }
+
+ // merge in the auth options if supplied
+ parseAuthOptions(opts)
+
+ // support clientId passed in the query string of the url
+ if (opts.query && typeof opts.query.clientId === 'string') {
+ opts.clientId = opts.query.clientId
+ }
+
+ if (opts.cert && opts.key) {
+ if (opts.protocol) {
+ if (['mqtts', 'wss', 'wxs', 'alis'].indexOf(opts.protocol) === -1) {
+ switch (opts.protocol) {
+ case 'mqtt':
+ opts.protocol = 'mqtts'
+ break
+ case 'ws':
+ opts.protocol = 'wss'
+ break
+ case 'wx':
+ opts.protocol = 'wxs'
+ break
+ case 'ali':
+ opts.protocol = 'alis'
+ break
+ default:
+ throw new Error(
+ `Unknown protocol for secure connection: "${opts.protocol}"!`,
+ )
+ }
+ }
+ } else {
+ // A cert and key was provided, however no protocol was specified, so we will throw an error.
+ throw new Error('Missing secure protocol key')
+ }
+ }
+
+ if (!protocols[opts.protocol]) {
+ const isSecure = ['mqtts', 'wss'].indexOf(opts.protocol) !== -1
+ opts.protocol = [
+ 'mqtt',
+ 'mqtts',
+ 'ws',
+ 'wss',
+ 'wx',
+ 'wxs',
+ 'ali',
+ 'alis',
+ ].filter((key, index) => {
+ if (isSecure && index % 2 === 0) {
+ // Skip insecure protocols when requesting a secure one.
+ return false
+ }
+ return typeof protocols[key] === 'function'
+ })[0]
+ }
+
+ if (opts.clean === false && !opts.clientId) {
+ throw new Error('Missing clientId for unclean clients')
+ }
+
+ if (opts.protocol) {
+ opts.defaultProtocol = opts.protocol
+ }
+
+ function wrapper(client) {
+ if (opts.servers) {
+ if (
+ !client._reconnectCount ||
+ client._reconnectCount === opts.servers.length
+ ) {
+ client._reconnectCount = 0
+ }
+
+ opts.host = opts.servers[client._reconnectCount].host
+ opts.port = opts.servers[client._reconnectCount].port
+ opts.protocol = !opts.servers[client._reconnectCount].protocol
+ ? opts.defaultProtocol
+ : opts.servers[client._reconnectCount].protocol
+ opts.hostname = opts.host
+
+ client._reconnectCount++
+ }
+
+ debug('calling streambuilder for', opts.protocol)
+ return protocols[opts.protocol](client, opts)
+ }
+ const client = new MqttClient(wrapper, opts)
+ client.on('error', () => {
+ /* Automatically set up client error handling */
+ })
+ return client
}
module.exports = connect
diff --git a/lib/connect/tcp.js b/lib/connect/tcp.js
index 607036537..650ce844e 100644
--- a/lib/connect/tcp.js
+++ b/lib/connect/tcp.js
@@ -1,4 +1,3 @@
-'use strict'
const net = require('net')
const debug = require('debug')('mqttjs:tcp')
@@ -6,15 +5,15 @@ const debug = require('debug')('mqttjs:tcp')
variables port and host can be removed since
you have all required information in opts object
*/
-function streamBuilder (client, opts) {
- opts.port = opts.port || 1883
- opts.hostname = opts.hostname || opts.host || 'localhost'
+function streamBuilder(client, opts) {
+ opts.port = opts.port || 1883
+ opts.hostname = opts.hostname || opts.host || 'localhost'
- const port = opts.port
- const host = opts.hostname
+ const { port } = opts
+ const host = opts.hostname
- debug('port %d and host %s', port, host)
- return net.createConnection(port, host)
+ debug('port %d and host %s', port, host)
+ return net.createConnection(port, host)
}
module.exports = streamBuilder
diff --git a/lib/connect/tls.js b/lib/connect/tls.js
index bb01aeb8f..076c64faa 100644
--- a/lib/connect/tls.js
+++ b/lib/connect/tls.js
@@ -1,48 +1,52 @@
-'use strict'
const tls = require('tls')
const net = require('net')
const debug = require('debug')('mqttjs:tls')
-function buildBuilder (mqttClient, opts) {
- opts.port = opts.port || 8883
- opts.host = opts.hostname || opts.host || 'localhost'
-
- if (net.isIP(opts.host) === 0) {
- opts.servername = opts.host
- }
-
- opts.rejectUnauthorized = opts.rejectUnauthorized !== false
-
- delete opts.path
-
- debug('port %d host %s rejectUnauthorized %b', opts.port, opts.host, opts.rejectUnauthorized)
-
- const connection = tls.connect(opts)
- /* eslint no-use-before-define: [2, "nofunc"] */
- connection.on('secureConnect', function () {
- if (opts.rejectUnauthorized && !connection.authorized) {
- connection.emit('error', new Error('TLS not authorized'))
- } else {
- connection.removeListener('error', handleTLSerrors)
- }
- })
-
- function handleTLSerrors (err) {
- // How can I get verify this error is a tls error?
- if (opts.rejectUnauthorized) {
- mqttClient.emit('error', err)
- }
-
- // close this connection to match the behaviour of net
- // otherwise all we get is an error from the connection
- // and close event doesn't fire. This is a work around
- // to enable the reconnect code to work the same as with
- // net.createConnection
- connection.end()
- }
-
- connection.on('error', handleTLSerrors)
- return connection
+function buildBuilder(mqttClient, opts) {
+ opts.port = opts.port || 8883
+ opts.host = opts.hostname || opts.host || 'localhost'
+
+ if (net.isIP(opts.host) === 0) {
+ opts.servername = opts.host
+ }
+
+ opts.rejectUnauthorized = opts.rejectUnauthorized !== false
+
+ delete opts.path
+
+ debug(
+ 'port %d host %s rejectUnauthorized %b',
+ opts.port,
+ opts.host,
+ opts.rejectUnauthorized,
+ )
+
+ const connection = tls.connect(opts)
+ /* eslint no-use-before-define: [2, "nofunc"] */
+ connection.on('secureConnect', () => {
+ if (opts.rejectUnauthorized && !connection.authorized) {
+ connection.emit('error', new Error('TLS not authorized'))
+ } else {
+ connection.removeListener('error', handleTLSerrors)
+ }
+ })
+
+ function handleTLSerrors(err) {
+ // How can I get verify this error is a tls error?
+ if (opts.rejectUnauthorized) {
+ mqttClient.emit('error', err)
+ }
+
+ // close this connection to match the behaviour of net
+ // otherwise all we get is an error from the connection
+ // and close event doesn't fire. This is a work around
+ // to enable the reconnect code to work the same as with
+ // net.createConnection
+ connection.end()
+ }
+
+ connection.on('error', handleTLSerrors)
+ return connection
}
module.exports = buildBuilder
diff --git a/lib/connect/ws.js b/lib/connect/ws.js
index 2cd5cce86..de80c0aad 100644
--- a/lib/connect/ws.js
+++ b/lib/connect/ws.js
@@ -1,257 +1,265 @@
-'use strict'
-
const { Buffer } = require('buffer')
const WS = require('ws')
const debug = require('debug')('mqttjs:ws')
const duplexify = require('duplexify')
-const Transform = require('readable-stream').Transform
+const { Transform } = require('readable-stream')
const { IS_BROWSER } = require('../is-browser')
const WSS_OPTIONS = [
- 'rejectUnauthorized',
- 'ca',
- 'cert',
- 'key',
- 'pfx',
- 'passphrase'
+ 'rejectUnauthorized',
+ 'ca',
+ 'cert',
+ 'key',
+ 'pfx',
+ 'passphrase',
]
-function buildUrl (opts, client) {
- let url = opts.protocol + '://' + opts.hostname + ':' + opts.port + opts.path
- if (typeof (opts.transformWsUrl) === 'function') {
- url = opts.transformWsUrl(url, opts, client)
- }
- return url
+function buildUrl(opts, client) {
+ let url = `${opts.protocol}://${opts.hostname}:${opts.port}${opts.path}`
+ if (typeof opts.transformWsUrl === 'function') {
+ url = opts.transformWsUrl(url, opts, client)
+ }
+ return url
}
-function setDefaultOpts (opts) {
- const options = opts
- if (!opts.hostname) {
- options.hostname = 'localhost'
- }
- if (!opts.port) {
- if (opts.protocol === 'wss') {
- options.port = 443
- } else {
- options.port = 80
- }
- }
- if (!opts.path) {
- options.path = '/'
- }
-
- if (!opts.wsOptions) {
- options.wsOptions = {}
- }
- if (!IS_BROWSER && opts.protocol === 'wss') {
- // Add cert/key/ca etc options
- WSS_OPTIONS.forEach(function (prop) {
- if (Object.prototype.hasOwnProperty.call(opts, prop) && !Object.prototype.hasOwnProperty.call(opts.wsOptions, prop)) {
- options.wsOptions[prop] = opts[prop]
- }
- })
- }
-
- return options
+function setDefaultOpts(opts) {
+ const options = opts
+ if (!opts.hostname) {
+ options.hostname = 'localhost'
+ }
+ if (!opts.port) {
+ if (opts.protocol === 'wss') {
+ options.port = 443
+ } else {
+ options.port = 80
+ }
+ }
+ if (!opts.path) {
+ options.path = '/'
+ }
+
+ if (!opts.wsOptions) {
+ options.wsOptions = {}
+ }
+ if (!IS_BROWSER && opts.protocol === 'wss') {
+ // Add cert/key/ca etc options
+ WSS_OPTIONS.forEach((prop) => {
+ if (
+ Object.prototype.hasOwnProperty.call(opts, prop) &&
+ !Object.prototype.hasOwnProperty.call(opts.wsOptions, prop)
+ ) {
+ options.wsOptions[prop] = opts[prop]
+ }
+ })
+ }
+
+ return options
}
-function setDefaultBrowserOpts (opts) {
- const options = setDefaultOpts(opts)
-
- if (!options.hostname) {
- options.hostname = options.host
- }
-
- if (!options.hostname) {
- // Throwing an error in a Web Worker if no `hostname` is given, because we
- // can not determine the `hostname` automatically. If connecting to
- // localhost, please supply the `hostname` as an argument.
- if (typeof (document) === 'undefined') {
- throw new Error('Could not determine host. Specify host manually.')
- }
- const parsed = new URL(document.URL)
- options.hostname = parsed.hostname
-
- if (!options.port) {
- options.port = parsed.port
- }
- }
-
- // objectMode should be defined for logic
- if (options.objectMode === undefined) {
- options.objectMode = !(options.binary === true || options.binary === undefined)
- }
-
- return options
+function setDefaultBrowserOpts(opts) {
+ const options = setDefaultOpts(opts)
+
+ if (!options.hostname) {
+ options.hostname = options.host
+ }
+
+ if (!options.hostname) {
+ // Throwing an error in a Web Worker if no `hostname` is given, because we
+ // can not determine the `hostname` automatically. If connecting to
+ // localhost, please supply the `hostname` as an argument.
+ if (typeof document === 'undefined') {
+ throw new Error('Could not determine host. Specify host manually.')
+ }
+ const parsed = new URL(document.URL)
+ options.hostname = parsed.hostname
+
+ if (!options.port) {
+ options.port = parsed.port
+ }
+ }
+
+ // objectMode should be defined for logic
+ if (options.objectMode === undefined) {
+ options.objectMode = !(
+ options.binary === true || options.binary === undefined
+ )
+ }
+
+ return options
}
-function createWebSocket (client, url, opts) {
- debug('createWebSocket')
- debug('protocol: ' + opts.protocolId + ' ' + opts.protocolVersion)
- const websocketSubProtocol =
- (opts.protocolId === 'MQIsdp') && (opts.protocolVersion === 3)
- ? 'mqttv3.1'
- : 'mqtt'
-
- debug('creating new Websocket for url: ' + url + ' and protocol: ' + websocketSubProtocol)
- const socket = new WS(url, [websocketSubProtocol], opts.wsOptions)
- return socket
+function createWebSocket(client, url, opts) {
+ debug('createWebSocket')
+ debug(`protocol: ${opts.protocolId} ${opts.protocolVersion}`)
+ const websocketSubProtocol =
+ opts.protocolId === 'MQIsdp' && opts.protocolVersion === 3
+ ? 'mqttv3.1'
+ : 'mqtt'
+
+ debug(
+ `creating new Websocket for url: ${url} and protocol: ${websocketSubProtocol}`,
+ )
+ const socket = new WS(url, [websocketSubProtocol], opts.wsOptions)
+ return socket
}
-function createBrowserWebSocket (client, opts) {
- const websocketSubProtocol =
- (opts.protocolId === 'MQIsdp') && (opts.protocolVersion === 3)
- ? 'mqttv3.1'
- : 'mqtt'
-
- const url = buildUrl(opts, client)
- /* global WebSocket */
- const socket = new WebSocket(url, [websocketSubProtocol])
- socket.binaryType = 'arraybuffer'
- return socket
+function createBrowserWebSocket(client, opts) {
+ const websocketSubProtocol =
+ opts.protocolId === 'MQIsdp' && opts.protocolVersion === 3
+ ? 'mqttv3.1'
+ : 'mqtt'
+
+ const url = buildUrl(opts, client)
+ const socket = new WebSocket(url, [websocketSubProtocol])
+ socket.binaryType = 'arraybuffer'
+ return socket
}
-function streamBuilder (client, opts) {
- debug('streamBuilder')
- const options = setDefaultOpts(opts)
- const url = buildUrl(options, client)
- const socket = createWebSocket(client, url, options)
- const webSocketStream = WS.createWebSocketStream(socket, options.wsOptions)
- webSocketStream.url = url
- socket.on('close', () => { webSocketStream.destroy() })
- return webSocketStream
+function streamBuilder(client, opts) {
+ debug('streamBuilder')
+ const options = setDefaultOpts(opts)
+ const url = buildUrl(options, client)
+ const socket = createWebSocket(client, url, options)
+ const webSocketStream = WS.createWebSocketStream(socket, options.wsOptions)
+ webSocketStream.url = url
+ socket.on('close', () => {
+ webSocketStream.destroy()
+ })
+ return webSocketStream
}
-function browserStreamBuilder (client, opts) {
- debug('browserStreamBuilder')
- let stream
- const options = setDefaultBrowserOpts(opts)
- // sets the maximum socket buffer size before throttling
- const bufferSize = options.browserBufferSize || 1024 * 512
-
- const bufferTimeout = opts.browserBufferTimeout || 1000
-
- const coerceToBuffer = !opts.objectMode
-
- const socket = createBrowserWebSocket(client, opts)
-
- const proxy = buildProxy(opts, socketWriteBrowser, socketEndBrowser)
-
- if (!opts.objectMode) {
- proxy._writev = writev
- }
- proxy.on('close', () => { socket.close() })
-
- const eventListenerSupport = (typeof socket.addEventListener !== 'undefined')
-
- // was already open when passed in
- if (socket.readyState === socket.OPEN) {
- stream = proxy
- } else {
- stream = stream = duplexify(undefined, undefined, opts)
- if (!opts.objectMode) {
- stream._writev = writev
- }
-
- if (eventListenerSupport) {
- socket.addEventListener('open', onopen)
- } else {
- socket.onopen = onopen
- }
- }
-
- stream.socket = socket
-
- if (eventListenerSupport) {
- socket.addEventListener('close', onclose)
- socket.addEventListener('error', onerror)
- socket.addEventListener('message', onmessage)
- } else {
- socket.onclose = onclose
- socket.onerror = onerror
- socket.onmessage = onmessage
- }
-
- // methods for browserStreamBuilder
-
- function buildProxy (options, socketWrite, socketEnd) {
- const proxy = new Transform({
- objectModeMode: options.objectMode
- })
-
- proxy._write = socketWrite
- proxy._flush = socketEnd
-
- return proxy
- }
-
- function onopen () {
- stream.setReadable(proxy)
- stream.setWritable(proxy)
- stream.emit('connect')
- }
-
- function onclose () {
- stream.end()
- stream.destroy()
- }
-
- function onerror (err) {
- stream.destroy(err)
- }
-
- function onmessage (event) {
- let data = event.data
- if (data instanceof ArrayBuffer) data = Buffer.from(data)
- else data = Buffer.from(data, 'utf8')
- proxy.push(data)
- }
-
- // this is to be enabled only if objectMode is false
- function writev (chunks, cb) {
- const buffers = new Array(chunks.length)
- for (let i = 0; i < chunks.length; i++) {
- if (typeof chunks[i].chunk === 'string') {
- buffers[i] = Buffer.from(chunks[i], 'utf8')
- } else {
- buffers[i] = chunks[i].chunk
- }
- }
-
- this._write(Buffer.concat(buffers), 'binary', cb)
- }
-
- function socketWriteBrowser (chunk, enc, next) {
- if (socket.bufferedAmount > bufferSize) {
- // throttle data until buffered amount is reduced.
- setTimeout(socketWriteBrowser, bufferTimeout, chunk, enc, next)
- }
-
- if (coerceToBuffer && typeof chunk === 'string') {
- chunk = Buffer.from(chunk, 'utf8')
- }
-
- try {
- socket.send(chunk)
- } catch (err) {
- return next(err)
- }
-
- next()
- }
-
- function socketEndBrowser (done) {
- socket.close()
- done()
- }
-
- // end methods for browserStreamBuilder
-
- return stream
+function browserStreamBuilder(client, opts) {
+ debug('browserStreamBuilder')
+ let stream
+ const options = setDefaultBrowserOpts(opts)
+ // sets the maximum socket buffer size before throttling
+ const bufferSize = options.browserBufferSize || 1024 * 512
+
+ const bufferTimeout = opts.browserBufferTimeout || 1000
+
+ const coerceToBuffer = !opts.objectMode
+
+ const socket = createBrowserWebSocket(client, opts)
+
+ const proxy = buildProxy(opts, socketWriteBrowser, socketEndBrowser)
+
+ if (!opts.objectMode) {
+ proxy._writev = writev
+ }
+ proxy.on('close', () => {
+ socket.close()
+ })
+
+ const eventListenerSupport = typeof socket.addEventListener !== 'undefined'
+
+ // was already open when passed in
+ if (socket.readyState === socket.OPEN) {
+ stream = proxy
+ } else {
+ stream = duplexify(undefined, undefined, opts)
+ if (!opts.objectMode) {
+ stream._writev = writev
+ }
+
+ if (eventListenerSupport) {
+ socket.addEventListener('open', onOpen)
+ } else {
+ socket.onopen = onOpen
+ }
+ }
+
+ stream.socket = socket
+
+ if (eventListenerSupport) {
+ socket.addEventListener('close', onclose)
+ socket.addEventListener('error', onerror)
+ socket.addEventListener('message', onmessage)
+ } else {
+ socket.onclose = onclose
+ socket.onerror = onerror
+ socket.onmessage = onmessage
+ }
+
+ // methods for browserStreamBuilder
+
+ function buildProxy(pOptions, socketWrite, socketEnd) {
+ const _proxy = new Transform({
+ objectModeMode: pOptions.objectMode,
+ })
+
+ _proxy._write = socketWrite
+ _proxy._flush = socketEnd
+
+ return _proxy
+ }
+
+ function onOpen() {
+ stream.setReadable(proxy)
+ stream.setWritable(proxy)
+ stream.emit('connect')
+ }
+
+ function onclose() {
+ stream.end()
+ stream.destroy()
+ }
+
+ function onerror(err) {
+ stream.destroy(err)
+ }
+
+ function onmessage(event) {
+ let { data } = event
+ if (data instanceof ArrayBuffer) data = Buffer.from(data)
+ else data = Buffer.from(data, 'utf8')
+ proxy.push(data)
+ }
+
+ // this is to be enabled only if objectMode is false
+ function writev(chunks, cb) {
+ const buffers = new Array(chunks.length)
+ for (let i = 0; i < chunks.length; i++) {
+ if (typeof chunks[i].chunk === 'string') {
+ buffers[i] = Buffer.from(chunks[i], 'utf8')
+ } else {
+ buffers[i] = chunks[i].chunk
+ }
+ }
+
+ this._write(Buffer.concat(buffers), 'binary', cb)
+ }
+
+ function socketWriteBrowser(chunk, enc, next) {
+ if (socket.bufferedAmount > bufferSize) {
+ // throttle data until buffered amount is reduced.
+ setTimeout(socketWriteBrowser, bufferTimeout, chunk, enc, next)
+ }
+
+ if (coerceToBuffer && typeof chunk === 'string') {
+ chunk = Buffer.from(chunk, 'utf8')
+ }
+
+ try {
+ socket.send(chunk)
+ } catch (err) {
+ return next(err)
+ }
+
+ next()
+ }
+
+ function socketEndBrowser(done) {
+ socket.close()
+ done()
+ }
+
+ // end methods for browserStreamBuilder
+
+ return stream
}
if (IS_BROWSER) {
- module.exports = browserStreamBuilder
+ module.exports = browserStreamBuilder
} else {
- module.exports = streamBuilder
+ module.exports = streamBuilder
}
diff --git a/lib/connect/wx.js b/lib/connect/wx.js
index 003099e3e..b6e1139be 100644
--- a/lib/connect/wx.js
+++ b/lib/connect/wx.js
@@ -1,133 +1,132 @@
-'use strict'
-
const { Buffer } = require('buffer')
-const Transform = require('readable-stream').Transform
+const { Transform } = require('readable-stream')
const duplexify = require('duplexify')
/* global wx */
-let socketTask, proxy, stream
-
-function buildProxy () {
- const proxy = new Transform()
- proxy._write = function (chunk, encoding, next) {
- socketTask.send({
- data: chunk.buffer,
- success: function () {
- next()
- },
- fail: function (errMsg) {
- next(new Error(errMsg))
- }
- })
- }
- proxy._flush = function socketEnd (done) {
- socketTask.close({
- success: function () {
- done()
- }
- })
- }
-
- return proxy
+let socketTask
+let proxy
+let stream
+
+function buildProxy() {
+ const _proxy = new Transform()
+ _proxy._write = (chunk, encoding, next) => {
+ socketTask.send({
+ data: chunk.buffer,
+ success() {
+ next()
+ },
+ fail(errMsg) {
+ next(new Error(errMsg))
+ },
+ })
+ }
+ _proxy._flush = (done) => {
+ socketTask.close({
+ success() {
+ done()
+ },
+ })
+ }
+
+ return _proxy
}
-function setDefaultOpts (opts) {
- if (!opts.hostname) {
- opts.hostname = 'localhost'
- }
- if (!opts.path) {
- opts.path = '/'
- }
-
- if (!opts.wsOptions) {
- opts.wsOptions = {}
- }
+function setDefaultOpts(opts) {
+ if (!opts.hostname) {
+ opts.hostname = 'localhost'
+ }
+ if (!opts.path) {
+ opts.path = '/'
+ }
+
+ if (!opts.wsOptions) {
+ opts.wsOptions = {}
+ }
}
-function buildUrl (opts, client) {
- const protocol = opts.protocol === 'wxs' ? 'wss' : 'ws'
- let url = protocol + '://' + opts.hostname + opts.path
- if (opts.port && opts.port !== 80 && opts.port !== 443) {
- url = protocol + '://' + opts.hostname + ':' + opts.port + opts.path
- }
- if (typeof (opts.transformWsUrl) === 'function') {
- url = opts.transformWsUrl(url, opts, client)
- }
- return url
+function buildUrl(opts, client) {
+ const protocol = opts.protocol === 'wxs' ? 'wss' : 'ws'
+ let url = `${protocol}://${opts.hostname}${opts.path}`
+ if (opts.port && opts.port !== 80 && opts.port !== 443) {
+ url = `${protocol}://${opts.hostname}:${opts.port}${opts.path}`
+ }
+ if (typeof opts.transformWsUrl === 'function') {
+ url = opts.transformWsUrl(url, opts, client)
+ }
+ return url
}
-function bindEventHandler () {
- socketTask.onOpen(function () {
- stream.setReadable(proxy)
- stream.setWritable(proxy)
- stream.emit('connect')
- })
-
- socketTask.onMessage(function (res) {
- let data = res.data
-
- if (data instanceof ArrayBuffer) data = Buffer.from(data)
- else data = Buffer.from(data, 'utf8')
- proxy.push(data)
- })
-
- socketTask.onClose(function () {
- stream.end()
- stream.destroy()
- })
-
- socketTask.onError(function (res) {
- stream.destroy(new Error(res.errMsg))
- })
+function bindEventHandler() {
+ socketTask.onOpen(() => {
+ stream.setReadable(proxy)
+ stream.setWritable(proxy)
+ stream.emit('connect')
+ })
+
+ socketTask.onMessage((res) => {
+ let { data } = res
+
+ if (data instanceof ArrayBuffer) data = Buffer.from(data)
+ else data = Buffer.from(data, 'utf8')
+ proxy.push(data)
+ })
+
+ socketTask.onClose(() => {
+ stream.end()
+ stream.destroy()
+ })
+
+ socketTask.onError((res) => {
+ stream.destroy(new Error(res.errMsg))
+ })
}
-function buildStream (client, opts) {
- opts.hostname = opts.hostname || opts.host
-
- if (!opts.hostname) {
- throw new Error('Could not determine host. Specify host manually.')
- }
-
- const websocketSubProtocol =
- (opts.protocolId === 'MQIsdp') && (opts.protocolVersion === 3)
- ? 'mqttv3.1'
- : 'mqtt'
-
- setDefaultOpts(opts)
-
- const url = buildUrl(opts, client)
- socketTask = wx.connectSocket({
- url,
- protocols: [websocketSubProtocol]
- })
-
- proxy = buildProxy()
- stream = duplexify.obj()
- stream._destroy = function (err, cb) {
- socketTask.close({
- success: function () {
- cb && cb(err)
- }
- })
- }
-
- const destroyRef = stream.destroy
- stream.destroy = function () {
- stream.destroy = destroyRef
-
- const self = this
- setTimeout(function () {
- socketTask.close({
- fail: function () {
- self._destroy(new Error())
- }
- })
- }, 0)
- }.bind(stream)
-
- bindEventHandler()
-
- return stream
+function buildStream(client, opts) {
+ opts.hostname = opts.hostname || opts.host
+
+ if (!opts.hostname) {
+ throw new Error('Could not determine host. Specify host manually.')
+ }
+
+ const websocketSubProtocol =
+ opts.protocolId === 'MQIsdp' && opts.protocolVersion === 3
+ ? 'mqttv3.1'
+ : 'mqtt'
+
+ setDefaultOpts(opts)
+
+ const url = buildUrl(opts, client)
+ socketTask = wx.connectSocket({
+ url,
+ protocols: [websocketSubProtocol],
+ })
+
+ proxy = buildProxy()
+ stream = duplexify.obj()
+ stream._destroy = (err, cb) => {
+ socketTask.close({
+ success() {
+ if (cb) cb(err)
+ },
+ })
+ }
+
+ const destroyRef = stream.destroy
+ stream.destroy = () => {
+ stream.destroy = destroyRef
+
+ setTimeout(() => {
+ socketTask.close({
+ fail() {
+ stream._destroy(new Error())
+ },
+ })
+ }, 0)
+ }
+
+ bindEventHandler()
+
+ return stream
}
module.exports = buildStream
diff --git a/lib/default-message-id-provider.js b/lib/default-message-id-provider.js
index d7a1da87f..6f544aacf 100644
--- a/lib/default-message-id-provider.js
+++ b/lib/default-message-id-provider.js
@@ -1,67 +1,63 @@
-'use strict'
-
/**
* DefaultMessageAllocator constructor
* @constructor
*/
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))
- }
+ 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 () {
- // 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
- }
+ /**
+ * allocate
+ *
+ * Get the next messageId.
+ * @return unsigned int
+ */
+ 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)
- }
+ /**
+ * 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
- }
+ /**
+ * 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) {
- }
+ /**
+ * deallocate
+ * Deallocate messageId.
+ * @param { unsigned int } - messageId to deallocate,
+ */
+ deallocate(messageId) {}
- /**
- * clear
- * Deallocate all messageIds.
- */
- clear () {
- }
+ /**
+ * clear
+ * Deallocate all messageIds.
+ */
+ clear() {}
}
module.exports = DefaultMessageIdProvider
diff --git a/lib/handlers/ack.js b/lib/handlers/ack.js
index 1109ef11c..de92af60b 100644
--- a/lib/handlers/ack.js
+++ b/lib/handlers/ack.js
@@ -1,151 +1,153 @@
-
// Other Socket Errors: EADDRINUSE, ECONNRESET, ENOTFOUND, ETIMEDOUT.
const errors = {
- 0: '',
- 1: 'Unacceptable protocol version',
- 2: 'Identifier rejected',
- 3: 'Server unavailable',
- 4: 'Bad username or password',
- 5: 'Not authorized',
- 16: 'No matching subscribers',
- 17: 'No subscription existed',
- 128: 'Unspecified error',
- 129: 'Malformed Packet',
- 130: 'Protocol Error',
- 131: 'Implementation specific error',
- 132: 'Unsupported Protocol Version',
- 133: 'Client Identifier not valid',
- 134: 'Bad User Name or Password',
- 135: 'Not authorized',
- 136: 'Server unavailable',
- 137: 'Server busy',
- 138: 'Banned',
- 139: 'Server shutting down',
- 140: 'Bad authentication method',
- 141: 'Keep Alive timeout',
- 142: 'Session taken over',
- 143: 'Topic Filter invalid',
- 144: 'Topic Name invalid',
- 145: 'Packet identifier in use',
- 146: 'Packet Identifier not found',
- 147: 'Receive Maximum exceeded',
- 148: 'Topic Alias invalid',
- 149: 'Packet too large',
- 150: 'Message rate too high',
- 151: 'Quota exceeded',
- 152: 'Administrative action',
- 153: 'Payload format invalid',
- 154: 'Retain not supported',
- 155: 'QoS not supported',
- 156: 'Use another server',
- 157: 'Server moved',
- 158: 'Shared Subscriptions not supported',
- 159: 'Connection rate exceeded',
- 160: 'Maximum connect time',
- 161: 'Subscription Identifiers not supported',
- 162: 'Wildcard Subscriptions not supported'
- }
+ 0: '',
+ 1: 'Unacceptable protocol version',
+ 2: 'Identifier rejected',
+ 3: 'Server unavailable',
+ 4: 'Bad username or password',
+ 5: 'Not authorized',
+ 16: 'No matching subscribers',
+ 17: 'No subscription existed',
+ 128: 'Unspecified error',
+ 129: 'Malformed Packet',
+ 130: 'Protocol Error',
+ 131: 'Implementation specific error',
+ 132: 'Unsupported Protocol Version',
+ 133: 'Client Identifier not valid',
+ 134: 'Bad User Name or Password',
+ 135: 'Not authorized',
+ 136: 'Server unavailable',
+ 137: 'Server busy',
+ 138: 'Banned',
+ 139: 'Server shutting down',
+ 140: 'Bad authentication method',
+ 141: 'Keep Alive timeout',
+ 142: 'Session taken over',
+ 143: 'Topic Filter invalid',
+ 144: 'Topic Name invalid',
+ 145: 'Packet identifier in use',
+ 146: 'Packet Identifier not found',
+ 147: 'Receive Maximum exceeded',
+ 148: 'Topic Alias invalid',
+ 149: 'Packet too large',
+ 150: 'Message rate too high',
+ 151: 'Quota exceeded',
+ 152: 'Administrative action',
+ 153: 'Payload format invalid',
+ 154: 'Retain not supported',
+ 155: 'QoS not supported',
+ 156: 'Use another server',
+ 157: 'Server moved',
+ 158: 'Shared Subscriptions not supported',
+ 159: 'Connection rate exceeded',
+ 160: 'Maximum connect time',
+ 161: 'Subscription Identifiers not supported',
+ 162: 'Wildcard Subscriptions not supported',
+}
function handleAck(client, packet) {
- /* eslint no-fallthrough: "off" */
- const messageId = packet.messageId
- const type = packet.cmd
- let response = null
- const cb = client.outgoing[messageId] ? client.outgoing[messageId].cb : null
- let err
+ /* eslint no-fallthrough: "off" */
+ const { messageId } = packet
+ const type = packet.cmd
+ let response = null
+ const cb = client.outgoing[messageId] ? client.outgoing[messageId].cb : null
+ let err
- // Checking `!cb` happens to work, but it's not technically "correct".
- //
- // Why? client code assumes client "no callback" is the same as client "we're not
- // waiting for responses" (puback, pubrec, pubcomp, suback, or unsuback).
- //
- // It would be better to check `if (!client.outgoing[messageId])` here, but
- // there's no reason to change it and risk (another) regression.
- //
- // The only reason client code works is becaues code in MqttClient.publish,
- // MqttClinet.subscribe, and MqttClient.unsubscribe ensures client we will
- // have a callback even if the user doesn't pass one in.)
- if (!cb) {
- client.log('_handleAck :: Server sent an ack in error. Ignoring.')
- // Server sent an ack in error, ignore it.
- return
- }
+ // Checking `!cb` happens to work, but it's not technically "correct".
+ //
+ // Why? client code assumes client "no callback" is the same as client "we're not
+ // waiting for responses" (puback, pubrec, pubcomp, suback, or unsuback).
+ //
+ // It would be better to check `if (!client.outgoing[messageId])` here, but
+ // there's no reason to change it and risk (another) regression.
+ //
+ // The only reason client code works is becaues code in MqttClient.publish,
+ // MqttClinet.subscribe, and MqttClient.unsubscribe ensures client we will
+ // have a callback even if the user doesn't pass one in.)
+ if (!cb) {
+ client.log('_handleAck :: Server sent an ack in error. Ignoring.')
+ // Server sent an ack in error, ignore it.
+ return
+ }
- // Process
- client.log('_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
- client._removeOutgoingAndStoreMessage(messageId, function () {
- cb(err, packet)
- })
- } else {
- client._removeOutgoingAndStoreMessage(messageId, cb)
- }
+ // Process
+ client.log('_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
+ client._removeOutgoingAndStoreMessage(messageId, () => {
+ cb(err, packet)
+ })
+ } else {
+ client._removeOutgoingAndStoreMessage(messageId, cb)
+ }
- break
- }
- case 'pubrec': {
- response = {
- cmd: 'pubrel',
- qos: 2,
- messageId
- }
- const pubrecRC = packet.reasonCode
+ break
+ }
+ 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
- client._removeOutgoingAndStoreMessage(messageId, function () {
- cb(err, packet)
- })
- } else {
- client._sendPacket(response)
- }
- break
- }
- case 'suback': {
- delete client.outgoing[messageId]
- client.messageIdProvider.deallocate(messageId)
- for (let grantedI = 0; grantedI < packet.granted.length; grantedI++) {
- if ((packet.granted[grantedI] & 0x80) !== 0) {
- // suback with Failure status
- const topics = client.messageIdToTopic[messageId]
- if (topics) {
- topics.forEach(function (topic) {
- delete client._resubscribeTopics[topic]
- })
- }
- }
- }
- delete client.messageIdToTopic[messageId]
- client._invokeStoreProcessingQueue()
- cb(null, packet)
- break
- }
- case 'unsuback': {
- delete client.outgoing[messageId]
- client.messageIdProvider.deallocate(messageId)
- client._invokeStoreProcessingQueue()
- cb(null)
- break
- }
- default:
- client.emit('error', new Error('unrecognized packet type'))
- }
+ if (pubrecRC && pubrecRC > 0 && pubrecRC !== 16) {
+ err = new Error(`Publish error: ${errors[pubrecRC]}`)
+ err.code = pubrecRC
+ client._removeOutgoingAndStoreMessage(messageId, () => {
+ cb(err, packet)
+ })
+ } else {
+ client._sendPacket(response)
+ }
+ break
+ }
+ case 'suback': {
+ delete client.outgoing[messageId]
+ client.messageIdProvider.deallocate(messageId)
+ for (
+ let grantedI = 0;
+ grantedI < packet.granted.length;
+ grantedI++
+ ) {
+ if ((packet.granted[grantedI] & 0x80) !== 0) {
+ // suback with Failure status
+ const topics = client.messageIdToTopic[messageId]
+ if (topics) {
+ topics.forEach((topic) => {
+ delete client._resubscribeTopics[topic]
+ })
+ }
+ }
+ }
+ delete client.messageIdToTopic[messageId]
+ client._invokeStoreProcessingQueue()
+ cb(null, packet)
+ break
+ }
+ case 'unsuback': {
+ delete client.outgoing[messageId]
+ client.messageIdProvider.deallocate(messageId)
+ client._invokeStoreProcessingQueue()
+ cb(null)
+ break
+ }
+ default:
+ client.emit('error', new Error('unrecognized packet type'))
+ }
- if (client.disconnecting &&
- Object.keys(client.outgoing).length === 0) {
- client.emit('outgoingEmpty')
- }
+ if (client.disconnecting && Object.keys(client.outgoing).length === 0) {
+ client.emit('outgoingEmpty')
+ }
}
-module.exports = handleAck;
-module.exports.errors = errors;
\ No newline at end of file
+module.exports = handleAck
+module.exports.errors = errors
diff --git a/lib/handlers/auth.js b/lib/handlers/auth.js
index 9c2f23d83..6d2d3e9be 100644
--- a/lib/handlers/auth.js
+++ b/lib/handlers/auth.js
@@ -1,33 +1,34 @@
-
+const { errors } = require('./ack')
function handleAuth(client, packet) {
- const options = client.options
- const version = options.protocolVersion
- const rc = version === 5 ? packet.reasonCode : packet.returnCode
+ const { options } = client
+ 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
- client.emit('error', err)
- return
- }
+ if (version !== 5) {
+ const err = new Error(
+ `Protocol error: Auth packets are only supported in MQTT 5. Your version:${version}`,
+ )
+ err.code = rc
+ client.emit('error', err)
+ return
+ }
- client.handleAuth(packet, function (err, packet) {
- if (err) {
- client.emit('error', err)
- return
- }
+ client.handleAuth(packet, (err, packet2) => {
+ if (err) {
+ client.emit('error', err)
+ return
+ }
- if (rc === 24) {
- client.reconnecting = false
- client._sendPacket(packet)
- } else {
- const error = new Error('Connection refused: ' + errors[rc])
- err.code = rc
- client.emit('error', error)
- }
- })
+ if (rc === 24) {
+ client.reconnecting = false
+ client._sendPacket(packet2)
+ } else {
+ const error = new Error(`Connection refused: ${errors[rc]}`)
+ err.code = rc
+ client.emit('error', error)
+ }
+ })
}
-
-module.exports = handleAuth
\ No newline at end of file
+module.exports = handleAuth
diff --git a/lib/handlers/connack.js b/lib/handlers/connack.js
index 55c3d97af..4c52ae80f 100644
--- a/lib/handlers/connack.js
+++ b/lib/handlers/connack.js
@@ -2,42 +2,50 @@ const { errors } = require('./ack')
const TopicAliasSend = require('../topic-alias-send')
function handleConnack(client, packet) {
- client.log('_handleConnack')
- const options = client.options
- const version = options.protocolVersion
- const rc = version === 5 ? packet.reasonCode : packet.returnCode
+ client.log('_handleConnack')
+ const { options } = client
+ const version = options.protocolVersion
+ const rc = version === 5 ? packet.reasonCode : packet.returnCode
- clearTimeout(client.connackTimer)
- delete client.topicAliasSend
+ clearTimeout(client.connackTimer)
+ delete client.topicAliasSend
- if (packet.properties) {
- if (packet.properties.topicAliasMaximum) {
- if (packet.properties.topicAliasMaximum > 0xffff) {
- client.emit('error', new Error('topicAliasMaximum from broker is out of range'))
- return
- }
- if (packet.properties.topicAliasMaximum > 0) {
- client.topicAliasSend = new TopicAliasSend(packet.properties.topicAliasMaximum)
- }
- }
- if (packet.properties.serverKeepAlive && options.keepalive) {
- options.keepalive = packet.properties.serverKeepAlive
- client._shiftPingInterval()
- }
- if (packet.properties.maximumPacketSize) {
- if (!options.properties) { options.properties = {} }
- options.properties.maximumPacketSize = packet.properties.maximumPacketSize
- }
- }
+ if (packet.properties) {
+ if (packet.properties.topicAliasMaximum) {
+ if (packet.properties.topicAliasMaximum > 0xffff) {
+ client.emit(
+ 'error',
+ new Error('topicAliasMaximum from broker is out of range'),
+ )
+ return
+ }
+ if (packet.properties.topicAliasMaximum > 0) {
+ client.topicAliasSend = new TopicAliasSend(
+ packet.properties.topicAliasMaximum,
+ )
+ }
+ }
+ if (packet.properties.serverKeepAlive && options.keepalive) {
+ options.keepalive = packet.properties.serverKeepAlive
+ client._shiftPingInterval()
+ }
+ if (packet.properties.maximumPacketSize) {
+ if (!options.properties) {
+ options.properties = {}
+ }
+ options.properties.maximumPacketSize =
+ packet.properties.maximumPacketSize
+ }
+ }
- if (rc === 0) {
- client.reconnecting = false
- client._onConnect(packet)
- } else if (rc > 0) {
- const err = new Error('Connection refused: ' + errors[rc])
- err.code = rc
- client.emit('error', err)
- }
+ if (rc === 0) {
+ client.reconnecting = false
+ client._onConnect(packet)
+ } else if (rc > 0) {
+ const err = new Error(`Connection refused: ${errors[rc]}`)
+ err.code = rc
+ client.emit('error', err)
+ }
}
-module.exports = handleConnack
\ No newline at end of file
+module.exports = handleConnack
diff --git a/lib/handlers/index.js b/lib/handlers/index.js
index 318b5d5ef..51054dbbe 100644
--- a/lib/handlers/index.js
+++ b/lib/handlers/index.js
@@ -5,54 +5,62 @@ const handleAck = require('./ack')
const handlePubrel = require('./pubrel')
function handle(client, packet, done) {
- const options = client.options
+ const { options } = client
- if (options.protocolVersion === 5 && options.properties && options.properties.maximumPacketSize && options.properties.maximumPacketSize < packet.length) {
- client.emit('error', new Error('exceeding packets size ' + packet.cmd))
- client.end({ reasonCode: 149, properties: { reasonString: 'Maximum packet size was exceeded' } })
- return client
- }
- client.log('_handlePacket :: emitting packetreceive')
- client.emit('packetreceive', packet)
+ if (
+ options.protocolVersion === 5 &&
+ options.properties &&
+ options.properties.maximumPacketSize &&
+ options.properties.maximumPacketSize < packet.length
+ ) {
+ client.emit('error', new Error(`exceeding packets size ${packet.cmd}`))
+ client.end({
+ reasonCode: 149,
+ properties: { reasonString: 'Maximum packet size was exceeded' },
+ })
+ return client
+ }
+ client.log('_handlePacket :: emitting packetreceive')
+ client.emit('packetreceive', packet)
- switch (packet.cmd) {
- case 'publish':
- handlePublish(client, packet, done)
- break
- case 'puback':
- case 'pubrec':
- case 'pubcomp':
- case 'suback':
- case 'unsuback':
- handleAck(client, packet)
- done()
- break
- case 'pubrel':
- handlePubrel(client, packet, done)
- break
- case 'connack':
- handleConnack(client, packet)
- done()
- break
- case 'auth':
- handleAuth(client, packet)
- done()
- break
- case 'pingresp':
- // this will be checked in _checkPing client method every keepalive interval
- client.pingResp = true
- done()
- break
- case 'disconnect':
- client.emit('disconnect', packet)
- done()
- break
- default:
- // TODO: unknown packet received. Should we emit an error?
- client.log('_handlePacket :: unknown command')
- done()
- break
- }
+ switch (packet.cmd) {
+ case 'publish':
+ handlePublish(client, packet, done)
+ break
+ case 'puback':
+ case 'pubrec':
+ case 'pubcomp':
+ case 'suback':
+ case 'unsuback':
+ handleAck(client, packet)
+ done()
+ break
+ case 'pubrel':
+ handlePubrel(client, packet, done)
+ break
+ case 'connack':
+ handleConnack(client, packet)
+ done()
+ break
+ case 'auth':
+ handleAuth(client, packet)
+ done()
+ break
+ case 'pingresp':
+ // this will be checked in _checkPing client method every keepalive interval
+ client.pingResp = true
+ done()
+ break
+ case 'disconnect':
+ client.emit('disconnect', packet)
+ done()
+ break
+ default:
+ // TODO: unknown packet received. Should we emit an error?
+ client.log('_handlePacket :: unknown command')
+ done()
+ break
+ }
}
-module.exports = handle
\ No newline at end of file
+module.exports = handle
diff --git a/lib/handlers/publish.js b/lib/handlers/publish.js
index d2dbf3221..509fabcf7 100644
--- a/lib/handlers/publish.js
+++ b/lib/handlers/publish.js
@@ -25,96 +25,143 @@ const validReasonCodes = [0, 16, 128, 131, 135, 144, 145, 151, 153]
for now i just suppressed the warnings
*/
function handlePublish(client, packet, done) {
- client.log('handlePublish: packet %o', packet)
- done = typeof done !== 'undefined' ? done : client.nop
- let topic = packet.topic.toString()
- const message = packet.payload
- const qos = packet.qos
- const messageId = packet.messageId
- const options = client.options
- if (client.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 = client.topicAliasRecv.getTopicByAlias(alias)
- if (gotTopic) {
- topic = gotTopic
- client.log('handlePublish :: topic complemented by alias. topic: %s - alias: %d', topic, alias)
- } else {
- client.log('handlePublish :: unregistered topic alias. alias: %d', alias)
- client.emit('error', new Error('Received unregistered Topic Alias'))
- return
- }
- } else {
- client.log('handlePublish :: topic alias out of range. alias: %d', alias)
- client.emit('error', new Error('Received Topic Alias is out of range'))
- return
- }
- } else {
- if (client.topicAliasRecv.put(topic, alias)) {
- client.log('handlePublish :: registered topic: %s - alias: %d', topic, alias)
- } else {
- client.log('handlePublish :: topic alias out of range. alias: %d', alias)
- client.emit('error', new Error('Received Topic Alias is out of range'))
- return
- }
- }
- }
- }
- client.log('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 client.emit('error', error) }
- if (validReasonCodes.indexOf(code) === -1) { return client.emit('error', new Error('Wrong reason code for pubrec')) }
- if (code) {
- client._sendPacket({ cmd: 'pubrec', messageId, reasonCode: code }, done)
- } else {
- client.incomingStore.put(packet, function () {
- client._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 client.emit('error', error) }
- if (validReasonCodes.indexOf(code) === -1) { return client.emit('error', new Error('Wrong reason code for puback')) }
- if (!code) { client.emit('message', topic, message, packet) }
- client.handleMessage(packet, function (err) {
- if (err) {
- return done && done(err)
- }
- client._sendPacket({ cmd: 'puback', messageId, reasonCode: code }, done)
- })
- })
- break
- }
- case 0:
- // emit the message event
- client.emit('message', topic, message, packet)
- client.handleMessage(packet, done)
- break
- default:
- // do nothing
- client.log('handlePublish: unknown QoS. Doing nothing.')
- // log or throw an error about unknown qos
- break
- }
+ client.log('handlePublish: packet %o', packet)
+ done = typeof done !== 'undefined' ? done : client.noop
+ let topic = packet.topic.toString()
+ const message = packet.payload
+ const { qos } = packet
+ const { messageId } = packet
+ const { options } = client
+ if (client.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 =
+ client.topicAliasRecv.getTopicByAlias(alias)
+ if (gotTopic) {
+ topic = gotTopic
+ client.log(
+ 'handlePublish :: topic complemented by alias. topic: %s - alias: %d',
+ topic,
+ alias,
+ )
+ } else {
+ client.log(
+ 'handlePublish :: unregistered topic alias. alias: %d',
+ alias,
+ )
+ client.emit(
+ 'error',
+ new Error('Received unregistered Topic Alias'),
+ )
+ return
+ }
+ } else {
+ client.log(
+ 'handlePublish :: topic alias out of range. alias: %d',
+ alias,
+ )
+ client.emit(
+ 'error',
+ new Error('Received Topic Alias is out of range'),
+ )
+ return
+ }
+ } else if (client.topicAliasRecv.put(topic, alias)) {
+ client.log(
+ 'handlePublish :: registered topic: %s - alias: %d',
+ topic,
+ alias,
+ )
+ } else {
+ client.log(
+ 'handlePublish :: topic alias out of range. alias: %d',
+ alias,
+ )
+ client.emit(
+ 'error',
+ new Error('Received Topic Alias is out of range'),
+ )
+ return
+ }
+ }
+ }
+ client.log('handlePublish: qos %d', qos)
+ switch (qos) {
+ case 2: {
+ options.customHandleAcks(topic, message, packet, (error, code) => {
+ if (!(error instanceof Error)) {
+ code = error
+ error = null
+ }
+ if (error) {
+ return client.emit('error', error)
+ }
+ if (validReasonCodes.indexOf(code) === -1) {
+ return client.emit(
+ 'error',
+ new Error('Wrong reason code for pubrec'),
+ )
+ }
+ if (code) {
+ client._sendPacket(
+ { cmd: 'pubrec', messageId, reasonCode: code },
+ done,
+ )
+ } else {
+ client.incomingStore.put(packet, () => {
+ client._sendPacket({ cmd: 'pubrec', messageId }, done)
+ })
+ }
+ })
+ break
+ }
+ case 1: {
+ // emit the message event
+ options.customHandleAcks(topic, message, packet, (error, code) => {
+ if (!(error instanceof Error)) {
+ code = error
+ error = null
+ }
+ if (error) {
+ return client.emit('error', error)
+ }
+ if (validReasonCodes.indexOf(code) === -1) {
+ return client.emit(
+ 'error',
+ new Error('Wrong reason code for puback'),
+ )
+ }
+ if (!code) {
+ client.emit('message', topic, message, packet)
+ }
+ client.handleMessage(packet, (err) => {
+ if (err) {
+ return done && done(err)
+ }
+ client._sendPacket(
+ { cmd: 'puback', messageId, reasonCode: code },
+ done,
+ )
+ })
+ })
+ break
+ }
+ case 0:
+ // emit the message event
+ client.emit('message', topic, message, packet)
+ client.handleMessage(packet, done)
+ break
+ default:
+ // do nothing
+ client.log('handlePublish: unknown QoS. Doing nothing.')
+ // log or throw an error about unknown qos
+ break
+ }
}
-module.exports = handlePublish
\ No newline at end of file
+module.exports = handlePublish
diff --git a/lib/handlers/pubrel.js b/lib/handlers/pubrel.js
index fb2bc10ef..571ccbc79 100644
--- a/lib/handlers/pubrel.js
+++ b/lib/handlers/pubrel.js
@@ -1,26 +1,24 @@
-
-
function handlePubrel(client, packet, done) {
- client.log('handling pubrel packet')
- callback = typeof done !== 'undefined' ? done : client.nop
- const messageId = packet.messageId
+ client.log('handling pubrel packet')
+ const callback = typeof done !== 'undefined' ? done : client.noop
+ const { messageId } = packet
- const comp = { cmd: 'pubcomp', messageId }
+ const comp = { cmd: 'pubcomp', messageId }
- client.incomingStore.get(packet, function (err, pub) {
- if (!err) {
- client.emit('message', pub.topic, pub.payload, pub)
- client.handleMessage(pub, function (err) {
- if (err) {
- return callback(err)
- }
- client.incomingStore.del(pub, client.nop)
- client._sendPacket(comp, callback)
- })
- } else {
- client._sendPacket(comp, callback)
- }
- })
+ client.incomingStore.get(packet, (err, pub) => {
+ if (!err) {
+ client.emit('message', pub.topic, pub.payload, pub)
+ client.handleMessage(pub, (err2) => {
+ if (err2) {
+ return callback(err2)
+ }
+ client.incomingStore.del(pub, client.noop)
+ client._sendPacket(comp, callback)
+ })
+ } else {
+ client._sendPacket(comp, callback)
+ }
+ })
}
-module.exports = handlePubrel
\ No newline at end of file
+module.exports = handlePubrel
diff --git a/lib/is-browser.js b/lib/is-browser.js
index ac143732f..92c21a3c2 100644
--- a/lib/is-browser.js
+++ b/lib/is-browser.js
@@ -1,11 +1,11 @@
const legacyIsBrowser =
-(typeof process !== 'undefined' && process.title === 'browser') ||
-// eslint-disable-next-line camelcase
- typeof __webpack_require__ === 'function'
+ (typeof process !== 'undefined' && process.title === 'browser') ||
+ // eslint-disable-next-line camelcase
+ typeof __webpack_require__ === 'function'
const isBrowser =
- typeof window !== 'undefined' && typeof document !== 'undefined'
+ typeof window !== 'undefined' && typeof document !== 'undefined'
module.exports = {
- IS_BROWSER: isBrowser || legacyIsBrowser
+ IS_BROWSER: isBrowser || legacyIsBrowser,
}
diff --git a/lib/store.js b/lib/store.js
index 1412967eb..ce7608562 100644
--- a/lib/store.js
+++ b/lib/store.js
@@ -1,12 +1,11 @@
-'use strict'
-
/**
* Module dependencies
*/
-const Readable = require('readable-stream').Readable
+const { Readable } = require('readable-stream')
+
const streamsOpts = { objectMode: true }
const defaultStoreOptions = {
- clean: true
+ clean: true,
}
/**
@@ -16,109 +15,107 @@ const defaultStoreOptions = {
* @param {Object} [options] - store options
*/
class Store {
- constructor (options) {
- this.options = options || {}
-
- // Defaults
- this.options = { ...defaultStoreOptions, ...options }
-
- this._inflights = new Map()
- }
-
- /**
- * 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()
- }
-
- 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)
- }
- }
-
- stream.destroy = function () {
- if (destroyed) {
- return
- }
-
- const self = this
-
- destroyed = true
-
- setTimeout(function () {
- self.emit('close')
- }, 0)
- }
-
- 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.
- */
- 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
- */
- close (cb) {
- if (this.options.clean) {
- this._inflights = null
- }
- if (cb) {
- cb()
- }
- }
+ constructor(options) {
+ this.options = options || {}
+
+ // Defaults
+ this.options = { ...defaultStoreOptions, ...options }
+
+ this._inflights = new Map()
+ }
+
+ /**
+ * 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()
+ }
+
+ 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((value, key) => {
+ values.push(value)
+ })
+
+ stream._read = () => {
+ if (!destroyed && i < values.length) {
+ stream.push(values[i++])
+ } else {
+ stream.push(null)
+ }
+ }
+
+ stream.destroy = () => {
+ if (destroyed) {
+ return
+ }
+
+ destroyed = true
+
+ setTimeout(() => {
+ stream.emit('close')
+ }, 0)
+ }
+
+ 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.
+ */
+ 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
+ */
+ close(cb) {
+ if (this.options.clean) {
+ this._inflights = null
+ }
+ if (cb) {
+ cb()
+ }
+ }
}
module.exports = Store
diff --git a/lib/topic-alias-recv.js b/lib/topic-alias-recv.js
index e6412d68d..949cae680 100644
--- a/lib/topic-alias-recv.js
+++ b/lib/topic-alias-recv.js
@@ -1,46 +1,44 @@
-'use strict'
-
/**
* Topic Alias receiving manager
* This holds alias to topic map
* @param {Number} [max] - topic alias maximum entries
*/
class TopicAliasRecv {
- constructor (max) {
- this.aliasToTopic = {}
- this.max = 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) {
- if (alias === 0 || alias > this.max) {
- return false
- }
- this.aliasToTopic[alias] = topic
- this.length = Object.keys(this.aliasToTopic).length
- return true
- }
+ /**
+ * 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]
- }
+ /**
+ * 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 = {}
- }
+ /**
+ * Clear all entries
+ */
+ clear() {
+ this.aliasToTopic = {}
+ }
}
module.exports = TopicAliasRecv
diff --git a/lib/topic-alias-send.js b/lib/topic-alias-send.js
index f6efcc822..1904a2b35 100644
--- a/lib/topic-alias-send.js
+++ b/lib/topic-alias-send.js
@@ -1,10 +1,8 @@
-'use strict'
-
/**
* Module dependencies
*/
const LRUCache = require('lru-cache')
-const NumberAllocator = require('number-allocator').NumberAllocator
+const { NumberAllocator } = require('number-allocator')
/**
* Topic Alias sending manager
@@ -12,79 +10,79 @@ const NumberAllocator = require('number-allocator').NumberAllocator
* @param {Number} [max] - topic alias maximum entries
*/
class TopicAliasSend {
- constructor (max) {
- 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
- */
- 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
- }
+ /**
+ * 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
+ }
- /**
- * 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)
- }
+ /**
+ * 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)
+ }
- /**
- * 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
- }
+ /**
+ * 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
- }
+ /**
+ * 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]
- }
+ /**
+ * 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]
+ }
}
module.exports = TopicAliasSend
diff --git a/lib/unique-message-id-provider.js b/lib/unique-message-id-provider.js
index 882bdf7d7..c88be4bb9 100644
--- a/lib/unique-message-id-provider.js
+++ b/lib/unique-message-id-provider.js
@@ -1,67 +1,61 @@
-'use strict'
-
-const NumberAllocator = require('number-allocator').NumberAllocator
+const { NumberAllocator } = require('number-allocator')
/**
* UniqueMessageAllocator constructor
* @constructor
*/
class UniqueMessageIdProvider {
- constructor () {
- 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.
- */
- 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()
- }
+ constructor() {
+ 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
diff --git a/lib/validations.js b/lib/validations.js
index 452da7e99..e71dd52a2 100644
--- a/lib/validations.js
+++ b/lib/validations.js
@@ -1,5 +1,3 @@
-'use strict'
-
/**
* Validate a topic to see if it's valid or not.
* A topic is valid if it follow below rules:
@@ -9,44 +7,44 @@
* @param {String} topic - A topic
* @returns {Boolean} If the topic is valid, returns true. Otherwise, returns false.
*/
-function validateTopic (topic) {
- const parts = topic.split('/')
+function validateTopic(topic) {
+ const parts = topic.split('/')
- for (let i = 0; i < parts.length; i++) {
- if (parts[i] === '+') {
- continue
- }
+ for (let i = 0; i < parts.length; i++) {
+ if (parts[i] === '+') {
+ continue
+ }
- if (parts[i] === '#') {
- // for Rule #2
- return i === parts.length - 1
- }
+ if (parts[i] === '#') {
+ // for Rule #2
+ return i === parts.length - 1
+ }
- if (parts[i].indexOf('+') !== -1 || parts[i].indexOf('#') !== -1) {
- return false
- }
- }
+ if (parts[i].indexOf('+') !== -1 || parts[i].indexOf('#') !== -1) {
+ return false
+ }
+ }
- return true
+ return true
}
/**
* Validate an array of topics to see if any of them is valid or not
- * @param {Array} topics - Array of topics
+ * @param {Array} topics - Array of topics
* @returns {String} If the topics is valid, returns null. Otherwise, returns the invalid one
*/
-function validateTopics (topics) {
- if (topics.length === 0) {
- return 'empty_topic_list'
- }
- for (let i = 0; i < topics.length; i++) {
- if (!validateTopic(topics[i])) {
- return topics[i]
- }
- }
- return null
+function validateTopics(topics) {
+ if (topics.length === 0) {
+ return 'empty_topic_list'
+ }
+ for (let i = 0; i < topics.length; i++) {
+ if (!validateTopic(topics[i])) {
+ return topics[i]
+ }
+ }
+ return null
}
module.exports = {
- validateTopics
+ validateTopics,
}
diff --git a/package-lock.json b/package-lock.json
index 4d37e7172..bd4207460 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -42,6 +42,11 @@
"codecov": "^3.8.2",
"conventional-changelog-cli": "^3.0.0",
"end-of-stream": "^1.4.4",
+ "eslint": "^8.45.0",
+ "eslint-config-airbnb-base": "^15.0.0",
+ "eslint-config-prettier": "^8.8.0",
+ "eslint-plugin-import": "^2.27.5",
+ "eslint-plugin-prettier": "^5.0.0",
"global": "^4.4.0",
"mkdirp": "^3.0.1",
"mocha": "^10.2.0",
@@ -49,12 +54,12 @@
"mqtt-level-store": "^3.1.0",
"nyc": "^15.1.0",
"pre-commit": "^1.2.2",
+ "prettier": "^3.0.0",
"release-it": "^15.11.0",
"rimraf": "^5.0.1",
"should": "^13.2.3",
"sinon": "^15.2.0",
"snazzy": "^9.0.0",
- "standard": "^17.1.0",
"tape": "^5.6.4",
"terser": "^5.18.2",
"typescript": "^5.1.6"
@@ -459,18 +464,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/@eslint/eslintrc/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/@eslint/eslintrc/node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@@ -1024,6 +1017,44 @@
"node": ">=14"
}
},
+ "node_modules/@pkgr/utils": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
+ "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==",
+ "dev": true,
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "fast-glob": "^3.3.0",
+ "is-glob": "^4.0.3",
+ "open": "^9.1.0",
+ "picocolors": "^1.0.0",
+ "tslib": "^2.6.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts"
+ }
+ },
+ "node_modules/@pkgr/utils/node_modules/open": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
+ "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
+ "dev": true,
+ "dependencies": {
+ "default-browser": "^4.0.0",
+ "define-lazy-prop": "^3.0.0",
+ "is-inside-container": "^1.0.0",
+ "is-wsl": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@pnpm/config.env-replace": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz",
@@ -1980,19 +2011,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/array.prototype.tosorted": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz",
- "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "es-shim-unscopables": "^1.0.0",
- "get-intrinsic": "^1.1.3"
- }
- },
"node_modules/arrify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
@@ -2798,42 +2816,6 @@
"integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
"dev": true
},
- "node_modules/builtins": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
- "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
- "dev": true,
- "dependencies": {
- "semver": "^7.0.0"
- }
- },
- "node_modules/builtins/node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/builtins/node_modules/semver": {
- "version": "7.5.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
- "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
- "dev": true,
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
"node_modules/bundle-name": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
@@ -3397,6 +3379,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/confusing-browser-globals": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
+ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==",
+ "dev": true
+ },
"node_modules/console-browserify": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
@@ -4208,14 +4196,14 @@
}
},
"node_modules/degenerator": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-4.0.3.tgz",
- "integrity": "sha512-2wY8vmCfxrQpe2PKGYdiWRre5HQRwsAXbAAWRbC+z2b80MEpnWc8A3a9k4TwqwN3Z/Fm3uhNm5vYUZIbMhyRxQ==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-4.0.4.tgz",
+ "integrity": "sha512-MTZdZsuNxSBL92rsjx3VFWe57OpRlikyLbcx2B5Dmdv6oScqpMrvpY7zHLMymrUxo3U5+suPUMsNgW/+SZB1lg==",
"dev": true,
"dependencies": {
- "ast-types": "^0.13.2",
- "escodegen": "^1.8.1",
- "esprima": "^4.0.0",
+ "ast-types": "^0.13.4",
+ "escodegen": "^1.14.3",
+ "esprima": "^4.0.1",
"vm2": "^3.9.19"
},
"engines": {
@@ -4928,17 +4916,17 @@
}
},
"node_modules/escodegen/node_modules/optionator": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
- "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+ "integrity": "sha512-oCOQ8AIC2ciLy/sE2ehafRBleBgDLvzGhBRRev87sP7ovnbvQfqpc3XFI0DhHey2OfVoNV91W+GPC6B3540/5Q==",
"dev": true,
"dependencies": {
"deep-is": "~0.1.3",
- "fast-levenshtein": "~2.0.6",
+ "fast-levenshtein": "~2.0.4",
"levn": "~0.3.0",
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2",
- "word-wrap": "~1.2.3"
+ "wordwrap": "~1.0.0"
},
"engines": {
"node": ">= 0.8.0"
@@ -4976,9 +4964,9 @@
}
},
"node_modules/eslint": {
- "version": "8.44.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz",
- "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==",
+ "version": "8.45.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz",
+ "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
@@ -5006,7 +4994,6 @@
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
- "import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
@@ -5018,7 +5005,6 @@
"natural-compare": "^1.4.0",
"optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
- "strip-json-comments": "^3.1.0",
"text-table": "^0.2.0"
},
"bin": {
@@ -5031,57 +5017,35 @@
"url": "https://opencollective.com/eslint"
}
},
- "node_modules/eslint-config-standard": {
- "version": "17.1.0",
- "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
- "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==",
+ "node_modules/eslint-config-airbnb-base": {
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz",
+ "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==",
"dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
+ "dependencies": {
+ "confusing-browser-globals": "^1.0.10",
+ "object.assign": "^4.1.2",
+ "object.entries": "^1.1.5",
+ "semver": "^6.3.0"
+ },
"engines": {
- "node": ">=12.0.0"
+ "node": "^10.12.0 || >=12.0.0"
},
"peerDependencies": {
- "eslint": "^8.0.1",
- "eslint-plugin-import": "^2.25.2",
- "eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
- "eslint-plugin-promise": "^6.0.0"
+ "eslint": "^7.32.0 || ^8.2.0",
+ "eslint-plugin-import": "^2.25.2"
}
},
- "node_modules/eslint-config-standard-jsx": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-11.0.0.tgz",
- "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==",
+ "node_modules/eslint-config-prettier": {
+ "version": "8.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz",
+ "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==",
"dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
"peerDependencies": {
- "eslint": "^8.8.0",
- "eslint-plugin-react": "^7.28.0"
+ "eslint": ">=7.0.0"
}
},
"node_modules/eslint-import-resolver-node": {
@@ -5104,23 +5068,6 @@
"ms": "^2.1.1"
}
},
- "node_modules/eslint-import-resolver-node/node_modules/resolve": {
- "version": "1.22.2",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
- "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
- "dev": true,
- "dependencies": {
- "is-core-module": "^2.11.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/eslint-module-utils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
@@ -5147,49 +5094,6 @@
"ms": "^2.1.1"
}
},
- "node_modules/eslint-plugin-es": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz",
- "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==",
- "dev": true,
- "dependencies": {
- "eslint-utils": "^2.0.0",
- "regexpp": "^3.0.0"
- },
- "engines": {
- "node": ">=8.10.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/mysticatea"
- },
- "peerDependencies": {
- "eslint": ">=4.19.1"
- }
- },
- "node_modules/eslint-plugin-es/node_modules/eslint-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
- "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
- "dev": true,
- "dependencies": {
- "eslint-visitor-keys": "^1.1.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/mysticatea"
- }
- },
- "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
- "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/eslint-plugin-import": {
"version": "2.27.5",
"resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
@@ -5236,243 +5140,53 @@
"dependencies": {
"esutils": "^2.0.2"
},
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/eslint-plugin-import/node_modules/resolve": {
- "version": "1.22.2",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
- "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
- "dev": true,
- "dependencies": {
- "is-core-module": "^2.11.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/eslint-plugin-n": {
- "version": "15.7.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz",
- "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==",
- "dev": true,
- "dependencies": {
- "builtins": "^5.0.1",
- "eslint-plugin-es": "^4.1.0",
- "eslint-utils": "^3.0.0",
- "ignore": "^5.1.1",
- "is-core-module": "^2.11.0",
- "minimatch": "^3.1.2",
- "resolve": "^1.22.1",
- "semver": "^7.3.8"
- },
- "engines": {
- "node": ">=12.22.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/mysticatea"
- },
- "peerDependencies": {
- "eslint": ">=7.0.0"
- }
- },
- "node_modules/eslint-plugin-n/node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/eslint-plugin-n/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/eslint-plugin-n/node_modules/resolve": {
- "version": "1.22.2",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
- "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
- "dev": true,
- "dependencies": {
- "is-core-module": "^2.11.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/eslint-plugin-n/node_modules/semver": {
- "version": "7.5.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
- "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
- "dev": true,
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/eslint-plugin-promise": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz",
- "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==",
- "dev": true,
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "peerDependencies": {
- "eslint": "^7.0.0 || ^8.0.0"
- }
- },
- "node_modules/eslint-plugin-react": {
- "version": "7.32.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz",
- "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==",
- "dev": true,
- "dependencies": {
- "array-includes": "^3.1.6",
- "array.prototype.flatmap": "^1.3.1",
- "array.prototype.tosorted": "^1.1.1",
- "doctrine": "^2.1.0",
- "estraverse": "^5.3.0",
- "jsx-ast-utils": "^2.4.1 || ^3.0.0",
- "minimatch": "^3.1.2",
- "object.entries": "^1.1.6",
- "object.fromentries": "^2.0.6",
- "object.hasown": "^1.1.2",
- "object.values": "^1.1.6",
- "prop-types": "^15.8.1",
- "resolve": "^2.0.0-next.4",
- "semver": "^6.3.0",
- "string.prototype.matchall": "^4.0.8"
- },
- "engines": {
- "node": ">=4"
- },
- "peerDependencies": {
- "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
- }
- },
- "node_modules/eslint-plugin-react/node_modules/doctrine": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
- "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
- "dev": true,
- "dependencies": {
- "esutils": "^2.0.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/eslint-plugin-react/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/eslint-plugin-react/node_modules/resolve": {
- "version": "2.0.0-next.4",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
- "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
- "dev": true,
- "dependencies": {
- "is-core-module": "^2.9.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/eslint-scope": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
- "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==",
- "dev": true,
- "dependencies": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
- },
- "engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/eslint"
+ "engines": {
+ "node": ">=0.10.0"
}
},
- "node_modules/eslint-utils": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
- "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "node_modules/eslint-plugin-prettier": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz",
+ "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==",
"dev": true,
"dependencies": {
- "eslint-visitor-keys": "^2.0.0"
+ "prettier-linter-helpers": "^1.0.0",
+ "synckit": "^0.8.5"
},
"engines": {
- "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+ "node": "^14.18.0 || >=16.0.0"
},
"funding": {
- "url": "https://github.com/sponsors/mysticatea"
+ "url": "https://opencollective.com/prettier"
},
"peerDependencies": {
- "eslint": ">=5"
+ "@types/eslint": ">=8.0.0",
+ "eslint": ">=8.0.0",
+ "prettier": ">=3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/eslint": {
+ "optional": true
+ },
+ "eslint-config-prettier": {
+ "optional": true
+ }
}
},
- "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
- "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "node_modules/eslint-scope": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz",
+ "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==",
"dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
"engines": {
- "node": ">=10"
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-visitor-keys": {
@@ -5584,18 +5298,6 @@
"node": ">=8"
}
},
- "node_modules/eslint/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/eslint/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -5621,9 +5323,9 @@
}
},
"node_modules/espree": {
- "version": "9.6.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz",
- "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==",
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
"dependencies": {
"acorn": "^8.9.0",
@@ -5872,10 +5574,16 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true
+ },
"node_modules/fast-glob": {
- "version": "3.2.12",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
- "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
+ "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -6390,18 +6098,6 @@
"xtend": "~4.0.1"
}
},
- "node_modules/get-stdin": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
- "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -8424,21 +8120,6 @@
"node": "*"
}
},
- "node_modules/jsx-ast-utils": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.4.tgz",
- "integrity": "sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==",
- "dev": true,
- "dependencies": {
- "array-includes": "^3.1.6",
- "array.prototype.flat": "^1.3.1",
- "object.assign": "^4.1.4",
- "object.values": "^1.1.6"
- },
- "engines": {
- "node": ">=4.0"
- }
- },
"node_modules/just-extend": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz",
@@ -9059,18 +8740,6 @@
"integrity": "sha512-6DzMHJcjbQX/UPHc1rRCBfKlLwDkvuGZ715cIR36wSdYqWXFT35uLXq5P/2orl3tz+t+VOVPxw4yPinQlUDGDQ==",
"dev": true
},
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
"node_modules/loupe": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz",
@@ -9525,9 +9194,9 @@
"dev": true
},
"node_modules/minimatch": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.1.tgz",
- "integrity": "sha512-reLxBcKUPNBnc/sVtAbxgRVFSegoGeLaSjmphNhcwcolhYLRgtJscn5mRl6YRZNQv40Y7P6JM2YhSIsbL9OB5A==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -10630,36 +10299,6 @@
"node": ">= 0.4"
}
},
- "node_modules/object.fromentries": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
- "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/object.hasown": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz",
- "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==",
- "dev": true,
- "dependencies": {
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/object.values": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
@@ -11021,9 +10660,9 @@
}
},
"node_modules/pac-proxy-agent": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-6.0.3.tgz",
- "integrity": "sha512-5Hr1KgPDoc21Vn3rsXBirwwDnF/iac1jN/zkpsOYruyT+ZgsUhUOgVwq3v9+ukjZd/yGm/0nzO1fDfl7rkGoHQ==",
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-6.0.4.tgz",
+ "integrity": "sha512-FbJYeusBOZNe6bmrC2/+r/HljwExryon16lNKEU82gWiwIPMCEktUPSEAcTkO9K3jd/YPGuX/azZel1ltmo6nQ==",
"dev": true,
"dependencies": {
"agent-base": "^7.0.2",
@@ -11077,13 +10716,13 @@
}
},
"node_modules/pac-resolver": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-6.0.1.tgz",
- "integrity": "sha512-dg497MhVT7jZegPRuOScQ/z0aV/5WR0gTdRu1md+Irs9J9o+ls5jIuxjo1WfaTG+eQQkxyn5HMGvWK+w7EIBkQ==",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-6.0.2.tgz",
+ "integrity": "sha512-EQpuJ2ifOjpZY5sg1Q1ZeAxvtLwR7Mj3RgY8cysPGbsRu3RBXyJFWxnMus9PScjxya/0LzvVDxNh/gl0eXBU4w==",
"dev": true,
"dependencies": {
- "degenerator": "^4.0.1",
- "ip": "^1.1.5",
+ "degenerator": "^4.0.4",
+ "ip": "^1.1.8",
"netmask": "^2.0.2"
},
"engines": {
@@ -11400,123 +11039,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/pkg-conf": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz",
- "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==",
- "dev": true,
- "dependencies": {
- "find-up": "^3.0.0",
- "load-json-file": "^5.2.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/pkg-conf/node_modules/find-up": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
- "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
- "dev": true,
- "dependencies": {
- "locate-path": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/pkg-conf/node_modules/load-json-file": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz",
- "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==",
- "dev": true,
- "dependencies": {
- "graceful-fs": "^4.1.15",
- "parse-json": "^4.0.0",
- "pify": "^4.0.1",
- "strip-bom": "^3.0.0",
- "type-fest": "^0.3.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/pkg-conf/node_modules/locate-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
- "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
- "dev": true,
- "dependencies": {
- "p-locate": "^3.0.0",
- "path-exists": "^3.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/pkg-conf/node_modules/p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pkg-conf/node_modules/p-locate": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
- "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
- "dev": true,
- "dependencies": {
- "p-limit": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/pkg-conf/node_modules/path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/pkg-conf/node_modules/pify": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
- "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/pkg-conf/node_modules/strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/pkg-conf/node_modules/type-fest": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz",
- "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
@@ -11688,6 +11210,33 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/prettier": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
+ "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -11733,17 +11282,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
- "dependencies": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
"node_modules/proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@@ -12081,12 +11619,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
- },
"node_modules/read-only-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
@@ -12345,18 +11877,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/regexpp": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
- "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/mysticatea"
- }
- },
"node_modules/registry-auth-token": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz",
@@ -12557,17 +12077,20 @@
"dev": true
},
"node_modules/resolve": {
- "version": "1.22.0",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
- "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+ "version": "1.22.2",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
+ "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
"dev": true,
"dependencies": {
- "is-core-module": "^2.8.1",
+ "is-core-module": "^2.11.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-alpn": {
@@ -13661,95 +13184,29 @@
"node_modules/split": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
- "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
- "dev": true,
- "dependencies": {
- "through": "2"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/split2": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
- "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
- "engines": {
- "node": ">= 10.x"
- }
- },
- "node_modules/sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
- "dev": true
- },
- "node_modules/standard": {
- "version": "17.1.0",
- "resolved": "https://registry.npmjs.org/standard/-/standard-17.1.0.tgz",
- "integrity": "sha512-jaDqlNSzLtWYW4lvQmU0EnxWMUGQiwHasZl5ZEIwx3S/ijZDjZOzs1y1QqKwKs5vqnFpGtizo4NOYX2s0Voq/g==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
+ "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
+ "dev": true,
"dependencies": {
- "eslint": "^8.41.0",
- "eslint-config-standard": "17.1.0",
- "eslint-config-standard-jsx": "^11.0.0",
- "eslint-plugin-import": "^2.27.5",
- "eslint-plugin-n": "^15.7.0",
- "eslint-plugin-promise": "^6.1.1",
- "eslint-plugin-react": "^7.32.2",
- "standard-engine": "^15.0.0",
- "version-guard": "^1.1.1"
- },
- "bin": {
- "standard": "bin/cmd.cjs"
+ "through": "2"
},
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": "*"
}
},
- "node_modules/standard-engine": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.1.0.tgz",
- "integrity": "sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "dependencies": {
- "get-stdin": "^8.0.0",
- "minimist": "^1.2.6",
- "pkg-conf": "^3.1.0",
- "xdg-basedir": "^4.0.0"
- },
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"engines": {
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ "node": ">= 10.x"
}
},
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
"node_modules/standard-json": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/standard-json/-/standard-json-1.1.0.tgz",
@@ -14056,25 +13513,6 @@
"node": ">=8"
}
},
- "node_modules/string.prototype.matchall": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
- "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "get-intrinsic": "^1.1.3",
- "has-symbols": "^1.0.3",
- "internal-slot": "^1.0.3",
- "regexp.prototype.flags": "^1.4.3",
- "side-channel": "^1.0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/string.prototype.trim": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
@@ -14223,6 +13661,22 @@
"node": ">= 0.4"
}
},
+ "node_modules/synckit": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
+ "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==",
+ "dev": true,
+ "dependencies": {
+ "@pkgr/utils": "^2.3.1",
+ "tslib": "^2.5.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unts"
+ }
+ },
"node_modules/syntax-error": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz",
@@ -15031,15 +14485,6 @@
"node": ">= 0.8"
}
},
- "node_modules/version-guard": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.1.tgz",
- "integrity": "sha512-MGQLX89UxmYHgDvcXyjBI0cbmoW+t/dANDppNPrno64rYr8nH4SHSuElQuSYdXGEs0mUzdQe1BY+FhVPNsAmJQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.48"
- }
- },
"node_modules/vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@@ -15050,6 +14495,7 @@
"version": "3.9.19",
"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.19.tgz",
"integrity": "sha512-J637XF0DHDMV57R6JyVsTak7nIL8gy5KH4r1HiwWLf/4GBbb5MKL5y7LpmF4A8E2nR6XmzpmMFQ7V7ppPTmUQg==",
+ "deprecated": "The library contains critical security issues and should not be used for production! The maintenance of the project has been discontinued. Consider migrating your code to isolated-vm.",
"dev": true,
"dependencies": {
"acorn": "^8.7.0",
@@ -15063,9 +14509,9 @@
}
},
"node_modules/vm2/node_modules/acorn": {
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
- "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
+ "version": "8.10.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
+ "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@@ -15390,15 +14836,6 @@
"node": ">=6"
}
},
- "node_modules/word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
@@ -15543,15 +14980,6 @@
}
}
},
- "node_modules/xdg-basedir": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
- "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
@@ -15976,15 +15404,6 @@
"type-fest": "^0.20.2"
}
},
- "minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
"type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
@@ -16412,6 +15831,34 @@
"dev": true,
"optional": true
},
+ "@pkgr/utils": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz",
+ "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^7.0.3",
+ "fast-glob": "^3.3.0",
+ "is-glob": "^4.0.3",
+ "open": "^9.1.0",
+ "picocolors": "^1.0.0",
+ "tslib": "^2.6.0"
+ },
+ "dependencies": {
+ "open": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
+ "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
+ "dev": true,
+ "requires": {
+ "default-browser": "^4.0.0",
+ "define-lazy-prop": "^3.0.0",
+ "is-inside-container": "^1.0.0",
+ "is-wsl": "^2.2.0"
+ }
+ }
+ }
+ },
"@pnpm/config.env-replace": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz",
@@ -17208,19 +16655,6 @@
"is-string": "^1.0.7"
}
},
- "array.prototype.tosorted": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz",
- "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "es-shim-unscopables": "^1.0.0",
- "get-intrinsic": "^1.1.3"
- }
- },
"arrify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
@@ -17912,35 +17346,6 @@
"integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
"dev": true
},
- "builtins": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
- "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==",
- "dev": true,
- "requires": {
- "semver": "^7.0.0"
- },
- "dependencies": {
- "lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "requires": {
- "yallist": "^4.0.0"
- }
- },
- "semver": {
- "version": "7.5.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
- "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
- "dev": true,
- "requires": {
- "lru-cache": "^6.0.0"
- }
- }
- }
- },
"bundle-name": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
@@ -18390,6 +17795,12 @@
}
}
},
+ "confusing-browser-globals": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
+ "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==",
+ "dev": true
+ },
"console-browserify": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz",
@@ -18998,14 +18409,14 @@
"dev": true
},
"degenerator": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-4.0.3.tgz",
- "integrity": "sha512-2wY8vmCfxrQpe2PKGYdiWRre5HQRwsAXbAAWRbC+z2b80MEpnWc8A3a9k4TwqwN3Z/Fm3uhNm5vYUZIbMhyRxQ==",
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-4.0.4.tgz",
+ "integrity": "sha512-MTZdZsuNxSBL92rsjx3VFWe57OpRlikyLbcx2B5Dmdv6oScqpMrvpY7zHLMymrUxo3U5+suPUMsNgW/+SZB1lg==",
"dev": true,
"requires": {
- "ast-types": "^0.13.2",
- "escodegen": "^1.8.1",
- "esprima": "^4.0.0",
+ "ast-types": "^0.13.4",
+ "escodegen": "^1.14.3",
+ "esprima": "^4.0.1",
"vm2": "^3.9.19"
}
},
@@ -19598,17 +19009,17 @@
}
},
"optionator": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
- "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+ "integrity": "sha512-oCOQ8AIC2ciLy/sE2ehafRBleBgDLvzGhBRRev87sP7ovnbvQfqpc3XFI0DhHey2OfVoNV91W+GPC6B3540/5Q==",
"dev": true,
"requires": {
"deep-is": "~0.1.3",
- "fast-levenshtein": "~2.0.6",
+ "fast-levenshtein": "~2.0.4",
"levn": "~0.3.0",
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2",
- "word-wrap": "~1.2.3"
+ "wordwrap": "~1.0.0"
}
},
"prelude-ls": {
@@ -19636,9 +19047,9 @@
}
},
"eslint": {
- "version": "8.44.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz",
- "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==",
+ "version": "8.45.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz",
+ "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.2.0",
@@ -19666,7 +19077,6 @@
"globals": "^13.19.0",
"graphemer": "^1.4.0",
"ignore": "^5.2.0",
- "import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4",
"is-glob": "^4.0.0",
"is-path-inside": "^3.0.3",
@@ -19678,7 +19088,6 @@
"natural-compare": "^1.4.0",
"optionator": "^0.9.3",
"strip-ansi": "^6.0.1",
- "strip-json-comments": "^3.1.0",
"text-table": "^0.2.0"
},
"dependencies": {
@@ -19746,15 +19155,6 @@
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
- "minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -19772,17 +19172,22 @@
}
}
},
- "eslint-config-standard": {
- "version": "17.1.0",
- "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz",
- "integrity": "sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==",
+ "eslint-config-airbnb-base": {
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz",
+ "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==",
"dev": true,
- "requires": {}
+ "requires": {
+ "confusing-browser-globals": "^1.0.10",
+ "object.assign": "^4.1.2",
+ "object.entries": "^1.1.5",
+ "semver": "^6.3.0"
+ }
},
- "eslint-config-standard-jsx": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-11.0.0.tgz",
- "integrity": "sha512-+1EV/R0JxEK1L0NGolAr8Iktm3Rgotx3BKwgaX+eAuSX8D952LULKtjgZD3F+e6SvibONnhLwoTi9DPxN5LvvQ==",
+ "eslint-config-prettier": {
+ "version": "8.8.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz",
+ "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==",
"dev": true,
"requires": {}
},
@@ -19805,17 +19210,6 @@
"requires": {
"ms": "^2.1.1"
}
- },
- "resolve": {
- "version": "1.22.2",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
- "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
- "dev": true,
- "requires": {
- "is-core-module": "^2.11.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- }
}
}
},
@@ -19825,196 +19219,52 @@
"integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==",
"dev": true,
"requires": {
- "debug": "^3.2.7"
- },
- "dependencies": {
- "debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "requires": {
- "ms": "^2.1.1"
- }
- }
- }
- },
- "eslint-plugin-es": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-4.1.0.tgz",
- "integrity": "sha512-GILhQTnjYE2WorX5Jyi5i4dz5ALWxBIdQECVQavL6s7cI76IZTDWleTHkxz/QT3kvcs2QlGHvKLYsSlPOlPXnQ==",
- "dev": true,
- "requires": {
- "eslint-utils": "^2.0.0",
- "regexpp": "^3.0.0"
- },
- "dependencies": {
- "eslint-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
- "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
- "dev": true,
- "requires": {
- "eslint-visitor-keys": "^1.1.0"
- }
- },
- "eslint-visitor-keys": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
- "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
- "dev": true
- }
- }
- },
- "eslint-plugin-import": {
- "version": "2.27.5",
- "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
- "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==",
- "dev": true,
- "requires": {
- "array-includes": "^3.1.6",
- "array.prototype.flat": "^1.3.1",
- "array.prototype.flatmap": "^1.3.1",
- "debug": "^3.2.7",
- "doctrine": "^2.1.0",
- "eslint-import-resolver-node": "^0.3.7",
- "eslint-module-utils": "^2.7.4",
- "has": "^1.0.3",
- "is-core-module": "^2.11.0",
- "is-glob": "^4.0.3",
- "minimatch": "^3.1.2",
- "object.values": "^1.1.6",
- "resolve": "^1.22.1",
- "semver": "^6.3.0",
- "tsconfig-paths": "^3.14.1"
- },
- "dependencies": {
- "debug": {
- "version": "3.2.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
- "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
- "dev": true,
- "requires": {
- "ms": "^2.1.1"
- }
- },
- "doctrine": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
- "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
- "dev": true,
- "requires": {
- "esutils": "^2.0.2"
- }
- },
- "minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "resolve": {
- "version": "1.22.2",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
- "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
- "dev": true,
- "requires": {
- "is-core-module": "^2.11.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- }
- }
- }
- },
- "eslint-plugin-n": {
- "version": "15.7.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-15.7.0.tgz",
- "integrity": "sha512-jDex9s7D/Qial8AGVIHq4W7NswpUD5DPDL2RH8Lzd9EloWUuvUkHfv4FRLMipH5q2UtyurorBkPeNi1wVWNh3Q==",
- "dev": true,
- "requires": {
- "builtins": "^5.0.1",
- "eslint-plugin-es": "^4.1.0",
- "eslint-utils": "^3.0.0",
- "ignore": "^5.1.1",
- "is-core-module": "^2.11.0",
- "minimatch": "^3.1.2",
- "resolve": "^1.22.1",
- "semver": "^7.3.8"
+ "debug": "^3.2.7"
},
"dependencies": {
- "lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "requires": {
- "yallist": "^4.0.0"
- }
- },
- "minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "resolve": {
- "version": "1.22.2",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
- "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
- "dev": true,
- "requires": {
- "is-core-module": "^2.11.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- }
- },
- "semver": {
- "version": "7.5.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.3.tgz",
- "integrity": "sha512-QBlUtyVk/5EeHbi7X0fw6liDZc7BBmEaSYn01fMU1OUYbf6GPsbTtd8WmnqbI20SeycoHSeiybkE/q1Q+qlThQ==",
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dev": true,
"requires": {
- "lru-cache": "^6.0.0"
+ "ms": "^2.1.1"
}
}
}
},
- "eslint-plugin-promise": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-6.1.1.tgz",
- "integrity": "sha512-tjqWDwVZQo7UIPMeDReOpUgHCmCiH+ePnVT+5zVapL0uuHnegBUs2smM13CzOs2Xb5+MHMRFTs9v24yjba4Oig==",
- "dev": true,
- "requires": {}
- },
- "eslint-plugin-react": {
- "version": "7.32.2",
- "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz",
- "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==",
+ "eslint-plugin-import": {
+ "version": "2.27.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz",
+ "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==",
"dev": true,
"requires": {
"array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
"array.prototype.flatmap": "^1.3.1",
- "array.prototype.tosorted": "^1.1.1",
+ "debug": "^3.2.7",
"doctrine": "^2.1.0",
- "estraverse": "^5.3.0",
- "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "eslint-import-resolver-node": "^0.3.7",
+ "eslint-module-utils": "^2.7.4",
+ "has": "^1.0.3",
+ "is-core-module": "^2.11.0",
+ "is-glob": "^4.0.3",
"minimatch": "^3.1.2",
- "object.entries": "^1.1.6",
- "object.fromentries": "^2.0.6",
- "object.hasown": "^1.1.2",
"object.values": "^1.1.6",
- "prop-types": "^15.8.1",
- "resolve": "^2.0.0-next.4",
+ "resolve": "^1.22.1",
"semver": "^6.3.0",
- "string.prototype.matchall": "^4.0.8"
+ "tsconfig-paths": "^3.14.1"
},
"dependencies": {
+ "debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
"doctrine": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -20023,54 +19273,27 @@
"requires": {
"esutils": "^2.0.2"
}
- },
- "minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "resolve": {
- "version": "2.0.0-next.4",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
- "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
- "dev": true,
- "requires": {
- "is-core-module": "^2.9.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- }
}
}
},
- "eslint-scope": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
- "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==",
+ "eslint-plugin-prettier": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz",
+ "integrity": "sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==",
"dev": true,
"requires": {
- "esrecurse": "^4.3.0",
- "estraverse": "^5.2.0"
+ "prettier-linter-helpers": "^1.0.0",
+ "synckit": "^0.8.5"
}
},
- "eslint-utils": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
- "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "eslint-scope": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz",
+ "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==",
"dev": true,
"requires": {
- "eslint-visitor-keys": "^2.0.0"
- },
- "dependencies": {
- "eslint-visitor-keys": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
- "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
- "dev": true
- }
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
}
},
"eslint-visitor-keys": {
@@ -20080,9 +19303,9 @@
"dev": true
},
"espree": {
- "version": "9.6.0",
- "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz",
- "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==",
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
"dev": true,
"requires": {
"acorn": "^8.9.0",
@@ -20276,10 +19499,16 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
+ "fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "dev": true
+ },
"fast-glob": {
- "version": "3.2.12",
- "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
- "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
+ "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
@@ -20684,12 +19913,6 @@
}
}
},
- "get-stdin": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
- "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
- "dev": true
- },
"get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -22250,18 +21473,6 @@
"through": ">=2.2.7 <3"
}
},
- "jsx-ast-utils": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.4.tgz",
- "integrity": "sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==",
- "dev": true,
- "requires": {
- "array-includes": "^3.1.6",
- "array.prototype.flat": "^1.3.1",
- "object.assign": "^4.1.4",
- "object.values": "^1.1.6"
- }
- },
"just-extend": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz",
@@ -22781,15 +21992,6 @@
"integrity": "sha512-6DzMHJcjbQX/UPHc1rRCBfKlLwDkvuGZ715cIR36wSdYqWXFT35uLXq5P/2orl3tz+t+VOVPxw4yPinQlUDGDQ==",
"dev": true
},
- "loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
- "requires": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- }
- },
"loupe": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz",
@@ -23129,9 +22331,9 @@
"dev": true
},
"minimatch": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.1.tgz",
- "integrity": "sha512-reLxBcKUPNBnc/sVtAbxgRVFSegoGeLaSjmphNhcwcolhYLRgtJscn5mRl6YRZNQv40Y7P6JM2YhSIsbL9OB5A==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
@@ -24026,27 +23228,6 @@
"es-abstract": "^1.20.4"
}
},
- "object.fromentries": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
- "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- }
- },
- "object.hasown": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz",
- "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==",
- "dev": true,
- "requires": {
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4"
- }
- },
"object.values": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
@@ -24296,9 +23477,9 @@
"dev": true
},
"pac-proxy-agent": {
- "version": "6.0.3",
- "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-6.0.3.tgz",
- "integrity": "sha512-5Hr1KgPDoc21Vn3rsXBirwwDnF/iac1jN/zkpsOYruyT+ZgsUhUOgVwq3v9+ukjZd/yGm/0nzO1fDfl7rkGoHQ==",
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-6.0.4.tgz",
+ "integrity": "sha512-FbJYeusBOZNe6bmrC2/+r/HljwExryon16lNKEU82gWiwIPMCEktUPSEAcTkO9K3jd/YPGuX/azZel1ltmo6nQ==",
"dev": true,
"requires": {
"agent-base": "^7.0.2",
@@ -24342,13 +23523,13 @@
}
},
"pac-resolver": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-6.0.1.tgz",
- "integrity": "sha512-dg497MhVT7jZegPRuOScQ/z0aV/5WR0gTdRu1md+Irs9J9o+ls5jIuxjo1WfaTG+eQQkxyn5HMGvWK+w7EIBkQ==",
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-6.0.2.tgz",
+ "integrity": "sha512-EQpuJ2ifOjpZY5sg1Q1ZeAxvtLwR7Mj3RgY8cysPGbsRu3RBXyJFWxnMus9PScjxya/0LzvVDxNh/gl0eXBU4w==",
"dev": true,
"requires": {
- "degenerator": "^4.0.1",
- "ip": "^1.1.5",
+ "degenerator": "^4.0.4",
+ "ip": "^1.1.8",
"netmask": "^2.0.2"
}
},
@@ -24599,92 +23780,6 @@
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true
},
- "pkg-conf": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-3.1.0.tgz",
- "integrity": "sha512-m0OTbR/5VPNPqO1ph6Fqbj7Hv6QU7gR/tQW40ZqrL1rjgCU85W6C1bJn0BItuJqnR98PWzw7Z8hHeChD1WrgdQ==",
- "dev": true,
- "requires": {
- "find-up": "^3.0.0",
- "load-json-file": "^5.2.0"
- },
- "dependencies": {
- "find-up": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
- "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
- "dev": true,
- "requires": {
- "locate-path": "^3.0.0"
- }
- },
- "load-json-file": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-5.3.0.tgz",
- "integrity": "sha512-cJGP40Jc/VXUsp8/OrnyKyTZ1y6v/dphm3bioS+RrKXjK2BB6wHUd6JptZEFDGgGahMT+InnZO5i1Ei9mpC8Bw==",
- "dev": true,
- "requires": {
- "graceful-fs": "^4.1.15",
- "parse-json": "^4.0.0",
- "pify": "^4.0.1",
- "strip-bom": "^3.0.0",
- "type-fest": "^0.3.0"
- }
- },
- "locate-path": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
- "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
- "dev": true,
- "requires": {
- "p-locate": "^3.0.0",
- "path-exists": "^3.0.0"
- }
- },
- "p-limit": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
- "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
- "requires": {
- "p-try": "^2.0.0"
- }
- },
- "p-locate": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
- "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
- "dev": true,
- "requires": {
- "p-limit": "^2.0.0"
- }
- },
- "path-exists": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
- "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==",
- "dev": true
- },
- "pify": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
- "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
- "dev": true
- },
- "strip-bom": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
- "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
- "dev": true
- },
- "type-fest": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz",
- "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==",
- "dev": true
- }
- }
- },
"pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
@@ -24820,6 +23915,21 @@
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true
},
+ "prettier": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
+ "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
+ "dev": true
+ },
+ "prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "dev": true,
+ "requires": {
+ "fast-diff": "^1.1.2"
+ }
+ },
"process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -24853,17 +23963,6 @@
"iterate-value": "^1.0.2"
}
},
- "prop-types": {
- "version": "15.8.1",
- "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
- "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
- "requires": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.13.1"
- }
- },
"proto-list": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
@@ -25144,12 +24243,6 @@
}
}
},
- "react-is": {
- "version": "16.13.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
- },
"read-only-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
@@ -25357,12 +24450,6 @@
"functions-have-names": "^1.2.3"
}
},
- "regexpp": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
- "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
- "dev": true
- },
"registry-auth-token": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz",
@@ -25510,12 +24597,12 @@
"dev": true
},
"resolve": {
- "version": "1.22.0",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz",
- "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==",
+ "version": "1.22.2",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
+ "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
"dev": true,
"requires": {
- "is-core-module": "^2.8.1",
+ "is-core-module": "^2.11.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
}
@@ -26413,35 +25500,6 @@
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
},
- "standard": {
- "version": "17.1.0",
- "resolved": "https://registry.npmjs.org/standard/-/standard-17.1.0.tgz",
- "integrity": "sha512-jaDqlNSzLtWYW4lvQmU0EnxWMUGQiwHasZl5ZEIwx3S/ijZDjZOzs1y1QqKwKs5vqnFpGtizo4NOYX2s0Voq/g==",
- "dev": true,
- "requires": {
- "eslint": "^8.41.0",
- "eslint-config-standard": "17.1.0",
- "eslint-config-standard-jsx": "^11.0.0",
- "eslint-plugin-import": "^2.27.5",
- "eslint-plugin-n": "^15.7.0",
- "eslint-plugin-promise": "^6.1.1",
- "eslint-plugin-react": "^7.32.2",
- "standard-engine": "^15.0.0",
- "version-guard": "^1.1.1"
- }
- },
- "standard-engine": {
- "version": "15.1.0",
- "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-15.1.0.tgz",
- "integrity": "sha512-VHysfoyxFu/ukT+9v49d4BRXIokFRZuH3z1VRxzFArZdjSCFpro6rEIU3ji7e4AoAtuSfKBkiOmsrDqKW5ZSRw==",
- "dev": true,
- "requires": {
- "get-stdin": "^8.0.0",
- "minimist": "^1.2.6",
- "pkg-conf": "^3.1.0",
- "xdg-basedir": "^4.0.0"
- }
- },
"standard-json": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/standard-json/-/standard-json-1.1.0.tgz",
@@ -26715,22 +25773,6 @@
"strip-ansi": "^6.0.1"
}
},
- "string.prototype.matchall": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
- "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.4",
- "es-abstract": "^1.20.4",
- "get-intrinsic": "^1.1.3",
- "has-symbols": "^1.0.3",
- "internal-slot": "^1.0.3",
- "regexp.prototype.flags": "^1.4.3",
- "side-channel": "^1.0.4"
- }
- },
"string.prototype.trim": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
@@ -26839,6 +25881,16 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
+ "synckit": {
+ "version": "0.8.5",
+ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
+ "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==",
+ "dev": true,
+ "requires": {
+ "@pkgr/utils": "^2.3.1",
+ "tslib": "^2.5.0"
+ }
+ },
"syntax-error": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz",
@@ -27472,12 +26524,6 @@
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
"dev": true
},
- "version-guard": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/version-guard/-/version-guard-1.1.1.tgz",
- "integrity": "sha512-MGQLX89UxmYHgDvcXyjBI0cbmoW+t/dANDppNPrno64rYr8nH4SHSuElQuSYdXGEs0mUzdQe1BY+FhVPNsAmJQ==",
- "dev": true
- },
"vm-browserify": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz",
@@ -27495,9 +26541,9 @@
},
"dependencies": {
"acorn": {
- "version": "8.9.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
- "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
+ "version": "8.10.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
+ "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"dev": true
},
"acorn-walk": {
@@ -27743,12 +26789,6 @@
}
}
},
- "word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
- "dev": true
- },
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
@@ -27858,12 +26898,6 @@
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"requires": {}
},
- "xdg-basedir": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz",
- "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==",
- "dev": true
- },
"xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
diff --git a/package.json b/package.json
index 8947b686e..092d9f1c3 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,8 @@
"main": "mqtt.js",
"types": "types/index.d.ts",
"scripts": {
- "pretest": "standard | snazzy",
+ "lint": "eslint --ext .js .",
+ "lint-fix": "eslint --fix --ext .js .",
"typescript-compile-test": "tsc -p test/typescript/tsconfig.json",
"typescript-compile-execute": "node test/typescript/broker-connect-subscribe-and-publish.js",
"browser-build": "rimraf dist/ && mkdirp dist/ && browserify mqtt.js --standalone mqtt > dist/mqtt.js && terser dist/mqtt.js --compress --mangle --output dist/mqtt.min.js",
@@ -62,7 +63,7 @@
}
},
"pre-commit": [
- "pretest"
+ "lint"
],
"bin": {
"mqtt_pub": "./bin/pub.js",
@@ -116,6 +117,11 @@
"codecov": "^3.8.2",
"conventional-changelog-cli": "^3.0.0",
"end-of-stream": "^1.4.4",
+ "eslint": "^8.45.0",
+ "eslint-config-airbnb-base": "^15.0.0",
+ "eslint-config-prettier": "^8.8.0",
+ "eslint-plugin-import": "^2.27.5",
+ "eslint-plugin-prettier": "^5.0.0",
"global": "^4.4.0",
"mkdirp": "^3.0.1",
"mocha": "^10.2.0",
@@ -123,19 +129,14 @@
"mqtt-level-store": "^3.1.0",
"nyc": "^15.1.0",
"pre-commit": "^1.2.2",
+ "prettier": "^3.0.0",
"release-it": "^15.11.0",
"rimraf": "^5.0.1",
"should": "^13.2.3",
"sinon": "^15.2.0",
"snazzy": "^9.0.0",
- "standard": "^17.1.0",
"tape": "^5.6.4",
"terser": "^5.18.2",
"typescript": "^5.1.6"
- },
- "standard": {
- "env": [
- "mocha"
- ]
}
}
diff --git a/test/abstract_client.js b/test/abstract_client.js
index f4ac2c72e..cba436cb4 100644
--- a/test/abstract_client.js
+++ b/test/abstract_client.js
@@ -1,3469 +1,3718 @@
-'use strict'
-
/**
* Testing dependencies
*/
-const should = require('chai').should
+const { should } = require('chai')
const sinon = require('sinon')
-const mqtt = require('../')
-const Store = require('./../lib/store')
-const assert = require('chai').assert
-const ports = require('./helpers/port_list')
-const serverBuilder = require('./server_helpers_for_client_tests').serverBuilder
+const { assert } = require('chai')
const fs = require('fs')
const levelStore = require('mqtt-level-store')
+const mqtt = require('..')
+const Store = require('../lib/store')
+const ports = require('./helpers/port_list')
+const { serverBuilder } = require('./server_helpers_for_client_tests')
const handlePubrel = require('../lib/handlers/pubrel')
const handle = require('../lib/handlers/index')
const handlePublish = require('../lib/handlers/publish')
/**
- * These tests try to be consistent with names for servers (brokers) and clients,
- * but it can be confusing. To make it easier, here is a handy translation
- * chart:
- *
- * name | meaning
- * ---------------|--------
- * client | The MQTT.js client object being tested. A new instance is created for each test (by calling the `connect` function.)
- * server | A mock broker that you can control. The same server instance is used for all tests, so only use this if you plan to clean up when you're done.
- * serverBuilder | A factory that can make mock test servers (MQTT brokers). Useful if you need to do things that you can't (or don't want to) clean up after your test is done.
- * server2 | The name used for mock brokers that are created for an individual test and then destroyed.
- * serverClient | An socket on the mock broker. This gets created when your client connects and gets collected when you're done with it.
- *
- * Also worth noting:
- *
- * `serverClient.disconnect()` does not disconnect that socket. Instead, it sends an MQTT disconnect packet.
- * If you want to disconnect the socket from the broker side, you probably want to use `serverClient.destroy()`
- * or `serverClient.stream.destroy()`.
- *
- */
-
-module.exports = function (server, config) {
- const version = config.protocolVersion || 4
-
- function connect(opts) {
- opts = { ...config, ...opts }
- return mqtt.connect(opts)
- }
-
- describe('closing', function () {
- it('should emit close if stream closes', function (done) {
- const client = connect()
-
- client.once('connect', function () {
- client.stream.end()
- })
- client.once('close', function () {
- client.end((err) => done(err))
- })
- })
-
- it('should mark the client as disconnected', function (done) {
- const client = connect()
-
- client.once('close', function () {
- client.end((err) => {
- if (!client.connected) {
- done(err)
- } else {
- done(new Error('Not marked as disconnected'))
- }
- })
- assert.isFalse(client.connected)
- })
- client.once('connect', function () {
- client.stream.end()
- })
- })
-
- it('should stop ping timer if stream closes', function (done) {
- const client = connect()
-
- client.once('close', function () {
- assert.notExists(client.pingTimer)
- client.end(true, (err) => done(err))
- })
-
- client.once('connect', function () {
- assert.exists(client.pingTimer)
- client.stream.end()
- })
- })
-
- it('should emit close after end called', function (done) {
- const client = connect()
-
- client.once('close', function () {
- done()
- })
-
- client.once('connect', function () {
- client.end()
- })
- })
-
- it('should emit end after end called and client must be disconnected', function (done) {
- const client = connect()
-
- client.once('end', function () {
- if (client.disconnected) {
- return done()
- }
- done(new Error('client must be disconnected'))
- })
-
- client.once('connect', function () {
- client.end()
- })
- })
-
- it('should pass store close error to end callback but not to end listeners (incomingStore)', function (done) {
- const store = new Store()
- const client = connect({ incomingStore: store })
-
- store.close = function (cb) {
- cb(new Error('test'))
- }
- client.once('end', function () {
- if (arguments.length === 0) {
- return
- }
- throw new Error('no argument should be passed to event')
- })
-
- client.once('connect', function () {
- client.end(function (testError) {
- if (testError && testError.message === 'test') {
- return done()
- }
- throw new Error('bad argument passed to callback')
- })
- })
- })
-
- it('should pass store close error to end callback but not to end listeners (outgoingStore)', function (done) {
- const store = new Store()
- const client = connect({ outgoingStore: store })
-
- store.close = function (cb) {
- cb(new Error('test'))
- }
- client.once('end', function () {
- if (arguments.length === 0) {
- return
- }
- throw new Error('no argument should be passed to event')
- })
-
- client.once('connect', function () {
- client.end(function (testError) {
- if (testError && testError.message === 'test') {
- return done()
- }
- throw new Error('bad argument passed to callback')
- })
- })
- })
-
- it('should return `this` if end called twice', function (done) {
- const client = connect()
-
- client.once('connect', function () {
- client.end()
- const value = client.end()
- if (value === client) {
- done()
- } else {
- done(new Error('Not returning client.'))
- }
- })
- })
-
- it('should emit end only on first client end', function (done) {
- const client = connect()
-
- client.once('end', function () {
- const timeout = setTimeout(done.bind(null), 200)
- client.once('end', function () {
- clearTimeout(timeout)
- done(new Error('end was emitted twice'))
- })
- client.end()
- })
-
- client.once('connect', client.end.bind(client))
- })
-
- it('should stop ping timer after end called', function (done) {
- const client = connect()
-
- client.once('connect', function () {
- assert.exists(client.pingTimer)
- client.end((err) => {
- assert.notExists(client.pingTimer)
- done(err)
- })
- })
- })
-
- it('should be able to end even on a failed connection', function (done) {
- const client = connect({ host: 'this_hostname_should_not_exist' })
-
- const timeout = setTimeout(function () {
- done(new Error('Failed to end a disconnected client'))
- }, 500)
-
- setTimeout(function () {
- client.end(function (err) {
- clearTimeout(timeout)
- done(err)
- })
- }, 200)
- })
-
- it('should emit end even on a failed connection', function (done) {
- const client = connect({ host: 'this_hostname_should_not_exist' })
-
- const timeout = setTimeout(function () {
- done(new Error('Disconnected client has failed to emit end'))
- }, 500)
-
- client.once('end', function () {
- clearTimeout(timeout)
- done()
- })
-
- // after 200ms manually invoke client.end
- setTimeout(() => {
- const boundEnd = client.end.bind(client)
- boundEnd()
- }, 200)
- })
-
- it.skip('should emit end only once for a reconnecting client', function (done) {
- // I want to fix this test, but it will take signficant work, so I am marking it as a skipping test right now.
- // Reason for it is that there are overlaps in the reconnectTimer and connectTimer. In the PR for this code
- // there will be gists showing the difference between a successful test here and a failed test. For now we
- // will add the retries syntax because of the flakiness.
- const client = connect({ host: 'this_hostname_should_not_exist', connectTimeout: 10, reconnectPeriod: 20 })
- setTimeout(done.bind(null), 1000)
- const endCallback = function () {
- assert.strictEqual(spy.callCount, 1, 'end was emitted more than once for reconnecting client')
- }
-
- const spy = sinon.spy(endCallback)
- client.on('end', spy)
- setTimeout(() => {
- client.end.bind(client)
- client.end()
- }, 300)
- })
- })
-
- describe('connecting', function () {
- it('should connect to the broker', function (done) {
- const client = connect()
- client.on('error', done)
-
- server.once('client', function () {
- client.end((err) => done(err))
- })
- })
-
- it('should send a default client id', function (done) {
- const client = connect()
- client.on('error', done)
-
- server.once('client', function (serverClient) {
- serverClient.once('connect', function (packet) {
- assert.include(packet.clientId, 'mqttjs')
- client.end((err) => done(err))
- })
- })
- })
-
- it('should send be clean by default', function (done) {
- const client = connect()
- client.on('error', done)
-
- server.once('client', function (serverClient) {
- serverClient.once('connect', function (packet) {
- assert.strictEqual(packet.clean, true)
- done()
- })
- })
- })
-
- it('should connect with the given client id', function (done) {
- const client = connect({ clientId: 'testclient' })
- client.on('error', function (err) {
- throw err
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('connect', function (packet) {
- assert.include(packet.clientId, 'testclient')
- client.end((err) => done(err))
- })
- })
- })
-
- it('should connect with the client id and unclean state', function (done) {
- const client = connect({ clientId: 'testclient', clean: false })
- client.on('error', function (err) {
- throw err
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('connect', function (packet) {
- assert.include(packet.clientId, 'testclient')
- assert.isFalse(packet.clean)
- client.end(false, (err) => done(err))
- })
- })
- })
-
- it('should require a clientId with clean=false', function (done) {
- try {
- const client = connect({ clean: false })
- client.on('error', function (err) {
- done(err)
- })
- } catch (err) {
- assert.strictEqual(err.message, 'Missing clientId for unclean clients')
- done()
- }
- })
-
- it('should default to localhost', function (done) {
- const client = connect({ clientId: 'testclient' })
- client.on('error', function (err) {
- throw err
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('connect', function (packet) {
- assert.include(packet.clientId, 'testclient')
- done()
- })
- })
- })
-
- it('should emit connect', function (done) {
- const client = connect()
- client.once('connect', function () {
- client.end(true, (err) => done(err))
- })
- client.once('error', done)
- })
-
- it('should provide connack packet with connect event', function (done) {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- server.once('client', function (serverClient) {
- connack.sessionPresent = true
- serverClient.connack(connack)
- server.once('client', function (serverClient) {
- connack.sessionPresent = false
- serverClient.connack(connack)
- })
- })
-
- const client = connect()
- client.once('connect', function (packet) {
- assert.strictEqual(packet.sessionPresent, true)
- client.once('connect', function (packet) {
- assert.strictEqual(packet.sessionPresent, false)
- client.end((err) => done(err))
- })
- })
- })
-
- it('should mark the client as connected', function (done) {
- const client = connect()
- client.once('connect', function () {
- assert.isTrue(client.connected)
- client.end((err) => done(err))
- })
- })
-
- it('should emit error on invalid clientId', function (done) {
- const client = connect({ clientId: 'invalid' })
- client.once('connect', function () {
- done(new Error('Should not emit connect'))
- })
- client.once('error', function (error) {
- const value = version === 5 ? 128 : 2
- assert.strictEqual(error.code, value) // code for clientID identifer rejected
- client.end((err) => done(err))
- })
- })
-
- it('should emit error event if the socket refuses the connection', function (done) {
- // fake a port
- const client = connect({ port: 4557 })
-
- client.on('error', function (e) {
- assert.equal(e.code, 'ECONNREFUSED')
- client.end((err) => done(err))
- })
- })
-
- it('should have different client ids', function (done) {
- // bug identified in this test: the client.end callback is invoked twice, once when the `end`
- // method completes closing the stores and invokes the callback, and another time when the
- // stream is closed. When the stream is closed, for some reason the closeStores method is called
- // a second time.
- const client1 = connect()
- const client2 = connect()
-
- assert.notStrictEqual(client1.options.clientId, client2.options.clientId)
- client1.end(true, () => {
- client2.end(true, () => {
- done()
- })
- })
- })
- })
-
- describe('handling offline states', function () {
- it('should emit offline event once when the client transitions from connected states to disconnected ones', function (done) {
- const client = connect({ reconnectPeriod: 20 })
-
- client.on('connect', function () {
- this.stream.end()
- })
-
- client.on('offline', function () {
- client.end(true, done)
- })
- })
-
- it('should emit offline event once when the client (at first) can NOT connect to servers', function (done) {
- // fake a port
- const client = connect({ reconnectPeriod: 20, port: 4557 })
-
- client.on('error', function () { })
-
- client.on('offline', function () {
- client.end(true, done)
- })
- })
- })
-
- describe('topic validations when subscribing', function () {
- it('should be ok for well-formated topics', function (done) {
- const client = connect()
- client.subscribe(
- [
- '+', '+/event', 'event/+', '#', 'event/#', 'system/event/+',
- 'system/+/event', 'system/registry/event/#', 'system/+/event/#',
- 'system/registry/event/new_device', 'system/+/+/new_device'
- ],
- function (err) {
- client.end(function () {
- if (err) {
- return done(new Error(err))
- }
- done()
- })
- }
- )
- })
-
- it('should return an error (via callbacks) for topic #/event', function (done) {
- const client = connect()
- client.subscribe(['#/event', 'event#', 'event+'], function (err) {
- client.end(false, function () {
- if (err) {
- return done()
- }
- done(new Error('Validations do NOT work'))
- })
- })
- })
-
- it('should return an empty array for duplicate subs', function (done) {
- const client = connect()
- client.subscribe('event', function (err, granted1) {
- if (err) {
- return done(err)
- }
- client.subscribe('event', function (err, granted2) {
- if (err) {
- return done(err)
- }
- assert.isArray(granted2)
- assert.isEmpty(granted2)
- done()
- })
- })
- })
-
- it('should return an error (via callbacks) for topic #/event', function (done) {
- const client = connect()
- client.subscribe('#/event', function (err) {
- client.end(function () {
- if (err) {
- return done()
- }
- done(new Error('Validations do NOT work'))
- })
- })
- })
-
- it('should return an error (via callbacks) for topic event#', function (done) {
- const client = connect()
- client.subscribe('event#', function (err) {
- client.end(function () {
- if (err) {
- return done()
- }
- done(new Error('Validations do NOT work'))
- })
- })
- })
-
- it('should return an error (via callbacks) for topic system/#/event', function (done) {
- const client = connect()
- client.subscribe('system/#/event', function (err) {
- client.end(function () {
- if (err) {
- return done()
- }
- done(new Error('Validations do NOT work'))
- })
- })
- })
-
- it('should return an error (via callbacks) for empty topic list', function (done) {
- const client = connect()
- client.subscribe([], (subErr) => {
- client.end((endErr) => {
- if (subErr) {
- return done(endErr)
- } else {
- done(new Error('Validations do NOT work'))
- }
- })
- })
- })
-
- it('should return an error (via callbacks) for topic system/+/#/event', function (done) {
- const client = connect()
- client.subscribe('system/+/#/event', function (subErr) {
- client.end(true, (endErr) => {
- if (subErr) {
- return done(endErr)
- } else {
- done(new Error('Validations do NOT work'))
- }
- })
- })
- })
- })
-
- describe('offline messages', function () {
- it('should queue message until connected', function (done) {
- const client = connect()
-
- client.publish('test', 'test')
- client.subscribe('test')
- client.unsubscribe('test')
- assert.strictEqual(client.queue.length, 3)
-
- client.once('connect', function () {
- assert.strictEqual(client.queue.length, 0)
- setTimeout(function () {
- client.end(true, done)
- }, 10)
- })
- })
-
- it('should not queue qos 0 messages if queueQoSZero is false', function (done) {
- const client = connect({ queueQoSZero: false })
-
- client.publish('test', 'test', { qos: 0 })
- assert.strictEqual(client.queue.length, 0)
- client.on('connect', function () {
- setTimeout(function () {
- client.end(true, done)
- }, 10)
- })
- })
-
- it('should queue qos != 0 messages', function (done) {
- const client = connect({ queueQoSZero: false })
-
- client.publish('test', 'test', { qos: 1 })
- client.publish('test', 'test', { qos: 2 })
- client.subscribe('test')
- client.unsubscribe('test')
- assert.strictEqual(client.queue.length, 2)
- client.on('connect', function () {
- setTimeout(function () {
- client.end(true, done)
- }, 10)
- })
- })
-
- it('should not interrupt messages', function (done) {
- let client = null
- let publishCount = 0
- const incomingStore = new mqtt.Store({ clean: false })
- const outgoingStore = new mqtt.Store({ clean: false })
- const server2 = serverBuilder(config.protocol, function (serverClient) {
- serverClient.on('connect', function () {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- })
- serverClient.on('publish', function (packet) {
- if (packet.qos !== 0) {
- serverClient.puback({ messageId: packet.messageId })
- }
- switch (publishCount++) {
- case 0:
- assert.strictEqual(packet.payload.toString(), 'payload1')
- break
- case 1:
- assert.strictEqual(packet.payload.toString(), 'payload2')
- break
- case 2:
- assert.strictEqual(packet.payload.toString(), 'payload3')
- break
- case 3:
- assert.strictEqual(packet.payload.toString(), 'payload4')
- client.end((err1) => {
- server2.close((err2) => done(err1 || err2))
- })
- }
- })
- })
-
- server2.listen(ports.PORTAND50, function () {
- client = connect({
- port: ports.PORTAND50,
- host: 'localhost',
- clean: false,
- clientId: 'cid1',
- reconnectPeriod: 0,
- incomingStore,
- outgoingStore,
- queueQoSZero: true
- })
- client.on('packetreceive', function (packet) {
- if (packet.cmd === 'connack') {
- setImmediate(
- function () {
- client.publish('test', 'payload3', { qos: 1 })
- client.publish('test', 'payload4', { qos: 0 })
- }
- )
- }
- })
- client.publish('test', 'payload1', { qos: 2 })
- client.publish('test', 'payload2', { qos: 2 })
- })
- })
-
- it('should not overtake the messages stored in the level-db-store', function (done) {
- const storePath = fs.mkdtempSync('test-store_')
- const store = levelStore(storePath)
- let client = null
- const incomingStore = store.incoming
- const outgoingStore = store.outgoing
- let publishCount = 0
-
- const server2 = serverBuilder(config.protocol, function (serverClient) {
- serverClient.on('connect', function () {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- })
- serverClient.on('publish', function (packet) {
- if (packet.qos !== 0) {
- serverClient.puback({ messageId: packet.messageId })
- }
-
- switch (publishCount++) {
- case 0:
- assert.strictEqual(packet.payload.toString(), 'payload1')
- break
- case 1:
- assert.strictEqual(packet.payload.toString(), 'payload2')
- break
- case 2:
- assert.strictEqual(packet.payload.toString(), 'payload3')
-
- server2.close((err) => {
- fs.rmSync(storePath, { recursive: true })
- done(err)
- })
- break
- }
- })
- })
-
- const clientOptions = {
- port: ports.PORTAND72,
- host: 'localhost',
- clean: false,
- clientId: 'cid1',
- reconnectPeriod: 0,
- incomingStore,
- outgoingStore,
- queueQoSZero: true
- }
-
- server2.listen(ports.PORTAND72, function () {
- client = connect(clientOptions)
-
- client.once('close', function () {
- client.once('connect', function () {
- client.publish('test', 'payload2', { qos: 1 })
- client.publish('test', 'payload3', { qos: 1 }, function () {
- client.end(false)
- })
- })
- // reconecting
- client.reconnect(clientOptions)
- })
-
- // publish and close
- client.once('connect', function () {
- client.publish('test', 'payload1', {
- qos: 1,
- cbStorePut: function () {
- client.end(true)
- }
- })
- })
- })
- })
-
- it('should call cb if an outgoing QoS 0 message is not sent', function (done) {
- const client = connect({ queueQoSZero: false })
- let called = false
-
- client.publish('test', 'test', { qos: 0 }, function () {
- called = true
- })
-
- client.on('connect', function () {
- assert.isTrue(called)
- setTimeout(function () {
- client.end(true, done)
- }, 10)
- })
- })
-
- it('should delay ending up until all inflight messages are delivered', function (done) {
- const client = connect()
- let subscribeCalled = false
-
- client.on('connect', function () {
- client.subscribe('test', function () {
- subscribeCalled = true
- })
- client.publish('test', 'test', function () {
- client.end(false, function () {
- assert.strictEqual(subscribeCalled, true)
- done()
- })
- })
- })
- })
-
- it('wait QoS 1 publish messages', function (done) {
- const client = connect()
- let messageReceived = false
-
- client.on('connect', function () {
- client.subscribe('test')
- client.publish('test', 'test', { qos: 1 }, function () {
- client.end(false, function () {
- assert.strictEqual(messageReceived, true)
- done()
- })
- })
- client.on('message', function () {
- messageReceived = true
- })
- })
-
- server.once('client', function (serverClient) {
- serverClient.on('subscribe', function () {
- serverClient.on('publish', function (packet) {
- serverClient.publish(packet)
- })
- })
- })
- })
-
- it('does not wait acks when force-closing', function (done) {
- // non-running broker
- const client = connect('mqtt://localhost:8993')
- client.publish('test', 'test', { qos: 1 })
- client.end(true, done)
- })
-
- it('should call cb if store.put fails', function (done) {
- const store = new Store()
- store.put = function (packet, cb) {
- process.nextTick(cb, new Error('oops there is an error'))
- }
- const client = connect({ incomingStore: store, outgoingStore: store })
- client.publish('test', 'test', { qos: 2 }, function (err) {
- if (err) {
- client.end(true, done)
- }
- })
- })
- })
-
- describe('publishing', function () {
- it('should publish a message (offline)', function (done) {
- const client = connect()
- const payload = 'test'
- const topic = 'test'
- // don't wait on connect to send publish
- client.publish(topic, payload)
-
- server.on('client', onClient)
-
- function onClient(serverClient) {
- serverClient.once('connect', function () {
- server.removeListener('client', onClient)
- })
-
- serverClient.once('publish', function (packet) {
- assert.strictEqual(packet.topic, topic)
- assert.strictEqual(packet.payload.toString(), payload)
- assert.strictEqual(packet.qos, 0)
- assert.strictEqual(packet.retain, false)
- client.end(true, done)
- })
- }
- })
-
- it('should publish a message (online)', function (done) {
- const client = connect()
- const payload = 'test'
- const topic = 'test'
- // block on connect before sending publish
- client.on('connect', function () {
- client.publish(topic, payload)
- })
-
- server.on('client', onClient)
-
- function onClient(serverClient) {
- serverClient.once('connect', function () {
- server.removeListener('client', onClient)
- })
-
- serverClient.once('publish', function (packet) {
- assert.strictEqual(packet.topic, topic)
- assert.strictEqual(packet.payload.toString(), payload)
- assert.strictEqual(packet.qos, 0)
- assert.strictEqual(packet.retain, false)
- client.end(true, done)
- })
- }
- })
-
- it('should publish a message (retain, offline)', function (done) {
- const client = connect({ queueQoSZero: true })
- const payload = 'test'
- const topic = 'test'
- let called = false
-
- client.publish(topic, payload, { retain: true }, function () {
- called = true
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('publish', function (packet) {
- assert.strictEqual(packet.topic, topic)
- assert.strictEqual(packet.payload.toString(), payload)
- assert.strictEqual(packet.qos, 0)
- assert.strictEqual(packet.retain, true)
- assert.strictEqual(called, true)
- client.end(true, done)
- })
- })
- })
-
- it('should emit a packetsend event', function (done) {
- const client = connect()
- const payload = 'test_payload'
- const topic = 'testTopic'
-
- client.on('packetsend', function (packet) {
- if (packet.cmd === 'publish') {
- assert.strictEqual(packet.topic, topic)
- assert.strictEqual(packet.payload.toString(), payload)
- assert.strictEqual(packet.qos, 0)
- assert.strictEqual(packet.retain, false)
- client.end(true, done)
- } else {
- done(new Error('packet.cmd was not publish!'))
- }
- })
-
- client.publish(topic, payload)
- })
-
- it('should accept options', function (done) {
- const client = connect()
- const payload = 'test'
- const topic = 'test'
- const opts = {
- retain: true,
- qos: 1
- }
- let received = false
-
- client.once('connect', function () {
- client.publish(topic, payload, opts, function (err) {
- assert(received)
- client.end(function () {
- done(err)
- })
- })
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('publish', function (packet) {
- assert.strictEqual(packet.topic, topic)
- assert.strictEqual(packet.payload.toString(), payload)
- assert.strictEqual(packet.qos, opts.qos, 'incorrect qos')
- assert.strictEqual(packet.retain, opts.retain, 'incorrect ret')
- assert.strictEqual(packet.dup, false, 'incorrect dup')
- received = true
- })
- })
- })
-
- it('should publish with the default options for an empty parameter', function (done) {
- const client = connect()
- const payload = 'test'
- const topic = 'test'
- const defaultOpts = { qos: 0, retain: false, dup: false }
-
- client.once('connect', function () {
- client.publish(topic, payload, {})
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('publish', function (packet) {
- assert.strictEqual(packet.topic, topic)
- assert.strictEqual(packet.payload.toString(), payload)
- assert.strictEqual(packet.qos, defaultOpts.qos, 'incorrect qos')
- assert.strictEqual(packet.retain, defaultOpts.retain, 'incorrect ret')
- assert.strictEqual(packet.dup, defaultOpts.dup, 'incorrect dup')
- client.end(true, done)
- })
- })
- })
-
- it('should mark a message as duplicate when "dup" option is set', function (done) {
- const client = connect()
- const payload = 'duplicated-test'
- const topic = 'test'
- const opts = {
- retain: true,
- qos: 1,
- dup: true
- }
- let received = false
-
- client.once('connect', function () {
- client.publish(topic, payload, opts, function (err) {
- assert(received)
- client.end(function () {
- done(err)
- })
- })
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('publish', function (packet) {
- assert.strictEqual(packet.topic, topic)
- assert.strictEqual(packet.payload.toString(), payload)
- assert.strictEqual(packet.qos, opts.qos, 'incorrect qos')
- assert.strictEqual(packet.retain, opts.retain, 'incorrect ret')
- assert.strictEqual(packet.dup, opts.dup, 'incorrect dup')
- received = true
- })
- })
- })
-
- it('should fire a callback (qos 0)', function (done) {
- const client = connect()
-
- client.once('connect', function () {
- client.publish('a', 'b', function () {
- client.end((err) => done(err))
- })
- })
- })
-
- it('should fire a callback (qos 1)', function (done) {
- const client = connect()
- const opts = { qos: 1 }
-
- client.once('connect', function () {
- client.publish('a', 'b', opts, function () {
- client.end((err) => done(err))
- })
- })
- })
-
- it('should fire a callback (qos 1) on error', function (done) {
- // 145 = Packet Identifier in use
- const pubackReasonCode = 145
- const pubOpts = { qos: 1 }
- let client = null
-
- const server2 = serverBuilder(config.protocol, function (serverClient) {
- serverClient.on('connect', function () {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- })
- serverClient.on('publish', function (packet) {
- if (packet.qos === 1) {
- if (version === 5) {
- serverClient.puback({
- messageId: packet.messageId,
- reasonCode: pubackReasonCode
- })
- } else {
- serverClient.puback({ messageId: packet.messageId })
- }
- }
- })
- })
-
- server2.listen(ports.PORTAND72, function () {
- client = connect({
- port: ports.PORTAND72,
- host: 'localhost',
- clean: true,
- clientId: 'cid1',
- reconnectPeriod: 0
- })
-
- client.once('connect', function () {
- client.publish('a', 'b', pubOpts, function (err) {
- if (version === 5) {
- assert.strictEqual(err.code, pubackReasonCode)
- } else {
- assert.ifError(err)
- }
- setImmediate(function () {
- client.end(() => {
- server2.close(done())
- })
- })
- })
- })
- })
- })
-
- it('should fire a callback (qos 2)', function (done) {
- const client = connect()
- const opts = { qos: 2 }
-
- client.once('connect', function () {
- client.publish('a', 'b', opts, function () {
- client.end((err) => done(err))
- })
- })
- })
-
- it('should fire a callback (qos 2) on error', function (done) {
- // 145 = Packet Identifier in use
- const pubrecReasonCode = 145
- const pubOpts = { qos: 2 }
- let client = null
-
- const server2 = serverBuilder(config.protocol, function (serverClient) {
- serverClient.on('connect', function () {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- })
- serverClient.on('publish', function (packet) {
- if (packet.qos === 2) {
- if (version === 5) {
- serverClient.pubrec({
- messageId: packet.messageId,
- reasonCode: pubrecReasonCode
- })
- } else {
- serverClient.pubrec({ messageId: packet.messageId })
- }
- }
- })
- serverClient.on('pubrel', function (packet) {
- if (!serverClient.writable) return false
- serverClient.pubcomp(packet)
- })
- })
-
- server2.listen(ports.PORTAND103, function () {
- client = connect({
- port: ports.PORTAND103,
- host: 'localhost',
- clean: true,
- clientId: 'cid1',
- reconnectPeriod: 0
- })
-
- client.once('connect', function () {
- client.publish('a', 'b', pubOpts, function (err) {
- if (version === 5) {
- assert.strictEqual(err.code, pubrecReasonCode)
- } else {
- assert.ifError(err)
- }
- setImmediate(function () {
- client.end(true, () => {
- server2.close(done())
- })
- })
- })
- })
- })
- })
-
- it('should support UTF-8 characters in topic', function (done) {
- const client = connect()
-
- client.once('connect', function () {
- client.publish('ä¸å›½', 'hello', function () {
- client.end((err) => done(err))
- })
- })
- })
-
- it('should support UTF-8 characters in payload', function (done) {
- const client = connect()
-
- client.once('connect', function () {
- client.publish('hello', 'ä¸å›½', function () {
- client.end((err) => done(err))
- })
- })
- })
-
- it('should publish 10 QoS 2 and receive them', function (done) {
- const client = connect()
- let countSent = 0
- let countReceived = 0
-
- function publishNext() {
- client.publish('test', 'test', { qos: 2 }, function (err) {
- assert.ifError(err)
- countSent++
- })
- }
-
- client.on('connect', function () {
- client.subscribe('test', function (err) {
- assert.ifError(err)
- publishNext()
- })
- })
-
- client.on('message', function () {
- countReceived++
- if (countSent >= 10 && countReceived >= 10) {
- client.end(done)
- } else {
- publishNext()
- }
- })
-
- server.once('client', function (serverClient) {
- serverClient.on('offline', function () {
- client.end()
- done('error went offline... didnt see this happen')
- })
-
- serverClient.on('subscribe', function () {
- serverClient.on('publish', function (packet) {
- serverClient.publish(packet)
- })
- })
- })
- })
-
- function testQosHandleMessage(qos, done) {
- const client = connect()
-
- let messageEventCount = 0
- let handleMessageCount = 0
-
- client.handleMessage = function (packet, callback) {
- setTimeout(function () {
- handleMessageCount++
- // next message event should not emit until handleMessage completes
- assert.strictEqual(handleMessageCount, messageEventCount)
- if (handleMessageCount === 10) {
- setTimeout(function () {
- client.end(true, done)
- })
- }
- callback()
- }, 100)
- }
-
- client.on('message', function (topic, message, packet) {
- messageEventCount++
- })
-
- client.on('connect', function () {
- client.subscribe('test')
- })
-
- server.once('client', function (serverClient) {
- serverClient.on('offline', function () {
- client.end(true, function () {
- done('error went offline... didnt see this happen')
- })
- })
-
- serverClient.on('subscribe', function () {
- for (let i = 0; i < 10; i++) {
- serverClient.publish({
- messageId: i,
- topic: 'test',
- payload: 'test' + i,
- qos
- })
- }
- })
- })
- }
-
- const qosTests = [0, 1, 2]
- qosTests.forEach(function (QoS) {
- it('should publish 10 QoS ' + QoS + 'and receive them only when `handleMessage` finishes', function (done) {
- testQosHandleMessage(QoS, done)
- })
- })
-
- it('should not send a `puback` if the execution of `handleMessage` fails for messages with QoS `1`', function (done) {
- const client = connect()
-
- client.handleMessage = function (packet, callback) {
- callback(new Error('Error thrown by the application'))
- }
-
- client._sendPacket = sinon.spy()
-
- handlePublish(client, {
- messageId: Math.floor(65535 * Math.random()),
- topic: 'test',
- payload: 'test',
- qos: 1
- }, function (err) {
- assert.exists(err)
- })
-
- assert.strictEqual(client._sendPacket.callCount, 0)
- client.end()
- client.on('connect', function () { done() })
- })
-
- it('should silently ignore errors thrown by `handleMessage` and return when no callback is passed ' +
- 'into `handlePublish` method', function (done) {
- const client = connect()
-
- client.handleMessage = function (packet, callback) {
- callback(new Error('Error thrown by the application'))
- }
-
- try {
- handlePublish(client, {
- 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) {
- process.nextTick(function () {
- cb(null, 'Error')
- })
- }
-
- close(cb) {
- cb()
- }
- }
-
- const store = new AsyncStore()
- const client = connect({ incomingStore: store })
-
- handlePublish(client, {
- messageId: 1,
- topic: 'test',
- payload: 'test',
- qos: 1
- }, function () {
- client.end((err) => done(err))
- })
- })
-
- it('should handle error with async incoming store in QoS 2 `handlePublish` method', function (done) {
- class AsyncStore {
- put(packet, cb) {
- process.nextTick(function () {
- cb(null, 'Error')
- })
- }
-
- del(packet, cb) {
- process.nextTick(function () {
- cb(new Error('Error'))
- })
- }
-
- get(packet, cb) {
- process.nextTick(function () {
- cb(null, { cmd: 'publish' })
- })
- }
-
- close(cb) {
- cb()
- }
- }
-
- const store = new AsyncStore()
- const client = connect({ incomingStore: store })
-
- handlePublish(client, {
- messageId: 1,
- topic: 'test',
- payload: 'test',
- qos: 2
- }, function () {
- client.end((err) => done(err))
- })
- })
-
- it('should handle error with async incoming store in QoS 2 `handlePubrel` method', function (done) {
- class AsyncStore {
- put(packet, cb) {
- process.nextTick(function () {
- cb(null, 'Error')
- })
- }
-
- del(packet, cb) {
- process.nextTick(function () {
- cb(new Error('Error'))
- })
- }
-
- get(packet, cb) {
- process.nextTick(function () {
- cb(null, { cmd: 'publish' })
- })
- }
-
- close(cb) {
- cb()
- }
- }
-
- const store = new AsyncStore()
- const client = connect({ incomingStore: store })
-
- handlePubrel(client, {
- messageId: 1,
- qos: 2
- }, function () {
- client.end(true, (err) => done(err))
- })
- })
-
- it('should handle success with async incoming store in QoS 2 `handlePubrel` method', function (done) {
- let delComplete = false
- class AsyncStore {
- put(packet, cb) {
- process.nextTick(function () {
- cb(null, 'Error')
- })
- }
-
- del(packet, cb) {
- process.nextTick(function () {
- delComplete = true
- cb(null)
- })
- }
-
- get(packet, cb) {
- process.nextTick(function () {
- cb(null, { cmd: 'publish' })
- })
- }
-
- close(cb) {
- cb()
- }
- }
-
- const store = new AsyncStore()
- const client = connect({ incomingStore: store })
-
- handlePubrel(client, {
- messageId: 1,
- qos: 2
- }, function () {
- assert.isTrue(delComplete)
- client.end(true, done)
- })
- })
-
- it('should not send a `pubcomp` if the execution of `handleMessage` fails for messages with QoS `2`', function (done) {
- const store = new Store()
- const client = connect({ incomingStore: store })
-
- const messageId = Math.floor(65535 * Math.random())
- const topic = 'testTopic'
- const payload = 'testPayload'
- const qos = 2
-
- client.handleMessage = function (packet, callback) {
- callback(new Error('Error thrown by the application'))
- }
-
- client.once('connect', function () {
- client.subscribe(topic, { qos: 2 })
-
- store.put({
- messageId,
- topic,
- payload,
- qos,
- cmd: 'publish'
- }, function () {
- // cleans up the client
- client._sendPacket = sinon.spy()
- handlePubrel(client, { cmd: 'pubrel', messageId }, function (err) {
- assert.exists(err)
- assert.strictEqual(client._sendPacket.callCount, 0)
- client.end(true, done)
- })
- })
- })
- })
-
- 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 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.once('connect', function () {
- client.subscribe(topic, { qos: 2 })
-
- store.put({
- messageId,
- topic,
- payload,
- qos,
- cmd: 'publish'
- }, function () {
- try {
- handlePubrel(client, { cmd: 'pubrel', messageId })
- client.end(true, done)
- } catch (err) {
- client.end(true, () => { done(err) })
- }
- })
- })
- })
-
- it('should keep message order', function (done) {
- let publishCount = 0
- let reconnect = false
- let client = {}
- const incomingStore = new mqtt.Store({ clean: false })
- const outgoingStore = new mqtt.Store({ clean: false })
- 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('connect', function (packet) {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- })
- serverClient.on('publish', function (packet) {
- serverClient.puback({ messageId: packet.messageId })
- if (reconnect) {
- switch (publishCount++) {
- case 0:
- assert.strictEqual(packet.payload.toString(), 'payload1')
- break
- case 1:
- assert.strictEqual(packet.payload.toString(), 'payload2')
- break
- case 2:
- assert.strictEqual(packet.payload.toString(), 'payload3')
- client.end((err1) => {
- server2.close((err2) => done(err1 || err2))
- })
- break
- }
- }
- })
- })
-
- server2.listen(ports.PORTAND50, function () {
- client = connect({
- port: ports.PORTAND50,
- host: 'localhost',
- clean: false,
- clientId: 'cid1',
- reconnectPeriod: 0,
- incomingStore,
- outgoingStore
- })
-
- client.on('connect', function () {
- if (!reconnect) {
- client.publish('topic', 'payload1', { qos: 1 })
- client.publish('topic', 'payload2', { qos: 1 })
- client.end(true)
- } else {
- client.publish('topic', 'payload3', { qos: 1 })
- }
- })
- client.on('close', function () {
- if (!reconnect) {
- client.reconnect({
- clean: false,
- incomingStore,
- outgoingStore
- })
- reconnect = true
- }
- })
- })
- })
-
- function testCallbackStorePutByQoS(qos, clean, expected, done) {
- const client = connect({
- clean,
- clientId: 'testId'
- })
-
- const callbacks = []
-
- function cbStorePut() {
- callbacks.push('storeput')
- }
-
- client.on('connect', function () {
- client.publish('test', 'test', { qos, cbStorePut }, function (err) {
- if (err) done(err)
- callbacks.push('publish')
- assert.deepEqual(callbacks, expected)
- client.end(true, done)
- })
- })
- }
-
- const callbackStorePutByQoSParameters = [
- { args: [0, true], expected: ['publish'] },
- { args: [0, false], expected: ['publish'] },
- { args: [1, true], expected: ['storeput', 'publish'] },
- { args: [1, false], expected: ['storeput', 'publish'] },
- { args: [2, true], expected: ['storeput', 'publish'] },
- { args: [2, false], expected: ['storeput', 'publish'] }
- ]
-
- callbackStorePutByQoSParameters.forEach(function (test) {
- if (test.args[0] === 0) { // QoS 0
- it('should not call cbStorePut when publishing message with QoS `' + test.args[0] + '` and clean `' + test.args[1] + '`', function (done) {
- testCallbackStorePutByQoS(test.args[0], test.args[1], test.expected, done)
- })
- } else { // QoS 1 and 2
- it('should call cbStorePut before publish completes when publishing message with QoS `' + test.args[0] + '` and clean `' + test.args[1] + '`', function (done) {
- testCallbackStorePutByQoS(test.args[0], test.args[1], test.expected, done)
- })
- }
- })
- })
-
- describe('unsubscribing', function () {
- it('should send an unsubscribe packet (offline)', function (done) {
- const client = connect()
- let received = false
-
- client.unsubscribe('test', function (err) {
- assert.ifError(err)
- assert(received)
- client.end(done)
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('unsubscribe', function (packet) {
- assert.include(packet.unsubscriptions, 'test')
- received = true
- })
- })
- })
-
- it('should send an unsubscribe packet', function (done) {
- const client = connect()
- const topic = 'topic'
- let received = false
-
- client.once('connect', function () {
- client.unsubscribe(topic, function (err) {
- assert.ifError(err)
- assert(received)
- client.end(done)
- })
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('unsubscribe', function (packet) {
- assert.include(packet.unsubscriptions, topic)
- received = true
- })
- })
- })
-
- it('should emit a packetsend event', function (done) {
- const client = connect()
- const testTopic = 'testTopic'
-
- client.once('connect', function () {
- client.subscribe(testTopic)
- })
-
- client.on('packetsend', function (packet) {
- if (packet.cmd === 'subscribe') {
- client.end(true, done)
- }
- })
- })
-
- it('should emit a packetreceive event', function (done) {
- const client = connect()
- const testTopic = 'testTopic'
-
- client.once('connect', function () {
- client.subscribe(testTopic)
- })
-
- client.on('packetreceive', function (packet) {
- if (packet.cmd === 'suback') {
- client.end(true, done)
- }
- })
- })
-
- it('should accept an array of unsubs', function (done) {
- const client = connect()
- const topics = ['topic1', 'topic2']
- let received = false
-
- client.once('connect', function () {
- client.unsubscribe(topics, function (err) {
- assert.ifError(err)
- assert(received)
- client.end(done)
- })
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('unsubscribe', function (packet) {
- assert.deepStrictEqual(packet.unsubscriptions, topics)
- received = true
- })
- })
- })
-
- it('should fire a callback on unsuback', function (done) {
- const client = connect()
- const topic = 'topic'
-
- client.once('connect', function () {
- client.unsubscribe(topic, () => {
- client.end(true, done)
- })
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('unsubscribe', function (packet) {
- serverClient.unsuback(packet)
- })
- })
- })
-
- it('should unsubscribe from a chinese topic', function (done) {
- const client = connect()
- const topic = 'ä¸å›½'
-
- client.once('connect', function () {
- client.unsubscribe(topic, () => {
- client.end(err => {
- done(err)
- })
- })
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('unsubscribe', function (packet) {
- assert.include(packet.unsubscriptions, topic)
- })
- })
- })
- })
-
- describe('keepalive', function () {
- let clock
-
- // eslint-disable-next-line
- beforeEach(function () {
- clock = sinon.useFakeTimers()
- })
-
- afterEach(function () {
- clock.restore()
- })
-
- it('should checkPing at keepalive interval', function (done) {
- const interval = 3
- const client = connect({ keepalive: interval })
-
- client._checkPing = sinon.spy()
-
- client.once('connect', function () {
- clock.tick(interval * 1000)
- assert.strictEqual(client._checkPing.callCount, 1)
-
- clock.tick(interval * 1000)
- assert.strictEqual(client._checkPing.callCount, 2)
-
- clock.tick(interval * 1000)
- assert.strictEqual(client._checkPing.callCount, 3)
-
- client.end(true, done)
- })
- })
-
- it('should not checkPing if publishing at a higher rate than keepalive', function (done) {
- const intervalMs = 3000
- const client = connect({ keepalive: intervalMs / 1000 })
-
- client._checkPing = sinon.spy()
-
- client.once('connect', function () {
- client.publish('foo', 'bar')
- clock.tick(intervalMs - 1)
- client.publish('foo', 'bar')
- clock.tick(2)
-
- assert.strictEqual(client._checkPing.callCount, 0)
- client.end(true, done)
- })
- })
-
- it('should checkPing if publishing at a higher rate than keepalive and reschedulePings===false', function (done) {
- const intervalMs = 3000
- const client = connect({
- keepalive: intervalMs / 1000,
- reschedulePings: false
- })
-
- client._checkPing = sinon.spy()
-
- client.once('connect', function () {
- client.publish('foo', 'bar')
- clock.tick(intervalMs - 1)
- client.publish('foo', 'bar')
- clock.tick(2)
-
- assert.strictEqual(client._checkPing.callCount, 1)
- client.end(true, done)
- })
- })
- })
-
- describe('pinging', function () {
- it('should set a ping timer', function (done) {
- const client = connect({ keepalive: 3 })
- client.once('connect', function () {
- assert.exists(client.pingTimer)
- client.end(true, done)
- })
- })
-
- it('should not set a ping timer keepalive=0', function (done) {
- const client = connect({ keepalive: 0 })
- client.on('connect', function () {
- assert.notExists(client.pingTimer)
- client.end(true, done)
- })
- })
-
- it('should reconnect if pingresp is not sent', function (done) {
- this.timeout(4000)
- const client = connect({ keepalive: 1, reconnectPeriod: 100 })
-
- // Fake no pingresp being send by stubbing the _handlePingresp function
- client.on('packetreceive', function (packet) {
- if (packet.cmd === 'pingresp') {
- setImmediate(() => {
- client.pingResp = false
- })
- }
- })
-
- client.once('connect', function () {
- client.once('connect', function () {
- client.end(true, done)
- })
- })
- })
-
- it('should not reconnect if pingresp is successful', function (done) {
- const client = connect({ keepalive: 100 })
- client.once('close', function () {
- done(new Error('Client closed connection'))
- })
- setTimeout(done, 1000)
- })
-
- it('should defer the next ping when sending a control packet', function (done) {
- const client = connect({ keepalive: 1 })
-
- client.once('connect', function () {
- client._checkPing = sinon.spy()
-
- client.publish('foo', 'bar')
- setTimeout(function () {
- assert.strictEqual(client._checkPing.callCount, 0)
- client.publish('foo', 'bar')
-
- setTimeout(function () {
- assert.strictEqual(client._checkPing.callCount, 0)
- client.publish('foo', 'bar')
-
- setTimeout(function () {
- assert.strictEqual(client._checkPing.callCount, 0)
- done()
- }, 75)
- }, 75)
- }, 75)
- })
- })
- })
-
- describe('subscribing', function () {
- it('should send a subscribe message (offline)', function (done) {
- const client = connect()
-
- client.subscribe('test')
-
- server.once('client', function (serverClient) {
- serverClient.once('subscribe', function () {
- done()
- })
- })
- })
-
- it('should send a subscribe message', function (done) {
- const client = connect()
- const topic = 'test'
-
- client.once('connect', function () {
- client.subscribe(topic)
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('subscribe', function (packet) {
- const result = {
- topic,
- qos: 0
- }
- if (version === 5) {
- result.nl = false
- result.rap = false
- result.rh = 0
- }
- assert.include(packet.subscriptions[0], result)
- done()
- })
- })
- })
-
- it('should emit a packetsend event', function (done) {
- const client = connect()
- const testTopic = 'testTopic'
-
- client.once('connect', function () {
- client.subscribe(testTopic)
- })
-
- client.on('packetsend', function (packet) {
- if (packet.cmd === 'subscribe') {
- done()
- }
- })
- })
-
- it('should emit a packetreceive event', function (done) {
- const client = connect()
- const testTopic = 'testTopic'
-
- client.once('connect', function () {
- client.subscribe(testTopic)
- })
-
- client.on('packetreceive', function (packet) {
- if (packet.cmd === 'suback') {
- done()
- }
- })
- })
-
- it('should accept an array of subscriptions', function (done) {
- const client = connect()
- const subs = ['test1', 'test2']
-
- client.once('connect', function () {
- client.subscribe(subs)
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('subscribe', function (packet) {
- // i.e. [{topic: 'a', qos: 0}, {topic: 'b', qos: 0}]
- const expected = subs.map(function (i) {
- const result = { topic: i, qos: 0 }
- if (version === 5) {
- result.nl = false
- result.rap = false
- result.rh = 0
- }
- return result
- })
-
- assert.deepStrictEqual(packet.subscriptions, expected)
- client.end(done)
- })
- })
- })
-
- it('should accept a hash of subscriptions', function (done) {
- const client = connect()
- const topics = {
- test1: { qos: 0 },
- test2: { qos: 1 }
- }
-
- client.once('connect', function () {
- client.subscribe(topics)
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('subscribe', function (packet) {
- const expected = []
-
- for (const k in topics) {
- if (Object.prototype.hasOwnProperty.call(topics, k)) {
- const result = {
- topic: k,
- qos: topics[k].qos
- }
- if (version === 5) {
- result.nl = false
- result.rap = false
- result.rh = 0
- }
- expected.push(result)
- }
- }
-
- assert.deepStrictEqual(packet.subscriptions, expected)
- client.end(done)
- })
- })
- })
-
- it('should accept an options parameter', function (done) {
- const client = connect()
- const topic = 'test'
- const opts = { qos: 1 }
-
- client.once('connect', function () {
- client.subscribe(topic, opts)
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('subscribe', function (packet) {
- const expected = [{
- topic,
- qos: 1
- }]
-
- if (version === 5) {
- expected[0].nl = false
- expected[0].rap = false
- expected[0].rh = 0
- }
-
- assert.deepStrictEqual(packet.subscriptions, expected)
- done()
- })
- })
- })
-
- it('should subscribe with the default options for an empty options parameter', function (done) {
- const client = connect()
- const topic = 'test'
- const defaultOpts = { qos: 0 }
-
- client.once('connect', function () {
- client.subscribe(topic, {})
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('subscribe', function (packet) {
- const result = {
- topic,
- qos: defaultOpts.qos
- }
- if (version === 5) {
- result.nl = false
- result.rap = false
- result.rh = 0
- }
-
- assert.include(packet.subscriptions[0], result)
- client.end(err => done(err))
- })
- })
- })
-
- it('should fire a callback on suback', function (done) {
- const client = connect()
- const topic = 'test'
-
- client.once('connect', function () {
- client.subscribe(topic, { qos: 2 }, function (err, granted) {
- if (err) {
- done(err)
- } else {
- assert.exists(granted, 'granted not given')
- const expectedResult = { topic: 'test', qos: 2 }
- if (version === 5) {
- expectedResult.nl = false
- expectedResult.rap = false
- expectedResult.rh = 0
- expectedResult.properties = undefined
- }
- assert.include(granted[0], expectedResult)
- client.end(err => done(err))
- }
- })
- })
- })
-
- it('should fire a callback with error if disconnected (options provided)', function (done) {
- const client = connect()
- const topic = 'test'
- client.once('connect', function () {
- client.end(true, function () {
- client.subscribe(topic, { qos: 2 }, function (err, granted) {
- assert.notExists(granted, 'granted given')
- assert.exists(err, 'no error given')
- done()
- })
- })
- })
- })
-
- it('should fire a callback with error if disconnected (options not provided)', function (done) {
- const client = connect()
- const topic = 'test'
-
- client.once('connect', function () {
- client.end(true, function () {
- client.subscribe(topic, function (err, granted) {
- assert.notExists(granted, 'granted given')
- assert.exists(err, 'no error given')
- done()
- })
- })
- })
- })
-
- it('should subscribe with a chinese topic', function (done) {
- const client = connect()
- const topic = 'ä¸å›½'
-
- client.once('connect', function () {
- client.subscribe(topic)
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('subscribe', function (packet) {
- const result = {
- topic,
- qos: 0
- }
- if (version === 5) {
- result.nl = false
- result.rap = false
- result.rh = 0
- }
- assert.include(packet.subscriptions[0], result)
- client.end(done)
- })
- })
- })
- })
-
- describe('receiving messages', function () {
- it('should fire the message event', function (done) {
- const client = connect()
- const testPacket = {
- topic: 'test',
- payload: 'message',
- retain: true,
- qos: 1,
- messageId: 5
- }
-
- //
- client.subscribe(testPacket.topic)
- client.once('message', function (topic, message, packet) {
- assert.strictEqual(topic, testPacket.topic)
- assert.strictEqual(message.toString(), testPacket.payload)
- assert.strictEqual(packet.cmd, 'publish')
- client.end(true, done)
- })
-
- server.once('client', function (serverClient) {
- serverClient.on('subscribe', function () {
- serverClient.publish(testPacket)
- })
- })
- })
-
- it('should emit a packetreceive event', function (done) {
- const client = connect()
- const testPacket = {
- topic: 'test',
- payload: 'message',
- retain: true,
- qos: 1,
- messageId: 5
- }
-
- client.subscribe(testPacket.topic)
- client.on('packetreceive', function (packet) {
- if (packet.cmd === 'publish') {
- assert.strictEqual(packet.qos, 1)
- assert.strictEqual(packet.topic, testPacket.topic)
- assert.strictEqual(packet.payload.toString(), testPacket.payload)
- assert.strictEqual(packet.retain, true)
- client.end(true, done)
- }
- })
-
- server.once('client', function (serverClient) {
- serverClient.on('subscribe', function () {
- serverClient.publish(testPacket)
- })
- })
- })
-
- it('should support binary data', function (done) {
- const client = connect({ encoding: 'binary' })
- const testPacket = {
- topic: 'test',
- payload: 'message',
- retain: true,
- qos: 1,
- messageId: 5
- }
-
- client.subscribe(testPacket.topic)
- client.once('message', function (topic, message, packet) {
- assert.strictEqual(topic, testPacket.topic)
- assert.instanceOf(message, Buffer)
- assert.strictEqual(message.toString(), testPacket.payload)
- assert.strictEqual(packet.cmd, 'publish')
- client.end(true, done)
- })
-
- server.once('client', function (serverClient) {
- serverClient.on('subscribe', function () {
- serverClient.publish(testPacket)
- })
- })
- })
-
- it('should emit a message event (qos=2)', function (done) {
- const client = connect()
- const testPacket = {
- topic: 'test',
- payload: 'message',
- retain: true,
- qos: 2,
- messageId: 5
- }
-
- server.testPublish = testPacket
-
- client.subscribe(testPacket.topic)
- client.once('message', function (topic, message, packet) {
- assert.strictEqual(topic, testPacket.topic)
- assert.strictEqual(message.toString(), testPacket.payload)
- assert.strictEqual(packet.messageId, testPacket.messageId)
- assert.strictEqual(packet.qos, testPacket.qos)
- client.end(true, done)
- })
-
- server.once('client', function (serverClient) {
- serverClient.on('subscribe', function () {
- serverClient.publish(testPacket)
- })
- })
- })
-
- it('should emit a message event (qos=2) - repeated publish', function (done) {
- const client = connect()
- const testPacket = {
- topic: 'test',
- payload: 'message',
- retain: true,
- qos: 2,
- messageId: 5
- }
-
- server.testPublish = testPacket
-
- const messageHandler = function (topic, message, packet) {
- assert.strictEqual(topic, testPacket.topic)
- assert.strictEqual(message.toString(), testPacket.payload)
- assert.strictEqual(packet.messageId, testPacket.messageId)
- assert.strictEqual(packet.qos, testPacket.qos)
-
- assert.strictEqual(spiedMessageHandler.callCount, 1)
- client.end(true, done)
- }
-
- const spiedMessageHandler = sinon.spy(messageHandler)
-
- client.subscribe(testPacket.topic)
- client.on('message', spiedMessageHandler)
-
- server.once('client', function (serverClient) {
- serverClient.on('subscribe', function () {
- serverClient.publish(testPacket)
- // twice, should be ignored
- serverClient.publish(testPacket)
- })
- })
- })
-
- it('should support a chinese topic', function (done) {
- const client = connect({ encoding: 'binary' })
- const testPacket = {
- topic: '国',
- payload: 'message',
- retain: true,
- qos: 1,
- messageId: 5
- }
-
- client.subscribe(testPacket.topic)
- client.once('message', function (topic, message, packet) {
- assert.strictEqual(topic, testPacket.topic)
- assert.instanceOf(message, Buffer)
- assert.strictEqual(message.toString(), testPacket.payload)
- assert.strictEqual(packet.messageId, testPacket.messageId)
- assert.strictEqual(packet.qos, testPacket.qos)
- client.end(true, done)
- })
-
- server.once('client', function (serverClient) {
- serverClient.on('subscribe', function () {
- serverClient.publish(testPacket)
- })
- })
- })
- })
-
- describe('qos handling', function () {
- it('should follow qos 0 semantics (trivial)', function (done) {
- const client = connect()
- const testTopic = 'test'
- const testMessage = 'message'
-
- client.once('connect', function () {
- client.subscribe(testTopic, { qos: 0 }, () => {
- client.end(true, done)
- })
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('subscribe', function () {
- serverClient.publish({
- topic: testTopic,
- payload: testMessage,
- qos: 0,
- retain: false
- })
- })
- })
- })
-
- it('should follow qos 1 semantics', function (done) {
- const client = connect()
- const testTopic = 'test'
- const testMessage = 'message'
- const mid = 50
-
- client.once('connect', function () {
- client.subscribe(testTopic, { qos: 1 })
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('subscribe', function () {
- serverClient.publish({
- topic: testTopic,
- payload: testMessage,
- messageId: mid,
- qos: 1
- })
- })
-
- serverClient.once('puback', function (packet) {
- assert.strictEqual(packet.messageId, mid)
- client.end(done)
- })
- })
- })
-
- it('should follow qos 2 semantics', function (done) {
- const client = connect()
- const testTopic = 'test'
- const testMessage = 'message'
- const mid = 253
- let publishReceived = 0
- let pubrecReceived = 0
- let pubrelReceived = 0
-
- client.once('connect', function () {
- client.subscribe(testTopic, { qos: 2 })
- })
-
- client.on('packetreceive', (packet) => {
- switch (packet.cmd) {
- case 'connack':
- case 'suback':
- // expected, but not specifically part of QOS 2 semantics
- break
- case 'publish':
- assert.strictEqual(pubrecReceived, 0, 'server received pubrec before client sent')
- assert.strictEqual(pubrelReceived, 0, 'server received pubrec before client sent')
- publishReceived += 1
- break
- case 'pubrel':
- assert.strictEqual(publishReceived, 1, 'only 1 publish must be received before a pubrel')
- assert.strictEqual(pubrecReceived, 1, 'invalid number of PUBREC messages (not only 1)')
- pubrelReceived += 1
- break
- default:
- should.fail()
- }
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('subscribe', function () {
- serverClient.publish({
- topic: testTopic,
- payload: testMessage,
- qos: 2,
- messageId: mid
- })
- })
-
- serverClient.on('pubrec', function () {
- assert.strictEqual(publishReceived, 1, 'invalid number of PUBLISH messages received')
- assert.strictEqual(pubrecReceived, 0, 'invalid number of PUBREC messages recevied')
- pubrecReceived += 1
- })
-
- serverClient.once('pubcomp', function () {
- client.removeAllListeners()
- serverClient.removeAllListeners()
- assert.strictEqual(publishReceived, 1, 'invalid number of PUBLISH messages')
- assert.strictEqual(pubrecReceived, 1, 'invalid number of PUBREC messages')
- assert.strictEqual(pubrelReceived, 1, 'invalid nubmer of PUBREL messages')
- client.end(true, done)
- })
- })
- })
-
- it('should should empty the incoming store after a qos 2 handshake is completed', function (done) {
- const client = connect()
- const testTopic = 'test'
- const testMessage = 'message'
- const mid = 253
-
- client.once('connect', function () {
- client.subscribe(testTopic, { qos: 2 })
- })
-
- client.on('packetreceive', (packet) => {
- if (packet.cmd === 'pubrel') {
- assert.strictEqual(client.incomingStore._inflights.size, 1)
- }
- })
-
- server.once('client', function (serverClient) {
- serverClient.once('subscribe', function () {
- serverClient.publish({
- topic: testTopic,
- payload: testMessage,
- qos: 2,
- messageId: mid
- })
- })
-
- serverClient.once('pubcomp', function () {
- assert.strictEqual(client.incomingStore._inflights.size, 0)
- client.removeAllListeners()
- client.end(true, done)
- })
- })
- })
-
- function testMultiplePubrel(shouldSendPubcompFail, done) {
- const client = connect()
- const testTopic = 'test'
- const testMessage = 'message'
- const mid = 253
- let pubcompCount = 0
- let pubrelCount = 0
- let handleMessageCount = 0
- let emitMessageCount = 0
- const origSendPacket = client._sendPacket
- let shouldSendFail
-
- client.handleMessage = function (packet, callback) {
- handleMessageCount++
- callback()
- }
-
- client.on('message', function () {
- emitMessageCount++
- })
-
- client._sendPacket = function (packet, sendDone) {
- shouldSendFail = packet.cmd === 'pubcomp' && shouldSendPubcompFail
- if (sendDone) {
- sendDone(shouldSendFail ? new Error('testing pubcomp failure') : undefined)
- }
-
- // send the mocked response
- switch (packet.cmd) {
- case 'subscribe': {
- const suback = { cmd: 'suback', messageId: packet.messageId, granted: [2] }
- handle(client, suback, function (err) {
- assert.isNotOk(err)
- })
- break
- }
- 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
- }
- }
-
- // simulate the pubrel message, either in response to pubrec or to mock pubcomp failing to be received
- const pubrel = { cmd: 'pubrel', messageId: mid }
- pubrelCount++
- handle(client, pubrel, function (err) {
- if (shouldSendFail) {
- assert.exists(err)
- assert.instanceOf(err, Error)
- } else {
- assert.notExists(err)
- }
- })
- break
- }
- }
- }
-
- client.once('connect', function () {
- client.subscribe(testTopic, { qos: 2 })
- const publish = { cmd: 'publish', topic: testTopic, payload: testMessage, qos: 2, messageId: mid }
- handle(client, publish, function (err) {
- assert.notExists(err)
- })
- })
- }
-
- it('handle qos 2 messages exactly once when multiple pubrel received', function (done) {
- testMultiplePubrel(false, done)
- })
-
- it('handle qos 2 messages exactly once when multiple pubrel received and sending pubcomp fails on client', function (done) {
- testMultiplePubrel(true, done)
- })
- })
-
- describe('auto reconnect', function () {
- it('should mark the client disconnecting if #end called', function (done) {
- const client = connect()
-
- client.end(true, err => {
- assert.isTrue(client.disconnecting)
- done(err)
- })
- })
-
- it('should reconnect after stream disconnect', function (done) {
- const client = connect()
-
- let tryReconnect = true
-
- client.on('connect', function () {
- if (tryReconnect) {
- client.stream.end()
- tryReconnect = false
- } else {
- client.end(true, done)
- }
- })
- })
-
- it('should emit \'reconnect\' when reconnecting', function (done) {
- const client = connect()
- let tryReconnect = true
- let reconnectEvent = false
-
- client.on('reconnect', function () {
- reconnectEvent = true
- })
-
- client.on('connect', function () {
- if (tryReconnect) {
- client.stream.end()
- tryReconnect = false
- } else {
- assert.isTrue(reconnectEvent)
- client.end(true, done)
- }
- })
- })
-
- it('should emit \'offline\' after going offline', function (done) {
- const client = connect()
-
- let tryReconnect = true
- let offlineEvent = false
-
- client.on('offline', function () {
- offlineEvent = true
- })
-
- client.on('connect', function () {
- if (tryReconnect) {
- client.stream.end()
- tryReconnect = false
- } else {
- assert.isTrue(offlineEvent)
- client.end(true, done)
- }
- })
- })
-
- it('should not reconnect if it was ended by the user', function (done) {
- const client = connect()
-
- client.on('connect', function () {
- client.end()
- done() // it will raise an exception if called two times
- })
- })
-
- it('should setup a reconnect timer on disconnect', function (done) {
- const client = connect()
-
- client.once('connect', function () {
- assert.notExists(client.reconnectTimer)
- client.stream.end()
- })
-
- client.once('close', function () {
- assert.exists(client.reconnectTimer)
- client.end(true, done)
- })
- })
-
- const reconnectPeriodTests = [{ period: 200 }, { period: 2000 }, { period: 4000 }]
- reconnectPeriodTests.forEach((test) => {
- it('should allow specification of a reconnect period (' + test.period + 'ms)', function (done) {
- this.timeout(10000)
- let end
- const reconnectSlushTime = 200
- const client = connect({ reconnectPeriod: test.period })
- let reconnect = false
- const start = Date.now()
-
- client.on('connect', function () {
- if (!reconnect) {
- client.stream.end()
- reconnect = true
- } else {
- end = Date.now()
- client.end(() => {
- const reconnectPeriodDuringTest = end - start
- if (reconnectPeriodDuringTest >= test.period - reconnectSlushTime && reconnectPeriodDuringTest <= test.period + reconnectSlushTime) {
- // give the connection a 200 ms slush window
- done()
- } else {
- done(new Error('Strange reconnect period: ' + reconnectPeriodDuringTest))
- }
- })
- }
- })
- })
- })
-
- it('should always cleanup successfully on reconnection', function (done) {
- const client = connect({ host: 'this_hostname_should_not_exist', connectTimeout: 0, reconnectPeriod: 1 })
- // bind client.end so that when it is called it is automatically passed in the done callback
- setTimeout(() => {
- const boundEnd = client.end.bind(client, done)
- boundEnd()
- }, 50)
- })
-
- it('should resend in-flight QoS 1 publish messages from the client', function (done) {
- this.timeout(4000)
- const client = connect({ reconnectPeriod: 200 })
- let serverPublished = false
- let clientCalledBack = false
-
- server.once('client', function (serverClient) {
- serverClient.on('connect', function () {
- setImmediate(function () {
- serverClient.stream.destroy()
- })
- })
-
- server.once('client', function (serverClientNew) {
- serverClientNew.on('publish', function () {
- serverPublished = true
- check()
- })
- })
- })
-
- client.publish('hello', 'world', { qos: 1 }, function () {
- clientCalledBack = true
- check()
- })
-
- function check() {
- if (serverPublished && clientCalledBack) {
- client.end(true, done)
- }
- }
- })
-
- it('should not resend in-flight publish messages if disconnecting', function (done) {
- const client = connect({ reconnectPeriod: 200 })
- let serverPublished = false
- let clientCalledBack = false
- server.once('client', function (serverClient) {
- serverClient.on('connect', function () {
- setImmediate(function () {
- serverClient.stream.destroy()
- client.end(true, err => {
- assert.isFalse(serverPublished)
- assert.isFalse(clientCalledBack)
- done(err)
- })
- })
- })
- server.once('client', function (serverClientNew) {
- serverClientNew.on('publish', function () {
- serverPublished = true
- })
- })
- })
- client.publish('hello', 'world', { qos: 1 }, function () {
- clientCalledBack = true
- })
- })
-
- it('should resend in-flight QoS 2 publish messages from the client', function (done) {
- const client = connect({ reconnectPeriod: 200 })
- let serverPublished = false
- let clientCalledBack = false
-
- server.once('client', function (serverClient) {
- // ignore errors
- serverClient.on('error', function () { })
- serverClient.on('publish', function () {
- setImmediate(function () {
- serverClient.stream.destroy()
- })
- })
-
- server.once('client', function (serverClientNew) {
- serverClientNew.on('pubrel', function () {
- serverPublished = true
- check()
- })
- })
- })
-
- client.publish('hello', 'world', { qos: 2 }, function () {
- clientCalledBack = true
- check()
- })
-
- function check() {
- if (serverPublished && clientCalledBack) {
- client.end(true, done)
- }
- }
- })
-
- it('should not resend in-flight QoS 1 removed publish messages from the client', function (done) {
- const client = connect({ reconnectPeriod: 200 })
- let clientCalledBack = false
-
- server.once('client', function (serverClient) {
- serverClient.on('connect', function () {
- setImmediate(function () {
- serverClient.stream.destroy()
- })
- })
-
- server.once('client', function (serverClientNew) {
- serverClientNew.on('publish', function () {
- should.fail()
- done()
- })
- })
- })
-
- client.publish('hello', 'world', { qos: 1 }, function (err) {
- clientCalledBack = true
- assert.exists(err, 'error should exist')
- assert.strictEqual(err.message, 'Message removed', 'error message is incorrect')
- })
- assert.strictEqual(Object.keys(client.outgoing).length, 1)
- assert.strictEqual(client.outgoingStore._inflights.size, 1)
- client.removeOutgoingMessage(client.getLastMessageId())
- assert.strictEqual(Object.keys(client.outgoing).length, 0)
- assert.strictEqual(client.outgoingStore._inflights.size, 0)
- assert.isTrue(clientCalledBack)
- client.end(true, (err) => {
- done(err)
- })
- })
-
- it('should not resend in-flight QoS 2 removed publish messages from the client', function (done) {
- const client = connect({ reconnectPeriod: 200 })
- let clientCalledBack = false
-
- server.once('client', function (serverClient) {
- serverClient.on('connect', function () {
- setImmediate(function () {
- serverClient.stream.destroy()
- })
- })
-
- server.once('client', function (serverClientNew) {
- serverClientNew.on('publish', function () {
- should.fail()
- done()
- })
- })
- })
-
- client.publish('hello', 'world', { qos: 2 }, function (err) {
- clientCalledBack = true
- assert.strictEqual(err.message, 'Message removed')
- })
- assert.strictEqual(Object.keys(client.outgoing).length, 1)
- assert.strictEqual(client.outgoingStore._inflights.size, 1)
- client.removeOutgoingMessage(client.getLastMessageId())
- assert.strictEqual(Object.keys(client.outgoing).length, 0)
- assert.strictEqual(client.outgoingStore._inflights.size, 0)
- assert.isTrue(clientCalledBack)
- client.end(true, done)
- })
-
- it('should resubscribe when reconnecting', function (done) {
- const client = connect({ reconnectPeriod: 100 })
- let tryReconnect = true
- let reconnectEvent = false
-
- client.on('reconnect', function () {
- reconnectEvent = true
- })
-
- client.on('connect', function () {
- if (tryReconnect) {
- client.subscribe('hello', function () {
- client.stream.end()
-
- server.once('client', function (serverClient) {
- serverClient.on('subscribe', function () {
- client.end(done)
- })
- })
- })
-
- tryReconnect = false
- } else {
- assert.isTrue(reconnectEvent)
- }
- })
- })
-
- it('should not resubscribe when reconnecting if resubscribe is disabled', function (done) {
- const client = connect({ reconnectPeriod: 100, resubscribe: false })
- let tryReconnect = true
- let reconnectEvent = false
-
- client.on('reconnect', function () {
- reconnectEvent = true
- })
-
- client.on('connect', function () {
- if (tryReconnect) {
- client.subscribe('hello', function () {
- client.stream.end()
-
- server.once('client', function (serverClient) {
- serverClient.on('subscribe', function () {
- should.fail()
- })
- })
- })
-
- tryReconnect = false
- } else {
- assert.isTrue(reconnectEvent)
- assert.strictEqual(Object.keys(client._resubscribeTopics).length, 0)
- client.end(true, done)
- }
- })
- })
-
- it('should not resubscribe when reconnecting if suback is error', function (done) {
- let tryReconnect = true
- let reconnectEvent = false
- const server2 = serverBuilder(config.protocol, function (serverClient) {
- serverClient.on('connect', function (packet) {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- })
- serverClient.on('subscribe', function (packet) {
- serverClient.suback({
- messageId: packet.messageId,
- granted: packet.subscriptions.map(function (e) {
- return e.qos | 0x80
- })
- })
- serverClient.pubrel({ messageId: Math.floor(Math.random() * 9000) + 1000 })
- })
- })
-
- server2.listen(ports.PORTAND49, function () {
- const client = connect({
- port: ports.PORTAND49,
- host: 'localhost',
- reconnectPeriod: 100
- })
-
- client.on('reconnect', function () {
- reconnectEvent = true
- })
-
- client.on('connect', function () {
- if (tryReconnect) {
- client.subscribe('hello', function () {
- client.stream.end()
-
- server.once('client', function (serverClient) {
- serverClient.on('subscribe', function () {
- should.fail()
- })
- })
- })
- tryReconnect = false
- } else {
- assert.isTrue(reconnectEvent)
- assert.strictEqual(Object.keys(client._resubscribeTopics).length, 0)
- client.end(true, (err1) => {
- server2.close((err2) => done(err1 || err2))
- })
- }
- })
- })
- })
-
- it('should preserved incomingStore after disconnecting if clean is false', function (done) {
- let reconnect = false
- let client = {}
- const incomingStore = new mqtt.Store({ clean: false })
- const outgoingStore = new mqtt.Store({ clean: false })
- const server2 = serverBuilder(config.protocol, function (serverClient) {
- serverClient.on('connect', function (packet) {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- if (reconnect) {
- serverClient.pubrel({ messageId: 1 })
- }
- })
- serverClient.on('subscribe', function (packet) {
- serverClient.suback({
- messageId: packet.messageId,
- granted: packet.subscriptions.map(function (e) {
- return e.qos
- })
- })
- serverClient.publish({ topic: 'topic', payload: 'payload', qos: 2, messageId: 1, retain: false })
- })
- serverClient.on('pubrec', function (packet) {
- client.end(false, function () {
- client.reconnect({
- incomingStore,
- outgoingStore
- })
- })
- })
- serverClient.on('pubcomp', function (packet) {
- client.end(true, (err1) => {
- server2.close((err2) => done(err1 || err2))
- })
- })
- })
-
- server2.listen(ports.PORTAND50, function () {
- client = connect({
- port: ports.PORTAND50,
- host: 'localhost',
- clean: false,
- clientId: 'cid1',
- reconnectPeriod: 0,
- incomingStore,
- outgoingStore
- })
-
- client.on('connect', function () {
- if (!reconnect) {
- client.subscribe('test', { qos: 2 }, function () {
- })
- reconnect = true
- }
- })
- client.on('message', function (topic, message) {
- assert.strictEqual(topic, 'topic')
- assert.strictEqual(message.toString(), 'payload')
- })
- })
- })
-
- it('should clear outgoing if close from server', function (done) {
- let reconnect = false
- let client = {}
- const server2 = serverBuilder(config.protocol, function (serverClient) {
- serverClient.on('connect', function (packet) {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- })
- serverClient.on('subscribe', function (packet) {
- if (reconnect) {
- serverClient.suback({
- messageId: packet.messageId,
- granted: packet.subscriptions.map(function (e) {
- return e.qos
- })
- })
- } else {
- serverClient.destroy()
- }
- })
- })
-
- server2.listen(ports.PORTAND50, function () {
- client = connect({
- port: ports.PORTAND50,
- host: 'localhost',
- clean: true,
- clientId: 'cid1',
- keepalive: 1,
- reconnectPeriod: 0
- })
-
- client.on('connect', function () {
- client.subscribe('test', { qos: 2 }, function (e) {
- if (!e) {
- client.end()
- }
- })
- })
-
- client.on('close', function () {
- if (reconnect) {
- server2.close((err) => done(err))
- } else {
- assert.strictEqual(Object.keys(client.outgoing).length, 0)
- reconnect = true
- client.reconnect()
- }
- })
- })
- })
-
- it('should resend in-flight QoS 1 publish messages from the client if clean is false', function (done) {
- let reconnect = false
- let client = {}
- const incomingStore = new mqtt.Store({ clean: false })
- const outgoingStore = new mqtt.Store({ clean: false })
- const server2 = serverBuilder(config.protocol, function (serverClient) {
- serverClient.on('connect', function (packet) {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- })
- serverClient.on('publish', function (packet) {
- if (reconnect) {
- client.end(true, (err1) => {
- server2.close((err2) => done(err1 || err2))
- })
- } else {
- client.end(true, () => {
- client.reconnect({
- incomingStore,
- outgoingStore
- })
- reconnect = true
- })
- }
- })
- })
-
- server2.listen(ports.PORTAND50, function () {
- client = connect({
- port: ports.PORTAND50,
- host: 'localhost',
- clean: false,
- clientId: 'cid1',
- reconnectPeriod: 0,
- incomingStore,
- outgoingStore
- })
-
- client.on('connect', function () {
- if (!reconnect) {
- client.publish('topic', 'payload', { qos: 1 })
- }
- })
- client.on('error', function () { })
- })
- })
-
- it('should resend in-flight QoS 2 publish messages from the client if clean is false', function (done) {
- let reconnect = false
- let client = {}
- const incomingStore = new mqtt.Store({ clean: false })
- const outgoingStore = new mqtt.Store({ clean: false })
- const server2 = serverBuilder(config.protocol, function (serverClient) {
- serverClient.on('connect', function (packet) {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- })
- serverClient.on('publish', function (packet) {
- if (reconnect) {
- client.end(true, (err1) => {
- server2.close((err2) => done(err1 || err2))
- })
- } else {
- client.end(true, function () {
- client.reconnect({
- incomingStore,
- outgoingStore
- })
- reconnect = true
- })
- }
- })
- })
-
- server2.listen(ports.PORTAND50, function () {
- client = connect({
- port: ports.PORTAND50,
- host: 'localhost',
- clean: false,
- clientId: 'cid1',
- reconnectPeriod: 0,
- incomingStore,
- outgoingStore
- })
-
- client.on('connect', function () {
- if (!reconnect) {
- client.publish('topic', 'payload', { qos: 2 })
- }
- })
- client.on('error', function () { })
- })
- })
-
- it('should resend in-flight QoS 2 pubrel messages from the client if clean is false', function (done) {
- let reconnect = false
- let client = {}
- const incomingStore = new mqtt.Store({ clean: false })
- const outgoingStore = new mqtt.Store({ clean: false })
- const server2 = serverBuilder(config.protocol, function (serverClient) {
- serverClient.on('connect', function (packet) {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- })
- serverClient.on('publish', function (packet) {
- if (!reconnect) {
- serverClient.pubrec({ messageId: packet.messageId })
- }
- })
- serverClient.on('pubrel', function (packet) {
- if (reconnect) {
- serverClient.pubcomp({ messageId: packet.messageId })
- } else {
- client.end(true, function () {
- client.reconnect({
- incomingStore,
- outgoingStore
- })
- reconnect = true
- })
- }
- })
- })
-
- server2.listen(ports.PORTAND50, function () {
- client = connect({
- port: ports.PORTAND50,
- host: 'localhost',
- clean: false,
- clientId: 'cid1',
- reconnectPeriod: 0,
- incomingStore,
- outgoingStore
- })
-
- client.on('connect', function () {
- if (!reconnect) {
- client.publish('topic', 'payload', { qos: 2 }, function (err) {
- assert(reconnect)
- assert.ifError(err)
- client.end(true, (err1) => {
- server2.close((err2) => done(err1 || err2))
- })
- })
- }
- })
- client.on('error', function () { })
- })
- })
-
- it('should resend in-flight publish messages by published order', function (done) {
- let publishCount = 0
- let reconnect = false
- let disconnectOnce = true
- let client = {}
- const incomingStore = new mqtt.Store({ clean: false })
- const outgoingStore = new mqtt.Store({ clean: false })
- 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('connect', function (packet) {
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
- serverClient.connack(connack)
- })
- serverClient.on('publish', function (packet) {
- serverClient.puback({ messageId: packet.messageId })
- if (reconnect) {
- switch (publishCount++) {
- case 0:
- assert.strictEqual(packet.payload.toString(), 'payload1')
- break
- case 1:
- assert.strictEqual(packet.payload.toString(), 'payload2')
- break
- case 2:
- assert.strictEqual(packet.payload.toString(), 'payload3')
- client.end(true, (err1) => {
- server2.close((err2) => done(err1 || err2))
- })
- break
- }
- } else {
- if (disconnectOnce) {
- client.end(true, function () {
- reconnect = true
- client.reconnect({
- incomingStore,
- outgoingStore
- })
- })
- disconnectOnce = false
- }
- }
- })
- })
-
- server2.listen(ports.PORTAND50, function () {
- client = connect({
- port: ports.PORTAND50,
- host: 'localhost',
- clean: false,
- clientId: 'cid1',
- reconnectPeriod: 0,
- incomingStore,
- outgoingStore
- })
-
- client.nextId = 65535
-
- client.on('connect', function () {
- if (!reconnect) {
- client.publish('topic', 'payload1', { qos: 1 })
- client.publish('topic', 'payload2', { qos: 1 })
- client.publish('topic', 'payload3', { qos: 1 })
- }
- })
- client.on('error', function () { })
- })
- })
-
- it('should be able to pub/sub if reconnect() is called at close handler', function (done) {
- const client = connect({ reconnectPeriod: 0 })
- let tryReconnect = true
- let reconnectEvent = false
-
- client.on('close', function () {
- if (tryReconnect) {
- tryReconnect = false
- client.reconnect()
- } else {
- assert.isTrue(reconnectEvent)
- done()
- }
- })
-
- client.on('reconnect', function () {
- reconnectEvent = true
- })
-
- client.on('connect', function () {
- if (tryReconnect) {
- client.end()
- } else {
- client.subscribe('hello', function () {
- client.end()
- })
- }
- })
- })
-
- it('should be able to pub/sub if reconnect() is called at out of close handler', function (done) {
- const client = connect({ reconnectPeriod: 0 })
- let tryReconnect = true
- let reconnectEvent = false
-
- client.on('close', function () {
- if (tryReconnect) {
- tryReconnect = false
- setTimeout(function () {
- client.reconnect()
- }, 100)
- } else {
- assert.isTrue(reconnectEvent)
- done()
- }
- })
-
- client.on('reconnect', function () {
- reconnectEvent = true
- })
-
- client.on('connect', function () {
- if (tryReconnect) {
- client.end()
- } else {
- client.subscribe('hello', function () {
- client.end()
- })
- }
- })
- })
-
- context('with alternate server client', function () {
- let cachedClientListeners
- const connack = version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
-
- beforeEach(function () {
- cachedClientListeners = server.listeners('client')
- server.removeAllListeners('client')
- })
-
- afterEach(function () {
- server.removeAllListeners('client')
- cachedClientListeners.forEach(function (listener) {
- server.on('client', listener)
- })
- })
-
- it('should resubscribe even if disconnect is before suback', function (done) {
- const client = connect(Object.assign({ reconnectPeriod: 100 }, config))
- let subscribeCount = 0
- let connectCount = 0
-
- server.on('client', function (serverClient) {
- serverClient.on('connect', function () {
- connectCount++
- serverClient.connack(connack)
- })
-
- serverClient.on('subscribe', function () {
- subscribeCount++
-
- // disconnect before sending the suback on the first subscribe
- if (subscribeCount === 1) {
- client.stream.end()
- }
-
- // after the second connection, confirm that the only two
- // subscribes have taken place, then cleanup and exit
- if (connectCount >= 2) {
- assert.strictEqual(subscribeCount, 2)
- client.end(true, done)
- }
- })
- })
-
- client.subscribe('hello')
- })
-
- it('should resubscribe exactly once', function (done) {
- const client = connect(Object.assign({ reconnectPeriod: 100 }, config))
- let subscribeCount = 0
-
- server.on('client', function (serverClient) {
- serverClient.on('connect', function () {
- serverClient.connack(connack)
- })
-
- serverClient.on('subscribe', function () {
- subscribeCount++
-
- // disconnect before sending the suback on the first subscribe
- if (subscribeCount === 1) {
- client.stream.end()
- }
-
- // after the second connection, only two subs
- // subscribes have taken place, then cleanup and exit
- if (subscribeCount === 2) {
- client.end(true, done)
- }
- })
- })
-
- client.subscribe('hello')
- })
- })
- })
-
- describe('message id to subscription topic mapping', () => {
- it('should not create a mapping if resubscribe is disabled', function (done) {
- const client = connect({ resubscribe: false })
- client.subscribe('test1')
- client.subscribe('test2')
- assert.strictEqual(Object.keys(client.messageIdToTopic).length, 0)
- client.end(true, done)
- })
-
- it('should create a mapping for each subscribe call', function (done) {
- const client = connect()
- client.subscribe('test1')
- assert.strictEqual(Object.keys(client.messageIdToTopic).length, 1)
- client.subscribe('test2')
- assert.strictEqual(Object.keys(client.messageIdToTopic).length, 2)
-
- client.subscribe(['test3', 'test4'])
- assert.strictEqual(Object.keys(client.messageIdToTopic).length, 3)
- client.subscribe(['test5', 'test6'])
- assert.strictEqual(Object.keys(client.messageIdToTopic).length, 4)
-
- client.end(true, done)
- })
-
- it('should remove the mapping after suback', function (done) {
- const client = connect()
- client.once('connect', function () {
- client.subscribe('test1', { qos: 2 }, function () {
- assert.strictEqual(Object.keys(client.messageIdToTopic).length, 0)
-
- client.subscribe(['test2', 'test3'], { qos: 2 }, function () {
- assert.strictEqual(Object.keys(client.messageIdToTopic).length, 0)
- client.end(done)
- })
- })
- })
- })
- })
+ * These tests try to be consistent with names for servers (brokers) and clients,
+ * but it can be confusing. To make it easier, here is a handy translation
+ * chart:
+ *
+ * name | meaning
+ * ---------------|--------
+ * client | The MQTT.js client object being tested. A new instance is created for each test (by calling the `connect` function.)
+ * server | A mock broker that you can control. The same server instance is used for all tests, so only use this if you plan to clean up when you're done.
+ * serverBuilder | A factory that can make mock test servers (MQTT brokers). Useful if you need to do things that you can't (or don't want to) clean up after your test is done.
+ * server2 | The name used for mock brokers that are created for an individual test and then destroyed.
+ * serverClient | An socket on the mock broker. This gets created when your client connects and gets collected when you're done with it.
+ *
+ * Also worth noting:
+ *
+ * `serverClient.disconnect()` does not disconnect that socket. Instead, it sends an MQTT disconnect packet.
+ * If you want to disconnect the socket from the broker side, you probably want to use `serverClient.destroy()`
+ * or `serverClient.stream.destroy()`.
+ *
+ */
+
+module.exports = (server, config) => {
+ const version = config.protocolVersion || 4
+
+ function connect(opts) {
+ opts = { ...config, ...opts }
+ return mqtt.connect(opts)
+ }
+
+ describe('closing', () => {
+ it('should emit close if stream closes', function _test(done) {
+ const client = connect()
+
+ client.once('connect', () => {
+ client.stream.end()
+ })
+ client.once('close', () => {
+ client.end((err) => done(err))
+ })
+ })
+
+ it('should mark the client as disconnected', function _test(done) {
+ const client = connect()
+
+ client.once('close', () => {
+ client.end((err) => {
+ if (!client.connected) {
+ done(err)
+ } else {
+ done(new Error('Not marked as disconnected'))
+ }
+ })
+ assert.isFalse(client.connected)
+ })
+ client.once('connect', () => {
+ client.stream.end()
+ })
+ })
+
+ it('should stop ping timer if stream closes', function _test(done) {
+ const client = connect()
+
+ client.once('close', () => {
+ assert.notExists(client.pingTimer)
+ client.end(true, (err) => done(err))
+ })
+
+ client.once('connect', () => {
+ assert.exists(client.pingTimer)
+ client.stream.end()
+ })
+ })
+
+ it('should emit close after end called', function _test(done) {
+ const client = connect()
+
+ client.once('close', () => {
+ done()
+ })
+
+ client.once('connect', () => {
+ client.end()
+ })
+ })
+
+ it('should emit end after end called and client must be disconnected', function _test(done) {
+ const client = connect()
+
+ client.once('end', () => {
+ if (client.disconnected) {
+ return done()
+ }
+ done(new Error('client must be disconnected'))
+ })
+
+ client.once('connect', () => {
+ client.end()
+ })
+ })
+
+ it('should pass store close error to end callback but not to end listeners (incomingStore)', function _test(done) {
+ const store = new Store()
+ const client = connect({ incomingStore: store })
+
+ store.close = (cb) => {
+ cb(new Error('test'))
+ }
+ client.once('end', (...args) => {
+ if (args.length === 0) {
+ return
+ }
+ throw new Error('no argument should be passed to event')
+ })
+
+ client.once('connect', () => {
+ client.end((testError) => {
+ if (testError && testError.message === 'test') {
+ return done()
+ }
+ throw new Error('bad argument passed to callback')
+ })
+ })
+ })
+
+ it('should pass store close error to end callback but not to end listeners (outgoingStore)', function _test(done) {
+ const store = new Store()
+ const client = connect({ outgoingStore: store })
+
+ store.close = (cb) => {
+ cb(new Error('test'))
+ }
+ client.once('end', (...args) => {
+ if (args.length === 0) {
+ return
+ }
+ throw new Error('no argument should be passed to event')
+ })
+
+ client.once('connect', () => {
+ client.end((testError) => {
+ if (testError && testError.message === 'test') {
+ return done()
+ }
+ throw new Error('bad argument passed to callback')
+ })
+ })
+ })
+
+ it('should return `this` if end called twice', function _test(done) {
+ const client = connect()
+
+ client.once('connect', () => {
+ client.end()
+ const value = client.end()
+ if (value === client) {
+ done()
+ } else {
+ done(new Error('Not returning client.'))
+ }
+ })
+ })
+
+ it('should emit end only on first client end', function _test(done) {
+ const client = connect()
+
+ client.once('end', () => {
+ const timeout = setTimeout(() => done(), 200)
+ client.once('end', () => {
+ clearTimeout(timeout)
+ done(new Error('end was emitted twice'))
+ })
+ client.end()
+ })
+
+ client.once('connect', () => {
+ client.end()
+ })
+ })
+
+ it('should stop ping timer after end called', function _test(done) {
+ const client = connect()
+
+ client.once('connect', () => {
+ assert.exists(client.pingTimer)
+ client.end((err) => {
+ assert.notExists(client.pingTimer)
+ done(err)
+ })
+ })
+ })
+
+ it('should be able to end even on a failed connection', function _test(done) {
+ const client = connect({ host: 'this_hostname_should_not_exist' })
+
+ const timeout = setTimeout(() => {
+ done(new Error('Failed to end a disconnected client'))
+ }, 500)
+
+ setTimeout(() => {
+ client.end((err) => {
+ clearTimeout(timeout)
+ done(err)
+ })
+ }, 200)
+ })
+
+ it('should emit end even on a failed connection', function _test(done) {
+ const client = connect({ host: 'this_hostname_should_not_exist' })
+
+ const timeout = setTimeout(() => {
+ done(new Error('Disconnected client has failed to emit end'))
+ }, 500)
+
+ client.once('end', () => {
+ clearTimeout(timeout)
+ done()
+ })
+
+ // after 200ms manually invoke client.end
+ setTimeout(() => {
+ client.end.call(client)
+ }, 200)
+ })
+
+ it.skip('should emit end only once for a reconnecting client', function _test(done) {
+ // I want to fix this test, but it will take signficant work, so I am marking it as a skipping test right now.
+ // Reason for it is that there are overlaps in the reconnectTimer and connectTimer. In the PR for this code
+ // there will be gists showing the difference between a successful test here and a failed test. For now we
+ // will add the retries syntax because of the flakiness.
+ const client = connect({
+ host: 'this_hostname_should_not_exist',
+ connectTimeout: 10,
+ reconnectPeriod: 20,
+ })
+ setTimeout(() => done(), 1000)
+ const endCallback = () => {
+ assert.strictEqual(
+ spy.callCount,
+ 1,
+ 'end was emitted more than once for reconnecting client',
+ )
+ }
+
+ const spy = sinon.spy(endCallback)
+ client.on('end', spy)
+ setTimeout(() => {
+ client.end.call(client)
+ }, 300)
+ })
+ })
+
+ describe('connecting', () => {
+ it('should connect to the broker', function _test(done) {
+ const client = connect()
+ client.on('error', done)
+
+ server.once('client', () => {
+ client.end((err) => done(err))
+ })
+ })
+
+ it('should send a default client id', function _test(done) {
+ const client = connect()
+ client.on('error', done)
+
+ server.once('client', (serverClient) => {
+ serverClient.once('connect', (packet) => {
+ assert.include(packet.clientId, 'mqttjs')
+ client.end((err) => done(err))
+ })
+ })
+ })
+
+ it('should send be clean by default', function _test(done) {
+ const client = connect()
+ client.on('error', done)
+
+ server.once('client', (serverClient) => {
+ serverClient.once('connect', (packet) => {
+ assert.strictEqual(packet.clean, true)
+ done()
+ })
+ })
+ })
+
+ it('should connect with the given client id', function _test(done) {
+ const client = connect({ clientId: 'testclient' })
+ client.on('error', (err) => {
+ throw err
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('connect', (packet) => {
+ assert.include(packet.clientId, 'testclient')
+ client.end((err) => done(err))
+ })
+ })
+ })
+
+ it('should connect with the client id and unclean state', function _test(done) {
+ const client = connect({ clientId: 'testclient', clean: false })
+ client.on('error', (err) => {
+ throw err
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('connect', (packet) => {
+ assert.include(packet.clientId, 'testclient')
+ assert.isFalse(packet.clean)
+ client.end(false, (err) => done(err))
+ })
+ })
+ })
+
+ it('should require a clientId with clean=false', function _test(done) {
+ try {
+ const client = connect({ clean: false })
+ client.on('error', (err) => {
+ done(err)
+ })
+ } catch (err) {
+ assert.strictEqual(
+ err.message,
+ 'Missing clientId for unclean clients',
+ )
+ done()
+ }
+ })
+
+ it('should default to localhost', function _test(done) {
+ const client = connect({ clientId: 'testclient' })
+ client.on('error', (err) => {
+ throw err
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('connect', (packet) => {
+ assert.include(packet.clientId, 'testclient')
+ done()
+ })
+ })
+ })
+
+ it('should emit connect', function _test(done) {
+ const client = connect()
+ client.once('connect', () => {
+ client.end(true, (err) => done(err))
+ })
+ client.once('error', done)
+ })
+
+ it('should provide connack packet with connect event', function _test(done) {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ server.once('client', (serverClient) => {
+ connack.sessionPresent = true
+ serverClient.connack(connack)
+ server.once('client', (serverClient2) => {
+ connack.sessionPresent = false
+ serverClient2.connack(connack)
+ })
+ })
+
+ const client = connect()
+ client.once('connect', (packet) => {
+ assert.strictEqual(packet.sessionPresent, true)
+ client.once('connect', (packet2) => {
+ assert.strictEqual(packet2.sessionPresent, false)
+ client.end((err) => done(err))
+ })
+ })
+ })
+
+ it('should mark the client as connected', function _test(done) {
+ const client = connect()
+ client.once('connect', () => {
+ assert.isTrue(client.connected)
+ client.end((err) => done(err))
+ })
+ })
+
+ it('should emit error on invalid clientId', function _test(done) {
+ const client = connect({ clientId: 'invalid' })
+ client.once('connect', () => {
+ done(new Error('Should not emit connect'))
+ })
+ client.once('error', (error) => {
+ const value = version === 5 ? 128 : 2
+ assert.strictEqual(error.code, value) // code for clientID identifer rejected
+ client.end((err) => done(err))
+ })
+ })
+
+ it('should emit error event if the socket refuses the connection', function _test(done) {
+ // fake a port
+ const client = connect({ port: 4557 })
+
+ client.on('error', (e) => {
+ assert.equal(e.code, 'ECONNREFUSED')
+ client.end((err) => done(err))
+ })
+ })
+
+ it('should have different client ids', function _test(done) {
+ // bug identified in this test: the client.end callback is invoked twice, once when the `end`
+ // method completes closing the stores and invokes the callback, and another time when the
+ // stream is closed. When the stream is closed, for some reason the closeStores method is called
+ // a second time.
+ const client1 = connect()
+ const client2 = connect()
+
+ assert.notStrictEqual(
+ client1.options.clientId,
+ client2.options.clientId,
+ )
+ client1.end(true, () => {
+ client2.end(true, () => {
+ done()
+ })
+ })
+ })
+ })
+
+ describe('handling offline states', () => {
+ it('should emit offline event once when the client transitions from connected states to disconnected ones', function _test(done) {
+ const client = connect({ reconnectPeriod: 20 })
+
+ client.on('connect', () => {
+ client.stream.end()
+ })
+
+ client.on('offline', () => {
+ client.end(true, done)
+ })
+ })
+
+ it('should emit offline event once when the client (at first) can NOT connect to servers', function _test(done) {
+ // fake a port
+ const client = connect({ reconnectPeriod: 20, port: 4557 })
+
+ client.on('error', () => {})
+
+ client.on('offline', () => {
+ client.end(true, done)
+ })
+ })
+ })
+
+ describe('topic validations when subscribing', () => {
+ it('should be ok for well-formated topics', function _test(done) {
+ const client = connect()
+ client.subscribe(
+ [
+ '+',
+ '+/event',
+ 'event/+',
+ '#',
+ 'event/#',
+ 'system/event/+',
+ 'system/+/event',
+ 'system/registry/event/#',
+ 'system/+/event/#',
+ 'system/registry/event/new_device',
+ 'system/+/+/new_device',
+ ],
+ (err) => {
+ client.end(() => {
+ if (err) {
+ return done(new Error(err))
+ }
+ done()
+ })
+ },
+ )
+ })
+
+ it('should return an error (via callbacks) for topic #/event', function _test(done) {
+ const client = connect()
+ client.subscribe(['#/event', 'event#', 'event+'], (err) => {
+ client.end(false, () => {
+ if (err) {
+ return done()
+ }
+ done(new Error('Validations do NOT work'))
+ })
+ })
+ })
+
+ it('should return an empty array for duplicate subs', function _test(done) {
+ const client = connect()
+ client.subscribe('event', (err, granted1) => {
+ if (err) {
+ return done(err)
+ }
+ client.subscribe('event', (err2, granted2) => {
+ if (err2) {
+ return done(err2)
+ }
+ assert.isArray(granted2)
+ assert.isEmpty(granted2)
+ done()
+ })
+ })
+ })
+
+ it('should return an error (via callbacks) for topic #/event', function _test(done) {
+ const client = connect()
+ client.subscribe('#/event', (err) => {
+ client.end(() => {
+ if (err) {
+ return done()
+ }
+ done(new Error('Validations do NOT work'))
+ })
+ })
+ })
+
+ it('should return an error (via callbacks) for topic event#', function _test(done) {
+ const client = connect()
+ client.subscribe('event#', (err) => {
+ client.end(() => {
+ if (err) {
+ return done()
+ }
+ done(new Error('Validations do NOT work'))
+ })
+ })
+ })
+
+ it('should return an error (via callbacks) for topic system/#/event', function _test(done) {
+ const client = connect()
+ client.subscribe('system/#/event', (err) => {
+ client.end(() => {
+ if (err) {
+ return done()
+ }
+ done(new Error('Validations do NOT work'))
+ })
+ })
+ })
+
+ it('should return an error (via callbacks) for empty topic list', function _test(done) {
+ const client = connect()
+ client.subscribe([], (subErr) => {
+ client.end((endErr) => {
+ if (subErr) {
+ return done(endErr)
+ }
+ done(new Error('Validations do NOT work'))
+ })
+ })
+ })
+
+ it('should return an error (via callbacks) for topic system/+/#/event', function _test(done) {
+ const client = connect()
+ client.subscribe('system/+/#/event', (subErr) => {
+ client.end(true, (endErr) => {
+ if (subErr) {
+ return done(endErr)
+ }
+ done(new Error('Validations do NOT work'))
+ })
+ })
+ })
+ })
+
+ describe('offline messages', () => {
+ it('should queue message until connected', function _test(done) {
+ const client = connect()
+
+ client.publish('test', 'test')
+ client.subscribe('test')
+ client.unsubscribe('test')
+ assert.strictEqual(client.queue.length, 3)
+
+ client.once('connect', () => {
+ assert.strictEqual(client.queue.length, 0)
+ setTimeout(() => {
+ client.end(true, done)
+ }, 10)
+ })
+ })
+
+ it('should not queue qos 0 messages if queueQoSZero is false', function _test(done) {
+ const client = connect({ queueQoSZero: false })
+
+ client.publish('test', 'test', { qos: 0 })
+ assert.strictEqual(client.queue.length, 0)
+ client.on('connect', () => {
+ setTimeout(() => {
+ client.end(true, done)
+ }, 10)
+ })
+ })
+
+ it('should queue qos != 0 messages', function _test(done) {
+ const client = connect({ queueQoSZero: false })
+
+ client.publish('test', 'test', { qos: 1 })
+ client.publish('test', 'test', { qos: 2 })
+ client.subscribe('test')
+ client.unsubscribe('test')
+ assert.strictEqual(client.queue.length, 2)
+ client.on('connect', () => {
+ setTimeout(() => {
+ client.end(true, done)
+ }, 10)
+ })
+ })
+
+ it('should not interrupt messages', function _test(done) {
+ let client = null
+ let publishCount = 0
+ const incomingStore = new mqtt.Store({ clean: false })
+ const outgoingStore = new mqtt.Store({ clean: false })
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ serverClient.on('connect', () => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ })
+ serverClient.on('publish', (packet) => {
+ if (packet.qos !== 0) {
+ serverClient.puback({ messageId: packet.messageId })
+ }
+ switch (publishCount++) {
+ case 0:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload1',
+ )
+ break
+ case 1:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload2',
+ )
+ break
+ case 2:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload3',
+ )
+ break
+ case 3:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload4',
+ )
+ client.end((err1) => {
+ server2.close((err2) => done(err1 || err2))
+ })
+ }
+ })
+ })
+
+ server2.listen(ports.PORTAND50, () => {
+ client = connect({
+ port: ports.PORTAND50,
+ host: 'localhost',
+ clean: false,
+ clientId: 'cid1',
+ reconnectPeriod: 0,
+ incomingStore,
+ outgoingStore,
+ queueQoSZero: true,
+ })
+ client.on('packetreceive', (packet) => {
+ if (packet.cmd === 'connack') {
+ setImmediate(() => {
+ client.publish('test', 'payload3', { qos: 1 })
+ client.publish('test', 'payload4', { qos: 0 })
+ })
+ }
+ })
+ client.publish('test', 'payload1', { qos: 2 })
+ client.publish('test', 'payload2', { qos: 2 })
+ })
+ })
+
+ it('should not overtake the messages stored in the level-db-store', function _test(done) {
+ const storePath = fs.mkdtempSync('test-store_')
+ const store = levelStore(storePath)
+ let client = null
+ const incomingStore = store.incoming
+ const outgoingStore = store.outgoing
+ let publishCount = 0
+
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ serverClient.on('connect', () => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ })
+ serverClient.on('publish', (packet) => {
+ if (packet.qos !== 0) {
+ serverClient.puback({ messageId: packet.messageId })
+ }
+
+ switch (publishCount++) {
+ case 0:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload1',
+ )
+ break
+ case 1:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload2',
+ )
+ break
+ case 2:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload3',
+ )
+
+ server2.close((err) => {
+ fs.rmSync(storePath, { recursive: true })
+ done(err)
+ })
+ break
+ }
+ })
+ })
+
+ const clientOptions = {
+ port: ports.PORTAND72,
+ host: 'localhost',
+ clean: false,
+ clientId: 'cid1',
+ reconnectPeriod: 0,
+ incomingStore,
+ outgoingStore,
+ queueQoSZero: true,
+ }
+
+ server2.listen(ports.PORTAND72, () => {
+ client = connect(clientOptions)
+
+ client.once('close', () => {
+ client.once('connect', () => {
+ client.publish('test', 'payload2', { qos: 1 })
+ client.publish('test', 'payload3', { qos: 1 }, () => {
+ client.end(false)
+ })
+ })
+ // reconecting
+ client.reconnect(clientOptions)
+ })
+
+ // publish and close
+ client.once('connect', () => {
+ client.publish('test', 'payload1', {
+ qos: 1,
+ cbStorePut() {
+ client.end(true)
+ },
+ })
+ })
+ })
+ })
+
+ it('should call cb if an outgoing QoS 0 message is not sent', function _test(done) {
+ const client = connect({ queueQoSZero: false })
+ let called = false
+
+ client.publish('test', 'test', { qos: 0 }, () => {
+ called = true
+ })
+
+ client.on('connect', () => {
+ assert.isTrue(called)
+ setTimeout(() => {
+ client.end(true, done)
+ }, 10)
+ })
+ })
+
+ it('should delay ending up until all inflight messages are delivered', function _test(done) {
+ const client = connect()
+ let subscribeCalled = false
+
+ client.on('connect', () => {
+ client.subscribe('test', () => {
+ subscribeCalled = true
+ })
+ client.publish('test', 'test', () => {
+ client.end(false, () => {
+ assert.strictEqual(subscribeCalled, true)
+ done()
+ })
+ })
+ })
+ })
+
+ it('wait QoS 1 publish messages', function _test(done) {
+ const client = connect()
+ let messageReceived = false
+
+ client.on('connect', () => {
+ client.subscribe('test')
+ client.publish('test', 'test', { qos: 1 }, () => {
+ client.end(false, () => {
+ assert.strictEqual(messageReceived, true)
+ done()
+ })
+ })
+ client.on('message', () => {
+ messageReceived = true
+ })
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.on('subscribe', () => {
+ serverClient.on('publish', (packet) => {
+ serverClient.publish(packet)
+ })
+ })
+ })
+ })
+
+ it('does not wait acks when force-closing', function _test(done) {
+ // non-running broker
+ const client = connect('mqtt://localhost:8993')
+ client.publish('test', 'test', { qos: 1 })
+ client.end(true, done)
+ })
+
+ it('should call cb if store.put fails', function _test(done) {
+ const store = new Store()
+ store.put = (packet, cb) => {
+ process.nextTick(cb, new Error('oops there is an error'))
+ }
+ const client = connect({
+ incomingStore: store,
+ outgoingStore: store,
+ })
+ client.publish('test', 'test', { qos: 2 }, (err) => {
+ if (err) {
+ client.end(true, done)
+ }
+ })
+ })
+ })
+
+ describe('publishing', () => {
+ it('should publish a message (offline)', function _test(done) {
+ const client = connect()
+ const payload = 'test'
+ const topic = 'test'
+ // don't wait on connect to send publish
+ client.publish(topic, payload)
+
+ server.on('client', onClient)
+
+ function onClient(serverClient) {
+ serverClient.once('connect', () => {
+ server.removeListener('client', onClient)
+ })
+
+ serverClient.once('publish', (packet) => {
+ assert.strictEqual(packet.topic, topic)
+ assert.strictEqual(packet.payload.toString(), payload)
+ assert.strictEqual(packet.qos, 0)
+ assert.strictEqual(packet.retain, false)
+ client.end(true, done)
+ })
+ }
+ })
+
+ it('should publish a message (online)', function _test(done) {
+ const client = connect()
+ const payload = 'test'
+ const topic = 'test'
+ // block on connect before sending publish
+ client.on('connect', () => {
+ client.publish(topic, payload)
+ })
+
+ server.on('client', onClient)
+
+ function onClient(serverClient) {
+ serverClient.once('connect', () => {
+ server.removeListener('client', onClient)
+ })
+
+ serverClient.once('publish', (packet) => {
+ assert.strictEqual(packet.topic, topic)
+ assert.strictEqual(packet.payload.toString(), payload)
+ assert.strictEqual(packet.qos, 0)
+ assert.strictEqual(packet.retain, false)
+ client.end(true, done)
+ })
+ }
+ })
+
+ it('should publish a message (retain, offline)', function _test(done) {
+ const client = connect({ queueQoSZero: true })
+ const payload = 'test'
+ const topic = 'test'
+ let called = false
+
+ client.publish(topic, payload, { retain: true }, () => {
+ called = true
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('publish', (packet) => {
+ assert.strictEqual(packet.topic, topic)
+ assert.strictEqual(packet.payload.toString(), payload)
+ assert.strictEqual(packet.qos, 0)
+ assert.strictEqual(packet.retain, true)
+ assert.strictEqual(called, true)
+ client.end(true, done)
+ })
+ })
+ })
+
+ it('should emit a packetsend event', function _test(done) {
+ const client = connect()
+ const payload = 'test_payload'
+ const topic = 'testTopic'
+
+ client.on('packetsend', (packet) => {
+ if (packet.cmd === 'publish') {
+ assert.strictEqual(packet.topic, topic)
+ assert.strictEqual(packet.payload.toString(), payload)
+ assert.strictEqual(packet.qos, 0)
+ assert.strictEqual(packet.retain, false)
+ client.end(true, done)
+ } else {
+ done(new Error('packet.cmd was not publish!'))
+ }
+ })
+
+ client.publish(topic, payload)
+ })
+
+ it('should accept options', function _test(done) {
+ const client = connect()
+ const payload = 'test'
+ const topic = 'test'
+ const opts = {
+ retain: true,
+ qos: 1,
+ }
+ let received = false
+
+ client.once('connect', () => {
+ client.publish(topic, payload, opts, (err) => {
+ assert(received)
+ client.end(() => {
+ done(err)
+ })
+ })
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('publish', (packet) => {
+ assert.strictEqual(packet.topic, topic)
+ assert.strictEqual(packet.payload.toString(), payload)
+ assert.strictEqual(packet.qos, opts.qos, 'incorrect qos')
+ assert.strictEqual(
+ packet.retain,
+ opts.retain,
+ 'incorrect ret',
+ )
+ assert.strictEqual(packet.dup, false, 'incorrect dup')
+ received = true
+ })
+ })
+ })
+
+ it('should publish with the default options for an empty parameter', function _test(done) {
+ const client = connect()
+ const payload = 'test'
+ const topic = 'test'
+ const defaultOpts = { qos: 0, retain: false, dup: false }
+
+ client.once('connect', () => {
+ client.publish(topic, payload, {})
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('publish', (packet) => {
+ assert.strictEqual(packet.topic, topic)
+ assert.strictEqual(packet.payload.toString(), payload)
+ assert.strictEqual(
+ packet.qos,
+ defaultOpts.qos,
+ 'incorrect qos',
+ )
+ assert.strictEqual(
+ packet.retain,
+ defaultOpts.retain,
+ 'incorrect ret',
+ )
+ assert.strictEqual(
+ packet.dup,
+ defaultOpts.dup,
+ 'incorrect dup',
+ )
+ client.end(true, done)
+ })
+ })
+ })
+
+ it('should mark a message as duplicate when "dup" option is set', function _test(done) {
+ const client = connect()
+ const payload = 'duplicated-test'
+ const topic = 'test'
+ const opts = {
+ retain: true,
+ qos: 1,
+ dup: true,
+ }
+ let received = false
+
+ client.once('connect', () => {
+ client.publish(topic, payload, opts, (err) => {
+ assert(received)
+ client.end(() => {
+ done(err)
+ })
+ })
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('publish', (packet) => {
+ assert.strictEqual(packet.topic, topic)
+ assert.strictEqual(packet.payload.toString(), payload)
+ assert.strictEqual(packet.qos, opts.qos, 'incorrect qos')
+ assert.strictEqual(
+ packet.retain,
+ opts.retain,
+ 'incorrect ret',
+ )
+ assert.strictEqual(packet.dup, opts.dup, 'incorrect dup')
+ received = true
+ })
+ })
+ })
+
+ it('should fire a callback (qos 0)', function _test(done) {
+ const client = connect()
+
+ client.once('connect', () => {
+ client.publish('a', 'b', () => {
+ client.end((err) => done(err))
+ })
+ })
+ })
+
+ it('should fire a callback (qos 1)', function _test(done) {
+ const client = connect()
+ const opts = { qos: 1 }
+
+ client.once('connect', () => {
+ client.publish('a', 'b', opts, () => {
+ client.end((err) => done(err))
+ })
+ })
+ })
+
+ it('should fire a callback (qos 1) on error', function _test(done) {
+ // 145 = Packet Identifier in use
+ const pubackReasonCode = 145
+ const pubOpts = { qos: 1 }
+ let client = null
+
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ serverClient.on('connect', () => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ })
+ serverClient.on('publish', (packet) => {
+ if (packet.qos === 1) {
+ if (version === 5) {
+ serverClient.puback({
+ messageId: packet.messageId,
+ reasonCode: pubackReasonCode,
+ })
+ } else {
+ serverClient.puback({ messageId: packet.messageId })
+ }
+ }
+ })
+ })
+
+ server2.listen(ports.PORTAND72, () => {
+ client = connect({
+ port: ports.PORTAND72,
+ host: 'localhost',
+ clean: true,
+ clientId: 'cid1',
+ reconnectPeriod: 0,
+ })
+
+ client.once('connect', () => {
+ client.publish('a', 'b', pubOpts, (err) => {
+ if (version === 5) {
+ assert.strictEqual(err.code, pubackReasonCode)
+ } else {
+ assert.ifError(err)
+ }
+ setImmediate(() => {
+ client.end(() => {
+ server2.close(done())
+ })
+ })
+ })
+ })
+ })
+ })
+
+ it('should fire a callback (qos 2)', function _test(done) {
+ const client = connect()
+ const opts = { qos: 2 }
+
+ client.once('connect', () => {
+ client.publish('a', 'b', opts, () => {
+ client.end((err) => done(err))
+ })
+ })
+ })
+
+ it('should fire a callback (qos 2) on error', function _test(done) {
+ // 145 = Packet Identifier in use
+ const pubrecReasonCode = 145
+ const pubOpts = { qos: 2 }
+ let client = null
+
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ serverClient.on('connect', () => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ })
+ serverClient.on('publish', (packet) => {
+ if (packet.qos === 2) {
+ if (version === 5) {
+ serverClient.pubrec({
+ messageId: packet.messageId,
+ reasonCode: pubrecReasonCode,
+ })
+ } else {
+ serverClient.pubrec({ messageId: packet.messageId })
+ }
+ }
+ })
+ serverClient.on('pubrel', (packet) => {
+ if (!serverClient.writable) return false
+ serverClient.pubcomp(packet)
+ })
+ })
+
+ server2.listen(ports.PORTAND103, () => {
+ client = connect({
+ port: ports.PORTAND103,
+ host: 'localhost',
+ clean: true,
+ clientId: 'cid1',
+ reconnectPeriod: 0,
+ })
+
+ client.once('connect', () => {
+ client.publish('a', 'b', pubOpts, (err) => {
+ if (version === 5) {
+ assert.strictEqual(err.code, pubrecReasonCode)
+ } else {
+ assert.ifError(err)
+ }
+ setImmediate(() => {
+ client.end(true, () => {
+ server2.close(done())
+ })
+ })
+ })
+ })
+ })
+ })
+
+ it('should support UTF-8 characters in topic', function _test(done) {
+ const client = connect()
+
+ client.once('connect', () => {
+ client.publish('ä¸å›½', 'hello', () => {
+ client.end((err) => done(err))
+ })
+ })
+ })
+
+ it('should support UTF-8 characters in payload', function _test(done) {
+ const client = connect()
+
+ client.once('connect', () => {
+ client.publish('hello', 'ä¸å›½', () => {
+ client.end((err) => done(err))
+ })
+ })
+ })
+
+ it('should publish 10 QoS 2 and receive them', function _test(done) {
+ const client = connect()
+ let countSent = 0
+ let countReceived = 0
+
+ function publishNext() {
+ client.publish('test', 'test', { qos: 2 }, (err) => {
+ assert.ifError(err)
+ countSent++
+ })
+ }
+
+ client.on('connect', () => {
+ client.subscribe('test', (err) => {
+ assert.ifError(err)
+ publishNext()
+ })
+ })
+
+ client.on('message', () => {
+ countReceived++
+ if (countSent >= 10 && countReceived >= 10) {
+ client.end(done)
+ } else {
+ publishNext()
+ }
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.on('offline', () => {
+ client.end()
+ done('error went offline... didnt see this happen')
+ })
+
+ serverClient.on('subscribe', () => {
+ serverClient.on('publish', (packet) => {
+ serverClient.publish(packet)
+ })
+ })
+ })
+ })
+
+ function testQosHandleMessage(qos, done) {
+ const client = connect()
+
+ let messageEventCount = 0
+ let handleMessageCount = 0
+
+ client.handleMessage = (packet, callback) => {
+ setTimeout(() => {
+ handleMessageCount++
+ // next message event should not emit until handleMessage completes
+ assert.strictEqual(handleMessageCount, messageEventCount)
+ if (handleMessageCount === 10) {
+ setTimeout(() => {
+ client.end(true, done)
+ })
+ }
+ callback()
+ }, 100)
+ }
+
+ client.on('message', (topic, message, packet) => {
+ messageEventCount++
+ })
+
+ client.on('connect', () => {
+ client.subscribe('test')
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.on('offline', () => {
+ client.end(true, () => {
+ done('error went offline... didnt see this happen')
+ })
+ })
+
+ serverClient.on('subscribe', () => {
+ for (let i = 0; i < 10; i++) {
+ serverClient.publish({
+ messageId: i,
+ topic: 'test',
+ payload: `test${i}`,
+ qos,
+ })
+ }
+ })
+ })
+ }
+
+ const qosTests = [0, 1, 2]
+ qosTests.forEach((QoS) => {
+ it(`should publish 10 QoS ${QoS}and receive them only when \`handleMessage\` finishes`, function _test(done) {
+ testQosHandleMessage(QoS, done)
+ })
+ })
+
+ it('should not send a `puback` if the execution of `handleMessage` fails for messages with QoS `1`', function _test(done) {
+ const client = connect()
+
+ client.handleMessage = (packet, callback) => {
+ callback(new Error('Error thrown by the application'))
+ }
+
+ client._sendPacket = sinon.spy()
+
+ handlePublish(
+ client,
+ {
+ messageId: Math.floor(65535 * Math.random()),
+ topic: 'test',
+ payload: 'test',
+ qos: 1,
+ },
+ (err) => {
+ assert.exists(err)
+ },
+ )
+
+ assert.strictEqual(client._sendPacket.callCount, 0)
+ client.end()
+ client.on('connect', () => {
+ done()
+ })
+ })
+
+ it(
+ 'should silently ignore errors thrown by `handleMessage` and return when no callback is passed ' +
+ 'into `handlePublish` method',
+ function _test(done) {
+ const client = connect()
+
+ client.handleMessage = (packet, callback) => {
+ callback(new Error('Error thrown by the application'))
+ }
+
+ try {
+ handlePublish(client, {
+ 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 _test(done) {
+ class AsyncStore {
+ put(packet, cb) {
+ process.nextTick(() => {
+ cb(null, 'Error')
+ })
+ }
+
+ close(cb) {
+ cb()
+ }
+ }
+
+ const store = new AsyncStore()
+ const client = connect({ incomingStore: store })
+
+ handlePublish(
+ client,
+ {
+ messageId: 1,
+ topic: 'test',
+ payload: 'test',
+ qos: 1,
+ },
+ () => {
+ client.end((err) => done(err))
+ },
+ )
+ })
+
+ it('should handle error with async incoming store in QoS 2 `handlePublish` method', function _test(done) {
+ class AsyncStore {
+ put(packet, cb) {
+ process.nextTick(() => {
+ cb(null, 'Error')
+ })
+ }
+
+ del(packet, cb) {
+ process.nextTick(() => {
+ cb(new Error('Error'))
+ })
+ }
+
+ get(packet, cb) {
+ process.nextTick(() => {
+ cb(null, { cmd: 'publish' })
+ })
+ }
+
+ close(cb) {
+ cb()
+ }
+ }
+
+ const store = new AsyncStore()
+ const client = connect({ incomingStore: store })
+
+ handlePublish(
+ client,
+ {
+ messageId: 1,
+ topic: 'test',
+ payload: 'test',
+ qos: 2,
+ },
+ () => {
+ client.end((err) => done(err))
+ },
+ )
+ })
+
+ it('should handle error with async incoming store in QoS 2 `handlePubrel` method', function _test(done) {
+ class AsyncStore {
+ put(packet, cb) {
+ process.nextTick(() => {
+ cb(null, 'Error')
+ })
+ }
+
+ del(packet, cb) {
+ process.nextTick(() => {
+ cb(new Error('Error'))
+ })
+ }
+
+ get(packet, cb) {
+ process.nextTick(() => {
+ cb(null, { cmd: 'publish' })
+ })
+ }
+
+ close(cb) {
+ cb()
+ }
+ }
+
+ const store = new AsyncStore()
+ const client = connect({ incomingStore: store })
+
+ handlePubrel(
+ client,
+ {
+ messageId: 1,
+ qos: 2,
+ },
+ () => {
+ client.end(true, (err) => done(err))
+ },
+ )
+ })
+
+ it('should handle success with async incoming store in QoS 2 `handlePubrel` method', function _test(done) {
+ let delComplete = false
+ class AsyncStore {
+ put(packet, cb) {
+ process.nextTick(() => {
+ cb(null, 'Error')
+ })
+ }
+
+ del(packet, cb) {
+ process.nextTick(() => {
+ delComplete = true
+ cb(null)
+ })
+ }
+
+ get(packet, cb) {
+ process.nextTick(() => {
+ cb(null, { cmd: 'publish' })
+ })
+ }
+
+ close(cb) {
+ cb()
+ }
+ }
+
+ const store = new AsyncStore()
+ const client = connect({ incomingStore: store })
+
+ handlePubrel(
+ client,
+ {
+ messageId: 1,
+ qos: 2,
+ },
+ () => {
+ assert.isTrue(delComplete)
+ client.end(true, done)
+ },
+ )
+ })
+
+ it('should not send a `pubcomp` if the execution of `handleMessage` fails for messages with QoS `2`', function _test(done) {
+ const store = new Store()
+ const client = connect({ incomingStore: store })
+
+ const messageId = Math.floor(65535 * Math.random())
+ const topic = 'testTopic'
+ const payload = 'testPayload'
+ const qos = 2
+
+ client.handleMessage = (packet, callback) => {
+ callback(new Error('Error thrown by the application'))
+ }
+
+ client.once('connect', () => {
+ client.subscribe(topic, { qos: 2 })
+
+ store.put(
+ {
+ messageId,
+ topic,
+ payload,
+ qos,
+ cmd: 'publish',
+ },
+ () => {
+ // cleans up the client
+ client._sendPacket = sinon.spy()
+ handlePubrel(
+ client,
+ { cmd: 'pubrel', messageId },
+ (err) => {
+ assert.exists(err)
+ assert.strictEqual(
+ client._sendPacket.callCount,
+ 0,
+ )
+ client.end(true, done)
+ },
+ )
+ },
+ )
+ })
+ })
+
+ it(
+ 'should silently ignore errors thrown by `handleMessage` and return when no callback is passed ' +
+ 'into `handlePubrel` method',
+ function _test(done) {
+ 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
+
+ client.handleMessage = (packet, callback) => {
+ callback(new Error('Error thrown by the application'))
+ }
+
+ client.once('connect', () => {
+ client.subscribe(topic, { qos: 2 })
+
+ store.put(
+ {
+ messageId,
+ topic,
+ payload,
+ qos,
+ cmd: 'publish',
+ },
+ () => {
+ try {
+ handlePubrel(client, {
+ cmd: 'pubrel',
+ messageId,
+ })
+ client.end(true, done)
+ } catch (err) {
+ client.end(true, () => {
+ done(err)
+ })
+ }
+ },
+ )
+ })
+ },
+ )
+
+ it('should keep message order', function _test(done) {
+ let publishCount = 0
+ let reconnect = false
+ let client = {}
+ const incomingStore = new mqtt.Store({ clean: false })
+ const outgoingStore = new mqtt.Store({ clean: false })
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ // errors are not interesting for this test
+ // but they might happen on some platforms
+ serverClient.on('error', () => {})
+
+ serverClient.on('connect', (packet) => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ })
+ serverClient.on('publish', (packet) => {
+ serverClient.puback({ messageId: packet.messageId })
+ if (reconnect) {
+ switch (publishCount++) {
+ case 0:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload1',
+ )
+ break
+ case 1:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload2',
+ )
+ break
+ case 2:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload3',
+ )
+ client.end((err1) => {
+ server2.close((err2) => done(err1 || err2))
+ })
+ break
+ }
+ }
+ })
+ })
+
+ server2.listen(ports.PORTAND50, () => {
+ client = connect({
+ port: ports.PORTAND50,
+ host: 'localhost',
+ clean: false,
+ clientId: 'cid1',
+ reconnectPeriod: 0,
+ incomingStore,
+ outgoingStore,
+ })
+
+ client.on('connect', () => {
+ if (!reconnect) {
+ client.publish('topic', 'payload1', { qos: 1 })
+ client.publish('topic', 'payload2', { qos: 1 })
+ client.end(true)
+ } else {
+ client.publish('topic', 'payload3', { qos: 1 })
+ }
+ })
+ client.on('close', () => {
+ if (!reconnect) {
+ client.reconnect({
+ clean: false,
+ incomingStore,
+ outgoingStore,
+ })
+ reconnect = true
+ }
+ })
+ })
+ })
+
+ function testCallbackStorePutByQoS(qos, clean, expected, done) {
+ const client = connect({
+ clean,
+ clientId: 'testId',
+ })
+
+ const callbacks = []
+
+ function cbStorePut() {
+ callbacks.push('storeput')
+ }
+
+ client.on('connect', () => {
+ client.publish('test', 'test', { qos, cbStorePut }, (err) => {
+ if (err) done(err)
+ callbacks.push('publish')
+ assert.deepEqual(callbacks, expected)
+ client.end(true, done)
+ })
+ })
+ }
+
+ const callbackStorePutByQoSParameters = [
+ { args: [0, true], expected: ['publish'] },
+ { args: [0, false], expected: ['publish'] },
+ { args: [1, true], expected: ['storeput', 'publish'] },
+ { args: [1, false], expected: ['storeput', 'publish'] },
+ { args: [2, true], expected: ['storeput', 'publish'] },
+ { args: [2, false], expected: ['storeput', 'publish'] },
+ ]
+
+ callbackStorePutByQoSParameters.forEach((test) => {
+ if (test.args[0] === 0) {
+ // QoS 0
+ it(`should not call cbStorePut when publishing message with QoS \`${test.args[0]}\` and clean \`${test.args[1]}\``, function _test(done) {
+ testCallbackStorePutByQoS(
+ test.args[0],
+ test.args[1],
+ test.expected,
+ done,
+ )
+ })
+ } else {
+ // QoS 1 and 2
+ it(`should call cbStorePut before publish completes when publishing message with QoS \`${test.args[0]}\` and clean \`${test.args[1]}\``, function _test(done) {
+ testCallbackStorePutByQoS(
+ test.args[0],
+ test.args[1],
+ test.expected,
+ done,
+ )
+ })
+ }
+ })
+ })
+
+ describe('unsubscribing', () => {
+ it('should send an unsubscribe packet (offline)', function _test(done) {
+ const client = connect()
+ let received = false
+
+ client.unsubscribe('test', (err) => {
+ assert.ifError(err)
+ assert(received)
+ client.end(done)
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('unsubscribe', (packet) => {
+ assert.include(packet.unsubscriptions, 'test')
+ received = true
+ })
+ })
+ })
+
+ it('should send an unsubscribe packet', function _test(done) {
+ const client = connect()
+ const topic = 'topic'
+ let received = false
+
+ client.once('connect', () => {
+ client.unsubscribe(topic, (err) => {
+ assert.ifError(err)
+ assert(received)
+ client.end(done)
+ })
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('unsubscribe', (packet) => {
+ assert.include(packet.unsubscriptions, topic)
+ received = true
+ })
+ })
+ })
+
+ it('should emit a packetsend event', function _test(done) {
+ const client = connect()
+ const testTopic = 'testTopic'
+
+ client.once('connect', () => {
+ client.subscribe(testTopic)
+ })
+
+ client.on('packetsend', (packet) => {
+ if (packet.cmd === 'subscribe') {
+ client.end(true, done)
+ }
+ })
+ })
+
+ it('should emit a packetreceive event', function _test(done) {
+ const client = connect()
+ const testTopic = 'testTopic'
+
+ client.once('connect', () => {
+ client.subscribe(testTopic)
+ })
+
+ client.on('packetreceive', (packet) => {
+ if (packet.cmd === 'suback') {
+ client.end(true, done)
+ }
+ })
+ })
+
+ it('should accept an array of unsubs', function _test(done) {
+ const client = connect()
+ const topics = ['topic1', 'topic2']
+ let received = false
+
+ client.once('connect', () => {
+ client.unsubscribe(topics, (err) => {
+ assert.ifError(err)
+ assert(received)
+ client.end(done)
+ })
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('unsubscribe', (packet) => {
+ assert.deepStrictEqual(packet.unsubscriptions, topics)
+ received = true
+ })
+ })
+ })
+
+ it('should fire a callback on unsuback', function _test(done) {
+ const client = connect()
+ const topic = 'topic'
+
+ client.once('connect', () => {
+ client.unsubscribe(topic, () => {
+ client.end(true, done)
+ })
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('unsubscribe', (packet) => {
+ serverClient.unsuback(packet)
+ })
+ })
+ })
+
+ it('should unsubscribe from a chinese topic', function _test(done) {
+ const client = connect()
+ const topic = 'ä¸å›½'
+
+ client.once('connect', () => {
+ client.unsubscribe(topic, () => {
+ client.end((err) => {
+ done(err)
+ })
+ })
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('unsubscribe', (packet) => {
+ assert.include(packet.unsubscriptions, topic)
+ })
+ })
+ })
+ })
+
+ describe('keepalive', () => {
+ let clock
+
+ // eslint-disable-next-line
+ beforeEach(() => {
+ clock = sinon.useFakeTimers()
+ })
+
+ afterEach(() => {
+ clock.restore()
+ })
+
+ it('should checkPing at keepalive interval', function _test(done) {
+ const interval = 3
+ const client = connect({ keepalive: interval })
+
+ client._checkPing = sinon.spy()
+
+ client.once('connect', () => {
+ clock.tick(interval * 1000)
+ assert.strictEqual(client._checkPing.callCount, 1)
+
+ clock.tick(interval * 1000)
+ assert.strictEqual(client._checkPing.callCount, 2)
+
+ clock.tick(interval * 1000)
+ assert.strictEqual(client._checkPing.callCount, 3)
+
+ client.end(true, done)
+ })
+ })
+
+ it('should not checkPing if publishing at a higher rate than keepalive', function _test(done) {
+ const intervalMs = 3000
+ const client = connect({ keepalive: intervalMs / 1000 })
+
+ client._checkPing = sinon.spy()
+
+ client.once('connect', () => {
+ client.publish('foo', 'bar')
+ clock.tick(intervalMs - 1)
+ client.publish('foo', 'bar')
+ clock.tick(2)
+
+ assert.strictEqual(client._checkPing.callCount, 0)
+ client.end(true, done)
+ })
+ })
+
+ it('should checkPing if publishing at a higher rate than keepalive and reschedulePings===false', function _test(done) {
+ const intervalMs = 3000
+ const client = connect({
+ keepalive: intervalMs / 1000,
+ reschedulePings: false,
+ })
+
+ client._checkPing = sinon.spy()
+
+ client.once('connect', () => {
+ client.publish('foo', 'bar')
+ clock.tick(intervalMs - 1)
+ client.publish('foo', 'bar')
+ clock.tick(2)
+
+ assert.strictEqual(client._checkPing.callCount, 1)
+ client.end(true, done)
+ })
+ })
+ })
+
+ describe('pinging', () => {
+ it('should set a ping timer', function _test(done) {
+ const client = connect({ keepalive: 3 })
+ client.once('connect', () => {
+ assert.exists(client.pingTimer)
+ client.end(true, done)
+ })
+ })
+
+ it('should not set a ping timer keepalive=0', function _test(done) {
+ const client = connect({ keepalive: 0 })
+ client.on('connect', () => {
+ assert.notExists(client.pingTimer)
+ client.end(true, done)
+ })
+ })
+
+ it('should reconnect if pingresp is not sent', function _test(done) {
+ this.timeout(4000)
+ const client = connect({ keepalive: 1, reconnectPeriod: 100 })
+
+ // Fake no pingresp being send by stubbing the _handlePingresp function
+ client.on('packetreceive', (packet) => {
+ if (packet.cmd === 'pingresp') {
+ setImmediate(() => {
+ client.pingResp = false
+ })
+ }
+ })
+
+ client.once('connect', () => {
+ client.once('connect', () => {
+ client.end(true, done)
+ })
+ })
+ })
+
+ it('should not reconnect if pingresp is successful', function _test(done) {
+ const client = connect({ keepalive: 100 })
+ client.once('close', () => {
+ done(new Error('Client closed connection'))
+ })
+ setTimeout(() => {
+ client.removeAllListeners('close')
+ client.end(true, done)
+ }, 1000)
+ })
+
+ it('should defer the next ping when sending a control packet', function _test(done) {
+ const client = connect({ keepalive: 1 })
+
+ client.once('connect', () => {
+ client._checkPing = sinon.spy()
+
+ client.publish('foo', 'bar')
+ setTimeout(() => {
+ assert.strictEqual(client._checkPing.callCount, 0)
+ client.publish('foo', 'bar')
+
+ setTimeout(() => {
+ assert.strictEqual(client._checkPing.callCount, 0)
+ client.publish('foo', 'bar')
+
+ setTimeout(() => {
+ assert.strictEqual(client._checkPing.callCount, 0)
+ done()
+ }, 75)
+ }, 75)
+ }, 75)
+ })
+ })
+ })
+
+ describe('subscribing', () => {
+ it('should send a subscribe message (offline)', function _test(done) {
+ const client = connect()
+
+ client.subscribe('test')
+
+ server.once('client', (serverClient) => {
+ serverClient.once('subscribe', () => {
+ done()
+ })
+ })
+ })
+
+ it('should send a subscribe message', function _test(done) {
+ const client = connect()
+ const topic = 'test'
+
+ client.once('connect', () => {
+ client.subscribe(topic)
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('subscribe', (packet) => {
+ const result = {
+ topic,
+ qos: 0,
+ }
+ if (version === 5) {
+ result.nl = false
+ result.rap = false
+ result.rh = 0
+ }
+ assert.include(packet.subscriptions[0], result)
+ done()
+ })
+ })
+ })
+
+ it('should emit a packetsend event', function _test(done) {
+ const client = connect()
+ const testTopic = 'testTopic'
+
+ client.once('connect', () => {
+ client.subscribe(testTopic)
+ })
+
+ client.on('packetsend', (packet) => {
+ if (packet.cmd === 'subscribe') {
+ done()
+ }
+ })
+ })
+
+ it('should emit a packetreceive event', function _test(done) {
+ const client = connect()
+ const testTopic = 'testTopic'
+
+ client.once('connect', () => {
+ client.subscribe(testTopic)
+ })
+
+ client.on('packetreceive', (packet) => {
+ if (packet.cmd === 'suback') {
+ done()
+ }
+ })
+ })
+
+ it('should accept an array of subscriptions', function _test(done) {
+ const client = connect()
+ const subs = ['test1', 'test2']
+
+ client.once('connect', () => {
+ client.subscribe(subs)
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('subscribe', (packet) => {
+ // i.e. [{topic: 'a', qos: 0}, {topic: 'b', qos: 0}]
+ const expected = subs.map((i) => {
+ const result = { topic: i, qos: 0 }
+ if (version === 5) {
+ result.nl = false
+ result.rap = false
+ result.rh = 0
+ }
+ return result
+ })
+
+ assert.deepStrictEqual(packet.subscriptions, expected)
+ client.end(done)
+ })
+ })
+ })
+
+ it('should accept a hash of subscriptions', function _test(done) {
+ const client = connect()
+ const topics = {
+ test1: { qos: 0 },
+ test2: { qos: 1 },
+ }
+
+ client.once('connect', () => {
+ client.subscribe(topics)
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('subscribe', (packet) => {
+ const expected = []
+
+ for (const k in topics) {
+ if (Object.prototype.hasOwnProperty.call(topics, k)) {
+ const result = {
+ topic: k,
+ qos: topics[k].qos,
+ }
+ if (version === 5) {
+ result.nl = false
+ result.rap = false
+ result.rh = 0
+ }
+ expected.push(result)
+ }
+ }
+
+ assert.deepStrictEqual(packet.subscriptions, expected)
+ client.end(done)
+ })
+ })
+ })
+
+ it('should accept an options parameter', function _test(done) {
+ const client = connect()
+ const topic = 'test'
+ const opts = { qos: 1 }
+
+ client.once('connect', () => {
+ client.subscribe(topic, opts)
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('subscribe', (packet) => {
+ const expected = [
+ {
+ topic,
+ qos: 1,
+ },
+ ]
+
+ if (version === 5) {
+ expected[0].nl = false
+ expected[0].rap = false
+ expected[0].rh = 0
+ }
+
+ assert.deepStrictEqual(packet.subscriptions, expected)
+ done()
+ })
+ })
+ })
+
+ it('should subscribe with the default options for an empty options parameter', function _test(done) {
+ const client = connect()
+ const topic = 'test'
+ const defaultOpts = { qos: 0 }
+
+ client.once('connect', () => {
+ client.subscribe(topic, {})
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('subscribe', (packet) => {
+ const result = {
+ topic,
+ qos: defaultOpts.qos,
+ }
+ if (version === 5) {
+ result.nl = false
+ result.rap = false
+ result.rh = 0
+ }
+
+ assert.include(packet.subscriptions[0], result)
+ client.end((err) => done(err))
+ })
+ })
+ })
+
+ it('should fire a callback on suback', function _test(done) {
+ const client = connect()
+ const topic = 'test'
+
+ client.once('connect', () => {
+ client.subscribe(topic, { qos: 2 }, (err, granted) => {
+ if (err) {
+ done(err)
+ } else {
+ assert.exists(granted, 'granted not given')
+ const expectedResult = { topic: 'test', qos: 2 }
+ if (version === 5) {
+ expectedResult.nl = false
+ expectedResult.rap = false
+ expectedResult.rh = 0
+ expectedResult.properties = undefined
+ }
+ assert.include(granted[0], expectedResult)
+ client.end((err2) => done(err2))
+ }
+ })
+ })
+ })
+
+ it('should fire a callback with error if disconnected (options provided)', function _test(done) {
+ const client = connect()
+ const topic = 'test'
+ client.once('connect', () => {
+ client.end(true, () => {
+ client.subscribe(topic, { qos: 2 }, (err, granted) => {
+ assert.notExists(granted, 'granted given')
+ assert.exists(err, 'no error given')
+ done()
+ })
+ })
+ })
+ })
+
+ it('should fire a callback with error if disconnected (options not provided)', function _test(done) {
+ const client = connect()
+ const topic = 'test'
+
+ client.once('connect', () => {
+ client.end(true, () => {
+ client.subscribe(topic, (err, granted) => {
+ assert.notExists(granted, 'granted given')
+ assert.exists(err, 'no error given')
+ done()
+ })
+ })
+ })
+ })
+
+ it('should subscribe with a chinese topic', function _test(done) {
+ const client = connect()
+ const topic = 'ä¸å›½'
+
+ client.once('connect', () => {
+ client.subscribe(topic)
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('subscribe', (packet) => {
+ const result = {
+ topic,
+ qos: 0,
+ }
+ if (version === 5) {
+ result.nl = false
+ result.rap = false
+ result.rh = 0
+ }
+ assert.include(packet.subscriptions[0], result)
+ client.end(done)
+ })
+ })
+ })
+ })
+
+ describe('receiving messages', () => {
+ it('should fire the message event', function _test(done) {
+ const client = connect()
+ const testPacket = {
+ topic: 'test',
+ payload: 'message',
+ retain: true,
+ qos: 1,
+ messageId: 5,
+ }
+
+ //
+ client.subscribe(testPacket.topic)
+ client.once('message', (topic, message, packet) => {
+ assert.strictEqual(topic, testPacket.topic)
+ assert.strictEqual(message.toString(), testPacket.payload)
+ assert.strictEqual(packet.cmd, 'publish')
+ client.end(true, done)
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.on('subscribe', () => {
+ serverClient.publish(testPacket)
+ })
+ })
+ })
+
+ it('should emit a packetreceive event', function _test(done) {
+ const client = connect()
+ const testPacket = {
+ topic: 'test',
+ payload: 'message',
+ retain: true,
+ qos: 1,
+ messageId: 5,
+ }
+
+ client.subscribe(testPacket.topic)
+ client.on('packetreceive', (packet) => {
+ if (packet.cmd === 'publish') {
+ assert.strictEqual(packet.qos, 1)
+ assert.strictEqual(packet.topic, testPacket.topic)
+ assert.strictEqual(
+ packet.payload.toString(),
+ testPacket.payload,
+ )
+ assert.strictEqual(packet.retain, true)
+ client.end(true, done)
+ }
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.on('subscribe', () => {
+ serverClient.publish(testPacket)
+ })
+ })
+ })
+
+ it('should support binary data', function _test(done) {
+ const client = connect({ encoding: 'binary' })
+ const testPacket = {
+ topic: 'test',
+ payload: 'message',
+ retain: true,
+ qos: 1,
+ messageId: 5,
+ }
+
+ client.subscribe(testPacket.topic)
+ client.once('message', (topic, message, packet) => {
+ assert.strictEqual(topic, testPacket.topic)
+ assert.instanceOf(message, Buffer)
+ assert.strictEqual(message.toString(), testPacket.payload)
+ assert.strictEqual(packet.cmd, 'publish')
+ client.end(true, done)
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.on('subscribe', () => {
+ serverClient.publish(testPacket)
+ })
+ })
+ })
+
+ it('should emit a message event (qos=2)', function _test(done) {
+ const client = connect()
+ const testPacket = {
+ topic: 'test',
+ payload: 'message',
+ retain: true,
+ qos: 2,
+ messageId: 5,
+ }
+
+ server.testPublish = testPacket
+
+ client.subscribe(testPacket.topic)
+ client.once('message', (topic, message, packet) => {
+ assert.strictEqual(topic, testPacket.topic)
+ assert.strictEqual(message.toString(), testPacket.payload)
+ assert.strictEqual(packet.messageId, testPacket.messageId)
+ assert.strictEqual(packet.qos, testPacket.qos)
+ client.end(true, done)
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.on('subscribe', () => {
+ serverClient.publish(testPacket)
+ })
+ })
+ })
+
+ it('should emit a message event (qos=2) - repeated publish', function _test(done) {
+ const client = connect()
+ const testPacket = {
+ topic: 'test',
+ payload: 'message',
+ retain: true,
+ qos: 2,
+ messageId: 5,
+ }
+
+ server.testPublish = testPacket
+
+ const messageHandler = (topic, message, packet) => {
+ assert.strictEqual(topic, testPacket.topic)
+ assert.strictEqual(message.toString(), testPacket.payload)
+ assert.strictEqual(packet.messageId, testPacket.messageId)
+ assert.strictEqual(packet.qos, testPacket.qos)
+
+ assert.strictEqual(spiedMessageHandler.callCount, 1)
+ client.end(true, done)
+ }
+
+ const spiedMessageHandler = sinon.spy(messageHandler)
+
+ client.subscribe(testPacket.topic)
+ client.on('message', spiedMessageHandler)
+
+ server.once('client', (serverClient) => {
+ serverClient.on('subscribe', () => {
+ serverClient.publish(testPacket)
+ // twice, should be ignored
+ serverClient.publish(testPacket)
+ })
+ })
+ })
+
+ it('should support a chinese topic', function _test(done) {
+ const client = connect({ encoding: 'binary' })
+ const testPacket = {
+ topic: '国',
+ payload: 'message',
+ retain: true,
+ qos: 1,
+ messageId: 5,
+ }
+
+ client.subscribe(testPacket.topic)
+ client.once('message', (topic, message, packet) => {
+ assert.strictEqual(topic, testPacket.topic)
+ assert.instanceOf(message, Buffer)
+ assert.strictEqual(message.toString(), testPacket.payload)
+ assert.strictEqual(packet.messageId, testPacket.messageId)
+ assert.strictEqual(packet.qos, testPacket.qos)
+ client.end(true, done)
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.on('subscribe', () => {
+ serverClient.publish(testPacket)
+ })
+ })
+ })
+ })
+
+ describe('qos handling', () => {
+ it('should follow qos 0 semantics (trivial)', function _test(done) {
+ const client = connect()
+ const testTopic = 'test'
+ const testMessage = 'message'
+
+ client.once('connect', () => {
+ client.subscribe(testTopic, { qos: 0 }, () => {
+ client.end(true, done)
+ })
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('subscribe', () => {
+ serverClient.publish({
+ topic: testTopic,
+ payload: testMessage,
+ qos: 0,
+ retain: false,
+ })
+ })
+ })
+ })
+
+ it('should follow qos 1 semantics', function _test(done) {
+ const client = connect()
+ const testTopic = 'test'
+ const testMessage = 'message'
+ const mid = 50
+
+ client.once('connect', () => {
+ client.subscribe(testTopic, { qos: 1 })
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('subscribe', () => {
+ serverClient.publish({
+ topic: testTopic,
+ payload: testMessage,
+ messageId: mid,
+ qos: 1,
+ })
+ })
+
+ serverClient.once('puback', (packet) => {
+ assert.strictEqual(packet.messageId, mid)
+ client.end(done)
+ })
+ })
+ })
+
+ it('should follow qos 2 semantics', function _test(done) {
+ const client = connect()
+ const testTopic = 'test'
+ const testMessage = 'message'
+ const mid = 253
+ let publishReceived = 0
+ let pubrecReceived = 0
+ let pubrelReceived = 0
+
+ client.once('connect', () => {
+ client.subscribe(testTopic, { qos: 2 })
+ })
+
+ client.on('packetreceive', (packet) => {
+ switch (packet.cmd) {
+ case 'connack':
+ case 'suback':
+ // expected, but not specifically part of QOS 2 semantics
+ break
+ case 'publish':
+ assert.strictEqual(
+ pubrecReceived,
+ 0,
+ 'server received pubrec before client sent',
+ )
+ assert.strictEqual(
+ pubrelReceived,
+ 0,
+ 'server received pubrec before client sent',
+ )
+ publishReceived += 1
+ break
+ case 'pubrel':
+ assert.strictEqual(
+ publishReceived,
+ 1,
+ 'only 1 publish must be received before a pubrel',
+ )
+ assert.strictEqual(
+ pubrecReceived,
+ 1,
+ 'invalid number of PUBREC messages (not only 1)',
+ )
+ pubrelReceived += 1
+ break
+ default:
+ should.fail()
+ }
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('subscribe', () => {
+ serverClient.publish({
+ topic: testTopic,
+ payload: testMessage,
+ qos: 2,
+ messageId: mid,
+ })
+ })
+
+ serverClient.on('pubrec', () => {
+ assert.strictEqual(
+ publishReceived,
+ 1,
+ 'invalid number of PUBLISH messages received',
+ )
+ assert.strictEqual(
+ pubrecReceived,
+ 0,
+ 'invalid number of PUBREC messages recevied',
+ )
+ pubrecReceived += 1
+ })
+
+ serverClient.once('pubcomp', () => {
+ client.removeAllListeners()
+ serverClient.removeAllListeners()
+ assert.strictEqual(
+ publishReceived,
+ 1,
+ 'invalid number of PUBLISH messages',
+ )
+ assert.strictEqual(
+ pubrecReceived,
+ 1,
+ 'invalid number of PUBREC messages',
+ )
+ assert.strictEqual(
+ pubrelReceived,
+ 1,
+ 'invalid nubmer of PUBREL messages',
+ )
+ client.end(true, done)
+ })
+ })
+ })
+
+ it('should should empty the incoming store after a qos 2 handshake is completed', function _test(done) {
+ const client = connect()
+ const testTopic = 'test'
+ const testMessage = 'message'
+ const mid = 253
+
+ client.once('connect', () => {
+ client.subscribe(testTopic, { qos: 2 })
+ })
+
+ client.on('packetreceive', (packet) => {
+ if (packet.cmd === 'pubrel') {
+ assert.strictEqual(client.incomingStore._inflights.size, 1)
+ }
+ })
+
+ server.once('client', (serverClient) => {
+ serverClient.once('subscribe', () => {
+ serverClient.publish({
+ topic: testTopic,
+ payload: testMessage,
+ qos: 2,
+ messageId: mid,
+ })
+ })
+
+ serverClient.once('pubcomp', () => {
+ assert.strictEqual(client.incomingStore._inflights.size, 0)
+ client.removeAllListeners()
+ client.end(true, done)
+ })
+ })
+ })
+
+ function testMultiplePubrel(shouldSendPubcompFail, done) {
+ const client = connect()
+ const testTopic = 'test'
+ const testMessage = 'message'
+ const mid = 253
+ let pubcompCount = 0
+ let pubrelCount = 0
+ let handleMessageCount = 0
+ let emitMessageCount = 0
+ const origSendPacket = client._sendPacket
+ let shouldSendFail
+
+ client.handleMessage = (packet, callback) => {
+ handleMessageCount++
+ callback()
+ }
+
+ client.on('message', () => {
+ emitMessageCount++
+ })
+
+ client._sendPacket = (packet, sendDone) => {
+ shouldSendFail =
+ packet.cmd === 'pubcomp' && shouldSendPubcompFail
+ if (sendDone) {
+ sendDone(
+ shouldSendFail
+ ? new Error('testing pubcomp failure')
+ : undefined,
+ )
+ }
+
+ // send the mocked response
+ switch (packet.cmd) {
+ case 'subscribe': {
+ const suback = {
+ cmd: 'suback',
+ messageId: packet.messageId,
+ granted: [2],
+ }
+ handle(client, suback, (err) => {
+ assert.isNotOk(err)
+ })
+ break
+ }
+ 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
+ }
+ }
+
+ // simulate the pubrel message, either in response to pubrec or to mock pubcomp failing to be received
+ const pubrel = { cmd: 'pubrel', messageId: mid }
+ pubrelCount++
+ handle(client, pubrel, (err) => {
+ if (shouldSendFail) {
+ assert.exists(err)
+ assert.instanceOf(err, Error)
+ } else {
+ assert.notExists(err)
+ }
+ })
+ break
+ }
+ }
+ }
+
+ client.once('connect', () => {
+ client.subscribe(testTopic, { qos: 2 })
+ const publish = {
+ cmd: 'publish',
+ topic: testTopic,
+ payload: testMessage,
+ qos: 2,
+ messageId: mid,
+ }
+ handle(client, publish, (err) => {
+ assert.notExists(err)
+ })
+ })
+ }
+
+ it('handle qos 2 messages exactly once when multiple pubrel received', function _test(done) {
+ testMultiplePubrel(false, done)
+ })
+
+ it('handle qos 2 messages exactly once when multiple pubrel received and sending pubcomp fails on client', function _test(done) {
+ testMultiplePubrel(true, done)
+ })
+ })
+
+ describe('auto reconnect', () => {
+ it('should mark the client disconnecting if #end called', function _test(done) {
+ const client = connect()
+
+ client.end(true, (err) => {
+ assert.isTrue(client.disconnecting)
+ done(err)
+ })
+ })
+
+ it('should reconnect after stream disconnect', function _test(done) {
+ const client = connect()
+
+ let tryReconnect = true
+
+ client.on('connect', () => {
+ if (tryReconnect) {
+ client.stream.end()
+ tryReconnect = false
+ } else {
+ client.end(true, done)
+ }
+ })
+ })
+
+ it("should emit 'reconnect' when reconnecting", function _test(done) {
+ const client = connect()
+ let tryReconnect = true
+ let reconnectEvent = false
+
+ client.on('reconnect', () => {
+ reconnectEvent = true
+ })
+
+ client.on('connect', () => {
+ if (tryReconnect) {
+ client.stream.end()
+ tryReconnect = false
+ } else {
+ assert.isTrue(reconnectEvent)
+ client.end(true, done)
+ }
+ })
+ })
+
+ it("should emit 'offline' after going offline", function _test(done) {
+ const client = connect()
+
+ let tryReconnect = true
+ let offlineEvent = false
+
+ client.on('offline', () => {
+ offlineEvent = true
+ })
+
+ client.on('connect', () => {
+ if (tryReconnect) {
+ client.stream.end()
+ tryReconnect = false
+ } else {
+ assert.isTrue(offlineEvent)
+ client.end(true, done)
+ }
+ })
+ })
+
+ it('should not reconnect if it was ended by the user', function _test(done) {
+ const client = connect()
+
+ client.on('connect', () => {
+ client.end()
+ done() // it will raise an exception if called two times
+ })
+ })
+
+ it('should setup a reconnect timer on disconnect', function _test(done) {
+ const client = connect()
+
+ client.once('connect', () => {
+ assert.notExists(client.reconnectTimer)
+ client.stream.end()
+ })
+
+ client.once('close', () => {
+ assert.exists(client.reconnectTimer)
+ client.end(true, done)
+ })
+ })
+
+ const reconnectPeriodTests = [
+ { period: 200 },
+ { period: 2000 },
+ { period: 4000 },
+ ]
+ reconnectPeriodTests.forEach((test) => {
+ it(`should allow specification of a reconnect period (${test.period}ms)`, function _test(done) {
+ this.timeout(10000)
+ let end
+ const reconnectSlushTime = 200
+ const client = connect({ reconnectPeriod: test.period })
+ let reconnect = false
+ const start = Date.now()
+
+ client.on('connect', () => {
+ if (!reconnect) {
+ client.stream.end()
+ reconnect = true
+ } else {
+ end = Date.now()
+ client.end(() => {
+ const reconnectPeriodDuringTest = end - start
+ if (
+ reconnectPeriodDuringTest >=
+ test.period - reconnectSlushTime &&
+ reconnectPeriodDuringTest <=
+ test.period + reconnectSlushTime
+ ) {
+ // give the connection a 200 ms slush window
+ done()
+ } else {
+ done(
+ new Error(
+ `Strange reconnect period: ${reconnectPeriodDuringTest}`,
+ ),
+ )
+ }
+ })
+ }
+ })
+ })
+ })
+
+ it('should always cleanup successfully on reconnection', function _test(done) {
+ const client = connect({
+ host: 'this_hostname_should_not_exist',
+ connectTimeout: 0,
+ reconnectPeriod: 1,
+ })
+ // bind client.end so that when it is called it is automatically passed in the done callback
+ setTimeout(() => {
+ client.end.call(client, done)
+ }, 50)
+ })
+
+ it('should resend in-flight QoS 1 publish messages from the client', function _test(done) {
+ this.timeout(4000)
+ const client = connect({ reconnectPeriod: 200 })
+ let serverPublished = false
+ let clientCalledBack = false
+
+ server.once('client', (serverClient) => {
+ serverClient.on('connect', () => {
+ setImmediate(() => {
+ serverClient.stream.destroy()
+ })
+ })
+
+ server.once('client', (serverClientNew) => {
+ serverClientNew.on('publish', () => {
+ serverPublished = true
+ check()
+ })
+ })
+ })
+
+ client.publish('hello', 'world', { qos: 1 }, () => {
+ clientCalledBack = true
+ check()
+ })
+
+ function check() {
+ if (serverPublished && clientCalledBack) {
+ client.end(true, done)
+ }
+ }
+ })
+
+ it('should not resend in-flight publish messages if disconnecting', function _test(done) {
+ const client = connect({ reconnectPeriod: 200 })
+ let serverPublished = false
+ let clientCalledBack = false
+ server.once('client', (serverClient) => {
+ serverClient.on('connect', () => {
+ setImmediate(() => {
+ serverClient.stream.destroy()
+ client.end(true, (err) => {
+ assert.isFalse(serverPublished)
+ assert.isFalse(clientCalledBack)
+ done(err)
+ })
+ })
+ })
+ server.once('client', (serverClientNew) => {
+ serverClientNew.on('publish', () => {
+ serverPublished = true
+ })
+ })
+ })
+ client.publish('hello', 'world', { qos: 1 }, () => {
+ clientCalledBack = true
+ })
+ })
+
+ it('should resend in-flight QoS 2 publish messages from the client', function _test(done) {
+ const client = connect({ reconnectPeriod: 200 })
+ let serverPublished = false
+ let clientCalledBack = false
+
+ server.once('client', (serverClient) => {
+ // ignore errors
+ serverClient.on('error', () => {})
+ serverClient.on('publish', () => {
+ setImmediate(() => {
+ serverClient.stream.destroy()
+ })
+ })
+
+ server.once('client', (serverClientNew) => {
+ serverClientNew.on('pubrel', () => {
+ serverPublished = true
+ check()
+ })
+ })
+ })
+
+ client.publish('hello', 'world', { qos: 2 }, () => {
+ clientCalledBack = true
+ check()
+ })
+
+ function check() {
+ if (serverPublished && clientCalledBack) {
+ client.end(true, done)
+ }
+ }
+ })
+
+ it('should not resend in-flight QoS 1 removed publish messages from the client', function _test(done) {
+ const client = connect({ reconnectPeriod: 200 })
+ let clientCalledBack = false
+
+ server.once('client', (serverClient) => {
+ serverClient.on('connect', () => {
+ setImmediate(() => {
+ serverClient.stream.destroy()
+ })
+ })
+
+ server.once('client', (serverClientNew) => {
+ serverClientNew.on('publish', () => {
+ should.fail()
+ done()
+ })
+ })
+ })
+
+ client.publish('hello', 'world', { qos: 1 }, (err) => {
+ clientCalledBack = true
+ assert.exists(err, 'error should exist')
+ assert.strictEqual(
+ err.message,
+ 'Message removed',
+ 'error message is incorrect',
+ )
+ })
+ assert.strictEqual(Object.keys(client.outgoing).length, 1)
+ assert.strictEqual(client.outgoingStore._inflights.size, 1)
+ client.removeOutgoingMessage(client.getLastMessageId())
+ assert.strictEqual(Object.keys(client.outgoing).length, 0)
+ assert.strictEqual(client.outgoingStore._inflights.size, 0)
+ assert.isTrue(clientCalledBack)
+ client.end(true, (err) => {
+ done(err)
+ })
+ })
+
+ it('should not resend in-flight QoS 2 removed publish messages from the client', function _test(done) {
+ const client = connect({ reconnectPeriod: 200 })
+ let clientCalledBack = false
+
+ server.once('client', (serverClient) => {
+ serverClient.on('connect', () => {
+ setImmediate(() => {
+ serverClient.stream.destroy()
+ })
+ })
+
+ server.once('client', (serverClientNew) => {
+ serverClientNew.on('publish', () => {
+ should.fail()
+ done()
+ })
+ })
+ })
+
+ client.publish('hello', 'world', { qos: 2 }, (err) => {
+ clientCalledBack = true
+ assert.strictEqual(err.message, 'Message removed')
+ })
+ assert.strictEqual(Object.keys(client.outgoing).length, 1)
+ assert.strictEqual(client.outgoingStore._inflights.size, 1)
+ client.removeOutgoingMessage(client.getLastMessageId())
+ assert.strictEqual(Object.keys(client.outgoing).length, 0)
+ assert.strictEqual(client.outgoingStore._inflights.size, 0)
+ assert.isTrue(clientCalledBack)
+ client.end(true, done)
+ })
+
+ it('should resubscribe when reconnecting', function _test(done) {
+ const client = connect({ reconnectPeriod: 100 })
+ let tryReconnect = true
+ let reconnectEvent = false
+
+ client.on('reconnect', () => {
+ reconnectEvent = true
+ })
+
+ client.on('connect', () => {
+ if (tryReconnect) {
+ client.subscribe('hello', () => {
+ client.stream.end()
+
+ server.once('client', (serverClient) => {
+ serverClient.on('subscribe', () => {
+ client.end(done)
+ })
+ })
+ })
+
+ tryReconnect = false
+ } else {
+ assert.isTrue(reconnectEvent)
+ }
+ })
+ })
+
+ it('should not resubscribe when reconnecting if resubscribe is disabled', function _test(done) {
+ const client = connect({ reconnectPeriod: 100, resubscribe: false })
+ let tryReconnect = true
+ let reconnectEvent = false
+
+ client.on('reconnect', () => {
+ reconnectEvent = true
+ })
+
+ client.on('connect', () => {
+ if (tryReconnect) {
+ client.subscribe('hello', () => {
+ client.stream.end()
+
+ server.once('client', (serverClient) => {
+ serverClient.on('subscribe', () => {
+ should.fail()
+ })
+ })
+ })
+
+ tryReconnect = false
+ } else {
+ assert.isTrue(reconnectEvent)
+ assert.strictEqual(
+ Object.keys(client._resubscribeTopics).length,
+ 0,
+ )
+ client.end(true, done)
+ }
+ })
+ })
+
+ it('should not resubscribe when reconnecting if suback is error', function _test(done) {
+ let tryReconnect = true
+ let reconnectEvent = false
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ serverClient.on('connect', (packet) => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ })
+ serverClient.on('subscribe', (packet) => {
+ serverClient.suback({
+ messageId: packet.messageId,
+ granted: packet.subscriptions.map((e) => e.qos | 0x80),
+ })
+ serverClient.pubrel({
+ messageId: Math.floor(Math.random() * 9000) + 1000,
+ })
+ })
+ })
+
+ server2.listen(ports.PORTAND49, () => {
+ const client = connect({
+ port: ports.PORTAND49,
+ host: 'localhost',
+ reconnectPeriod: 100,
+ })
+
+ client.on('reconnect', () => {
+ reconnectEvent = true
+ })
+
+ client.on('connect', () => {
+ if (tryReconnect) {
+ client.subscribe('hello', () => {
+ client.stream.end()
+
+ server.once('client', (serverClient) => {
+ serverClient.on('subscribe', () => {
+ should.fail()
+ })
+ })
+ })
+ tryReconnect = false
+ } else {
+ assert.isTrue(reconnectEvent)
+ assert.strictEqual(
+ Object.keys(client._resubscribeTopics).length,
+ 0,
+ )
+ client.end(true, (err1) => {
+ server2.close((err2) => done(err1 || err2))
+ })
+ }
+ })
+ })
+ })
+
+ it('should preserved incomingStore after disconnecting if clean is false', function _test(done) {
+ let reconnect = false
+ let client = {}
+ const incomingStore = new mqtt.Store({ clean: false })
+ const outgoingStore = new mqtt.Store({ clean: false })
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ serverClient.on('connect', (packet) => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ if (reconnect) {
+ serverClient.pubrel({ messageId: 1 })
+ }
+ })
+ serverClient.on('subscribe', (packet) => {
+ serverClient.suback({
+ messageId: packet.messageId,
+ granted: packet.subscriptions.map((e) => e.qos),
+ })
+ serverClient.publish({
+ topic: 'topic',
+ payload: 'payload',
+ qos: 2,
+ messageId: 1,
+ retain: false,
+ })
+ })
+ serverClient.on('pubrec', (packet) => {
+ client.end(false, () => {
+ client.reconnect({
+ incomingStore,
+ outgoingStore,
+ })
+ })
+ })
+ serverClient.on('pubcomp', (packet) => {
+ client.end(true, (err1) => {
+ server2.close((err2) => done(err1 || err2))
+ })
+ })
+ })
+
+ server2.listen(ports.PORTAND50, () => {
+ client = connect({
+ port: ports.PORTAND50,
+ host: 'localhost',
+ clean: false,
+ clientId: 'cid1',
+ reconnectPeriod: 0,
+ incomingStore,
+ outgoingStore,
+ })
+
+ client.on('connect', () => {
+ if (!reconnect) {
+ client.subscribe('test', { qos: 2 }, () => {})
+ reconnect = true
+ }
+ })
+ client.on('message', (topic, message) => {
+ assert.strictEqual(topic, 'topic')
+ assert.strictEqual(message.toString(), 'payload')
+ })
+ })
+ })
+
+ it('should clear outgoing if close from server', function _test(done) {
+ let reconnect = false
+ let client = {}
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ serverClient.on('connect', (packet) => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ })
+ serverClient.on('subscribe', (packet) => {
+ if (reconnect) {
+ serverClient.suback({
+ messageId: packet.messageId,
+ granted: packet.subscriptions.map((e) => e.qos),
+ })
+ } else {
+ serverClient.destroy()
+ }
+ })
+ })
+
+ server2.listen(ports.PORTAND50, () => {
+ client = connect({
+ port: ports.PORTAND50,
+ host: 'localhost',
+ clean: true,
+ clientId: 'cid1',
+ keepalive: 1,
+ reconnectPeriod: 0,
+ })
+
+ client.on('connect', () => {
+ client.subscribe('test', { qos: 2 }, (e) => {
+ if (!e) {
+ client.end()
+ }
+ })
+ })
+
+ client.on('close', () => {
+ if (reconnect) {
+ server2.close((err) => done(err))
+ } else {
+ assert.strictEqual(
+ Object.keys(client.outgoing).length,
+ 0,
+ )
+ reconnect = true
+ client.reconnect()
+ }
+ })
+ })
+ })
+
+ it('should resend in-flight QoS 1 publish messages from the client if clean is false', function _test(done) {
+ let reconnect = false
+ let client = {}
+ const incomingStore = new mqtt.Store({ clean: false })
+ const outgoingStore = new mqtt.Store({ clean: false })
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ serverClient.on('connect', (packet) => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ })
+ serverClient.on('publish', (packet) => {
+ if (reconnect) {
+ client.end(true, (err1) => {
+ server2.close((err2) => done(err1 || err2))
+ })
+ } else {
+ client.end(true, () => {
+ client.reconnect({
+ incomingStore,
+ outgoingStore,
+ })
+ reconnect = true
+ })
+ }
+ })
+ })
+
+ server2.listen(ports.PORTAND50, () => {
+ client = connect({
+ port: ports.PORTAND50,
+ host: 'localhost',
+ clean: false,
+ clientId: 'cid1',
+ reconnectPeriod: 0,
+ incomingStore,
+ outgoingStore,
+ })
+
+ client.on('connect', () => {
+ if (!reconnect) {
+ client.publish('topic', 'payload', { qos: 1 })
+ }
+ })
+ client.on('error', () => {})
+ })
+ })
+
+ it('should resend in-flight QoS 2 publish messages from the client if clean is false', function _test(done) {
+ let reconnect = false
+ let client = {}
+ const incomingStore = new mqtt.Store({ clean: false })
+ const outgoingStore = new mqtt.Store({ clean: false })
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ serverClient.on('connect', (packet) => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ })
+ serverClient.on('publish', (packet) => {
+ if (reconnect) {
+ client.end(true, (err1) => {
+ server2.close((err2) => done(err1 || err2))
+ })
+ } else {
+ client.end(true, () => {
+ client.reconnect({
+ incomingStore,
+ outgoingStore,
+ })
+ reconnect = true
+ })
+ }
+ })
+ })
+
+ server2.listen(ports.PORTAND50, () => {
+ client = connect({
+ port: ports.PORTAND50,
+ host: 'localhost',
+ clean: false,
+ clientId: 'cid1',
+ reconnectPeriod: 0,
+ incomingStore,
+ outgoingStore,
+ })
+
+ client.on('connect', () => {
+ if (!reconnect) {
+ client.publish('topic', 'payload', { qos: 2 })
+ }
+ })
+ client.on('error', () => {})
+ })
+ })
+
+ it('should resend in-flight QoS 2 pubrel messages from the client if clean is false', function _test(done) {
+ let reconnect = false
+ let client = {}
+ const incomingStore = new mqtt.Store({ clean: false })
+ const outgoingStore = new mqtt.Store({ clean: false })
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ serverClient.on('connect', (packet) => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ })
+ serverClient.on('publish', (packet) => {
+ if (!reconnect) {
+ serverClient.pubrec({ messageId: packet.messageId })
+ }
+ })
+ serverClient.on('pubrel', (packet) => {
+ if (reconnect) {
+ serverClient.pubcomp({ messageId: packet.messageId })
+ } else {
+ client.end(true, () => {
+ client.reconnect({
+ incomingStore,
+ outgoingStore,
+ })
+ reconnect = true
+ })
+ }
+ })
+ })
+
+ server2.listen(ports.PORTAND50, () => {
+ client = connect({
+ port: ports.PORTAND50,
+ host: 'localhost',
+ clean: false,
+ clientId: 'cid1',
+ reconnectPeriod: 0,
+ incomingStore,
+ outgoingStore,
+ })
+
+ client.on('connect', () => {
+ if (!reconnect) {
+ client.publish(
+ 'topic',
+ 'payload',
+ { qos: 2 },
+ (err) => {
+ assert(reconnect)
+ assert.ifError(err)
+ client.end(true, (err1) => {
+ server2.close((err2) => done(err1 || err2))
+ })
+ },
+ )
+ }
+ })
+ client.on('error', () => {})
+ })
+ })
+
+ it('should resend in-flight publish messages by published order', function _test(done) {
+ let publishCount = 0
+ let reconnect = false
+ let disconnectOnce = true
+ let client = {}
+ const incomingStore = new mqtt.Store({ clean: false })
+ const outgoingStore = new mqtt.Store({ clean: false })
+ const server2 = serverBuilder(config.protocol, (serverClient) => {
+ // errors are not interesting for this test
+ // but they might happen on some platforms
+ serverClient.on('error', () => {})
+
+ serverClient.on('connect', (packet) => {
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+ serverClient.connack(connack)
+ })
+ serverClient.on('publish', (packet) => {
+ serverClient.puback({ messageId: packet.messageId })
+ if (reconnect) {
+ switch (publishCount++) {
+ case 0:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload1',
+ )
+ break
+ case 1:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload2',
+ )
+ break
+ case 2:
+ assert.strictEqual(
+ packet.payload.toString(),
+ 'payload3',
+ )
+ client.end(true, (err1) => {
+ server2.close((err2) => done(err1 || err2))
+ })
+ break
+ }
+ } else if (disconnectOnce) {
+ client.end(true, () => {
+ reconnect = true
+ client.reconnect({
+ incomingStore,
+ outgoingStore,
+ })
+ })
+ disconnectOnce = false
+ }
+ })
+ })
+
+ server2.listen(ports.PORTAND50, () => {
+ client = connect({
+ port: ports.PORTAND50,
+ host: 'localhost',
+ clean: false,
+ clientId: 'cid1',
+ reconnectPeriod: 0,
+ incomingStore,
+ outgoingStore,
+ })
+
+ client.nextId = 65535
+
+ client.on('connect', () => {
+ if (!reconnect) {
+ client.publish('topic', 'payload1', { qos: 1 })
+ client.publish('topic', 'payload2', { qos: 1 })
+ client.publish('topic', 'payload3', { qos: 1 })
+ }
+ })
+ client.on('error', () => {})
+ })
+ })
+
+ it('should be able to pub/sub if reconnect() is called at close handler', function _test(done) {
+ const client = connect({ reconnectPeriod: 0 })
+ let tryReconnect = true
+ let reconnectEvent = false
+
+ client.on('close', () => {
+ if (tryReconnect) {
+ tryReconnect = false
+ client.reconnect()
+ } else {
+ assert.isTrue(reconnectEvent)
+ done()
+ }
+ })
+
+ client.on('reconnect', () => {
+ reconnectEvent = true
+ })
+
+ client.on('connect', () => {
+ if (tryReconnect) {
+ client.end()
+ } else {
+ client.subscribe('hello', () => {
+ client.end()
+ })
+ }
+ })
+ })
+
+ it('should be able to pub/sub if reconnect() is called at out of close handler', function _test(done) {
+ const client = connect({ reconnectPeriod: 0 })
+ let tryReconnect = true
+ let reconnectEvent = false
+
+ client.on('close', () => {
+ if (tryReconnect) {
+ tryReconnect = false
+ setTimeout(() => {
+ client.reconnect()
+ }, 100)
+ } else {
+ assert.isTrue(reconnectEvent)
+ done()
+ }
+ })
+
+ client.on('reconnect', () => {
+ reconnectEvent = true
+ })
+
+ client.on('connect', () => {
+ if (tryReconnect) {
+ client.end()
+ } else {
+ client.subscribe('hello', () => {
+ client.end()
+ })
+ }
+ })
+ })
+
+ context('with alternate server client', () => {
+ let cachedClientListeners
+ const connack =
+ version === 5 ? { reasonCode: 0 } : { returnCode: 0 }
+
+ beforeEach(() => {
+ cachedClientListeners = server.listeners('client')
+ server.removeAllListeners('client')
+ })
+
+ afterEach(() => {
+ server.removeAllListeners('client')
+ cachedClientListeners.forEach((listener) => {
+ server.on('client', listener)
+ })
+ })
+
+ it('should resubscribe even if disconnect is before suback', function _test(done) {
+ const client = connect({ reconnectPeriod: 100, ...config })
+ let subscribeCount = 0
+ let connectCount = 0
+
+ server.on('client', (serverClient) => {
+ serverClient.on('connect', () => {
+ connectCount++
+ serverClient.connack(connack)
+ })
+
+ serverClient.on('subscribe', () => {
+ subscribeCount++
+
+ // disconnect before sending the suback on the first subscribe
+ if (subscribeCount === 1) {
+ client.stream.end()
+ }
+
+ // after the second connection, confirm that the only two
+ // subscribes have taken place, then cleanup and exit
+ if (connectCount >= 2) {
+ assert.strictEqual(subscribeCount, 2)
+ client.end(true, done)
+ }
+ })
+ })
+
+ client.subscribe('hello')
+ })
+
+ it('should resubscribe exactly once', function _test(done) {
+ const client = connect({ reconnectPeriod: 100, ...config })
+ let subscribeCount = 0
+
+ server.on('client', (serverClient) => {
+ serverClient.on('connect', () => {
+ serverClient.connack(connack)
+ })
+
+ serverClient.on('subscribe', () => {
+ subscribeCount++
+
+ // disconnect before sending the suback on the first subscribe
+ if (subscribeCount === 1) {
+ client.stream.end()
+ }
+
+ // after the second connection, only two subs
+ // subscribes have taken place, then cleanup and exit
+ if (subscribeCount === 2) {
+ client.end(true, done)
+ }
+ })
+ })
+
+ client.subscribe('hello')
+ })
+ })
+ })
+
+ describe('message id to subscription topic mapping', () => {
+ it('should not create a mapping if resubscribe is disabled', function _test(done) {
+ const client = connect({ resubscribe: false })
+ client.subscribe('test1')
+ client.subscribe('test2')
+ assert.strictEqual(Object.keys(client.messageIdToTopic).length, 0)
+ client.end(true, done)
+ })
+
+ it('should create a mapping for each subscribe call', function _test(done) {
+ const client = connect()
+ client.subscribe('test1')
+ assert.strictEqual(Object.keys(client.messageIdToTopic).length, 1)
+ client.subscribe('test2')
+ assert.strictEqual(Object.keys(client.messageIdToTopic).length, 2)
+
+ client.subscribe(['test3', 'test4'])
+ assert.strictEqual(Object.keys(client.messageIdToTopic).length, 3)
+ client.subscribe(['test5', 'test6'])
+ assert.strictEqual(Object.keys(client.messageIdToTopic).length, 4)
+
+ client.end(true, done)
+ })
+
+ it('should remove the mapping after suback', function _test(done) {
+ const client = connect()
+ client.once('connect', () => {
+ client.subscribe('test1', { qos: 2 }, () => {
+ assert.strictEqual(
+ Object.keys(client.messageIdToTopic).length,
+ 0,
+ )
+
+ client.subscribe(['test2', 'test3'], { qos: 2 }, () => {
+ assert.strictEqual(
+ Object.keys(client.messageIdToTopic).length,
+ 0,
+ )
+ client.end(done)
+ })
+ })
+ })
+ })
+ })
}
diff --git a/test/abstract_store.js b/test/abstract_store.js
index bae4bb033..56a060f61 100644
--- a/test/abstract_store.js
+++ b/test/abstract_store.js
@@ -1,136 +1,130 @@
-'use strict'
-
require('should')
-module.exports = function abstractStoreTest (build) {
- let store
+module.exports = function abstractStoreTest(build) {
+ let store
- // eslint-disable-next-line
+ // eslint-disable-next-line
beforeEach(function (done) {
- build(function (err, _store) {
- store = _store
- done(err)
- })
- })
-
- afterEach(function (done) {
- store.close(done)
- })
-
- it('should put and stream in-flight packets', function (done) {
- const packet = {
- topic: 'hello',
- payload: 'world',
- qos: 1,
- messageId: 42
- }
-
- store.put(packet, function () {
- store
- .createStream()
- .on('data', function (data) {
- data.should.eql(packet)
- done()
- })
- })
- })
-
- it('should support destroying the stream', function (done) {
- const packet = {
- topic: 'hello',
- payload: 'world',
- qos: 1,
- messageId: 42
- }
-
- store.put(packet, function () {
- const stream = store.createStream()
- stream.on('close', done)
- stream.destroy()
- })
- })
-
- it('should add and del in-flight packets', function (done) {
- const packet = {
- topic: 'hello',
- payload: 'world',
- qos: 1,
- messageId: 42
- }
-
- store.put(packet, function () {
- store.del(packet, function () {
- store
- .createStream()
- .on('data', function () {
- done(new Error('this should never happen'))
- })
- .on('end', done)
- })
- })
- })
-
- it('should replace a packet when doing put with the same messageId', function (done) {
- const packet1 = {
- cmd: 'publish', // added
- topic: 'hello',
- payload: 'world',
- qos: 2,
- messageId: 42
- }
- const packet2 = {
- cmd: 'pubrel', // added
- qos: 2,
- messageId: 42
- }
-
- store.put(packet1, function () {
- store.put(packet2, function () {
- store
- .createStream()
- .on('data', function (data) {
- data.should.eql(packet2)
- done()
- })
- })
- })
- })
-
- it('should return the original packet on del', function (done) {
- const packet = {
- topic: 'hello',
- payload: 'world',
- qos: 1,
- messageId: 42
- }
-
- store.put(packet, function () {
- store.del({ messageId: 42 }, function (err, deleted) {
- if (err) {
- throw err
- }
- deleted.should.eql(packet)
- done()
- })
- })
- })
-
- it('should get a packet with the same messageId', function (done) {
- const packet = {
- topic: 'hello',
- payload: 'world',
- qos: 1,
- messageId: 42
- }
-
- store.put(packet, function () {
- store.get({ messageId: 42 }, function (err, fromDb) {
- if (err) {
- throw err
- }
- fromDb.should.eql(packet)
- done()
- })
- })
- })
+ build((err, _store) => {
+ store = _store
+ done(err)
+ })
+ })
+
+ afterEach(function test(done) {
+ store.close(done)
+ })
+
+ it('should put and stream in-flight packets', function test(done) {
+ const packet = {
+ topic: 'hello',
+ payload: 'world',
+ qos: 1,
+ messageId: 42,
+ }
+
+ store.put(packet, () => {
+ store.createStream().on('data', (data) => {
+ data.should.eql(packet)
+ done()
+ })
+ })
+ })
+
+ it('should support destroying the stream', function test(done) {
+ const packet = {
+ topic: 'hello',
+ payload: 'world',
+ qos: 1,
+ messageId: 42,
+ }
+
+ store.put(packet, () => {
+ const stream = store.createStream()
+ stream.on('close', done)
+ stream.destroy()
+ })
+ })
+
+ it('should add and del in-flight packets', function test(done) {
+ const packet = {
+ topic: 'hello',
+ payload: 'world',
+ qos: 1,
+ messageId: 42,
+ }
+
+ store.put(packet, () => {
+ store.del(packet, () => {
+ store
+ .createStream()
+ .on('data', () => {
+ done(new Error('this should never happen'))
+ })
+ .on('end', done)
+ })
+ })
+ })
+
+ it('should replace a packet when doing put with the same messageId', function test(done) {
+ const packet1 = {
+ cmd: 'publish', // added
+ topic: 'hello',
+ payload: 'world',
+ qos: 2,
+ messageId: 42,
+ }
+ const packet2 = {
+ cmd: 'pubrel', // added
+ qos: 2,
+ messageId: 42,
+ }
+
+ store.put(packet1, () => {
+ store.put(packet2, () => {
+ store.createStream().on('data', (data) => {
+ data.should.eql(packet2)
+ done()
+ })
+ })
+ })
+ })
+
+ it('should return the original packet on del', function test(done) {
+ const packet = {
+ topic: 'hello',
+ payload: 'world',
+ qos: 1,
+ messageId: 42,
+ }
+
+ store.put(packet, () => {
+ store.del({ messageId: 42 }, (err, deleted) => {
+ if (err) {
+ throw err
+ }
+ deleted.should.eql(packet)
+ done()
+ })
+ })
+ })
+
+ it('should get a packet with the same messageId', function test(done) {
+ const packet = {
+ topic: 'hello',
+ payload: 'world',
+ qos: 1,
+ messageId: 42,
+ }
+
+ store.put(packet, () => {
+ store.get({ messageId: 42 }, (err, fromDb) => {
+ if (err) {
+ throw err
+ }
+ fromDb.should.eql(packet)
+ done()
+ })
+ })
+ })
}
diff --git a/test/browser/server.js b/test/browser/server.js
index 73066eda3..fe1349967 100644
--- a/test/browser/server.js
+++ b/test/browser/server.js
@@ -1,129 +1,134 @@
-'use strict'
-
const WS = require('ws')
+
const WebSocketServer = WS.Server
const Connection = require('mqtt-connection')
const http = require('http')
-const handleClient = function (client) {
- const self = this
-
- if (!self.clients) {
- self.clients = {}
- }
-
- client.on('connect', function (packet) {
- if (packet.clientId === 'invalid') {
- client.connack({ returnCode: 2 })
- } else {
- client.connack({ returnCode: 0 })
- }
- self.clients[packet.clientId] = client
- client.subscriptions = []
- })
-
- client.on('publish', function (packet) {
- let k, c, s, publish
- switch (packet.qos) {
- case 0:
- break
- case 1:
- client.puback(packet)
- break
- case 2:
- client.pubrec(packet)
- break
- }
-
- for (k in self.clients) {
- c = self.clients[k]
- publish = false
-
- for (let i = 0; i < c.subscriptions.length; i++) {
- s = c.subscriptions[i]
-
- if (s.test(packet.topic)) {
- publish = true
- }
- }
-
- if (publish) {
- try {
- c.publish({ topic: packet.topic, payload: packet.payload })
- } catch (error) {
- delete self.clients[k]
- }
- }
- }
- })
-
- client.on('pubrel', function (packet) {
- client.pubcomp(packet)
- })
-
- client.on('pubrec', function (packet) {
- client.pubrel(packet)
- })
-
- client.on('pubcomp', function () {
- // Nothing to be done
- })
-
- client.on('subscribe', function (packet) {
- let qos, topic, reg
- const granted = []
-
- for (let i = 0; i < packet.subscriptions.length; i++) {
- qos = packet.subscriptions[i].qos
- topic = packet.subscriptions[i].topic
- reg = new RegExp(topic.replace('+', '[^/]+').replace('#', '.+') + '$')
-
- granted.push(qos)
- client.subscriptions.push(reg)
- }
-
- client.suback({ messageId: packet.messageId, granted })
- })
-
- client.on('unsubscribe', function (packet) {
- client.unsuback(packet)
- })
-
- client.on('pingreq', function () {
- client.pingresp()
- })
+const handleClient = (client) => {
+ const self = this
+
+ if (!self.clients) {
+ self.clients = {}
+ }
+
+ client.on('connect', (packet) => {
+ if (packet.clientId === 'invalid') {
+ client.connack({ returnCode: 2 })
+ } else {
+ client.connack({ returnCode: 0 })
+ }
+ self.clients[packet.clientId] = client
+ client.subscriptions = []
+ })
+
+ client.on('publish', (packet) => {
+ let k
+ let c
+ let s
+ let publish
+ switch (packet.qos) {
+ case 0:
+ break
+ case 1:
+ client.puback(packet)
+ break
+ case 2:
+ client.pubrec(packet)
+ break
+ }
+
+ for (k in self.clients) {
+ c = self.clients[k]
+ publish = false
+
+ for (let i = 0; i < c.subscriptions.length; i++) {
+ s = c.subscriptions[i]
+
+ if (s.test(packet.topic)) {
+ publish = true
+ }
+ }
+
+ if (publish) {
+ try {
+ c.publish({ topic: packet.topic, payload: packet.payload })
+ } catch (error) {
+ delete self.clients[k]
+ }
+ }
+ }
+ })
+
+ client.on('pubrel', (packet) => {
+ client.pubcomp(packet)
+ })
+
+ client.on('pubrec', (packet) => {
+ client.pubrel(packet)
+ })
+
+ client.on('pubcomp', () => {
+ // Nothing to be done
+ })
+
+ client.on('subscribe', (packet) => {
+ let qos
+ let topic
+ let reg
+ const granted = []
+
+ for (let i = 0; i < packet.subscriptions.length; i++) {
+ qos = packet.subscriptions[i].qos
+ topic = packet.subscriptions[i].topic
+ reg = new RegExp(
+ `${topic.replace('+', '[^/]+').replace('#', '.+')}$`,
+ )
+
+ granted.push(qos)
+ client.subscriptions.push(reg)
+ }
+
+ client.suback({ messageId: packet.messageId, granted })
+ })
+
+ client.on('unsubscribe', (packet) => {
+ client.unsuback(packet)
+ })
+
+ client.on('pingreq', () => {
+ client.pingresp()
+ })
}
-function start (startPort, done) {
- const server = http.createServer()
- const wss = new WebSocketServer({ server })
-
- wss.on('connection', function (ws) {
- if (!(ws.protocol === 'mqtt' ||
- ws.protocol === 'mqttv3.1')) {
- return ws.close()
- }
-
- const stream = WS.createWebSocketStream(ws)
- const connection = new Connection(stream)
- handleClient.call(server, connection)
- })
- server.listen(startPort, done)
- server.on('request', function (req, res) {
- res.statusCode = 404
- res.end('Not Found')
- })
- return server
+function start(startPort, done) {
+ const server = http.createServer()
+ const wss = new WebSocketServer({ server })
+
+ wss.on('connection', (ws) => {
+ if (!(ws.protocol === 'mqtt' || ws.protocol === 'mqttv3.1')) {
+ return ws.close()
+ }
+
+ const stream = WS.createWebSocketStream(ws)
+ const connection = new Connection(stream)
+ handleClient.call(server, connection)
+ })
+ server.listen(startPort, done)
+ server.on('request', (req, res) => {
+ res.statusCode = 404
+ res.end('Not Found')
+ })
+ return server
}
const port = process.env.PORT || process.env.AIRTAP_SUPPORT_PORT
if (require.main === module) {
- start(port, function (err) {
- if (err) {
- console.error(err)
- return
- }
- console.log('tunnelled server started on port', port)
- })
+ start(port, (err) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ console.log('tunnelled server started on port', port)
+ })
}
diff --git a/test/browser/test.js b/test/browser/test.js
index 37bc95cfa..8825a2e4c 100644
--- a/test/browser/test.js
+++ b/test/browser/test.js
@@ -1,8 +1,6 @@
-'use strict'
-
const test = require('tape')
-const mqtt = require('../../dist/mqtt.min')
const _URL = require('url')
+const mqtt = require('../../dist/mqtt.min')
// eslint-disable-next-line
const parsed = _URL.parse(document.URL)
const isHttps = parsed.protocol === 'https:'
@@ -10,43 +8,50 @@ const port = parsed.port || (isHttps ? 443 : 80)
const host = parsed.hostname
const protocol = isHttps ? 'wss' : 'ws'
-const client = mqtt.connect({ protocolId: 'MQIsdp', protocolVersion: 3, protocol, port, host, log: console.log.bind(console) })
-client.on('offline', function () {
- console.log('client offline')
+const client = mqtt.connect({
+ protocolId: 'MQIsdp',
+ protocolVersion: 3,
+ protocol,
+ port,
+ host,
+ log: console.log.bind(console),
+})
+client.on('offline', () => {
+ console.log('client offline')
})
-client.on('connect', function () {
- console.log('client connect')
+client.on('connect', () => {
+ console.log('client connect')
})
-client.on('reconnect', function () {
- console.log('client reconnect')
+client.on('reconnect', () => {
+ console.log('client reconnect')
})
-test('MQTT.js browser test', function (t) {
- t.plan(6)
- client.on('connect', function () {
- client.on('message', function (topic, msg) {
- t.equal(topic, 'hello', 'should match topic')
- t.equal(msg.toString(), 'Hello World!', 'should match payload')
- client.end(() => {
- t.pass('client should close')
- })
- })
+test('MQTT.js browser test', (t) => {
+ t.plan(6)
+ client.on('connect', () => {
+ client.on('message', (topic, msg) => {
+ t.equal(topic, 'hello', 'should match topic')
+ t.equal(msg.toString(), 'Hello World!', 'should match payload')
+ client.end(() => {
+ t.pass('client should close')
+ })
+ })
- client.subscribe('hello', function (err) {
- t.error(err, 'no error on subscribe')
- if (!err) {
- client.publish('hello', 'Hello World!', function (err) {
- t.error(err, 'no error on publish')
- })
- }
- })
- })
+ client.subscribe('hello', (err) => {
+ t.error(err, 'no error on subscribe')
+ if (!err) {
+ client.publish('hello', 'Hello World!', (err2) => {
+ t.error(err2, 'no error on publish')
+ })
+ }
+ })
+ })
- client.on('error', function (err) {
- t.fail(err, 'no error')
- })
+ client.on('error', (err) => {
+ t.fail(err, 'no error')
+ })
- client.once('close', function () {
- t.pass('should emit close')
- })
+ client.once('close', () => {
+ t.pass('should emit close')
+ })
})
diff --git a/test/client.js b/test/client.js
index 2d78a81d4..e9127174d 100644
--- a/test/client.js
+++ b/test/client.js
@@ -1,492 +1,529 @@
-'use strict'
-
const mqtt = require('..')
-const assert = require('chai').assert
+const { assert } = require('chai')
const { fork } = require('child_process')
const path = require('path')
-const abstractClientTests = require('./abstract_client')
const net = require('net')
const eos = require('end-of-stream')
const mqttPacket = require('mqtt-packet')
-const Duplex = require('readable-stream').Duplex
+const { Duplex } = require('readable-stream')
const Connection = require('mqtt-connection')
-const MqttServer = require('./server').MqttServer
const util = require('util')
const ports = require('./helpers/port_list')
-const serverBuilder = require('./server_helpers_for_client_tests').serverBuilder
+const { serverBuilder } = require('./server_helpers_for_client_tests')
const debug = require('debug')('TEST:client')
+const { MqttServer } = require('./server')
+const abstractClientTests = require('./abstract_client')
-describe('MqttClient', function () {
- let client
- const server = serverBuilder('mqtt')
- const config = { protocol: 'mqtt', port: ports.PORT }
- server.listen(ports.PORT)
-
- after(function () {
- // clean up and make sure the server is no longer listening...
- if (server.listening) {
- server.close()
- }
- })
-
- abstractClientTests(server, config)
-
- describe('creating', function () {
- it('should allow instantiation of MqttClient', function (done) {
- try {
- client = new mqtt.MqttClient(function () {
- throw Error('break')
- }, {})
- client.end()
- } catch (err) {
- assert.strictEqual(err.message, 'break')
- done()
- }
- })
-
- it('should disable number cache if specified in options', function (done) {
- try {
- assert.isTrue(mqttPacket.writeToStream.cacheNumbers)
- client = new mqtt.MqttClient(function () {
- throw Error('break')
- }, { writeCache: false })
- client.end()
- } catch (err) {
- assert.isFalse(mqttPacket.writeToStream.cacheNumbers)
- done()
- }
- })
- })
-
- describe('message ids', function () {
- it('should increment the message id', function (done) {
- client = mqtt.connect(config)
- const currentId = client._nextId()
-
- assert.equal(client._nextId(), currentId + 1)
- client.end((err) => done(err))
- })
-
- it('should not throw an error if packet\'s messageId is not found when receiving a pubrel packet', function (done) {
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({ returnCode: 0 })
- serverClient.pubrel({ messageId: Math.floor(Math.random() * 9000) + 1000 })
- })
- })
-
- server2.listen(ports.PORTAND49, function () {
- client = mqtt.connect({
- port: ports.PORTAND49,
- host: 'localhost'
- })
-
- client.on('packetsend', function (packet) {
- if (packet.cmd === 'pubcomp') {
- client.end((err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- }
- })
- })
- })
-
- it('should not go overflow if the TCP frame contains a lot of PUBLISH packets', function (done) {
- const parser = mqttPacket.parser()
- const max = 1000
- let count = 0
- const duplex = new Duplex({
- read: function (n) { },
- write: function (chunk, enc, cb) {
- parser.parse(chunk)
- cb() // nothing to do
- }
- })
- client = new mqtt.MqttClient(function () {
- return duplex
- }, {})
-
- client.on('message', function (t, p, packet) {
- if (++count === max) {
- // BUGBUG: the client.end callback never gets called here
- // client.end((err) => done(err))
- client.end()
- done()
- }
- })
-
- parser.on('packet', function (packet) {
- const packets = []
-
- if (packet.cmd === 'connect') {
- duplex.push(mqttPacket.generate({
- cmd: 'connack',
- sessionPresent: false,
- returnCode: 0
- }))
-
- for (let i = 0; i < max; i++) {
- packets.push(mqttPacket.generate({
- cmd: 'publish',
- topic: Buffer.from('hello'),
- payload: Buffer.from('world'),
- retain: false,
- dup: false,
- messageId: i + 1,
- qos: 1
- }))
- }
-
- duplex.push(Buffer.concat(packets))
- }
- })
- })
- })
-
- describe('flushing', function () {
- it('should attempt to complete pending unsub and send on ping timeout', function (done) {
- this.timeout(10000)
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({ returnCode: 0 })
- })
- }).listen(ports.PORTAND72)
-
- let pubCallbackCalled = false
- let unsubscribeCallbackCalled = false
- client = mqtt.connect({
- port: ports.PORTAND72,
- host: 'localhost',
- keepalive: 1,
- connectTimeout: 350,
- reconnectPeriod: 0
- })
- client.once('connect', () => {
- client.publish('fakeTopic', 'fakeMessage', { qos: 1 }, (err, result) => {
- assert.exists(err)
- pubCallbackCalled = true
- })
- client.unsubscribe('fakeTopic', (err, result) => {
- assert.exists(err)
- unsubscribeCallbackCalled = true
- })
- setTimeout(() => {
- client.end((err1) => {
- assert.strictEqual(pubCallbackCalled && unsubscribeCallbackCalled, true, 'callbacks not invoked')
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- }, 5000)
- })
- })
- })
-
- describe('reconnecting', function () {
- it('should attempt to reconnect once server is down', function (done) {
- this.timeout(30000)
-
- const innerServer = fork(path.join(__dirname, 'helpers', 'server_process.js'), { execArgv: ['--inspect'] })
- innerServer.on('close', (code) => {
- if (code) {
- done(util.format('child process closed with code %d', code))
- }
- })
-
- innerServer.on('exit', (code) => {
- if (code) {
- done(util.format('child process exited with code %d', code))
- }
- })
-
- client = mqtt.connect({ port: 3481, host: 'localhost', keepalive: 1 })
- client.once('connect', function () {
- innerServer.kill('SIGINT') // mocks server shutdown
- client.once('close', function () {
- assert.exists(client.reconnectTimer)
- client.end(true, (err) => done(err))
- })
- })
- })
-
- it('should reconnect if a connack is not received in an interval', function (done) {
- this.timeout(2000)
-
- const server2 = net.createServer().listen(ports.PORTAND43)
-
- server2.on('connection', function (c) {
- eos(c, function () {
- server2.close()
- })
- })
-
- server2.on('listening', function () {
- client = mqtt.connect({
- servers: [
- { port: ports.PORTAND43, host: 'localhost_fake' },
- { port: ports.PORT, host: 'localhost' }
- ],
- connectTimeout: 500
- })
-
- server.once('client', function () {
- client.end(false, (err) => {
- done(err)
- })
- })
-
- client.once('connect', function () {
- client.stream.destroy()
- })
- })
- })
-
- it('should not be cleared by the connack timer', function (done) {
- this.timeout(4000)
-
- const server2 = net.createServer().listen(ports.PORTAND44)
-
- server2.on('connection', function (c) {
- c.destroy()
- })
-
- server2.once('listening', function () {
- const connectTimeout = 1000
- const reconnectPeriod = 100
- const expectedReconnects = Math.floor(connectTimeout / reconnectPeriod)
- let reconnects = 0
- client = mqtt.connect({
- port: ports.PORTAND44,
- host: 'localhost',
- connectTimeout,
- reconnectPeriod
- })
-
- client.on('reconnect', function () {
- reconnects++
- if (reconnects >= expectedReconnects) {
- client.end(true, (err) => done(err))
- }
- })
- })
- })
-
- it('should not keep requeueing the first message when offline', function (done) {
- this.timeout(2500)
-
- const server2 = serverBuilder('mqtt').listen(ports.PORTAND45)
- client = mqtt.connect({
- port: ports.PORTAND45,
- host: 'localhost',
- connectTimeout: 350,
- reconnectPeriod: 300
- })
-
- server2.on('client', function (serverClient) {
- client.publish('hello', 'world', { qos: 1 }, function () {
- serverClient.destroy()
- server2.close(() => {
- debug('now publishing message in an offline state')
- client.publish('hello', 'world', { qos: 1 })
- })
- })
- })
-
- setTimeout(function () {
- if (client.queue.length === 0) {
- debug('calling final client.end()')
- client.end(true, (err) => done(err))
- } else {
- debug('calling client.end()')
- // Do not call done. We want to trigger a reconnect here.
- client.end(true)
- }
- }, 2000)
- })
-
- it('should not send the same subscribe multiple times on a flaky connection', function (done) {
- this.timeout(3500)
-
- const KILL_COUNT = 4
- const subIds = {}
- let killedConnections = 0
- client = mqtt.connect({
- port: ports.PORTAND46,
- host: 'localhost',
- connectTimeout: 350,
- reconnectPeriod: 300
- })
-
- const server2 = new MqttServer(function (serverClient) {
- debug('client received on server2.')
- debug('subscribing to topic `topic`')
- client.subscribe('topic', function () {
- debug('once subscribed to topic, end client, destroy serverClient, and close server.')
- serverClient.destroy()
- server2.close(() => {
- client.end(true, (err) => done(err))
- })
- })
-
- serverClient.on('subscribe', function (packet) {
- if (killedConnections < KILL_COUNT) {
- // Kill the first few sub attempts to simulate a flaky connection
- killedConnections++
- serverClient.destroy()
- } else {
- // Keep track of acks
- if (!subIds[packet.messageId]) {
- subIds[packet.messageId] = 0
- }
- subIds[packet.messageId]++
- if (subIds[packet.messageId] > 1) {
- done(new Error('Multiple duplicate acked subscriptions received for messageId ' + packet.messageId))
- client.end(true)
- serverClient.end()
- server2.destroy()
- }
-
- serverClient.suback({
- messageId: packet.messageId,
- granted: packet.subscriptions.map(function (e) {
- return e.qos
- })
- })
- }
- })
- }).listen(ports.PORTAND46)
- })
-
- it('should not fill the queue of subscribes if it cannot connect', function (done) {
- this.timeout(2500)
- const server2 = net.createServer(function (stream) {
- const serverClient = new Connection(stream)
-
- serverClient.on('error', function (e) { /* do nothing */ })
- serverClient.on('connect', function (packet) {
- serverClient.connack({ returnCode: 0 })
- serverClient.destroy()
- })
- })
-
- server2.listen(ports.PORTAND48, function () {
- client = mqtt.connect({
- port: ports.PORTAND48,
- host: 'localhost',
- connectTimeout: 350,
- reconnectPeriod: 300
- })
-
- client.subscribe('hello')
-
- setTimeout(function () {
- assert.equal(client.queue.length, 1)
- client.end(true, (err) => done(err))
- }, 1000)
- })
- })
-
- it('should not send the same publish multiple times on a flaky connection', function (done) {
- this.timeout(3500)
-
- const KILL_COUNT = 4
- let killedConnections = 0
- const pubIds = {}
- client = mqtt.connect({
- port: ports.PORTAND47,
- host: 'localhost',
- connectTimeout: 350,
- reconnectPeriod: 300
- })
-
- const server2 = net.createServer(function (stream) {
- const serverClient = new Connection(stream)
- serverClient.on('error', function () { })
- serverClient.on('connect', function (packet) {
- if (packet.clientId === 'invalid') {
- serverClient.connack({ returnCode: 2 })
- } else {
- serverClient.connack({ returnCode: 0 })
- }
- })
-
- this.emit('client', serverClient)
- }).listen(ports.PORTAND47)
-
- server2.on('client', function (serverClient) {
- client.publish('topic', 'data', { qos: 1 }, function () {
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- })
-
- serverClient.on('publish', function onPublish (packet) {
- if (killedConnections < KILL_COUNT) {
- // Kill the first few pub attempts to simulate a flaky connection
- killedConnections++
- serverClient.destroy()
-
- // to avoid receiving inflight messages
- serverClient.removeListener('publish', onPublish)
- } else {
- // Keep track of acks
- if (!pubIds[packet.messageId]) {
- pubIds[packet.messageId] = 0
- }
-
- pubIds[packet.messageId]++
-
- if (pubIds[packet.messageId] > 1) {
- done(new Error('Multiple duplicate acked publishes received for messageId ' + packet.messageId))
- client.end(true)
- serverClient.destroy()
- server2.destroy()
- }
-
- serverClient.puback(packet)
- }
- })
- })
- })
- })
-
- it('check emit error on checkDisconnection w/o callback', function (done) {
- this.timeout(15000)
-
- const server2 = new MqttServer(function (client) {
- client.on('connect', function (packet) {
- client.connack({
- reasonCode: 0
- })
- })
- client.on('publish', function (packet) {
- setImmediate(function () {
- packet.reasonCode = 0
- client.puback(packet)
- })
- })
- }).listen(ports.PORTAND118)
-
- const opts = {
- host: 'localhost',
- port: ports.PORTAND118,
- protocolVersion: 5
- }
- client = mqtt.connect(opts)
-
- // wait for the client to receive an error...
- client.on('error', function (error) {
- assert.equal(error.message, 'client disconnecting')
- server2.close((err) => done(err))
- })
- client.on('connect', function () {
- client.end(function () {
- client._checkDisconnecting()
- })
- })
- })
+describe('MqttClient', () => {
+ let client
+ const server = serverBuilder('mqtt')
+ const config = { protocol: 'mqtt', port: ports.PORT }
+ server.listen(ports.PORT)
+
+ after(() => {
+ // clean up and make sure the server is no longer listening...
+ if (server.listening) {
+ server.close()
+ }
+ })
+
+ abstractClientTests(server, config)
+
+ describe('creating', () => {
+ it('should allow instantiation of MqttClient', function test(done) {
+ try {
+ client = new mqtt.MqttClient(() => {
+ throw Error('break')
+ }, {})
+ client.end()
+ } catch (err) {
+ assert.strictEqual(err.message, 'break')
+ done()
+ }
+ })
+
+ it('should disable number cache if specified in options', function test(done) {
+ try {
+ assert.isTrue(mqttPacket.writeToStream.cacheNumbers)
+ client = new mqtt.MqttClient(
+ () => {
+ throw Error('break')
+ },
+ { writeCache: false },
+ )
+ client.end()
+ } catch (err) {
+ assert.isFalse(mqttPacket.writeToStream.cacheNumbers)
+ done()
+ }
+ })
+ })
+
+ describe('message ids', () => {
+ it('should increment the message id', function test(done) {
+ client = mqtt.connect(config)
+ const currentId = client._nextId()
+
+ assert.equal(client._nextId(), currentId + 1)
+ client.end((err) => done(err))
+ })
+
+ it("should not throw an error if packet's messageId is not found when receiving a pubrel packet", function test(done) {
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({ returnCode: 0 })
+ serverClient.pubrel({
+ messageId: Math.floor(Math.random() * 9000) + 1000,
+ })
+ })
+ })
+
+ server2.listen(ports.PORTAND49, () => {
+ client = mqtt.connect({
+ port: ports.PORTAND49,
+ host: 'localhost',
+ })
+
+ client.on('packetsend', (packet) => {
+ if (packet.cmd === 'pubcomp') {
+ client.end((err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ }
+ })
+ })
+ })
+
+ it('should not go overflow if the TCP frame contains a lot of PUBLISH packets', function test(done) {
+ const parser = mqttPacket.parser()
+ const max = 1000
+ let count = 0
+ const duplex = new Duplex({
+ read(n) {},
+ write(chunk, enc, cb) {
+ parser.parse(chunk)
+ cb() // nothing to do
+ },
+ })
+ client = new mqtt.MqttClient(() => duplex, {})
+
+ client.on('message', (t, p, packet) => {
+ if (++count === max) {
+ // BUGBUG: the client.end callback never gets called here
+ // client.end((err) => done(err))
+ client.end()
+ done()
+ }
+ })
+
+ parser.on('packet', (packet) => {
+ const packets = []
+
+ if (packet.cmd === 'connect') {
+ duplex.push(
+ mqttPacket.generate({
+ cmd: 'connack',
+ sessionPresent: false,
+ returnCode: 0,
+ }),
+ )
+
+ for (let i = 0; i < max; i++) {
+ packets.push(
+ mqttPacket.generate({
+ cmd: 'publish',
+ topic: Buffer.from('hello'),
+ payload: Buffer.from('world'),
+ retain: false,
+ dup: false,
+ messageId: i + 1,
+ qos: 1,
+ }),
+ )
+ }
+
+ duplex.push(Buffer.concat(packets))
+ }
+ })
+ })
+ })
+
+ describe('flushing', () => {
+ it('should attempt to complete pending unsub and send on ping timeout', function test(done) {
+ this.timeout(10000)
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({ returnCode: 0 })
+ })
+ }).listen(ports.PORTAND72)
+
+ let pubCallbackCalled = false
+ let unsubscribeCallbackCalled = false
+ client = mqtt.connect({
+ port: ports.PORTAND72,
+ host: 'localhost',
+ keepalive: 1,
+ connectTimeout: 350,
+ reconnectPeriod: 0,
+ })
+ client.once('connect', () => {
+ client.publish(
+ 'fakeTopic',
+ 'fakeMessage',
+ { qos: 1 },
+ (err, result) => {
+ assert.exists(err)
+ pubCallbackCalled = true
+ },
+ )
+ client.unsubscribe('fakeTopic', (err, result) => {
+ assert.exists(err)
+ unsubscribeCallbackCalled = true
+ })
+ setTimeout(() => {
+ client.end((err1) => {
+ assert.strictEqual(
+ pubCallbackCalled && unsubscribeCallbackCalled,
+ true,
+ 'callbacks not invoked',
+ )
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ }, 5000)
+ })
+ })
+ })
+
+ describe('reconnecting', () => {
+ it('should attempt to reconnect once server is down', function test(done) {
+ this.timeout(30000)
+
+ const innerServer = fork(
+ path.join(__dirname, 'helpers', 'server_process.js'),
+ {
+ execArgv: ['--inspect'],
+ },
+ )
+ innerServer.on('close', (code) => {
+ if (code) {
+ done(util.format('child process closed with code %d', code))
+ }
+ })
+
+ innerServer.on('exit', (code) => {
+ if (code) {
+ done(util.format('child process exited with code %d', code))
+ }
+ })
+
+ client = mqtt.connect({
+ port: 3481,
+ host: 'localhost',
+ keepalive: 1,
+ })
+ client.once('connect', () => {
+ innerServer.kill('SIGINT') // mocks server shutdown
+ client.once('close', () => {
+ assert.exists(client.reconnectTimer)
+ client.end(true, (err) => done(err))
+ })
+ })
+ })
+
+ it('should reconnect if a connack is not received in an interval', function test(done) {
+ this.timeout(2000)
+
+ const server2 = net.createServer().listen(ports.PORTAND43)
+
+ server2.on('connection', (c) => {
+ eos(c, () => {
+ server2.close()
+ })
+ })
+
+ server2.on('listening', () => {
+ client = mqtt.connect({
+ servers: [
+ { port: ports.PORTAND43, host: 'localhost_fake' },
+ { port: ports.PORT, host: 'localhost' },
+ ],
+ connectTimeout: 500,
+ })
+
+ server.once('client', () => {
+ client.end(false, (err) => {
+ done(err)
+ })
+ })
+
+ client.once('connect', () => {
+ client.stream.destroy()
+ })
+ })
+ })
+
+ it('should not be cleared by the connack timer', function test(done) {
+ this.timeout(4000)
+
+ const server2 = net.createServer().listen(ports.PORTAND44)
+
+ server2.on('connection', (c) => {
+ c.destroy()
+ })
+
+ server2.once('listening', () => {
+ const connectTimeout = 1000
+ const reconnectPeriod = 100
+ const expectedReconnects = Math.floor(
+ connectTimeout / reconnectPeriod,
+ )
+ let reconnects = 0
+ client = mqtt.connect({
+ port: ports.PORTAND44,
+ host: 'localhost',
+ connectTimeout,
+ reconnectPeriod,
+ })
+
+ client.on('reconnect', () => {
+ reconnects++
+ if (reconnects >= expectedReconnects) {
+ client.end(true, (err) => done(err))
+ }
+ })
+ })
+ })
+
+ it('should not keep requeueing the first message when offline', function test(done) {
+ this.timeout(2500)
+
+ const server2 = serverBuilder('mqtt').listen(ports.PORTAND45)
+ client = mqtt.connect({
+ port: ports.PORTAND45,
+ host: 'localhost',
+ connectTimeout: 350,
+ reconnectPeriod: 300,
+ })
+
+ server2.on('client', (serverClient) => {
+ client.publish('hello', 'world', { qos: 1 }, () => {
+ serverClient.destroy()
+ server2.close(() => {
+ debug('now publishing message in an offline state')
+ client.publish('hello', 'world', { qos: 1 })
+ })
+ })
+ })
+
+ setTimeout(() => {
+ if (client.queue.length === 0) {
+ debug('calling final client.end()')
+ client.end(true, (err) => done(err))
+ } else {
+ debug('calling client.end()')
+ // Do not call done. We want to trigger a reconnect here.
+ client.end(true)
+ }
+ }, 2000)
+ })
+
+ it('should not send the same subscribe multiple times on a flaky connection', function test(done) {
+ this.timeout(3500)
+
+ const KILL_COUNT = 4
+ const subIds = {}
+ let killedConnections = 0
+ client = mqtt.connect({
+ port: ports.PORTAND46,
+ host: 'localhost',
+ connectTimeout: 350,
+ reconnectPeriod: 300,
+ })
+
+ const server2 = new MqttServer((serverClient) => {
+ debug('client received on server2.')
+ debug('subscribing to topic `topic`')
+ client.subscribe('topic', () => {
+ debug(
+ 'once subscribed to topic, end client, destroy serverClient, and close server.',
+ )
+ serverClient.destroy()
+ server2.close(() => {
+ client.end(true, (err) => done(err))
+ })
+ })
+
+ serverClient.on('subscribe', (packet) => {
+ if (killedConnections < KILL_COUNT) {
+ // Kill the first few sub attempts to simulate a flaky connection
+ killedConnections++
+ serverClient.destroy()
+ } else {
+ // Keep track of acks
+ if (!subIds[packet.messageId]) {
+ subIds[packet.messageId] = 0
+ }
+ subIds[packet.messageId]++
+ if (subIds[packet.messageId] > 1) {
+ done(
+ new Error(
+ `Multiple duplicate acked subscriptions received for messageId ${packet.messageId}`,
+ ),
+ )
+ client.end(true)
+ serverClient.end()
+ server2.destroy()
+ }
+
+ serverClient.suback({
+ messageId: packet.messageId,
+ granted: packet.subscriptions.map((e) => e.qos),
+ })
+ }
+ })
+ }).listen(ports.PORTAND46)
+ })
+
+ it('should not fill the queue of subscribes if it cannot connect', function test(done) {
+ this.timeout(2500)
+ const server2 = net.createServer((stream) => {
+ const serverClient = new Connection(stream)
+
+ serverClient.on('error', (e) => {
+ /* do nothing */
+ })
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({ returnCode: 0 })
+ serverClient.destroy()
+ })
+ })
+
+ server2.listen(ports.PORTAND48, () => {
+ client = mqtt.connect({
+ port: ports.PORTAND48,
+ host: 'localhost',
+ connectTimeout: 350,
+ reconnectPeriod: 300,
+ })
+
+ client.subscribe('hello')
+
+ setTimeout(() => {
+ assert.equal(client.queue.length, 1)
+ client.end(true, (err) => done(err))
+ }, 1000)
+ })
+ })
+
+ it('should not send the same publish multiple times on a flaky connection', function test(done) {
+ this.timeout(3500)
+
+ const KILL_COUNT = 4
+ let killedConnections = 0
+ const pubIds = {}
+ client = mqtt.connect({
+ port: ports.PORTAND47,
+ host: 'localhost',
+ connectTimeout: 350,
+ reconnectPeriod: 300,
+ })
+
+ const server2 = net
+ .createServer((stream) => {
+ const serverClient = new Connection(stream)
+ serverClient.on('error', () => {})
+ serverClient.on('connect', (packet) => {
+ if (packet.clientId === 'invalid') {
+ serverClient.connack({ returnCode: 2 })
+ } else {
+ serverClient.connack({ returnCode: 0 })
+ }
+ })
+
+ server2.emit('client', serverClient)
+ })
+ .listen(ports.PORTAND47)
+
+ server2.on('client', (serverClient) => {
+ client.publish('topic', 'data', { qos: 1 }, () => {
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+
+ serverClient.on('publish', function onPublish(packet) {
+ if (killedConnections < KILL_COUNT) {
+ // Kill the first few pub attempts to simulate a flaky connection
+ killedConnections++
+ serverClient.destroy()
+
+ // to avoid receiving inflight messages
+ serverClient.removeListener('publish', onPublish)
+ } else {
+ // Keep track of acks
+ if (!pubIds[packet.messageId]) {
+ pubIds[packet.messageId] = 0
+ }
+
+ pubIds[packet.messageId]++
+
+ if (pubIds[packet.messageId] > 1) {
+ done(
+ new Error(
+ `Multiple duplicate acked publishes received for messageId ${packet.messageId}`,
+ ),
+ )
+ client.end(true)
+ serverClient.destroy()
+ server2.destroy()
+ }
+
+ serverClient.puback(packet)
+ }
+ })
+ })
+ })
+ })
+
+ it('check emit error on checkDisconnection w/o callback', function test(done) {
+ this.timeout(15000)
+
+ const server2 = new MqttServer((c) => {
+ c.on('connect', (packet) => {
+ c.connack({
+ reasonCode: 0,
+ })
+ })
+ c.on('publish', (packet) => {
+ setImmediate(() => {
+ packet.reasonCode = 0
+ c.puback(packet)
+ })
+ })
+ }).listen(ports.PORTAND118)
+
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND118,
+ protocolVersion: 5,
+ }
+ client = mqtt.connect(opts)
+
+ // wait for the client to receive an error...
+ client.on('error', (error) => {
+ assert.equal(error.message, 'client disconnecting')
+ server2.close((err) => done(err))
+ })
+ client.on('connect', () => {
+ client.end(() => {
+ client._checkDisconnecting()
+ })
+ })
+ })
})
diff --git a/test/client_mqtt5.js b/test/client_mqtt5.js
index 8a516cf21..fa1a61078 100644
--- a/test/client_mqtt5.js
+++ b/test/client_mqtt5.js
@@ -1,1128 +1,1251 @@
-'use strict'
-
+const { assert } = require('chai')
const mqtt = require('..')
const abstractClientTests = require('./abstract_client')
-const MqttServer = require('./server').MqttServer
-const assert = require('chai').assert
-const serverBuilder = require('./server_helpers_for_client_tests').serverBuilder
+const { MqttServer } = require('./server')
+const { serverBuilder } = require('./server_helpers_for_client_tests')
const ports = require('./helpers/port_list')
-
-describe('MQTT 5.0', function () {
- const server = serverBuilder('mqtt').listen(ports.PORTAND115)
- const config = { protocol: 'mqtt', port: ports.PORTAND115, protocolVersion: 5, properties: { maximumPacketSize: 200 } }
-
- abstractClientTests(server, config)
-
- it('topic should be complemented on receive', function (done) {
- this.timeout(15000)
-
- const opts = {
- host: 'localhost',
- port: ports.PORTAND103,
- protocolVersion: 5,
- properties: {
- topicAliasMaximum: 3
- }
- }
- const client = mqtt.connect(opts)
- let publishCount = 0
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- assert.strictEqual(packet.properties.topicAliasMaximum, 3)
- serverClient.connack({
- reasonCode: 0
- })
- // register topicAlias
- serverClient.publish({
- messageId: 0,
- topic: 'test1',
- payload: 'Message',
- qos: 0,
- properties: { topicAlias: 1 }
- })
- // use topicAlias
- serverClient.publish({
- messageId: 0,
- topic: '',
- payload: 'Message',
- qos: 0,
- properties: { topicAlias: 1 }
- })
- // overwrite registered topicAlias
- serverClient.publish({
- messageId: 0,
- topic: 'test2',
- payload: 'Message',
- qos: 0,
- properties: { topicAlias: 1 }
- })
- // use topicAlias
- serverClient.publish({
- messageId: 0,
- topic: '',
- payload: 'Message',
- qos: 0,
- properties: { topicAlias: 1 }
- })
- })
- }).listen(ports.PORTAND103)
-
- client.on('message', function (topic, messagee, packet) {
- switch (publishCount++) {
- case 0:
- assert.strictEqual(topic, 'test1')
- assert.strictEqual(packet.topic, 'test1')
- assert.strictEqual(packet.properties.topicAlias, 1)
- break
- case 1:
- assert.strictEqual(topic, 'test1')
- assert.strictEqual(packet.topic, '')
- assert.strictEqual(packet.properties.topicAlias, 1)
- break
- case 2:
- assert.strictEqual(topic, 'test2')
- assert.strictEqual(packet.topic, 'test2')
- assert.strictEqual(packet.properties.topicAlias, 1)
- break
- case 3:
- assert.strictEqual(topic, 'test2')
- assert.strictEqual(packet.topic, '')
- assert.strictEqual(packet.properties.topicAlias, 1)
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- break
- }
- })
- })
-
- it('registered topic alias should automatically used if autoUseTopicAlias is true', function (done) {
- this.timeout(15000)
-
- const opts = {
- host: 'localhost',
- port: ports.PORTAND103,
- protocolVersion: 5,
- autoUseTopicAlias: true
- }
- const client = mqtt.connect(opts)
-
- let publishCount = 0
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0,
- properties: {
- topicAliasMaximum: 3
- }
- })
- })
- serverClient.on('publish', function (packet) {
- switch (publishCount++) {
- case 0:
- assert.strictEqual(packet.topic, 'test1')
- assert.strictEqual(packet.properties.topicAlias, 1)
- break
- case 1:
- assert.strictEqual(packet.topic, '')
- assert.strictEqual(packet.properties.topicAlias, 1)
- break
- case 2:
- assert.strictEqual(packet.topic, '')
- assert.strictEqual(packet.properties.topicAlias, 1)
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- break
- }
- })
- }).listen(ports.PORTAND103)
-
- client.on('connect', function () {
- // register topicAlias
- client.publish('test1', 'Message', { properties: { topicAlias: 1 } })
- // use topicAlias
- client.publish('', 'Message', { properties: { topicAlias: 1 } })
- // use topicAlias by autoApplyTopicAlias
- client.publish('test1', 'Message')
- })
- })
-
- it('topicAlias is automatically used if autoAssignTopicAlias is true', function (done) {
- this.timeout(15000)
-
- const opts = {
- host: 'localhost',
- port: ports.PORTAND103,
- protocolVersion: 5,
- autoAssignTopicAlias: true
- }
- const client = mqtt.connect(opts)
-
- let publishCount = 0
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0,
- properties: {
- topicAliasMaximum: 3
- }
- })
- })
- serverClient.on('publish', function (packet) {
- switch (publishCount++) {
- case 0:
- assert.strictEqual(packet.topic, 'test1')
- assert.strictEqual(packet.properties.topicAlias, 1)
- break
- case 1:
- assert.strictEqual(packet.topic, 'test2')
- assert.strictEqual(packet.properties.topicAlias, 2)
- break
- case 2:
- assert.strictEqual(packet.topic, 'test3')
- assert.strictEqual(packet.properties.topicAlias, 3)
- break
- case 3:
- assert.strictEqual(packet.topic, '')
- assert.strictEqual(packet.properties.topicAlias, 1)
- break
- case 4:
- assert.strictEqual(packet.topic, '')
- assert.strictEqual(packet.properties.topicAlias, 3)
- break
- case 5:
- assert.strictEqual(packet.topic, 'test4')
- assert.strictEqual(packet.properties.topicAlias, 2)
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- break
- }
- })
- }).listen(ports.PORTAND103)
-
- client.on('connect', function () {
- // register topicAlias
- client.publish('test1', 'Message')
- client.publish('test2', 'Message')
- client.publish('test3', 'Message')
-
- // use topicAlias
- client.publish('test1', 'Message')
- client.publish('test3', 'Message')
-
- // renew LRU topicAlias
- client.publish('test4', 'Message')
- })
- })
-
- it('topicAlias should be removed and topic restored on resend', function (done) {
- this.timeout(15000)
-
- const incomingStore = new mqtt.Store({ clean: false })
- const outgoingStore = new mqtt.Store({ clean: false })
- const opts = {
- host: 'localhost',
- port: ports.PORTAND103,
- protocolVersion: 5,
- clientId: 'cid1',
- incomingStore,
- outgoingStore,
- clean: false,
- reconnectPeriod: 100
- }
- const client = mqtt.connect(opts)
-
- let connectCount = 0
- let publishCount = 0
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- switch (connectCount++) {
- case 0:
- serverClient.connack({
- reasonCode: 0,
- sessionPresent: false,
- properties: {
- topicAliasMaximum: 3
- }
- })
- break
- case 1:
- serverClient.connack({
- reasonCode: 0,
- sessionPresent: true,
- properties: {
- topicAliasMaximum: 3
- }
- })
- break
- }
- })
- serverClient.on('publish', function (packet) {
- switch (publishCount++) {
- case 0:
- assert.strictEqual(packet.topic, 'test1')
- assert.strictEqual(packet.properties.topicAlias, 1)
- break
- case 1:
- assert.strictEqual(packet.topic, '')
- assert.strictEqual(packet.properties.topicAlias, 1)
- setImmediate(function () {
- serverClient.stream.destroy()
- })
- break
- case 2: {
- assert.strictEqual(packet.topic, 'test1')
- let alias1
- if (packet.properties) {
- alias1 = packet.properties.topicAlias
- }
- assert.strictEqual(alias1, undefined)
- serverClient.puback({ messageId: packet.messageId })
- break
- }
- case 3: {
- assert.strictEqual(packet.topic, 'test1')
- let alias2
- if (packet.properties) {
- alias2 = packet.properties.topicAlias
- }
- assert.strictEqual(alias2, undefined)
- serverClient.puback({ messageId: packet.messageId })
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- break
- }
- }
- })
- }).listen(ports.PORTAND103)
-
- client.once('connect', function () {
- // register topicAlias
- client.publish('test1', 'Message', { qos: 1, properties: { topicAlias: 1 } })
- // use topicAlias
- client.publish('', 'Message', { qos: 1, properties: { topicAlias: 1 } })
- })
- })
-
- it('topicAlias should be removed and topic restored on offline publish', function (done) {
- this.timeout(15000)
-
- const incomingStore = new mqtt.Store({ clean: false })
- const outgoingStore = new mqtt.Store({ clean: false })
- const opts = {
- host: 'localhost',
- port: ports.PORTAND103,
- protocolVersion: 5,
- clientId: 'cid1',
- incomingStore,
- outgoingStore,
- clean: false,
- reconnectPeriod: 100
- }
- const client = mqtt.connect(opts)
-
- let connectCount = 0
- let publishCount = 0
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- switch (connectCount++) {
- case 0:
- serverClient.connack({
- reasonCode: 0,
- sessionPresent: false,
- properties: {
- topicAliasMaximum: 3
- }
- })
- setImmediate(function () {
- serverClient.stream.destroy()
- })
- break
- case 1:
- serverClient.connack({
- reasonCode: 0,
- sessionPresent: true,
- properties: {
- topicAliasMaximum: 3
- }
- })
- break
- }
- })
- serverClient.on('publish', function (packet) {
- switch (publishCount++) {
- case 0: {
- assert.strictEqual(packet.topic, 'test1')
- let alias1
- if (packet.properties) {
- alias1 = packet.properties.topicAlias
- }
- assert.strictEqual(alias1, undefined)
- assert.strictEqual(packet.qos, 1)
- serverClient.puback({ messageId: packet.messageId })
- break
- }
- case 1: {
- assert.strictEqual(packet.topic, 'test1')
- let alias2
- if (packet.properties) {
- alias2 = packet.properties.topicAlias
- }
- assert.strictEqual(alias2, undefined)
- assert.strictEqual(packet.qos, 0)
- break
- }
- case 2: {
- assert.strictEqual(packet.topic, 'test1')
- let alias3
- if (packet.properties) {
- alias3 = packet.properties.topicAlias
- }
- assert.strictEqual(alias3, undefined)
- assert.strictEqual(packet.qos, 0)
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- break
- }
- }
- })
- }).listen(ports.PORTAND103)
-
- client.once('close', function () {
- // register topicAlias
- client.publish('test1', 'Message', { qos: 0, properties: { topicAlias: 1 } })
- // use topicAlias
- client.publish('', 'Message', { qos: 0, properties: { topicAlias: 1 } })
- client.publish('', 'Message', { qos: 1, properties: { topicAlias: 1 } })
- })
- })
-
- it('should error cb call if PUBLISH out of range topicAlias', function (done) {
- this.timeout(15000)
-
- const opts = {
- host: 'localhost',
- port: ports.PORTAND103,
- protocolVersion: 5
- }
- const client = mqtt.connect(opts)
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0,
- sessionPresent: false,
- properties: {
- topicAliasMaximum: 3
- }
- })
- })
- }).listen(ports.PORTAND103)
-
- client.on('connect', function () {
- // register topicAlias
- client.publish(
- 'test1',
- 'Message',
- { properties: { topicAlias: 4 } },
- function (error) {
- assert.strictEqual(error.message, 'Sending Topic Alias out of range')
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- })
- })
-
- it('should error cb call if PUBLISH out of range topicAlias on topicAlias disabled by broker', function (done) {
- this.timeout(15000)
-
- const opts = {
- host: 'localhost',
- port: ports.PORTAND103,
- protocolVersion: 5
- }
- const client = mqtt.connect(opts)
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0,
- sessionPresent: false
- })
- })
- }).listen(ports.PORTAND103)
-
- client.on('connect', function () {
- // register topicAlias
- client.publish(
- 'test1',
- 'Message',
- { properties: { topicAlias: 1 } },
- function (error) {
- assert.strictEqual(error.message, 'Sending Topic Alias out of range')
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- })
- })
-
- it('should throw an error if broker PUBLISH out of range topicAlias', function (done) {
- this.timeout(15000)
-
- const opts = {
- host: 'localhost',
- port: ports.PORTAND103,
- protocolVersion: 5,
- properties: {
- topicAliasMaximum: 3
- }
- }
- const client = mqtt.connect(opts)
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0,
- sessionPresent: false
- })
- // register out of range topicAlias
- serverClient.publish({
- messageId: 0,
- topic: 'test1',
- payload: 'Message',
- qos: 0,
- properties: { topicAlias: 4 }
- })
- })
- }).listen(ports.PORTAND103)
-
- client.on('error', function (error) {
- assert.strictEqual(error.message, 'Received Topic Alias is out of range')
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- })
-
- it('should throw an error if broker PUBLISH topicAlias:0', function (done) {
- this.timeout(15000)
-
- const opts = {
- host: 'localhost',
- port: ports.PORTAND103,
- protocolVersion: 5,
- properties: {
- topicAliasMaximum: 3
- }
- }
- const client = mqtt.connect(opts)
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0,
- sessionPresent: false
- })
- // register out of range topicAlias
- serverClient.publish({
- messageId: 0,
- topic: 'test1',
- payload: 'Message',
- qos: 0,
- properties: { topicAlias: 0 }
- })
- })
- }).listen(ports.PORTAND103)
-
- client.on('error', function (error) {
- assert.strictEqual(error.message, 'Received Topic Alias is out of range')
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- })
-
- it('should throw an error if broker PUBLISH unregistered topicAlias', function (done) {
- this.timeout(15000)
-
- const opts = {
- host: 'localhost',
- port: ports.PORTAND103,
- protocolVersion: 5,
- properties: {
- topicAliasMaximum: 3
- }
- }
- const client = mqtt.connect(opts)
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0,
- sessionPresent: false
- })
- // register out of range topicAlias
- serverClient.publish({
- messageId: 0,
- topic: '', // use topic alias
- payload: 'Message',
- qos: 0,
- properties: { topicAlias: 1 } // in range topic alias
- })
- })
- }).listen(ports.PORTAND103)
-
- client.on('error', function (error) {
- assert.strictEqual(error.message, 'Received unregistered Topic Alias')
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- })
-
- it('should throw an error if there is Auth Data with no Auth Method', function (done) {
- this.timeout(5000)
- const opts = { host: 'localhost', port: ports.PORTAND115, protocolVersion: 5, properties: { authenticationData: Buffer.from([1, 2, 3, 4]) } }
- console.log('client connecting')
- const client = mqtt.connect(opts)
- client.on('error', function (error) {
- console.log('error hit')
- assert.strictEqual(error.message, 'Packet has no Authentication Method')
- // client will not be connected, so we will call done.
- assert.isTrue(client.disconnected, 'validate client is disconnected')
- client.end(true, done)
- })
- })
-
- it('auth packet', function (done) {
- this.timeout(15000)
- const opts = { host: 'localhost', port: ports.PORTAND115, protocolVersion: 5, properties: { authenticationMethod: 'json' }, authPacket: {} }
- const client = mqtt.connect(opts)
- server.once('client', function (c) {
- console.log('server received client')
- c.on('auth', function (packet) {
- console.log('serverClient received auth: packet %o', packet)
- client.end(done)
- })
- })
- console.log('calling mqtt connect')
- })
-
- it('Maximum Packet Size', function (done) {
- this.timeout(15000)
- const opts = { host: 'localhost', port: ports.PORTAND115, protocolVersion: 5, properties: { maximumPacketSize: 1 } }
- const client = mqtt.connect(opts)
- client.on('error', function (error) {
- assert.strictEqual(error.message, 'exceeding packets size connack')
- client.end(true, done)
- })
- })
-
- it('Change values of some properties by server response', function (done) {
- this.timeout(15000)
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0,
- properties: {
- serverKeepAlive: 16,
- maximumPacketSize: 95
- }
- })
- })
- }).listen(ports.PORTAND116)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND116,
- protocolVersion: 5,
- properties: {
- topicAliasMaximum: 10,
- serverKeepAlive: 11,
- maximumPacketSize: 100
- }
- }
- const client = mqtt.connect(opts)
- client.on('connect', function () {
- assert.strictEqual(client.options.keepalive, 16)
- assert.strictEqual(client.options.properties.maximumPacketSize, 95)
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- })
-
- it('should resubscribe when reconnecting with protocolVersion 5 and Session Present flag is false', function (done) {
- this.timeout(15000)
- let tryReconnect = true
- let reconnectEvent = false
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0,
- sessionPresent: false
- })
- serverClient.on('subscribe', function () {
- if (!tryReconnect) {
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- }
- })
- })
- }).listen(ports.PORTAND316)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND316,
- protocolVersion: 5
- }
- const client = mqtt.connect(opts)
-
- client.on('reconnect', function () {
- reconnectEvent = true
- })
-
- client.on('connect', function (connack) {
- assert.isFalse(connack.sessionPresent)
- if (tryReconnect) {
- client.subscribe('hello', function () {
- client.stream.end()
- })
-
- tryReconnect = false
- } else {
- assert.isTrue(reconnectEvent)
- }
- })
- })
-
- it('should resubscribe when reconnecting with protocolVersion 5 and properties', function (done) {
- // this.timeout(15000)
- let tryReconnect = true
- let reconnectEvent = false
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0,
- sessionPresent: false
- })
- })
- serverClient.on('subscribe', function (packet) {
- if (!reconnectEvent) {
- serverClient.suback({
- messageId: packet.messageId,
- granted: packet.subscriptions.map(function (e) {
- return e.qos
- })
- })
- } else {
- if (!tryReconnect) {
- assert.strictEqual(packet.properties.userProperties.test, 'test')
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- }
- }
- })
- }).listen(ports.PORTAND326)
-
- const opts = {
- host: 'localhost',
- port: ports.PORTAND326,
- protocolVersion: 5
- }
- const client = mqtt.connect(opts)
-
- client.on('reconnect', function () {
- reconnectEvent = true
- })
-
- client.on('connect', function (connack) {
- assert.isFalse(connack.sessionPresent)
- if (tryReconnect) {
- client.subscribe('hello', { properties: { userProperties: { test: 'test' } } }, function () {
- client.stream.end()
- })
-
- tryReconnect = false
- } else {
- assert.isTrue(reconnectEvent)
- }
- })
- })
-
- const serverThatSendsErrors = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0
- })
- })
- serverClient.on('publish', function (packet) {
- setImmediate(function () {
- switch (packet.qos) {
- case 0:
- break
- case 1:
- packet.reasonCode = 142
- delete packet.cmd
- serverClient.puback(packet)
- break
- case 2:
- packet.reasonCode = 142
- delete packet.cmd
- serverClient.pubrec(packet)
- break
- }
- })
- })
-
- serverClient.on('pubrel', function (packet) {
- packet.reasonCode = 142
- delete packet.cmd
- serverClient.pubcomp(packet)
- })
- })
-
- it('Subscribe properties', function (done) {
- this.timeout(15000)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND119,
- protocolVersion: 5
- }
- const subOptions = { properties: { subscriptionIdentifier: 1234 } }
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0
- })
- })
- serverClient.on('subscribe', function (packet) {
- assert.strictEqual(packet.properties.subscriptionIdentifier, subOptions.properties.subscriptionIdentifier)
- client.end(true, (err1) => {
- server2.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- }).listen(ports.PORTAND119)
-
- const client = mqtt.connect(opts)
- client.on('connect', function () {
- client.subscribe('a/b', subOptions)
- })
- })
-
- it('puback handling errors check', function (done) {
- this.timeout(15000)
- serverThatSendsErrors.listen(ports.PORTAND117)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND117,
- protocolVersion: 5
- }
- const client = mqtt.connect(opts)
- client.once('connect', () => {
- client.publish('a/b', 'message', { qos: 1 }, function (err, packet) {
- assert.strictEqual(err.message, 'Publish error: Session taken over')
- assert.strictEqual(err.code, 142)
- })
- client.end(true, (err1) => {
- serverThatSendsErrors.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- })
-
- it('pubrec handling errors check', function (done) {
- this.timeout(15000)
- serverThatSendsErrors.listen(ports.PORTAND118)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND118,
- protocolVersion: 5
- }
- const client = mqtt.connect(opts)
- client.once('connect', () => {
- client.publish('a/b', 'message', { qos: 2 }, function (err, packet) {
- assert.strictEqual(err.message, 'Publish error: Session taken over')
- assert.strictEqual(err.code, 142)
- })
- client.end(true, (err1) => {
- serverThatSendsErrors.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- })
-
- it('puback handling custom reason code', function (done) {
- // this.timeout(15000)
- serverThatSendsErrors.listen(ports.PORTAND117)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND117,
- protocolVersion: 5,
- customHandleAcks: function (topic, message, packet, cb) {
- let code = 0
- if (topic === 'a/b') {
- code = 128
- }
- cb(code)
- }
- }
-
- serverThatSendsErrors.once('client', function (serverClient) {
- serverClient.once('subscribe', function () {
- serverClient.publish({ topic: 'a/b', payload: 'payload', qos: 1, messageId: 1 })
- })
-
- serverClient.on('puback', function (packet) {
- assert.strictEqual(packet.reasonCode, 128)
- client.end(true, (err1) => {
- serverThatSendsErrors.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- })
-
- const client = mqtt.connect(opts)
- client.once('connect', function () {
- client.subscribe('a/b', { qos: 1 })
- })
- })
-
- it('server side disconnect', function (done) {
- this.timeout(15000)
- const server2 = new MqttServer(function (serverClient) {
- serverClient.on('connect', function (packet) {
- serverClient.connack({
- reasonCode: 0
- })
- serverClient.disconnect({ reasonCode: 128 })
- server2.close()
- })
- })
- server2.listen(ports.PORTAND327)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND327,
- protocolVersion: 5
- }
-
- const client = mqtt.connect(opts)
- client.once('disconnect', function (disconnectPacket) {
- assert.strictEqual(disconnectPacket.reasonCode, 128)
- client.end(true, (err) => done(err))
- })
- })
-
- it('pubrec handling custom reason code', function (done) {
- this.timeout(15000)
- serverThatSendsErrors.listen(ports.PORTAND117)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND117,
- protocolVersion: 5,
- customHandleAcks: function (topic, message, packet, cb) {
- let code = 0
- if (topic === 'a/b') {
- code = 128
- }
- cb(code)
- }
- }
-
- serverThatSendsErrors.once('client', function (serverClient) {
- serverClient.once('subscribe', function () {
- serverClient.publish({ topic: 'a/b', payload: 'payload', qos: 2, messageId: 1 })
- })
-
- serverClient.on('pubrec', function (packet) {
- assert.strictEqual(packet.reasonCode, 128)
- client.end(true, (err1) => {
- serverThatSendsErrors.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- })
-
- const client = mqtt.connect(opts)
- client.once('connect', function () {
- client.subscribe('a/b', { qos: 1 })
- })
- })
-
- it('puback handling custom reason code with error', function (done) {
- this.timeout(15000)
- serverThatSendsErrors.listen(ports.PORTAND117)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND117,
- protocolVersion: 5,
- customHandleAcks: function (topic, message, packet, cb) {
- const code = 0
- if (topic === 'a/b') {
- cb(new Error('a/b is not valid'))
- }
- cb(code)
- }
- }
-
- serverThatSendsErrors.once('client', function (serverClient) {
- serverClient.once('subscribe', function () {
- serverClient.publish({ topic: 'a/b', payload: 'payload', qos: 1, messageId: 1 })
- })
- })
-
- const client = mqtt.connect(opts)
- client.on('error', function (error) {
- assert.strictEqual(error.message, 'a/b is not valid')
- client.end(true, (err1) => {
- serverThatSendsErrors.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- client.once('connect', function () {
- client.subscribe('a/b', { qos: 1 })
- })
- })
-
- it('pubrec handling custom reason code with error', function (done) {
- this.timeout(15000)
- serverThatSendsErrors.listen(ports.PORTAND117)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND117,
- protocolVersion: 5,
- customHandleAcks: function (topic, message, packet, cb) {
- const code = 0
- if (topic === 'a/b') {
- cb(new Error('a/b is not valid'))
- }
- cb(code)
- }
- }
-
- serverThatSendsErrors.once('client', function (serverClient) {
- serverClient.once('subscribe', function () {
- serverClient.publish({ topic: 'a/b', payload: 'payload', qos: 2, messageId: 1 })
- })
- })
-
- const client = mqtt.connect(opts)
- client.on('error', function (error) {
- assert.strictEqual(error.message, 'a/b is not valid')
- client.end(true, (err1) => {
- serverThatSendsErrors.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- client.once('connect', function () {
- client.subscribe('a/b', { qos: 1 })
- })
- })
-
- it('puback handling custom invalid reason code', function (done) {
- this.timeout(15000)
- serverThatSendsErrors.listen(ports.PORTAND117)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND117,
- protocolVersion: 5,
- customHandleAcks: function (topic, message, packet, cb) {
- let code = 0
- if (topic === 'a/b') {
- code = 124124
- }
- cb(code)
- }
- }
-
- serverThatSendsErrors.once('client', function (serverClient) {
- serverClient.once('subscribe', function () {
- serverClient.publish({ topic: 'a/b', payload: 'payload', qos: 1, messageId: 1 })
- })
- })
-
- const client = mqtt.connect(opts)
- client.on('error', function (error) {
- assert.strictEqual(error.message, 'Wrong reason code for puback')
- client.end(true, (err1) => {
- serverThatSendsErrors.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- client.once('connect', function () {
- client.subscribe('a/b', { qos: 1 })
- })
- })
-
- it('pubrec handling custom invalid reason code', function (done) {
- this.timeout(15000)
- serverThatSendsErrors.listen(ports.PORTAND117)
- const opts = {
- host: 'localhost',
- port: ports.PORTAND117,
- protocolVersion: 5,
- customHandleAcks: function (topic, message, packet, cb) {
- let code = 0
- if (topic === 'a/b') {
- code = 34535
- }
- cb(code)
- }
- }
-
- serverThatSendsErrors.once('client', function (serverClient) {
- serverClient.once('subscribe', function () {
- serverClient.publish({ topic: 'a/b', payload: 'payload', qos: 2, messageId: 1 })
- })
- })
-
- const client = mqtt.connect(opts)
- client.on('error', function (error) {
- assert.strictEqual(error.message, 'Wrong reason code for pubrec')
- client.end(true, (err1) => {
- serverThatSendsErrors.close((err2) => {
- done(err1 || err2)
- })
- })
- })
- client.once('connect', function () {
- client.subscribe('a/b', { qos: 1 })
- })
- })
+const { close } = require('inspector')
+
+describe('MQTT 5.0', () => {
+ const server = serverBuilder('mqtt').listen(ports.PORTAND115)
+ const config = {
+ protocol: 'mqtt',
+ port: ports.PORTAND115,
+ protocolVersion: 5,
+ properties: { maximumPacketSize: 200 },
+ }
+
+ abstractClientTests(server, config)
+
+ it('topic should be complemented on receive', function test(done) {
+ this.timeout(15000)
+
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND103,
+ protocolVersion: 5,
+ properties: {
+ topicAliasMaximum: 3,
+ },
+ }
+ const client = mqtt.connect(opts)
+ let publishCount = 0
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ assert.strictEqual(packet.properties.topicAliasMaximum, 3)
+ serverClient.connack({
+ reasonCode: 0,
+ })
+ // register topicAlias
+ serverClient.publish({
+ messageId: 0,
+ topic: 'test1',
+ payload: 'Message',
+ qos: 0,
+ properties: { topicAlias: 1 },
+ })
+ // use topicAlias
+ serverClient.publish({
+ messageId: 0,
+ topic: '',
+ payload: 'Message',
+ qos: 0,
+ properties: { topicAlias: 1 },
+ })
+ // overwrite registered topicAlias
+ serverClient.publish({
+ messageId: 0,
+ topic: 'test2',
+ payload: 'Message',
+ qos: 0,
+ properties: { topicAlias: 1 },
+ })
+ // use topicAlias
+ serverClient.publish({
+ messageId: 0,
+ topic: '',
+ payload: 'Message',
+ qos: 0,
+ properties: { topicAlias: 1 },
+ })
+ })
+ }).listen(ports.PORTAND103)
+
+ client.on('message', (topic, messagee, packet) => {
+ switch (publishCount++) {
+ case 0:
+ assert.strictEqual(topic, 'test1')
+ assert.strictEqual(packet.topic, 'test1')
+ assert.strictEqual(packet.properties.topicAlias, 1)
+ break
+ case 1:
+ assert.strictEqual(topic, 'test1')
+ assert.strictEqual(packet.topic, '')
+ assert.strictEqual(packet.properties.topicAlias, 1)
+ break
+ case 2:
+ assert.strictEqual(topic, 'test2')
+ assert.strictEqual(packet.topic, 'test2')
+ assert.strictEqual(packet.properties.topicAlias, 1)
+ break
+ case 3:
+ assert.strictEqual(topic, 'test2')
+ assert.strictEqual(packet.topic, '')
+ assert.strictEqual(packet.properties.topicAlias, 1)
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ break
+ }
+ })
+ })
+
+ it('registered topic alias should automatically used if autoUseTopicAlias is true', function test(done) {
+ this.timeout(15000)
+
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND103,
+ protocolVersion: 5,
+ autoUseTopicAlias: true,
+ }
+ const client = mqtt.connect(opts)
+
+ let publishCount = 0
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ properties: {
+ topicAliasMaximum: 3,
+ },
+ })
+ })
+ serverClient.on('publish', (packet) => {
+ switch (publishCount++) {
+ case 0:
+ assert.strictEqual(packet.topic, 'test1')
+ assert.strictEqual(packet.properties.topicAlias, 1)
+ break
+ case 1:
+ assert.strictEqual(packet.topic, '')
+ assert.strictEqual(packet.properties.topicAlias, 1)
+ break
+ case 2:
+ assert.strictEqual(packet.topic, '')
+ assert.strictEqual(packet.properties.topicAlias, 1)
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ break
+ }
+ })
+ }).listen(ports.PORTAND103)
+
+ client.on('connect', () => {
+ // register topicAlias
+ client.publish('test1', 'Message', {
+ properties: { topicAlias: 1 },
+ })
+ // use topicAlias
+ client.publish('', 'Message', { properties: { topicAlias: 1 } })
+ // use topicAlias by autoApplyTopicAlias
+ client.publish('test1', 'Message')
+ })
+ })
+
+ it('topicAlias is automatically used if autoAssignTopicAlias is true', function test(done) {
+ this.timeout(15000)
+
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND103,
+ protocolVersion: 5,
+ autoAssignTopicAlias: true,
+ }
+ const client = mqtt.connect(opts)
+
+ let publishCount = 0
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ properties: {
+ topicAliasMaximum: 3,
+ },
+ })
+ })
+ serverClient.on('publish', (packet) => {
+ switch (publishCount++) {
+ case 0:
+ assert.strictEqual(packet.topic, 'test1')
+ assert.strictEqual(packet.properties.topicAlias, 1)
+ break
+ case 1:
+ assert.strictEqual(packet.topic, 'test2')
+ assert.strictEqual(packet.properties.topicAlias, 2)
+ break
+ case 2:
+ assert.strictEqual(packet.topic, 'test3')
+ assert.strictEqual(packet.properties.topicAlias, 3)
+ break
+ case 3:
+ assert.strictEqual(packet.topic, '')
+ assert.strictEqual(packet.properties.topicAlias, 1)
+ break
+ case 4:
+ assert.strictEqual(packet.topic, '')
+ assert.strictEqual(packet.properties.topicAlias, 3)
+ break
+ case 5:
+ assert.strictEqual(packet.topic, 'test4')
+ assert.strictEqual(packet.properties.topicAlias, 2)
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ break
+ }
+ })
+ }).listen(ports.PORTAND103)
+
+ client.on('connect', () => {
+ // register topicAlias
+ client.publish('test1', 'Message')
+ client.publish('test2', 'Message')
+ client.publish('test3', 'Message')
+
+ // use topicAlias
+ client.publish('test1', 'Message')
+ client.publish('test3', 'Message')
+
+ // renew LRU topicAlias
+ client.publish('test4', 'Message')
+ })
+ })
+
+ it('topicAlias should be removed and topic restored on resend', function test(done) {
+ this.timeout(15000)
+
+ const incomingStore = new mqtt.Store({ clean: false })
+ const outgoingStore = new mqtt.Store({ clean: false })
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND103,
+ protocolVersion: 5,
+ clientId: 'cid1',
+ incomingStore,
+ outgoingStore,
+ clean: false,
+ reconnectPeriod: 100,
+ }
+ const client = mqtt.connect(opts)
+
+ let connectCount = 0
+ let publishCount = 0
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ switch (connectCount++) {
+ case 0:
+ serverClient.connack({
+ reasonCode: 0,
+ sessionPresent: false,
+ properties: {
+ topicAliasMaximum: 3,
+ },
+ })
+ break
+ case 1:
+ serverClient.connack({
+ reasonCode: 0,
+ sessionPresent: true,
+ properties: {
+ topicAliasMaximum: 3,
+ },
+ })
+ break
+ }
+ })
+ serverClient.on('publish', (packet) => {
+ switch (publishCount++) {
+ case 0:
+ assert.strictEqual(packet.topic, 'test1')
+ assert.strictEqual(packet.properties.topicAlias, 1)
+ break
+ case 1:
+ assert.strictEqual(packet.topic, '')
+ assert.strictEqual(packet.properties.topicAlias, 1)
+ setImmediate(() => {
+ serverClient.stream.destroy()
+ })
+ break
+ case 2: {
+ assert.strictEqual(packet.topic, 'test1')
+ let alias1
+ if (packet.properties) {
+ alias1 = packet.properties.topicAlias
+ }
+ assert.strictEqual(alias1, undefined)
+ serverClient.puback({ messageId: packet.messageId })
+ break
+ }
+ case 3: {
+ assert.strictEqual(packet.topic, 'test1')
+ let alias2
+ if (packet.properties) {
+ alias2 = packet.properties.topicAlias
+ }
+ assert.strictEqual(alias2, undefined)
+ serverClient.puback({ messageId: packet.messageId })
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ break
+ }
+ }
+ })
+ }).listen(ports.PORTAND103)
+
+ client.once('connect', () => {
+ // register topicAlias
+ client.publish('test1', 'Message', {
+ qos: 1,
+ properties: { topicAlias: 1 },
+ })
+ // use topicAlias
+ client.publish('', 'Message', {
+ qos: 1,
+ properties: { topicAlias: 1 },
+ })
+ })
+ })
+
+ it('topicAlias should be removed and topic restored on offline publish', function test(done) {
+ this.timeout(15000)
+
+ const incomingStore = new mqtt.Store({ clean: false })
+ const outgoingStore = new mqtt.Store({ clean: false })
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND103,
+ protocolVersion: 5,
+ clientId: 'cid1',
+ incomingStore,
+ outgoingStore,
+ clean: false,
+ reconnectPeriod: 100,
+ }
+ const client = mqtt.connect(opts)
+
+ let connectCount = 0
+ let publishCount = 0
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ switch (connectCount++) {
+ case 0:
+ serverClient.connack({
+ reasonCode: 0,
+ sessionPresent: false,
+ properties: {
+ topicAliasMaximum: 3,
+ },
+ })
+ setImmediate(() => {
+ serverClient.stream.destroy()
+ })
+ break
+ case 1:
+ serverClient.connack({
+ reasonCode: 0,
+ sessionPresent: true,
+ properties: {
+ topicAliasMaximum: 3,
+ },
+ })
+ break
+ }
+ })
+ serverClient.on('publish', (packet) => {
+ switch (publishCount++) {
+ case 0: {
+ assert.strictEqual(packet.topic, 'test1')
+ let alias1
+ if (packet.properties) {
+ alias1 = packet.properties.topicAlias
+ }
+ assert.strictEqual(alias1, undefined)
+ assert.strictEqual(packet.qos, 1)
+ serverClient.puback({ messageId: packet.messageId })
+ break
+ }
+ case 1: {
+ assert.strictEqual(packet.topic, 'test1')
+ let alias2
+ if (packet.properties) {
+ alias2 = packet.properties.topicAlias
+ }
+ assert.strictEqual(alias2, undefined)
+ assert.strictEqual(packet.qos, 0)
+ break
+ }
+ case 2: {
+ assert.strictEqual(packet.topic, 'test1')
+ let alias3
+ if (packet.properties) {
+ alias3 = packet.properties.topicAlias
+ }
+ assert.strictEqual(alias3, undefined)
+ assert.strictEqual(packet.qos, 0)
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ break
+ }
+ }
+ })
+ }).listen(ports.PORTAND103)
+
+ client.once('close', () => {
+ // register topicAlias
+ client.publish('test1', 'Message', {
+ qos: 0,
+ properties: { topicAlias: 1 },
+ })
+ // use topicAlias
+ client.publish('', 'Message', {
+ qos: 0,
+ properties: { topicAlias: 1 },
+ })
+ client.publish('', 'Message', {
+ qos: 1,
+ properties: { topicAlias: 1 },
+ })
+ })
+ })
+
+ it('should error cb call if PUBLISH out of range topicAlias', function test(done) {
+ this.timeout(15000)
+
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND103,
+ protocolVersion: 5,
+ }
+ const client = mqtt.connect(opts)
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ sessionPresent: false,
+ properties: {
+ topicAliasMaximum: 3,
+ },
+ })
+ })
+ }).listen(ports.PORTAND103)
+
+ client.on('connect', () => {
+ // register topicAlias
+ client.publish(
+ 'test1',
+ 'Message',
+ { properties: { topicAlias: 4 } },
+ (error) => {
+ assert.strictEqual(
+ error.message,
+ 'Sending Topic Alias out of range',
+ )
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ },
+ )
+ })
+ })
+
+ it('should error cb call if PUBLISH out of range topicAlias on topicAlias disabled by broker', function test(done) {
+ this.timeout(15000)
+
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND103,
+ protocolVersion: 5,
+ }
+ const client = mqtt.connect(opts)
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ sessionPresent: false,
+ })
+ })
+ }).listen(ports.PORTAND103)
+
+ client.on('connect', () => {
+ // register topicAlias
+ client.publish(
+ 'test1',
+ 'Message',
+ { properties: { topicAlias: 1 } },
+ (error) => {
+ assert.strictEqual(
+ error.message,
+ 'Sending Topic Alias out of range',
+ )
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ },
+ )
+ })
+ })
+
+ it('should throw an error if broker PUBLISH out of range topicAlias', function test(done) {
+ this.timeout(15000)
+
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND103,
+ protocolVersion: 5,
+ properties: {
+ topicAliasMaximum: 3,
+ },
+ }
+ const client = mqtt.connect(opts)
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ sessionPresent: false,
+ })
+ // register out of range topicAlias
+ serverClient.publish({
+ messageId: 0,
+ topic: 'test1',
+ payload: 'Message',
+ qos: 0,
+ properties: { topicAlias: 4 },
+ })
+ })
+ }).listen(ports.PORTAND103)
+
+ client.on('error', (error) => {
+ assert.strictEqual(
+ error.message,
+ 'Received Topic Alias is out of range',
+ )
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ })
+
+ it('should throw an error if broker PUBLISH topicAlias:0', function test(done) {
+ this.timeout(15000)
+
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND103,
+ protocolVersion: 5,
+ properties: {
+ topicAliasMaximum: 3,
+ },
+ }
+ const client = mqtt.connect(opts)
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ sessionPresent: false,
+ })
+ // register out of range topicAlias
+ serverClient.publish({
+ messageId: 0,
+ topic: 'test1',
+ payload: 'Message',
+ qos: 0,
+ properties: { topicAlias: 0 },
+ })
+ })
+ }).listen(ports.PORTAND103)
+
+ client.on('error', (error) => {
+ assert.strictEqual(
+ error.message,
+ 'Received Topic Alias is out of range',
+ )
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ })
+
+ it('should throw an error if broker PUBLISH unregistered topicAlias', function test(done) {
+ this.timeout(15000)
+
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND103,
+ protocolVersion: 5,
+ properties: {
+ topicAliasMaximum: 3,
+ },
+ }
+ const client = mqtt.connect(opts)
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ sessionPresent: false,
+ })
+ // register out of range topicAlias
+ serverClient.publish({
+ messageId: 0,
+ topic: '', // use topic alias
+ payload: 'Message',
+ qos: 0,
+ properties: { topicAlias: 1 }, // in range topic alias
+ })
+ })
+ }).listen(ports.PORTAND103)
+
+ client.on('error', (error) => {
+ assert.strictEqual(
+ error.message,
+ 'Received unregistered Topic Alias',
+ )
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ })
+
+ it('should throw an error if there is Auth Data with no Auth Method', function test(done) {
+ this.timeout(5000)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND115,
+ protocolVersion: 5,
+ properties: { authenticationData: Buffer.from([1, 2, 3, 4]) },
+ }
+ console.log('client connecting')
+ const client = mqtt.connect(opts)
+ client.on('error', (error) => {
+ console.log('error hit')
+ assert.strictEqual(
+ error.message,
+ 'Packet has no Authentication Method',
+ )
+ // client will not be connected, so we will call done.
+ assert.isTrue(
+ client.disconnected,
+ 'validate client is disconnected',
+ )
+ client.end(true, done)
+ })
+ })
+
+ it('auth packet', function test(done) {
+ this.timeout(2500)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND115,
+ protocolVersion: 5,
+ properties: { authenticationMethod: 'json' },
+ authPacket: {},
+ manualConnect: true,
+ }
+ let authSent = false
+
+ const client = mqtt.connect(opts)
+ server.once('client', (c) => {
+ // this test is flaky, there is a race condition
+ // that could make the test fail as the auth packet
+ // is sent by the client even before connack so it could arrive before
+ // the clientServer is listening for the auth packet. To avoid this
+ // if the event is not emitted we simply check if
+ // the auth packet is sent after 1 second.
+ let closeTimeout = setTimeout(() => {
+ assert.isTrue(authSent)
+ closeTimeout = null
+ client.end(true, done)
+ }, 1000)
+
+ c.on('auth', (packet) => {
+ if (closeTimeout) {
+ clearTimeout(closeTimeout)
+ client.end(done)
+ }
+ })
+ })
+ client.on('packetsend', (packet) => {
+ if (packet.cmd === 'auth') {
+ authSent = true
+ }
+ })
+
+ client.connect()
+ })
+
+ it('Maximum Packet Size', function test(done) {
+ this.timeout(15000)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND115,
+ protocolVersion: 5,
+ properties: { maximumPacketSize: 1 },
+ }
+ const client = mqtt.connect(opts)
+ client.on('error', (error) => {
+ assert.strictEqual(error.message, 'exceeding packets size connack')
+ client.end(true, done)
+ })
+ })
+
+ it('Change values of some properties by server response', function test(done) {
+ this.timeout(15000)
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ properties: {
+ serverKeepAlive: 16,
+ maximumPacketSize: 95,
+ },
+ })
+ })
+ }).listen(ports.PORTAND116)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND116,
+ protocolVersion: 5,
+ properties: {
+ topicAliasMaximum: 10,
+ serverKeepAlive: 11,
+ maximumPacketSize: 100,
+ },
+ }
+ const client = mqtt.connect(opts)
+ client.on('connect', () => {
+ assert.strictEqual(client.options.keepalive, 16)
+ assert.strictEqual(client.options.properties.maximumPacketSize, 95)
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ })
+
+ it('should resubscribe when reconnecting with protocolVersion 5 and Session Present flag is false', function test(done) {
+ this.timeout(15000)
+ let tryReconnect = true
+ let reconnectEvent = false
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ sessionPresent: false,
+ })
+ serverClient.on('subscribe', () => {
+ if (!tryReconnect) {
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ }
+ })
+ })
+ }).listen(ports.PORTAND316)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND316,
+ protocolVersion: 5,
+ }
+ const client = mqtt.connect(opts)
+
+ client.on('reconnect', () => {
+ reconnectEvent = true
+ })
+
+ client.on('connect', (connack) => {
+ assert.isFalse(connack.sessionPresent)
+ if (tryReconnect) {
+ client.subscribe('hello', () => {
+ client.stream.end()
+ })
+
+ tryReconnect = false
+ } else {
+ assert.isTrue(reconnectEvent)
+ }
+ })
+ })
+
+ it('should resubscribe when reconnecting with protocolVersion 5 and properties', function test(done) {
+ // this.timeout(15000)
+ let tryReconnect = true
+ let reconnectEvent = false
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ sessionPresent: false,
+ })
+ })
+ serverClient.on('subscribe', (packet) => {
+ if (!reconnectEvent) {
+ serverClient.suback({
+ messageId: packet.messageId,
+ granted: packet.subscriptions.map((e) => e.qos),
+ })
+ } else if (!tryReconnect) {
+ assert.strictEqual(
+ packet.properties.userProperties.test,
+ 'test',
+ )
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ }
+ })
+ }).listen(ports.PORTAND326)
+
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND326,
+ protocolVersion: 5,
+ }
+ const client = mqtt.connect(opts)
+
+ client.on('reconnect', () => {
+ reconnectEvent = true
+ })
+
+ client.on('connect', (connack) => {
+ assert.isFalse(connack.sessionPresent)
+ if (tryReconnect) {
+ client.subscribe(
+ 'hello',
+ { properties: { userProperties: { test: 'test' } } },
+ () => {
+ client.stream.end()
+ },
+ )
+
+ tryReconnect = false
+ } else {
+ assert.isTrue(reconnectEvent)
+ }
+ })
+ })
+
+ const serverThatSendsErrors = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ })
+ })
+ serverClient.on('publish', (packet) => {
+ setImmediate(() => {
+ switch (packet.qos) {
+ case 0:
+ break
+ case 1:
+ packet.reasonCode = 142
+ delete packet.cmd
+ serverClient.puback(packet)
+ break
+ case 2:
+ packet.reasonCode = 142
+ delete packet.cmd
+ serverClient.pubrec(packet)
+ break
+ }
+ })
+ })
+
+ serverClient.on('pubrel', (packet) => {
+ packet.reasonCode = 142
+ delete packet.cmd
+ serverClient.pubcomp(packet)
+ })
+ })
+
+ it('Subscribe properties', function test(done) {
+ this.timeout(15000)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND119,
+ protocolVersion: 5,
+ }
+ const subOptions = { properties: { subscriptionIdentifier: 1234 } }
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ })
+ })
+ serverClient.on('subscribe', (packet) => {
+ assert.strictEqual(
+ packet.properties.subscriptionIdentifier,
+ subOptions.properties.subscriptionIdentifier,
+ )
+ client.end(true, (err1) => {
+ server2.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ }).listen(ports.PORTAND119)
+
+ const client = mqtt.connect(opts)
+ client.on('connect', () => {
+ client.subscribe('a/b', subOptions)
+ })
+ })
+
+ it('puback handling errors check', function test(done) {
+ this.timeout(15000)
+ serverThatSendsErrors.listen(ports.PORTAND117)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND117,
+ protocolVersion: 5,
+ }
+ const client = mqtt.connect(opts)
+ client.once('connect', () => {
+ client.publish('a/b', 'message', { qos: 1 }, (err, packet) => {
+ assert.strictEqual(
+ err.message,
+ 'Publish error: Session taken over',
+ )
+ assert.strictEqual(err.code, 142)
+ })
+ client.end(true, (err1) => {
+ serverThatSendsErrors.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ })
+
+ it('pubrec handling errors check', function test(done) {
+ this.timeout(15000)
+ serverThatSendsErrors.listen(ports.PORTAND118)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND118,
+ protocolVersion: 5,
+ }
+ const client = mqtt.connect(opts)
+ client.once('connect', () => {
+ client.publish('a/b', 'message', { qos: 2 }, (err, packet) => {
+ assert.strictEqual(
+ err.message,
+ 'Publish error: Session taken over',
+ )
+ assert.strictEqual(err.code, 142)
+ })
+ client.end(true, (err1) => {
+ serverThatSendsErrors.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ })
+
+ it('puback handling custom reason code', function test(done) {
+ // this.timeout(15000)
+ serverThatSendsErrors.listen(ports.PORTAND117)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND117,
+ protocolVersion: 5,
+ customHandleAcks(topic, message, packet, cb) {
+ let code = 0
+ if (topic === 'a/b') {
+ code = 128
+ }
+ cb(code)
+ },
+ }
+
+ serverThatSendsErrors.once('client', (serverClient) => {
+ serverClient.once('subscribe', () => {
+ serverClient.publish({
+ topic: 'a/b',
+ payload: 'payload',
+ qos: 1,
+ messageId: 1,
+ })
+ })
+
+ serverClient.on('puback', (packet) => {
+ assert.strictEqual(packet.reasonCode, 128)
+ client.end(true, (err1) => {
+ serverThatSendsErrors.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ })
+
+ const client = mqtt.connect(opts)
+ client.once('connect', () => {
+ client.subscribe('a/b', { qos: 1 })
+ })
+ })
+
+ it('server side disconnect', function test(done) {
+ this.timeout(15000)
+ const server2 = new MqttServer((serverClient) => {
+ serverClient.on('connect', (packet) => {
+ serverClient.connack({
+ reasonCode: 0,
+ })
+ serverClient.disconnect({ reasonCode: 128 })
+ server2.close()
+ })
+ })
+ server2.listen(ports.PORTAND327)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND327,
+ protocolVersion: 5,
+ }
+
+ const client = mqtt.connect(opts)
+ client.once('disconnect', (disconnectPacket) => {
+ assert.strictEqual(disconnectPacket.reasonCode, 128)
+ client.end(true, (err) => done(err))
+ })
+ })
+
+ it('pubrec handling custom reason code', function test(done) {
+ this.timeout(15000)
+ serverThatSendsErrors.listen(ports.PORTAND117)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND117,
+ protocolVersion: 5,
+ customHandleAcks(topic, message, packet, cb) {
+ let code = 0
+ if (topic === 'a/b') {
+ code = 128
+ }
+ cb(code)
+ },
+ }
+ const client = mqtt.connect(opts)
+ client.once('connect', () => {
+ client.subscribe('a/b', { qos: 1 })
+ })
+
+ serverThatSendsErrors.once('client', (serverClient) => {
+ serverClient.once('subscribe', () => {
+ serverClient.publish({
+ topic: 'a/b',
+ payload: 'payload',
+ qos: 2,
+ messageId: 1,
+ })
+ })
+
+ serverClient.on('pubrec', (packet) => {
+ assert.strictEqual(packet.reasonCode, 128)
+ client.end(true, (err1) => {
+ serverThatSendsErrors.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ })
+ })
+
+ it('puback handling custom reason code with error', function test(done) {
+ this.timeout(15000)
+ serverThatSendsErrors.listen(ports.PORTAND117)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND117,
+ protocolVersion: 5,
+ customHandleAcks(topic, message, packet, cb) {
+ const code = 0
+ if (topic === 'a/b') {
+ cb(new Error('a/b is not valid'))
+ }
+ cb(code)
+ },
+ }
+
+ serverThatSendsErrors.once('client', (serverClient) => {
+ serverClient.once('subscribe', () => {
+ serverClient.publish({
+ topic: 'a/b',
+ payload: 'payload',
+ qos: 1,
+ messageId: 1,
+ })
+ })
+ })
+
+ const client = mqtt.connect(opts)
+ client.on('error', (error) => {
+ assert.strictEqual(error.message, 'a/b is not valid')
+ client.end(true, (err1) => {
+ serverThatSendsErrors.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ client.once('connect', () => {
+ client.subscribe('a/b', { qos: 1 })
+ })
+ })
+
+ it('pubrec handling custom reason code with error', function test(done) {
+ this.timeout(15000)
+ serverThatSendsErrors.listen(ports.PORTAND117)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND117,
+ protocolVersion: 5,
+ customHandleAcks(topic, message, packet, cb) {
+ const code = 0
+ if (topic === 'a/b') {
+ cb(new Error('a/b is not valid'))
+ }
+ cb(code)
+ },
+ }
+
+ serverThatSendsErrors.once('client', (serverClient) => {
+ serverClient.once('subscribe', () => {
+ serverClient.publish({
+ topic: 'a/b',
+ payload: 'payload',
+ qos: 2,
+ messageId: 1,
+ })
+ })
+ })
+
+ const client = mqtt.connect(opts)
+ client.on('error', (error) => {
+ assert.strictEqual(error.message, 'a/b is not valid')
+ client.end(true, (err1) => {
+ serverThatSendsErrors.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ client.once('connect', () => {
+ client.subscribe('a/b', { qos: 1 })
+ })
+ })
+
+ it('puback handling custom invalid reason code', function test(done) {
+ this.timeout(15000)
+ serverThatSendsErrors.listen(ports.PORTAND117)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND117,
+ protocolVersion: 5,
+ customHandleAcks(topic, message, packet, cb) {
+ let code = 0
+ if (topic === 'a/b') {
+ code = 124124
+ }
+ cb(code)
+ },
+ }
+
+ serverThatSendsErrors.once('client', (serverClient) => {
+ serverClient.once('subscribe', () => {
+ serverClient.publish({
+ topic: 'a/b',
+ payload: 'payload',
+ qos: 1,
+ messageId: 1,
+ })
+ })
+ })
+
+ const client = mqtt.connect(opts)
+ client.on('error', (error) => {
+ assert.strictEqual(error.message, 'Wrong reason code for puback')
+ client.end(true, (err1) => {
+ serverThatSendsErrors.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ client.once('connect', () => {
+ client.subscribe('a/b', { qos: 1 })
+ })
+ })
+
+ it('pubrec handling custom invalid reason code', function test(done) {
+ this.timeout(15000)
+ serverThatSendsErrors.listen(ports.PORTAND117)
+ const opts = {
+ host: 'localhost',
+ port: ports.PORTAND117,
+ protocolVersion: 5,
+ customHandleAcks(topic, message, packet, cb) {
+ let code = 0
+ if (topic === 'a/b') {
+ code = 34535
+ }
+ cb(code)
+ },
+ }
+
+ serverThatSendsErrors.once('client', (serverClient) => {
+ serverClient.once('subscribe', () => {
+ serverClient.publish({
+ topic: 'a/b',
+ payload: 'payload',
+ qos: 2,
+ messageId: 1,
+ })
+ })
+ })
+
+ const client = mqtt.connect(opts)
+ client.on('error', (error) => {
+ assert.strictEqual(error.message, 'Wrong reason code for pubrec')
+ client.end(true, (err1) => {
+ serverThatSendsErrors.close((err2) => {
+ done(err1 || err2)
+ })
+ })
+ })
+ client.once('connect', () => {
+ client.subscribe('a/b', { qos: 1 })
+ })
+ })
})
diff --git a/test/helpers/port_list.js b/test/helpers/port_list.js
index 27138954b..a8a0616dd 100644
--- a/test/helpers/port_list.js
+++ b/test/helpers/port_list.js
@@ -24,28 +24,28 @@ const PORTAND327 = PORT + 327
const PORTAND400 = PORT + 400
module.exports = {
- PORT,
- PORTAND40,
- PORTAND41,
- PORTAND42,
- PORTAND43,
- PORTAND44,
- PORTAND45,
- PORTAND46,
- PORTAND47,
- PORTAND48,
- PORTAND49,
- PORTAND50,
- PORTAND72,
- PORTAND103,
- PORTAND114,
- PORTAND115,
- PORTAND116,
- PORTAND117,
- PORTAND118,
- PORTAND119,
- PORTAND316,
- PORTAND326,
- PORTAND327,
- PORTAND400
+ PORT,
+ PORTAND40,
+ PORTAND41,
+ PORTAND42,
+ PORTAND43,
+ PORTAND44,
+ PORTAND45,
+ PORTAND46,
+ PORTAND47,
+ PORTAND48,
+ PORTAND49,
+ PORTAND50,
+ PORTAND72,
+ PORTAND103,
+ PORTAND114,
+ PORTAND115,
+ PORTAND116,
+ PORTAND117,
+ PORTAND118,
+ PORTAND119,
+ PORTAND316,
+ PORTAND326,
+ PORTAND327,
+ PORTAND400,
}
diff --git a/test/helpers/server.js b/test/helpers/server.js
index aae053517..112c9a8a4 100644
--- a/test/helpers/server.js
+++ b/test/helpers/server.js
@@ -1,53 +1,54 @@
-'use strict'
-
-const MqttServer = require('../server').MqttServer
-const MqttSecureServer = require('../server').MqttSecureServer
const fs = require('fs')
+const { MqttServer } = require('../server')
+const { MqttSecureServer } = require('../server')
-module.exports.init_server = function (PORT) {
- const server = new MqttServer(function (client) {
- client.on('connect', function () {
- client.connack(0)
- })
+module.exports.init_server = (PORT) => {
+ const server = new MqttServer((client) => {
+ client.on('connect', () => {
+ client.connack(0)
+ })
- client.on('publish', function (packet) {
- switch (packet.qos) {
- case 1:
- client.puback({ messageId: packet.messageId })
- break
- case 2:
- client.pubrec({ messageId: packet.messageId })
- break
- default:
- break
- }
- })
+ client.on('publish', (packet) => {
+ switch (packet.qos) {
+ case 1:
+ client.puback({ messageId: packet.messageId })
+ break
+ case 2:
+ client.pubrec({ messageId: packet.messageId })
+ break
+ default:
+ break
+ }
+ })
- client.on('pubrel', function (packet) {
- client.pubcomp({ messageId: packet.messageId })
- })
+ client.on('pubrel', (packet) => {
+ client.pubcomp({ messageId: packet.messageId })
+ })
- client.on('pingreq', function () {
- client.pingresp()
- })
+ client.on('pingreq', () => {
+ client.pingresp()
+ })
- client.on('disconnect', function () {
- client.stream.end()
- })
- })
- server.listen(PORT)
- return server
+ client.on('disconnect', () => {
+ client.stream.end()
+ })
+ })
+ server.listen(PORT)
+ return server
}
-module.exports.init_secure_server = function (port, key, cert) {
- const server = new MqttSecureServer({
- key: fs.readFileSync(key),
- cert: fs.readFileSync(cert)
- }, function (client) {
- client.on('connect', function () {
- client.connack({ returnCode: 0 })
- })
- })
- server.listen(port)
- return server
+module.exports.init_secure_server = (port, key, cert) => {
+ const server = new MqttSecureServer(
+ {
+ key: fs.readFileSync(key),
+ cert: fs.readFileSync(cert),
+ },
+ (client) => {
+ client.on('connect', () => {
+ client.connack({ returnCode: 0 })
+ })
+ },
+ )
+ server.listen(port)
+ return server
}
diff --git a/test/helpers/server_process.js b/test/helpers/server_process.js
index 0875cd9da..60df9a987 100644
--- a/test/helpers/server_process.js
+++ b/test/helpers/server_process.js
@@ -1,9 +1,7 @@
-'use strict'
+const { MqttServer } = require('../server')
-const MqttServer = require('../server').MqttServer
-
-new MqttServer(function (client) {
- client.on('connect', function () {
- client.connack({ returnCode: 0 })
- })
+new MqttServer((client) => {
+ client.on('connect', () => {
+ client.connack({ returnCode: 0 })
+ })
}).listen(3481, 'localhost')
diff --git a/test/message-id-provider.js b/test/message-id-provider.js
index 3d359def3..8ac0163ca 100644
--- a/test/message-id-provider.js
+++ b/test/message-id-provider.js
@@ -1,90 +1,89 @@
-'use strict'
-const assert = require('chai').assert
+const { assert } = require('chai')
const DefaultMessageIdProvider = require('../lib/default-message-id-provider')
const UniqueMessageIdProvider = require('../lib/unique-message-id-provider')
-describe('message id provider', function () {
- describe('default', function () {
- it('should return 1 once the internal counter reached limit', function () {
- const provider = new DefaultMessageIdProvider()
- provider.nextId = 65535
+describe('message id provider', () => {
+ describe('default', () => {
+ it('should return 1 once the internal counter reached limit', () => {
+ const provider = new DefaultMessageIdProvider()
+ provider.nextId = 65535
- assert.equal(provider.allocate(), 65535)
- assert.equal(provider.allocate(), 1)
- })
+ assert.equal(provider.allocate(), 65535)
+ assert.equal(provider.allocate(), 1)
+ })
- it('should return 65535 for last message id once the internal counter reached limit', function () {
- const provider = new DefaultMessageIdProvider()
- provider.nextId = 65535
+ it('should return 65535 for last message id once the internal counter reached limit', () => {
+ const provider = new DefaultMessageIdProvider()
+ provider.nextId = 65535
- assert.equal(provider.allocate(), 65535)
- assert.equal(provider.getLastAllocated(), 65535)
- assert.equal(provider.allocate(), 1)
- assert.equal(provider.getLastAllocated(), 1)
- })
- it('should return true when register with non allocated messageId', function () {
- const provider = new DefaultMessageIdProvider()
- assert.equal(provider.register(10), true)
- })
- })
- describe('unique', function () {
- it('should return 1, 2, 3.., when allocate', function () {
- const provider = new UniqueMessageIdProvider()
- assert.equal(provider.allocate(), 1)
- assert.equal(provider.allocate(), 2)
- assert.equal(provider.allocate(), 3)
- })
- it('should skip registerd messageId', function () {
- const provider = new UniqueMessageIdProvider()
- assert.equal(provider.register(2), true)
- assert.equal(provider.allocate(), 1)
- assert.equal(provider.allocate(), 3)
- })
- it('should return false register allocated messageId', function () {
- const provider = new UniqueMessageIdProvider()
- assert.equal(provider.allocate(), 1)
- assert.equal(provider.register(1), false)
- assert.equal(provider.register(5), true)
- assert.equal(provider.register(5), false)
- })
- it('should retrun correct last messageId', function () {
- const provider = new UniqueMessageIdProvider()
- assert.equal(provider.allocate(), 1)
- assert.equal(provider.getLastAllocated(), 1)
- assert.equal(provider.register(2), true)
- assert.equal(provider.getLastAllocated(), 1)
- assert.equal(provider.allocate(), 3)
- assert.equal(provider.getLastAllocated(), 3)
- })
- it('should be reusable deallocated messageId', function () {
- const provider = new UniqueMessageIdProvider()
- assert.equal(provider.allocate(), 1)
- assert.equal(provider.allocate(), 2)
- assert.equal(provider.allocate(), 3)
- provider.deallocate(2)
- assert.equal(provider.allocate(), 2)
- })
- it('should allocate all messageId and then return null', function () {
- const provider = new UniqueMessageIdProvider()
- for (let i = 1; i <= 65535; i++) {
- assert.equal(provider.allocate(), i)
- }
- assert.equal(provider.allocate(), null)
- provider.deallocate(10000)
- assert.equal(provider.allocate(), 10000)
- assert.equal(provider.allocate(), null)
- })
- it('should all messageId reallocatable after clear', function () {
- const provider = new UniqueMessageIdProvider()
- for (let i = 1; i <= 65535; i++) {
- assert.equal(provider.allocate(), i)
- }
- assert.equal(provider.allocate(), null)
- provider.clear()
- for (let i = 1; i <= 65535; i++) {
- assert.equal(provider.allocate(), i)
- }
- assert.equal(provider.allocate(), null)
- })
- })
+ assert.equal(provider.allocate(), 65535)
+ assert.equal(provider.getLastAllocated(), 65535)
+ assert.equal(provider.allocate(), 1)
+ assert.equal(provider.getLastAllocated(), 1)
+ })
+ it('should return true when register with non allocated messageId', () => {
+ const provider = new DefaultMessageIdProvider()
+ assert.equal(provider.register(10), true)
+ })
+ })
+ describe('unique', () => {
+ it('should return 1, 2, 3.., when allocate', () => {
+ const provider = new UniqueMessageIdProvider()
+ assert.equal(provider.allocate(), 1)
+ assert.equal(provider.allocate(), 2)
+ assert.equal(provider.allocate(), 3)
+ })
+ it('should skip registerd messageId', () => {
+ const provider = new UniqueMessageIdProvider()
+ assert.equal(provider.register(2), true)
+ assert.equal(provider.allocate(), 1)
+ assert.equal(provider.allocate(), 3)
+ })
+ it('should return false register allocated messageId', () => {
+ const provider = new UniqueMessageIdProvider()
+ assert.equal(provider.allocate(), 1)
+ assert.equal(provider.register(1), false)
+ assert.equal(provider.register(5), true)
+ assert.equal(provider.register(5), false)
+ })
+ it('should retrun correct last messageId', () => {
+ const provider = new UniqueMessageIdProvider()
+ assert.equal(provider.allocate(), 1)
+ assert.equal(provider.getLastAllocated(), 1)
+ assert.equal(provider.register(2), true)
+ assert.equal(provider.getLastAllocated(), 1)
+ assert.equal(provider.allocate(), 3)
+ assert.equal(provider.getLastAllocated(), 3)
+ })
+ it('should be reusable deallocated messageId', () => {
+ const provider = new UniqueMessageIdProvider()
+ assert.equal(provider.allocate(), 1)
+ assert.equal(provider.allocate(), 2)
+ assert.equal(provider.allocate(), 3)
+ provider.deallocate(2)
+ assert.equal(provider.allocate(), 2)
+ })
+ it('should allocate all messageId and then return null', () => {
+ const provider = new UniqueMessageIdProvider()
+ for (let i = 1; i <= 65535; i++) {
+ assert.equal(provider.allocate(), i)
+ }
+ assert.equal(provider.allocate(), null)
+ provider.deallocate(10000)
+ assert.equal(provider.allocate(), 10000)
+ assert.equal(provider.allocate(), null)
+ })
+ it('should all messageId reallocatable after clear', () => {
+ const provider = new UniqueMessageIdProvider()
+ for (let i = 1; i <= 65535; i++) {
+ assert.equal(provider.allocate(), i)
+ }
+ assert.equal(provider.allocate(), null)
+ provider.clear()
+ for (let i = 1; i <= 65535; i++) {
+ assert.equal(provider.allocate(), i)
+ }
+ assert.equal(provider.allocate(), null)
+ })
+ })
})
diff --git a/test/mqtt.js b/test/mqtt.js
index 24e84c859..aa3bc4465 100644
--- a/test/mqtt.js
+++ b/test/mqtt.js
@@ -1,230 +1,236 @@
-'use strict'
-
const fs = require('fs')
const path = require('path')
-const mqtt = require('../')
-
-describe('mqtt', function () {
- describe('#connect', function () {
- it('should return an MqttClient when connect is called with mqtt:/ url', function (done) {
- const c = mqtt.connect('mqtt://localhost:1883')
-
- c.should.be.instanceOf(mqtt.MqttClient)
- c.end((err) => done(err))
- })
+const mqtt = require('..')
- it('should throw an error when called with no protocol specified', function () {
- (function () {
- mqtt.connect('foo.bar.com')
- }).should.throw('Missing protocol')
- })
+describe('mqtt', () => {
+ describe('#connect', () => {
+ it('should return an MqttClient when connect is called with mqtt:/ url', function test(done) {
+ const c = mqtt.connect('mqtt://localhost:1883')
- it('should throw an error when called with no protocol specified - with options', function () {
- (function () {
- mqtt.connect('tcp://foo.bar.com', { protocol: null })
- }).should.throw('Missing protocol')
- })
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.end((err) => done(err))
+ })
- it('should return an MqttClient with username option set', function (done) {
- const c = mqtt.connect('mqtt://user:pass@localhost:1883')
+ it('should throw an error when called with no protocol specified', () => {
+ ;(() => {
+ mqtt.connect('foo.bar.com')
+ }).should.throw('Missing protocol')
+ })
- c.should.be.instanceOf(mqtt.MqttClient)
- c.options.should.have.property('username', 'user')
- c.options.should.have.property('password', 'pass')
- c.end((err) => done(err))
- })
+ it('should throw an error when called with no protocol specified - with options', () => {
+ ;(() => {
+ mqtt.connect('tcp://foo.bar.com', { protocol: null })
+ }).should.throw('Missing protocol')
+ })
- it('should return an MqttClient with username and password options set', function (done) {
- const c = mqtt.connect('mqtt://user@localhost:1883')
+ it('should return an MqttClient with username option set', function test(done) {
+ const c = mqtt.connect('mqtt://user:pass@localhost:1883')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.options.should.have.property('username', 'user')
- c.end((err) => done(err))
- })
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.options.should.have.property('username', 'user')
+ c.options.should.have.property('password', 'pass')
+ c.end((err) => done(err))
+ })
- it('should return an MqttClient with the clientid with random value', function (done) {
- const c = mqtt.connect('mqtt://user@localhost:1883')
+ it('should return an MqttClient with username and password options set', function test(done) {
+ const c = mqtt.connect('mqtt://user@localhost:1883')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.options.should.have.property('clientId')
- c.end((err) => done(err))
- })
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.options.should.have.property('username', 'user')
+ c.end((err) => done(err))
+ })
- it('should return an MqttClient with the clientid with empty string', function (done) {
- const c = mqtt.connect('mqtt://user@localhost:1883?clientId=')
+ it('should return an MqttClient with the clientid with random value', function test(done) {
+ const c = mqtt.connect('mqtt://user@localhost:1883')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.options.should.have.property('clientId', '')
- c.end((err) => done(err))
- })
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.options.should.have.property('clientId')
+ c.end((err) => done(err))
+ })
- it('should return an MqttClient with the clientid option set', function (done) {
- const c = mqtt.connect('mqtt://user@localhost:1883?clientId=123')
+ it('should return an MqttClient with the clientid with empty string', function test(done) {
+ const c = mqtt.connect('mqtt://user@localhost:1883?clientId=')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.options.should.have.property('clientId', '123')
- c.end((err) => done(err))
- })
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.options.should.have.property('clientId', '')
+ c.end((err) => done(err))
+ })
+
+ it('should return an MqttClient with the clientid option set', function test(done) {
+ const c = mqtt.connect('mqtt://user@localhost:1883?clientId=123')
- it('should return an MqttClient when connect is called with tcp:/ url', function (done) {
- const c = mqtt.connect('tcp://localhost')
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.options.should.have.property('clientId', '123')
+ c.end((err) => done(err))
+ })
- c.should.be.instanceOf(mqtt.MqttClient)
- c.end((err) => done(err))
- })
+ it('should return an MqttClient when connect is called with tcp:/ url', function test(done) {
+ const c = mqtt.connect('tcp://localhost')
- it('should return an MqttClient with correct host when called with a host and port', function (done) {
- const c = mqtt.connect('tcp://user:pass@localhost:1883')
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.end((err) => done(err))
+ })
- c.options.should.have.property('hostname', 'localhost')
- c.options.should.have.property('port', 1883)
- c.end((err) => done(err))
- })
+ it('should return an MqttClient with correct host when called with a host and port', function test(done) {
+ const c = mqtt.connect('tcp://user:pass@localhost:1883')
- const sslOpts = {
- keyPath: path.join(__dirname, 'helpers', 'private-key.pem'),
- certPath: path.join(__dirname, 'helpers', 'public-cert.pem'),
- caPaths: [path.join(__dirname, 'helpers', 'public-cert.pem')]
- }
+ c.options.should.have.property('hostname', 'localhost')
+ c.options.should.have.property('port', 1883)
+ c.end((err) => done(err))
+ })
- it('should return an MqttClient when connect is called with mqtts:/ url', function (done) {
- const c = mqtt.connect('mqtts://localhost', sslOpts)
+ const sslOpts = {
+ keyPath: path.join(__dirname, 'helpers', 'private-key.pem'),
+ certPath: path.join(__dirname, 'helpers', 'public-cert.pem'),
+ caPaths: [path.join(__dirname, 'helpers', 'public-cert.pem')],
+ }
- c.options.should.have.property('protocol', 'mqtts')
+ it('should return an MqttClient when connect is called with mqtts:/ url', function test(done) {
+ const c = mqtt.connect('mqtts://localhost', sslOpts)
- c.on('error', function () {})
+ c.options.should.have.property('protocol', 'mqtts')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.end((err) => done(err))
- })
+ c.on('error', () => {})
- it('should return an MqttClient when connect is called with ssl:/ url', function (done) {
- const c = mqtt.connect('ssl://localhost', sslOpts)
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.end((err) => done(err))
+ })
- c.options.should.have.property('protocol', 'ssl')
+ it('should return an MqttClient when connect is called with ssl:/ url', function test(done) {
+ const c = mqtt.connect('ssl://localhost', sslOpts)
- c.on('error', function () {})
+ c.options.should.have.property('protocol', 'ssl')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.end((err) => done(err))
- })
+ c.on('error', () => {})
- it('should return an MqttClient when connect is called with ws:/ url', function (done) {
- const c = mqtt.connect('ws://localhost', sslOpts)
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.end((err) => done(err))
+ })
- c.options.should.have.property('protocol', 'ws')
+ it('should return an MqttClient when connect is called with ws:/ url', function test(done) {
+ const c = mqtt.connect('ws://localhost', sslOpts)
- c.on('error', function () {})
+ c.options.should.have.property('protocol', 'ws')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.end((err) => done(err))
- })
+ c.on('error', () => {})
- it('should return an MqttClient when connect is called with wss:/ url', function (done) {
- const c = mqtt.connect('wss://localhost', sslOpts)
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.end((err) => done(err))
+ })
- c.options.should.have.property('protocol', 'wss')
+ it('should return an MqttClient when connect is called with wss:/ url', function test(done) {
+ const c = mqtt.connect('wss://localhost', sslOpts)
- c.on('error', function () {})
+ c.options.should.have.property('protocol', 'wss')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.end((err) => done(err))
- })
+ c.on('error', () => {})
- const sslOpts2 = {
- key: fs.readFileSync(path.join(__dirname, 'helpers', 'private-key.pem')),
- cert: fs.readFileSync(path.join(__dirname, 'helpers', 'public-cert.pem')),
- ca: [fs.readFileSync(path.join(__dirname, 'helpers', 'public-cert.pem'))]
- }
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.end((err) => done(err))
+ })
- it('should throw an error when it is called with cert and key set but no protocol specified', function () {
- // to do rewrite wrap function
- (function () {
- mqtt.connect(sslOpts2)
- }).should.throw('Missing secure protocol key')
- })
+ const sslOpts2 = {
+ key: fs.readFileSync(
+ path.join(__dirname, 'helpers', 'private-key.pem'),
+ ),
+ cert: fs.readFileSync(
+ path.join(__dirname, 'helpers', 'public-cert.pem'),
+ ),
+ ca: [
+ fs.readFileSync(
+ path.join(__dirname, 'helpers', 'public-cert.pem'),
+ ),
+ ],
+ }
- it('should throw an error when it is called with cert and key set and protocol other than allowed: mqtt,mqtts,ws,wss,wxs', function () {
- (function () {
- sslOpts2.protocol = 'UNKNOWNPROTOCOL'
- mqtt.connect(sslOpts2)
- }).should.throw()
- })
+ it('should throw an error when it is called with cert and key set but no protocol specified', () => {
+ // to do rewrite wrap function
+ ;(() => {
+ mqtt.connect(sslOpts2)
+ }).should.throw('Missing secure protocol key')
+ })
- it('should return a MqttClient with mqtts set when connect is called key and cert set and protocol mqtt', function (done) {
- sslOpts2.protocol = 'mqtt'
- const c = mqtt.connect(sslOpts2)
+ it('should throw an error when it is called with cert and key set and protocol other than allowed: mqtt,mqtts,ws,wss,wxs', () => {
+ ;(() => {
+ sslOpts2.protocol = 'UNKNOWNPROTOCOL'
+ mqtt.connect(sslOpts2)
+ }).should.throw()
+ })
- c.options.should.have.property('protocol', 'mqtts')
+ it('should return a MqttClient with mqtts set when connect is called key and cert set and protocol mqtt', function test(done) {
+ sslOpts2.protocol = 'mqtt'
+ const c = mqtt.connect(sslOpts2)
- c.on('error', function () {})
+ c.options.should.have.property('protocol', 'mqtts')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.end((err) => done(err))
- })
+ c.on('error', () => {})
- it('should return a MqttClient with mqtts set when connect is called key and cert set and protocol mqtts', function (done) {
- sslOpts2.protocol = 'mqtts'
- const c = mqtt.connect(sslOpts2)
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.end((err) => done(err))
+ })
- c.options.should.have.property('protocol', 'mqtts')
+ it('should return a MqttClient with mqtts set when connect is called key and cert set and protocol mqtts', function test(done) {
+ sslOpts2.protocol = 'mqtts'
+ const c = mqtt.connect(sslOpts2)
- c.on('error', function () {})
+ c.options.should.have.property('protocol', 'mqtts')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.end((err) => done(err))
- })
+ c.on('error', () => {})
- it('should return a MqttClient with wss set when connect is called key and cert set and protocol ws', function (done) {
- sslOpts2.protocol = 'ws'
- const c = mqtt.connect(sslOpts2)
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.end((err) => done(err))
+ })
- c.options.should.have.property('protocol', 'wss')
+ it('should return a MqttClient with wss set when connect is called key and cert set and protocol ws', function test(done) {
+ sslOpts2.protocol = 'ws'
+ const c = mqtt.connect(sslOpts2)
- c.on('error', function () {})
+ c.options.should.have.property('protocol', 'wss')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.end((err) => done(err))
- })
+ c.on('error', () => {})
- it('should return a MqttClient with wss set when connect is called key and cert set and protocol wss', function (done) {
- sslOpts2.protocol = 'wss'
- const c = mqtt.connect(sslOpts2)
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.end((err) => done(err))
+ })
- c.options.should.have.property('protocol', 'wss')
+ it('should return a MqttClient with wss set when connect is called key and cert set and protocol wss', function test(done) {
+ sslOpts2.protocol = 'wss'
+ const c = mqtt.connect(sslOpts2)
- c.on('error', function () {})
+ c.options.should.have.property('protocol', 'wss')
- c.should.be.instanceOf(mqtt.MqttClient)
- c.end((err) => done(err))
- })
+ c.on('error', () => {})
- it('should return an MqttClient with the clientid with option of clientId as empty string', function (done) {
- const c = mqtt.connect('mqtt://localhost:1883', {
- clientId: ''
- })
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.end((err) => done(err))
+ })
- c.should.be.instanceOf(mqtt.MqttClient)
- c.options.should.have.property('clientId', '')
- c.end((err) => done(err))
- })
+ it('should return an MqttClient with the clientid with option of clientId as empty string', function test(done) {
+ const c = mqtt.connect('mqtt://localhost:1883', {
+ clientId: '',
+ })
- it('should return an MqttClient with the clientid with option of clientId empty', function (done) {
- const c = mqtt.connect('mqtt://localhost:1883')
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.options.should.have.property('clientId', '')
+ c.end((err) => done(err))
+ })
- c.should.be.instanceOf(mqtt.MqttClient)
- c.options.should.have.property('clientId')
- c.end((err) => done(err))
- })
+ it('should return an MqttClient with the clientid with option of clientId empty', function test(done) {
+ const c = mqtt.connect('mqtt://localhost:1883')
- it('should return an MqttClient with the clientid with option of with specific clientId', function (done) {
- const c = mqtt.connect('mqtt://localhost:1883', {
- clientId: '123'
- })
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.options.should.have.property('clientId')
+ c.end((err) => done(err))
+ })
- c.should.be.instanceOf(mqtt.MqttClient)
- c.options.should.have.property('clientId', '123')
- c.end((err) => done(err))
- })
- })
+ it('should return an MqttClient with the clientid with option of with specific clientId', function test(done) {
+ const c = mqtt.connect('mqtt://localhost:1883', {
+ clientId: '123',
+ })
+
+ c.should.be.instanceOf(mqtt.MqttClient)
+ c.options.should.have.property('clientId', '123')
+ c.end((err) => done(err))
+ })
+ })
})
diff --git a/test/mqtt_store.js b/test/mqtt_store.js
index 9fa7e915d..d5c6dff40 100644
--- a/test/mqtt_store.js
+++ b/test/mqtt_store.js
@@ -1,11 +1,9 @@
-'use strict'
-
const mqtt = require('../lib/connect')
-describe('store in lib/connect/index.js (webpack entry point)', function () {
- it('should create store', function (done) {
- const store = new mqtt.Store()
- store.should.be.instanceOf(mqtt.Store)
- done()
- })
+describe('store in lib/connect/index.js (webpack entry point)', () => {
+ it('should create store', function test(done) {
+ const store = new mqtt.Store()
+ store.should.be.instanceOf(mqtt.Store)
+ done()
+ })
})
diff --git a/test/secure_client.js b/test/secure_client.js
index d63902258..630408269 100644
--- a/test/secure_client.js
+++ b/test/secure_client.js
@@ -1,185 +1,187 @@
-'use strict'
-
-const mqtt = require('..')
const path = require('path')
-const abstractClientTests = require('./abstract_client')
const fs = require('fs')
+const mqtt = require('..')
+const abstractClientTests = require('./abstract_client')
+
const port = 9899
const KEY = path.join(__dirname, 'helpers', 'tls-key.pem')
const CERT = path.join(__dirname, 'helpers', 'tls-cert.pem')
const WRONG_CERT = path.join(__dirname, 'helpers', 'wrong-cert.pem')
-const MqttSecureServer = require('./server').MqttSecureServer
-const assert = require('chai').assert
-
-const serverListener = function (client) {
- // this is the Server's MQTT Client
- client.on('connect', function (packet) {
- if (packet.clientId === 'invalid') {
- client.connack({ returnCode: 2 })
- } else {
- server.emit('connect', client)
- client.connack({ returnCode: 0 })
- }
- })
-
- client.on('publish', function (packet) {
- setImmediate(function () {
- /* jshint -W027 */
- /* eslint default-case:0 */
- switch (packet.qos) {
- case 0:
- break
- case 1:
- client.puback(packet)
- break
- case 2:
- client.pubrec(packet)
- break
- }
- /* jshint +W027 */
- })
- })
-
- client.on('pubrel', function (packet) {
- client.pubcomp(packet)
- })
-
- client.on('pubrec', function (packet) {
- client.pubrel(packet)
- })
-
- client.on('pubcomp', function () {
- // Nothing to be done
- })
-
- client.on('subscribe', function (packet) {
- client.suback({
- messageId: packet.messageId,
- granted: packet.subscriptions.map(function (e) {
- return e.qos
- })
- })
- })
-
- client.on('unsubscribe', function (packet) {
- client.unsuback(packet)
- })
-
- client.on('pingreq', function () {
- client.pingresp()
- })
+const { MqttSecureServer } = require('./server')
+const { assert } = require('chai')
+
+const serverListener = (client) => {
+ // this is the Server's MQTT Client
+ client.on('connect', (packet) => {
+ if (packet.clientId === 'invalid') {
+ client.connack({ returnCode: 2 })
+ } else {
+ server.emit('connect', client)
+ client.connack({ returnCode: 0 })
+ }
+ })
+
+ client.on('publish', (packet) => {
+ setImmediate(() => {
+ /* jshint -W027 */
+ /* eslint default-case:0 */
+ switch (packet.qos) {
+ case 0:
+ break
+ case 1:
+ client.puback(packet)
+ break
+ case 2:
+ client.pubrec(packet)
+ break
+ }
+ /* jshint +W027 */
+ })
+ })
+
+ client.on('pubrel', (packet) => {
+ client.pubcomp(packet)
+ })
+
+ client.on('pubrec', (packet) => {
+ client.pubrel(packet)
+ })
+
+ client.on('pubcomp', () => {
+ // Nothing to be done
+ })
+
+ client.on('subscribe', (packet) => {
+ client.suback({
+ messageId: packet.messageId,
+ granted: packet.subscriptions.map((e) => e.qos),
+ })
+ })
+
+ client.on('unsubscribe', (packet) => {
+ client.unsuback(packet)
+ })
+
+ client.on('pingreq', () => {
+ client.pingresp()
+ })
}
-const server = new MqttSecureServer({
- key: fs.readFileSync(KEY),
- cert: fs.readFileSync(CERT)
-}, serverListener).listen(port)
-
-describe('MqttSecureClient', function () {
- const config = { protocol: 'mqtts', port, rejectUnauthorized: false }
- abstractClientTests(server, config)
-
- describe('with secure parameters', function () {
- it('should validate successfully the CA', function (done) {
- const client = mqtt.connect({
- protocol: 'mqtts',
- port,
- ca: [fs.readFileSync(CERT)],
- rejectUnauthorized: true
- })
-
- client.on('error', function (err) {
- done(err)
- })
-
- server.once('connect', function () {
- done()
- })
- })
-
- it('should validate successfully the CA using URI', function (done) {
- const client = mqtt.connect('mqtts://localhost:' + port, {
- ca: [fs.readFileSync(CERT)],
- rejectUnauthorized: true
- })
-
- client.on('error', function (err) {
- done(err)
- })
-
- server.once('connect', function () {
- done()
- })
- })
-
- it('should validate successfully the CA using URI with path', function (done) {
- const client = mqtt.connect('mqtts://localhost:' + port + '/', {
- ca: [fs.readFileSync(CERT)],
- rejectUnauthorized: true
- })
-
- client.on('error', function (err) {
- done(err)
- })
-
- server.once('connect', function () {
- done()
- })
- })
-
- it('should validate unsuccessfully the CA', function (done) {
- const client = mqtt.connect({
- protocol: 'mqtts',
- port,
- ca: [fs.readFileSync(WRONG_CERT)],
- rejectUnauthorized: true
- })
-
- client.once('error', function (err) {
- err.should.be.instanceOf(Error)
- client.end((err) => done(err))
- })
- })
-
- it('should emit close on TLS error', function (done) {
- const client = mqtt.connect({
- protocol: 'mqtts',
- port,
- ca: [fs.readFileSync(WRONG_CERT)],
- rejectUnauthorized: true
- })
-
- client.on('error', function () {})
-
- client.once('close', function () {
- client.end((err) => done(err))
- })
- })
-
- it('should support SNI on the TLS connection', function (done) {
- server.removeAllListeners('secureConnection') // clear eventHandler
- server.once('secureConnection', function (tlsSocket) { // one time eventHandler
- assert.equal(tlsSocket.servername, hostname) // validate SNI set
- server.setupConnection(tlsSocket)
- })
-
- const hostname = 'localhost'
- const client = mqtt.connect({
- protocol: 'mqtts',
- port,
- ca: [fs.readFileSync(CERT)],
- rejectUnauthorized: true,
- host: hostname
- })
-
- client.on('error', function (err) {
- done(err)
- })
-
- server.once('connect', function () {
- server.on('secureConnection', server.setupConnection) // reset eventHandler
- client.end((err) => done(err))
- })
- })
- })
+const server = new MqttSecureServer(
+ {
+ key: fs.readFileSync(KEY),
+ cert: fs.readFileSync(CERT),
+ },
+ serverListener,
+).listen(port)
+
+describe('MqttSecureClient', () => {
+ const config = { protocol: 'mqtts', port, rejectUnauthorized: false }
+ abstractClientTests(server, config)
+
+ describe('with secure parameters', () => {
+ it('should validate successfully the CA', function test(done) {
+ const client = mqtt.connect({
+ protocol: 'mqtts',
+ port,
+ ca: [fs.readFileSync(CERT)],
+ rejectUnauthorized: true,
+ })
+
+ client.on('error', (err) => {
+ done(err)
+ })
+
+ server.once('connect', () => {
+ done()
+ })
+ })
+
+ it('should validate successfully the CA using URI', function test(done) {
+ const client = mqtt.connect(`mqtts://localhost:${port}`, {
+ ca: [fs.readFileSync(CERT)],
+ rejectUnauthorized: true,
+ })
+
+ client.on('error', (err) => {
+ done(err)
+ })
+
+ server.once('connect', () => {
+ done()
+ })
+ })
+
+ it('should validate successfully the CA using URI with path', function test(done) {
+ const client = mqtt.connect(`mqtts://localhost:${port}/`, {
+ ca: [fs.readFileSync(CERT)],
+ rejectUnauthorized: true,
+ })
+
+ client.on('error', (err) => {
+ done(err)
+ })
+
+ server.once('connect', () => {
+ done()
+ })
+ })
+
+ it('should validate unsuccessfully the CA', function test(done) {
+ const client = mqtt.connect({
+ protocol: 'mqtts',
+ port,
+ ca: [fs.readFileSync(WRONG_CERT)],
+ rejectUnauthorized: true,
+ })
+
+ client.once('error', (err) => {
+ err.should.be.instanceOf(Error)
+ client.end((err2) => done(err2))
+ })
+ })
+
+ it('should emit close on TLS error', function test(done) {
+ const client = mqtt.connect({
+ protocol: 'mqtts',
+ port,
+ ca: [fs.readFileSync(WRONG_CERT)],
+ rejectUnauthorized: true,
+ })
+
+ client.on('error', () => {})
+
+ client.once('close', () => {
+ client.end((err) => done(err))
+ })
+ })
+
+ it('should support SNI on the TLS connection', function test(done) {
+ const hostname = 'localhost'
+
+ server.removeAllListeners('secureConnection') // clear eventHandler
+ server.once('secureConnection', (tlsSocket) => {
+ // one time eventHandler
+ assert.equal(tlsSocket.servername, hostname) // validate SNI set
+ server.setupConnection(tlsSocket)
+ })
+
+ const client = mqtt.connect({
+ protocol: 'mqtts',
+ port,
+ ca: [fs.readFileSync(CERT)],
+ rejectUnauthorized: true,
+ host: hostname,
+ })
+
+ client.on('error', (err) => {
+ done(err)
+ })
+
+ server.once('connect', () => {
+ server.on('secureConnection', server.setupConnection) // reset eventHandler
+ client.end((err) => done(err))
+ })
+ })
+ })
})
diff --git a/test/server.js b/test/server.js
index a190444e5..8a3732eed 100644
--- a/test/server.js
+++ b/test/server.js
@@ -1,5 +1,3 @@
-'use strict'
-
const net = require('net')
const tls = require('tls')
const Connection = require('mqtt-connection')
@@ -10,22 +8,21 @@ const Connection = require('mqtt-connection')
* @param {Function} listener - fired on client connection
*/
class MqttServer extends net.Server {
- constructor (listener) {
- super()
- this.connectionList = []
+ constructor(listener) {
+ super()
+ this.connectionList = []
- const that = this
- this.on('connection', function (duplex) {
- this.connectionList.push(duplex)
- const connection = new Connection(duplex, function () {
- that.emit('client', connection)
- })
- })
+ this.on('connection', (duplex) => {
+ this.connectionList.push(duplex)
+ const connection = new Connection(duplex, () => {
+ this.emit('client', connection)
+ })
+ })
- if (listener) {
- this.on('client', listener)
- }
- }
+ if (listener) {
+ this.on('client', listener)
+ }
+ }
}
/**
@@ -34,21 +31,21 @@ class MqttServer extends net.Server {
* @param {Function} listener - fired on client connection
*/
class MqttServerNoWait extends net.Server {
- constructor (listener) {
- super()
- this.connectionList = []
+ constructor(listener) {
+ super()
+ this.connectionList = []
- this.on('connection', function (duplex) {
- this.connectionList.push(duplex)
- const connection = new Connection(duplex)
- // do not wait for connection to return to send it to the client.
- this.emit('client', connection)
- })
+ this.on('connection', (duplex) => {
+ this.connectionList.push(duplex)
+ const connection = new Connection(duplex)
+ // do not wait for connection to return to send it to the client.
+ this.emit('client', connection)
+ })
- if (listener) {
- this.on('client', listener)
- }
- }
+ if (listener) {
+ this.on('client', listener)
+ }
+ }
}
/**
@@ -58,35 +55,33 @@ class MqttServerNoWait extends net.Server {
* @param {Function} listener
*/
class MqttSecureServer extends tls.Server {
- constructor (opts, listener) {
- if (typeof opts === 'function') {
- listener = opts
- opts = {}
- }
+ constructor(opts, listener) {
+ if (typeof opts === 'function') {
+ listener = opts
+ opts = {}
+ }
- // sets a listener for the 'connection' event
- super(opts)
- this.connectionList = []
+ // sets a listener for the 'connection' event
+ super(opts)
+ this.connectionList = []
- this.on('secureConnection', function (socket) {
- this.connectionList.push(socket)
- const that = this
- const connection = new Connection(socket, function () {
- that.emit('client', connection)
- })
- })
+ this.on('secureConnection', (socket) => {
+ this.connectionList.push(socket)
+ const connection = new Connection(socket, () => {
+ this.emit('client', connection)
+ })
+ })
- if (listener) {
- this.on('client', listener)
- }
- }
+ if (listener) {
+ this.on('client', listener)
+ }
+ }
- setupConnection (duplex) {
- const that = this
- const connection = new Connection(duplex, function () {
- that.emit('client', connection)
- })
- }
+ setupConnection(duplex) {
+ const connection = new Connection(duplex, () => {
+ this.emit('client', connection)
+ })
+ }
}
exports.MqttServer = MqttServer
diff --git a/test/server_helpers_for_client_tests.js b/test/server_helpers_for_client_tests.js
index 647aacb0b..c1a9685b3 100644
--- a/test/server_helpers_for_client_tests.js
+++ b/test/server_helpers_for_client_tests.js
@@ -1,17 +1,16 @@
-'use strict'
-
-const MqttServer = require('./server').MqttServer
-const MqttSecureServer = require('./server').MqttSecureServer
+const { MqttServer } = require('./server')
const debug = require('debug')('TEST:server_helpers')
const path = require('path')
const fs = require('fs')
+
const KEY = path.join(__dirname, 'helpers', 'tls-key.pem')
const CERT = path.join(__dirname, 'helpers', 'tls-cert.pem')
const http = require('http')
const WebSocket = require('ws')
const MQTTConnection = require('mqtt-connection')
+const { MqttSecureServer } = require('./server')
/**
* This will build the client for the server to use during testing, and set up the
@@ -19,131 +18,135 @@ const MQTTConnection = require('mqtt-connection')
* @param {String} protocol - 'mqtt', 'mqtts' or 'ws'
* @param {Function} handler - event handler
*/
-function serverBuilder (protocol, handler) {
- const defaultHandler = function (serverClient) {
- serverClient.on('auth', function (packet) {
- if (serverClient.writable) return false
- const rc = 'reasonCode'
- const connack = {}
- connack[rc] = 0
- serverClient.connack(connack)
- })
- serverClient.on('connect', function (packet) {
- if (!serverClient.writable) return false
- let rc = 'returnCode'
- const connack = {}
- if (serverClient.options && serverClient.options.protocolVersion === 5) {
- rc = 'reasonCode'
- if (packet.clientId === 'invalid') {
- connack[rc] = 128
- } else {
- connack[rc] = 0
- }
- } else {
- if (packet.clientId === 'invalid') {
- connack[rc] = 2
- } else {
- connack[rc] = 0
- }
- }
- if (packet.properties && packet.properties.authenticationMethod) {
- return false
- } else {
- serverClient.connack(connack)
- }
- })
-
- serverClient.on('publish', function (packet) {
- if (!serverClient.writable) return false
- setImmediate(function () {
- switch (packet.qos) {
- case 0:
- break
- case 1:
- serverClient.puback(packet)
- break
- case 2:
- serverClient.pubrec(packet)
- break
- }
- })
- })
-
- serverClient.on('pubrel', function (packet) {
- if (!serverClient.writable) return false
- serverClient.pubcomp(packet)
- })
-
- serverClient.on('pubrec', function (packet) {
- if (!serverClient.writable) return false
- serverClient.pubrel(packet)
- })
-
- serverClient.on('pubcomp', function () {
- // Nothing to be done
- })
-
- serverClient.on('subscribe', function (packet) {
- if (!serverClient.writable) return false
- serverClient.suback({
- messageId: packet.messageId,
- granted: packet.subscriptions.map(function (e) {
- return e.qos
- })
- })
- })
-
- serverClient.on('unsubscribe', function (packet) {
- if (!serverClient.writable) return false
- packet.granted = packet.unsubscriptions.map(function () { return 0 })
- serverClient.unsuback(packet)
- })
-
- serverClient.on('pingreq', function () {
- if (!serverClient.writable) return false
- serverClient.pingresp()
- })
-
- serverClient.on('end', function () {
- debug('disconnected from server')
- })
- }
-
- if (!handler) {
- handler = defaultHandler
- }
-
- if (
- protocol === 'mqtt') {
- return new MqttServer(handler)
- } else if (
- protocol === 'mqtts') {
- return new MqttSecureServer({
- key: fs.readFileSync(KEY),
- cert: fs.readFileSync(CERT)
- },
- handler)
- } else if (
- protocol === 'ws') {
- const attachWebsocketServer = function (server) {
- const webSocketServer = new WebSocket.Server({ server, perMessageDeflate: false })
-
- webSocketServer.on('connection', function (ws) {
- const stream = WebSocket.createWebSocketStream(ws)
- const connection = new MQTTConnection(stream)
- connection.protocol = ws.protocol
- server.emit('client', connection)
- stream.on('error', function () {})
- connection.on('error', function () {})
- connection.on('close', function () {})
- })
- }
-
- const httpServer = http.createServer()
- attachWebsocketServer(httpServer)
- httpServer.on('client', handler)
- return httpServer
- }
+function serverBuilder(protocol, handler) {
+ const defaultHandler = (serverClient) => {
+ serverClient.on('auth', (packet) => {
+ if (serverClient.writable) return false
+ const rc = 'reasonCode'
+ const connack = {}
+ connack[rc] = 0
+ serverClient.connack(connack)
+ })
+ serverClient.on('connect', (packet) => {
+ if (!serverClient.writable) return false
+ let rc = 'returnCode'
+ const connack = {}
+ if (
+ serverClient.options &&
+ serverClient.options.protocolVersion === 5
+ ) {
+ rc = 'reasonCode'
+ if (packet.clientId === 'invalid') {
+ connack[rc] = 128
+ } else {
+ connack[rc] = 0
+ }
+ } else if (packet.clientId === 'invalid') {
+ connack[rc] = 2
+ } else {
+ connack[rc] = 0
+ }
+ if (packet.properties && packet.properties.authenticationMethod) {
+ return false
+ }
+ serverClient.connack(connack)
+ })
+
+ serverClient.on('publish', (packet) => {
+ if (!serverClient.writable) return false
+ setImmediate(() => {
+ switch (packet.qos) {
+ case 0:
+ break
+ case 1:
+ serverClient.puback(packet)
+ break
+ case 2:
+ serverClient.pubrec(packet)
+ break
+ }
+ })
+ })
+
+ serverClient.on('pubrel', (packet) => {
+ if (!serverClient.writable) return false
+ serverClient.pubcomp(packet)
+ })
+
+ serverClient.on('pubrec', (packet) => {
+ if (!serverClient.writable) return false
+ serverClient.pubrel(packet)
+ })
+
+ serverClient.on('pubcomp', () => {
+ // Nothing to be done
+ })
+
+ serverClient.on('subscribe', (packet) => {
+ if (!serverClient.writable) return false
+ serverClient.suback({
+ messageId: packet.messageId,
+ granted: packet.subscriptions.map((e) => e.qos),
+ })
+ })
+
+ serverClient.on('unsubscribe', (packet) => {
+ if (!serverClient.writable) return false
+ packet.granted = packet.unsubscriptions.map(() => 0)
+ serverClient.unsuback(packet)
+ })
+
+ serverClient.on('pingreq', () => {
+ if (!serverClient.writable) return false
+ serverClient.pingresp()
+ })
+
+ serverClient.on('end', () => {
+ debug('disconnected from server')
+ })
+ }
+
+ if (!handler) {
+ handler = defaultHandler
+ }
+
+ if (protocol === 'mqtt') {
+ return new MqttServer(handler)
+ }
+ if (protocol === 'mqtts') {
+ return new MqttSecureServer(
+ {
+ key: fs.readFileSync(KEY),
+ cert: fs.readFileSync(CERT),
+ },
+ handler,
+ )
+ }
+ if (protocol === 'ws') {
+ const attachWebsocketServer = (server) => {
+ const webSocketServer = new WebSocket.Server({
+ server,
+ perMessageDeflate: false,
+ })
+
+ webSocketServer.on('connection', (ws) => {
+ server.connectionList.push(ws)
+ const stream = WebSocket.createWebSocketStream(ws)
+ const connection = new MQTTConnection(stream)
+ connection.protocol = ws.protocol
+ server.emit('client', connection)
+ stream.on('error', () => {})
+ connection.on('error', () => {})
+ connection.on('close', () => {})
+ })
+ }
+
+ const httpServer = http.createServer()
+ httpServer.connectionList = []
+ attachWebsocketServer(httpServer)
+ httpServer.on('client', handler)
+ return httpServer
+ }
}
exports.serverBuilder = serverBuilder
diff --git a/test/store.js b/test/store.js
index f97fe72cc..6788039fe 100644
--- a/test/store.js
+++ b/test/store.js
@@ -1,10 +1,8 @@
-'use strict'
-
const Store = require('../lib/store')
-const abstractTest = require('../test/abstract_store')
+const abstractTest = require('./abstract_store')
-describe('in-memory store', function () {
- abstractTest(function (done) {
- done(null, new Store())
- })
+describe('in-memory store', () => {
+ abstractTest(function test(done) {
+ done(null, new Store())
+ })
})
diff --git a/test/unique_message_id_provider_client.js b/test/unique_message_id_provider_client.js
index 8853583c3..c67d99b9f 100644
--- a/test/unique_message_id_provider_client.js
+++ b/test/unique_message_id_provider_client.js
@@ -1,21 +1,23 @@
-'use strict'
-
const abstractClientTests = require('./abstract_client')
-const serverBuilder = require('./server_helpers_for_client_tests').serverBuilder
+const { serverBuilder } = require('./server_helpers_for_client_tests')
const UniqueMessageIdProvider = require('../lib/unique-message-id-provider')
const ports = require('./helpers/port_list')
-describe('UniqueMessageIdProviderMqttClient', function () {
- const server = serverBuilder('mqtt')
- const config = { protocol: 'mqtt', port: ports.PORTAND400, messageIdProvider: new UniqueMessageIdProvider() }
- server.listen(ports.PORTAND400)
+describe('UniqueMessageIdProviderMqttClient', () => {
+ const server = serverBuilder('mqtt')
+ const config = {
+ protocol: 'mqtt',
+ port: ports.PORTAND400,
+ messageIdProvider: new UniqueMessageIdProvider(),
+ }
+ server.listen(ports.PORTAND400)
- after(function () {
- // clean up and make sure the server is no longer listening...
- if (server.listening) {
- server.close()
- }
- })
+ after(() => {
+ // clean up and make sure the server is no longer listening...
+ if (server.listening) {
+ server.close()
+ }
+ })
- abstractClientTests(server, config)
+ abstractClientTests(server, config)
})
diff --git a/test/util.js b/test/util.js
index f0b8d0f9d..922c2b334 100644
--- a/test/util.js
+++ b/test/util.js
@@ -1,15 +1,12 @@
-'use strict'
+const { Transform } = require('readable-stream')
-const Transform = require('readable-stream').Transform
-
-module.exports.testStream = function () {
- return new Transform({
- transform (buf, enc, cb) {
- const that = this
- setImmediate(function () {
- that.push(buf)
- cb()
- })
- }
- })
+module.exports.testStream = () => {
+ return new Transform({
+ transform(buf, enc, cb) {
+ setImmediate(() => {
+ this.push(buf)
+ cb()
+ })
+ },
+ })
}
diff --git a/test/websocket_client.js b/test/websocket_client.js
index 0ff253fb4..06516ca4c 100644
--- a/test/websocket_client.js
+++ b/test/websocket_client.js
@@ -1,193 +1,214 @@
-'use strict'
-
const http = require('http')
const WebSocket = require('ws')
const MQTTConnection = require('mqtt-connection')
+const assert = require('assert')
const abstractClientTests = require('./abstract_client')
const ports = require('./helpers/port_list')
-const MqttServerNoWait = require('./server').MqttServerNoWait
-const mqtt = require('../')
-const assert = require('assert')
+const { MqttServerNoWait } = require('./server')
+const mqtt = require('..')
+
const port = 9999
const httpServer = http.createServer()
-function attachWebsocketServer (httpServer) {
- const webSocketServer = new WebSocket.Server({ server: httpServer, perMessageDeflate: false })
-
- webSocketServer.on('connection', function (ws) {
- const stream = WebSocket.createWebSocketStream(ws)
- const connection = new MQTTConnection(stream)
- connection.protocol = ws.protocol
- httpServer.emit('client', connection)
- stream.on('error', function () { })
- connection.on('error', function () { })
- })
-
- return httpServer
+function attachWebsocketServer(httpServer2) {
+ const webSocketServer = new WebSocket.Server({
+ server: httpServer2,
+ perMessageDeflate: false,
+ })
+
+ webSocketServer.on('connection', (ws) => {
+ const stream = WebSocket.createWebSocketStream(ws)
+ const connection = new MQTTConnection(stream)
+ connection.protocol = ws.protocol
+ httpServer2.emit('client', connection)
+ stream.on('error', () => {})
+ connection.on('error', () => {})
+ })
+
+ return httpServer2
}
-function attachClientEventHandlers (client) {
- client.on('connect', function (packet) {
- if (packet.clientId === 'invalid') {
- client.connack({ returnCode: 2 })
- } else {
- httpServer.emit('connect', client)
- client.connack({ returnCode: 0 })
- }
- })
-
- client.on('publish', function (packet) {
- setImmediate(function () {
- switch (packet.qos) {
- case 0:
- break
- case 1:
- client.puback(packet)
- break
- case 2:
- client.pubrec(packet)
- break
- }
- })
- })
-
- client.on('pubrel', function (packet) {
- client.pubcomp(packet)
- })
-
- client.on('pubrec', function (packet) {
- client.pubrel(packet)
- })
-
- client.on('pubcomp', function () {
- // Nothing to be done
- })
-
- client.on('subscribe', function (packet) {
- client.suback({
- messageId: packet.messageId,
- granted: packet.subscriptions.map(function (e) {
- return e.qos
- })
- })
- })
-
- client.on('unsubscribe', function (packet) {
- client.unsuback(packet)
- })
-
- client.on('pingreq', function () {
- client.pingresp()
- })
+function attachClientEventHandlers(client) {
+ client.on('connect', (packet) => {
+ if (packet.clientId === 'invalid') {
+ client.connack({ returnCode: 2 })
+ } else {
+ httpServer.emit('connect', client)
+ client.connack({ returnCode: 0 })
+ }
+ })
+
+ client.on('publish', (packet) => {
+ setImmediate(() => {
+ switch (packet.qos) {
+ case 0:
+ break
+ case 1:
+ client.puback(packet)
+ break
+ case 2:
+ client.pubrec(packet)
+ break
+ }
+ })
+ })
+
+ client.on('pubrel', (packet) => {
+ client.pubcomp(packet)
+ })
+
+ client.on('pubrec', (packet) => {
+ client.pubrel(packet)
+ })
+
+ client.on('pubcomp', () => {
+ // Nothing to be done
+ })
+
+ client.on('subscribe', (packet) => {
+ client.suback({
+ messageId: packet.messageId,
+ granted: packet.subscriptions.map((e) => e.qos),
+ })
+ })
+
+ client.on('unsubscribe', (packet) => {
+ client.unsuback(packet)
+ })
+
+ client.on('pingreq', () => {
+ client.pingresp()
+ })
}
attachWebsocketServer(httpServer)
httpServer.on('client', attachClientEventHandlers).listen(port)
-describe('Websocket Client', function () {
- const baseConfig = { protocol: 'ws', port }
-
- 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)
- 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)
- })
- })
- })
- 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()
- })
- })
- })
- })
-
- abstractClientTests(httpServer, makeOptions())
+describe('Websocket Client', () => {
+ const baseConfig = { protocol: 'ws', port }
+
+ function makeOptions(custom) {
+ return { ...baseConfig, ...(custom || {}) }
+ }
+
+ it('should use mqtt as the protocol by default', function test(done) {
+ httpServer.once('client', (client) => {
+ assert.strictEqual(client.protocol, 'mqtt')
+ })
+ const client = mqtt.connect(makeOptions())
+
+ client.on('connect', () => {
+ client.end(true, (err) => done(err))
+ })
+ })
+
+ it('should be able to transform the url (for e.g. to sign it)', function test(done) {
+ const baseUrl = 'ws://localhost:9999/mqtt'
+ const sig = '?AUTH=token'
+ const expected = baseUrl + sig
+ let actual
+ const opts = makeOptions({
+ path: '/mqtt',
+ transformWsUrl(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
+ },
+ })
+ const client = mqtt.connect(opts)
+
+ client.on('connect', () => {
+ assert.equal(client.stream.url, expected)
+ assert.equal(actual, expected)
+ client.end(true, (err) => done(err))
+ })
+ })
+
+ it('should use mqttv3.1 as the protocol if using v3.1', function test(done) {
+ httpServer.once('client', (client) => {
+ assert.strictEqual(client.protocol, 'mqttv3.1')
+ })
+
+ const opts = makeOptions({
+ protocolId: 'MQIsdp',
+ protocolVersion: 3,
+ })
+
+ const client = mqtt.connect(opts)
+
+ client.on('connect', () => {
+ client.end(true, (err) => done(err))
+ })
+ })
+
+ describe('reconnecting', () => {
+ it('should reconnect to multiple host-ports-protocol combinations if servers is passed', function test(done) {
+ let serverPort42Connected = false
+ const handler = (serverClient) => {
+ serverClient.on('connect', (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', () => {
+ const client = mqtt.connect({
+ protocol: 'wss',
+ servers: [
+ {
+ port: ports.PORTAND42,
+ host: 'localhost',
+ protocol: 'ws',
+ },
+ { port: ports.PORTAND41, host: 'localhost' },
+ ],
+ keepalive: 50,
+ })
+ serverPort41.once('client', (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', (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', () => {
+ client.stream.destroy()
+ })
+ })
+ })
+ })
+
+ abstractClientTests(httpServer, makeOptions())
})
diff --git a/types/lib/client-options.d.ts b/types/lib/client-options.d.ts
index f4408fd3d..c71ea344f 100644
--- a/types/lib/client-options.d.ts
+++ b/types/lib/client-options.d.ts
@@ -107,7 +107,12 @@ export interface IClientOptions extends ISecureClientOptions {
responseTopic?: string,
correlationData?: Buffer,
userProperties?: UserProperties
- }
+ },
+
+ authPacket?: any
+
+ /** Prevent to call `connect` in constructor */
+ manualConnect?: boolean
}
transformWsUrl?: (url: string, options: IClientOptions, client: MqttClient) => string,
properties?: {
diff --git a/types/lib/client.d.ts b/types/lib/client.d.ts
index 2914392ee..d19c5febc 100644
--- a/types/lib/client.d.ts
+++ b/types/lib/client.d.ts
@@ -121,6 +121,11 @@ export declare class MqttClient extends events.EventEmitter {
public once (event: 'end' | 'reconnect' | 'offline' | 'outgoingEmpty', cb: () => void): this
public once (event: string, cb: Function): this
+ /**
+ * Setup the event handlers in the inner stream, sends `connect` and `auth` packets
+ */
+ public connect(): this
+
/**
* publish - publish to
*