aedes 0.45.1 → 0.46.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +2 -2
- package/.taprc +2 -1
- package/aedes.d.ts +1 -0
- package/aedes.js +10 -6
- package/docs/Aedes.md +13 -2
- package/lib/handlers/subscribe.js +4 -4
- package/lib/handlers/unsubscribe.js +5 -5
- package/lib/utils.js +2 -1
- package/package.json +6 -6
- package/test/basic.js +46 -0
- package/types/client.d.ts +2 -1
- package/types/instance.d.ts +9 -1
package/.github/workflows/ci.yml
CHANGED
|
@@ -20,10 +20,10 @@ jobs:
|
|
|
20
20
|
os: [ubuntu-latest, windows-latest, macOS-latest]
|
|
21
21
|
|
|
22
22
|
steps:
|
|
23
|
-
- uses: actions/checkout@v2
|
|
23
|
+
- uses: actions/checkout@v2.3.5
|
|
24
24
|
|
|
25
25
|
- name: Use Node.js
|
|
26
|
-
uses: actions/setup-node@v2.
|
|
26
|
+
uses: actions/setup-node@v2.3.2
|
|
27
27
|
with:
|
|
28
28
|
node-version: ${{ matrix.node-version }}
|
|
29
29
|
|
package/.taprc
CHANGED
package/aedes.d.ts
CHANGED
package/aedes.js
CHANGED
|
@@ -12,6 +12,7 @@ const Packet = require('aedes-packet')
|
|
|
12
12
|
const memory = require('aedes-persistence')
|
|
13
13
|
const mqemitter = require('mqemitter')
|
|
14
14
|
const Client = require('./lib/client')
|
|
15
|
+
const { $SYS_PREFIX } = require('./lib/utils')
|
|
15
16
|
|
|
16
17
|
module.exports = Aedes.Server = Aedes
|
|
17
18
|
|
|
@@ -78,7 +79,7 @@ function Aedes (opts) {
|
|
|
78
79
|
this.clients = {}
|
|
79
80
|
this.brokers = {}
|
|
80
81
|
|
|
81
|
-
const heartbeatTopic =
|
|
82
|
+
const heartbeatTopic = $SYS_PREFIX + that.id + '/heartbeat'
|
|
82
83
|
this._heartbeatInterval = setInterval(heartbeat, opts.heartbeatInterval)
|
|
83
84
|
|
|
84
85
|
const bufId = Buffer.from(that.id, 'utf8')
|
|
@@ -138,12 +139,12 @@ function Aedes (opts) {
|
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
|
|
141
|
-
this.mq.on('
|
|
142
|
+
this.mq.on($SYS_PREFIX + '+/heartbeat', function storeBroker (packet, done) {
|
|
142
143
|
that.brokers[packet.payload.toString()] = Date.now()
|
|
143
144
|
done()
|
|
144
145
|
})
|
|
145
146
|
|
|
146
|
-
this.mq.on('
|
|
147
|
+
this.mq.on($SYS_PREFIX + '+/new/clients', function closeSameClients (packet, done) {
|
|
147
148
|
const serverId = packet.topic.split('/')[1]
|
|
148
149
|
const clientId = packet.payload.toString()
|
|
149
150
|
|
|
@@ -206,7 +207,7 @@ function DoEnqueues () {
|
|
|
206
207
|
return
|
|
207
208
|
}
|
|
208
209
|
|
|
209
|
-
if (that.topic.indexOf(
|
|
210
|
+
if (that.topic.indexOf($SYS_PREFIX) === 0) {
|
|
210
211
|
subs = subs.filter(removeSharp)
|
|
211
212
|
}
|
|
212
213
|
|
|
@@ -281,7 +282,7 @@ Aedes.prototype._finishRegisterClient = function (client) {
|
|
|
281
282
|
this.clients[client.id] = client
|
|
282
283
|
this.emit('client', client)
|
|
283
284
|
this.publish({
|
|
284
|
-
topic:
|
|
285
|
+
topic: $SYS_PREFIX + this.id + '/new/clients',
|
|
285
286
|
payload: Buffer.from(client.id, 'utf8')
|
|
286
287
|
}, noop)
|
|
287
288
|
}
|
|
@@ -291,7 +292,7 @@ Aedes.prototype.unregisterClient = function (client) {
|
|
|
291
292
|
delete this.clients[client.id]
|
|
292
293
|
this.emit('clientDisconnect', client)
|
|
293
294
|
this.publish({
|
|
294
|
-
topic:
|
|
295
|
+
topic: $SYS_PREFIX + this.id + '/disconnect/clients',
|
|
295
296
|
payload: Buffer.from(client.id, 'utf8')
|
|
296
297
|
}, noop)
|
|
297
298
|
}
|
|
@@ -326,6 +327,9 @@ function defaultAuthenticate (client, username, password, callback) {
|
|
|
326
327
|
}
|
|
327
328
|
|
|
328
329
|
function defaultAuthorizePublish (client, packet, callback) {
|
|
330
|
+
if (packet.topic.startsWith($SYS_PREFIX)) {
|
|
331
|
+
return callback(new Error($SYS_PREFIX + ' topic is reserved'))
|
|
332
|
+
}
|
|
329
333
|
callback(null)
|
|
330
334
|
}
|
|
331
335
|
|
package/docs/Aedes.md
CHANGED
|
@@ -113,7 +113,7 @@ Emitted when timeout happes in the `client` keepalive.
|
|
|
113
113
|
- `packet` `<aedes-packet>` & [`PUBLISH`][PUBLISH]
|
|
114
114
|
- `client` [`<Client>`](./Client.md) | `null`
|
|
115
115
|
|
|
116
|
-
Emitted when servers delivers the `packet` to subscribed `client`. If there are no clients subscribed to the `packet` topic, server still publish the `packet` and emit
|
|
116
|
+
Emitted when servers delivers the `packet` to subscribed `client`. If there are no clients subscribed to the `packet` topic, server still publish the `packet` and emit the event. `client` is `null` when `packet` is an internal message like aedes heartbeat message and LWT.
|
|
117
117
|
|
|
118
118
|
> _Note! `packet` belongs `aedes-packet` type. Some properties belongs to aedes internal, any changes on them will break aedes internal flow._
|
|
119
119
|
|
|
@@ -315,6 +315,17 @@ aedes.authorizePublish = function (client, packet, callback) {
|
|
|
315
315
|
}
|
|
316
316
|
```
|
|
317
317
|
|
|
318
|
+
By default `authorizePublish` throws error in case a client publish to topics with `$SYS/` prefix to prevent possible DoS (see [#597](https://github.com/moscajs/aedes/issues/597)). If you write your own implementation of `authorizePublish` we suggest you to add a check for this. Default implementation:
|
|
319
|
+
|
|
320
|
+
```js
|
|
321
|
+
function defaultAuthorizePublish (client, packet, callback) {
|
|
322
|
+
if (packet.topic.startsWith($SYS_PREFIX)) {
|
|
323
|
+
return callback(new Error($SYS_PREFIX + ' topic is reserved'))
|
|
324
|
+
}
|
|
325
|
+
callback(null)
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
318
329
|
## Handler: authorizeSubscribe (client, subscription, callback)
|
|
319
330
|
|
|
320
331
|
- client: [`<Client>`](./Client.md)
|
|
@@ -392,7 +403,7 @@ aedes.authorizeForward = function (client, packet) {
|
|
|
392
403
|
- client: [`<Client>`](./Client.md)
|
|
393
404
|
- callback: `<Function>`
|
|
394
405
|
|
|
395
|
-
same as [`Event: publish`](#event-publish), but provides a backpressure functionality.
|
|
406
|
+
same as [`Event: publish`](#event-publish), but provides a backpressure functionality. TLDR; If you are doing operations on packets that MUST require finishing operations on a packet before handling the next one use this otherwise, expecially for long running operations, you should use [`Event: publish`](#event-publish) instead.
|
|
396
407
|
|
|
397
408
|
[CONNECT]: https://github.com/mqttjs/mqtt-packet#connect
|
|
398
409
|
[CONNACK]: https://github.com/mqttjs/mqtt-packet#connack
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fastfall = require('fastfall')
|
|
4
4
|
const Packet = require('aedes-packet')
|
|
5
5
|
const { through } = require('../utils')
|
|
6
|
-
const { validateTopic } = require('../utils')
|
|
6
|
+
const { validateTopic, $SYS_PREFIX } = require('../utils')
|
|
7
7
|
const write = require('../write')
|
|
8
8
|
|
|
9
9
|
const subscribeTopicActions = fastfall([
|
|
@@ -184,7 +184,7 @@ function completeSubscribe (err) {
|
|
|
184
184
|
const packet = this.packet
|
|
185
185
|
const client = this.client
|
|
186
186
|
|
|
187
|
-
if (packet.messageId) {
|
|
187
|
+
if (packet.messageId !== undefined) {
|
|
188
188
|
// [MQTT-3.9.3-1]
|
|
189
189
|
write(client,
|
|
190
190
|
new SubAck(packet, this.subState.map(obj => obj.granted)),
|
|
@@ -207,7 +207,7 @@ function completeSubscribe (err) {
|
|
|
207
207
|
|
|
208
208
|
broker.emit('subscribe', subs, client)
|
|
209
209
|
broker.publish({
|
|
210
|
-
topic:
|
|
210
|
+
topic: $SYS_PREFIX + broker.id + '/new/subscribes',
|
|
211
211
|
payload: Buffer.from(JSON.stringify({
|
|
212
212
|
clientId: client.id,
|
|
213
213
|
subs: subs
|
|
@@ -234,6 +234,6 @@ function completeSubscribe (err) {
|
|
|
234
234
|
}))
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
function noop () {}
|
|
237
|
+
function noop () { }
|
|
238
238
|
|
|
239
239
|
module.exports = handleSubscribe
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const write = require('../write')
|
|
4
|
-
const { validateTopic } = require('../utils')
|
|
4
|
+
const { validateTopic, $SYS_PREFIX } = require('../utils')
|
|
5
5
|
|
|
6
6
|
function UnSubAck (packet) {
|
|
7
7
|
this.cmd = 'unsuback'
|
|
@@ -26,7 +26,7 @@ function handleUnsubscribe (client, packet, done) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
if (packet.messageId) {
|
|
29
|
+
if (packet.messageId !== undefined) {
|
|
30
30
|
if (client.clean) {
|
|
31
31
|
return actualUnsubscribe(client, packet, done)
|
|
32
32
|
}
|
|
@@ -80,7 +80,7 @@ function completeUnsubscribe (err) {
|
|
|
80
80
|
const packet = this.packet
|
|
81
81
|
const done = this.finish
|
|
82
82
|
|
|
83
|
-
if (packet.messageId) {
|
|
83
|
+
if (packet.messageId !== undefined) {
|
|
84
84
|
write(client, new UnSubAck(packet),
|
|
85
85
|
done)
|
|
86
86
|
} else {
|
|
@@ -90,7 +90,7 @@ function completeUnsubscribe (err) {
|
|
|
90
90
|
if ((!client.closed || client.clean === true) && packet.unsubscriptions.length > 0) {
|
|
91
91
|
client.broker.emit('unsubscribe', packet.unsubscriptions, client)
|
|
92
92
|
client.broker.publish({
|
|
93
|
-
topic:
|
|
93
|
+
topic: $SYS_PREFIX + client.broker.id + '/new/unsubscribes',
|
|
94
94
|
payload: Buffer.from(JSON.stringify({
|
|
95
95
|
clientId: client.id,
|
|
96
96
|
subs: packet.unsubscriptions
|
|
@@ -99,6 +99,6 @@ function completeUnsubscribe (err) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
function noop () {}
|
|
102
|
+
function noop () { }
|
|
103
103
|
|
|
104
104
|
module.exports = handleUnsubscribe
|
package/lib/utils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aedes",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.46.2",
|
|
4
4
|
"description": "Stream-based MQTT broker",
|
|
5
5
|
"main": "aedes.js",
|
|
6
6
|
"types": "aedes.d.ts",
|
|
@@ -96,14 +96,14 @@
|
|
|
96
96
|
],
|
|
97
97
|
"license": "MIT",
|
|
98
98
|
"devDependencies": {
|
|
99
|
-
"@sinonjs/fake-timers": "^
|
|
100
|
-
"@types/node": "^
|
|
99
|
+
"@sinonjs/fake-timers": "^8.0.1",
|
|
100
|
+
"@types/node": "^16.0.0",
|
|
101
101
|
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
|
102
102
|
"@typescript-eslint/parser": "^4.22.0",
|
|
103
103
|
"concat-stream": "^2.0.0",
|
|
104
104
|
"duplexify": "^4.1.1",
|
|
105
105
|
"license-checker": "^25.0.1",
|
|
106
|
-
"markdownlint-cli": "^0.
|
|
106
|
+
"markdownlint-cli": "^0.29.0",
|
|
107
107
|
"mqtt": "^4.2.6",
|
|
108
108
|
"mqtt-connection": "^4.1.0",
|
|
109
109
|
"pre-commit": "^1.2.2",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"snazzy": "^9.0.0",
|
|
113
113
|
"standard": "^16.0.3",
|
|
114
114
|
"tap": "^15.0.2",
|
|
115
|
-
"tsd": "^0.
|
|
115
|
+
"tsd": "^0.18.0",
|
|
116
116
|
"typescript": "^4.2.4",
|
|
117
117
|
"websocket-stream": "^5.5.2"
|
|
118
118
|
},
|
|
@@ -126,7 +126,7 @@
|
|
|
126
126
|
"fastseries": "^2.0.0",
|
|
127
127
|
"hyperid": "^2.1.0",
|
|
128
128
|
"mqemitter": "^4.4.1",
|
|
129
|
-
"mqtt-packet": "^
|
|
129
|
+
"mqtt-packet": "^7.0.0",
|
|
130
130
|
"readable-stream": "^3.6.0",
|
|
131
131
|
"retimer": "^3.0.0",
|
|
132
132
|
"reusify": "^1.0.4",
|
package/test/basic.js
CHANGED
|
@@ -95,6 +95,23 @@ test('publish empty topic throws error', function (t) {
|
|
|
95
95
|
})
|
|
96
96
|
})
|
|
97
97
|
|
|
98
|
+
test('publish to $SYS topic throws error', function (t) {
|
|
99
|
+
t.plan(1)
|
|
100
|
+
|
|
101
|
+
const s = connect(setup())
|
|
102
|
+
t.teardown(s.broker.close.bind(s.broker))
|
|
103
|
+
|
|
104
|
+
s.inStream.write({
|
|
105
|
+
cmd: 'publish',
|
|
106
|
+
topic: '$SYS/not/allowed',
|
|
107
|
+
payload: 'world'
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
s.broker.on('clientError', function (client, err) {
|
|
111
|
+
t.pass('should emit error')
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
98
115
|
;[{ qos: 0, clean: false }, { qos: 0, clean: true }, { qos: 1, clean: false }, { qos: 1, clean: true }].forEach(function (ele) {
|
|
99
116
|
test('subscribe a single topic in QoS ' + ele.qos + ' [clean=' + ele.clean + ']', function (t) {
|
|
100
117
|
t.plan(5)
|
|
@@ -243,6 +260,35 @@ test('subscribe should have messageId', function (t) {
|
|
|
243
260
|
})
|
|
244
261
|
})
|
|
245
262
|
|
|
263
|
+
test('subscribe with messageId 0 should return suback', function (t) {
|
|
264
|
+
t.plan(1)
|
|
265
|
+
|
|
266
|
+
const s = connect(setup())
|
|
267
|
+
t.teardown(s.broker.close.bind(s.broker))
|
|
268
|
+
|
|
269
|
+
s.inStream.write({
|
|
270
|
+
cmd: 'subscribe',
|
|
271
|
+
subscriptions: [{
|
|
272
|
+
topic: 'hello',
|
|
273
|
+
qos: 0
|
|
274
|
+
}],
|
|
275
|
+
messageId: 0
|
|
276
|
+
})
|
|
277
|
+
s.outStream.once('data', function (packet) {
|
|
278
|
+
t.same(packet, {
|
|
279
|
+
cmd: 'suback',
|
|
280
|
+
messageId: 0,
|
|
281
|
+
dup: false,
|
|
282
|
+
length: 3,
|
|
283
|
+
qos: 0,
|
|
284
|
+
retain: false,
|
|
285
|
+
granted: [
|
|
286
|
+
0
|
|
287
|
+
]
|
|
288
|
+
}, 'packet matches')
|
|
289
|
+
})
|
|
290
|
+
})
|
|
291
|
+
|
|
246
292
|
test('unsubscribe', function (t) {
|
|
247
293
|
t.plan(5)
|
|
248
294
|
|
package/types/client.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { IncomingMessage } from 'http'
|
|
2
2
|
import { PublishPacket, SubscribePacket, Subscription, Subscriptions, UnsubscribePacket } from './packet'
|
|
3
3
|
import { Connection } from './instance'
|
|
4
|
+
import { EventEmitter } from 'events'
|
|
4
5
|
|
|
5
|
-
export interface Client {
|
|
6
|
+
export interface Client extends EventEmitter {
|
|
6
7
|
id: Readonly<string>
|
|
7
8
|
clean: Readonly<boolean>
|
|
8
9
|
version: Readonly<number>
|
package/types/instance.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Duplex } from 'stream'
|
|
|
2
2
|
import { Socket } from 'net'
|
|
3
3
|
import { Client } from './client'
|
|
4
4
|
import type { AedesPublishPacket, ConnectPacket, ConnackPacket, Subscription, PingreqPacket, PublishPacket, PubrelPacket } from './packet'
|
|
5
|
+
import { EventEmitter } from 'events'
|
|
5
6
|
|
|
6
7
|
type LastHearthbeatTimestamp = Date;
|
|
7
8
|
|
|
@@ -56,7 +57,7 @@ export interface AedesOptions {
|
|
|
56
57
|
published?: PublishedHandler
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
export interface Aedes {
|
|
60
|
+
export interface Aedes extends EventEmitter {
|
|
60
61
|
id: Readonly<string>
|
|
61
62
|
connectedClients: Readonly<number>
|
|
62
63
|
closed: Readonly<boolean>
|
|
@@ -89,4 +90,11 @@ export interface Aedes {
|
|
|
89
90
|
callback: () => void
|
|
90
91
|
): void
|
|
91
92
|
close (callback?: () => void): void
|
|
93
|
+
|
|
94
|
+
preConnect: PreConnectHandler
|
|
95
|
+
authenticate: AuthenticateHandler
|
|
96
|
+
authorizePublish: AuthorizePublishHandler
|
|
97
|
+
authorizeSubscribe: AuthorizeSubscribeHandler
|
|
98
|
+
authorizeForward: AuthorizeForwardHandler
|
|
99
|
+
published: PublishedHandler
|
|
92
100
|
}
|