diff --git a/.eslintignore b/.eslintignore index 4e53d5816..e58e2c3d2 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,6 @@ /examples/ /doc/ /dist/ -/test/typescript/ \ No newline at end of file +/build/ + +*.js \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 51e23bd0c..0d1652d95 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,6 @@ module.exports = { + parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint/eslint-plugin'], env: { browser: true, commonjs: true, @@ -6,9 +8,16 @@ module.exports = { node: true, mocha: true, }, - extends: ['airbnb-base', 'plugin:prettier/recommended'], + extends: [ + 'airbnb-base', + 'airbnb-typescript/base', + 'plugin:prettier/recommended', + 'plugin:@typescript-eslint/recommended' + ], parserOptions: { - ecmaVersion: 'latest', + project: 'tsconfig.json', + sourceType: 'module', + tsconfigRootDir: __dirname }, rules: { 'global-require': 'off', @@ -29,5 +38,14 @@ module.exports = { 'no-continue': 'off', 'prefer-destructuring': 'off', 'no-use-before-define': 'off', - }, + // Typescript rules + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/dot-notation': 'off', + '@typescript-eslint/no-use-before-define': 'off', + } } diff --git a/.github/workflows/mqttjs-test.yml b/.github/workflows/mqttjs-test.yml index 05d67813d..bf3fe168d 100644 --- a/.github/workflows/mqttjs-test.yml +++ b/.github/workflows/mqttjs-test.yml @@ -43,18 +43,12 @@ jobs: CI: true DEBUG: "${{ runner.debug == '1' && 'mqttjs:*' || '' }}" - - name: Test Typescript - run: npm run test:typescript - env: - CI: true - DEBUG: "${{ runner.debug == '1' && 'mqttjs:*' || '' }}" - - name: Test Browser if: matrix.node-version == '20.x' # only run on latest node version, no reason to run on all timeout-minutes: 2 run: | - npm run browser-build + npm run build:browser npm run unit-test:browser diff --git a/.gitignore b/.gitignore index afe1f2e2b..13052c8f8 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ test/typescript/*.map .vscode/ .npmrc +/build/ + diff --git a/.mocharc.js b/.mocharc.js new file mode 100644 index 000000000..3899769be --- /dev/null +++ b/.mocharc.js @@ -0,0 +1,10 @@ +// --check-leaks +// --timeout 10000 +// --exit + +module.exports = { + checkLeaks: true, + exit: true, + timeout: 10000, +}; + diff --git a/README.md b/README.md index ddfd6480b..7e36e3043 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,11 @@ There is work being done on the next generation of MQTT.js (vNext). We invite th ## Important notes for existing users -**v5.0.0** (**BETA** 06/2023) removes support for all end of life node versions (v12 and v14), and now supports node v18 and v20. +**v5.0.0** (**BETA** 06/2023) + +- Removes support for all end of life node versions (v12 and v14), and now supports node v18 and v20. +- Completely rewritten in Typescript 🚀. +- When creating `MqttClient` instance `new` is now required **v4.0.0** (Released 04/2020) removes support for all end of life node versions, and now supports node v12 and v14. It also adds improvements to debug logging, along with some feature additions. @@ -389,7 +393,7 @@ The arguments are: - `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)*/} + customHandleAcks: function(topic, message, packet, done) {/*some logic with calling done(error, reasonCode)*/} ``` - `autoUseTopicAlias`: enabling automatic Topic Alias using functionality diff --git a/bin/pub.js b/bin/pub.js deleted file mode 100755 index d9e3ec8bb..000000000 --- a/bin/pub.js +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env node - -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'), -}) -const minimist = require('minimist') -const split2 = require('split2') - -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 = (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((data) => { - args.message = data - send(args) - }), - ) - } - } else { - send(args) - } -} - -module.exports = start - -if (require.main === module) { - start(process.argv.slice(2)) -} diff --git a/bin/sub.js b/bin/sub.js deleted file mode 100755 index d2e52d5f2..000000000 --- a/bin/sub.js +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env node - -const mqtt = require('..') -const path = require('path') -const fs = require('fs') -const helpMe = require('help-me')({ - 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', () => { - 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)) -} diff --git a/lib/connect/tcp.js b/lib/connect/tcp.js deleted file mode 100644 index 650ce844e..000000000 --- a/lib/connect/tcp.js +++ /dev/null @@ -1,19 +0,0 @@ -const net = require('net') -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' - - const { port } = opts - const host = opts.hostname - - debug('port %d and host %s', port, host) - return net.createConnection(port, host) -} - -module.exports = streamBuilder diff --git a/lib/default-message-id-provider.js b/lib/default-message-id-provider.js deleted file mode 100644 index 6f544aacf..000000000 --- a/lib/default-message-id-provider.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * 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)) - } - - /** - * 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 - } - - /** - * register - * Register messageId. If success return true, otherwise return false. - * @param { unsigned int } - messageId to register, - * @return boolean - */ - register(messageId) { - return true - } - - /** - * deallocate - * Deallocate messageId. - * @param { unsigned int } - messageId to deallocate, - */ - deallocate(messageId) {} - - /** - * clear - * Deallocate all messageIds. - */ - clear() {} -} - -module.exports = DefaultMessageIdProvider diff --git a/lib/handlers/auth.js b/lib/handlers/auth.js deleted file mode 100644 index 6d2d3e9be..000000000 --- a/lib/handlers/auth.js +++ /dev/null @@ -1,34 +0,0 @@ -const { errors } = require('./ack') - -function handleAuth(client, packet) { - 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 - } - - client.handleAuth(packet, (err, packet2) => { - if (err) { - client.emit('error', err) - return - } - - 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 diff --git a/lib/handlers/pubrel.js b/lib/handlers/pubrel.js deleted file mode 100644 index 571ccbc79..000000000 --- a/lib/handlers/pubrel.js +++ /dev/null @@ -1,24 +0,0 @@ -function handlePubrel(client, packet, done) { - client.log('handling pubrel packet') - const callback = typeof done !== 'undefined' ? done : client.noop - const { messageId } = packet - - const comp = { cmd: 'pubcomp', messageId } - - 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 diff --git a/mqtt.js b/mqtt.js deleted file mode 100644 index bdf9389b1..000000000 --- a/mqtt.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (c) 2015-2015 MQTT.js contributors. - * Copyright (c) 2011-2014 Adam Rudd. - * - * See LICENSE for more information - */ - -const MqttClient = require('./lib/client') -const connect = require('./lib/connect') -const Store = require('./lib/store') -const DefaultMessageIdProvider = require('./lib/default-message-id-provider') -const UniqueMessageIdProvider = require('./lib/unique-message-id-provider') - -module.exports.connect = connect - -// Expose MqttClient -module.exports.MqttClient = MqttClient -module.exports.Client = MqttClient -module.exports.Store = Store -module.exports.DefaultMessageIdProvider = DefaultMessageIdProvider -module.exports.UniqueMessageIdProvider = UniqueMessageIdProvider diff --git a/package-lock.json b/package-lock.json index 933602ba7..6a1e22acd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,15 +25,22 @@ "ws": "^8.13.0" }, "bin": { - "mqtt": "bin/mqtt.js", - "mqtt_pub": "bin/pub.js", - "mqtt_sub": "bin/sub.js" + "mqtt": "build/bin/mqtt.js", + "mqtt_pub": "build/bin/pub.js", + "mqtt_sub": "build/bin/sub.js" }, "devDependencies": { "@release-it/conventional-changelog": "^6.0.0", - "@types/node": "^20.4.0", + "@types/chai": "^4.3.5", + "@types/duplexify": "^3.6.1", + "@types/mocha": "^10.0.1", + "@types/node": "^20.4.2", + "@types/readable-stream": "^2.3.15", + "@types/sinon": "^10.0.15", "@types/tape": "^5.6.0", "@types/ws": "^8.5.5", + "@typescript-eslint/eslint-plugin": "^6.1.0", + "@typescript-eslint/parser": "^6.1.0", "airtap": "^4.0.4", "airtap-playwright": "^1.0.1", "browserify": "^17.0.0", @@ -44,6 +51,7 @@ "end-of-stream": "^1.4.4", "eslint": "^8.45.0", "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-prettier": "^5.0.0", @@ -62,6 +70,7 @@ "snazzy": "^9.0.0", "tape": "^5.6.4", "terser": "^5.18.2", + "ts-node": "^10.9.1", "typescript": "^5.1.6" }, "engines": { @@ -402,6 +411,28 @@ "node": ">=6.9.0" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -1227,6 +1258,36 @@ "node": ">= 6" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", + "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", + "dev": true + }, "node_modules/@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", @@ -1239,12 +1300,27 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", "dev": true }, + "node_modules/@types/duplexify": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.1.tgz", + "integrity": "sha512-n0zoEj/fMdMOvqbHxmqnza/kXyoGgJmEpsXjpP+gEqE1Ye4yNqc7xWipKnUoMpWhMuzJQSfK2gMrwlElly7OGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -1257,10 +1333,16 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "node_modules/@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, "node_modules/@types/node": { - "version": "20.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.0.tgz", - "integrity": "sha512-jfT7iTf/4kOQ9S7CHV9BIyRaQqHu67mOjsIQBC3BKZvzvUB6zLxEwJ6sBE3ozcvP8kF6Uk5PXN0Q+c0dfhGX0g==", + "version": "20.4.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", + "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==", "dev": true }, "node_modules/@types/normalize-package-data": { @@ -1269,6 +1351,43 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, + "node_modules/@types/readable-stream": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", + "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "safe-buffer": "~5.1.1" + } + }, + "node_modules/@types/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@types/sinon": { + "version": "10.0.15", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.15.tgz", + "integrity": "sha512-3lrFNQG0Kr2LDzvjyjB6AMJk4ge+8iYhQfdnSwIwlG88FUOV43kPcQqDZkDa/h3WSZy6i8Fr0BSjfQtB1B3xuQ==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "node_modules/@types/tape": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@types/tape/-/tape-5.6.0.tgz", @@ -1297,6 +1416,306 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.1.0.tgz", + "integrity": "sha512-qg7Bm5TyP/I7iilGyp6DRqqkt8na00lI6HbjWZObgk3FFSzH5ypRwAHXJhJkwiRtTcfn+xYQIMOR5kJgpo6upw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.1.0", + "@typescript-eslint/type-utils": "6.1.0", + "@typescript-eslint/utils": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/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/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.1.0.tgz", + "integrity": "sha512-hIzCPvX4vDs4qL07SYzyomamcs2/tQYXg5DtdAfj35AyJ5PIUqhsLf4YrEIFzZcND7R2E8tpQIZKayxg8/6Wbw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.1.0", + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/typescript-estree": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.1.0.tgz", + "integrity": "sha512-AxjgxDn27hgPpe2rQe19k0tXw84YCOsjDJ2r61cIebq1t+AIxbgiXKvD4999Wk49GVaAcdJ/d49FYel+Pp3jjw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.1.0.tgz", + "integrity": "sha512-kFXBx6QWS1ZZ5Ni89TyT1X9Ag6RXVIVhqDs0vZE/jUeWlBv/ixq2diua6G7ece6+fXw3TvNRxP77/5mOMusx2w==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.1.0", + "@typescript-eslint/utils": "6.1.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.1.0.tgz", + "integrity": "sha512-+Gfd5NHCpDoHDOaU/yIF3WWRI2PcBRKKpP91ZcVbL0t5tQpqYWBs3z/GGhvU+EV1D0262g9XCnyqQh19prU0JQ==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.1.0.tgz", + "integrity": "sha512-nUKAPWOaP/tQjU1IQw9sOPCDavs/iU5iYLiY/6u7gxS7oKQoi4aUxXS1nrrVGTyBBaGesjkcwwHkbkiD5eBvcg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.1.0.tgz", + "integrity": "sha512-wp652EogZlKmQoMS5hAvWqRKplXvkuOnNzZSE0PVvsKjpexd/XznRVHAtrfHFYmqaJz0DFkjlDsGYC9OXw+OhQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.1.0", + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/typescript-estree": "6.1.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/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/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.1.0.tgz", + "integrity": "sha512-yQeh+EXhquh119Eis4k0kYhj9vmFzNpbhM3LftWQVwqVjipCkwHBQOZutcYW+JVkjtTG9k8nrZU1UoNedPDd1A==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.1.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -1861,6 +2280,12 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1929,6 +2354,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", @@ -3803,6 +4237,12 @@ "sha.js": "^2.4.8" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5036,6 +5476,21 @@ "eslint-plugin-import": "^2.25.2" } }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", + "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^15.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.13.0 || ^6.0.0", + "@typescript-eslint/parser": "^5.0.0 || ^6.0.0", + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.3" + } + }, "node_modules/eslint-config-prettier": { "version": "8.8.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", @@ -8799,6 +9254,12 @@ "node": ">=8" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/make-promises-safe": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/make-promises-safe/-/make-promises-safe-5.1.0.tgz", @@ -9764,6 +10225,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -14015,37 +14482,122 @@ "node": ">=8.0" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "node_modules/transient-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/transient-error/-/transient-error-1.0.0.tgz", + "integrity": "sha512-pHi+O9OajOp96ldjeZ0i45n/d4QqV8ef8VCrpx5+RL5VO7Xdt2oQj//zcOhuGw5tiPYe73I4d28wV+yCqhSWxA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", + "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/acorn": { + "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" + }, "engines": { - "node": ">=0.6" + "node": ">=0.4.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true - }, - "node_modules/transient-error": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/transient-error/-/transient-error-1.0.0.tgz", - "integrity": "sha512-pHi+O9OajOp96ldjeZ0i45n/d4QqV8ef8VCrpx5+RL5VO7Xdt2oQj//zcOhuGw5tiPYe73I4d28wV+yCqhSWxA==", + "node_modules/ts-node/node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true, "engines": { - "node": ">=6" + "node": ">=0.4.0" } }, - "node_modules/trim-newlines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, "engines": { - "node": ">=8" + "node": ">=0.3.1" } }, "node_modules/tsconfig-paths": { @@ -14466,6 +15018,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -15088,6 +15646,15 @@ "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", "dev": true }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -15363,6 +15930,27 @@ "to-fast-properties": "^2.0.0" } }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -15996,6 +16584,36 @@ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "dev": true }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "@types/chai": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.5.tgz", + "integrity": "sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==", + "dev": true + }, "@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", @@ -16008,12 +16626,27 @@ "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", "dev": true }, + "@types/duplexify": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@types/duplexify/-/duplexify-3.6.1.tgz", + "integrity": "sha512-n0zoEj/fMdMOvqbHxmqnza/kXyoGgJmEpsXjpP+gEqE1Ye4yNqc7xWipKnUoMpWhMuzJQSfK2gMrwlElly7OGQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==", "dev": true }, + "@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -16026,10 +16659,16 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "@types/mocha": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz", + "integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q==", + "dev": true + }, "@types/node": { - "version": "20.4.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.0.tgz", - "integrity": "sha512-jfT7iTf/4kOQ9S7CHV9BIyRaQqHu67mOjsIQBC3BKZvzvUB6zLxEwJ6sBE3ozcvP8kF6Uk5PXN0Q+c0dfhGX0g==", + "version": "20.4.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz", + "integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==", "dev": true }, "@types/normalize-package-data": { @@ -16038,6 +16677,45 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, + "@types/readable-stream": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-2.3.15.tgz", + "integrity": "sha512-oM5JSKQCcICF1wvGgmecmHldZ48OZamtMxcGGVICOJA8o8cahXC1zEVAif8iwoc5j8etxFaRFnf095+CDsuoFQ==", + "dev": true, + "requires": { + "@types/node": "*", + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "@types/sinon": { + "version": "10.0.15", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.15.tgz", + "integrity": "sha512-3lrFNQG0Kr2LDzvjyjB6AMJk4ge+8iYhQfdnSwIwlG88FUOV43kPcQqDZkDa/h3WSZy6i8Fr0BSjfQtB1B3xuQ==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "@types/tape": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/@types/tape/-/tape-5.6.0.tgz", @@ -16066,6 +16744,187 @@ "@types/node": "*" } }, + "@typescript-eslint/eslint-plugin": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.1.0.tgz", + "integrity": "sha512-qg7Bm5TyP/I7iilGyp6DRqqkt8na00lI6HbjWZObgk3FFSzH5ypRwAHXJhJkwiRtTcfn+xYQIMOR5kJgpo6upw==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.1.0", + "@typescript-eslint/type-utils": "6.1.0", + "@typescript-eslint/utils": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "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.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.1.0.tgz", + "integrity": "sha512-hIzCPvX4vDs4qL07SYzyomamcs2/tQYXg5DtdAfj35AyJ5PIUqhsLf4YrEIFzZcND7R2E8tpQIZKayxg8/6Wbw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "6.1.0", + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/typescript-estree": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.1.0.tgz", + "integrity": "sha512-AxjgxDn27hgPpe2rQe19k0tXw84YCOsjDJ2r61cIebq1t+AIxbgiXKvD4999Wk49GVaAcdJ/d49FYel+Pp3jjw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.1.0.tgz", + "integrity": "sha512-kFXBx6QWS1ZZ5Ni89TyT1X9Ag6RXVIVhqDs0vZE/jUeWlBv/ixq2diua6G7ece6+fXw3TvNRxP77/5mOMusx2w==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "6.1.0", + "@typescript-eslint/utils": "6.1.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + } + }, + "@typescript-eslint/types": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.1.0.tgz", + "integrity": "sha512-+Gfd5NHCpDoHDOaU/yIF3WWRI2PcBRKKpP91ZcVbL0t5tQpqYWBs3z/GGhvU+EV1D0262g9XCnyqQh19prU0JQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.1.0.tgz", + "integrity": "sha512-nUKAPWOaP/tQjU1IQw9sOPCDavs/iU5iYLiY/6u7gxS7oKQoi4aUxXS1nrrVGTyBBaGesjkcwwHkbkiD5eBvcg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/visitor-keys": "6.1.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "dependencies": { + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "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.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + } + } + }, + "@typescript-eslint/utils": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.1.0.tgz", + "integrity": "sha512-wp652EogZlKmQoMS5hAvWqRKplXvkuOnNzZSE0PVvsKjpexd/XznRVHAtrfHFYmqaJz0DFkjlDsGYC9OXw+OhQ==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.1.0", + "@typescript-eslint/types": "6.1.0", + "@typescript-eslint/typescript-estree": "6.1.0", + "semver": "^7.5.4" + }, + "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.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.1.0.tgz", + "integrity": "sha512-yQeh+EXhquh119Eis4k0kYhj9vmFzNpbhM3LftWQVwqVjipCkwHBQOZutcYW+JVkjtTG9k8nrZU1UoNedPDd1A==", + "dev": true, + "requires": { + "@typescript-eslint/types": "6.1.0", + "eslint-visitor-keys": "^3.4.1" + } + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -16547,6 +17406,12 @@ "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -16600,6 +17465,12 @@ "is-string": "^1.0.7" } }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", @@ -18132,6 +19003,12 @@ "sha.js": "^2.4.8" } }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -19184,6 +20061,15 @@ "semver": "^6.3.0" } }, + "eslint-config-airbnb-typescript": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-17.1.0.tgz", + "integrity": "sha512-GPxI5URre6dDpJ0CtcthSZVBAfI+Uw7un5OYNVxP2EYi3H81Jw701yFP7AU+/vCE7xBtFmjge7kfhhk4+RAiig==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^15.0.0" + } + }, "eslint-config-prettier": { "version": "8.8.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", @@ -22033,6 +22919,12 @@ "semver": "^6.0.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "make-promises-safe": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/make-promises-safe/-/make-promises-safe-5.1.0.tgz", @@ -22818,6 +23710,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -26179,6 +27077,54 @@ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", "dev": true }, + "ts-api-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.1.tgz", + "integrity": "sha512-lC/RGlPmwdrIBFTX59wwNzqh7aR2otPNPR/5brHZm/XKFYKsfqxihXUe9pU3JI+3vGkl+vyCoNNnPhJn3aLK1A==", + "dev": true, + "requires": {} + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, "tsconfig-paths": { "version": "3.14.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", @@ -26508,6 +27454,12 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -26981,6 +27933,12 @@ "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=", "dev": true }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index fafb47fc4..f7ea6ce31 100644 --- a/package.json +++ b/package.json @@ -20,21 +20,46 @@ "type": "git", "url": "git://github.com/mqttjs/MQTT.js.git" }, - "main": "mqtt.js", - "types": "types/index.d.ts", + "main": "./build/src/mqtt.js", + "bin": { + "mqtt_pub": "./build/bin/pub.js", + "mqtt_sub": "./build/bin/sub.js", + "mqtt": "./build/bin/mqtt.js" + }, + "files": [ + "dist/", + "CONTRIBUTING.md", + "LICENSE.md", + "doc/", + "build/", + "src/" + ], + "exports": { + ".": "./build/src/mqtt.js", + "./package.json": "./package.json", + "./*.map": "./build/*.js.map", + "./*": "./build/*.js" + }, + "types": "build/src/mqtt.d.ts", + "typesVersions": { + "*": { + "*": [ + "./build/src/mqtt.d.ts" + ] + } + }, "scripts": { - "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", - "prepare": "npm run browser-build", - "unit-test:node": "node_modules/.bin/nyc --reporter=lcov --reporter=text ./node_modules/mocha/bin/_mocha --exit", + "lint": "eslint --ext .ts .", + "lint-fix": "eslint --fix --ext .ts .", + "build:ts": "rimraf build/ && tsc -p tsconfig.build.json", + "build:browser": "rimraf dist/ && mkdirp dist/ && browserify build/src/mqtt.js --standalone mqtt > dist/mqtt.js && terser dist/mqtt.js --compress --mangle --output dist/mqtt.min.js", + "build": "npm run build:ts && npm run build:browser", + "prepare": "npm run build", + "unit-test:node": "node_modules/.bin/nyc --reporter=lcov --reporter=text ./node_modules/mocha/bin/_mocha -r ts-node/register test/*.ts --exit", "unit-test:browser": "airtap --server test/browser/server.js test/browser/test.js", "test:node": "npm run unit-test:node && codecov", - "test:browser": "npm run browser-build && npm run unit-test:browser", - "test:typescript": "npm run typescript-compile-test && npm run typescript-compile-execute", - "test": "npm run test:node && npm run test:typescript", + "test:browser": "npm run build && npm run unit-test:browser", + "test": "npm run test:node", "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s && git add CHANGELOG.md", "changelog-init": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0", "release": "read -p 'GITHUB_TOKEN: ' GITHUB_TOKEN && export GITHUB_TOKEN=$GITHUB_TOKEN && release-it" @@ -65,25 +90,11 @@ "pre-commit": [ "lint" ], - "bin": { - "mqtt_pub": "./bin/pub.js", - "mqtt_sub": "./bin/sub.js", - "mqtt": "./bin/mqtt.js" - }, - "files": [ - "dist/", - "CONTRIBUTING.md", - "doc", - "lib", - "bin", - "types", - "mqtt.js" - ], "engines": { "node": ">=16.0.0" }, "browser": { - "./mqtt.js": "./lib/connect/index.js", + "./mqtt.js": "./build/src/mqtt.js", "fs": false, "tls": false, "net": false @@ -106,9 +117,16 @@ }, "devDependencies": { "@release-it/conventional-changelog": "^6.0.0", - "@types/node": "^20.4.0", + "@types/chai": "^4.3.5", + "@types/duplexify": "^3.6.1", + "@types/mocha": "^10.0.1", + "@types/node": "^20.4.2", + "@types/readable-stream": "^2.3.15", + "@types/sinon": "^10.0.15", "@types/tape": "^5.6.0", "@types/ws": "^8.5.5", + "@typescript-eslint/eslint-plugin": "^6.1.0", + "@typescript-eslint/parser": "^6.1.0", "airtap": "^4.0.4", "airtap-playwright": "^1.0.1", "browserify": "^17.0.0", @@ -119,6 +137,7 @@ "end-of-stream": "^1.4.4", "eslint": "^8.45.0", "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-prettier": "^5.0.0", @@ -137,6 +156,7 @@ "snazzy": "^9.0.0", "tape": "^5.6.4", "terser": "^5.18.2", + "ts-node": "^10.9.1", "typescript": "^5.1.6" } } diff --git a/bin/mqtt.js b/src/bin/mqtt.ts similarity index 58% rename from bin/mqtt.js rename to src/bin/mqtt.ts index ee9417288..14e13203c 100755 --- a/bin/mqtt.js +++ b/src/bin/mqtt.ts @@ -6,18 +6,23 @@ * * See LICENSE for more information */ -const path = require('path') -const commist = require('commist')() -const helpMe = require('help-me')({ +import path from 'path' +import commist from 'commist' +import helpMe from 'help-me' +import { version } from '../../package.json' +import publish from './pub' +import subscribe from './sub' + +helpMe({ dir: path.join(path.dirname(require.main.filename), '/../doc'), ext: '.txt', }) -commist.register('publish', require('./pub')) -commist.register('subscribe', require('./sub')) +commist.register('publish', publish) +commist.register('subscribe', subscribe) commist.register('version', () => { - console.log('MQTT.js version:', require('../package.json').version) + console.log('MQTT.js version:', version) }) commist.register('help', helpMe.toStdout) diff --git a/src/bin/pub.ts b/src/bin/pub.ts new file mode 100755 index 000000000..203fe0f35 --- /dev/null +++ b/src/bin/pub.ts @@ -0,0 +1,171 @@ +#!/usr/bin/env node + +import { Writable } from 'readable-stream' +import path from 'path' +import fs from 'fs' +import concat from 'concat-stream' +import helpMe from 'help-me' +import minimist, { ParsedArgs } from 'minimist' +import split2 from 'split2' +import { connect } from '../mqtt' +import { IClientOptions, IClientPublishOptions } from 'src/lib/client' +import { pipeline } from 'stream' + +helpMe({ + dir: path.join(__dirname, '..', 'doc'), +}) + +function send(args: ParsedArgs) { + const client = connect(args as IClientOptions) + client.on('connect', () => { + client.publish( + args.topic, + args.message, + args as IClientPublishOptions, + (err) => { + if (err) { + console.warn(err) + } + client.end() + }, + ) + }) + client.on('error', (err) => { + console.warn(err) + client.end() + }) +} + +function multisend(args: ParsedArgs) { + const client = connect(args as IClientOptions) + const sender = new Writable({ + objectMode: true, + }) + sender._write = (line, enc, cb) => { + client.publish( + args.topic, + line.trim(), + args as IClientPublishOptions, + cb, + ) + } + + client.on('connect', () => { + pipeline(process.stdin, split2(), sender, (err) => { + client.end() + if (err) { + throw err + } + }) + }) +} + +export default function start(args: string[]) { + const parsedArgs = 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 (parsedArgs.help) { + return helpMe.toStdout('publish') + } + + if (parsedArgs.key) { + parsedArgs.key = fs.readFileSync(parsedArgs.key) + } + + if (parsedArgs.cert) { + parsedArgs.cert = fs.readFileSync(parsedArgs.cert) + } + + if (parsedArgs.ca) { + parsedArgs.ca = fs.readFileSync(parsedArgs.ca) + } + + if (parsedArgs.key && parsedArgs.cert && !parsedArgs.protocol) { + parsedArgs.protocol = 'mqtts' + } + + if (parsedArgs.port) { + if (typeof parsedArgs.port !== 'number') { + console.warn( + "# Port: number expected, '%s' was given.", + typeof parsedArgs.port, + ) + return + } + } + + if (parsedArgs['will-topic']) { + parsedArgs.will = {} + parsedArgs.will.topic = parsedArgs['will-topic'] + parsedArgs.will.payload = parsedArgs['will-message'] + parsedArgs.will.qos = parsedArgs['will-qos'] + parsedArgs.will.retain = parsedArgs['will-retain'] + } + + if (parsedArgs.insecure) { + parsedArgs.rejectUnauthorized = false + } + + parsedArgs.topic = (parsedArgs.topic || parsedArgs._.shift()).toString() + parsedArgs.message = (parsedArgs.message || parsedArgs._.shift()).toString() + + if (!parsedArgs.topic) { + console.error('missing topic\n') + return helpMe.toStdout('publish') + } + + if (parsedArgs.stdin) { + if (parsedArgs.multiline) { + multisend(parsedArgs) + } else { + process.stdin.pipe( + concat((data) => { + parsedArgs.message = data + send(parsedArgs) + }), + ) + } + } else { + send(parsedArgs) + } +} + +if (require.main === module) { + start(process.argv.slice(2)) +} diff --git a/src/bin/sub.ts b/src/bin/sub.ts new file mode 100755 index 000000000..d8ec02981 --- /dev/null +++ b/src/bin/sub.ts @@ -0,0 +1,146 @@ +#!/usr/bin/env node + +import path from 'path' +import fs from 'fs' +import minimist from 'minimist' +import helpMe from 'help-me' +import { connect } from '../mqtt' +import { IClientOptions } from 'src/lib/client' + +helpMe({ + dir: path.join(__dirname, '..', 'doc'), +}) + +export default function start(args: string[]) { + const parsedArgs = 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 (parsedArgs.help) { + return helpMe.toStdout('subscribe') + } + + parsedArgs.topic = parsedArgs.topic || parsedArgs._.shift() + + if (!parsedArgs.topic) { + console.error('missing topic\n') + return helpMe.toStdout('subscribe') + } + + if (parsedArgs.key) { + parsedArgs.key = fs.readFileSync(parsedArgs.key) + } + + if (parsedArgs.cert) { + parsedArgs.cert = fs.readFileSync(parsedArgs.cert) + } + + if (parsedArgs.ca) { + parsedArgs.ca = fs.readFileSync(parsedArgs.ca) + } + + if (parsedArgs.key && parsedArgs.cert && !parsedArgs.protocol) { + parsedArgs.protocol = 'mqtts' + } + + if (parsedArgs.insecure) { + parsedArgs.rejectUnauthorized = false + } + + if (parsedArgs.port) { + if (typeof parsedArgs.port !== 'number') { + console.warn( + "# Port: number expected, '%s' was given.", + typeof parsedArgs.port, + ) + return + } + } + + if (parsedArgs['will-topic']) { + parsedArgs.will = {} + parsedArgs.will.topic = parsedArgs['will-topic'] + parsedArgs.will.payload = parsedArgs['will-message'] + parsedArgs.will.qos = parsedArgs['will-qos'] + parsedArgs.will.retain = parsedArgs['will-retain'] + } + + parsedArgs.keepAlive = parsedArgs['keep-alive'] + + const client = connect(parsedArgs as IClientOptions) + + client.on('connect', () => { + client.subscribe( + parsedArgs.topic, + { qos: parsedArgs.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 (parsedArgs.verbose) { + console.log(topic, payload.toString()) + } else { + console.log(payload.toString()) + } + }) + + client.on('error', (err) => { + console.warn(err) + client.end() + }) +} + +if (require.main === module) { + start(process.argv.slice(2)) +} diff --git a/src/lib/TypedEmitter.ts b/src/lib/TypedEmitter.ts new file mode 100644 index 000000000..7af896c2e --- /dev/null +++ b/src/lib/TypedEmitter.ts @@ -0,0 +1,45 @@ +import EventEmitter from 'events' +import { applyMixin } from './shared' + +export type EventHandler = + // Add more overloads as necessary + | ((arg1: any, arg2: any, arg3: any, arg4: any) => void) + | ((arg1: any, arg2: any, arg3: any) => void) + | ((arg1: any, arg2: any) => void) + | ((arg1: any) => void) + | ((...args: any[]) => void) + +export interface TypedEventEmitter< + TEvents extends Record, +> { + on( + event: TEvent, + callback: TEvents[TEvent], + ): this + once( + event: TEvent, + callback: TEvents[TEvent], + ): this + removeListener( + event: TEvent, + callback: TEvents[TEvent], + ): this + off( + event: TEvent, + callback: TEvents[TEvent], + ): this + removeAllListeners(event?: keyof TEvents): this + + emit( + event: TEvent, + ...args: Parameters + ): boolean +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export class TypedEventEmitter< + TEvents extends Record, +> {} + +// Make TypedEventEmitter inherit from EventEmitter without actually extending +applyMixin(TypedEventEmitter, EventEmitter) diff --git a/lib/client.js b/src/lib/client.ts similarity index 65% rename from lib/client.js rename to src/lib/client.ts index 361b4b9cf..988b04517 100644 --- a/lib/client.js +++ b/src/lib/client.ts @@ -1,27 +1,53 @@ /** * Module dependencies */ -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') -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') +import TopicAliasRecv from './topic-alias-recv' +import mqttPacket, { + IAuthPacket, + IConnackPacket, + IDisconnectPacket, + IPublishPacket, + ISubscribePacket, + ISubscription, + IUnsubscribePacket, + Packet, + QoS, + UserProperties, + ISubackPacket, + IConnectPacket, +} from 'mqtt-packet' +import DefaultMessageIdProvider, { + IMessageIdProvider, +} from './default-message-id-provider' +import { DuplexOptions, Writable } from 'readable-stream' +import reInterval from 'reinterval' +import clone from 'rfdc/default' +import * as validations from './validations' +import _debug from 'debug' +import Store from './store' +import handlePacket from './handlers' +import { ClientOptions } from 'ws' +import { ClientRequestArgs } from 'http' +import { + DoneCallback, + ErrorWithReasonCode, + GenericCallback, + IStream, + StreamBuilder, + VoidCallback, +} from './shared' +import TopicAliasSend from './topic-alias-send' +import { TypedEventEmitter } from './TypedEmitter' const nextTick = process ? process.nextTick - : (callback) => { + : (callback: () => void) => { setTimeout(callback, 0) } const setImmediate = global.setImmediate || - ((...args) => { + ((...args: any[]) => { const callback = args.shift() nextTick(() => { callback(...args) @@ -48,6 +74,389 @@ const socketErrors = [ 'ETIMEDOUT', ] +export type MqttProtocol = + | 'wss' + | 'ws' + | 'mqtt' + | 'mqtts' + | 'tcp' + | 'ssl' + | 'wx' + | 'wxs' + | 'ali' + | 'alis' + +export type StorePutCallback = () => void + +export interface ISecureClientOptions { + /** + * optional private keys in PEM format + */ + key?: string | string[] | Buffer | Buffer[] | any[] + keyPath?: string + /** + * optional cert chains in PEM format + */ + cert?: string | string[] | Buffer | Buffer[] + certPath?: string + /** + * Optionally override the trusted CA certificates in PEM format + */ + ca?: string | string[] | Buffer | Buffer[] + caPaths?: string | string[] + + rejectUnauthorized?: boolean + /** + * optional alpn's + */ + ALPNProtocols?: string[] | Buffer[] | Uint8Array[] | Buffer | Uint8Array +} + +export type AckHandler = ( + topic: string, + message: Buffer, + packet: any, + cb: (error?: Error, code?: number) => void, +) => void + +export interface IClientOptions extends ISecureClientOptions { + /** Encoding to use. Example 'binary' */ + encoding?: BufferEncoding + /** Set browser buffer size. Default to 512KB */ + browserBufferSize?: number + /** used in ws protocol to set `objectMode` */ + binary?: boolean + /** Used on ali protocol */ + my?: any + /** Manually call `connect` after creating client instance */ + manualConnect?: any + /** Custom auth packet properties */ + authPacket?: Partial + /** Disable/Enable writeToStream.cacheNumbers */ + writeCache?: boolean + + servername?: string + defaultProtocol?: MqttProtocol + /** Support clientId passed in the query string of the url */ + query?: Record + /** Auth string in the format : */ + auth?: string + /** Custom ack handler */ + customHandleAcks?: AckHandler + /** Broker port */ + port?: number + /** Broker host. Does NOT include port */ + host?: string + /** @deprecated use `host instead */ + hostname?: string + /** Websocket `path` added as suffix */ + path?: string + /** The `MqttProtocol` to use */ + protocol?: MqttProtocol + + /** Websocket options */ + wsOptions?: ClientOptions | ClientRequestArgs | DuplexOptions + /** + * 10 seconds, set to 0 to disable + */ + keepalive?: number + /** + * 'mqttjs_' + Math.random().toString(16).substr(2, 8) + */ + clientId?: string + /** + * 'MQTT' + */ + protocolId?: string + /** + * 4 + */ + protocolVersion?: number + /** + * true, set to false to receive QoS 1 and 2 messages while offline + */ + clean?: boolean + /** + * 1000 milliseconds, interval between two reconnections + */ + reconnectPeriod?: number + /** + * 30 * 1000 milliseconds, time to wait before a CONNACK is received + */ + connectTimeout?: number + /** + * the username required by your broker, if any + */ + username?: string + /** + * the password required by your broker, if any + */ + password?: Buffer | string + /** + * a Store for the incoming packets + */ + incomingStore?: Store + /** + * a Store for the outgoing packets + */ + outgoingStore?: Store + + /** Enable/Disable queue for QoS 0 packets */ + queueQoSZero?: boolean + + /** Custom log function, default uses `debug` */ + log?: (...args: any[]) => void + + autoUseTopicAlias?: boolean + autoAssignTopicAlias?: boolean + + reschedulePings?: boolean + servers?: Array<{ + host: string + port: number + protocol?: + | 'wss' + | 'ws' + | 'mqtt' + | 'mqtts' + | 'tcp' + | 'ssl' + | 'wx' + | 'wxs' + }> + /** + * true, set to false to disable re-subscribe functionality + */ + resubscribe?: boolean + /** + * a message that will sent by the broker automatically when the client disconnect badly. + */ + will?: { + /** + * the topic to publish + */ + topic: string + /** + * the message to publish + */ + payload: Buffer | string + /** + * the QoS + */ + qos: QoS + /** + * the retain flag + */ + retain: boolean + /* + * properies object of will + * */ + properties?: { + willDelayInterval?: number + payloadFormatIndicator?: boolean + messageExpiryInterval?: number + contentType?: string + 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?: { + sessionExpiryInterval?: number + receiveMaximum?: number + maximumPacketSize?: number + topicAliasMaximum?: number + requestResponseInformation?: boolean + requestProblemInformation?: boolean + userProperties?: UserProperties + authenticationMethod?: string + authenticationData?: Buffer + } + messageIdProvider?: IMessageIdProvider + + browserBufferTimeout?: number + + objectMode?: boolean +} + +export interface IClientPublishOptions { + /** + * the QoS + */ + qos?: QoS + /** + * the retain flag + */ + retain?: boolean + /** + * whether or not mark a message as duplicate + */ + dup?: boolean + /* + * MQTT 5.0 properties object + */ + properties?: { + payloadFormatIndicator?: boolean + messageExpiryInterval?: number + topicAlias?: number + responseTopic?: string + correlationData?: Buffer + userProperties?: UserProperties + subscriptionIdentifier?: number + contentType?: string + } + /** + * callback called when message is put into `outgoingStore` + */ + cbStorePut?: StorePutCallback +} +export interface IClientSubscribeOptions { + /** + * the QoS + */ + qos: QoS + /* + * no local flag + * */ + nl?: boolean + /* + * Retain As Published flag + * */ + rap?: boolean + /* + * Retain Handling option + * */ + rh?: number + /* + * MQTT 5.0 properies object of subscribe + * */ + properties?: { + subscriptionIdentifier?: number + userProperties?: UserProperties + } +} +export interface IClientSubscribeProperties { + /* + * MQTT 5.0 properies object of subscribe + * */ + properties?: { + subscriptionIdentifier?: number + userProperties?: UserProperties + } +} +export interface IClientReconnectOptions { + /** + * a Store for the incoming packets + */ + incomingStore?: Store + /** + * a Store for the outgoing packets + */ + outgoingStore?: Store +} + +export interface ISubscriptionGrant { + /** + * is a subscribed to topic + */ + topic: string + /** + * is the granted qos level on it, may return 128 on error + */ + qos: QoS | number + /* + * no local flag + * */ + nl?: boolean + /* + * Retain As Published flag + * */ + rap?: boolean + /* + * Retain Handling option + * */ + rh?: number +} +export interface ISubscriptionRequest extends IClientSubscribeProperties { + /** + * is a subscribed to topic + */ + topic: string + /** + * is the granted qos level on it + */ + qos: QoS + /* + * no local flag + * */ + nl?: boolean + /* + * Retain As Published flag + * */ + rap?: boolean + /* + * Retain Handling option + * */ + rh?: number +} + +export interface ISubscriptioOptions extends IClientSubscribeProperties { + qos: QoS + nl?: boolean + rap?: boolean + rh?: number +} + +export type ISubscriptionMap = { + /** + * object which has topic names as object keys and as value the options, like {'test1': {qos: 0}, 'test2': {qos: 2}}. + */ + [topic: string]: ISubscriptioOptions +} & { + resubscribe?: boolean +} + +export type OnConnectCallback = (packet: IConnackPacket) => void +export type OnDisconnectCallback = (packet: IDisconnectPacket) => void +export type ClientSubscribeCallback = ( + err: Error | null, + granted: ISubscriptionGrant[], +) => void +export type OnMessageCallback = ( + topic: string, + payload: Buffer, + packet: IPublishPacket, +) => void +export type OnPacketCallback = (packet: Packet) => void +export type OnCloseCallback = () => void +export type OnErrorCallback = (error: Error | ErrorWithReasonCode) => void +export type PacketCallback = (error?: Error, packet?: Packet) => any +export type CloseCallback = (error?: Error) => void + +export interface MqttClientEventCallbacks { + connect: OnConnectCallback + message: OnMessageCallback + packetsend: OnPacketCallback + packetreceive: OnPacketCallback + disconnect: OnDisconnectCallback + error: OnErrorCallback + close: OnCloseCallback + end: VoidCallback + reconnect: VoidCallback + offline: VoidCallback + outgoingEmpty: VoidCallback +} + /** * MqttClient constructor * @@ -55,20 +464,92 @@ const socketErrors = [ * @param {Object} [options] - connection options * (see Connection#connect) */ -class MqttClient extends EventEmitter { - static defaultId() { +export default class MqttClient extends TypedEventEmitter { + /** Public fields */ + + /** It's true when client is connected to broker */ + public connected: boolean + + public disconnecting: boolean + + public disconnected: boolean + + public reconnecting: boolean + + public incomingStore: Store + + public outgoingStore: Store + + public options: IClientOptions + + public queueQoSZero: boolean + + public _reconnectCount: number + + public log: (...args: any[]) => void + + public messageIdProvider: IMessageIdProvider + + public pingResp: boolean + + public outgoing: Record< + number, + { volatile: boolean; cb: (err: Error, packet?: Packet) => void } + > + + public messageIdToTopic: Record + + public noop: (error?: any) => void + + public pingTimer: any + + public stream: IStream + + public queue: { packet: Packet; cb: PacketCallback }[] + + /* Private fields */ + + /** Function used to build the stream */ + private streamBuilder: StreamBuilder + + private _resubscribeTopics: ISubscriptionMap + + private connackTimer: NodeJS.Timeout + + private reconnectTimer: NodeJS.Timeout + + private _storeProcessing: boolean + + /** keep a reference of packets that have been successfully processed from outgoing store */ + private _packetIdsDuringStoreProcessing: Record + + private _storeProcessingQueue: { + invoke: () => any + cbStorePut?: DoneCallback + callback: GenericCallback + }[] + + private _firstConnection: boolean + + private topicAliasRecv: TopicAliasRecv + + private topicAliasSend: TopicAliasSend + + private _deferredReconnect: () => void + + private connackPacket: IConnackPacket + + public static defaultId() { return `mqttjs_${Math.random().toString(16).substr(2, 8)}` } - constructor(streamBuilder, options) { + constructor(streamBuilder: StreamBuilder, options: IClientOptions) { super() - let k - this.options = options || {} // Defaults - for (k in defaultConnectOptions) { + for (const k in defaultConnectOptions) { if (typeof this.options[k] === 'undefined') { this.options[k] = defaultConnectOptions[k] } else { @@ -76,7 +557,7 @@ class MqttClient extends EventEmitter { } } - this.log = this.options.log || debug + this.log = this.options.log || _debug('mqttjs:client') this.noop = this._noop.bind(this) this.log('MqttClient :: options.protocol', options.protocol) @@ -112,7 +593,7 @@ class MqttClient extends EventEmitter { options.protocolVersion === 5 && options.customHandleAcks ? options.customHandleAcks : (...args) => { - args[3](0) + args[3](null, 0) } // Disable pre-generated write cache if requested. Will allocate buffers on-the-fly instead. WARNING: This can affect write performance @@ -247,10 +728,47 @@ class MqttClient extends EventEmitter { } } + /** + * @param packet the packet received by the broker + * @return the auth packet to be returned to the broker + * @api public + */ + public handleAuth(packet: IAuthPacket, callback: PacketCallback) { + 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 + */ + public handleMessage(packet: IPublishPacket, callback: DoneCallback) { + callback() + } + + /** + * _nextId + * @return unsigned int + */ + private _nextId() { + return this.messageIdProvider.allocate() + } + + /** + * getLastMessageId + * @return unsigned int + */ + public getLastMessageId() { + return this.messageIdProvider.getLastAllocated() + } + /** * Setup the event handlers in the inner stream, sends `connect` and `auth` packets */ - connect() { + public connect() { const writable = new Writable() const parser = mqttPacket.parser(this.options) let completeParse = null @@ -322,14 +840,14 @@ class MqttClient extends EventEmitter { // Echo stream close this.stream.on('close', () => { this.log('(%s)stream :: on close', this.options.clientId) - this._flushVolatile(this.outgoing) + this._flushVolatile() 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) + const connectPacket: IConnectPacket = Object.create(this.options) connectPacket.cmd = 'connect' if (this.topicAliasRecv) { if (!connectPacket.properties) { @@ -365,7 +883,7 @@ class MqttClient extends EventEmitter { this.options.authPacket && typeof this.options.authPacket === 'object' ) { - const authPacket = { + const authPacket: IAuthPacket = { cmd: 'auth', reasonCode: 0, ...this.options.authPacket, @@ -388,78 +906,6 @@ class MqttClient extends EventEmitter { 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 * @@ -480,49 +926,72 @@ class MqttClient extends EventEmitter { * client.publish('topic', 'message', {qos: 1, retain: true, dup: true}); * @example client.publish('topic', 'message', console.log); */ - publish(topic, message, opts, callback) { + public publish(topic: string, message: string | Buffer): MqttClient + public publish( + topic: string, + message: string | Buffer, + callback?: DoneCallback, + ): MqttClient + public publish( + topic: string, + message: string | Buffer, + opts?: IClientPublishOptions, + callback?: DoneCallback, + ): MqttClient + public publish( + topic: string, + message: string | Buffer, + opts?: IClientPublishOptions | DoneCallback, + callback?: DoneCallback, + ): MqttClient { 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 + callback = opts as DoneCallback + opts = {} as IClientPublishOptions } // default opts - const defaultOpts = { qos: 0, retain: false, dup: false } + const defaultOpts: IClientPublishOptions = { + qos: 0, + retain: false, + dup: false, + } opts = { ...defaultOpts, ...opts } + const { qos, retain, dup, properties, cbStorePut } = opts + if (this._checkDisconnecting(callback)) { return this } const publishProc = () => { let messageId = 0 - if (opts.qos === 1 || opts.qos === 2) { + if (qos === 1 || qos === 2) { messageId = this._nextId() if (messageId === null) { this.log('No messageId left') return false } } - const packet = { + const packet: IPublishPacket = { cmd: 'publish', topic, payload: message, - qos: opts.qos, - retain: opts.retain, + qos, + retain, messageId, - dup: opts.dup, + dup, } if (options.protocolVersion === 5) { - packet.properties = opts.properties + packet.properties = properties } - this.log('publish :: qos', opts.qos) - switch (opts.qos) { + this.log('publish :: qos', qos) + switch (qos) { case 1: case 2: // Add to callbacks @@ -531,11 +1000,11 @@ class MqttClient extends EventEmitter { cb: callback || this.noop, } this.log('MqttClient:publish: packet cmd: %s', packet.cmd) - this._sendPacket(packet, undefined, opts.cbStorePut) + this._sendPacket(packet, undefined, cbStorePut) break default: this.log('MqttClient:publish: packet cmd: %s', packet.cmd) - this._sendPacket(packet, callback, opts.cbStorePut) + this._sendPacket(packet, callback, cbStorePut) break } return true @@ -571,26 +1040,56 @@ class MqttClient extends EventEmitter { * @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() + public subscribe( + topicObject: string | string[] | ISubscriptionMap, + ): MqttClient + public subscribe( + topicObject: string | string[] | ISubscriptionMap, + callback?: ClientSubscribeCallback, + ): MqttClient + public subscribe( + topicObject: string | string[] | ISubscriptionMap, + opts?: IClientSubscribeOptions | IClientSubscribeProperties, + ): MqttClient + public subscribe( + topicObject: string | string[] | ISubscriptionMap, + opts?: IClientSubscribeOptions | IClientSubscribeProperties, + callback?: ClientSubscribeCallback, + ): MqttClient + public subscribe( + topicObject: string | string[] | ISubscriptionMap, + opts?: + | IClientSubscribeOptions + | IClientSubscribeProperties + | ClientSubscribeCallback, + callback?: ClientSubscribeCallback, + ): MqttClient { const version = this.options.protocolVersion - delete obj.resubscribe - - if (typeof obj === 'string') { - obj = [obj] + if (typeof opts === 'function') { + callback = opts } - if (typeof callback !== 'function') { - opts = callback - callback = this.noop + callback = callback || this.noop + + // force re-subscribe on reconnect. This is only true + // when provided `topicObject` is `this._resubscribeTopics` + let resubscribe = false + let topicsList = [] + + if (typeof topicObject === 'string') { + topicObject = [topicObject] + topicsList = topicObject + } else if (Array.isArray(topicObject)) { + topicsList = topicObject + } else if (typeof topicObject === 'object') { + resubscribe = topicObject.resubscribe + delete topicObject.resubscribe + topicsList = Object.keys(topicObject) } - const invalidTopic = validations.validateTopics(obj) + // validate topics + const invalidTopic = validations.validateTopics(topicsList) if (invalidTopic !== null) { setImmediate(callback, new Error(`Invalid topic ${invalidTopic}`)) return this @@ -601,19 +1100,27 @@ class MqttClient extends EventEmitter { return this } - const defaultOpts = { + const defaultOpts: Partial = { qos: 0, } + if (version === 5) { defaultOpts.nl = false defaultOpts.rap = false defaultOpts.rh = 0 } - opts = { ...defaultOpts, ...opts } + opts = { ...defaultOpts, ...opts } as IClientSubscribeOptions + + const properties = opts.properties + + const subs: ISubscriptionRequest[] = [] - const parseSub = (topic, subOptions) => { + const parseSub = ( + topic: string, + subOptions?: IClientSubscribeOptions, + ) => { // subOptions is defined only when providing a subs map, use opts otherwise - subOptions = subOptions || opts + subOptions = (subOptions || opts) as IClientSubscribeOptions if ( !Object.prototype.hasOwnProperty.call( this._resubscribeTopics, @@ -622,16 +1129,17 @@ class MqttClient extends EventEmitter { this._resubscribeTopics[topic].qos < subOptions.qos || resubscribe ) { - const currentOpts = { - topic, - qos: subOptions.qos, - } + const currentOpts: ISubscription & IClientSubscribeProperties = + { + 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 + currentOpts.properties = properties } this.log( 'subscribe: pushing topic `%s` and qos `%s` to subs list', @@ -642,17 +1150,21 @@ class MqttClient extends EventEmitter { } } - if (Array.isArray(obj)) { + if (Array.isArray(topicObject)) { // array of topics - obj.forEach((topic) => { + topicObject.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]) + Object.keys(topicObject).forEach((topic) => { + this.log( + 'subscribe: object topic %s, %o', + topic, + topicObject[topic], + ) + parseSub(topic, topicObject[topic]) }) } @@ -668,17 +1180,17 @@ class MqttClient extends EventEmitter { return false } - const packet = { + const packet: ISubscribePacket = { cmd: 'subscribe', subscriptions: subs, - qos: 1, - retain: false, - dup: false, + // qos: 1, + // retain: false, + // dup: false, messageId, } - if (opts.properties) { - packet.properties = opts.properties + if (properties) { + packet.properties = properties } // subscriptions to resubscribe to in case of disconnect @@ -687,7 +1199,7 @@ class MqttClient extends EventEmitter { const topics = [] subs.forEach((sub) => { if (this.options.reconnectPeriod > 0) { - const topic = { qos: sub.qos } + const topic: ISubscriptioOptions = { qos: sub.qos } if (version === 5) { topic.nl = sub.nl || false topic.rap = sub.rap || false @@ -703,11 +1215,11 @@ class MqttClient extends EventEmitter { this.outgoing[packet.messageId] = { volatile: true, - cb(err, packet2) { + cb(err, packet2: ISubackPacket) { if (!err) { const { granted } = packet2 for (let i = 0; i < granted.length; i += 1) { - subs[i].qos = granted[i] + subs[i].qos = granted[i] as QoS } } @@ -745,19 +1257,35 @@ class MqttClient extends EventEmitter { * @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() + public unsubscribe(topic: string | string[]): MqttClient + public unsubscribe( + topic: string | string[], + opts?: IClientSubscribeOptions, + ): MqttClient + public unsubscribe( + topic: string | string[], + callback?: PacketCallback, + ): MqttClient + public unsubscribe( + topic: string | string[], + opts?: IClientSubscribeOptions, + callback?: PacketCallback, + ): MqttClient + public unsubscribe( + topic: string | string[], + opts?: IClientSubscribeOptions | PacketCallback, + callback?: PacketCallback, + ): MqttClient { if (typeof topic === 'string') { topic = [topic] } - if (typeof callback !== 'function') { - opts = callback - callback = this.noop + if (typeof opts === 'function') { + callback = opts } + callback = callback || this.noop + const invalidTopic = validations.validateTopics(topic) if (invalidTopic !== null) { setImmediate(callback, new Error(`Invalid topic ${invalidTopic}`)) @@ -774,10 +1302,11 @@ class MqttClient extends EventEmitter { this.log('No messageId left') return false } - const packet = { + const packet: IUnsubscribePacket = { cmd: 'unsubscribe', - qos: 1, + // qos: 1, messageId, + unsubscriptions: [], } if (typeof topic === 'string') { @@ -831,12 +1360,25 @@ class MqttClient extends EventEmitter { * * @api public */ - end(force, opts, cb) { + public end(cb?: DoneCallback): MqttClient + public end(force?: boolean): MqttClient + public end(opts?: Partial, cb?: DoneCallback): MqttClient + public end(force?: boolean, cb?: DoneCallback): MqttClient + public end( + force?: boolean, + opts?: Partial, + cb?: DoneCallback, + ): MqttClient + public end( + force?: boolean | Partial | DoneCallback, + opts?: Partial | DoneCallback, + cb?: DoneCallback, + ): MqttClient { this.log('end :: (%s)', this.options.clientId) if (force == null || typeof force !== 'boolean') { - cb = opts || this.noop - opts = force + cb = (opts || this.noop) as DoneCallback + opts = force as Partial force = false if (typeof opts !== 'object') { cb = opts @@ -886,7 +1428,7 @@ class MqttClient extends EventEmitter { force, ) this._cleanUp( - force, + force, () => { this.log( 'end :: finish :: calling process.nextTick on closeStores', @@ -935,7 +1477,7 @@ class MqttClient extends EventEmitter { * * @example client.removeOutgoingMessage(client.getLastAllocated()); */ - removeOutgoingMessage(messageId) { + public removeOutgoingMessage(messageId: number): MqttClient { if (this.outgoing[messageId]) { const { cb } = this.outgoing[messageId] this._removeOutgoingAndStoreMessage(messageId, () => { @@ -956,7 +1498,9 @@ class MqttClient extends EventEmitter { * * @api public */ - reconnect(opts) { + public reconnect( + opts?: Pick, + ): MqttClient { this.log('client reconnect') const f = () => { if (opts) { @@ -982,11 +1526,96 @@ class MqttClient extends EventEmitter { return this } + /** + * PRIVATE METHODS + * ===================== + * */ + + /** + * Flush all outgoing messages marked as `volatile` in `outgoing` queue. Volatile messages + * typically are subscription and unsubscription requests. + */ + private _flushVolatile() { + if (this.outgoing) { + this.log( + '_flushVolatile :: deleting volatile messages from the queue and setting their callbacks as error function', + ) + Object.keys(this.outgoing).forEach((messageId) => { + if ( + this.outgoing[messageId].volatile && + typeof this.outgoing[messageId].cb === 'function' + ) { + this.outgoing[messageId].cb(new Error('Connection closed')) + delete this.outgoing[messageId] + } + }) + } + } + + /** + * Flush all outgoing messages + */ + private _flush() { + if (this.outgoing) { + this.log('_flush: queue exists? %b', !!this.outgoing) + Object.keys(this.outgoing).forEach((messageId) => { + if (typeof this.outgoing[messageId].cb === 'function') { + this.outgoing[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 this.outgoing[messageId] + } + }) + } + } + + private _removeTopicAliasAndRecoverTopicName(packet: IPublishPacket) { + let alias: number | undefined + + 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 + } + } + + private _checkDisconnecting(callback: GenericCallback) { + if (this.disconnecting) { + if (callback && callback !== this.noop) { + callback(new Error('client disconnecting')) + } else { + this.emit('error', new Error('client disconnecting')) + } + } + return this.disconnecting + } + /** * _reconnect - implement reconnection - * @api privateish + * @api private */ - _reconnect() { + private _reconnect() { this.log('_reconnect: emitting reconnect to client') this.emit('reconnect') if (this.connected) { @@ -1003,7 +1632,7 @@ class MqttClient extends EventEmitter { /** * _setupReconnect - setup reconnect timer */ - _setupReconnect() { + private _setupReconnect() { if ( !this.disconnecting && !this.reconnectTimer && @@ -1031,7 +1660,7 @@ class MqttClient extends EventEmitter { /** * _clearReconnect - clear the reconnect timer */ - _clearReconnect() { + private _clearReconnect() { this.log('_clearReconnect : clearing reconnect timer') if (this.reconnectTimer) { clearInterval(this.reconnectTimer) @@ -1043,7 +1672,7 @@ class MqttClient extends EventEmitter { * _cleanUp - clean up on connection end * @api private */ - _cleanUp(forced, done, opts = {}) { + private _cleanUp(forced: boolean, done?: DoneCallback, opts = {}) { if (done) { this.log('_cleanUp :: done callback provided for on stream close') this.stream.on('close', done) @@ -1052,7 +1681,7 @@ class MqttClient extends EventEmitter { this.log('_cleanUp :: forced? %s', forced) if (forced) { if (this.options.reconnectPeriod === 0 && this.options.clean) { - this._flush(this.outgoing) + this._flush() } this.log( '_cleanUp :: (%s) :: destroying stream', @@ -1060,7 +1689,7 @@ class MqttClient extends EventEmitter { ) this.stream.destroy() } else { - const packet = { cmd: 'disconnect', ...opts } + const packet: IDisconnectPacket = { cmd: 'disconnect', ...opts } this.log( '_cleanUp :: (%s) :: call _sendPacket with disconnect packet', this.options.clientId, @@ -1107,19 +1736,25 @@ class MqttClient extends EventEmitter { } } - _storeAndSend(packet, cb, cbStorePut) { + private _storeAndSend( + packet: Packet, + cb: DoneCallback, + cbStorePut: DoneCallback, + ) { this.log( 'storeAndSend :: store packet with cmd %s to outgoingStore', packet.cmd, ) let storePacket = packet - let err + let err: Error | undefined 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) + err = this._removeTopicAliasAndRecoverTopicName( + storePacket as IPublishPacket, + ) if (err) { return cb && cb(err) } @@ -1133,10 +1768,10 @@ class MqttClient extends EventEmitter { }) } - _applyTopicAlias(packet) { + private _applyTopicAlias(packet: Packet) { if (this.options.protocolVersion === 5) { if (packet.cmd === 'publish') { - let alias + let alias: number if (packet.properties) { alias = packet.properties.topicAlias } @@ -1216,12 +1851,12 @@ class MqttClient extends EventEmitter { } } - _noop(err) { + private _noop(err?: Error) { this.log('noop ::', err) } /** Writes the packet to stream and emits events */ - _writePacket(packet, cb) { + private _writePacket(packet: Packet, cb?: DoneCallback) { this.log('_writePacket :: packet: %O', packet) this.log('_writePacket :: emitting `packetsend`') @@ -1256,7 +1891,12 @@ class MqttClient extends EventEmitter { * @param {Boolean} noStore - send without put to the store * @api private */ - _sendPacket(packet, cb, cbStorePut, noStore) { + private _sendPacket( + packet: Packet, + cb?: DoneCallback, + cbStorePut?: DoneCallback, + noStore?: boolean, + ) { this.log('_sendPacket :: (%s) :: start', this.options.clientId) cbStorePut = cbStorePut || this.noop cb = cb || this.noop @@ -1270,7 +1910,7 @@ class MqttClient extends EventEmitter { 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) + this._writePacket(packet, cb) return } @@ -1328,7 +1968,11 @@ class MqttClient extends EventEmitter { * @param {Function} cbStorePut - called when message is put into outgoingStore * @api private */ - _storePacket(packet, cb, cbStorePut) { + private _storePacket( + packet: Packet, + cb: DoneCallback, + cbStorePut: DoneCallback, + ) { this.log('_storePacket :: packet: %o', packet) this.log('_storePacket :: cb? %s', !!cb) cbStorePut = cbStorePut || this.noop @@ -1339,18 +1983,19 @@ class MqttClient extends EventEmitter { // 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) + const err = this._removeTopicAliasAndRecoverTopicName( + storePacket as IPublishPacket, + ) if (err) { return cb && cb(err) } } + + const qos = (storePacket as IPublishPacket).qos || 0 // 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' - ) { + if ((qos === 0 && this.queueQoSZero) || storePacket.cmd !== 'publish') { this.queue.push({ packet: storePacket, cb }) - } else if (storePacket.qos > 0) { + } else if (qos > 0) { cb = this.outgoing[storePacket.messageId] ? this.outgoing[storePacket.messageId].cb : null @@ -1370,7 +2015,7 @@ class MqttClient extends EventEmitter { * * @api private */ - _setupPingTimer() { + private _setupPingTimer() { this.log( '_setupPingTimer :: keepalive %d (seconds)', this.options.keepalive, @@ -1389,7 +2034,7 @@ class MqttClient extends EventEmitter { * * @api private */ - _shiftPingInterval() { + private _shiftPingInterval() { if ( this.pingTimer && this.options.keepalive && @@ -1404,7 +2049,7 @@ class MqttClient extends EventEmitter { * * @api private */ - _checkPing() { + private _checkPing() { this.log('_checkPing :: checking ping...') if (this.pingResp) { this.log( @@ -1419,48 +2064,11 @@ class MqttClient extends EventEmitter { } } - /** - * @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() { + private _resubscribe() { this.log('_resubscribe') const _resubscribeTopicsKeys = Object.keys(this._resubscribeTopics) if ( @@ -1478,7 +2086,7 @@ class MqttClient extends EventEmitter { topicI < _resubscribeTopicsKeys.length; topicI++ ) { - const resubscribeTopic = {} + const resubscribeTopic: ISubscriptionMap = {} resubscribeTopic[_resubscribeTopicsKeys[topicI]] = this._resubscribeTopics[ _resubscribeTopicsKeys[topicI] @@ -1507,7 +2115,7 @@ class MqttClient extends EventEmitter { * * @api private */ - _onConnect(packet) { + private _onConnect(packet: IConnackPacket) { if (this.disconnected) { this.emit('connect', packet) return @@ -1519,9 +2127,11 @@ class MqttClient extends EventEmitter { this.connected = true + /** check if there are packets in outgoing store and stream them */ const startStreamProcess = () => { let outStore = this.outgoingStore.createStream() + /** destroy the outgoing store stream */ const remove = () => { outStore.destroy() outStore = null @@ -1529,6 +2139,7 @@ class MqttClient extends EventEmitter { clearStoreProcessing() } + /** stop store processing and clear packets id processed */ const clearStoreProcessing = () => { this._storeProcessing = false this._packetIdsDuringStoreProcessing = {} @@ -1542,6 +2153,7 @@ class MqttClient extends EventEmitter { this.emit('error', err) }) + /** Read next packet in outgoing store and send it */ const storeDeliver = () => { // edge case, we wrapped this twice if (!outStore) { @@ -1550,7 +2162,7 @@ class MqttClient extends EventEmitter { const packet2 = outStore.read(1) - let cb + let cb: PacketCallback if (!packet2) { // read when data is available in the future @@ -1582,9 +2194,8 @@ class MqttClient extends EventEmitter { storeDeliver() }, } - this._packetIdsDuringStoreProcessing[ - packet2.messageId - ] = true + this._packetIdsDuringStoreProcessing[packet2.messageId] = + true if (this.messageIdProvider.register(packet2.messageId)) { this._sendPacket(packet2, undefined, undefined, true) } else { @@ -1621,7 +2232,7 @@ class MqttClient extends EventEmitter { startStreamProcess() } - _invokeStoreProcessingQueue() { + private _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) { @@ -1634,13 +2245,13 @@ class MqttClient extends EventEmitter { return false } - _invokeAllStoreProcessingQueue() { + private _invokeAllStoreProcessingQueue() { while (this._invokeStoreProcessingQueue()) { /* empty */ } } - _flushStoreProcessingQueue() { + private _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')) @@ -1654,15 +2265,15 @@ class MqttClient extends EventEmitter { * @param {Function} cb - called when the message removed * @api private */ - _removeOutgoingAndStoreMessage(messageId, cb) { - const self = this + private _removeOutgoingAndStoreMessage( + messageId: number, + cb: PacketCallback, + ) { delete this.outgoing[messageId] - self.outgoingStore.del({ messageId }, (err, packet) => { + this.outgoingStore.del({ messageId }, (err, packet) => { cb(err, packet) - self.messageIdProvider.deallocate(messageId) - self._invokeStoreProcessingQueue() + this.messageIdProvider.deallocate(messageId) + this._invokeStoreProcessingQueue() }) } } - -module.exports = MqttClient diff --git a/lib/connect/ali.js b/src/lib/connect/ali.ts similarity index 80% rename from lib/connect/ali.js rename to src/lib/connect/ali.ts index fb6dc3483..1f7360ab5 100644 --- a/lib/connect/ali.js +++ b/src/lib/connect/ali.ts @@ -1,10 +1,12 @@ -const { Buffer } = require('buffer') -const { Transform } = require('readable-stream') -const duplexify = require('duplexify') - -let my -let proxy -let stream +import { Buffer } from 'buffer' +import { Duplex, Transform } from 'readable-stream' +import duplexify, { Duplexify } from 'duplexify' +import { StreamBuilder } from '../shared' +import MqttClient, { IClientOptions } from '../client' + +let my: any +let proxy: Transform +let stream: Duplexify let isInitialized = false function buildProxy() { @@ -31,7 +33,7 @@ function buildProxy() { return _proxy } -function setDefaultOpts(opts) { +function setDefaultOpts(opts: IClientOptions) { if (!opts.hostname) { opts.hostname = 'localhost' } @@ -44,7 +46,7 @@ function setDefaultOpts(opts) { } } -function buildUrl(opts, client) { +function buildUrl(opts: IClientOptions, client: MqttClient) { const protocol = opts.protocol === 'alis' ? 'wss' : 'ws' let url = `${protocol}://${opts.hostname}${opts.path}` if (opts.port && opts.port !== 80 && opts.port !== 443) { @@ -94,7 +96,7 @@ function bindEventHandler() { }) } -function buildStream(client, opts) { +const buildStream: StreamBuilder = (client, opts) => { opts.hostname = opts.hostname || opts.host if (!opts.hostname) { @@ -120,7 +122,7 @@ function buildStream(client, opts) { bindEventHandler() - return stream + return stream as unknown as Duplex } -module.exports = buildStream +export default buildStream diff --git a/lib/connect/index.js b/src/lib/connect/index.ts similarity index 63% rename from lib/connect/index.js rename to src/lib/connect/index.ts index e50b31122..2ba862469 100644 --- a/lib/connect/index.js +++ b/src/lib/connect/index.ts @@ -1,37 +1,41 @@ -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 debug = require('debug')('mqttjs') +/* eslint-disable @typescript-eslint/no-var-requires */ +import url from 'url' +import MqttClient, { IClientOptions, MqttProtocol } from '../client' +import IS_BROWSER from '../is-browser' +import Store from '../store' +import DefaultMessageIdProvider from '../default-message-id-provider' +import UniqueMessageIdProvider from '../unique-message-id-provider' +import _debug from 'debug' +import { StreamBuilder } from '../shared' -const protocols = {} +const debug = _debug('mqttjs') + +const protocols: Record = {} 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').default + protocols.tcp = require('./tcp').default + protocols.ssl = require('./tls').default + protocols.tls = protocols.ssl + protocols.mqtts = require('./tls').default } else { - protocols.wx = require('./wx') - protocols.wxs = require('./wx') + protocols.wx = require('./wx').default + protocols.wxs = require('./wx').default - protocols.ali = require('./ali') - protocols.alis = require('./ali') + protocols.ali = require('./ali').default + protocols.alis = require('./ali').default } -protocols.ws = require('./ws') -protocols.wss = require('./ws') +protocols.ws = require('./ws').default +protocols.wss = require('./ws').default /** * Parse the auth attribute and merge username and password in the options object. * * @param {Object} [opts] option object */ -function parseAuthOptions(opts) { - let matches +function parseAuthOptions(opts: IClientOptions) { + let matches: RegExpMatchArray | null if (opts.auth) { matches = opts.auth.match(/^(.+):(.+)$/) if (matches) { @@ -45,33 +49,38 @@ function parseAuthOptions(opts) { /** * connect - connect to an MQTT broker. - * - * @param {String} [brokerUrl] - url of the broker, optional - * @param {Object} opts - see MqttClient#constructor */ -function connect(brokerUrl, opts) { +function connect(brokerUrl: string): MqttClient +function connect(opts: IClientOptions): MqttClient +function connect(brokerUrl: string, opts?: IClientOptions): MqttClient +function connect( + brokerUrl: string | IClientOptions, + opts?: IClientOptions, +): MqttClient { debug('connecting to an MQTT broker...') if (typeof brokerUrl === 'object' && !opts) { opts = brokerUrl - brokerUrl = null + brokerUrl = '' } opts = opts || {} - if (brokerUrl) { + if (brokerUrl && typeof brokerUrl === 'string') { // eslint-disable-next-line - const parsed = url.parse(brokerUrl, true) + const parsed = url.parse(brokerUrl, true) if (parsed.port != null) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore parsed.port = Number(parsed.port) } - opts = { ...parsed, ...opts } + opts = { ...parsed, ...opts } as IClientOptions if (opts.protocol === null) { throw new Error('Missing protocol') } - opts.protocol = opts.protocol.replace(/:$/, '') + opts.protocol = opts.protocol.replace(/:$/, '') as MqttProtocol } // merge in the auth options if supplied @@ -127,7 +136,7 @@ function connect(brokerUrl, opts) { return false } return typeof protocols[key] === 'function' - })[0] + })[0] as MqttProtocol } if (opts.clean === false && !opts.clientId) { @@ -138,7 +147,7 @@ function connect(brokerUrl, opts) { opts.defaultProtocol = opts.protocol } - function wrapper(client) { + function wrapper(client: MqttClient) { if (opts.servers) { if ( !client._reconnectCount || @@ -164,12 +173,8 @@ function connect(brokerUrl, opts) { client.on('error', () => { /* Automatically set up client error handling */ }) + return client } -module.exports = connect -module.exports.connect = connect -module.exports.MqttClient = MqttClient -module.exports.Store = Store -module.exports.DefaultMessageIdProvider = DefaultMessageIdProvider -module.exports.UniqueMessageIdProvider = UniqueMessageIdProvider +export default connect diff --git a/src/lib/connect/tcp.ts b/src/lib/connect/tcp.ts new file mode 100644 index 000000000..659356d0b --- /dev/null +++ b/src/lib/connect/tcp.ts @@ -0,0 +1,23 @@ +import { StreamBuilder } from '../shared' + +import net from 'net' +import _debug from 'debug' +import { Duplex } from 'readable-stream' + +const debug = _debug('mqttjs:tcp') +/* + variables port and host can be removed since + you have all required information in opts object +*/ +const buildStream: StreamBuilder = (client, opts) => { + opts.port = opts.port || 1883 + opts.hostname = opts.hostname || opts.host || 'localhost' + + const { port } = opts + const host = opts.hostname + + debug('port %d and host %s', port, host) + return net.createConnection(port, host) as unknown as Duplex +} + +export default buildStream diff --git a/lib/connect/tls.js b/src/lib/connect/tls.ts similarity index 73% rename from lib/connect/tls.js rename to src/lib/connect/tls.ts index 076c64faa..01b54016f 100644 --- a/lib/connect/tls.js +++ b/src/lib/connect/tls.ts @@ -1,8 +1,12 @@ -const tls = require('tls') -const net = require('net') -const debug = require('debug')('mqttjs:tls') +import tls from 'tls' +import net from 'net' +import _debug from 'debug' +import { StreamBuilder } from '../shared' +import { Duplex } from 'readable-stream' -function buildBuilder(mqttClient, opts) { +const debug = _debug('mqttjs:tls') + +const buildStream: StreamBuilder = (client, opts) => { opts.port = opts.port || 8883 opts.host = opts.hostname || opts.host || 'localhost' @@ -31,10 +35,10 @@ function buildBuilder(mqttClient, opts) { } }) - function handleTLSerrors(err) { + function handleTLSerrors(err: Error) { // How can I get verify this error is a tls error? if (opts.rejectUnauthorized) { - mqttClient.emit('error', err) + client.emit('error', err) } // close this connection to match the behaviour of net @@ -46,7 +50,7 @@ function buildBuilder(mqttClient, opts) { } connection.on('error', handleTLSerrors) - return connection + return connection as unknown as Duplex } -module.exports = buildBuilder +export default buildStream diff --git a/lib/connect/ws.js b/src/lib/connect/ws.ts similarity index 72% rename from lib/connect/ws.js rename to src/lib/connect/ws.ts index de80c0aad..ac37d28b6 100644 --- a/lib/connect/ws.js +++ b/src/lib/connect/ws.ts @@ -1,9 +1,14 @@ -const { Buffer } = require('buffer') -const WS = require('ws') -const debug = require('debug')('mqttjs:ws') -const duplexify = require('duplexify') -const { Transform } = require('readable-stream') -const { IS_BROWSER } = require('../is-browser') +import { StreamBuilder } from '../shared' + +import { Buffer } from 'buffer' +import WS, { ClientOptions } from 'ws' +import _debug from 'debug' +import duplexify from 'duplexify' +import { Duplex, DuplexOptions, Transform } from 'readable-stream' +import IS_BROWSER from '../is-browser' +import MqttClient, { IClientOptions } from '../client' + +const debug = _debug('mqttjs:ws') const WSS_OPTIONS = [ 'rejectUnauthorized', @@ -14,7 +19,7 @@ const WSS_OPTIONS = [ 'passphrase', ] -function buildUrl(opts, client) { +function buildUrl(opts: IClientOptions, client: MqttClient) { let url = `${opts.protocol}://${opts.hostname}:${opts.port}${opts.path}` if (typeof opts.transformWsUrl === 'function') { url = opts.transformWsUrl(url, opts, client) @@ -22,7 +27,7 @@ function buildUrl(opts, client) { return url } -function setDefaultOpts(opts) { +function setDefaultOpts(opts: IClientOptions) { const options = opts if (!opts.hostname) { options.hostname = 'localhost' @@ -56,7 +61,7 @@ function setDefaultOpts(opts) { return options } -function setDefaultBrowserOpts(opts) { +function setDefaultBrowserOpts(opts: IClientOptions) { const options = setDefaultOpts(opts) if (!options.hostname) { @@ -74,7 +79,7 @@ function setDefaultBrowserOpts(opts) { options.hostname = parsed.hostname if (!options.port) { - options.port = parsed.port + options.port = Number(parsed.port) } } @@ -88,7 +93,11 @@ function setDefaultBrowserOpts(opts) { return options } -function createWebSocket(client, url, opts) { +function createWebSocket( + client: MqttClient, + url: string, + opts: IClientOptions, +) { debug('createWebSocket') debug(`protocol: ${opts.protocolId} ${opts.protocolVersion}`) const websocketSubProtocol = @@ -99,11 +108,15 @@ function createWebSocket(client, url, opts) { debug( `creating new Websocket for url: ${url} and protocol: ${websocketSubProtocol}`, ) - const socket = new WS(url, [websocketSubProtocol], opts.wsOptions) + const socket = new WS( + url, + [websocketSubProtocol], + opts.wsOptions as ClientOptions, + ) return socket } -function createBrowserWebSocket(client, opts) { +function createBrowserWebSocket(client: MqttClient, opts: IClientOptions) { const websocketSubProtocol = opts.protocolId === 'MQIsdp' && opts.protocolVersion === 3 ? 'mqttv3.1' @@ -115,20 +128,25 @@ function createBrowserWebSocket(client, opts) { return socket } -function streamBuilder(client, opts) { +const streamBuilder: 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) + const webSocketStream = WS.createWebSocketStream( + socket, + options.wsOptions as DuplexOptions, + ) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore webSocketStream.url = url socket.on('close', () => { webSocketStream.destroy() }) - return webSocketStream + return webSocketStream as Duplex } -function browserStreamBuilder(client, opts) { +const browserStreamBuilder: StreamBuilder = (client, opts) => { debug('browserStreamBuilder') let stream const options = setDefaultBrowserOpts(opts) @@ -140,7 +158,6 @@ function browserStreamBuilder(client, opts) { const coerceToBuffer = !opts.objectMode const socket = createBrowserWebSocket(client, opts) - const proxy = buildProxy(opts, socketWriteBrowser, socketEndBrowser) if (!opts.objectMode) { @@ -171,20 +188,20 @@ function browserStreamBuilder(client, opts) { stream.socket = socket if (eventListenerSupport) { - socket.addEventListener('close', onclose) - socket.addEventListener('error', onerror) - socket.addEventListener('message', onmessage) + socket.addEventListener('close', onClose) + socket.addEventListener('error', onError) + socket.addEventListener('message', onMessage) } else { - socket.onclose = onclose - socket.onerror = onerror - socket.onmessage = onmessage + socket.onclose = onClose + socket.onerror = onError + socket.onmessage = onMessage } // methods for browserStreamBuilder - function buildProxy(pOptions, socketWrite, socketEnd) { + function buildProxy(pOptions: IClientOptions, socketWrite, socketEnd) { const _proxy = new Transform({ - objectModeMode: pOptions.objectMode, + objectMode: pOptions.objectMode, }) _proxy._write = socketWrite @@ -199,16 +216,16 @@ function browserStreamBuilder(client, opts) { stream.emit('connect') } - function onclose() { + function onClose() { stream.end() stream.destroy() } - function onerror(err) { + function onError(err: Event) { stream.destroy(err) } - function onmessage(event) { + function onMessage(event: MessageEvent) { let { data } = event if (data instanceof ArrayBuffer) data = Buffer.from(data) else data = Buffer.from(data, 'utf8') @@ -216,7 +233,7 @@ function browserStreamBuilder(client, opts) { } // this is to be enabled only if objectMode is false - function writev(chunks, cb) { + function writev(chunks: any, cb: (err?: Error) => void) { const buffers = new Array(chunks.length) for (let i = 0; i < chunks.length; i++) { if (typeof chunks[i].chunk === 'string') { @@ -229,7 +246,11 @@ function browserStreamBuilder(client, opts) { this._write(Buffer.concat(buffers), 'binary', cb) } - function socketWriteBrowser(chunk, enc, next) { + function socketWriteBrowser( + chunk: any, + enc: string, + next: (err?: Error) => void, + ) { if (socket.bufferedAmount > bufferSize) { // throttle data until buffered amount is reduced. setTimeout(socketWriteBrowser, bufferTimeout, chunk, enc, next) @@ -258,8 +279,4 @@ function browserStreamBuilder(client, opts) { return stream } -if (IS_BROWSER) { - module.exports = browserStreamBuilder -} else { - module.exports = streamBuilder -} +export default IS_BROWSER ? browserStreamBuilder : streamBuilder diff --git a/lib/connect/wx.js b/src/lib/connect/wx.ts similarity index 75% rename from lib/connect/wx.js rename to src/lib/connect/wx.ts index b6e1139be..2b0deccab 100644 --- a/lib/connect/wx.js +++ b/src/lib/connect/wx.ts @@ -1,11 +1,14 @@ -const { Buffer } = require('buffer') -const { Transform } = require('readable-stream') -const duplexify = require('duplexify') +import { StreamBuilder } from '../shared' + +import { Buffer } from 'buffer' +import { Duplex, Transform } from 'readable-stream' +import duplexify, { Duplexify } from 'duplexify' +import MqttClient, { IClientOptions } from '../client' /* global wx */ -let socketTask -let proxy -let stream +let socketTask: any +let proxy: Transform +let stream: Duplexify function buildProxy() { const _proxy = new Transform() @@ -44,7 +47,7 @@ function setDefaultOpts(opts) { } } -function buildUrl(opts, client) { +function buildUrl(opts: IClientOptions, client: MqttClient) { const protocol = opts.protocol === 'wxs' ? 'wss' : 'ws' let url = `${protocol}://${opts.hostname}${opts.path}` if (opts.port && opts.port !== 80 && opts.port !== 443) { @@ -81,7 +84,7 @@ function bindEventHandler() { }) } -function buildStream(client, opts) { +const buildStream: StreamBuilder = (client, opts) => { opts.hostname = opts.hostname || opts.host if (!opts.hostname) { @@ -96,6 +99,8 @@ function buildStream(client, opts) { setDefaultOpts(opts) const url = buildUrl(opts, client) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore socketTask = wx.connectSocket({ url, protocols: [websocketSubProtocol], @@ -112,12 +117,16 @@ function buildStream(client, opts) { } const destroyRef = stream.destroy + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore stream.destroy = () => { stream.destroy = destroyRef setTimeout(() => { socketTask.close({ fail() { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore stream._destroy(new Error()) }, }) @@ -126,7 +135,7 @@ function buildStream(client, opts) { bindEventHandler() - return stream + return stream as unknown as Duplex } -module.exports = buildStream +export default buildStream diff --git a/src/lib/default-message-id-provider.ts b/src/lib/default-message-id-provider.ts new file mode 100644 index 000000000..e09c08e42 --- /dev/null +++ b/src/lib/default-message-id-provider.ts @@ -0,0 +1,99 @@ +export interface IMessageIdProvider { + /** + * Allocate the first vacant messageId. The messageId become occupied status. + * @return {Number} - The first vacant messageId. If all messageIds are occupied, return null. + */ + allocate(): number | null + + /** + * Get the last allocated messageId. + * @return {Number} - messageId. + */ + getLastAllocated(): number | null + + /** + * Register the messageId. The messageId become occupied status. + * If the messageId has already been occupied, then return false. + * @param {Number} num - The messageId to request use. + * @return {Boolean} - If `num` was not occupied, then return true, otherwise return false. + */ + register(num: number): boolean + + /** + * Deallocate the messageId. The messageId become vacant status. + * @param {Number} num - The messageId to deallocate. The messageId must be occupied status. + * In other words, the messageId must be allocated by allocate() or + * occupied by register(). + */ + deallocate(num: number): void + + /** + * Clear all occupied messageIds. + * The all messageIds are set to vacant status. + */ + clear(): void +} + +/** + * DefaultMessageAllocator constructor + * @constructor + */ +export default class DefaultMessageIdProvider implements IMessageIdProvider { + private nextId: number + + 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 + } + + /** + * 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: number) { + return true + } + + /** + * deallocate + * Deallocate messageId. + * @param { unsigned int } - messageId to deallocate, + */ + deallocate(messageId: number) {} + + /** + * clear + * Deallocate all messageIds. + */ + clear() {} +} diff --git a/lib/handlers/ack.js b/src/lib/handlers/ack.ts similarity index 81% rename from lib/handlers/ack.js rename to src/lib/handlers/ack.ts index de92af60b..a088b6e01 100644 --- a/lib/handlers/ack.js +++ b/src/lib/handlers/ack.ts @@ -1,6 +1,8 @@ // Other Socket Errors: EADDRINUSE, ECONNRESET, ENOTFOUND, ETIMEDOUT. -const errors = { +import { PacketHandler } from '../shared' + +export const ReasonCodes = { 0: '', 1: 'Unacceptable protocol version', 2: 'Identifier rejected', @@ -46,7 +48,7 @@ const errors = { 162: 'Wildcard Subscriptions not supported', } -function handleAck(client, packet) { +const handleAck: PacketHandler = (client, packet) => { /* eslint no-fallthrough: "off" */ const { messageId } = packet const type = packet.cmd @@ -80,13 +82,13 @@ function handleAck(client, packet) { const pubackRC = packet.reasonCode // Callback - we're done if (pubackRC && pubackRC > 0 && pubackRC !== 16) { - err = new Error(`Publish error: ${errors[pubackRC]}`) + err = new Error(`Publish error: ${ReasonCodes[pubackRC]}`) err.code = pubackRC - client._removeOutgoingAndStoreMessage(messageId, () => { + client['_removeOutgoingAndStoreMessage'](messageId, () => { cb(err, packet) }) } else { - client._removeOutgoingAndStoreMessage(messageId, cb) + client['_removeOutgoingAndStoreMessage'](messageId, cb) } break @@ -100,43 +102,40 @@ function handleAck(client, packet) { const pubrecRC = packet.reasonCode if (pubrecRC && pubrecRC > 0 && pubrecRC !== 16) { - err = new Error(`Publish error: ${errors[pubrecRC]}`) + err = new Error(`Publish error: ${ReasonCodes[pubrecRC]}`) err.code = pubrecRC - client._removeOutgoingAndStoreMessage(messageId, () => { + client['_removeOutgoingAndStoreMessage'](messageId, () => { cb(err, packet) }) } else { - client._sendPacket(response) + 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) { + const granted = packet.granted as number[] + for (let grantedI = 0; grantedI < granted.length; grantedI++) { + if ((granted[grantedI] & 0x80) !== 0) { // suback with Failure status const topics = client.messageIdToTopic[messageId] if (topics) { topics.forEach((topic) => { - delete client._resubscribeTopics[topic] + delete client['_resubscribeTopics'][topic] }) } } } delete client.messageIdToTopic[messageId] - client._invokeStoreProcessingQueue() + client['_invokeStoreProcessingQueue']() cb(null, packet) break } case 'unsuback': { delete client.outgoing[messageId] client.messageIdProvider.deallocate(messageId) - client._invokeStoreProcessingQueue() + client['_invokeStoreProcessingQueue']() cb(null) break } @@ -149,5 +148,4 @@ function handleAck(client, packet) { } } -module.exports = handleAck -module.exports.errors = errors +export default handleAck diff --git a/src/lib/handlers/auth.ts b/src/lib/handlers/auth.ts new file mode 100644 index 000000000..62f5ae0b5 --- /dev/null +++ b/src/lib/handlers/auth.ts @@ -0,0 +1,44 @@ +import { IAuthPacket } from 'mqtt-packet' +import { ErrorWithReasonCode, PacketHandler } from '../shared' +import { ReasonCodes } from './ack' + +const handleAuth: PacketHandler = ( + client, + packet: IAuthPacket & { returnCode: number }, +) => { + const { options } = client + const version = options.protocolVersion + const rc = version === 5 ? packet.reasonCode : packet.returnCode + + if (version !== 5) { + const err = new ErrorWithReasonCode( + `Protocol error: Auth packets are only supported in MQTT 5. Your version:${version}`, + rc, + ) + client.emit('error', err) + return + } + + client.handleAuth( + packet, + (err: ErrorWithReasonCode, packet2: IAuthPacket) => { + if (err) { + client.emit('error', err) + return + } + + if (rc === 24) { + client.reconnecting = false + client['_sendPacket'](packet2) + } else { + const error = new ErrorWithReasonCode( + `Connection refused: ${ReasonCodes[rc]}`, + rc, + ) + client.emit('error', error) + } + }, + ) +} + +export default handleAuth diff --git a/lib/handlers/connack.js b/src/lib/handlers/connack.ts similarity index 62% rename from lib/handlers/connack.js rename to src/lib/handlers/connack.ts index 4c52ae80f..a723d3d0d 100644 --- a/lib/handlers/connack.js +++ b/src/lib/handlers/connack.ts @@ -1,14 +1,16 @@ -const { errors } = require('./ack') -const TopicAliasSend = require('../topic-alias-send') +import { ReasonCodes } from './ack' +import TopicAliasSend from '../topic-alias-send' +import { ErrorWithReasonCode, PacketHandler } from '../shared' +import { IConnackPacket } from 'mqtt-packet' -function handleConnack(client, packet) { +const handleConnack: PacketHandler = (client, packet: IConnackPacket) => { 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) { @@ -20,14 +22,14 @@ function handleConnack(client, packet) { return } if (packet.properties.topicAliasMaximum > 0) { - client.topicAliasSend = new TopicAliasSend( + client['topicAliasSend'] = new TopicAliasSend( packet.properties.topicAliasMaximum, ) } } if (packet.properties.serverKeepAlive && options.keepalive) { options.keepalive = packet.properties.serverKeepAlive - client._shiftPingInterval() + client['_shiftPingInterval']() } if (packet.properties.maximumPacketSize) { if (!options.properties) { @@ -40,12 +42,14 @@ function handleConnack(client, packet) { if (rc === 0) { client.reconnecting = false - client._onConnect(packet) + client['_onConnect'](packet) } else if (rc > 0) { - const err = new Error(`Connection refused: ${errors[rc]}`) - err.code = rc + const err = new ErrorWithReasonCode( + `Connection refused: ${ReasonCodes[rc]}`, + rc, + ) client.emit('error', err) } } -module.exports = handleConnack +export default handleConnack diff --git a/lib/handlers/index.js b/src/lib/handlers/index.ts similarity index 81% rename from lib/handlers/index.js rename to src/lib/handlers/index.ts index 51054dbbe..fab603cbb 100644 --- a/lib/handlers/index.js +++ b/src/lib/handlers/index.ts @@ -1,10 +1,11 @@ -const handlePublish = require('./publish') -const handleAuth = require('./auth') -const handleConnack = require('./connack') -const handleAck = require('./ack') -const handlePubrel = require('./pubrel') +import handlePublish from './publish' +import handleAuth from './auth' +import handleConnack from './connack' +import handleAck from './ack' +import handlePubrel from './pubrel' +import { PacketHandler } from '../shared' -function handle(client, packet, done) { +const handle: PacketHandler = (client, packet, done) => { const { options } = client if ( @@ -63,4 +64,4 @@ function handle(client, packet, done) { } } -module.exports = handle +export default handle diff --git a/lib/handlers/publish.js b/src/lib/handlers/publish.ts similarity index 58% rename from lib/handlers/publish.js rename to src/lib/handlers/publish.ts index 509fabcf7..ec7b2b2e9 100644 --- a/lib/handlers/publish.js +++ b/src/lib/handlers/publish.ts @@ -1,3 +1,6 @@ +import { IPublishPacket } from 'mqtt-packet' +import { PacketHandler } from '../shared' + const validReasonCodes = [0, 16, 128, 131, 135, 144, 145, 151, 153] /* @@ -24,7 +27,7 @@ const validReasonCodes = [0, 16, 128, 131, 135, 144, 145, 151, 153] for now i just suppressed the warnings */ -function handlePublish(client, packet, done) { +const handlePublish: PacketHandler = (client, packet: IPublishPacket, done) => { client.log('handlePublish: packet %o', packet) done = typeof done !== 'undefined' ? done : client.noop let topic = packet.topic.toString() @@ -33,7 +36,7 @@ function handlePublish(client, packet, done) { const { messageId } = packet const { options } = client if (client.options.protocolVersion === 5) { - let alias + let alias: number if (packet.properties) { alias = packet.properties.topicAlias } @@ -41,7 +44,7 @@ function handlePublish(client, packet, done) { if (topic.length === 0) { if (alias > 0 && alias <= 0xffff) { const gotTopic = - client.topicAliasRecv.getTopicByAlias(alias) + client['topicAliasRecv'].getTopicByAlias(alias) if (gotTopic) { topic = gotTopic client.log( @@ -71,7 +74,7 @@ function handlePublish(client, packet, done) { ) return } - } else if (client.topicAliasRecv.put(topic, alias)) { + } else if (client['topicAliasRecv'].put(topic, alias)) { client.log( 'handlePublish :: registered topic: %s - alias: %d', topic, @@ -93,67 +96,80 @@ function handlePublish(client, packet, done) { 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) - }) - } - }) + options.customHandleAcks( + topic, + message as Buffer, + packet, + (error, code) => { + if (typeof error === 'number') { + 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) + options.customHandleAcks( + topic, + message as Buffer, + packet, + (error, code) => { + if (typeof error === 'number') { + code = error + error = null } - client._sendPacket( - { cmd: 'puback', messageId, reasonCode: code }, - done, - ) - }) - }) + 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 as Buffer, 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.emit('message', topic, message as Buffer, packet) client.handleMessage(packet, done) break default: @@ -164,4 +180,4 @@ function handlePublish(client, packet, done) { } } -module.exports = handlePublish +export default handlePublish diff --git a/src/lib/handlers/pubrel.ts b/src/lib/handlers/pubrel.ts new file mode 100644 index 000000000..3f5d31a86 --- /dev/null +++ b/src/lib/handlers/pubrel.ts @@ -0,0 +1,27 @@ +import { IPubcompPacket, IPublishPacket, IPubrelPacket } from 'mqtt-packet' +import { PacketHandler } from '../shared' + +const handlePubrel: PacketHandler = (client, packet: IPubrelPacket, done) => { + client.log('handling pubrel packet') + const callback = typeof done !== 'undefined' ? done : client.noop + const { messageId } = packet + + const comp: IPubcompPacket = { cmd: 'pubcomp', messageId } + + client.incomingStore.get(packet, (err, pub: IPublishPacket) => { + if (!err) { + client.emit('message', pub.topic, pub.payload as Buffer, 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) + } + }) +} + +export default handlePubrel diff --git a/lib/is-browser.js b/src/lib/is-browser.ts similarity index 57% rename from lib/is-browser.js rename to src/lib/is-browser.ts index 92c21a3c2..3a6011a04 100644 --- a/lib/is-browser.js +++ b/src/lib/is-browser.ts @@ -1,11 +1,12 @@ const legacyIsBrowser = (typeof process !== 'undefined' && process.title === 'browser') || - // eslint-disable-next-line camelcase + // eslint-disable-next-line camelcase, @typescript-eslint/ban-ts-comment + // @ts-ignore typeof __webpack_require__ === 'function' const isBrowser = typeof window !== 'undefined' && typeof document !== 'undefined' -module.exports = { - IS_BROWSER: isBrowser || legacyIsBrowser, -} +const IS_BROWSER = isBrowser || legacyIsBrowser + +export default IS_BROWSER diff --git a/src/lib/shared.ts b/src/lib/shared.ts new file mode 100644 index 000000000..5929c3d2f --- /dev/null +++ b/src/lib/shared.ts @@ -0,0 +1,73 @@ +import type { IClientOptions } from './client' +import type MqttClient from './client' +import { Packet } from 'mqtt-packet' +import { Duplex, Writable } from 'readable-stream' + +export type DoneCallback = (error?: Error) => void + +export type GenericCallback = (error?: Error, result?: T) => void + +export type VoidCallback = () => void + +export type IStream = Duplex | Writable + +export type StreamBuilder = ( + client: MqttClient, + opts?: IClientOptions, +) => IStream + +export type Callback = () => void + +export type PacketHandler = ( + client: MqttClient, + packet: Packet, + done?: DoneCallback, +) => void + +export class ErrorWithReasonCode extends Error { + public code: number + + public constructor(message: string, code: number) { + super(message) + this.code = code + + // We need to set the prototype explicitly + Object.setPrototypeOf(this, ErrorWithReasonCode.prototype) + Object.getPrototypeOf(this).name = 'ErrorWithReasonCode' + } +} + +// eslint-disable-next-line @typescript-eslint/ban-types +export type Constructor = new (...args: any[]) => T + +export function applyMixin( + target: Constructor, + mixin: Constructor, + includeConstructor = false, +): void { + // Figure out the inheritance chain of the mixin + const inheritanceChain: Constructor[] = [mixin] + // eslint-disable-next-line no-constant-condition + while (true) { + const current = inheritanceChain[0] + const base = Object.getPrototypeOf(current) + if (base?.prototype) { + inheritanceChain.unshift(base) + } else { + break + } + } + for (const ctor of inheritanceChain) { + for (const prop of Object.getOwnPropertyNames(ctor.prototype)) { + // Do not override the constructor + if (includeConstructor || prop !== 'constructor') { + Object.defineProperty( + target.prototype, + prop, + Object.getOwnPropertyDescriptor(ctor.prototype, prop) ?? + Object.create(null), + ) + } + } + } +} diff --git a/lib/store.js b/src/lib/store.ts similarity index 51% rename from lib/store.js rename to src/lib/store.ts index ce7608562..49be51e99 100644 --- a/lib/store.js +++ b/src/lib/store.ts @@ -1,21 +1,66 @@ /** * Module dependencies */ -const { Readable } = require('readable-stream') +import { Readable } from 'readable-stream' +import { Packet } from 'mqtt-packet' +import { DoneCallback } from './shared' const streamsOpts = { objectMode: true } const defaultStoreOptions = { clean: true, } +export interface IStoreOptions { + /** + * true, clear _inflights at close + */ + clean?: boolean +} + +export type PacketCallback = (error?: Error, packet?: Packet) => void + +export interface IStore { + /** + * Adds a packet to the store, a packet is + * anything that has a messageId property. + * + */ + put(packet: Packet, cb: DoneCallback): IStore + + /** + * Creates a stream with all the packets in the store + * + */ + createStream(): Readable + + /** + * deletes a packet from the store. + */ + del(packet: Pick, cb: PacketCallback): IStore + + /** + * get a packet from the store. + */ + get(packet: Pick, cb: PacketCallback): IStore + + /** + * Close the store + */ + close(cb: DoneCallback): void +} + /** * In-memory implementation of the message store * This can actually be saved into files. * * @param {Object} [options] - store options */ -class Store { - constructor(options) { +export default class Store implements IStore { + private options: IStoreOptions + + private _inflights: Map + + constructor(options?: IStoreOptions) { this.options = options || {} // Defaults @@ -29,7 +74,7 @@ class Store { * anything that has a messageId property. * */ - put(packet, cb) { + put(packet: Packet, cb: DoneCallback) { this._inflights.set(packet.messageId, packet) if (cb) { @@ -61,7 +106,7 @@ class Store { } } - stream.destroy = () => { + stream.destroy = (err) => { if (destroyed) { return } @@ -71,6 +116,8 @@ class Store { setTimeout(() => { stream.emit('close') }, 0) + + return stream } return stream @@ -79,11 +126,11 @@ class Store { /** * deletes a packet from the store. */ - del(packet, cb) { - packet = this._inflights.get(packet.messageId) - if (packet) { + del(packet: Pick, cb: PacketCallback) { + const toDelete = this._inflights.get(packet.messageId) + if (toDelete) { this._inflights.delete(packet.messageId) - cb(null, packet) + cb(null, toDelete) } else if (cb) { cb(new Error('missing packet')) } @@ -94,10 +141,10 @@ class Store { /** * get a packet from the store. */ - get(packet, cb) { - packet = this._inflights.get(packet.messageId) - if (packet) { - cb(null, packet) + get(packet: Pick, cb: PacketCallback) { + const storedPacket = this._inflights.get(packet.messageId) + if (storedPacket) { + cb(null, storedPacket) } else if (cb) { cb(new Error('missing packet')) } @@ -108,7 +155,7 @@ class Store { /** * Close the store */ - close(cb) { + close(cb: DoneCallback) { if (this.options.clean) { this._inflights = null } @@ -117,5 +164,3 @@ class Store { } } } - -module.exports = Store diff --git a/lib/topic-alias-recv.js b/src/lib/topic-alias-recv.ts similarity index 76% rename from lib/topic-alias-recv.js rename to src/lib/topic-alias-recv.ts index 949cae680..1d3991436 100644 --- a/lib/topic-alias-recv.js +++ b/src/lib/topic-alias-recv.ts @@ -3,8 +3,14 @@ * This holds alias to topic map * @param {Number} [max] - topic alias maximum entries */ -class TopicAliasRecv { - constructor(max) { +export default class TopicAliasRecv { + private aliasToTopic: Record + + public max: number + + public length: number + + constructor(max: number) { this.aliasToTopic = {} this.max = max } @@ -15,7 +21,7 @@ class TopicAliasRecv { * @param {Number} [alias] - topic alias * @returns {Boolean} - if success return true otherwise false */ - put(topic, alias) { + put(topic: string, alias: number): boolean { if (alias === 0 || alias > this.max) { return false } @@ -29,7 +35,7 @@ class TopicAliasRecv { * @param {String} [topic] - topic * @returns {Number} - if mapped topic exists return topic alias, otherwise return undefined */ - getTopicByAlias(alias) { + getTopicByAlias(alias: number): string { return this.aliasToTopic[alias] } @@ -40,5 +46,3 @@ class TopicAliasRecv { this.aliasToTopic = {} } } - -module.exports = TopicAliasRecv diff --git a/lib/topic-alias-send.js b/src/lib/topic-alias-send.ts similarity index 76% rename from lib/topic-alias-send.js rename to src/lib/topic-alias-send.ts index 1904a2b35..45cd41893 100644 --- a/lib/topic-alias-send.js +++ b/src/lib/topic-alias-send.ts @@ -1,18 +1,28 @@ /** * Module dependencies */ -const LRUCache = require('lru-cache') -const { NumberAllocator } = require('number-allocator') +import LRUCache from 'lru-cache' +import { NumberAllocator } from 'number-allocator' /** * Topic Alias sending manager * This holds both topic to alias and alias to topic map * @param {Number} [max] - topic alias maximum entries */ -class TopicAliasSend { - constructor(max) { +export default class TopicAliasSend { + private aliasToTopic: LRUCache + + private topicToAlias: Record + + private max: number + + private numberAllocator: NumberAllocator + + public length: number + + constructor(max: number) { if (max > 0) { - this.aliasToTopic = new LRUCache({ max }) + this.aliasToTopic = new LRUCache({ max }) this.topicToAlias = {} this.numberAllocator = new NumberAllocator(1, max) this.max = max @@ -26,7 +36,7 @@ class TopicAliasSend { * @param {Number} [alias] - topic alias * @returns {Boolean} - if success return true otherwise false */ - put(topic, alias) { + put(topic: string, alias: number): boolean { if (alias === 0 || alias > this.max) { return false } @@ -46,7 +56,7 @@ class TopicAliasSend { * @param {Number} [alias] - topic alias * @returns {String} - if mapped topic exists return topic, otherwise return undefined */ - getTopicByAlias(alias) { + getTopicByAlias(alias: number): string { return this.aliasToTopic.get(alias) } @@ -55,7 +65,7 @@ class TopicAliasSend { * @param {String} [topic] - topic * @returns {Number} - if mapped topic exists return topic alias, otherwise return undefined */ - getAliasByTopic(topic) { + getAliasByTopic(topic: string): number | undefined { const alias = this.topicToAlias[topic] if (typeof alias !== 'undefined') { this.aliasToTopic.get(alias) // LRU update @@ -77,12 +87,10 @@ class TopicAliasSend { * Get Least Recently Used (LRU) topic alias * @returns {Number} - if vacant alias exists then return it, otherwise then return LRU alias */ - getLruAlias() { + getLruAlias(): number { 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/src/lib/unique-message-id-provider.ts similarity index 81% rename from lib/unique-message-id-provider.js rename to src/lib/unique-message-id-provider.ts index c88be4bb9..784b8e0cf 100644 --- a/lib/unique-message-id-provider.js +++ b/src/lib/unique-message-id-provider.ts @@ -1,10 +1,14 @@ -const { NumberAllocator } = require('number-allocator') +import { NumberAllocator } from 'number-allocator' /** * UniqueMessageAllocator constructor * @constructor */ -class UniqueMessageIdProvider { +export default class UniqueMessageIdProvider { + private numberAllocator: NumberAllocator + + private lastId: number + constructor() { this.numberAllocator = new NumberAllocator(1, 65535) } @@ -36,7 +40,7 @@ class UniqueMessageIdProvider { * @param { unsigned int } - messageId to register, * @return boolean */ - register(messageId) { + register(messageId: number) { return this.numberAllocator.use(messageId) } @@ -45,7 +49,7 @@ class UniqueMessageIdProvider { * Deallocate messageId. * @param { unsigned int } - messageId to deallocate, */ - deallocate(messageId) { + deallocate(messageId: number) { this.numberAllocator.free(messageId) } @@ -57,5 +61,3 @@ class UniqueMessageIdProvider { this.numberAllocator.clear() } } - -module.exports = UniqueMessageIdProvider diff --git a/lib/validations.js b/src/lib/validations.ts similarity index 90% rename from lib/validations.js rename to src/lib/validations.ts index e71dd52a2..e2533991d 100644 --- a/lib/validations.js +++ b/src/lib/validations.ts @@ -7,7 +7,7 @@ * @param {String} topic - A topic * @returns {Boolean} If the topic is valid, returns true. Otherwise, returns false. */ -function validateTopic(topic) { +export function validateTopic(topic: string): boolean { const parts = topic.split('/') for (let i = 0; i < parts.length; i++) { @@ -33,7 +33,7 @@ function validateTopic(topic) { * @param {Array} topics - Array of topics * @returns {String} If the topics is valid, returns null. Otherwise, returns the invalid one */ -function validateTopics(topics) { +export function validateTopics(topics: string[]): string { if (topics.length === 0) { return 'empty_topic_list' } @@ -44,7 +44,3 @@ function validateTopics(topics) { } return null } - -module.exports = { - validateTopics, -} diff --git a/src/mqtt.ts b/src/mqtt.ts new file mode 100644 index 000000000..d14c463a3 --- /dev/null +++ b/src/mqtt.ts @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2015-2015 MQTT.js contributors. + * Copyright (c) 2011-2014 Adam Rudd. + * + * See LICENSE for more information + */ + +import MqttClient from './lib/client' +import DefaultMessageIdProvider from './lib/default-message-id-provider' +import UniqueMessageIdProvider from './lib/unique-message-id-provider' +import Store, { IStore } from './lib/store' +import connect from './lib/connect' + +export const Client = MqttClient +export { + connect, + MqttClient, + Store, + DefaultMessageIdProvider, + UniqueMessageIdProvider, + IStore, +} +export * from './lib/client' diff --git a/test/abstract_client.js b/test/abstract_client.ts similarity index 94% rename from test/abstract_client.js rename to test/abstract_client.ts index cba436cb4..f80f02526 100644 --- a/test/abstract_client.js +++ b/test/abstract_client.ts @@ -1,18 +1,27 @@ /** * Testing dependencies */ -const { should } = require('chai') -const sinon = require('sinon') -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') +import { assert } from 'chai' +import sinon from 'sinon' +import fs from 'fs' +import levelStore from 'mqtt-level-store' +import * as mqtt from '../src/mqtt' +import Store from '../src/lib/store' +import ports from './helpers/port_list' +import serverBuilder from './server_helpers_for_client_tests' +import handlePubrel from '../src/lib/handlers/pubrel' +import handle from '../src/lib/handlers/index' +import handlePublish from '../src/lib/handlers/publish' +import { + IClientOptions, + IClientPublishOptions, + ISubscriptioOptions, + ISubscriptionMap, + ISubscriptionRequest, +} from '../src/lib/client' +import { IPublishPacket, IPubrelPacket, ISubackPacket, QoS } from 'mqtt-packet' +import { DoneCallback, ErrorWithReasonCode } from 'src/lib/shared' +import { fail } from 'assert' /** * These tests try to be consistent with names for servers (brokers) and clients, @@ -35,11 +44,14 @@ const handlePublish = require('../lib/handlers/publish') * */ -module.exports = (server, config) => { +export default function abstractTest(server, config) { const version = config.protocolVersion || 4 - function connect(opts) { - opts = { ...config, ...opts } + function connect(opts?: IClientOptions | string) { + if (typeof opts === 'string') { + opts = { host: opts } + } + opts = { ...config, ...opts } as IClientOptions return mqtt.connect(opts) } @@ -367,7 +379,9 @@ module.exports = (server, config) => { it('should provide connack packet with connect event', function _test(done) { const connack = - version === 5 ? { reasonCode: 0 } : { returnCode: 0 } + version === 5 + ? { reasonCode: 0, sessionPresent: undefined } + : { returnCode: 0, sessionPresent: undefined } server.once('client', (serverClient) => { connack.sessionPresent = true serverClient.connack(connack) @@ -400,7 +414,7 @@ module.exports = (server, config) => { client.once('connect', () => { done(new Error('Should not emit connect')) }) - client.once('error', (error) => { + client.once('error', (error: ErrorWithReasonCode) => { const value = version === 5 ? 128 : 2 assert.strictEqual(error.code, value) // code for clientID identifer rejected client.end((err) => done(err)) @@ -411,7 +425,7 @@ module.exports = (server, config) => { // fake a port const client = connect({ port: 4557 }) - client.on('error', (e) => { + client.on('error', (e: any) => { assert.equal(e.code, 'ECONNREFUSED') client.end((err) => done(err)) }) @@ -482,7 +496,7 @@ module.exports = (server, config) => { (err) => { client.end(() => { if (err) { - return done(new Error(err)) + return done(err) } done() }) @@ -848,6 +862,7 @@ module.exports = (server, config) => { const store = new Store() store.put = (packet, cb) => { process.nextTick(cb, new Error('oops there is an error')) + return store } const client = connect({ incomingStore: store, @@ -958,7 +973,7 @@ module.exports = (server, config) => { const client = connect() const payload = 'test' const topic = 'test' - const opts = { + const opts: IClientPublishOptions = { retain: true, qos: 1, } @@ -1027,7 +1042,7 @@ module.exports = (server, config) => { const client = connect() const payload = 'duplicated-test' const topic = 'test' - const opts = { + const opts: IClientPublishOptions = { retain: true, qos: 1, dup: true, @@ -1071,7 +1086,7 @@ module.exports = (server, config) => { it('should fire a callback (qos 1)', function _test(done) { const client = connect() - const opts = { qos: 1 } + const opts: IClientPublishOptions = { qos: 1 } client.once('connect', () => { client.publish('a', 'b', opts, () => { @@ -1124,7 +1139,7 @@ module.exports = (server, config) => { } setImmediate(() => { client.end(() => { - server2.close(done()) + server2.close(done) }) }) }) @@ -1134,7 +1149,7 @@ module.exports = (server, config) => { it('should fire a callback (qos 2)', function _test(done) { const client = connect() - const opts = { qos: 2 } + const opts: IClientPublishOptions = { qos: 2 } client.once('connect', () => { client.publish('a', 'b', opts, () => { @@ -1191,7 +1206,7 @@ module.exports = (server, config) => { } setImmediate(() => { client.end(true, () => { - server2.close(done()) + server2.close(done) }) }) }) @@ -1310,9 +1325,9 @@ module.exports = (server, config) => { } 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) + qosTests.forEach((qos) => { + it(`should publish 10 QoS ${qos}and receive them only when \`handleMessage\` finishes`, function _test(done) { + testQosHandleMessage(qos, done) }) }) @@ -1323,22 +1338,27 @@ module.exports = (server, config) => { callback(new Error('Error thrown by the application')) } - client._sendPacket = sinon.spy() + const sendSpy = sinon.spy() + + client['_sendPacket'] = sendSpy handlePublish( client, { + cmd: 'publish', messageId: Math.floor(65535 * Math.random()), topic: 'test', payload: 'test', qos: 1, + dup: false, + retain: false, }, (err) => { assert.exists(err) }, ) - assert.strictEqual(client._sendPacket.callCount, 0) + assert.strictEqual(sendSpy.callCount, 0) client.end() client.on('connect', () => { done() @@ -1357,10 +1377,13 @@ module.exports = (server, config) => { try { handlePublish(client, { + cmd: 'publish', messageId: Math.floor(65535 * Math.random()), topic: 'test', payload: 'test', qos: 1, + dup: false, + retain: false, }) client.end(true, done) } catch (err) { @@ -1372,11 +1395,13 @@ module.exports = (server, config) => { ) it('should handle error with async incoming store in QoS 1 `handlePublish` method', function _test(done) { - class AsyncStore { + class AsyncStore extends Store { put(packet, cb) { process.nextTick(() => { cb(null, 'Error') }) + + return this } close(cb) { @@ -1390,10 +1415,13 @@ module.exports = (server, config) => { handlePublish( client, { + cmd: 'publish', messageId: 1, topic: 'test', payload: 'test', qos: 1, + dup: false, + retain: false, }, () => { client.end((err) => done(err)) @@ -1402,23 +1430,28 @@ module.exports = (server, config) => { }) it('should handle error with async incoming store in QoS 2 `handlePublish` method', function _test(done) { - class AsyncStore { + class AsyncStore extends Store { put(packet, cb) { process.nextTick(() => { cb(null, 'Error') }) + + return this } del(packet, cb) { process.nextTick(() => { cb(new Error('Error')) }) + + return this } get(packet, cb) { process.nextTick(() => { cb(null, { cmd: 'publish' }) }) + return this } close(cb) { @@ -1432,6 +1465,9 @@ module.exports = (server, config) => { handlePublish( client, { + cmd: 'publish', + dup: false, + retain: false, messageId: 1, topic: 'test', payload: 'test', @@ -1444,23 +1480,29 @@ module.exports = (server, config) => { }) it('should handle error with async incoming store in QoS 2 `handlePubrel` method', function _test(done) { - class AsyncStore { + class AsyncStore extends Store { put(packet, cb) { process.nextTick(() => { cb(null, 'Error') }) + + return this } del(packet, cb) { process.nextTick(() => { cb(new Error('Error')) }) + + return this } get(packet, cb) { process.nextTick(() => { cb(null, { cmd: 'publish' }) }) + + return this } close(cb) { @@ -1474,8 +1516,9 @@ module.exports = (server, config) => { handlePubrel( client, { + cmd: 'pubrel', messageId: 1, - qos: 2, + // qos: 2, }, () => { client.end(true, (err) => done(err)) @@ -1485,11 +1528,13 @@ module.exports = (server, config) => { it('should handle success with async incoming store in QoS 2 `handlePubrel` method', function _test(done) { let delComplete = false - class AsyncStore { + class AsyncStore extends Store { put(packet, cb) { process.nextTick(() => { cb(null, 'Error') }) + + return this } del(packet, cb) { @@ -1497,12 +1542,15 @@ module.exports = (server, config) => { delComplete = true cb(null) }) + + return this } get(packet, cb) { process.nextTick(() => { cb(null, { cmd: 'publish' }) }) + return this } close(cb) { @@ -1516,8 +1564,9 @@ module.exports = (server, config) => { handlePubrel( client, { + cmd: 'pubrel', messageId: 1, - qos: 2, + // qos: 2, }, () => { assert.isTrue(delComplete) @@ -1549,19 +1598,19 @@ module.exports = (server, config) => { payload, qos, cmd: 'publish', + dup: false, + retain: false, }, () => { + const spy = sinon.spy() // cleans up the client - client._sendPacket = sinon.spy() + client['_sendPacket'] = spy handlePubrel( client, { cmd: 'pubrel', messageId }, (err) => { assert.exists(err) - assert.strictEqual( - client._sendPacket.callCount, - 0, - ) + assert.strictEqual(spy.callCount, 0) client.end(true, done) }, ) @@ -1596,6 +1645,8 @@ module.exports = (server, config) => { payload, qos, cmd: 'publish', + dup: false, + retain: false, }, () => { try { @@ -1618,9 +1669,9 @@ module.exports = (server, config) => { 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 }) + let client: mqtt.MqttClient + const incomingStore = new Store({ clean: false }) + const outgoingStore = new Store({ clean: false }) const server2 = serverBuilder(config.protocol, (serverClient) => { // errors are not interesting for this test // but they might happen on some platforms @@ -1684,7 +1735,7 @@ module.exports = (server, config) => { client.on('close', () => { if (!reconnect) { client.reconnect({ - clean: false, + // clean: false, TODO: should we handle this? incomingStore, outgoingStore, }) @@ -1694,7 +1745,12 @@ module.exports = (server, config) => { }) }) - function testCallbackStorePutByQoS(qos, clean, expected, done) { + function testCallbackStorePutByQoS( + qos: number, + clean: boolean, + expected: string[], + done: DoneCallback, + ) { const client = connect({ clean, clientId: 'testId', @@ -1707,12 +1763,17 @@ module.exports = (server, config) => { } 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) - }) + client.publish( + 'test', + 'test', + { qos: qos as QoS, cbStorePut }, + (err) => { + if (err) done(err) + callbacks.push('publish') + assert.deepEqual(callbacks, expected) + client.end(true, done) + }, + ) }) } @@ -1730,8 +1791,8 @@ module.exports = (server, config) => { // 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.args[0] as number, + test.args[1] as boolean, test.expected, done, ) @@ -1740,8 +1801,8 @@ module.exports = (server, config) => { // 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.args[0] as number, + test.args[1] as boolean, test.expected, done, ) @@ -1882,7 +1943,7 @@ module.exports = (server, config) => { let clock // eslint-disable-next-line - beforeEach(() => { + beforeEach(() => { clock = sinon.useFakeTimers() }) @@ -1894,17 +1955,18 @@ module.exports = (server, config) => { const interval = 3 const client = connect({ keepalive: interval }) - client._checkPing = sinon.spy() + const spy = sinon.spy() + client['_checkPing'] = spy client.once('connect', () => { clock.tick(interval * 1000) - assert.strictEqual(client._checkPing.callCount, 1) + assert.strictEqual(spy.callCount, 1) clock.tick(interval * 1000) - assert.strictEqual(client._checkPing.callCount, 2) + assert.strictEqual(spy.callCount, 2) clock.tick(interval * 1000) - assert.strictEqual(client._checkPing.callCount, 3) + assert.strictEqual(spy.callCount, 3) client.end(true, done) }) @@ -1914,7 +1976,8 @@ module.exports = (server, config) => { const intervalMs = 3000 const client = connect({ keepalive: intervalMs / 1000 }) - client._checkPing = sinon.spy() + const spy = sinon.spy() + client['_checkPing'] = spy client.once('connect', () => { client.publish('foo', 'bar') @@ -1922,7 +1985,7 @@ module.exports = (server, config) => { client.publish('foo', 'bar') clock.tick(2) - assert.strictEqual(client._checkPing.callCount, 0) + assert.strictEqual(spy.callCount, 0) client.end(true, done) }) }) @@ -1934,7 +1997,8 @@ module.exports = (server, config) => { reschedulePings: false, }) - client._checkPing = sinon.spy() + const spy = sinon.spy() + client['_checkPing'] = spy client.once('connect', () => { client.publish('foo', 'bar') @@ -1942,7 +2006,7 @@ module.exports = (server, config) => { client.publish('foo', 'bar') clock.tick(2) - assert.strictEqual(client._checkPing.callCount, 1) + assert.strictEqual(spy.callCount, 1) client.end(true, done) }) }) @@ -2000,19 +2064,20 @@ module.exports = (server, config) => { const client = connect({ keepalive: 1 }) client.once('connect', () => { - client._checkPing = sinon.spy() + const spy = sinon.spy() + client['_checkPing'] = spy client.publish('foo', 'bar') setTimeout(() => { - assert.strictEqual(client._checkPing.callCount, 0) + assert.strictEqual(spy.callCount, 0) client.publish('foo', 'bar') setTimeout(() => { - assert.strictEqual(client._checkPing.callCount, 0) + assert.strictEqual(spy.callCount, 0) client.publish('foo', 'bar') setTimeout(() => { - assert.strictEqual(client._checkPing.callCount, 0) + assert.strictEqual(spy.callCount, 0) done() }, 75) }, 75) @@ -2044,7 +2109,7 @@ module.exports = (server, config) => { server.once('client', (serverClient) => { serverClient.once('subscribe', (packet) => { - const result = { + const result: ISubscriptionRequest = { topic, qos: 0, } @@ -2101,7 +2166,10 @@ module.exports = (server, config) => { 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 } + const result: ISubscriptionRequest = { + topic: i, + qos: 0, + } if (version === 5) { result.nl = false result.rap = false @@ -2118,7 +2186,7 @@ module.exports = (server, config) => { it('should accept a hash of subscriptions', function _test(done) { const client = connect() - const topics = { + const topics: ISubscriptionMap = { test1: { qos: 0 }, test2: { qos: 1 }, } @@ -2133,7 +2201,7 @@ module.exports = (server, config) => { for (const k in topics) { if (Object.prototype.hasOwnProperty.call(topics, k)) { - const result = { + const result: ISubscriptionRequest = { topic: k, qos: topics[k].qos, } @@ -2155,7 +2223,7 @@ module.exports = (server, config) => { it('should accept an options parameter', function _test(done) { const client = connect() const topic = 'test' - const opts = { qos: 1 } + const opts: ISubscriptioOptions = { qos: 1 } client.once('connect', () => { client.subscribe(topic, opts) @@ -2163,7 +2231,7 @@ module.exports = (server, config) => { server.once('client', (serverClient) => { serverClient.once('subscribe', (packet) => { - const expected = [ + const expected: ISubscriptionRequest[] = [ { topic, qos: 1, @@ -2193,9 +2261,9 @@ module.exports = (server, config) => { server.once('client', (serverClient) => { serverClient.once('subscribe', (packet) => { - const result = { + const result: ISubscriptionRequest = { topic, - qos: defaultOpts.qos, + qos: defaultOpts.qos as QoS, } if (version === 5) { result.nl = false @@ -2219,7 +2287,10 @@ module.exports = (server, config) => { done(err) } else { assert.exists(granted, 'granted not given') - const expectedResult = { topic: 'test', qos: 2 } + const expectedResult: ISubscriptionRequest = { + topic: 'test', + qos: 2, + } if (version === 5) { expectedResult.nl = false expectedResult.rap = false @@ -2272,7 +2343,7 @@ module.exports = (server, config) => { server.once('client', (serverClient) => { serverClient.once('subscribe', (packet) => { - const result = { + const result: ISubscriptionRequest = { topic, qos: 0, } @@ -2561,7 +2632,7 @@ module.exports = (server, config) => { pubrelReceived += 1 break default: - should.fail() + fail() } }) @@ -2624,7 +2695,10 @@ module.exports = (server, config) => { client.on('packetreceive', (packet) => { if (packet.cmd === 'pubrel') { - assert.strictEqual(client.incomingStore._inflights.size, 1) + assert.strictEqual( + client.incomingStore['_inflights'].size, + 1, + ) } }) @@ -2639,7 +2713,10 @@ module.exports = (server, config) => { }) serverClient.once('pubcomp', () => { - assert.strictEqual(client.incomingStore._inflights.size, 0) + assert.strictEqual( + client.incomingStore['_inflights'].size, + 0, + ) client.removeAllListeners() client.end(true, done) }) @@ -2655,7 +2732,7 @@ module.exports = (server, config) => { let pubrelCount = 0 let handleMessageCount = 0 let emitMessageCount = 0 - const origSendPacket = client._sendPacket + const origSendPacket = client['_sendPacket'] let shouldSendFail client.handleMessage = (packet, callback) => { @@ -2667,7 +2744,7 @@ module.exports = (server, config) => { emitMessageCount++ }) - client._sendPacket = (packet, sendDone) => { + client['_sendPacket'] = (packet, sendDone) => { shouldSendFail = packet.cmd === 'pubcomp' && shouldSendPubcompFail if (sendDone) { @@ -2681,7 +2758,7 @@ module.exports = (server, config) => { // send the mocked response switch (packet.cmd) { case 'subscribe': { - const suback = { + const suback: ISubackPacket = { cmd: 'suback', messageId: packet.messageId, granted: [2], @@ -2701,14 +2778,17 @@ module.exports = (server, config) => { assert.strictEqual(pubrelCount, 2) assert.strictEqual(handleMessageCount, 1) assert.strictEqual(emitMessageCount, 1) - client._sendPacket = origSendPacket + 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 } + const pubrel: IPubrelPacket = { + cmd: 'pubrel', + messageId: mid, + } pubrelCount++ handle(client, pubrel, (err) => { if (shouldSendFail) { @@ -2725,12 +2805,14 @@ module.exports = (server, config) => { client.once('connect', () => { client.subscribe(testTopic, { qos: 2 }) - const publish = { + const publish: IPublishPacket = { cmd: 'publish', topic: testTopic, payload: testMessage, qos: 2, messageId: mid, + dup: false, + retain: false, } handle(client, publish, (err) => { assert.notExists(err) @@ -2826,12 +2908,12 @@ module.exports = (server, config) => { const client = connect() client.once('connect', () => { - assert.notExists(client.reconnectTimer) + assert.notExists(client['reconnectTimer']) client.stream.end() }) client.once('close', () => { - assert.exists(client.reconnectTimer) + assert.exists(client['reconnectTimer']) client.end(true, done) }) }) @@ -2997,7 +3079,7 @@ module.exports = (server, config) => { server.once('client', (serverClientNew) => { serverClientNew.on('publish', () => { - should.fail() + fail() done() }) }) @@ -3013,10 +3095,10 @@ module.exports = (server, config) => { ) }) assert.strictEqual(Object.keys(client.outgoing).length, 1) - assert.strictEqual(client.outgoingStore._inflights.size, 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.strictEqual(client['outgoingStore']['_inflights'].size, 0) assert.isTrue(clientCalledBack) client.end(true, (err) => { done(err) @@ -3036,7 +3118,7 @@ module.exports = (server, config) => { server.once('client', (serverClientNew) => { serverClientNew.on('publish', () => { - should.fail() + fail() done() }) }) @@ -3047,10 +3129,10 @@ module.exports = (server, config) => { assert.strictEqual(err.message, 'Message removed') }) assert.strictEqual(Object.keys(client.outgoing).length, 1) - assert.strictEqual(client.outgoingStore._inflights.size, 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.strictEqual(client['outgoingStore']['_inflights'].size, 0) assert.isTrue(clientCalledBack) client.end(true, done) }) @@ -3099,7 +3181,7 @@ module.exports = (server, config) => { server.once('client', (serverClient) => { serverClient.on('subscribe', () => { - should.fail() + fail() }) }) }) @@ -3108,7 +3190,7 @@ module.exports = (server, config) => { } else { assert.isTrue(reconnectEvent) assert.strictEqual( - Object.keys(client._resubscribeTopics).length, + Object.keys(client['_resubscribeTopics']).length, 0, ) client.end(true, done) @@ -3154,7 +3236,7 @@ module.exports = (server, config) => { server.once('client', (serverClient) => { serverClient.on('subscribe', () => { - should.fail() + fail() }) }) }) @@ -3162,7 +3244,7 @@ module.exports = (server, config) => { } else { assert.isTrue(reconnectEvent) assert.strictEqual( - Object.keys(client._resubscribeTopics).length, + Object.keys(client['_resubscribeTopics']).length, 0, ) client.end(true, (err1) => { @@ -3175,7 +3257,7 @@ module.exports = (server, config) => { it('should preserved incomingStore after disconnecting if clean is false', function _test(done) { let reconnect = false - let client = {} + let client: mqtt.MqttClient const incomingStore = new mqtt.Store({ clean: false }) const outgoingStore = new mqtt.Store({ clean: false }) const server2 = serverBuilder(config.protocol, (serverClient) => { @@ -3241,7 +3323,7 @@ module.exports = (server, config) => { it('should clear outgoing if close from server', function _test(done) { let reconnect = false - let client = {} + let client: mqtt.MqttClient const server2 = serverBuilder(config.protocol, (serverClient) => { serverClient.on('connect', (packet) => { const connack = @@ -3295,7 +3377,7 @@ module.exports = (server, config) => { it('should resend in-flight QoS 1 publish messages from the client if clean is false', function _test(done) { let reconnect = false - let client = {} + let client: mqtt.MqttClient const incomingStore = new mqtt.Store({ clean: false }) const outgoingStore = new mqtt.Store({ clean: false }) const server2 = serverBuilder(config.protocol, (serverClient) => { @@ -3343,7 +3425,7 @@ module.exports = (server, config) => { it('should resend in-flight QoS 2 publish messages from the client if clean is false', function _test(done) { let reconnect = false - let client = {} + let client: mqtt.MqttClient const incomingStore = new mqtt.Store({ clean: false }) const outgoingStore = new mqtt.Store({ clean: false }) const server2 = serverBuilder(config.protocol, (serverClient) => { @@ -3391,7 +3473,7 @@ module.exports = (server, config) => { it('should resend in-flight QoS 2 pubrel messages from the client if clean is false', function _test(done) { let reconnect = false - let client = {} + let client: mqtt.MqttClient const incomingStore = new mqtt.Store({ clean: false }) const outgoingStore = new mqtt.Store({ clean: false }) const server2 = serverBuilder(config.protocol, (serverClient) => { @@ -3455,7 +3537,7 @@ module.exports = (server, config) => { let publishCount = 0 let reconnect = false let disconnectOnce = true - let client = {} + let client: mqtt.MqttClient const incomingStore = new mqtt.Store({ clean: false }) const outgoingStore = new mqtt.Store({ clean: false }) const server2 = serverBuilder(config.protocol, (serverClient) => { @@ -3518,7 +3600,7 @@ module.exports = (server, config) => { outgoingStore, }) - client.nextId = 65535 + client['nextId'] = 65535 client.on('connect', () => { if (!reconnect) { diff --git a/test/abstract_store.js b/test/abstract_store.ts similarity index 74% rename from test/abstract_store.js rename to test/abstract_store.ts index 56a060f61..ff6105436 100644 --- a/test/abstract_store.js +++ b/test/abstract_store.ts @@ -1,7 +1,9 @@ -require('should') +import { IPublishPacket, IPubrelPacket } from 'mqtt-packet' +import Store from '../src/lib/store' +import 'should' -module.exports = function abstractStoreTest(build) { - let store +export default function abstractStoreTest(build) { + let store: Store // eslint-disable-next-line beforeEach(function (done) { @@ -16,11 +18,14 @@ module.exports = function abstractStoreTest(build) { }) it('should put and stream in-flight packets', function test(done) { - const packet = { + const packet: IPublishPacket = { topic: 'hello', payload: 'world', qos: 1, messageId: 42, + cmd: 'publish', + dup: false, + retain: false, } store.put(packet, () => { @@ -32,11 +37,14 @@ module.exports = function abstractStoreTest(build) { }) it('should support destroying the stream', function test(done) { - const packet = { + const packet: IPublishPacket = { topic: 'hello', payload: 'world', qos: 1, messageId: 42, + cmd: 'publish', + dup: false, + retain: false, } store.put(packet, () => { @@ -47,11 +55,14 @@ module.exports = function abstractStoreTest(build) { }) it('should add and del in-flight packets', function test(done) { - const packet = { + const packet: IPublishPacket = { topic: 'hello', payload: 'world', qos: 1, messageId: 42, + cmd: 'publish', + dup: false, + retain: false, } store.put(packet, () => { @@ -67,16 +78,18 @@ module.exports = function abstractStoreTest(build) { }) it('should replace a packet when doing put with the same messageId', function test(done) { - const packet1 = { + const packet1: IPublishPacket = { cmd: 'publish', // added topic: 'hello', payload: 'world', qos: 2, messageId: 42, + dup: false, + retain: false, } - const packet2 = { + const packet2: IPubrelPacket = { cmd: 'pubrel', // added - qos: 2, + // qos: 2, messageId: 42, } @@ -91,11 +104,14 @@ module.exports = function abstractStoreTest(build) { }) it('should return the original packet on del', function test(done) { - const packet = { + const packet: IPublishPacket = { topic: 'hello', payload: 'world', qos: 1, messageId: 42, + cmd: 'publish', + dup: false, + retain: false, } store.put(packet, () => { @@ -110,11 +126,14 @@ module.exports = function abstractStoreTest(build) { }) it('should get a packet with the same messageId', function test(done) { - const packet = { + const packet: IPublishPacket = { topic: 'hello', payload: 'world', qos: 1, messageId: 42, + cmd: 'publish', + dup: false, + retain: false, } store.put(packet, () => { diff --git a/test/browser/server.js b/test/browser/server.js index fe1349967..6be7295b8 100644 --- a/test/browser/server.js +++ b/test/browser/server.js @@ -5,10 +5,8 @@ const Connection = require('mqtt-connection') const http = require('http') const handleClient = (client) => { - const self = this - - if (!self.clients) { - self.clients = {} + if (!this.clients) { + this.clients = {} } client.on('connect', (packet) => { @@ -17,7 +15,7 @@ const handleClient = (client) => { } else { client.connack({ returnCode: 0 }) } - self.clients[packet.clientId] = client + this.clients[packet.clientId] = client client.subscriptions = [] }) @@ -37,8 +35,8 @@ const handleClient = (client) => { break } - for (k in self.clients) { - c = self.clients[k] + for (k in this.clients) { + c = this.clients[k] publish = false for (let i = 0; i < c.subscriptions.length; i++) { @@ -53,7 +51,7 @@ const handleClient = (client) => { try { c.publish({ topic: packet.topic, payload: packet.payload }) } catch (error) { - delete self.clients[k] + delete this.clients[k] } } } diff --git a/test/client.js b/test/client.ts similarity index 93% rename from test/client.js rename to test/client.ts index e9127174d..94f69ce00 100644 --- a/test/client.js +++ b/test/client.ts @@ -1,23 +1,26 @@ -const mqtt = require('..') -const { assert } = require('chai') -const { fork } = require('child_process') -const path = require('path') -const net = require('net') -const eos = require('end-of-stream') -const mqttPacket = require('mqtt-packet') -const { Duplex } = require('readable-stream') -const Connection = require('mqtt-connection') -const util = require('util') -const ports = require('./helpers/port_list') -const { serverBuilder } = require('./server_helpers_for_client_tests') -const debug = require('debug')('TEST:client') -const { MqttServer } = require('./server') -const abstractClientTests = require('./abstract_client') +import * as mqtt from '../src/mqtt' +import { assert } from 'chai' +import { fork } from 'child_process' +import path from 'path' +import net from 'net' +import eos from 'end-of-stream' +import mqttPacket from 'mqtt-packet' +import { Duplex } from 'readable-stream' +import Connection from 'mqtt-connection' +import util from 'util' +import ports from './helpers/port_list' +import serverBuilder from './server_helpers_for_client_tests' +import _debug from 'debug' +import { MqttServer } from './server' +import abstractClientTests from './abstract_client' +import { IClientOptions } from 'src/lib/client' + +const debug = _debug('mqttjs:client-test') describe('MqttClient', () => { let client const server = serverBuilder('mqtt') - const config = { protocol: 'mqtt', port: ports.PORT } + const config: IClientOptions = { protocol: 'mqtt', port: ports.PORT } server.listen(ports.PORT) after(() => { @@ -134,7 +137,7 @@ describe('MqttClient', () => { packets.push( mqttPacket.generate({ cmd: 'publish', - topic: Buffer.from('hello'), + topic: 'hello', payload: Buffer.from('world'), retain: false, dup: false, @@ -203,9 +206,9 @@ describe('MqttClient', () => { this.timeout(30000) const innerServer = fork( - path.join(__dirname, 'helpers', 'server_process.js'), + path.join(__dirname, 'helpers', 'server_process.ts'), { - execArgv: ['--inspect'], + execArgv: ['--inspect', '-r', 'ts-node/register'], }, ) innerServer.on('close', (code) => { @@ -376,7 +379,7 @@ describe('MqttClient', () => { ) client.end(true) serverClient.end() - server2.destroy() + server2.close() } serverClient.suback({ @@ -481,7 +484,7 @@ describe('MqttClient', () => { ) client.end(true) serverClient.destroy() - server2.destroy() + server2.close() } serverClient.puback(packet) diff --git a/test/client_mqtt5.js b/test/client_mqtt5.ts similarity index 97% rename from test/client_mqtt5.js rename to test/client_mqtt5.ts index fa1a61078..3a84d71e0 100644 --- a/test/client_mqtt5.js +++ b/test/client_mqtt5.ts @@ -1,10 +1,10 @@ -const { assert } = require('chai') -const mqtt = require('..') -const abstractClientTests = require('./abstract_client') -const { MqttServer } = require('./server') -const { serverBuilder } = require('./server_helpers_for_client_tests') -const ports = require('./helpers/port_list') -const { close } = require('inspector') +import { assert } from 'chai' +import * as mqtt from '../src/mqtt' +import abstractClientTests from './abstract_client' +import { MqttServer } from './server' +import serverBuilder from './server_helpers_for_client_tests' +import ports from './helpers/port_list' +import { ErrorWithReasonCode } from '../src/lib/shared' describe('MQTT 5.0', () => { const server = serverBuilder('mqtt').listen(ports.PORTAND115) @@ -937,13 +937,18 @@ describe('MQTT 5.0', () => { } 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.publish( + 'a/b', + 'message', + { qos: 1 }, + (err: ErrorWithReasonCode) => { + assert.strictEqual( + err.message, + 'Publish error: Session taken over', + ) + assert.strictEqual(err.code, 142) + }, + ) client.end(true, (err1) => { serverThatSendsErrors.close((err2) => { done(err1 || err2) @@ -962,13 +967,18 @@ describe('MQTT 5.0', () => { } 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.publish( + 'a/b', + 'message', + { qos: 2 }, + (err: ErrorWithReasonCode) => { + assert.strictEqual( + err.message, + 'Publish error: Session taken over', + ) + assert.strictEqual(err.code, 142) + }, + ) client.end(true, (err1) => { serverThatSendsErrors.close((err2) => { done(err1 || err2) diff --git a/test/helpers/port_list.js b/test/helpers/port_list.ts similarity index 98% rename from test/helpers/port_list.js rename to test/helpers/port_list.ts index a8a0616dd..b4aed27d1 100644 --- a/test/helpers/port_list.js +++ b/test/helpers/port_list.ts @@ -23,7 +23,7 @@ const PORTAND326 = PORT + 326 const PORTAND327 = PORT + 327 const PORTAND400 = PORT + 400 -module.exports = { +export default { PORT, PORTAND40, PORTAND41, diff --git a/test/helpers/server.js b/test/helpers/server.ts similarity index 79% rename from test/helpers/server.js rename to test/helpers/server.ts index 112c9a8a4..02a2fc103 100644 --- a/test/helpers/server.js +++ b/test/helpers/server.ts @@ -1,8 +1,7 @@ -const fs = require('fs') -const { MqttServer } = require('../server') -const { MqttSecureServer } = require('../server') +import fs from 'fs' +import { MqttServer, MqttSecureServer } from '../server' -module.exports.init_server = (PORT) => { +export function init_server(PORT: number) { const server = new MqttServer((client) => { client.on('connect', () => { client.connack(0) @@ -37,7 +36,7 @@ module.exports.init_server = (PORT) => { return server } -module.exports.init_secure_server = (port, key, cert) => { +export function init_secure_server(port: number, key: string, cert: string) { const server = new MqttSecureServer( { key: fs.readFileSync(key), diff --git a/test/helpers/server_process.js b/test/helpers/server_process.ts similarity index 74% rename from test/helpers/server_process.js rename to test/helpers/server_process.ts index 60df9a987..29a7fb768 100644 --- a/test/helpers/server_process.js +++ b/test/helpers/server_process.ts @@ -1,4 +1,4 @@ -const { MqttServer } = require('../server') +import { MqttServer } from '../server' new MqttServer((client) => { client.on('connect', () => { diff --git a/test/message-id-provider.js b/test/message-id-provider.ts similarity index 92% rename from test/message-id-provider.js rename to test/message-id-provider.ts index 8ac0163ca..8068604aa 100644 --- a/test/message-id-provider.js +++ b/test/message-id-provider.ts @@ -1,12 +1,12 @@ -const { assert } = require('chai') -const DefaultMessageIdProvider = require('../lib/default-message-id-provider') -const UniqueMessageIdProvider = require('../lib/unique-message-id-provider') +import { assert } from 'chai' +import DefaultMessageIdProvider from '../src/lib/default-message-id-provider' +import UniqueMessageIdProvider from '../src/lib/unique-message-id-provider' describe('message id provider', () => { describe('default', () => { it('should return 1 once the internal counter reached limit', () => { const provider = new DefaultMessageIdProvider() - provider.nextId = 65535 + provider['nextId'] = 65535 assert.equal(provider.allocate(), 65535) assert.equal(provider.allocate(), 1) @@ -14,7 +14,7 @@ describe('message id provider', () => { it('should return 65535 for last message id once the internal counter reached limit', () => { const provider = new DefaultMessageIdProvider() - provider.nextId = 65535 + provider['nextId'] = 65535 assert.equal(provider.allocate(), 65535) assert.equal(provider.getLastAllocated(), 65535) diff --git a/test/mocha.opts b/test/mocha.opts deleted file mode 100644 index 4008c54c1..000000000 --- a/test/mocha.opts +++ /dev/null @@ -1,4 +0,0 @@ ---check-leaks ---timeout 10000 ---exit - diff --git a/test/mqtt.js b/test/mqtt.ts similarity index 96% rename from test/mqtt.js rename to test/mqtt.ts index aa3bc4465..52e97eca4 100644 --- a/test/mqtt.js +++ b/test/mqtt.ts @@ -1,6 +1,7 @@ -const fs = require('fs') -const path = require('path') -const mqtt = require('..') +import fs from 'fs' +import path from 'path' +import * as mqtt from '../src/mqtt' +import { IClientOptions } from '../src/lib/client' describe('mqtt', () => { describe('#connect', () => { @@ -79,7 +80,7 @@ describe('mqtt', () => { c.end((err) => done(err)) }) - const sslOpts = { + const sslOpts: IClientOptions = { keyPath: path.join(__dirname, 'helpers', 'private-key.pem'), certPath: path.join(__dirname, 'helpers', 'public-cert.pem'), caPaths: [path.join(__dirname, 'helpers', 'public-cert.pem')], @@ -129,7 +130,7 @@ describe('mqtt', () => { c.end((err) => done(err)) }) - const sslOpts2 = { + const sslOpts2: IClientOptions = { key: fs.readFileSync( path.join(__dirname, 'helpers', 'private-key.pem'), ), @@ -152,7 +153,7 @@ describe('mqtt', () => { 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' + ;(sslOpts2 as any).protocol = 'UNKNOWNPROTOCOL' mqtt.connect(sslOpts2) }).should.throw() }) diff --git a/test/mqtt_store.js b/test/mqtt_store.ts similarity index 54% rename from test/mqtt_store.js rename to test/mqtt_store.ts index d5c6dff40..4e68b7091 100644 --- a/test/mqtt_store.js +++ b/test/mqtt_store.ts @@ -1,9 +1,9 @@ -const mqtt = require('../lib/connect') +import Store from '../src/lib/store' 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) + const store = new Store() + store.should.be.instanceOf(Store) done() }) }) diff --git a/test/secure_client.js b/test/secure_client.ts similarity index 91% rename from test/secure_client.js rename to test/secure_client.ts index 630408269..f0506734a 100644 --- a/test/secure_client.js +++ b/test/secure_client.ts @@ -1,16 +1,17 @@ -const path = require('path') -const fs = require('fs') -const mqtt = require('..') -const abstractClientTests = require('./abstract_client') +import path from 'path' +import fs from 'fs' +import * as mqtt from '../src/mqtt' +import abstractClientTests from './abstract_client' +import { MqttSecureServer, MqttServerListener } from './server' +import { assert } from 'chai' +import 'should' 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') -const { assert } = require('chai') -const serverListener = (client) => { +const serverListener: MqttServerListener = (client) => { // this is the Server's MQTT Client client.on('connect', (packet) => { if (packet.clientId === 'invalid') { @@ -162,7 +163,7 @@ describe('MqttSecureClient', () => { server.removeAllListeners('secureConnection') // clear eventHandler server.once('secureConnection', (tlsSocket) => { // one time eventHandler - assert.equal(tlsSocket.servername, hostname) // validate SNI set + assert.equal((tlsSocket as any).servername, hostname) // validate SNI set server.setupConnection(tlsSocket) }) diff --git a/test/server.js b/test/server.ts similarity index 69% rename from test/server.js rename to test/server.ts index 8a3732eed..8c4ec0370 100644 --- a/test/server.js +++ b/test/server.ts @@ -1,14 +1,19 @@ -const net = require('net') -const tls = require('tls') -const Connection = require('mqtt-connection') +import net from 'net' +import tls, { TlsOptions } from 'tls' +import Connection from 'mqtt-connection' +import { Duplex } from 'stream' + +export type MqttServerListener = (client: Connection) => void /** * MqttServer * * @param {Function} listener - fired on client connection */ -class MqttServer extends net.Server { - constructor(listener) { +export class MqttServer extends net.Server { + connectionList: Duplex[] + + constructor(listener: MqttServerListener) { super() this.connectionList = [] @@ -30,8 +35,10 @@ class MqttServer extends net.Server { * * @param {Function} listener - fired on client connection */ -class MqttServerNoWait extends net.Server { - constructor(listener) { +export class MqttServerNoWait extends net.Server { + connectionList: Duplex[] + + constructor(listener: MqttServerListener) { super() this.connectionList = [] @@ -54,8 +61,10 @@ class MqttServerNoWait extends net.Server { * @param {Object} opts - server options * @param {Function} listener */ -class MqttSecureServer extends tls.Server { - constructor(opts, listener) { +export class MqttSecureServer extends tls.Server { + connectionList: Duplex[] + + constructor(opts: TlsOptions, listener: MqttServerListener) { if (typeof opts === 'function') { listener = opts opts = {} @@ -77,13 +86,9 @@ class MqttSecureServer extends tls.Server { } } - setupConnection(duplex) { + setupConnection(duplex: Duplex) { const connection = new Connection(duplex, () => { this.emit('client', connection) }) } } - -exports.MqttServer = MqttServer -exports.MqttServerNoWait = MqttServerNoWait -exports.MqttSecureServer = MqttSecureServer diff --git a/test/server_helpers_for_client_tests.js b/test/server_helpers_for_client_tests.ts similarity index 85% rename from test/server_helpers_for_client_tests.js rename to test/server_helpers_for_client_tests.ts index c1a9685b3..833f23eb3 100644 --- a/test/server_helpers_for_client_tests.js +++ b/test/server_helpers_for_client_tests.ts @@ -1,16 +1,18 @@ -const { MqttServer } = require('./server') -const debug = require('debug')('TEST:server_helpers') +import { MqttServer, MqttSecureServer, MqttServerListener } from './server' +import _debug from 'debug' -const path = require('path') -const fs = require('fs') +import path from 'path' +import fs from 'fs' + +import http from 'http' +import WebSocket from 'ws' +import MQTTConnection from 'mqtt-connection' +import { Server } from 'net' 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') +const debug = _debug('mqttjs:server_helpers_for_client_tests') /** * This will build the client for the server to use during testing, and set up the @@ -18,8 +20,11 @@ const { MqttSecureServer } = require('./server') * @param {String} protocol - 'mqtt', 'mqtts' or 'ws' * @param {Function} handler - event handler */ -function serverBuilder(protocol, handler) { - const defaultHandler = (serverClient) => { +export default function serverBuilder( + protocol: string, + handler?: MqttServerListener, +): Server { + const defaultHandler: MqttServerListener = (serverClient) => { serverClient.on('auth', (packet) => { if (serverClient.writable) return false const rc = 'reasonCode' @@ -130,7 +135,7 @@ function serverBuilder(protocol, handler) { }) webSocketServer.on('connection', (ws) => { - server.connectionList.push(ws) + // server.connectionList.push(ws) const stream = WebSocket.createWebSocketStream(ws) const connection = new MQTTConnection(stream) connection.protocol = ws.protocol @@ -142,11 +147,9 @@ function serverBuilder(protocol, handler) { } const httpServer = http.createServer() - httpServer.connectionList = [] + // httpServer.connectionList = [] attachWebsocketServer(httpServer) httpServer.on('client', handler) return httpServer } } - -exports.serverBuilder = serverBuilder diff --git a/test/store.js b/test/store.ts similarity index 54% rename from test/store.js rename to test/store.ts index 6788039fe..5e37ef01e 100644 --- a/test/store.js +++ b/test/store.ts @@ -1,5 +1,5 @@ -const Store = require('../lib/store') -const abstractTest = require('./abstract_store') +import Store from '../src/lib/store' +import abstractTest from './abstract_store' describe('in-memory store', () => { abstractTest(function test(done) { diff --git a/test/typescript/broker-connect-subscribe-and-publish.ts b/test/typescript/broker-connect-subscribe-and-publish.ts deleted file mode 100644 index 1ef2eb594..000000000 --- a/test/typescript/broker-connect-subscribe-and-publish.ts +++ /dev/null @@ -1,33 +0,0 @@ -// relative path uses package.json {"types":"types/index.d.ts", ...} -import test from 'tape' -import {IClientOptions, Client, connect, IConnackPacket, UniqueMessageIdProvider} from '../..' -const BROKER = 'test.mosquitto.org' - -const PAYLOAD_WILL = Buffer.from('bye from TS') -const PAYLOAD_QOS = Buffer.from('hello from TS (with qos=2)') -const PAYLOAD_RETAIN = 'hello from TS (with retain=true)' -const TOPIC = 'typescript-test-' + Math.random().toString(16).substr(2) -const opts: IClientOptions = {will: {topic: TOPIC, payload: PAYLOAD_WILL, qos: 0, retain: false}, - messageIdProvider: new UniqueMessageIdProvider()} - -test('client test', function (t) { - t.plan(1) - console.log(`connect(${JSON.stringify(BROKER)})`) - const client:Client = connect(`mqtt://${BROKER}`, opts) - client.subscribe({[TOPIC]: {qos: 2}}, (err, granted) => { - granted.forEach(({topic, qos}) => { - console.log(`subscribed to ${topic} with qos=${qos}`) - }) - client.publish(TOPIC, PAYLOAD_QOS, {qos: 2}) - client.publish(TOPIC, PAYLOAD_RETAIN, {retain: true}) - }).on('message', (topic: string, payload: Buffer) => { - t.ok(payload) - console.log(`message from ${topic}: ${payload}`) - client.end(true, () => { - console.log('client closed') - t.end() - }) - }).on('connect', (packet: IConnackPacket) => { - console.log('connected!', JSON.stringify(packet)) - }) -}) diff --git a/test/typescript/tsconfig.json b/test/typescript/tsconfig.json deleted file mode 100644 index 23326816e..000000000 --- a/test/typescript/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "es5", - "moduleResolution": "node", - "noEmitOnError": true, - "noImplicitAny": true, - "alwaysStrict": true, - "strictNullChecks": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "sourceMap": true, - "esModuleInterop": true - } -} diff --git a/test/unique_message_id_provider_client.js b/test/unique_message_id_provider_client.ts similarity index 62% rename from test/unique_message_id_provider_client.js rename to test/unique_message_id_provider_client.ts index c67d99b9f..028369f4f 100644 --- a/test/unique_message_id_provider_client.js +++ b/test/unique_message_id_provider_client.ts @@ -1,7 +1,7 @@ -const abstractClientTests = require('./abstract_client') -const { serverBuilder } = require('./server_helpers_for_client_tests') -const UniqueMessageIdProvider = require('../lib/unique-message-id-provider') -const ports = require('./helpers/port_list') +import abstractClientTests from './abstract_client' +import serverBuilder from './server_helpers_for_client_tests' +import UniqueMessageIdProvider from '../src/lib/unique-message-id-provider' +import ports from './helpers/port_list' describe('UniqueMessageIdProviderMqttClient', () => { const server = serverBuilder('mqtt') diff --git a/test/util.js b/test/util.ts similarity index 55% rename from test/util.js rename to test/util.ts index 922c2b334..dc2c7c16e 100644 --- a/test/util.js +++ b/test/util.ts @@ -1,6 +1,6 @@ -const { Transform } = require('readable-stream') +import { Transform } from 'readable-stream' -module.exports.testStream = () => { +const testStream = () => { return new Transform({ transform(buf, enc, cb) { setImmediate(() => { @@ -10,3 +10,5 @@ module.exports.testStream = () => { }, }) } + +export default testStream diff --git a/test/websocket_client.js b/test/websocket_client.ts similarity index 87% rename from test/websocket_client.js rename to test/websocket_client.ts index 06516ca4c..52ea03a03 100644 --- a/test/websocket_client.js +++ b/test/websocket_client.ts @@ -1,11 +1,12 @@ -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') -const mqtt = require('..') +import http from 'http' +import WebSocket from 'ws' +import MQTTConnection from 'mqtt-connection' +import assert from 'assert' +import abstractClientTests from './abstract_client' +import ports from './helpers/port_list' +import { MqttServerNoWait } from './server' +import * as mqtt from '../src/mqtt' +import { IClientOptions } from '../src/lib/client' const port = 9999 const httpServer = http.createServer() @@ -86,9 +87,9 @@ attachWebsocketServer(httpServer) httpServer.on('client', attachClientEventHandlers).listen(port) describe('Websocket Client', () => { - const baseConfig = { protocol: 'ws', port } + const baseConfig: IClientOptions = { protocol: 'ws', port } - function makeOptions(custom) { + function makeOptions(custom?: IClientOptions): IClientOptions { return { ...baseConfig, ...(custom || {}) } } @@ -107,7 +108,7 @@ describe('Websocket Client', () => { const baseUrl = 'ws://localhost:9999/mqtt' const sig = '?AUTH=token' const expected = baseUrl + sig - let actual + let actual: string const opts = makeOptions({ path: '/mqtt', transformWsUrl(url, opt, client) { @@ -124,7 +125,8 @@ describe('Websocket Client', () => { const client = mqtt.connect(opts) client.on('connect', () => { - assert.equal(client.stream.url, expected) + // `url` is set in `connect/ws.ts` `streamBuilder` + assert.equal((client.stream as any).url, expected) assert.equal(actual, expected) client.end(true, (err) => done(err)) }) @@ -180,7 +182,7 @@ describe('Websocket Client', () => { }) serverPort41.once('client', (c) => { assert.equal( - client.stream.url, + (client.stream as any).url, actualURL41, 'Protocol for second client should use the default protocol: wss, on port: port + 41.', ) @@ -195,7 +197,7 @@ describe('Websocket Client', () => { serverPort42.once('client', (c) => { serverPort42Connected = true assert.equal( - client.stream.url, + (client.stream as any).url, actualURL42, 'Protocol for connection should use ws, on port: port + 42.', ) diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 000000000..a042068ba --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "build"], + } + \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..827f3b0ed --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "es2017", + "sourceMap": true, + "outDir": "./build", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "preserveSymlinks": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "typeRoots": [ + "node_modules/@types" + ], + "types": [ + "node", + "mocha" + ], + }, + "include": [ + "src", + "test", + ], + "exclude": [ + "dist", + "test/browser" + ] +} \ No newline at end of file diff --git a/types/index.d.ts b/types/index.d.ts deleted file mode 100644 index a3496b103..000000000 --- a/types/index.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -export * from './lib/client' -export * from './lib/connect' -export * from './lib/store' -export * from './lib/client-options' -import { MqttClient } from './lib/client' -export { MqttClient as Client } -export { - QoS, - PacketCmd, - IPacket, - IConnectPacket, - IPublishPacket, - IConnackPacket, - ISubscription, - ISubscribePacket, - ISubackPacket, - IUnsubscribePacket, - IUnsubackPacket, - IPubackPacket, - IPubcompPacket, - IPubrelPacket, - IPubrecPacket, - IPingreqPacket, - IPingrespPacket, - IDisconnectPacket, - Packet, - UserProperties -} from 'mqtt-packet' -export { IMessageIdProvider } from './lib/message-id-provider' -export { UniqueMessageIdProvider } from './lib/unique-message-id-provider' diff --git a/types/lib/client-options.d.ts b/types/lib/client-options.d.ts deleted file mode 100644 index c71ea344f..000000000 --- a/types/lib/client-options.d.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { MqttClient } from './client' -import { Store } from './store' -import { ClientOptions } from 'ws' -import { ClientRequestArgs } from 'http' -import { QoS, UserProperties } from 'mqtt-packet' -import { IMessageIdProvider } from './message-id-provider' - -export declare type StorePutCallback = () => void - -export interface IClientOptions extends ISecureClientOptions { - port?: number // port is made into a number subsequently - host?: string // host does NOT include port - hostname?: string - path?: string - protocol?: 'wss' | 'ws' | 'mqtt' | 'mqtts' | 'tcp' | 'ssl' | 'wx' | 'wxs' - - wsOptions?: ClientOptions | ClientRequestArgs - /** - * 10 seconds, set to 0 to disable - */ - keepalive?: number - /** - * 'mqttjs_' + Math.random().toString(16).substr(2, 8) - */ - clientId?: string - /** - * 'MQTT' - */ - protocolId?: string - /** - * 4 - */ - protocolVersion?: number - /** - * true, set to false to receive QoS 1 and 2 messages while offline - */ - clean?: boolean - /** - * 1000 milliseconds, interval between two reconnections - */ - reconnectPeriod?: number - /** - * 30 * 1000 milliseconds, time to wait before a CONNACK is received - */ - connectTimeout?: number - /** - * the username required by your broker, if any - */ - username?: string - /** - * the password required by your broker, if any - */ - password?: Buffer | string - /** - * a Store for the incoming packets - */ - incomingStore?: Store - /** - * a Store for the outgoing packets - */ - outgoingStore?: Store - queueQoSZero?: boolean - - log?: (...args: any[]) => void - - autoUseTopicAlias?: boolean - autoAssignTopicAlias?: boolean - - reschedulePings?: boolean - servers?: Array<{ - host: string - port: number - protocol?: 'wss' | 'ws' | 'mqtt' | 'mqtts' | 'tcp' | 'ssl' | 'wx' | 'wxs' - }> - /** - * true, set to false to disable re-subscribe functionality - */ - resubscribe?: boolean - /** - * a message that will sent by the broker automatically when the client disconnect badly. - */ - will?: { - /** - * the topic to publish - */ - topic: string - /** - * the message to publish - */ - payload: Buffer | string - /** - * the QoS - */ - qos: QoS - /** - * the retain flag - */ - retain: boolean, - /* - * properies object of will - * */ - properties?: { - willDelayInterval?: number, - payloadFormatIndicator?: boolean, - messageExpiryInterval?: number, - contentType?: string, - 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?: { - sessionExpiryInterval?: number, - receiveMaximum?: number, - maximumPacketSize?: number, - topicAliasMaximum?: number, - requestResponseInformation?: boolean, - requestProblemInformation?: boolean, - userProperties?: UserProperties, - authenticationMethod?: string, - authenticationData?: Buffer - }, - messageIdProvider?: IMessageIdProvider -} -export interface ISecureClientOptions { - /** - * optional private keys in PEM format - */ - key?: string | string[] | Buffer | Buffer[] | Object[] - /** - * optional cert chains in PEM format - */ - cert?: string | string[] | Buffer | Buffer[] - /** - * Optionally override the trusted CA certificates in PEM format - */ - ca?: string | string[] | Buffer | Buffer[] - rejectUnauthorized?: boolean - /** - * optional alpn's - */ - ALPNProtocols?: string[] | Buffer[] | Uint8Array[] | Buffer | Uint8Array -} -export interface IClientPublishOptions { - /** - * the QoS - */ - qos?: QoS - /** - * the retain flag - */ - retain?: boolean - /** - * whether or not mark a message as duplicate - */ - dup?: boolean - /* - * MQTT 5.0 properties object - */ - properties?: { - payloadFormatIndicator?: boolean, - messageExpiryInterval?: number, - topicAlias?: number, - responseTopic?: string, - correlationData?: Buffer, - userProperties?: UserProperties, - subscriptionIdentifier?: number, - contentType?: string - } - /** - * callback called when message is put into `outgoingStore` - */ - cbStorePut?: StorePutCallback -} -export interface IClientSubscribeOptions { - /** - * the QoS - */ - qos: QoS, - /* - * no local flag - * */ - nl?: boolean, - /* - * Retain As Published flag - * */ - rap?: boolean, - /* - * Retain Handling option - * */ - rh?: number, - /* - * MQTT 5.0 properies object of subscribe - * */ - properties?: { - subscriptionIdentifier?: number, - userProperties?: UserProperties - } -} -export interface IClientSubscribeProperties { - /* - * MQTT 5.0 properies object of subscribe - * */ - properties?: { - subscriptionIdentifier?: number, - userProperties?: UserProperties - } -} -export interface IClientReconnectOptions { - /** - * a Store for the incoming packets - */ - incomingStore?: Store - /** - * a Store for the outgoing packets - */ - outgoingStore?: Store -} diff --git a/types/lib/client.d.ts b/types/lib/client.d.ts deleted file mode 100644 index d19c5febc..000000000 --- a/types/lib/client.d.ts +++ /dev/null @@ -1,273 +0,0 @@ -/// - -import * as events from 'events' -import { - IClientOptions, - IClientPublishOptions, - IClientSubscribeOptions, - IClientSubscribeProperties, - IClientReconnectOptions -} from './client-options' -import { Store } from './store' -import { IAuthPacket, IPublishPacket, IDisconnectPacket, IConnackPacket, Packet, QoS } from 'mqtt-packet' - -export interface ISubscriptionGrant { - /** - * is a subscribed to topic - */ - topic: string - /** - * is the granted qos level on it, may return 128 on error - */ - qos: QoS | number - /* - * no local flag - * */ - nl?: boolean, - /* - * Retain As Published flag - * */ - rap?: boolean, - /* - * Retain Handling option - * */ - rh?: number -} -export interface ISubscriptionRequest { - /** - * is a subscribed to topic - */ - topic: string - /** - * is the granted qos level on it - */ - qos: QoS - /* - * no local flag - * */ - nl?: boolean, - /* - * Retain As Published flag - * */ - rap?: boolean, - /* - * Retain Handling option - * */ - rh?: number -} -export interface ISubscriptionMap { - /** - * object which has topic names as object keys and as value the options, like {'test1': {qos: 0}, 'test2': {qos: 2}}. - */ - [topic: string]: { - qos: QoS, - nl?: boolean, - rap?: boolean, - rh?: number - } -} - -export declare type OnConnectCallback = (packet: IConnackPacket) => void -export declare type OnDisconnectCallback = (packet: IDisconnectPacket) => void -export declare type ClientSubscribeCallback = (err: Error | null, granted: ISubscriptionGrant[]) => void -export declare type OnMessageCallback = (topic: string, payload: Buffer, packet: IPublishPacket) => void -export declare type OnPacketCallback = (packet: Packet) => void -export declare type OnCloseCallback = () => void -export declare type OnErrorCallback = (error: Error) => void -export declare type PacketCallback = (error?: Error, packet?: Packet) => any -export declare type CloseCallback = (error?: Error) => void - -export interface IStream extends events.EventEmitter { - pipe (to: any): any - destroy (): any - end (): any -} -/** - * MqttClient constructor - * - * @param {Stream} stream - stream - * @param {Object} [options] - connection options - * (see Connection#connect) - */ -export declare class MqttClient extends events.EventEmitter { - public connected: boolean - public disconnecting: boolean - public disconnected: boolean - public reconnecting: boolean - public incomingStore: Store - public outgoingStore: Store - public options: IClientOptions - public queueQoSZero: boolean - - static defaultId (): string - - constructor (streamBuilder: (client: MqttClient) => IStream, options: IClientOptions) - - public on (event: 'connect', cb: OnConnectCallback): this - public on (event: 'message', cb: OnMessageCallback): this - public on (event: 'packetsend' | 'packetreceive', cb: OnPacketCallback): this - public on (event: 'disconnect', cb: OnDisconnectCallback): this - public on (event: 'error', cb: OnErrorCallback): this - public on (event: 'close', cb: OnCloseCallback): this - public on (event: 'end' | 'reconnect' | 'offline' | 'outgoingEmpty', cb: () => void): this - public on (event: string, cb: Function): this - - public once (event: 'connect', cb: OnConnectCallback): this - public once (event: 'message', cb: OnMessageCallback): this - public once (event: 'packetsend' | 'packetreceive', cb: OnPacketCallback): this - public once (event: 'disconnect', cb: OnDisconnectCallback): this - public once (event: 'error', cb: OnErrorCallback): this - public once (event: 'close', cb: OnCloseCallback): this - 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 - * - * @param {String} topic - topic to publish to - * @param {(String|Buffer)} message - message to publish - * - * @param {Object} [opts] - publish options, includes: - * @param {Number} [opts.qos] - qos level to publish on - * @param {Boolean} [opts.retain] - whether or not to retain the message - * @param {Function}[opts.cbStorePut] - function(){} - * called when message is put into `outgoingStore` - * - * @param {Function} [callback] - function(err){} - * called when publish succeeds or fails - * - * @returns {Client} this - for chaining - * @api public - * - * @example client.publish('topic', 'message') - * @example - * client.publish('topic', 'message', {qos: 1, retain: true}) - * @example client.publish('topic', 'message', console.log) - */ - public publish (topic: string, message: string | Buffer, - opts: IClientPublishOptions, callback?: PacketCallback): this - public publish (topic: string, message: string | Buffer, - callback?: PacketCallback): 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: - * @param {Number} [opts.qos] - subscribe qos level - * @param {Function} [callback] - function(err, granted){} where: - * {Error} err - subscription error (none at the moment!) - * {Array} granted - array of {topic: 't', qos: 0} - * @returns {MqttClient} this - for chaining - * @api public - * @example client.subscribe('topic') - * @example client.subscribe('topic', {qos: 1}) - * @example client.subscribe({'topic': 0, 'topic2': 1}, console.log) - * @example client.subscribe('topic', console.log) - */ - public subscribe (topic: - string - | string[] - | ISubscriptionMap, - opts: - IClientSubscribeOptions - | IClientSubscribeProperties, callback?: ClientSubscribeCallback): this - public subscribe (topic: - string - | string[] - | ISubscriptionMap, callback?: ClientSubscribeCallback): this - - /** - * unsubscribe - unsubscribe from topic(s) - * - * @param {String, Array} topic - topics to unsubscribe from - * @param {Object} opts - opts of unsubscribe - * @param {Function} [callback] - callback fired on unsuback - * @returns {MqttClient} this - for chaining - * @api public - * @example client.unsubscribe('topic') - * @example client.unsubscribe('topic', console.log) - * @example client.unsubscribe('topic', opts, console.log) - */ - public unsubscribe (topic: string | string[], opts?: Object, callback?: PacketCallback): 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 - opts disconnect - * @param {Function} cb - called when the client has been closed - * - * @api public - */ - public end (force?: boolean, opts?: Object, cb?: CloseCallback): this - - /** - * removeOutgoingMessage - remove a message in outgoing store - * the outgoing callback will be called withe Error('Message removed') if the message is removed - * - * @param {Number} mid - messageId to remove message - * @returns {MqttClient} this - for chaining - * @api public - * - * @example client.removeOutgoingMessage(client.getLastMessageId()); - */ - public removeOutgoingMessage (mid: number): 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 - */ - public reconnect (opts?: IClientReconnectOptions): this - - /** - * Handle messages with backpressure support, one at a time. - * Override at will. - * - * @param packet packet the packet - * @param callback callback call when finished - * @api public - */ - public handleMessage (packet: Packet, callback: PacketCallback): void - - /** - * Handle auth packages for MQTT 5 enhanced authentication methods such - * as challenge response authentication. - * - * Challenge-response authentication flow would look something like this: - * - * --> CONNECT | authMethod = "mathChallenge" --> - * <-- AUTH | authMethod = "mathChallenge", authData = "12 + 34" <-- - * --> AUTH | authMethod = "mathChallenge", authData = "46" --> - * <-- CONNACK | reasonCode = SUCCESS <-- - * - * This form of authentication has several advantages over traditional - * credential-based approaches. For instance authentication without the direct - * exchange of authentication secrets. - * - * @param packet the auth packet to handle - * @param callback call when finished - * @api public - */ - public handleAuth (packet: IAuthPacket, callback: PacketCallback): void - - /** - * getLastMessageId - */ - public getLastMessageId (): number -} -export { IClientOptions } diff --git a/types/lib/connect/index.d.ts b/types/lib/connect/index.d.ts deleted file mode 100644 index bbcaf97a0..000000000 --- a/types/lib/connect/index.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { IClientOptions, MqttClient } from '../client' - -/** - * connect - connect to an MQTT broker. - * - * @param {Object} opts - see MqttClient#constructor - */ -declare function connect (opts: IClientOptions): MqttClient - -/** - * connect - connect to an MQTT broker. - * - * @param {String} brokerUrl - url of the broker - * @param {Object} opts - see MqttClient#constructor - */ -declare function connect (brokerUrl: string, opts?: IClientOptions): MqttClient - -export { connect } -export { MqttClient } diff --git a/types/lib/default-message-id-provider.d.ts b/types/lib/default-message-id-provider.d.ts deleted file mode 100644 index fafaa4c9b..000000000 --- a/types/lib/default-message-id-provider.d.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { IMessageIdProvider } from './message-id-provider' - -/** - * DefaultMessageIdProvider - * This is compatible behavior with the original MQTT.js internal messageId allocation. - */ -declare class DefaultMessageIdProvider implements IMessageIdProvider { - /** - * DefaultMessageIdProvider constructor. - * Randomize initial messageId - * @constructor - */ - constructor () - - /** - * Return the current messageId and increment the current messageId. - * @return {Number} - messageId - */ - public allocate (): Number | null - - /** - * Get the last allocated messageId. - * @return {Number} - messageId. - */ - public getLastAllocated (): Number | null - - /** - * Register the messageId. - * This function actually nothing and always return true. - * @param {Number} num - The messageId to request use. - * @return {Boolean} - If `num` was not occupied, then return true, otherwise return false. - */ - public register (num: Number): Boolean - - /** - * Deallocate the messageId. - * This function actually nothing. - * @param {Number} num - The messageId to deallocate. - */ - public deallocate (num: Number): void - - /** - * Clear all occupied messageIds. - * This function actually nothing. - */ - public clear (): void -} - -export { DefaultMessageIdProvider } diff --git a/types/lib/message-id-provider.d.ts b/types/lib/message-id-provider.d.ts deleted file mode 100644 index 9468cf3e2..000000000 --- a/types/lib/message-id-provider.d.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * MessageIdProvider - */ -declare interface IMessageIdProvider { - /** - * Allocate the first vacant messageId. The messageId become occupied status. - * @return {Number} - The first vacant messageId. If all messageIds are occupied, return null. - */ - allocate (): Number | null - - /** - * Get the last allocated messageId. - * @return {Number} - messageId. - */ - getLastAllocated (): Number | null - - /** - * Register the messageId. The messageId become occupied status. - * If the messageId has already been occupied, then return false. - * @param {Number} num - The messageId to request use. - * @return {Boolean} - If `num` was not occupied, then return true, otherwise return false. - */ - register (num: Number): Boolean - - /** - * Deallocate the messageId. The messageId become vacant status. - * @param {Number} num - The messageId to deallocate. The messageId must be occupied status. - * In other words, the messageId must be allocated by allocate() or - * occupied by register(). - */ - deallocate (num: Number): void - - /** - * Clear all occupied messageIds. - * The all messageIds are set to vacant status. - */ - clear (): void -} - -export { IMessageIdProvider } diff --git a/types/lib/store-options.d.ts b/types/lib/store-options.d.ts deleted file mode 100644 index 03a175e7f..000000000 --- a/types/lib/store-options.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface IStoreOptions { - /** - * true, clear _inflights at close - */ - clean?: boolean -} diff --git a/types/lib/store.d.ts b/types/lib/store.d.ts deleted file mode 100644 index dd4c17f20..000000000 --- a/types/lib/store.d.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { - IStoreOptions -} from './store-options' - -/** - * In-memory implementation of the message store - * This can actually be saved into files. - * - */ -declare class Store { - /** - * Store constructor - * - * @param {Object} [options] - store options - */ - constructor (options: IStoreOptions) - - /** - * Adds a packet to the store, a packet is - * anything that has a messageId property. - * - */ - public put (packet: any, cb?: Function): this - - /** - * Creates a stream with all the packets in the store - * - */ - public createStream (): any - - /** - * deletes a packet from the store. - */ - public del (packet: any, cb: Function): this - - /** - * get a packet from the store. - */ - public get (packet: any, cb: Function): this - - /** - * Close the store - */ - public close (cb: Function): void -} -export { Store } diff --git a/types/lib/unique-message-id-provider.d.ts b/types/lib/unique-message-id-provider.d.ts deleted file mode 100644 index 0941b2865..000000000 --- a/types/lib/unique-message-id-provider.d.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { IMessageIdProvider } from './message-id-provider' - -/** - * UniqueMessageIdProvider - */ -declare class UniqueMessageIdProvider implements IMessageIdProvider { - /** - * UniqueMessageIdProvider constructor. - * @constructor - */ - constructor () - - /** - * Allocate the first vacant messageId. The messageId become occupied status. - * @return {Number} - The first vacant messageId. If all messageIds are occupied, return null. - */ - public allocate (): Number | null - - /** - * Get the last allocated messageId. - * @return {Number} - messageId. - */ - public getLastAllocated (): Number | null - - /** - * Register the messageId. The messageId become occupied status. - * If the messageId has already been occupied, then return false. - * @param {Number} num - The messageId to request use. - * @return {Boolean} - If `num` was not occupied, then return true, otherwise return false. - */ - public register (num: Number): Boolean - - /** - * Deallocate the messageId. The messageId become vacant status. - * @param {Number} num - The messageId to deallocate. The messageId must be occupied status. - * In other words, the messageId must be allocated by allocate() or - * occupied by register(). - */ - public deallocate (num: Number): void - - /** - * Clear all occupied messageIds. - * The all messageIds are set to vacant status. - */ - public clear (): void -} - -export { UniqueMessageIdProvider }