aedes 0.51.3 → 1.0.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.
Files changed (67) hide show
  1. package/.github/actions/sticky-pr-comment/action.yml +55 -0
  2. package/.github/workflows/benchmark-compare-serial.yml +60 -0
  3. package/.github/workflows/ci.yml +12 -17
  4. package/.release-it.json +18 -0
  5. package/.taprc +15 -6
  6. package/README.md +6 -4
  7. package/aedes.d.ts +0 -6
  8. package/aedes.js +270 -242
  9. package/benchmarks/README.md +33 -0
  10. package/benchmarks/pingpong.js +94 -25
  11. package/benchmarks/receiver.js +77 -0
  12. package/benchmarks/report.js +150 -0
  13. package/benchmarks/runBenchmarks.js +118 -0
  14. package/benchmarks/sender.js +86 -0
  15. package/benchmarks/server.js +19 -18
  16. package/checkVersion.js +20 -0
  17. package/docs/Aedes.md +66 -8
  18. package/docs/Client.md +3 -4
  19. package/docs/Examples.md +39 -22
  20. package/docs/MIGRATION.md +50 -0
  21. package/eslint.config.js +8 -0
  22. package/example.js +51 -40
  23. package/examples/clusters/index.js +28 -23
  24. package/examples/clusters/package.json +10 -6
  25. package/lib/client.js +405 -306
  26. package/lib/handlers/connect.js +42 -38
  27. package/lib/handlers/index.js +9 -11
  28. package/lib/handlers/ping.js +2 -3
  29. package/lib/handlers/puback.js +5 -5
  30. package/lib/handlers/publish.js +29 -14
  31. package/lib/handlers/pubrec.js +9 -17
  32. package/lib/handlers/pubrel.js +34 -25
  33. package/lib/handlers/subscribe.js +47 -43
  34. package/lib/handlers/unsubscribe.js +16 -19
  35. package/lib/qos-packet.js +14 -17
  36. package/lib/utils.js +5 -12
  37. package/lib/write.js +4 -5
  38. package/package.json +134 -136
  39. package/test/auth.js +468 -804
  40. package/test/basic.js +613 -575
  41. package/test/bridge.js +44 -40
  42. package/test/client-pub-sub.js +531 -504
  43. package/test/close_socket_by_other_party.js +137 -102
  44. package/test/connect.js +487 -484
  45. package/test/drain-timeout.js +593 -0
  46. package/test/drain-toxiproxy.js +620 -0
  47. package/test/events.js +173 -145
  48. package/test/helper.js +351 -73
  49. package/test/keep-alive.js +40 -67
  50. package/test/meta.js +257 -210
  51. package/test/not-blocking.js +93 -197
  52. package/test/qos1.js +464 -554
  53. package/test/qos2.js +308 -393
  54. package/test/regr-21.js +39 -21
  55. package/test/require.cjs +22 -0
  56. package/test/retain.js +349 -398
  57. package/test/topics.js +176 -183
  58. package/test/types/aedes.test-d.ts +4 -8
  59. package/test/will.js +310 -428
  60. package/types/instance.d.ts +40 -35
  61. package/types/packet.d.ts +10 -10
  62. package/.coveralls.yml +0 -1
  63. package/benchmarks/bombing.js +0 -34
  64. package/benchmarks/bombingQoS1.js +0 -36
  65. package/benchmarks/throughputCounter.js +0 -23
  66. package/benchmarks/throughputCounterQoS1.js +0 -33
  67. package/types/.eslintrc.json +0 -47
