mqtt-plus 1.4.0 → 1.4.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/AGENTS.md +55 -44
- package/CHANGELOG.md +14 -0
- package/README.md +4 -3
- package/doc/mqtt-plus-api.md +693 -680
- package/doc/mqtt-plus-architecture.d2 +139 -0
- package/doc/mqtt-plus-architecture.md +10 -0
- package/doc/mqtt-plus-architecture.svg +95 -0
- package/doc/mqtt-plus-broker-setup.md +9 -3
- package/doc/mqtt-plus-comm.md +73 -0
- package/doc/mqtt-plus-internals.md +3 -3
- package/dst-stage1/mqtt-plus-base.d.ts +3 -2
- package/dst-stage1/mqtt-plus-base.js +53 -22
- package/dst-stage1/mqtt-plus-event.d.ts +0 -2
- package/dst-stage1/mqtt-plus-event.js +6 -26
- package/dst-stage1/mqtt-plus-meta.d.ts +2 -2
- package/dst-stage1/mqtt-plus-meta.js +2 -2
- package/dst-stage1/mqtt-plus-msg.d.ts +2 -0
- package/dst-stage1/mqtt-plus-msg.js +17 -0
- package/dst-stage1/mqtt-plus-service.d.ts +0 -5
- package/dst-stage1/mqtt-plus-service.js +12 -48
- package/dst-stage1/mqtt-plus-sink.d.ts +0 -10
- package/dst-stage1/mqtt-plus-sink.js +25 -92
- package/dst-stage1/mqtt-plus-source.d.ts +0 -10
- package/dst-stage1/mqtt-plus-source.js +23 -88
- package/dst-stage1/mqtt-plus-subscription.d.ts +20 -0
- package/dst-stage1/mqtt-plus-subscription.js +126 -0
- package/dst-stage1/mqtt-plus-timer.d.ts +8 -0
- package/dst-stage1/mqtt-plus-timer.js +57 -0
- package/dst-stage1/mqtt-plus-topic.d.ts +20 -0
- package/dst-stage1/mqtt-plus-topic.js +112 -0
- package/dst-stage1/mqtt-plus-trace.js +2 -0
- package/dst-stage1/mqtt-plus-util.d.ts +0 -13
- package/dst-stage1/mqtt-plus-util.js +1 -77
- package/dst-stage1/mqtt-plus-version.d.ts +0 -1
- package/dst-stage1/mqtt-plus-version.js +0 -6
- package/dst-stage1/tsc.tsbuildinfo +1 -1
- package/dst-stage2/mqtt-plus.cjs.js +242 -292
- package/dst-stage2/mqtt-plus.esm.js +240 -290
- package/dst-stage2/mqtt-plus.umd.js +12 -12
- package/etc/knip.jsonc +1 -1
- package/etc/stx.conf +6 -4
- package/package.json +1 -1
- package/src/mqtt-plus-base.ts +56 -26
- package/src/mqtt-plus-event.ts +8 -24
- package/src/mqtt-plus-meta.ts +3 -3
- package/src/mqtt-plus-msg.ts +28 -0
- package/src/mqtt-plus-service.ts +12 -50
- package/src/mqtt-plus-sink.ts +32 -105
- package/src/mqtt-plus-source.ts +29 -99
- package/src/mqtt-plus-subscription.ts +141 -0
- package/src/mqtt-plus-timer.ts +61 -0
- package/src/mqtt-plus-trace.ts +4 -0
- package/src/mqtt-plus-util.ts +1 -81
- package/src/mqtt-plus-version.ts +0 -7
- package/tst/mqtt-plus-0-fixture.ts +2 -2
- package/tst/mqtt-plus-0-mosquitto.ts +5 -0
- package/tst/mqtt-plus-1-api.spec.ts +1 -1
- package/tst/mqtt-plus-2-event.spec.ts +0 -6
- package/tst/mqtt-plus-3-service.spec.ts +3 -7
- package/tst/mqtt-plus-4-sink.spec.ts +14 -9
- package/tst/mqtt-plus-5-source.spec.ts +11 -5
- package/tst/mqtt-plus-6-misc.spec.ts +23 -23
- package/tst/tsc.json +1 -1
- package/doc/mqtt-plus-communication.md +0 -68
- /package/doc/{mqtt-plus-1-event-emission.d2 → mqtt-plus-comm-event-emission.d2} +0 -0
- /package/doc/{mqtt-plus-1-event-emission.svg → mqtt-plus-comm-event-emission.svg} +0 -0
- /package/doc/{mqtt-plus-2-service-call.d2 → mqtt-plus-comm-service-call.d2} +0 -0
- /package/doc/{mqtt-plus-2-service-call.svg → mqtt-plus-comm-service-call.svg} +0 -0
- /package/doc/{mqtt-plus-3-sink-push.d2 → mqtt-plus-comm-sink-push.d2} +0 -0
- /package/doc/{mqtt-plus-3-sink-push.svg → mqtt-plus-comm-sink-push.svg} +0 -0
- /package/doc/{mqtt-plus-4-source-fetch.d2 → mqtt-plus-comm-source-fetch.d2} +0 -0
- /package/doc/{mqtt-plus-4-source-fetch.svg → mqtt-plus-comm-source-fetch.svg} +0 -0
- /package/{doc/theme.d2 → etc/d2.theme.d2} +0 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** MQTT+ -- MQTT Communication Patterns
|
|
3
|
+
** Copyright (c) 2018-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
|
+
**
|
|
5
|
+
** Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
** a copy of this software and associated documentation files (the
|
|
7
|
+
** "Software"), to deal in the Software without restriction, including
|
|
8
|
+
** without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
** distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
** permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
** the following conditions:
|
|
12
|
+
**
|
|
13
|
+
** The above copyright notice and this permission notice shall be included
|
|
14
|
+
** in all copies or substantial portions of the Software.
|
|
15
|
+
**
|
|
16
|
+
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/* internal requirements */
|
|
26
|
+
import type { APISchema } from "./mqtt-plus-api"
|
|
27
|
+
import { SubscriptionTrait } from "./mqtt-plus-subscription"
|
|
28
|
+
|
|
29
|
+
/* Timer trait with reusable timer management */
|
|
30
|
+
export class TimerTrait<T extends APISchema = APISchema> extends SubscriptionTrait<T> {
|
|
31
|
+
/* internal state */
|
|
32
|
+
private timers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
33
|
+
|
|
34
|
+
/* destroy timer trait */
|
|
35
|
+
override async destroy () {
|
|
36
|
+
for (const timer of this.timers.values())
|
|
37
|
+
clearTimeout(timer)
|
|
38
|
+
this.timers.clear()
|
|
39
|
+
await super.destroy()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* refresh (or start) a named timer */
|
|
43
|
+
protected timerRefresh (id: string, onTimeout: () => void) {
|
|
44
|
+
const timer = this.timers.get(id)
|
|
45
|
+
if (timer !== undefined)
|
|
46
|
+
clearTimeout(timer)
|
|
47
|
+
this.timers.set(id, setTimeout(() => {
|
|
48
|
+
this.timers.delete(id)
|
|
49
|
+
onTimeout()
|
|
50
|
+
}, this.options.timeout))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* clear a named timer */
|
|
54
|
+
protected timerClear (id: string) {
|
|
55
|
+
const timer = this.timers.get(id)
|
|
56
|
+
if (timer !== undefined) {
|
|
57
|
+
clearTimeout(timer)
|
|
58
|
+
this.timers.delete(id)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/mqtt-plus-trace.ts
CHANGED
|
@@ -38,6 +38,8 @@ class LogEvent {
|
|
|
38
38
|
public msg: string | Promise<string>,
|
|
39
39
|
public data?: Record<string, Promise<any> | any>
|
|
40
40
|
) {}
|
|
41
|
+
|
|
42
|
+
/* resolve all pending promises in the log event */
|
|
41
43
|
async resolve () {
|
|
42
44
|
if (this.msg instanceof Promise)
|
|
43
45
|
this.msg = await this.msg.catch(() => "<resolve-failed>")
|
|
@@ -46,6 +48,8 @@ class LogEvent {
|
|
|
46
48
|
if (this.data[field] instanceof Promise)
|
|
47
49
|
this.data[field] = await this.data[field].catch(() => "<resolve-failed>")
|
|
48
50
|
}
|
|
51
|
+
|
|
52
|
+
/* render log event as string */
|
|
49
53
|
toString () {
|
|
50
54
|
/* render time */
|
|
51
55
|
const timestamp = new Date(this.timestamp)
|
package/src/mqtt-plus-util.ts
CHANGED
|
@@ -28,86 +28,6 @@ import { Readable } from "node:stream"
|
|
|
28
28
|
|
|
29
29
|
/* external requirements */
|
|
30
30
|
import PLazy from "p-lazy"
|
|
31
|
-
import type { IClientSubscribeOptions } from "mqtt"
|
|
32
|
-
|
|
33
|
-
/* reference-counted subscription helper */
|
|
34
|
-
export class RefCountedSubscription {
|
|
35
|
-
private counts = new Map<string, number>()
|
|
36
|
-
private pending = new Map<string, Promise<void>>()
|
|
37
|
-
private lingers = new Map<string, ReturnType<typeof setTimeout>>()
|
|
38
|
-
constructor (
|
|
39
|
-
private subscribeFn: (topic: string, options: IClientSubscribeOptions) => Promise<void>,
|
|
40
|
-
private unsubscribeFn: (topic: string) => Promise<void>,
|
|
41
|
-
private lingerMs: number = 30 * 1000
|
|
42
|
-
) {}
|
|
43
|
-
async subscribe (topic: string, options: IClientSubscribeOptions = { qos: 2 }): Promise<void> {
|
|
44
|
-
/* increment count first to reserve our interest */
|
|
45
|
-
const count = this.counts.get(topic) ?? 0
|
|
46
|
-
this.counts.set(topic, count + 1)
|
|
47
|
-
|
|
48
|
-
/* optionally just cancel a pending linger unsubscription
|
|
49
|
-
(subscription is still kept active on the broker) */
|
|
50
|
-
const linger = this.lingers.get(topic)
|
|
51
|
-
if (linger) {
|
|
52
|
-
clearTimeout(linger)
|
|
53
|
-
this.lingers.delete(topic)
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/* if we are the first, we must perform the actual subscription */
|
|
58
|
-
if (count === 0) {
|
|
59
|
-
const promise = this.subscribeFn(topic, options).finally(() => {
|
|
60
|
-
this.pending.delete(topic)
|
|
61
|
-
}).catch((err: Error) => {
|
|
62
|
-
const count = this.counts.get(topic)
|
|
63
|
-
if (count) {
|
|
64
|
-
if (count <= 1)
|
|
65
|
-
this.counts.delete(topic)
|
|
66
|
-
else
|
|
67
|
-
this.counts.set(topic, count - 1)
|
|
68
|
-
}
|
|
69
|
-
throw err
|
|
70
|
-
})
|
|
71
|
-
this.pending.set(topic, promise)
|
|
72
|
-
return promise
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
/* perhaps still need to wait for a pending subscription */
|
|
76
|
-
const pending = this.pending.get(topic)
|
|
77
|
-
if (pending)
|
|
78
|
-
return pending
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async unsubscribe (topic: string): Promise<void> {
|
|
82
|
-
const count = this.counts.get(topic)
|
|
83
|
-
if (count) {
|
|
84
|
-
if (count <= 1) {
|
|
85
|
-
this.counts.delete(topic)
|
|
86
|
-
if (this.lingerMs > 0) {
|
|
87
|
-
/* defer the actual broker unsubscription */
|
|
88
|
-
const timer = setTimeout(() => {
|
|
89
|
-
this.lingers.delete(topic)
|
|
90
|
-
this.unsubscribeFn(topic).catch(() => {})
|
|
91
|
-
}, this.lingerMs)
|
|
92
|
-
this.lingers.set(topic, timer)
|
|
93
|
-
}
|
|
94
|
-
else
|
|
95
|
-
await this.unsubscribeFn(topic).catch(() => {})
|
|
96
|
-
}
|
|
97
|
-
else
|
|
98
|
-
this.counts.set(topic, count - 1)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
async flush (): Promise<void> {
|
|
102
|
-
/* flush all pending linger timers and unsubscribe immediately */
|
|
103
|
-
const topics = [ ...this.lingers.keys() ]
|
|
104
|
-
for (const topic of topics) {
|
|
105
|
-
clearTimeout(this.lingers.get(topic))
|
|
106
|
-
this.lingers.delete(topic)
|
|
107
|
-
await this.unsubscribeFn(topic).catch(() => {})
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
31
|
|
|
112
32
|
/* credit-based flow control gate for chunk producers */
|
|
113
33
|
export class CreditGate {
|
|
@@ -166,7 +86,7 @@ export class CreditGate {
|
|
|
166
86
|
}
|
|
167
87
|
}
|
|
168
88
|
|
|
169
|
-
/* concatenate elements of
|
|
89
|
+
/* concatenate elements of a Uint8Array array */
|
|
170
90
|
function uint8ArrayConcat (arrays: Uint8Array[]) {
|
|
171
91
|
const totalLength = arrays.reduce((acc, value) => acc + value.byteLength, 0)
|
|
172
92
|
const result = new Uint8Array(totalLength)
|
package/src/mqtt-plus-version.ts
CHANGED
|
@@ -33,13 +33,6 @@ export const versionToNum = (str: string) => {
|
|
|
33
33
|
return parseInt(m[1], 10) + minor / 100
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
/* convert version number to string */
|
|
37
|
-
export const versionToStr = (num: number) => {
|
|
38
|
-
const intPart = Math.floor(num)
|
|
39
|
-
const decPart = Math.round((num - intPart) * 100)
|
|
40
|
-
return `${intPart.toFixed(0)}.${decPart.toFixed(0).padStart(2, "0")}`
|
|
41
|
-
}
|
|
42
|
-
|
|
43
36
|
/* package version (string format, injected) */
|
|
44
37
|
declare const __VERSION__: string
|
|
45
38
|
export const VERSION = __VERSION__
|
|
@@ -68,7 +68,7 @@ export const mochaHooks = {
|
|
|
68
68
|
/* connect with MQTT as client */
|
|
69
69
|
ctx.mqttC = MQTT.connect("mqtt://127.0.0.1:1883",
|
|
70
70
|
{ clientId: "client" })
|
|
71
|
-
ctx.apiC = new MQTTp<API>(ctx.mqttC, { id: "client", timeout:
|
|
71
|
+
ctx.apiC = new MQTTp<API>(ctx.mqttC, { id: "client", timeout: 500 })
|
|
72
72
|
await new Promise<void>((resolve, reject) => {
|
|
73
73
|
ctx.mqttC.once("connect", () => { resolve() })
|
|
74
74
|
ctx.mqttC.once("error", (err: Error) => { reject(err) })
|
|
@@ -81,7 +81,7 @@ export const mochaHooks = {
|
|
|
81
81
|
/* connect with MQTT as server */
|
|
82
82
|
ctx.mqttS = MQTT.connect("mqtt://127.0.0.1:1883",
|
|
83
83
|
{ clientId: "server", username: "example", password: "example" })
|
|
84
|
-
ctx.apiS = new MQTTp<API>(ctx.mqttS, { id: "server", timeout:
|
|
84
|
+
ctx.apiS = new MQTTp<API>(ctx.mqttS, { id: "server", timeout: 500 })
|
|
85
85
|
await new Promise<void>((resolve, reject) => {
|
|
86
86
|
ctx.mqttS.once("connect", () => { resolve() })
|
|
87
87
|
ctx.mqttS.once("error", (err: Error) => { reject(err) })
|
|
@@ -56,6 +56,7 @@ const ACL = textframe(`
|
|
|
56
56
|
topic write example/server/+/source-fetch-request/+
|
|
57
57
|
pattern read example/server/+/source-fetch-response/%c
|
|
58
58
|
pattern read example/server/+/source-fetch-chunk/%c
|
|
59
|
+
topic write example/server/+/source-fetch-credit/+
|
|
59
60
|
|
|
60
61
|
topic read example/client/+/source-fetch-request/any
|
|
61
62
|
pattern read example/client/+/source-fetch-request/%c
|
|
@@ -72,6 +73,7 @@ const ACL = textframe(`
|
|
|
72
73
|
pattern read example/client/+/sink-push-request/%c
|
|
73
74
|
pattern write example/client/+/sink-push-response/%c
|
|
74
75
|
pattern read example/client/+/sink-push-chunk/%c
|
|
76
|
+
pattern read example/client/+/sink-push-credit/%c
|
|
75
77
|
|
|
76
78
|
# ==== server/authenticated ACL ====
|
|
77
79
|
|
|
@@ -105,6 +107,8 @@ const ACL = textframe(`
|
|
|
105
107
|
pattern read $share/server/example/server/+/source-fetch-request/%c
|
|
106
108
|
topic write example/server/+/source-fetch-response/+
|
|
107
109
|
topic write example/server/+/source-fetch-chunk/+
|
|
110
|
+
pattern read example/server/+/source-fetch-credit/%c
|
|
111
|
+
pattern read $share/server/example/server/+/source-fetch-credit/%c
|
|
108
112
|
|
|
109
113
|
topic write example/client/+/source-fetch-request/+
|
|
110
114
|
pattern read example/client/+/source-fetch-response/%c
|
|
@@ -119,6 +123,7 @@ const ACL = textframe(`
|
|
|
119
123
|
topic write example/server/+/sink-push-response/+
|
|
120
124
|
pattern read example/server/+/sink-push-chunk/%c
|
|
121
125
|
pattern read $share/default/example/server/+/sink-push-chunk/%c
|
|
126
|
+
topic write example/client/+/sink-push-credit/+
|
|
122
127
|
|
|
123
128
|
topic write example/client/+/sink-push-request/+
|
|
124
129
|
pattern read example/client/+/sink-push-response/%c
|
|
@@ -100,7 +100,7 @@ describe("MQTT+ API", function () {
|
|
|
100
100
|
|
|
101
101
|
/* trigger some log activity by emitting an event */
|
|
102
102
|
apiTmp.emit("example/server/sample", "test", 1)
|
|
103
|
-
await new Promise((resolve) => { setTimeout(resolve,
|
|
103
|
+
await new Promise((resolve) => { setTimeout(resolve, 10) })
|
|
104
104
|
|
|
105
105
|
/* verify log entries were captured */
|
|
106
106
|
expect(logEntries.length).to.be.greaterThan(0)
|
|
@@ -41,7 +41,6 @@ describe("MQTT+ Event Emission", function () {
|
|
|
41
41
|
/* test case: Event Emission */
|
|
42
42
|
it("MQTT+ Event Emission", async function () {
|
|
43
43
|
/* setup */
|
|
44
|
-
this.timeout(1000)
|
|
45
44
|
const spy = sinon.spy()
|
|
46
45
|
|
|
47
46
|
/* register to event */
|
|
@@ -64,7 +63,6 @@ describe("MQTT+ Event Emission", function () {
|
|
|
64
63
|
/* test case: Event Emission (Object API) */
|
|
65
64
|
it("MQTT+ Event Emission (Object API)", async function () {
|
|
66
65
|
/* setup */
|
|
67
|
-
this.timeout(1000)
|
|
68
66
|
const spy = sinon.spy()
|
|
69
67
|
|
|
70
68
|
/* register event */
|
|
@@ -93,7 +91,6 @@ describe("MQTT+ Event Emission", function () {
|
|
|
93
91
|
/* test case: Event Emission with Meta Information */
|
|
94
92
|
it("MQTT+ Event Emission (Meta Information)", async function () {
|
|
95
93
|
/* setup */
|
|
96
|
-
this.timeout(1000)
|
|
97
94
|
const spy = sinon.spy()
|
|
98
95
|
|
|
99
96
|
/* register event */
|
|
@@ -122,9 +119,6 @@ describe("MQTT+ Event Emission", function () {
|
|
|
122
119
|
|
|
123
120
|
/* test case: Event Emission (Duplicate Registration) */
|
|
124
121
|
it("MQTT+ Event Emission (Duplicate Registration)", async function () {
|
|
125
|
-
/* setup */
|
|
126
|
-
this.timeout(1000)
|
|
127
|
-
|
|
128
122
|
/* register event */
|
|
129
123
|
const reg = await ctx.apiS.event("example/server/sample", () => {})
|
|
130
124
|
|
|
@@ -41,7 +41,6 @@ describe("MQTT+ Service Call", function () {
|
|
|
41
41
|
/* test case: Service Call */
|
|
42
42
|
it("MQTT+ Service Call", async function () {
|
|
43
43
|
/* setup */
|
|
44
|
-
this.timeout(1000)
|
|
45
44
|
const spy = sinon.spy()
|
|
46
45
|
|
|
47
46
|
/* provide service */
|
|
@@ -82,7 +81,6 @@ describe("MQTT+ Service Call", function () {
|
|
|
82
81
|
/* test case: Service Call (Object API) */
|
|
83
82
|
it("MQTT+ Service Call (Object API)", async function () {
|
|
84
83
|
/* setup */
|
|
85
|
-
this.timeout(1000)
|
|
86
84
|
const spy = sinon.spy()
|
|
87
85
|
|
|
88
86
|
/* provide service */
|
|
@@ -111,7 +109,6 @@ describe("MQTT+ Service Call", function () {
|
|
|
111
109
|
/* test case: Service Call (Meta Information) */
|
|
112
110
|
it("MQTT+ Service Call (Meta Information)", async function () {
|
|
113
111
|
/* setup */
|
|
114
|
-
this.timeout(1000)
|
|
115
112
|
const spy = sinon.spy()
|
|
116
113
|
|
|
117
114
|
/* provide service that checks metadata */
|
|
@@ -142,7 +139,8 @@ describe("MQTT+ Service Call", function () {
|
|
|
142
139
|
|
|
143
140
|
/* test case: Service Call (Timeout) */
|
|
144
141
|
it("MQTT+ Service Call (Timeout)", async function () {
|
|
145
|
-
/* setup (higher timeout for this test) */
|
|
142
|
+
/* setup (higher timeout for this particular test) */
|
|
143
|
+
this.slow(2000)
|
|
146
144
|
this.timeout(2000)
|
|
147
145
|
const spy = sinon.spy()
|
|
148
146
|
|
|
@@ -160,7 +158,6 @@ describe("MQTT+ Service Call", function () {
|
|
|
160
158
|
/* test case: Service Call (Direct Receiver) */
|
|
161
159
|
it("MQTT+ Service Call (Direct Receiver)", async function () {
|
|
162
160
|
/* setup */
|
|
163
|
-
this.timeout(1000)
|
|
164
161
|
const spy = sinon.spy()
|
|
165
162
|
|
|
166
163
|
/* provide service on server */
|
|
@@ -188,13 +185,12 @@ describe("MQTT+ Service Call", function () {
|
|
|
188
185
|
/* test case: Service Call (Async Handler) */
|
|
189
186
|
it("MQTT+ Service Call (Async Handler)", async function () {
|
|
190
187
|
/* setup */
|
|
191
|
-
this.timeout(1000)
|
|
192
188
|
const spy = sinon.spy()
|
|
193
189
|
|
|
194
190
|
/* provide async service */
|
|
195
191
|
const registration = await ctx.apiS.service("example/server/login", async (password: string) => {
|
|
196
192
|
spy("service")
|
|
197
|
-
await new Promise((resolve) => { setTimeout(resolve,
|
|
193
|
+
await new Promise((resolve) => { setTimeout(resolve, 0) })
|
|
198
194
|
if (password !== "secret")
|
|
199
195
|
throw new Error("invalid password")
|
|
200
196
|
return "token-abc"
|
|
@@ -45,7 +45,9 @@ const { expect } = chai
|
|
|
45
45
|
describe("MQTT+ Sink Push", function () {
|
|
46
46
|
/* test case: Sink Push (Buffer) */
|
|
47
47
|
it("MQTT+ Sink Push (Buffer)", async function () {
|
|
48
|
-
|
|
48
|
+
/* setup */
|
|
49
|
+
this.slow(1000)
|
|
50
|
+
this.timeout(1000)
|
|
49
51
|
const spy = sinon.spy()
|
|
50
52
|
|
|
51
53
|
/* generate random data */
|
|
@@ -61,7 +63,7 @@ describe("MQTT+ Sink Push", function () {
|
|
|
61
63
|
info.buffer!.then((buf: Uint8Array) => {
|
|
62
64
|
spy("buffer")
|
|
63
65
|
expect(Buffer.from(buf)).to.deep.equal(data)
|
|
64
|
-
})
|
|
66
|
+
}).catch(() => {})
|
|
65
67
|
})
|
|
66
68
|
|
|
67
69
|
/* push a buffer (instead of a stream) */
|
|
@@ -70,7 +72,7 @@ describe("MQTT+ Sink Push", function () {
|
|
|
70
72
|
}).catch((err: Error) => {
|
|
71
73
|
spy("push-error")
|
|
72
74
|
})
|
|
73
|
-
await new Promise((resolve) => { setTimeout(resolve,
|
|
75
|
+
await new Promise((resolve) => { setTimeout(resolve, 100) })
|
|
74
76
|
expect(spy.getCalls().map((call) => call.firstArg))
|
|
75
77
|
.to.be.same.deep.members([ "sink", "push-success", "buffer" ])
|
|
76
78
|
|
|
@@ -81,7 +83,8 @@ describe("MQTT+ Sink Push", function () {
|
|
|
81
83
|
/* test case: Sink Push (Stream) */
|
|
82
84
|
it("MQTT+ Sink Push (Stream)", async function () {
|
|
83
85
|
/* setup */
|
|
84
|
-
this.
|
|
86
|
+
this.slow(1000)
|
|
87
|
+
this.timeout(1000)
|
|
85
88
|
const spy = sinon.spy()
|
|
86
89
|
|
|
87
90
|
/* generate random data */
|
|
@@ -93,7 +96,7 @@ describe("MQTT+ Sink Push", function () {
|
|
|
93
96
|
if (name !== "foo")
|
|
94
97
|
throw new Error("invalid sink push")
|
|
95
98
|
expect(name).to.be.equal("foo")
|
|
96
|
-
expect(info).to.be.
|
|
99
|
+
expect(info).to.be.an("object")
|
|
97
100
|
expect(info.stream).to.be.instanceOf(stream.Readable)
|
|
98
101
|
const chunks: Buffer[] = []
|
|
99
102
|
info.stream!.on("data", (chunk: Buffer) => {
|
|
@@ -117,7 +120,7 @@ describe("MQTT+ Sink Push", function () {
|
|
|
117
120
|
}).catch((err: Error) => {
|
|
118
121
|
spy("transfer-error")
|
|
119
122
|
})
|
|
120
|
-
await new Promise((resolve) => { setTimeout(resolve,
|
|
123
|
+
await new Promise((resolve) => { setTimeout(resolve, 100) })
|
|
121
124
|
expect(spy.getCalls().map((call) => call.firstArg))
|
|
122
125
|
.to.be.same.deep.members([ "sink", "transfer-success", "end" ])
|
|
123
126
|
|
|
@@ -127,7 +130,9 @@ describe("MQTT+ Sink Push", function () {
|
|
|
127
130
|
|
|
128
131
|
/* test case: Sink Push (Meta Information) */
|
|
129
132
|
it("MQTT+ Sink Push (Meta Information)", async function () {
|
|
130
|
-
|
|
133
|
+
/* setup */
|
|
134
|
+
this.slow(1000)
|
|
135
|
+
this.timeout(1000)
|
|
131
136
|
const spy = sinon.spy()
|
|
132
137
|
|
|
133
138
|
/* set instance-level meta on client */
|
|
@@ -148,7 +153,7 @@ describe("MQTT+ Sink Push", function () {
|
|
|
148
153
|
info.buffer!.then((buf: Uint8Array) => {
|
|
149
154
|
spy("buffer")
|
|
150
155
|
expect(Buffer.from(buf)).to.deep.equal(data)
|
|
151
|
-
})
|
|
156
|
+
}).catch(() => {})
|
|
152
157
|
})
|
|
153
158
|
|
|
154
159
|
/* push with metadata */
|
|
@@ -162,7 +167,7 @@ describe("MQTT+ Sink Push", function () {
|
|
|
162
167
|
}).catch((err: Error) => {
|
|
163
168
|
spy("push-error")
|
|
164
169
|
})
|
|
165
|
-
await new Promise((resolve) => { setTimeout(resolve,
|
|
170
|
+
await new Promise((resolve) => { setTimeout(resolve, 100) })
|
|
166
171
|
expect(spy.getCalls().map((call) => call.firstArg))
|
|
167
172
|
.to.be.same.deep.members([ "sink", "push-success", "buffer" ])
|
|
168
173
|
|
|
@@ -41,7 +41,9 @@ const { expect } = chai
|
|
|
41
41
|
describe("MQTT+ Source Fetch", function () {
|
|
42
42
|
/* test case: Source Fetch (Buffer) */
|
|
43
43
|
it("MQTT+ Source Fetch (Buffer)", async function () {
|
|
44
|
-
|
|
44
|
+
/* setup */
|
|
45
|
+
this.slow(2000)
|
|
46
|
+
this.timeout(2000)
|
|
45
47
|
|
|
46
48
|
/* establish source */
|
|
47
49
|
const sourcing = await ctx.apiS.source("example/server/download", async (filename, info) => {
|
|
@@ -65,7 +67,7 @@ describe("MQTT+ Source Fetch", function () {
|
|
|
65
67
|
expect(error2).to.be.equal("invalid source")
|
|
66
68
|
|
|
67
69
|
/* fetch non-existing source (invalid source name) */
|
|
68
|
-
const result3 = await ctx.apiC.fetch("example/server/download-invalid", "foo")
|
|
70
|
+
const result3 = await ctx.apiC.fetch("example/server/download-invalid", "foo")
|
|
69
71
|
const error3 = await result3.buffer.catch((err: Error) => {
|
|
70
72
|
return err.message
|
|
71
73
|
})
|
|
@@ -76,7 +78,9 @@ describe("MQTT+ Source Fetch", function () {
|
|
|
76
78
|
|
|
77
79
|
/* test case: Source Fetch (Stream) */
|
|
78
80
|
it("MQTT+ Source Fetch (Stream)", async function () {
|
|
79
|
-
|
|
81
|
+
/* setup */
|
|
82
|
+
this.slow(1000)
|
|
83
|
+
this.timeout(1000)
|
|
80
84
|
|
|
81
85
|
/* establish source providing data via stream */
|
|
82
86
|
const sourcing = await ctx.apiS.source("example/server/download", async (filename, info) => {
|
|
@@ -104,13 +108,15 @@ describe("MQTT+ Source Fetch", function () {
|
|
|
104
108
|
|
|
105
109
|
/* test case: Source Fetch (Meta Information) */
|
|
106
110
|
it("MQTT+ Source Fetch (Meta Information)", async function () {
|
|
111
|
+
/* setup */
|
|
112
|
+
this.slow(1000)
|
|
107
113
|
this.timeout(1000)
|
|
108
114
|
|
|
109
115
|
/* set instance-level meta on server */
|
|
110
116
|
ctx.apiS.meta("server-version", "1.0")
|
|
111
117
|
|
|
112
118
|
/* establish source */
|
|
113
|
-
const sourcing = await ctx.apiS.source("example/server/download", async (
|
|
119
|
+
const sourcing = await ctx.apiS.source("example/server/download", async (_filename, info) => {
|
|
114
120
|
info.buffer = Promise.resolve(Buffer.from("data"))
|
|
115
121
|
})
|
|
116
122
|
|
|
@@ -121,7 +127,7 @@ describe("MQTT+ Source Fetch", function () {
|
|
|
121
127
|
expect(meta!["server-version"]).to.be.equal("1.0")
|
|
122
128
|
|
|
123
129
|
/* cleanup */
|
|
124
|
-
ctx.apiS.meta("server-version",
|
|
130
|
+
ctx.apiS.meta("server-version", null)
|
|
125
131
|
await sourcing.destroy()
|
|
126
132
|
})
|
|
127
133
|
})
|
|
@@ -46,9 +46,11 @@ const { expect } = chai
|
|
|
46
46
|
|
|
47
47
|
/* test suite */
|
|
48
48
|
describe("MQTT+ Miscellaneous", function () {
|
|
49
|
-
/* test case: Dry-Run & Last-Will
|
|
49
|
+
/* test case: Dry-Run & Last-Will */
|
|
50
50
|
it("MQTT+ Dry-Run & MQTT Last-Will", async function () {
|
|
51
|
-
|
|
51
|
+
/* setup */
|
|
52
|
+
this.slow(2000)
|
|
53
|
+
this.timeout(2000)
|
|
52
54
|
|
|
53
55
|
/* generate connection close event */
|
|
54
56
|
const mqttpDry = new MQTTp<API>(null, { id: "my-client" })
|
|
@@ -63,11 +65,11 @@ describe("MQTT+ Miscellaneous", function () {
|
|
|
63
65
|
mqttServer.once("connect", () => { resolve() })
|
|
64
66
|
mqttServer.once("error", (err: Error) => { reject(err) })
|
|
65
67
|
})
|
|
66
|
-
const apiServer = new MQTTp<API>(mqttServer, { timeout:
|
|
68
|
+
const apiServer = new MQTTp<API>(mqttServer, { timeout: 100 })
|
|
67
69
|
|
|
68
70
|
/* observe connection events */
|
|
69
71
|
const spy = sinon.spy()
|
|
70
|
-
apiServer.event("example/server/connection", (state) => {
|
|
72
|
+
const eventReg = await apiServer.event("example/server/connection", (state) => {
|
|
71
73
|
expect(state).to.match(/^(?:open|close)$/)
|
|
72
74
|
spy(state)
|
|
73
75
|
})
|
|
@@ -84,7 +86,7 @@ describe("MQTT+ Miscellaneous", function () {
|
|
|
84
86
|
mqttClient.once("connect", () => { resolve() })
|
|
85
87
|
mqttClient.once("error", (err: Error) => { reject(err) })
|
|
86
88
|
})
|
|
87
|
-
const apiClient = new MQTTp<API>(mqttClient, { timeout:
|
|
89
|
+
const apiClient = new MQTTp<API>(mqttClient, { timeout: 100 })
|
|
88
90
|
|
|
89
91
|
/* send connection open event */
|
|
90
92
|
await apiClient.emit("example/server/connection", "open")
|
|
@@ -93,11 +95,12 @@ describe("MQTT+ Miscellaneous", function () {
|
|
|
93
95
|
/* perform unexpected destruction of client */
|
|
94
96
|
apiClient.destroy()
|
|
95
97
|
mqttClient.end(true)
|
|
96
|
-
await new Promise((resolve) => { setTimeout(resolve,
|
|
98
|
+
await new Promise((resolve) => { setTimeout(resolve, 100) })
|
|
97
99
|
|
|
98
100
|
/* perform regular destruction of client */
|
|
101
|
+
await eventReg.destroy()
|
|
99
102
|
apiServer.destroy()
|
|
100
|
-
mqttServer.
|
|
103
|
+
await mqttServer.endAsync()
|
|
101
104
|
|
|
102
105
|
/* ensure connection open and close events were seen */
|
|
103
106
|
expect(spy.getCalls().map((call) => call.firstArg))
|
|
@@ -107,7 +110,8 @@ describe("MQTT+ Miscellaneous", function () {
|
|
|
107
110
|
/* test case: Authentication */
|
|
108
111
|
it("MQTT+ Authentication", async function () {
|
|
109
112
|
/* setup */
|
|
110
|
-
this.
|
|
113
|
+
this.slow(1000)
|
|
114
|
+
this.timeout(1000)
|
|
111
115
|
const spy = sinon.spy()
|
|
112
116
|
|
|
113
117
|
/* credentials */
|
|
@@ -141,13 +145,12 @@ describe("MQTT+ Miscellaneous", function () {
|
|
|
141
145
|
})
|
|
142
146
|
|
|
143
147
|
/* call service (without token) */
|
|
144
|
-
await ctx.apiC.call("example/server/hello", "world", 42).then(async (
|
|
148
|
+
await ctx.apiC.call("example/server/hello", "world", 42).then(async (_result) => {
|
|
145
149
|
spy("call1-success")
|
|
146
|
-
}).catch((
|
|
150
|
+
}).catch((_err: Error) => {
|
|
147
151
|
spy("call1-error")
|
|
148
152
|
})
|
|
149
|
-
expect(spy.getCalls()
|
|
150
|
-
.map((call) => call.firstArg))
|
|
153
|
+
expect(spy.getCalls().map((call) => call.firstArg))
|
|
151
154
|
.to.be.deep.equal([ "call1-error" ])
|
|
152
155
|
spy.resetHistory()
|
|
153
156
|
|
|
@@ -155,35 +158,32 @@ describe("MQTT+ Miscellaneous", function () {
|
|
|
155
158
|
await ctx.apiC.call("example/server/login", userCred).then(async (token) => {
|
|
156
159
|
spy("login-success")
|
|
157
160
|
expect(token).to.be.equal(userToken)
|
|
158
|
-
}).catch((
|
|
161
|
+
}).catch((_err: Error) => {
|
|
159
162
|
spy("login-error")
|
|
160
163
|
})
|
|
161
|
-
expect(spy.getCalls()
|
|
162
|
-
.map((call) => call.firstArg))
|
|
164
|
+
expect(spy.getCalls().map((call) => call.firstArg))
|
|
163
165
|
.to.be.deep.equal([ "login", "login-success" ])
|
|
164
166
|
spy.resetHistory()
|
|
165
167
|
|
|
166
168
|
/* call service (with wrong token) */
|
|
167
169
|
await ctx.apiC.authenticate("wrong")
|
|
168
|
-
await ctx.apiC.call("example/server/hello", "world", 42).then(async (
|
|
170
|
+
await ctx.apiC.call("example/server/hello", "world", 42).then(async (_result) => {
|
|
169
171
|
spy("call2-success")
|
|
170
|
-
}).catch((
|
|
172
|
+
}).catch((_err: Error) => {
|
|
171
173
|
spy("call2-error")
|
|
172
174
|
})
|
|
173
|
-
expect(spy.getCalls()
|
|
174
|
-
.map((call) => call.firstArg))
|
|
175
|
+
expect(spy.getCalls().map((call) => call.firstArg))
|
|
175
176
|
.to.be.deep.equal([ "call2-error" ])
|
|
176
177
|
spy.resetHistory()
|
|
177
178
|
|
|
178
179
|
/* call service (with correct token) */
|
|
179
180
|
await ctx.apiC.authenticate(userToken)
|
|
180
|
-
await ctx.apiC.call("example/server/hello", "world", 42).then(async (
|
|
181
|
+
await ctx.apiC.call("example/server/hello", "world", 42).then(async (_result) => {
|
|
181
182
|
spy("call3-success")
|
|
182
|
-
}).catch((
|
|
183
|
+
}).catch((_err: Error) => {
|
|
183
184
|
spy("call3-error")
|
|
184
185
|
})
|
|
185
|
-
expect(spy.getCalls()
|
|
186
|
-
.map((call) => call.firstArg))
|
|
186
|
+
expect(spy.getCalls().map((call) => call.firstArg))
|
|
187
187
|
.to.be.deep.equal([ "hello", "call3-success" ])
|
|
188
188
|
|
|
189
189
|
/* destroy service */
|