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.
- package/.github/actions/sticky-pr-comment/action.yml +55 -0
- package/.github/workflows/benchmark-compare-serial.yml +60 -0
- package/.github/workflows/ci.yml +12 -17
- package/.release-it.json +18 -0
- package/.taprc +15 -6
- package/README.md +6 -4
- package/aedes.d.ts +0 -6
- package/aedes.js +270 -242
- package/benchmarks/README.md +33 -0
- package/benchmarks/pingpong.js +94 -25
- package/benchmarks/receiver.js +77 -0
- package/benchmarks/report.js +150 -0
- package/benchmarks/runBenchmarks.js +118 -0
- package/benchmarks/sender.js +86 -0
- package/benchmarks/server.js +19 -18
- package/checkVersion.js +20 -0
- package/docs/Aedes.md +66 -8
- package/docs/Client.md +3 -4
- package/docs/Examples.md +39 -22
- package/docs/MIGRATION.md +50 -0
- package/eslint.config.js +8 -0
- package/example.js +51 -40
- package/examples/clusters/index.js +28 -23
- package/examples/clusters/package.json +10 -6
- package/lib/client.js +405 -306
- package/lib/handlers/connect.js +42 -38
- package/lib/handlers/index.js +9 -11
- package/lib/handlers/ping.js +2 -3
- package/lib/handlers/puback.js +5 -5
- package/lib/handlers/publish.js +29 -14
- package/lib/handlers/pubrec.js +9 -17
- package/lib/handlers/pubrel.js +34 -25
- package/lib/handlers/subscribe.js +47 -43
- package/lib/handlers/unsubscribe.js +16 -19
- package/lib/qos-packet.js +14 -17
- package/lib/utils.js +5 -12
- package/lib/write.js +4 -5
- package/package.json +134 -136
- package/test/auth.js +468 -804
- package/test/basic.js +613 -575
- package/test/bridge.js +44 -40
- package/test/client-pub-sub.js +531 -504
- package/test/close_socket_by_other_party.js +137 -102
- package/test/connect.js +487 -484
- package/test/drain-timeout.js +593 -0
- package/test/drain-toxiproxy.js +620 -0
- package/test/events.js +173 -145
- package/test/helper.js +351 -73
- package/test/keep-alive.js +40 -67
- package/test/meta.js +257 -210
- package/test/not-blocking.js +93 -197
- package/test/qos1.js +464 -554
- package/test/qos2.js +308 -393
- package/test/regr-21.js +39 -21
- package/test/require.cjs +22 -0
- package/test/retain.js +349 -398
- package/test/topics.js +176 -183
- package/test/types/aedes.test-d.ts +4 -8
- package/test/will.js +310 -428
- package/types/instance.d.ts +40 -35
- package/types/packet.d.ts +10 -10
- package/.coveralls.yml +0 -1
- package/benchmarks/bombing.js +0 -34
- package/benchmarks/bombingQoS1.js +0 -36
- package/benchmarks/throughputCounter.js +0 -23
- package/benchmarks/throughputCounterQoS1.js +0 -33
- package/types/.eslintrc.json +0 -47
package/lib/client.js
CHANGED
|
@@ -1,163 +1,433 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
261
|
+
// Disconnect the slow client
|
|
262
|
+
this.conn.destroy(error)
|
|
101
263
|
}
|
|
102
264
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
299
|
+
// Without drain timeout: wait indefinitely (original behavior)
|
|
300
|
+
this.conn.once('drain', callback)
|
|
119
301
|
}
|
|
120
302
|
}
|
|
121
303
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
304
|
+
close (done) {
|
|
305
|
+
if (this.closed) {
|
|
306
|
+
if (typeof done === 'function') {
|
|
307
|
+
done()
|
|
308
|
+
}
|
|
126
309
|
return
|
|
127
310
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
156
|
-
|
|
407
|
+
pause () {
|
|
408
|
+
this._paused = true
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
resume () {
|
|
412
|
+
this._paused = false
|
|
413
|
+
this._nextBatch()
|
|
414
|
+
}
|
|
157
415
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|