aedes 0.47.0 → 0.48.0

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.
@@ -11,21 +11,39 @@ on:
11
11
  - '*.md'
12
12
 
13
13
  jobs:
14
+ dependency-review:
15
+ name: Dependency Review
16
+ if: github.event_name == 'pull_request'
17
+ runs-on: ubuntu-latest
18
+ permissions:
19
+ contents: read
20
+ steps:
21
+ - name: Check out repo
22
+ uses: actions/checkout@v3
23
+ with:
24
+ persist-credentials: false
25
+
26
+ - name: Dependency review
27
+ uses: actions/dependency-review-action@v2
28
+
14
29
  test:
15
30
  runs-on: ${{ matrix.os }}
16
-
31
+ permissions:
32
+ contents: read
17
33
  strategy:
18
34
  matrix:
19
- node-version: [14.x, 16.x, "*"]
35
+ node-version: [14, 16, '*']
20
36
  os: [ubuntu-latest, windows-latest, macOS-latest]
21
-
22
37
  steps:
23
38
  - uses: actions/checkout@v3
39
+ with:
40
+ persist-credentials: false
24
41
 
25
42
  - name: Use Node.js
26
43
  uses: actions/setup-node@v3
27
44
  with:
28
45
  node-version: ${{ matrix.node-version }}
46
+ check-latest: true
29
47
 
30
48
  - name: Install
31
49
  run: |
@@ -49,6 +67,8 @@ jobs:
49
67
  coverage:
50
68
  needs: test
51
69
  runs-on: ubuntu-latest
70
+ permissions:
71
+ contents: read
52
72
  steps:
53
73
  - name: Coveralls Finished
54
74
  uses: coverallsapp/github-action@master
@@ -0,0 +1,29 @@
1
+ name: sast
2
+
3
+ on:
4
+ push:
5
+ branches-ignore:
6
+ - 'dependabot/**'
7
+ pull_request:
8
+
9
+ jobs:
10
+ analyze:
11
+ name: Analyze
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: read
15
+ security-events: write
16
+ strategy:
17
+ fail-fast: true
18
+ matrix:
19
+ language: [ 'javascript' ]
20
+ steps:
21
+ - uses: actions/checkout@v3
22
+ with:
23
+ persist-credentials: false
24
+
25
+ - uses: github/codeql-action/init@v2
26
+ with:
27
+ languages: ${{ matrix.language }}
28
+
29
+ - uses: github/codeql-action/analyze@v2
package/README.md CHANGED
@@ -107,7 +107,7 @@ Brokers that support the [Bridge Protocol][bridge_protocol] can connect to
107
107
  Aedes. When connecting with this special protocol, subscriptions work as usual
108
108
  except that the `retain` flag in the packet is propagated as-is.
109
109
 
110
- ## Exensions
110
+ ## Extensions
111
111
 
112
112
  - [aedes-logging]: Logging module for Aedes, based on Pino
113
113
  - [aedes-stats]: Stats for Aedes