package/lib/client.js CHANGED
@@ -1,163 +1,433 @@
1
- 'use strict'
2
-
3
- const mqtt = require('mqtt-packet')
4
- const EventEmitter = require('events')
5
- const util = require('util')
6
- const eos = require('end-of-stream')
7
- const Packet = require('aedes-packet')
8
- const write = require('./write')
9
- const QoSPacket = require('./qos-packet')
10
- const handleSubscribe = require('./handlers/subscribe')
11
- const handleUnsubscribe = require('./handlers/unsubscribe')
12
- const handle = require('./handlers')
13
- const { pipeline } = require('stream')
14
- const { through } = require('./utils')
15
-
16
- module.exports = Client
17
-
18
- function Client (broker, conn, req) {
19
- const that = this
20
-
21
- // metadata
22
- this.closed = false
23
- this.connecting = false
24
- this.connected = false
25
- this.connackSent = false
26
- this.errored = false
27
-
28
- // mqtt params
29
- this.id = null
30
- this.clean = true
31
- this.version = null
32
-
33
- this.subscriptions = {}
34
- this.duplicates = {}
35
-
36
- this.broker = broker
37
- this.conn = conn
38
- conn.client = this
39
-
40
- this._disconnected = false
41
- this._authorized = false
42
- this._parsingBatch = 1
43
- this._nextId = Math.ceil(Math.random() * 65535)
44
-
45
- this.req = req
46
- this.connDetails = req ? req.connDetails : null
47
-
48
- // we use two variables for the will
49
- // because we store in _will while
50
- // we are authenticating
51
- this.will = null
52
- this._will = null
53
-
54
- this._parser = mqtt.parser()
55
- this._parser.client = this
56
- this._parser._queue = [] // queue packets received before client fires 'connect' event. Prevents memory leaks on 'connect' event
57
- this._parser.on('packet', enqueue)
58
- this.once('connected', dequeue)
59
-
60
- function nextBatch (err) {
61
- if (err) {
62
- that.emit('error', err)
63
- return
1
+ import mqtt from 'mqtt-packet'
2
+ import EventEmitter from 'node:events'
3
+ import util from 'util'
4
+ import eos from 'end-of-stream'
5
+ import Packet from 'aedes-packet'
6
+ import write from './write.js'
7
+ import QoSPacket from './qos-packet.js'
8
+ import handleSubscribe from './handlers/subscribe.js'
9
+ import handleUnsubscribe from './handlers/unsubscribe.js'
10
+ import handle from './handlers/index.js'
11
+ import { pipeline } from 'stream'
12
+ import { through } from './utils.js'
13
+
14
+ class Client {
15
+ constructor (broker, conn, req) {
16
+ const that = this
17
+
18
+ // metadata
19
+ this.closed = false
20
+ this.connecting = false
21
+ this.connected = false
22
+ this.connackSent = false
23
+ this.errored = false
24
+
25
+ // mqtt params
26
+ this.id = null
27
+ this.clean = true
28
+ this.version = null
29
+
30
+ this.subscriptions = {}
31
+ this.duplicates = {}
32
+
33
+ this.broker = broker
34
+ this.conn = conn
35
+ conn.client = this
36
+
37
+ this._disconnected = false
38
+ this._authorized = false
39
+ this._parsingBatch = 1
40
+ this._nextId = Math.ceil(Math.random() * 65535)
41
+
42
+ // Drain timeout tracking - coalesced timer approach
43
+ this._drainTimer = null
44
+ this._pendingDrains = []
45
+
46
+ this.req = req
47
+ this.connDetails = req ? req.connDetails : null
48
+
49
+ // we use two variables for the will
50
+ // because we store in _will while
51
+ // we are authenticating
52
+ this.will = null
53
+ this._will = null
54
+
55
+ this._parser = mqtt.parser()
56
+ this._parser.client = this
57
+ this._parser._queue = [] // queue packets received before client fires 'connect' event. Prevents memory leaks on 'connect' event
58
+ this._parser.on('packet', enqueue)
59
+ this.once('connected', dequeue)
60
+
61
+ function nextBatch (err) {
62
+ if (err) {
63
+ that.emit('error', err)
64
+ return
65
+ }
66
+
67
+ const client = that
68
+
69
+ if (client._paused) {
70
+ return
71
+ }
72
+
73
+ that._parsingBatch--
74
+ if (that._parsingBatch <= 0) {
75
+ that._parsingBatch = 0
76
+ const buf = client.conn.read(null)
77
+ if (buf) {
78
+ client._parser.parse(buf)
79
+ }
80
+ }
64
81
  }
82
+ this._nextBatch = nextBatch
83
+
84
+ conn.on('readable', nextBatch)
65
85
 
66
- const client = that
86
+ this.on('error', this._onError)
87
+ conn.on('error', this.emit.bind(this, 'error'))
88
+ this._parser.on('error', this.emit.bind(this, 'error'))
89
+
90
+ conn.on('end', this.close.bind(this))
91
+ this._eos = eos(this.conn, this.close.bind(this))
92
+
93
+ const getToForwardPacket = (_packet) => {
94
+ // Mqttv5 3.8.3.1: https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html#_Toc3901169
95
+ // prevent to forward messages sent by the same client when no-local flag is set
96
+ if (_packet.clientId === that.id && _packet.nl) return
97
+
98
+ const toForward = dedupe(that, _packet) &&
99
+ that.broker.authorizeForward(that, _packet)
100
+
101
+ return toForward
102
+ }
103
+
104
+ this.deliver0 = function deliverQoS0 (_packet, cb) {
105
+ const toForward = getToForwardPacket(_packet)
106
+
107
+ if (toForward) {
108
+ // Give nodejs some time to clear stacks, or we will see
109
+ // "Maximum call stack size exceeded" in a very high load
110
+ setImmediate(() => {
111
+ const packet = new Packet(toForward, broker)
112
+ packet.qos = 0
113
+ write(that, packet, function (err) {
114
+ that._onError(err)
115
+ cb() // don't pass the error here or it will be thrown by mqemitter
116
+ })
117
+ })
118
+ } else {
119
+ setImmediate(cb)
120
+ }
121
+ }
67
122
 
68
- if (client._paused) {
123
+ this.deliverQoS = function deliverQoS (_packet, cb) {
124
+ // downgrade to qos0 if requested by publish
125
+ if (_packet.qos === 0) {
126
+ that.deliver0(_packet, cb)
127
+ return
128
+ }
129
+ const toForward = getToForwardPacket(_packet)
130
+
131
+ if (toForward) {
132
+ setImmediate(() => {
133
+ const packet = new QoSPacket(toForward, that)
134
+ // Downgrading to client subscription qos if needed
135
+ const clientSub = that.subscriptions[packet.topic]
136
+ if (clientSub && (clientSub.qos || 0) < packet.qos) {
137
+ packet.qos = clientSub.qos
138
+ }
139
+ packet.writeCallback = cb
140
+ const doWriteQoS = (err = null) => writeQoS(err, that, packet)
141
+ if (that.clean || packet.retain) {
142
+ doWriteQoS()
143
+ } else {
144
+ broker.persistence.outgoingUpdate(that, packet)
145
+ .then(doWriteQoS, doWriteQoS)
146
+ }
147
+ })
148
+ } else if (that.clean === false) {
149
+ that.broker.persistence.outgoingClearMessageId(that, _packet)
150
+ .then(noop, noop)
151
+ // we consider this to be an error, since the packet is undefined
152
+ // so there's nothing to send
153
+ setImmediate(cb)
154
+ } else {
155
+ setImmediate(cb)
156
+ }
157
+ }
158
+
159
+ this._keepaliveTimer = null
160
+ this._keepaliveInterval = -1
161
+
162
+ this._connectTimer = setTimeout(function () {
163
+ that.emit('error', new Error('connect did not arrive in time'))
164
+ }, broker.connectTimeout)
165
+ }
166
+
167
+ _onError (err) {
168
+ if (!err) return
169
+
170
+ this.errored = true
171
+ this.conn.removeAllListeners('error')
172
+ this.conn.on('error', noop)
173
+ // hack to clean up the write callbacks in case of error
174
+ const state = this.conn._writableState
175
+ const list = typeof state.getBuffer === 'function' ? state.getBuffer() : state.buffer
176
+ list.forEach(drainRequest)
177
+ this.broker.emit(this.id ? 'clientError' : 'connectionError', this, err)
178
+ this.close()
179
+ }
180
+
181
+ publish (message, done) {
182
+ const packet = new Packet(message, this.broker)
183
+ const that = this
184
+ if (packet.qos === 0) {
185
+ // skip offline and send it as it is
186
+ this.deliver0(packet, done)
69
187
  return
70
188
  }
71
189
 
72
- that._parsingBatch--
73
- if (that._parsingBatch <= 0) {
74
- that._parsingBatch = 0
75
- const buf = client.conn.read(null)
76
- if (buf) {
77
- client._parser.parse(buf)
190
+ if (!this.clean && this.id) {
191
+ this.broker.persistence.outgoingEnqueue({ clientId: this.id }, packet)
192
+ .then(() => that.deliverQoS(packet, done), done)
193
+ } else {
194
+ that.deliverQoS(packet, done)
195
+ }
196
+ }
197
+
198
+ subscribe (packet, done) {
199
+ if (!packet.subscriptions) {
200
+ if (!Array.isArray(packet)) {
201
+ packet = [packet]
202
+ }
203
+ packet = {
204
+ subscriptions: packet
205
+ }
206
+ }
207
+ handleSubscribe(this, packet, false, done)
208
+ }
209
+
210
+ unsubscribe (packet, done) {
211
+ if (!packet.unsubscriptions) {
212
+ if (!Array.isArray(packet)) {
213
+ packet = [packet]
214
+ }
215
+ packet = {
216
+ unsubscriptions: packet
78
217
  }
79
218
  }
219
+ handleUnsubscribe(this, packet, done)
80
220
  }
81
- this._nextBatch = nextBatch
82
221
 
83
- conn.on('readable', nextBatch)
222
+ /**
223
+ * Handle successful drain - socket is writable again
224
+ * Clears timer and completes all pending drain callbacks
225
+ */
226
+ _handleDrain () {
227
+ // Clear the single per-client timer
228
+ if (this._drainTimer) {
229
+ clearTimeout(this._drainTimer)
230
+ this._drainTimer = null
231
+ }
84
232
 
85
- this.on('error', onError)
86
- conn.on('error', this.emit.bind(this, 'error'))
87
- this._parser.on('error', this.emit.bind(this, 'error'))
233
+ // Complete all pending drain callbacks
234
+ const pending = this._pendingDrains
235
+ this._pendingDrains = []
236
+ for (let i = 0; i < pending.length; i++) {
237
+ setImmediate(pending[i], null, this)
238
+ }
239
+ }
88
240
 
89
- conn.on('end', this.close.bind(this))
90
- this._eos = eos(this.conn, this.close.bind(this))
241
+ /**
242
+ * Handle drain timeout - client failed to drain within timeout
243
+ * Disconnects the client and fails all pending callbacks
244
+ */
245
+ _handleDrainTimeout () {
246
+ this._drainTimer = null
91
247
 
92
- const getToForwardPacket = (_packet) => {
93
- // Mqttv5 3.8.3.1: https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.html#_Toc3901169
94
- // prevent to forward messages sent by the same client when no-local flag is set
95
- if (_packet.clientId === that.id && _packet.nl) return
248
+ // Remove drain listener to prevent it firing after disconnect
249
+ if (this._onDrainBound) {
250
+ this.conn.removeListener('drain', this._onDrainBound)
251
+ }
96
252
 
97
- const toForward = dedupe(that, _packet) &&
98
- that.broker.authorizeForward(that, _packet)
253
+ // Fail all pending callbacks
254
+ const error = new Error('drain timeout')
255
+ const pending = this._pendingDrains
256
+ this._pendingDrains = []
257
+ for (let i = 0; i < pending.length; i++) {
258
+ setImmediate(pending[i], error, this)
259
+ }
99
260
 
100
- return toForward
261
+ // Disconnect the slow client
262
+ this.conn.destroy(error)
101
263
  }
102
264
 
103
- this.deliver0 = function deliverQoS0 (_packet, cb) {
104
- const toForward = getToForwardPacket(_packet)
105
-
106
- if (toForward) {
107
- // Give nodejs some time to clear stacks, or we will see
108
- // "Maximum call stack size exceeded" in a very high load
109
- setImmediate(() => {
110
- const packet = new Packet(toForward, broker)
111
- packet.qos = 0
112
- write(that, packet, function (err) {
113
- that._onError(err)
114
- cb() // don't pass the error here or it will be thrown by mqemitter
115
- })
116
- })
265
+ /**
266
+ * Register a callback to be called when socket drains
267
+ * Uses per-client coalesced timer to reduce timer overhead
268
+ * @param {Function} callback - called with (err, client)
269
+ */
270
+ waitForDrain (callback) {
271
+ const drainTimeout = this.broker?.opts?.drainTimeout
272
+
273
+ if (drainTimeout > 0) {
274
+ // Per-client coalesced timer approach:
275
+ // Only create ONE timer per client, regardless of pending writes
276
+
277
+ // Add this callback to pending queue
278
+ this._pendingDrains.push(callback)
279
+
280
+ // If no timer exists, create one and set up drain listener
281
+ if (!this._drainTimer) {
282
+ // Create bound drain handler (if not already created)
283
+ if (!this._onDrainBound) {
284
+ this._onDrainBound = this._handleDrain.bind(this)
285
+ }
286
+
287
+ // Set up single drain listener
288
+ this.conn.once('drain', this._onDrainBound)
289
+
290
+ // Create single timer for this client
291
+ this._drainTimer = setTimeout(
292
+ this._handleDrainTimeout.bind(this),
293
+ drainTimeout
294
+ )
295
+ this._drainTimer.unref() // Don't keep process alive
296
+ }
297
+ // else: timer already running, just queued the callback
117
298
  } else {
118
- setImmediate(cb)
299
+ // Without drain timeout: wait indefinitely (original behavior)
300
+ this.conn.once('drain', callback)
119
301
  }
120
302
  }
121
303
 
122
- this.deliverQoS = function deliverQoS (_packet, cb) {
123
- // downgrade to qos0 if requested by publish
124
- if (_packet.qos === 0) {
125
- that.deliver0(_packet, cb)
304
+ close (done) {
305
+ if (this.closed) {
306
+ if (typeof done === 'function') {
307
+ done()
308
+ }
126
309
  return
127
310
  }
128
- const toForward = getToForwardPacket(_packet)
129
-
130
- if (toForward) {
131
- setImmediate(() => {
132
- const packet = new QoSPacket(toForward, that)
133
- // Downgrading to client subscription qos if needed
134
- const clientSub = that.subscriptions[packet.topic]
135
- if (clientSub && (clientSub.qos || 0) < packet.qos) {
136
- packet.qos = clientSub.qos
137
- }
138
- packet.writeCallback = cb
139
- if (that.clean || packet.retain) {
140
- writeQoS(null, that, packet)
141
- } else {
142
- broker.persistence.outgoingUpdate(that, packet, writeQoS)
143
- }
144
- })
145
- } else if (that.clean === false) {
146
- that.broker.persistence.outgoingClearMessageId(that, _packet, noop)
147
- // we consider this to be an error, since the packet is undefined
148
- // so there's nothing to send
149
- setImmediate(cb)
150
- } else {
151
- setImmediate(cb)
311
+
312
+ const that = this
313
+ const conn = this.conn
314
+
315
+ this.closed = true
316
+
317
+ this._parser.removeAllListeners('packet')
318
+ conn.removeAllListeners('readable')
319
+
320
+ this._parser._queue = null
321
+
322
+ if (this._keepaliveTimer) {
323
+ this._keepaliveTimer.clear()
324
+ this._keepaliveInterval = -1
325
+ this._keepaliveTimer = null
326
+ }
327
+
328
+ if (this._connectTimer) {
329
+ clearTimeout(this._connectTimer)
330
+ this._connectTimer = null
331
+ }
332
+
333
+ // Clean up drain timeout timer, listener, and flush pending callbacks
334
+ if (this._drainTimer) {
335
+ clearTimeout(this._drainTimer)
336
+ this._drainTimer = null
337
+ }
338
+ if (this._onDrainBound) {
339
+ this.conn.removeListener('drain', this._onDrainBound)
340
+ }
341
+ // Flush pending drain callbacks with connection closed error
342
+ const error = new Error('connection closed')
343
+ const pending = this._pendingDrains
344
+ this._pendingDrains = []
345
+ for (let i = 0; i < pending.length; i++) {
346
+ setImmediate(pending[i], error, this)
347
+ }
348
+
349
+ this._eos()
350
+ this._eos = noop
351
+
352
+ handleUnsubscribe(
353
+ this,
354
+ {
355
+ unsubscriptions: Object.keys(this.subscriptions)
356
+ },
357
+ finish)
358
+
359
+ function finish () {
360
+ const will = that.will
361
+ // _disconnected is set only if client is disconnected with a valid disconnect packet
362
+ if (!that._disconnected && will) {
363
+ that.broker.authorizePublish(that, will, function (err) {
364
+ if (err) { return done() }
365
+ that.broker.publish(will, that, done)
366
+
367
+ function done () {
368
+ that.broker.persistence.delWill({
369
+ id: that.id,
370
+ brokerId: that.broker.id
371
+ }).then(noop, noop)
372
+ }
373
+ })
374
+ } else if (will) {
375
+ // delete the persisted will even on clean disconnect https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349232
376
+ that.broker.persistence.delWill({
377
+ id: that.id,
378
+ brokerId: that.broker.id
379
+ }).then(noop, noop)
380
+ }
381
+
382
+ that.will = null // this function might be called twice
383
+ that._will = null
384
+
385
+ that.connected = false
386
+ that.connecting = false
387
+
388
+ conn.removeAllListeners('error')
389
+ conn.on('error', noop)
390
+
391
+ if (that.broker.clients[that.id] && that._authorized) {
392
+ that.broker.unregisterClient(that)
393
+ }
394
+
395
+ // clear up the drain event listeners
396
+ that.conn.emit('drain')
397
+ that.conn.removeAllListeners('drain')
398
+
399
+ conn.destroy()
400
+
401
+ if (typeof done === 'function') {
402
+ done()
403
+ }
152
404
  }
153
405
  }
154
406
 
155
- this._keepaliveTimer = null
156
- this._keepaliveInterval = -1
407
+ pause () {
408
+ this._paused = true
409
+ }
410
+
411
+ resume () {
412
+ this._paused = false
413
+ this._nextBatch()
414
+ }
157
415
 
158
- this._connectTimer = setTimeout(function () {
159
- that.emit('error', new Error('connect did not arrive in time'))
160
- }, broker.connectTimeout)
416
+ emptyOutgoingQueue (done) {
417
+ const client = this
418
+ const persistence = client.broker.persistence
419
+
420
+ function filter (packet, enc, next) {
421
+ persistence.outgoingClearMessageId(client, packet)
422
+ .then(pkt => next(null, pkt), next)
423
+ }
424
+
425
+ pipeline(
426
+ persistence.outgoingStream(client),
427
+ through(filter),
428
+ done
429
+ )
430
+ }
161
431
  }
162
432
 
163
433
  function dedupe (client, packet) {
@@ -195,166 +465,8 @@ function drainRequest (req) {
195
465
  req.callback()
196
466
  }
197
467
 
198
- function onError (err) {
199
- if (!err) return
200
-
201
- this.errored = true
202
- this.conn.removeAllListeners('error')
203
- this.conn.on('error', noop)
204
- // hack to clean up the write callbacks in case of error
205
- const state = this.conn._writableState
206
- const list = typeof state.getBuffer === 'function' ? state.getBuffer() : state.buffer
207
- list.forEach(drainRequest)
208
- this.broker.emit(this.id ? 'clientError' : 'connectionError', this, err)
209
- this.close()
210
- }
211
-
212
468
  util.inherits(Client, EventEmitter)
213
469
 
214
- Client.prototype._onError = onError
215
-
216
- Client.prototype.publish = function (message, done) {
217
- const packet = new Packet(message, this.broker)
218
- const that = this
219
- if (packet.qos === 0) {
220
- // skip offline and send it as it is
221
- this.deliver0(packet, done)
222
- return
223
- }
224
- if (!this.clean && this.id) {
225
- this.broker.persistence.outgoingEnqueue({
226
- clientId: this.id
227
- }, packet, function deliver (err) {
228
- if (err) {
229
- return done(err)
230
- }
231
- that.deliverQoS(packet, done)
232
- })
233
- } else {
234
- that.deliverQoS(packet, done)
235
- }
236
- }
237
-
238
- Client.prototype.subscribe = function (packet, done) {
239
- if (!packet.subscriptions) {
240
- if (!Array.isArray(packet)) {
241
- packet = [packet]
242
- }
243
- packet = {
244
- subscriptions: packet
245
- }
246
- }
247
- handleSubscribe(this, packet, false, done)
248
- }
249
-
250
- Client.prototype.unsubscribe = function (packet, done) {
251
- if (!packet.unsubscriptions) {
252
- if (!Array.isArray(packet)) {
253
- packet = [packet]
254
- }
255
- packet = {
256
- unsubscriptions: packet
257
- }
258
- }
259
- handleUnsubscribe(this, packet, done)
260
- }
261
-
262
- Client.prototype.close = function (done) {
263
- if (this.closed) {
264
- if (typeof done === 'function') {
265
- done()
266
- }
267
- return
268
- }
269
-
270
- const that = this
271
- const conn = this.conn
272
-
273
- this.closed = true
274
-
275
- this._parser.removeAllListeners('packet')
276
- conn.removeAllListeners('readable')
277
-
278
- this._parser._queue = null
279
-
280
- if (this._keepaliveTimer) {
281
- this._keepaliveTimer.clear()
282
- this._keepaliveInterval = -1
283
- this._keepaliveTimer = null
284
- }
285
-
286
- if (this._connectTimer) {
287
- clearTimeout(this._connectTimer)
288
- this._connectTimer = null
289
- }
290
-
291
- this._eos()
292
- this._eos = noop
293
-
294
- handleUnsubscribe(
295
- this,
296
- {
297
- unsubscriptions: Object.keys(this.subscriptions)
298
- },
299
- finish)
300
-
301
- function finish () {
302
- const will = that.will
303
- // _disconnected is set only if client is disconnected with a valid disconnect packet
304
- if (!that._disconnected && will) {
305
- that.broker.authorizePublish(that, will, function (err) {
306
- if (err) { return done() }
307
- that.broker.publish(will, that, done)
308
-
309
- function done () {
310
- that.broker.persistence.delWill({
311
- id: that.id,
312
- brokerId: that.broker.id
313
- }, noop)
314
- }
315
- })
316
- } else if (will) {
317
- // delete the persisted will even on clean disconnect https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349232
318
- that.broker.persistence.delWill({
319
- id: that.id,
320
- brokerId: that.broker.id
321
- }, noop)
322
- }
323
-
324
- that.will = null // this function might be called twice
325
- that._will = null
326
-
327
- that.connected = false
328
- that.connecting = false
329
-
330
- conn.removeAllListeners('error')
331
- conn.on('error', noop)
332
-
333
- if (that.broker.clients[that.id] && that._authorized) {
334
- that.broker.unregisterClient(that)
335
- }
336
-
337
- // clear up the drain event listeners
338
- that.conn.emit('drain')
339
- that.conn.removeAllListeners('drain')
340
-
341
- conn.destroy()
342
-
343
- if (typeof done === 'function') {
344
- done()
345
- }
346
- }
347
- }
348
-
349
- Client.prototype.pause = function () {
350
- this._paused = true
351
- }
352
-
353
- Client.prototype.resume = function () {
354
- this._paused = false
355
- this._nextBatch()
356
- }
357
-
358
470
  function enqueue (packet) {
359
471
  const client = this.client
360
472
  client._parsingBatch++
@@ -381,19 +493,6 @@ function dequeue () {
381
493
  }
382
494
  }
383
495
 
384
- Client.prototype.emptyOutgoingQueue = function (done) {
385
- const client = this
386
- const persistence = client.broker.persistence
387
-
388
- function filter (packet, enc, next) {
389
- persistence.outgoingClearMessageId(client, packet, next)
390
- }
391
-
392
- pipeline(
393
- persistence.outgoingStream(client),
394
- through(filter),
395
- done
396
- )
397
- }
398
-
399
496
  function noop () {}
497
+
498
+ export default Client