├── .eslintrc.json
├── .gitignore
├── LICENSE
├── README.md
├── index.d.ts
├── index.js
├── package.json
└── test.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "es6": true,
4 | "browser": true,
5 | "node": true,
6 | "mocha": true
7 | },
8 | "parserOptions": {
9 | "ecmaVersion": 2017
10 | },
11 | "rules": {
12 | "quotes": [
13 | 1,
14 | "single"
15 | ],
16 | "no-mixed-requires": [
17 | 0,
18 | false
19 | ],
20 | "no-underscore-dangle": 0,
21 | "yoda": [
22 | 1,
23 | "always"
24 | ],
25 | "indent": [
26 | 2,
27 | 2,
28 | {
29 | "SwitchCase": 1
30 | }
31 | ],
32 | "brace-style": [
33 | 2,
34 | "1tbs"
35 | ],
36 | "comma-style": [
37 | 2,
38 | "last"
39 | ],
40 | "default-case": 2,
41 | "func-style": [
42 | 2,
43 | "declaration"
44 | ],
45 | "guard-for-in": 2,
46 | "no-floating-decimal": 2,
47 | "no-nested-ternary": 2,
48 | "no-undefined": 2,
49 | "radix": 2,
50 | "space-before-function-paren": [
51 | 1,
52 | "always"
53 | ],
54 | "keyword-spacing": [
55 | 2,
56 | {
57 | "after": true
58 | }
59 | ],
60 | "space-before-blocks": 2,
61 | "spaced-comment": [
62 | 2,
63 | "always",
64 | {
65 | "exceptions": [
66 | "-"
67 | ],
68 | "markers": [
69 | "eslint",
70 | "jshint",
71 | "global"
72 | ]
73 | }
74 | ],
75 | "strict": [
76 | 2,
77 | "global"
78 | ],
79 | "wrap-iife": 2,
80 | "camelcase": 0,
81 | "new-cap": 0
82 | }
83 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | *.lock
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 |
13 | # Directory for instrumented libs generated by jscoverage/JSCover
14 | lib-cov
15 |
16 | # Coverage directory used by tools like istanbul
17 | coverage
18 |
19 | # nyc test coverage
20 | .nyc_output
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # node-waf configuration
26 | .lock-wscript
27 |
28 | # Compiled binary addons (http://nodejs.org/api/addons.html)
29 | build/Release
30 |
31 | # Dependency directories
32 | node_modules
33 | jspm_packages
34 |
35 | # Optional npm cache directory
36 | .npm
37 |
38 | # Optional REPL history
39 | .node_repl_history
40 |
41 | package-lock.json
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
async-mqtt
2 | Promise wrapper over MQTT.js
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | **IMPORTANT: Make sure you handle rejections from returned promises because they won't crash the process**
20 |
21 | ## API
22 |
23 | The API is the same as [MQTT.js](https://github.com/mqttjs/MQTT.js#api), except the following functions now return promises instead of taking callbacks
24 |
25 | - publish
26 | - subscribe
27 | - unsubscribe
28 | - end
29 |
30 |
31 | ## Example
32 |
33 | ```javascript
34 | const MQTT = require("async-mqtt");
35 |
36 | const client = MQTT.connect("tcp://somehost.com:1883");
37 |
38 | // When passing async functions as event listeners, make sure to have a try catch block
39 |
40 | const doStuff = async () => {
41 |
42 | console.log("Starting");
43 | try {
44 | await client.publish("wow/so/cool", "It works!");
45 | // This line doesn't run until the server responds to the publish
46 | await client.end();
47 | // This line doesn't run until the client has disconnected without error
48 | console.log("Done");
49 | } catch (e){
50 | // Do something about it!
51 | console.log(e.stack);
52 | process.exit();
53 | }
54 | }
55 |
56 | client.on("connect", doStuff);
57 | ```
58 |
59 | Alternately you can skip the event listeners and get a promise.
60 |
61 | ```js
62 | const MQTT = require("async-mqtt");
63 |
64 | run()
65 |
66 | async function run() {
67 | const client = await MQTT.connectAsync("tcp://somehost.com:1883")
68 |
69 | console.log("Starting");
70 | try {
71 | await client.publish("wow/so/cool", "It works!");
72 | // This line doesn't run until the server responds to the publish
73 | await client.end();
74 | // This line doesn't run until the client has disconnected without error
75 | console.log("Done");
76 | } catch (e){
77 | // Do something about it!
78 | console.log(e.stack);
79 | process.exit();
80 | }
81 | }
82 |
83 | ```
84 |
85 | ## Wrapping existing client
86 |
87 | ```javascript
88 | const { AsyncClient } = require("async-mqtt");
89 |
90 | const client = getRegularMQTTClientFromSomewhere();
91 |
92 | const asyncClient = new AsyncClient(client);
93 |
94 | asyncClient.publish("foo/bar", "baz").then(() => {
95 | console.log("We async now");
96 | return asyncClient.end();
97 | });
98 | ```
99 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import {
2 | MqttClient,
3 | IClientOptions,
4 | IClientPublishOptions,
5 | IClientSubscribeOptions,
6 | ISubscriptionGrant,
7 | ISubscriptionMap,
8 | } from 'mqtt';
9 |
10 | export * from 'mqtt/types/lib/client-options';
11 | export * from 'mqtt/types/lib/store';
12 |
13 | export {
14 | // mqtt/types/lib/client
15 | ISubscriptionGrant,
16 | ISubscriptionRequest,
17 | ISubscriptionMap,
18 | OnMessageCallback,
19 | OnPacketCallback,
20 | OnErrorCallback,
21 | IStream,
22 |
23 | // mqtt-packet
24 | QoS,
25 | PacketCmd,
26 | IPacket,
27 | IConnectPacket,
28 | IPublishPacket,
29 | IConnackPacket,
30 | ISubscription,
31 | ISubscribePacket,
32 | ISubackPacket,
33 | IUnsubscribePacket,
34 | IUnsubackPacket,
35 | IPubackPacket,
36 | IPubcompPacket,
37 | IPubrelPacket,
38 | IPubrecPacket,
39 | IPingreqPacket,
40 | IPingrespPacket,
41 | IDisconnectPacket,
42 | Packet
43 | } from 'mqtt'
44 |
45 | export interface IMqttClient extends MqttClient {}
46 |
47 | export declare class AsyncMqttClient extends MqttClient {
48 | constructor (client: IMqttClient);
49 |
50 | public subscribe (topic: string | string[], opts: IClientSubscribeOptions): Promise
51 | public subscribe (topic: string | string[] | ISubscriptionMap): Promise
52 | /* original */ public subscribe (topic: string | string[], opts: IClientSubscribeOptions, callback: never): this
53 | /* original */ public subscribe (topic: string | string[] | ISubscriptionMap, callback: never): this
54 |
55 | public unsubscribe (topic: string | string[]): Promise
56 | /* original */ public unsubscribe (topic: string | string[], callback: never): this;
57 |
58 | public publish (topic: string, message: string | Buffer, opts: IClientPublishOptions): Promise
59 | public publish (topic: string, message: string | Buffer): Promise
60 | /* original */ public publish (topic: string, message: string | Buffer, opts: IClientPublishOptions, callback: never): this
61 | /* original */ public publish (topic: string, message: string | Buffer, callback: never): this
62 |
63 | public end (force?: boolean): Promise
64 | /* original */ public end (force: boolean, callback: never): this;
65 | }
66 |
67 | export declare function connect (brokerUrl?: string | any, opts?: IClientOptions): AsyncMqttClient
68 | export declare function connectAsync (brokerUrl: string | any, opts?: IClientOptions, allowRetries?: boolean): Promise
69 |
70 | export { AsyncMqttClient as AsyncClient }
71 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const mqtt = require('mqtt');
4 |
5 | class AsyncClient {
6 | constructor (client) {
7 | this._client = client;
8 | }
9 |
10 | set handleMessage (newHandler) {
11 | this._client.handleMessage = newHandler;
12 | }
13 |
14 | get handleMessage () {
15 | return this._client.handleMessage;
16 | }
17 |
18 | get connected () {
19 | return this._client.connected;
20 | }
21 |
22 | get reconnecting () {
23 | return this._client.reconnecting;
24 | }
25 |
26 | publish (...args) {
27 | return new Promise((resolve, reject) => {
28 | this._client.publish(...args, (err, result) => {
29 | if (err) reject(err)
30 | else resolve(result)
31 | })
32 | })
33 | }
34 |
35 | subscribe (...args) {
36 | return new Promise((resolve, reject) => {
37 | this._client.subscribe(...args, (err, result) => {
38 | if (err) reject(err)
39 | else resolve(result)
40 | })
41 | })
42 | }
43 |
44 | unsubscribe (...args) {
45 | return new Promise((resolve, reject) => {
46 | this._client.unsubscribe(...args, (err, result) => {
47 | if (err) reject(err)
48 | else resolve(result)
49 | })
50 | })
51 | }
52 |
53 | end (...args) {
54 | return new Promise((resolve, reject) => {
55 | this._client.end(...args, (err, result) => {
56 | if (err) reject(err)
57 | else resolve(result)
58 | })
59 | })
60 | }
61 |
62 | reconnect (...args) {
63 | return this._client.reconnect(...args);
64 | }
65 |
66 | addListener (...args) {
67 | return this._client.addListener(...args);
68 | }
69 |
70 | emit (...args) {
71 | return this._client.emit(...args);
72 | }
73 |
74 | eventNames (...args) {
75 | return this._client.eventNames(...args);
76 | }
77 |
78 | getLastMessageId (...args) {
79 | return this._client.getLastMessageId(...args);
80 | }
81 |
82 | getMaxListeners (...args) {
83 | return this._client.getMaxListeners(...args);
84 | }
85 |
86 | listenerCount (...args) {
87 | return this._client.listenerCount(...args);
88 | }
89 |
90 | listeners (...args) {
91 | return this._client.listeners(...args);
92 | }
93 |
94 | off (...args) {
95 | return this._client.off(...args);
96 | }
97 |
98 | on (...args) {
99 | return this._client.on(...args);
100 | }
101 |
102 | once (...args) {
103 | return this._client.once(...args);
104 | }
105 |
106 | prependListener (...args) {
107 | return this._client.prependListener(...args);
108 | }
109 |
110 | prependOnceListener (...args) {
111 | return this._client.prependOnceListener(...args);
112 | }
113 |
114 | rawListeners (...args) {
115 | return this._client.rawListeners(...args);
116 | }
117 |
118 | removeAllListeners (...args) {
119 | return this._client.removeAllListeners(...args);
120 | }
121 |
122 | removeListener (...args) {
123 | return this._client.removeListener(...args);
124 | }
125 |
126 | removeOutgoingMessage (...args) {
127 | return this._client.removeOutgoingMessage(...args);
128 | }
129 |
130 | setMaxListeners (...args) {
131 | return this._client.setMaxListeners(...args);
132 | }
133 |
134 | }
135 |
136 |
137 | module.exports = {
138 | connect (brokerURL, opts) {
139 | const client = mqtt.connect(brokerURL, opts);
140 | const asyncClient = new AsyncClient(client);
141 |
142 | return asyncClient;
143 | },
144 | connectAsync (brokerURL, opts, allowRetries=true) {
145 | const client = mqtt.connect(brokerURL, opts);
146 | const asyncClient = new AsyncClient(client);
147 |
148 | return new Promise((resolve, reject) => {
149 | // Listeners added to client to trigger promise resolution
150 | const promiseResolutionListeners = {
151 | connect: (connack) => {
152 | removePromiseResolutionListeners();
153 | resolve(asyncClient); // Resolve on connect
154 | },
155 | end: () => {
156 | removePromiseResolutionListeners();
157 | resolve(asyncClient); // Resolve on end
158 | },
159 | error: (err) => {
160 | removePromiseResolutionListeners();
161 | client.end();
162 | reject(err); // Reject on error
163 | }
164 | };
165 |
166 | // If retries are not allowed, reject on close
167 | if (false === allowRetries) {
168 | promiseResolutionListeners.close = () => {
169 | promiseResolutionListeners.error('Couldn\'t connect to server');
170 | }
171 | }
172 |
173 | // Remove listeners added to client by this promise
174 | function removePromiseResolutionListeners () {
175 | Object.keys(promiseResolutionListeners).forEach((eventName) => {
176 | client.removeListener(eventName, promiseResolutionListeners[eventName]);
177 | });
178 | };
179 |
180 | // Add listeners to client
181 | Object.keys(promiseResolutionListeners).forEach((eventName) => {
182 | client.on(eventName, promiseResolutionListeners[eventName]);
183 | });
184 | });
185 | },
186 | AsyncClient
187 | };
188 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "async-mqtt",
3 | "version": "2.6.3",
4 | "description": "Promise wrapper over MQTT.js",
5 | "main": "index.js",
6 | "types": "index.d.ts",
7 | "scripts": {
8 | "test": "node test.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/mqttjs/async-mqtt.git"
13 | },
14 | "keywords": [
15 | "mqtt",
16 | "promise",
17 | "async",
18 | "publish",
19 | "subscribe"
20 | ],
21 | "author": "rangermauve",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/mqttjs/async-mqtt/issues"
25 | },
26 | "homepage": "https://github.com/mqttjs/async-mqtt#readme",
27 | "dependencies": {
28 | "mqtt": "^4.3.7"
29 | },
30 | "devDependencies": {
31 | "@types/node": "^14.0.5",
32 | "eslint": "^7.32.0",
33 | "mqtt-connection": "^4.0.0",
34 | "tape": "^5.0.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const net = require('net');
4 | const Connection = require('mqtt-connection');
5 |
6 | class Server extends net.Server {
7 | constructor (listener) {
8 | super()
9 | this.connectionList = []
10 |
11 | this.on('connection', duplex => {
12 | this.connectionList.push(duplex)
13 | const connection = new Connection(duplex, () => {
14 | this.emit('client', connection)
15 | })
16 | })
17 |
18 | if (listener) {
19 | this.on('client', listener)
20 | }
21 | }
22 | }
23 |
24 |
25 | const AsyncMQTT = require('./');
26 | const { AsyncClient } = AsyncMQTT;
27 |
28 | const test = require('tape');
29 |
30 | const SERVER_PORT = 1883;
31 | const SERVER_URL = `mqtt://localhost:${SERVER_PORT}`;
32 |
33 | const server = buildServer().listen(SERVER_PORT);
34 | server.unref();
35 |
36 | server.on('listening', runTests);
37 |
38 | function runTests () {
39 | test('Connect should return an instance of AsyncClient', t => {
40 | t.plan(1);
41 | const client = AsyncMQTT.connect(SERVER_URL);
42 |
43 | t.ok(client instanceof AsyncClient, 'Connect returned an AsyncClient');
44 |
45 | client.on('connect', () => {
46 | client.end();
47 | })
48 | });
49 |
50 | test('ConnectAsync should return AsyncClient after connection', t => {
51 | t.plan(1);
52 |
53 | AsyncMQTT.connectAsync(SERVER_URL, {}, false).then((client) => {
54 | t.ok(client.connected, 'AsyncClient is connected');
55 |
56 | client.end();
57 | }, (error) => {
58 | t.fail(error);
59 | });
60 |
61 |
62 | });
63 |
64 | test('Should be able to listen on event on client', t => {
65 | t.plan(1);
66 |
67 | const client = AsyncMQTT.connect(SERVER_URL);
68 |
69 | client.once('connect', () => {
70 | t.pass('Connected');
71 | client.end();
72 | });
73 | });
74 |
75 | test('client.connected should be true', t => {
76 | t.plan(1);
77 |
78 | const client = AsyncMQTT.connect(SERVER_URL);
79 |
80 | client.once('connect', () => {
81 | t.equal(client.connected, true)
82 | client.end();
83 | });
84 | });
85 |
86 | test('Calling end() should resolve once disconnected', t => {
87 | t.plan(2);
88 |
89 | const client = AsyncMQTT.connect(SERVER_URL);
90 |
91 | client.on('close', () => {
92 | t.pass('Close event occured');
93 | });
94 |
95 | client.on('connect', () => {
96 | // Wait for connect to emit before ending
97 | client.end().then(() => {
98 | t.pass('End resolved');
99 | });
100 | });
101 | });
102 |
103 | test('Calling subscribe should resolve once subscribed', t => {
104 | t.plan(1);
105 |
106 | const client = AsyncMQTT.connect(SERVER_URL);
107 |
108 | client.subscribe('example', {
109 | qos: 1
110 | }).then(() => {
111 | t.pass('Subscribed');
112 | client.end();
113 | })
114 | });
115 |
116 | test('Calling unsubscribe should resolve once completed', t => {
117 | t.plan(1);
118 |
119 | const client = AsyncMQTT.connect(SERVER_URL);
120 |
121 | client.subscribe('example', {
122 | qos: 1
123 | }).then(() => client.unsubscribe('example')).then(() => {
124 | t.pass('Unsunbscribed');
125 | return client.end();
126 | });
127 | });
128 |
129 | test('Calling publish should resolve once completed', t => {
130 | t.plan(1);
131 |
132 | const client = AsyncMQTT.connect(SERVER_URL);
133 |
134 | client.publish('example', 'test', {
135 | qos: 1
136 | }).then(() => {
137 | t.pass('Published');
138 | return client.end();
139 | });
140 | });
141 |
142 | test('Calling getLastMessageId should return number after published', t => {
143 | t.plan(1);
144 |
145 | const client = AsyncMQTT.connect(SERVER_URL);
146 |
147 | client.publish('example', 'test', {
148 | qos: 1
149 | }).then(() => {
150 | t.ok('number' === typeof client.getLastMessageId(), 'Message id is number');
151 | return client.end();
152 | });
153 | });
154 |
155 | test('Calling reconnect after end should resolve once reconnect', t => {
156 | t.plan(2);
157 |
158 | const client = AsyncMQTT.connect(SERVER_URL);
159 |
160 | client.once('reconnect', () => {
161 | t.pass('Reconnect event occured');
162 | client.once('connect', () => {
163 | client.end();
164 | });
165 | });
166 |
167 | client.once('connect', () => {
168 | client.end().then(() => {
169 | t.pass('End resolved');
170 | client.reconnect();
171 | });
172 | });
173 | });
174 | }
175 |
176 | // Taken from MQTT.js tests
177 | function buildServer () {
178 | return new Server(client => {
179 | client.on('connect', ({clientId}) => {
180 | if ('invalid' === clientId) {
181 | client.connack({returnCode: 2})
182 | } else {
183 | client.connack({returnCode: 0})
184 | }
185 | })
186 |
187 | client.on('publish', packet => {
188 | setImmediate(() => {
189 | switch (packet.qos) {
190 | case 0:
191 | break
192 | case 1:
193 | client.puback(packet)
194 | break
195 | case 2:
196 | client.pubrec(packet)
197 | break
198 | default:
199 | break
200 | }
201 | })
202 | })
203 |
204 | client.on('pubrel', packet => {
205 | client.pubcomp(packet)
206 | })
207 |
208 | client.on('pubrec', packet => {
209 | client.pubrel(packet)
210 | })
211 |
212 | client.on('pubcomp', () => {
213 | // Nothing to be done
214 | })
215 |
216 | client.on('subscribe', ({messageId, subscriptions}) => {
217 | client.suback({
218 | messageId,
219 | granted: subscriptions.map(({qos}) => qos)
220 | })
221 | })
222 |
223 | client.on('unsubscribe', packet => {
224 | client.unsuback(packet)
225 | })
226 |
227 | client.on('pingreq', () => {
228 | client.pingresp()
229 | })
230 | });
231 | }
232 |
--------------------------------------------------------------------------------