package/aedes.d.ts CHANGED
@@ -1,9 +1,3 @@
1
- import { Aedes, AedesOptions } from './types/instance'
2
-
3
- export declare function aedes (options?: AedesOptions): Aedes
4
- export declare function Server (options?: AedesOptions): Aedes
5
-
6
- export * from './types/instance'
7
- export * from './types/packet'
8
- export * from './types/client'
9
- export default aedes
1
+ /// <reference path="./types/instance.d.ts" />
2
+ /// <reference path="./types/client.d.ts" />
3
+ /// <reference path="./types/packet.d.ts" />
package/aedes.js CHANGED
@@ -79,10 +79,19 @@ function Aedes (opts) {
79
79
  this.brokers = {}
80
80
 
81
81
  const heartbeatTopic = $SYS_PREFIX + that.id + '/heartbeat'
82
+ const birthTopic = $SYS_PREFIX + that.id + '/birth'
83
+
82
84
  this._heartbeatInterval = setInterval(heartbeat, opts.heartbeatInterval)
83
85
 
84
86
  const bufId = Buffer.from(that.id, 'utf8')
85
87
 
88
+ // in a cluster env this is used to warn other broker instances
89
+ // that this broker is alive
90
+ that.publish({
91
+ topic: birthTopic,
92
+ payload: bufId
93
+ }, noop)
94
+
86
95
  function heartbeat () {
87
96
  that.publish({
88
97
  topic: heartbeatTopic,
@@ -115,27 +124,25 @@ function Aedes (opts) {
115
124
  }
116
125
 
117
126
  function checkAndPublish (will, done) {
118
- const needsPublishing =
119
- !that.brokers[will.brokerId] ||
120
- that.brokers[will.brokerId] + (3 * opts.heartbeatInterval) <
121
- Date.now()
122
-
123
- if (needsPublishing) {
124
- // randomize this, so that multiple brokers
125
- // do not publish the same wills at the same time
126
- that.publish(will, function publishWill (err) {
127
- if (err) {
128
- return done(err)
129
- }
127
+ const notPublish =
128
+ that.brokers[will.brokerId] !== undefined && that.brokers[will.brokerId] + (3 * opts.heartbeatInterval) >= Date.now()
130
129
 
130
+ if (notPublish) return done()
131
+
132
+ // randomize this, so that multiple brokers
133
+ // do not publish the same wills at the same time
134
+ this.authorizePublish(that.clients[will.clientId] || null, will, function (err) {
135
+ if (err) { return doneWill() }
136
+ that.publish(will, doneWill)
137
+
138
+ function doneWill (err) {
139
+ if (err) { return done(err) }
131
140
  that.persistence.delWill({
132
141
  id: will.clientId,
133
142
  brokerId: will.brokerId
134
143
  }, done)
135
- })
136
- } else {
137
- done()
138
- }
144
+ }
145
+ })
139
146
  }
140
147
 
141
148
  this.mq.on($SYS_PREFIX + '+/heartbeat', function storeBroker (packet, done) {
@@ -143,6 +150,19 @@ function Aedes (opts) {
143
150
  done()
144
151
  })
145
152
 
153
+ this.mq.on($SYS_PREFIX + '+/birth', function brokerBorn (packet, done) {
154
+ const brokerId = packet.payload.toString()
155
+
156
+ // reset duplicates counter
157
+ if (brokerId !== that.id) {
158
+ for (const clientId in that.clients) {
159
+ delete that.clients[clientId].duplicates[brokerId]
160
+ }
161
+ }
162
+
163
+ done()
164
+ })
165
+
146
166
  this.mq.on($SYS_PREFIX + '+/new/clients', function closeSameClients (packet, done) {
147
167
  const serverId = packet.topic.split('/')[1]
148
168
  const clientId = packet.payload.toString()
package/docs/Aedes.md CHANGED
@@ -186,7 +186,7 @@ const server = require('net').createServer(aedes.handle)
186
186
 
187
187
  Directly subscribe a `topic` in server side. Bypass [`authorizeSubscribe`](#handler-authorizesubscribe-client-subscription-callback)
188
188
 
189
- The `topic` and `deliverfunc` is a compound key to differentiate the uniqueness of its subscription pool. `topic` could be the one that is existed, in this case `deliverfunc` will be invoked as well as `SUBSCRIBE` does.
189
+ The `topic` and `deliverfunc` is a compound key to differentiate the uniqueness of its subscription pool. `topic` could be the one that is existed, in this case `deliverfunc` will be invoked as well as [`SUBSCRIBE`][SUBSCRIBE] does.
190
190
 
191
191
  `deliverfunc` supports backpressue.
192
192
 
@@ -291,7 +291,7 @@ Please refer to [Connect Return Code](http://docs.oasis-open.org/mqtt/mqtt/v3.1.
291
291
 
292
292
  ## Handler: authorizePublish (client, packet, callback)
293
293
 
294
- - client: [`<Client>`](./Client.md)
294
+ - client: [`<Client>`](./Client.md) | `null`
295
295
  - packet: `<object>` [`PUBLISH`][PUBLISH]
296
296
  - callback: `<Function>` `(error) => void`
297
297
  - error `<Error>` | `null`
@@ -301,6 +301,8 @@ Invoked when
301
301
  1. publish LWT to all online clients
302
302
  2. incoming client publish
303
303
 
304
+ `client` is `null` when aedes publishes obsolete LWT without connected clients
305
+
304
306
  If invoked `callback` with no errors, server authorizes the packet otherwise emits `clientError` with `error`. If an `error` occurs the client connection will be closed, but no error is returned to the client (MQTT-3.3.5-2)
305
307
 
306
308
  ```js
@@ -337,7 +339,7 @@ function defaultAuthorizePublish (client, packet, callback) {
337
339
  Invoked when
338
340
 
339
341
  1. restore subscriptions in non-clean session.
340
- 2. incoming client `SUBSCRIBE`
342
+ 2. incoming client [`SUBSCRIBE`][SUBSCRIBE]
341
343
 
342
344
  `subscription` is a dictionary object like `{ topic: hello, qos: 0 }`.
343
345
 
@@ -70,7 +70,7 @@ function init (client, packet, done) {
70
70
  const error = new Error(errorMessages[returnCode])
71
71
  error.errorCode = returnCode
72
72
  doConnack(
73
- { client: client, returnCode: returnCode, sessionPresent: false },
73
+ { client, returnCode, sessionPresent: false },
74
74
  done.bind(this, error))
75
75
  return
76
76
  }
@@ -156,8 +156,8 @@ function fetchSubs (arg, done) {
156
156
  if (!this.packet.clean) {
157
157
  client.broker.persistence.subscriptionsByClient({
158
158
  id: client.id,
159
- done: done,
160
- arg: arg
159
+ done,
160
+ arg
161
161
  }, gotSubs)
162
162
  return
163
163
  }
@@ -217,7 +217,7 @@ function completeSubscribe (err) {
217
217
  topic: $SYS_PREFIX + broker.id + '/new/subscribes',
218
218
  payload: Buffer.from(JSON.stringify({
219
219
  clientId: client.id,
220
- subs: subs
220
+ subs
221
221
  }), 'utf8')
222
222
  }, noop)
223
223
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "aedes",
3
- "version": "0.47.0",
3
+ "version": "0.48.0",
4
4
  "description": "Stream-based MQTT broker",
5
5
  "main": "aedes.js",
6
6
  "types": "aedes.d.ts",
7
7
  "scripts": {
8
8
  "lint": "npm run lint:standard && npm run lint:typescript && npm run lint:markdown",
9
- "lint:fix": "standard --fix",
9
+ "lint:fix": "standard --fix && eslint -c types/.eslintrc.json --fix aedes.d.ts types/**/*.ts test/types/**/*.test-d.ts",
10
10
  "lint:standard": "standard --verbose | snazzy",
11
11
  "lint:typescript": "eslint -c types/.eslintrc.json aedes.d.ts types/**/*.ts test/types/**/*.test-d.ts",
12
12
  "lint:markdown": "markdownlint docs/*.md README.md",
@@ -98,37 +98,37 @@
98
98
  },
99
99
  "devDependencies": {
100
100
  "@sinonjs/fake-timers": "^9.1.2",
101
- "@types/node": "^17.0.24",
102
- "@typescript-eslint/eslint-plugin": "^5.19.0",
103
- "@typescript-eslint/parser": "^5.19.0",
101
+ "@types/node": "^18.6.1",
102
+ "@typescript-eslint/eslint-plugin": "^5.31.0",
103
+ "@typescript-eslint/parser": "^5.31.0",
104
104
  "concat-stream": "^2.0.0",
105
105
  "duplexify": "^4.1.2",
106
106
  "license-checker": "^25.0.1",
107
- "markdownlint-cli": "^0.31.1",
107
+ "markdownlint-cli": "^0.32.1",
108
108
  "mqtt": "^4.3.7",
109
109
  "mqtt-connection": "^4.1.0",
110
110
  "pre-commit": "^1.2.2",
111
111
  "proxyquire": "^2.1.3",
112
- "release-it": "^14.14.2",
112
+ "release-it": "^15.2.0",
113
113
  "snazzy": "^9.0.0",
114
- "standard": "^16.0.4",
115
- "tap": "^16.0.1",
116
- "tsd": "^0.20.0",
117
- "typescript": "^4.6.3",
114
+ "standard": "^17.0.0",
115
+ "tap": "^16.3.0",
116
+ "tsd": "^0.23.0",
117
+ "typescript": "^4.7.4",
118
118
  "websocket-stream": "^5.5.2"
119
119
  },
120
120
  "dependencies": {
121
121
  "aedes-packet": "^3.0.0",
122
- "aedes-persistence": "^9.1.1",
122
+ "aedes-persistence": "^9.1.2",
123
123
  "end-of-stream": "^1.4.4",
124
124
  "fastfall": "^1.5.1",
125
125
  "fastparallel": "^2.4.1",
126
126
  "fastseries": "^2.0.0",
127
127
  "hyperid": "^3.0.1",
128
- "mqemitter": "^4.5.0",
129
- "mqtt-packet": "^7.1.2",
128
+ "mqemitter": "^5.0.0",
129
+ "mqtt-packet": "^8.1.1",
130
130
  "retimer": "^3.0.0",
131
131
  "reusify": "^1.0.4",
132
- "uuid": "^8.3.2"
132
+ "uuid": "^9.0.0"
133
133
  }
134
134
  }
package/test/connect.js CHANGED
@@ -214,13 +214,13 @@ test('client connect clear outgoing', function (t) {
214
214
  const broker = aedes({ id: brokerId })
215
215
  t.teardown(broker.close.bind(broker))
216
216
 
217
- const subs = [{ clientId: clientId }]
217
+ const subs = [{ clientId }]
218
218
  const packet = {
219
219
  cmd: 'publish',
220
220
  topic: 'hello',
221
221
  payload: Buffer.from('world'),
222
222
  qos: 1,
223
- brokerId: brokerId,
223
+ brokerId,
224
224
  brokerCounter: 2,
225
225
  retain: true,
226
226
  messageId: 42,
@@ -235,7 +235,7 @@ test('client connect clear outgoing', function (t) {
235
235
  protocolId: 'MQTT',
236
236
  protocolVersion: 4,
237
237
  clean: true,
238
- clientId: clientId,
238
+ clientId,
239
239
  keepalive: 0
240
240
  })
241
241
 
@@ -705,7 +705,7 @@ test('websocket clients have access to the request object', function (t) {
705
705
 
706
706
  const server = http.createServer()
707
707
  ws.createServer({
708
- server: server
708
+ server
709
709
  }, broker.handle)
710
710
 
711
711
  server.listen(port, function (err) {
package/test/events.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { test } = require('tap')
4
+ const mqemitter = require('mqemitter')
4
5
  const { setup, connect, subscribe } = require('./helper')
5
6
  const aedes = require('../')
6
7
 
@@ -16,9 +17,49 @@ test('publishes an hearbeat', function (t) {
16
17
  const id = message.topic.match(/\$SYS\/([^/]+)\/heartbeat/)[1]
17
18
  t.equal(id, broker.id, 'broker id matches')
18
19
  t.same(message.payload.toString(), id, 'message has id as the payload')
20
+ cb()
19
21
  })
20
22
  })
21
23
 
24
+ test('publishes birth', function (t) {
25
+ t.plan(4)
26
+
27
+ const mq = mqemitter()
28
+ const brokerId = 'test-broker'
29
+ const fakeBroker = 'fake-broker'
30
+ const clientId = 'test-client'
31
+
32
+ mq.on(`$SYS/${brokerId}/birth`, (message, cb) => {
33
+ t.pass('broker birth received')
34
+ t.same(message.payload.toString(), brokerId, 'message has id as the payload')
35
+ cb()
36
+ })
37
+
38
+ const broker = aedes({
39
+ id: brokerId,
40
+ mq
41
+ })
42
+
43
+ broker.on('client', (client) => {
44
+ t.equal(client.id, clientId, 'client connected')
45
+ // set a fake counter on a fake broker
46
+ process.nextTick(() => {
47
+ broker.clients[clientId].duplicates[fakeBroker] = 42
48
+ mq.emit({ topic: `$SYS/${fakeBroker}/birth`, payload: Buffer.from(fakeBroker) })
49
+ })
50
+ })
51
+
52
+ mq.on(`$SYS/${fakeBroker}/birth`, (message, cb) => {
53
+ process.nextTick(() => {
54
+ t.equal(!!broker.clients[clientId].duplicates[fakeBroker], false, 'client duplicates has been resetted')
55
+ cb()
56
+ })
57
+ })
58
+
59
+ const s = connect(setup(broker), { clientId })
60
+ t.teardown(s.broker.close.bind(s.broker))
61
+ })
62
+
22
63
  ;['$mcollina', '$SYS'].forEach(function (topic) {
23
64
  test('does not forward $ prefixed topics to # subscription - ' + topic, function (t) {
24
65
  t.plan(4)
@@ -161,7 +202,7 @@ test('Test backpressure aedes published function', function (t) {
161
202
 
162
203
  server.listen(0, function () {
163
204
  const port = server.address().port
164
- publisher = mqtt.connect({ port: port, host: 'localhost', clean: true, keepalive: 30 })
205
+ publisher = mqtt.connect({ port, host: 'localhost', clean: true, keepalive: 30 })
165
206
 
166
207
  function next () {
167
208
  if (--publishCount > 0) { process.nextTick(publish) }
package/test/helper.js CHANGED
@@ -19,10 +19,10 @@ function setup (broker) {
19
19
 
20
20
  return {
21
21
  client: broker.handle(conn),
22
- conn: conn,
23
- inStream: inStream,
24
- outStream: outStream,
25
- broker: broker
22
+ conn,
23
+ inStream,
24
+ outStream,
25
+ broker
26
26
  }
27
27
  }
28
28
 
@@ -74,8 +74,8 @@ function subscribe (t, subscriber, topic, qos, done) {
74
74
  cmd: 'subscribe',
75
75
  messageId: 24,
76
76
  subscriptions: [{
77
- topic: topic,
78
- qos: qos
77
+ topic,
78
+ qos
79
79
  }]
80
80
  })
81
81
 
@@ -110,10 +110,10 @@ function subscribeMultiple (t, subscriber, subs, expectedGranted, done) {
110
110
  }
111
111
 
112
112
  module.exports = {
113
- setup: setup,
114
- connect: connect,
115
- noError: noError,
116
- subscribe: subscribe,
117
- subscribeMultiple: subscribeMultiple,
113
+ setup,
114
+ connect,
115
+ noError,
116
+ subscribe,
117
+ subscribeMultiple,
118
118
  delay: util.promisify(setTimeout)
119
119
  }
package/test/meta.js CHANGED
@@ -307,7 +307,7 @@ test('connect and connackSent event', { timeout: 50 }, function (t) {
307
307
  protocolId: 'MQTT',
308
308
  protocolVersion: 4,
309
309
  clean: true,
310
- clientId: clientId,
310
+ clientId,
311
311
  keepalive: 0
312
312
  })
313
313
 
@@ -42,7 +42,7 @@ test('connect 500 concurrent clients', function (t) {
42
42
 
43
43
  for (let i = 0; i < total; i++) {
44
44
  clients[i] = mqtt.connect({
45
- port: port,
45
+ port,
46
46
  keepalive: 0,
47
47
  reconnectPeriod: 100
48
48
  }).on('connect', function () {
@@ -79,7 +79,7 @@ test('do not block after a subscription', function (t) {
79
79
  const port = server.address().port
80
80
 
81
81
  const publisher = mqtt.connect({
82
- port: port,
82
+ port,
83
83
  keepalive: 0
84
84
  }).on('error', function (err) {
85
85
  clock.clearTimeout(clockId)
@@ -103,7 +103,7 @@ test('do not block after a subscription', function (t) {
103
103
 
104
104
  function startSubscriber () {
105
105
  subscriber = mqtt.connect({
106
- port: port,
106
+ port,
107
107
  keepalive: 0
108
108
  }).on('error', function (err) {
109
109
  clock.clearTimeout(clockId)
@@ -163,7 +163,7 @@ test('do not block with overlapping subscription', function (t) {
163
163
  const port = server.address().port
164
164
 
165
165
  const publisher = mqtt.connect({
166
- port: port,
166
+ port,
167
167
  keepalive: 0
168
168
  }).on('error', function (err) {
169
169
  clock.clearTimeout(clockId)
@@ -187,7 +187,7 @@ test('do not block with overlapping subscription', function (t) {
187
187
 
188
188
  function startSubscriber () {
189
189
  subscriber = mqtt.connect({
190
- port: port,
190
+ port,
191
191
  keepalive: 0
192
192
  }).on('error', function (err) {
193
193
  clock.clearTimeout(clockId)
package/test/retain.js CHANGED
@@ -275,7 +275,7 @@ test('new QoS 0 subscribers receive QoS 0 retained messages when clean', functio
275
275
  })
276
276
 
277
277
  clock.setTimeout(() => {
278
- t.equal(broker.counter, 8)
278
+ t.equal(broker.counter, 9)
279
279
  }, 200)
280
280
  })
281
281
 
@@ -315,7 +315,7 @@ test('new QoS 0 subscribers receive downgraded QoS 1 retained messages when clea
315
315
  })
316
316
  })
317
317
  broker.on('closed', function () {
318
- t.equal(broker.counter, 7)
318
+ t.equal(broker.counter, 8)
319
319
  })
320
320
  })
321
321
 
package/test/topics.js CHANGED
@@ -108,7 +108,7 @@ test('publish invalid topic with +', function (t) {
108
108
  cmd: 'subscribe',
109
109
  messageId: 24,
110
110
  subscriptions: [{
111
- topic: topic,
111
+ topic,
112
112
  qos: 0
113
113
  }]
114
114
  })
@@ -157,7 +157,7 @@ test('topics are case-sensitive', function (t) {
157
157
  ;['hello', 'HELLO', 'heLLo', 'HELLO/#', 'hello/+'].forEach(function (topic) {
158
158
  publisher.inStream.write({
159
159
  cmd: 'publish',
160
- topic: topic,
160
+ topic,
161
161
  payload: 'world',
162
162
  qos: 0,
163
163
  retain: false
@@ -171,7 +171,7 @@ function subscribeMultipleTopics (t, broker, qos, subscriber, subscriptions, don
171
171
  subscriber.inStream.write({
172
172
  cmd: 'subscribe',
173
173
  messageId: 24,
174
- subscriptions: subscriptions
174
+ subscriptions
175
175
  })
176
176
 
177
177
  subscriber.outStream.once('data', function (packet) {
@@ -183,7 +183,7 @@ function subscribeMultipleTopics (t, broker, qos, subscriber, subscriptions, don
183
183
  cmd: 'publish',
184
184
  topic: 'hello/world',
185
185
  payload: 'world',
186
- qos: qos,
186
+ qos,
187
187
  messageId: 42
188
188
  })
189
189
 
@@ -1,17 +1,19 @@
1
+ /// <reference path="../../aedes.d.ts" />
2
+
3
+ import type { AedesPublishPacket, ConnackPacket, ConnectPacket, PingreqPacket, PublishPacket, PubrelPacket, SubscribePacket, Subscription, UnsubscribePacket } from 'aedes:packet'
4
+ import type { AuthenticateError, Brokers, Connection } from 'aedes:server'
5
+ import Server, { Aedes } from 'aedes:server'
6
+
7
+ import { IncomingMessage } from 'node:http'
8
+ import type { Client } from 'aedes:client'
9
+ import { Socket } from 'node:net'
1
10
  import { expectType } from 'tsd'
2
- import { Socket } from 'net'
3
- import type {
4
- Aedes,
5
- Brokers,
6
- AuthenticateError,
7
- Client,
8
- Connection
9
- } from '../../aedes'
10
- import { Server } from '../../aedes'
11
- import type { AedesPublishPacket, ConnackPacket, ConnectPacket, PingreqPacket, PublishPacket, PubrelPacket, Subscription, SubscribePacket, UnsubscribePacket } from '../../types/packet'
12
11
 
13
12
  // Aedes server
14
- const broker = Server({
13
+ let broker = new Server()
14
+ expectType<Aedes>(broker)
15
+
16
+ broker = new Aedes({
15
17
  id: 'aedes',
16
18
  concurrency: 100,
17
19
  heartbeatInterval: 60000,
@@ -37,7 +39,7 @@ const broker = Server({
37
39
  callback(error, false)
38
40
  }
39
41
  },
40
- authorizePublish: (client: Client, packet: PublishPacket, callback) => {
42
+ authorizePublish: (client: Client | null, packet: PublishPacket, callback) => {
41
43
  if (packet.topic === 'aaaa') {
42
44
  return callback(new Error('wrong topic'))
43
45
  }
@@ -93,7 +95,7 @@ expectType<Aedes>(broker.on('clientError', (client: Client, error: Error) => {})
93
95
  expectType<Aedes>(broker.on('connectionError', (client: Client, error: Error) => {}))
94
96
  expectType<Aedes>(broker.on('connackSent', (packet: ConnackPacket, client: Client) => {}))
95
97
  expectType<Aedes>(broker.on('ping', (packet: PingreqPacket, client: Client) => {}))
96
- expectType<Aedes>(broker.on('publish', (packet: AedesPublishPacket, client: Client) => {}))
98
+ expectType<Aedes>(broker.on('publish', (packet: AedesPublishPacket, client: Client | null) => {}))
97
99
  expectType<Aedes>(broker.on('ack', (packet: PublishPacket | PubrelPacket, client: Client) => {}))
98
100
  expectType<Aedes>(broker.on('subscribe', (subscriptions: Subscription[], client: Client) => {}))
99
101
  expectType<Aedes>(broker.on('unsubscribe', (unsubscriptions: string[], client: Client) => {}))
@@ -123,11 +125,12 @@ expectType<void>(broker.close())
123
125
  expectType<void>(broker.close(() => {}))
124
126
 
125
127
  // Aedes client
126
- const client = broker.handle({} as Connection)
128
+ const client = broker.handle({} as Connection, {} as IncomingMessage)
127
129
 
128
130
  expectType<Client>(client)
129
131
 
130
132
  expectType<Connection>(client.conn)
133
+ expectType<IncomingMessage>(client.req!)
131
134
 
132
135
  expectType<Client>(client.on('connected', () => {}))
133
136
  expectType<Client>(client.on('error', (error: Error) => {
package/test/will.js CHANGED
@@ -58,8 +58,11 @@ test('calling close two times should not deliver two wills', function (t) {
58
58
  willConnect(setup(broker), opts)
59
59
 
60
60
  function onWill (packet, cb) {
61
- broker.mq.removeListener('mywill', onWill)
62
- broker.mq.on('mywill', t.fail.bind(t))
61
+ broker.mq.removeListener('mywill', onWill, function () {
62
+ broker.mq.on('mywill', function (packet) {
63
+ t.fail('the will must be delivered only once')
64
+ })
65
+ })
63
66
  t.equal(packet.topic, opts.will.topic, 'topic matches')
64
67
  t.same(packet.payload, opts.will.payload, 'payload matches')
65
68
  t.equal(packet.qos, opts.will.qos, 'qos matches')
@@ -69,7 +72,7 @@ test('calling close two times should not deliver two wills', function (t) {
69
72
  })
70
73
 
71
74
  test('delivers old will in case of a crash', function (t) {
72
- t.plan(6)
75
+ t.plan(8)
73
76
 
74
77
  const persistence = memory()
75
78
  const will = {
@@ -88,10 +91,16 @@ test('delivers old will in case of a crash', function (t) {
88
91
  }, will, function (err) {
89
92
  t.error(err, 'no error')
90
93
 
94
+ let authorized = false
91
95
  const interval = 10 // ms, so that the will check happens fast!
92
96
  const broker = aedes({
93
- persistence: persistence,
94
- heartbeatInterval: interval
97
+ persistence,
98
+ heartbeatInterval: interval,
99
+ authorizePublish: function (client, packet, callback) {
100
+ t.strictSame(client, null, 'client must be null')
101
+ authorized = true
102
+ callback(null)
103
+ }
95
104
  })
96
105
  t.teardown(broker.close.bind(broker))
97
106
 
@@ -100,15 +109,58 @@ test('delivers old will in case of a crash', function (t) {
100
109
  broker.mq.on('mywill', check)
101
110
 
102
111
  function check (packet, cb) {
103
- broker.mq.removeListener('mywill', check)
112
+ broker.mq.removeListener('mywill', check, function () {
113
+ broker.mq.on('mywill', function (packet) {
114
+ t.fail('the will must be delivered only once')
115
+ })
116
+ })
104
117
  t.ok(Date.now() - start >= 3 * interval, 'the will needs to be emitted after 3 heartbeats')
105
118
  t.equal(packet.topic, will.topic, 'topic matches')
106
119
  t.same(packet.payload, will.payload, 'payload matches')
107
120
  t.equal(packet.qos, will.qos, 'qos matches')
108
121
  t.equal(packet.retain, will.retain, 'retain matches')
109
- broker.mq.on('mywill', function (packet) {
110
- t.fail('the will must be delivered only once')
111
- })
122
+ t.equal(authorized, true, 'authorization called')
123
+ cb()
124
+ }
125
+ })
126
+ })
127
+
128
+ test('deliver old will without authorization in case of a crash', function (t) {
129
+ t.plan(2)
130
+
131
+ const persistence = memory()
132
+ const will = {
133
+ topic: 'mywill',
134
+ payload: Buffer.from('last will'),
135
+ qos: 0,
136
+ retain: false
137
+ }
138
+
139
+ persistence.broker = {
140
+ id: 'anotherBroker'
141
+ }
142
+
143
+ persistence.putWill({
144
+ id: 'myClientId42'
145
+ }, will, function (err) {
146
+ t.error(err, 'no error')
147
+
148
+ const interval = 10 // ms, so that the will check happens fast!
149
+ const broker = aedes({
150
+ persistence,
151
+ heartbeatInterval: interval,
152
+ authorizePublish: function (client, packet, callback) {
153
+ t.strictSame(client, null, 'client must be null')
154
+ callback(new Error())
155
+ }
156
+ })
157
+
158
+ t.teardown(broker.close.bind(broker))
159
+
160
+ broker.mq.on('mywill', check)
161
+
162
+ function check (packet, cb) {
163
+ t.fail('received will without authorization')
112
164
  cb()
113
165
  }
114
166
  })
@@ -121,7 +173,7 @@ test('delete old broker', function (t) {
121
173
 
122
174
  const heartbeatInterval = 100
123
175
  const broker = aedes({
124
- heartbeatInterval: heartbeatInterval
176
+ heartbeatInterval
125
177
  })
126
178
  t.teardown(broker.close.bind(broker))
127
179
 
@@ -185,8 +237,7 @@ test('delete the will in the persistence after publish', function (t) {
185
237
  willConnect(setup(broker), opts)
186
238
 
187
239
  function check (packet, cb) {
188
- broker.mq.removeListener('mywill', check)
189
- setImmediate(function () {
240
+ broker.mq.removeListener('mywill', check, function () {
190
241
  broker.persistence.getWill({
191
242
  id: opts.clientId
192
243
  }, function (err, p) {
@@ -421,7 +472,7 @@ test('don\'t delivers a will if broker alive', function (t) {
421
472
  t.error(err, 'no error')
422
473
 
423
474
  const opts = {
424
- persistence: persistence,
475
+ persistence,
425
476
  heartbeatInterval: 10
426
477
  }
427
478
 
@@ -472,7 +523,7 @@ test('handle will publish error', function (t) {
472
523
  t.error(err, 'no error')
473
524
 
474
525
  const opts = {
475
- persistence: persistence,
526
+ persistence,
476
527
  heartbeatInterval: 10
477
528
  }
478
529
 
@@ -509,7 +560,7 @@ test('handle will publish error 2', function (t) {
509
560
  t.error(err, 'no error')
510
561
 
511
562
  const opts = {
512
- persistence: persistence,
563
+ persistence,
513
564
  heartbeatInterval: 10
514
565
  }
515
566
 
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "extends": [
3
3
  "eslint:recommended",
4
- "plugin:@typescript-eslint/eslint-recommended",
5
4
  "plugin:@typescript-eslint/recommended",
6
5
  "standard"
7
6
  ],
@@ -15,16 +14,19 @@
15
14
  "createDefaultProgram": true
16
15
  },
17
16
  "rules": {
17
+ "linebreak-style": ["warn", "unix"],
18
18
  "no-console": "off",
19
- "@typescript-eslint/indent": ["error", 2],
20
19
  "semi": ["error", "never"],
21
- "import/export": "off" // this errors on multiple exports (overload interfaces)
20
+ "import/export": "off", // this errors on multiple exports (overload interfaces)
21
+ "@typescript-eslint/indent": ["error", 2]
22
22
  },
23
23
  "overrides": [
24
24
  {
25
- "files": ["*.d.ts","*.test-d.ts"],
25
+ "files": ["*.d.ts", "*.test-d.ts"],
26
26
  "rules": {
27
- "@typescript-eslint/no-explicit-any": "off"
27
+ "no-dupe-class-members": "off",
28
+ "@typescript-eslint/no-explicit-any": "off",
29
+ "@typescript-eslint/triple-slash-reference": "off"
28
30
  }
29
31
  },
30
32
  {
package/types/client.d.ts CHANGED
@@ -1,27 +1,32 @@
1
- import { IncomingMessage } from 'http'
2
- import { PublishPacket, SubscribePacket, Subscription, Subscriptions, UnsubscribePacket } from './packet'
3
- import { Connection } from './instance'
4
- import { EventEmitter } from 'events'
1
+ declare module 'client' {
2
+ import { IncomingMessage } from 'node:http'
3
+ import { PublishPacket, SubscribePacket, Subscription, Subscriptions, UnsubscribePacket } from 'aedes:packet'
4
+ import { Connection } from 'aedes:server'
5
+ import { EventEmitter } from 'node:events'
5
6
 
6
- export interface Client extends EventEmitter {
7
- id: Readonly<string>
8
- clean: Readonly<boolean>
9
- version: Readonly<number>
10
- conn: Connection
11
- req?: IncomingMessage
12
- connecting: Readonly<boolean>
13
- connected: Readonly<boolean>
14
- closed: Readonly<boolean>
7
+ export interface Client extends EventEmitter {
8
+ id: Readonly<string>
9
+ clean: Readonly<boolean>
10
+ version: Readonly<number>
11
+ conn: Connection
12
+ req?: IncomingMessage
13
+ connecting: Readonly<boolean>
14
+ connected: Readonly<boolean>
15
+ closed: Readonly<boolean>
15
16
 
16
- on (event: 'connected', listener: () => void): this
17
- on (event: 'error', listener: (error: Error) => void): this
17
+ on (event: 'connected', listener: () => void): this
18
+ on (event: 'error', listener: (error: Error) => void): this
18
19
 
19
- publish (message: PublishPacket, callback?: (error?: Error) => void): void
20
- subscribe (
21
- subscriptions: Subscriptions | Subscription | Subscription[] | SubscribePacket,
22
- callback?: (error?: Error) => void
23
- ): void
24
- unsubscribe (topicObjects: Subscriptions | Subscription | Subscription[] | UnsubscribePacket, callback?: (error?: Error) => void): void
25
- close (callback?: () => void): void
26
- emptyOutgoingQueue (callback?: () => void): void
20
+ publish (message: PublishPacket, callback?: (error?: Error) => void): void
21
+ subscribe (
22
+ subscriptions: Subscriptions | Subscription | Subscription[] | SubscribePacket,
23
+ callback?: (error?: Error) => void
24
+ ): void
25
+ unsubscribe (topicObjects: Subscriptions | Subscription | Subscription[] | UnsubscribePacket, callback?: (error?: Error) => void): void
26
+ close (callback?: () => void): void
27
+ emptyOutgoingQueue (callback?: () => void): void
28
+ }
29
+ }
30
+ declare module 'aedes:client' {
31
+ export * from 'client'
27
32
  }
@@ -1,100 +1,101 @@
1
- import { Duplex } from 'stream'
2
- import { Socket } from 'net'
3
- import { Client } from './client'
4
- import type { AedesPublishPacket, ConnectPacket, ConnackPacket, Subscription, PingreqPacket, PublishPacket, PubrelPacket } from './packet'
5
- import { EventEmitter } from 'events'
6
-
7
- type LastHearthbeatTimestamp = Date;
8
-
9
- export interface Brokers {
10
- [brokerId: string]: LastHearthbeatTimestamp;
11
- }
12
-
13
- export type Connection = Duplex | Socket
14
-
15
- /* eslint no-unused-vars: 0 */
16
- export const enum AuthErrorCode {
17
- UNNACCEPTABLE_PROTOCOL = 1,
18
- IDENTIFIER_REJECTED = 2,
19
- SERVER_UNAVAILABLE = 3,
20
- BAD_USERNAME_OR_PASSWORD = 4,
21
- NOT_AUTHORIZED = 5
22
- }
23
-
24
- export type AuthenticateError = Error & { returnCode: AuthErrorCode }
25
-
26
- type PreConnectHandler = (client: Client, packet: ConnectPacket, callback: (error: Error | null, success: boolean) => void) => void
27
-
28
- type AuthenticateHandler = (
29
- client: Client,
30
- username: Readonly<string>,
31
- password: Readonly<Buffer>,
32
- done: (error: AuthenticateError | null, success: boolean | null) => void
33
- ) => void
34
-
35
- type AuthorizePublishHandler = (client: Client, packet: PublishPacket, callback: (error?: Error | null) => void) => void
36
-
37
- type AuthorizeSubscribeHandler = (client: Client, subscription: Subscription, callback: (error: Error | null, subscription?: Subscription | null) => void) => void
38
-
39
- type AuthorizeForwardHandler = (client: Client, packet: AedesPublishPacket) => AedesPublishPacket | null | void
40
-
41
- type PublishedHandler = (packet: AedesPublishPacket, client: Client, callback: (error?: Error | null) => void) => void
42
-
43
- export interface AedesOptions {
44
- mq?: any
45
- id?: string
46
- persistence?: any
47
- concurrency?: number
48
- heartbeatInterval?: number
49
- connectTimeout?: number
50
- queueLimit?: number
51
- maxClientsIdLength?: number
52
- preConnect?: PreConnectHandler
53
- authenticate?: AuthenticateHandler
54
- authorizePublish?: AuthorizePublishHandler
55
- authorizeSubscribe?: AuthorizeSubscribeHandler
56
- authorizeForward?: AuthorizeForwardHandler
57
- published?: PublishedHandler
1
+ declare module 'aedes' {
2
+ import { Duplex } from 'node:stream'
3
+ import { Socket } from 'node:net'
4
+ import { IncomingMessage } from 'http'
5
+ import { Client } from 'aedes:client'
6
+ import type { AedesPublishPacket, ConnectPacket, ConnackPacket, Subscription, PingreqPacket, PublishPacket, PubrelPacket } from 'aedes:packet'
7
+ import { EventEmitter } from 'node:events'
8
+
9
+ type LastHearthbeatTimestamp = Date;
10
+
11
+ export interface Brokers {
12
+ [brokerId: string]: LastHearthbeatTimestamp;
13
+ }
14
+
15
+ export type Connection = Duplex | Socket
16
+
17
+ /* eslint no-unused-vars: 0 */
18
+ export const enum AuthErrorCode {
19
+ UNNACCEPTABLE_PROTOCOL = 1,
20
+ IDENTIFIER_REJECTED = 2,
21
+ SERVER_UNAVAILABLE = 3,
22
+ BAD_USERNAME_OR_PASSWORD = 4,
23
+ NOT_AUTHORIZED = 5
24
+ }
25
+
26
+ export type AuthenticateError = Error & { returnCode: AuthErrorCode }
27
+
28
+ type PreConnectHandler = (client: Client, packet: ConnectPacket, callback: (error: Error | null, success: boolean) => void) => void
29
+
30
+ type AuthenticateHandler = (
31
+ client: Client,
32
+ username: Readonly<string>,
33
+ password: Readonly<Buffer>,
34
+ done: (error: AuthenticateError | null, success: boolean | null) => void
35
+ ) => void
36
+
37
+ type AuthorizePublishHandler = (client: Client | null, packet: PublishPacket, callback: (error?: Error | null) => void) => void
38
+
39
+ type AuthorizeSubscribeHandler = (client: Client, subscription: Subscription, callback: (error: Error | null, subscription?: Subscription | null) => void) => void
40
+
41
+ type AuthorizeForwardHandler = (client: Client, packet: AedesPublishPacket) => AedesPublishPacket | null | void
42
+
43
+ type PublishedHandler = (packet: AedesPublishPacket, client: Client, callback: (error?: Error | null) => void) => void
44
+
45
+ export interface AedesOptions {
46
+ mq?: any
47
+ id?: string
48
+ persistence?: any
49
+ concurrency?: number
50
+ heartbeatInterval?: number
51
+ connectTimeout?: number
52
+ queueLimit?: number
53
+ maxClientsIdLength?: number
54
+ preConnect?: PreConnectHandler
55
+ authenticate?: AuthenticateHandler
56
+ authorizePublish?: AuthorizePublishHandler
57
+ authorizeSubscribe?: AuthorizeSubscribeHandler
58
+ authorizeForward?: AuthorizeForwardHandler
59
+ published?: PublishedHandler
60
+ }
61
+
62
+ export default class Aedes extends EventEmitter {
63
+ id: Readonly<string>
64
+ connectedClients: Readonly<number>
65
+ closed: Readonly<boolean>
66
+ brokers: Readonly<Brokers>
67
+
68
+ constructor(option?: AedesOptions)
69
+ handle: (stream: Connection, request: IncomingMessage) => Client
70
+
71
+ on (event: 'closed', listener: () => void): this
72
+ on (event: 'client' | 'clientReady' | 'clientDisconnect' | 'keepaliveTimeout', listener: (client: Client) => void): this
73
+ on (event: 'clientError' | 'connectionError', listener: (client: Client, error: Error) => void): this
74
+ on (event: 'connackSent', listener: (packet: ConnackPacket, client: Client) => void): this
75
+ on (event: 'ping', listener: (packet: PingreqPacket, client: Client) => void): this
76
+ on (event: 'publish', listener: (packet: AedesPublishPacket, client: Client | null) => void): this
77
+ on (event: 'ack', listener: (packet: PublishPacket | PubrelPacket, client: Client) => void): this
78
+ on (event: 'subscribe', listener: (subscriptions: Subscription[], client: Client) => void): this
79
+ on (event: 'unsubscribe', listener: (unsubscriptions: string[], client: Client) => void): this
80
+
81
+ publish (packet: PublishPacket, callback: (error?: Error) => void): void
82
+ subscribe (topic: string, deliverfunc: (packet: AedesPublishPacket, callback: () => void) => void, callback: () => void): void
83
+ unsubscribe (topic: string, deliverfunc: (packet: AedesPublishPacket, callback: () => void) => void, callback: () => void): void
84
+ close (callback?: () => void): void
85
+
86
+ preConnect: PreConnectHandler
87
+ authenticate: AuthenticateHandler
88
+ authorizePublish: AuthorizePublishHandler
89
+ authorizeSubscribe: AuthorizeSubscribeHandler
90
+ authorizeForward: AuthorizeForwardHandler
91
+ published: PublishedHandler
92
+ }
93
+
94
+ export { Aedes }
95
+ // export function createServer(options?: AedesOptions): Aedes
58
96
  }
59
97
 
60
- export interface Aedes extends EventEmitter {
61
- id: Readonly<string>
62
- connectedClients: Readonly<number>
63
- closed: Readonly<boolean>
64
- brokers: Readonly<Brokers>
65
-
66
- handle: (stream: Connection) => Client
67
-
68
- on (event: 'closed', listener: () => void): this
69
- on (event: 'client' | 'clientReady' | 'clientDisconnect' | 'keepaliveTimeout', listener: (client: Client) => void): this
70
- on (event: 'clientError' | 'connectionError', listener: (client: Client, error: Error) => void): this
71
- on (event: 'connackSent', listener: (packet: ConnackPacket, client: Client) => void): this
72
- on (event: 'ping', listener: (packet: PingreqPacket, client: Client) => void): this
73
- on (event: 'publish', listener: (packet: AedesPublishPacket, client: Client) => void): this
74
- on (event: 'ack', listener: (packet: PublishPacket | PubrelPacket, client: Client) => void): this
75
- on (event: 'subscribe', listener: (subscriptions: Subscription[], client: Client) => void): this
76
- on (event: 'unsubscribe', listener: (unsubscriptions: string[], client: Client) => void): this
77
-
78
- publish (
79
- packet: PublishPacket,
80
- callback: (error?: Error) => void
81
- ): void
82
- subscribe (
83
- topic: string,
84
- deliverfunc: (packet: AedesPublishPacket, callback: () => void) => void,
85
- callback: () => void
86
- ): void
87
- unsubscribe (
88
- topic: string,
89
- deliverfunc: (packet: AedesPublishPacket, callback: () => void) => void,
90
- callback: () => void
91
- ): void
92
- close (callback?: () => void): void
93
-
94
- preConnect: PreConnectHandler
95
- authenticate: AuthenticateHandler
96
- authorizePublish: AuthorizePublishHandler
97
- authorizeSubscribe: AuthorizeSubscribeHandler
98
- authorizeForward: AuthorizeForwardHandler
99
- published: PublishedHandler
98
+ declare module 'aedes:server' {
99
+ export * from 'aedes'
100
+ export { default } from 'aedes'
100
101
  }
package/types/packet.d.ts CHANGED
@@ -1,18 +1,23 @@
1
- import { AedesPacket } from 'aedes-packet'
2
- import { IConnackPacket, IConnectPacket, IPingreqPacket, IPublishPacket, IPubrelPacket, ISubscribePacket, ISubscription, IUnsubscribePacket } from 'mqtt-packet'
3
- import { Client } from './client'
1
+ declare module 'packet' {
2
+ import { AedesPacket } from 'aedes-packet'
3
+ import { IConnackPacket, IConnectPacket, IPingreqPacket, IPublishPacket, IPubrelPacket, ISubscribePacket, ISubscription, IUnsubscribePacket } from 'mqtt-packet'
4
+ import { Client } from 'aedes:client'
4
5
 
5
- export type SubscribePacket = ISubscribePacket & { cmd: 'subscribe' }
6
- export type UnsubscribePacket = IUnsubscribePacket & { cmd: 'unsubscribe' }
7
- export type Subscription = ISubscription & { clientId?: Client['id'] }
8
- export type Subscriptions = { subscriptions: Subscription[] }
6
+ export type SubscribePacket = ISubscribePacket & { cmd: 'subscribe' }
7
+ export type UnsubscribePacket = IUnsubscribePacket & { cmd: 'unsubscribe' }
8
+ export type Subscription = ISubscription & { clientId?: Client['id'] }
9
+ export type Subscriptions = { subscriptions: Subscription[] }
9
10
 
10
- export type PublishPacket = IPublishPacket & { cmd: 'publish' }
11
+ export type PublishPacket = IPublishPacket & { cmd: 'publish' }
11
12
 
12
- export type ConnectPacket = IConnectPacket & { cmd: 'connect' }
13
- export type ConnackPacket = IConnackPacket & { cmd: 'connack' }
13
+ export type ConnectPacket = IConnectPacket & { cmd: 'connect' }
14
+ export type ConnackPacket = IConnackPacket & { cmd: 'connack' }
14
15
 
15
- export type PubrelPacket = IPubrelPacket & { cmd: 'pubrel' }
16
- export type PingreqPacket = IPingreqPacket & { cmd: 'pingreq' }
16
+ export type PubrelPacket = IPubrelPacket & { cmd: 'pubrel' }
17
+ export type PingreqPacket = IPingreqPacket & { cmd: 'pingreq' }
17
18
 
18
- export type AedesPublishPacket = PublishPacket & AedesPacket
19
+ export type AedesPublishPacket = PublishPacket & AedesPacket
20
+ }
21
+ declare module 'aedes:packet' {
22
+ export * from 'packet'
23
+ }
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "module": "commonjs",
4
3
  "target": "es6",
4
+ "module": "commonjs",
5
5
  "noEmit": true,
6
- "strict": true
6
+ "strict": true,
7
+ "removeComments": true,
8
+ "typeRoots" : ["../types", "../node_modules/@types/"]
7
9
  },
8
10
  "include": [
9
- "/test/types/*.test-d.ts",
10
- "/types/*.d.ts",
11
+ "./test/types/*.test-d.ts",
12
+ "./types/*.d.ts",
11
13
  "aedes.d.ts"
12
14
  ]
13
15
  }