mqtt-plus 0.9.17 → 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/CHANGELOG.md +11 -0
- package/README.md +166 -114
- package/dst-stage1/mqtt-plus-base.js +1 -1
- package/dst-stage1/mqtt-plus-codec.js +1 -3
- package/dst-stage1/mqtt-plus-event.d.ts +6 -1
- package/dst-stage1/mqtt-plus-event.js +19 -8
- package/dst-stage1/mqtt-plus-msg.d.ts +3 -2
- package/dst-stage1/mqtt-plus-msg.js +1 -1
- package/dst-stage1/mqtt-plus-resource.d.ts +6 -1
- package/dst-stage1/mqtt-plus-resource.js +25 -14
- package/dst-stage1/mqtt-plus-service.d.ts +6 -1
- package/dst-stage1/mqtt-plus-service.js +19 -8
- package/dst-stage2/mqtt-plus.cjs.js +54 -30
- package/dst-stage2/mqtt-plus.esm.js +54 -30
- package/dst-stage2/mqtt-plus.umd.js +12 -12
- package/package.json +1 -1
- package/src/mqtt-plus-base.ts +1 -1
- package/src/mqtt-plus-codec.ts +1 -3
- package/src/mqtt-plus-event.ts +31 -12
- package/src/mqtt-plus-msg.ts +10 -2
- package/src/mqtt-plus-resource.ts +36 -17
- package/src/mqtt-plus-service.ts +30 -11
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
ChangeLog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
1.0.0 (2026-01-25)
|
|
6
|
+
------------------
|
|
7
|
+
|
|
8
|
+
- CLEANUP: various code cleanups
|
|
9
|
+
|
|
10
|
+
0.9.18 (2026-01-25)
|
|
11
|
+
-------------------
|
|
12
|
+
|
|
13
|
+
- IMPROVEMENT: support config parameter API variant also in subscribe/register/provision
|
|
14
|
+
- IMPROVEMENT: support MQTT 5 shared subscriptions also in subscribe/register/provision
|
|
15
|
+
|
|
5
16
|
0.9.17 (2026-01-25)
|
|
6
17
|
-------------------
|
|
7
18
|
|
package/README.md
CHANGED
|
@@ -113,12 +113,16 @@ const mqttp = new MQTTp<API>(mqtt)
|
|
|
113
113
|
|
|
114
114
|
mqtt.on("connect", async () => {
|
|
115
115
|
await mqttp.subscribe("example/sample", (a1, a2, info) => {
|
|
116
|
-
console.log("example/sample:", a1, a2,
|
|
116
|
+
console.log("example/sample: SERVER:", a1, a2, info.sender)
|
|
117
117
|
})
|
|
118
118
|
await mqttp.register("example/hello", (a1, a2, info) => {
|
|
119
|
-
console.log("example/hello:", a1, a2,
|
|
119
|
+
console.log("example/hello: SERVER:", a1, a2, info.sender)
|
|
120
120
|
return `${a1}:${a2}`
|
|
121
121
|
})
|
|
122
|
+
await mqttp.provision("example/resource", async (filename, info) => {
|
|
123
|
+
console.log("example/resource: SERVER:", filename, info.sender)
|
|
124
|
+
info.buffer = Promise.resolve(new TextEncoder().encode(`the ${filename} content`))
|
|
125
|
+
})
|
|
122
126
|
})
|
|
123
127
|
```
|
|
124
128
|
|
|
@@ -132,12 +136,17 @@ import type { API } from [...]
|
|
|
132
136
|
const mqtt = MQTT.connect("wss://127.0.0.1:8883", { [...] })
|
|
133
137
|
const mqttp = new MQTTp<API>(mqtt)
|
|
134
138
|
|
|
135
|
-
mqtt.on("connect", () => {
|
|
139
|
+
mqtt.on("connect", async () => {
|
|
136
140
|
mqttp.emit("example/sample", "world", 42)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
+
|
|
142
|
+
const response = await mqttp.call("example/hello", "world", 42)
|
|
143
|
+
console.log("example/hello CLIENT:", response)
|
|
144
|
+
|
|
145
|
+
const result = await mqttp.fetch("example/resource", "foo")
|
|
146
|
+
const data = new TextDecoder().decode(await result.buffer)
|
|
147
|
+
console.log("example/resource CLIENT:", data)
|
|
148
|
+
|
|
149
|
+
mqtt.end()
|
|
141
150
|
})
|
|
142
151
|
```
|
|
143
152
|
|
|
@@ -148,8 +157,13 @@ The **MQTT+** API provides the following methods:
|
|
|
148
157
|
|
|
149
158
|
- **Construction**:<br/>
|
|
150
159
|
|
|
151
|
-
|
|
152
|
-
|
|
160
|
+
/* (simplified TypeScript API method signature) */
|
|
161
|
+
constructor<API extends Record<string,
|
|
162
|
+
Event< (...args: any[]) => void | Promise<void>> |
|
|
163
|
+
Service< (...args: any[]) => any | Promise<any> > |
|
|
164
|
+
Resource<(...args: any[]) => void | Promise<void>>
|
|
165
|
+
>>(
|
|
166
|
+
mqtt: MqttClient | null,
|
|
153
167
|
options?: {
|
|
154
168
|
id: string
|
|
155
169
|
codec: "cbor" | "json"
|
|
@@ -160,19 +174,16 @@ The **MQTT+** API provides the following methods:
|
|
|
160
174
|
}
|
|
161
175
|
)
|
|
162
176
|
|
|
163
|
-
The `API` is an optional TypeScript type
|
|
164
|
-
|
|
165
|
-
available events and services. Callbacks without return values
|
|
166
|
-
describe events, callbacks with return values describe services.
|
|
167
|
-
|
|
177
|
+
The `API` is an optional TypeScript type,
|
|
178
|
+
describing the available events, services and resources.
|
|
168
179
|
The `mqtt` is the [MQTT.js](https://www.npmjs.com/package/mqtt) instance,
|
|
169
|
-
which has to be established separately.
|
|
170
|
-
|
|
171
|
-
|
|
180
|
+
which has to be established separately. A `null` MQTT instance can be
|
|
181
|
+
used for performing dry-runs (see *Dry-Run Publishing for MQTT Last-Will* under
|
|
182
|
+
**Event Emission** below).
|
|
172
183
|
|
|
173
184
|
The optional `options` object supports the following fields:
|
|
174
185
|
- `id`: Custom MQTT peer identifier (default: auto-generated NanoID).
|
|
175
|
-
- `codec`: Encoding format (default: `cbor`).
|
|
186
|
+
- `codec`: Encoding format, either `cbor` or `json` (default: `cbor`).
|
|
176
187
|
- `timeout`: Communication timeout in milliseconds (default: `10000`).
|
|
177
188
|
- `chunkSize`: Chunk size in bytes for resource transfers (default: `16384`).
|
|
178
189
|
- `topicMake`: Custom topic generation function.
|
|
@@ -195,18 +206,29 @@ The **MQTT+** API provides the following methods:
|
|
|
195
206
|
/* (simplified TypeScript API method signature) */
|
|
196
207
|
subscribe(
|
|
197
208
|
event: string,
|
|
198
|
-
options?: MQTT::IClientSubscribeOptions
|
|
199
209
|
callback: (
|
|
200
210
|
...params: any[],
|
|
201
211
|
info: { sender: string, receiver?: string }
|
|
202
212
|
) => void | Promise<void>
|
|
203
213
|
): Promise<Subscription>
|
|
214
|
+
subscribe({
|
|
215
|
+
event: string,
|
|
216
|
+
callback: (
|
|
217
|
+
...params: any[],
|
|
218
|
+
info: { sender: string, receiver?: string }
|
|
219
|
+
) => void | Promise<void>,
|
|
220
|
+
options?: MQTT::IClientSubscribeOptions,
|
|
221
|
+
share?: string
|
|
222
|
+
}): Promise<Subscription>
|
|
204
223
|
|
|
205
224
|
Subscribe to an event.
|
|
206
225
|
The `event` has to be a valid MQTT topic name.
|
|
207
|
-
The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
|
|
208
226
|
The `callback` is called with the `params` passed to a remote `emit()`.
|
|
209
227
|
There is no return value of `callback`.
|
|
228
|
+
The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
|
|
229
|
+
The optional `share` enables [MQTT Shared Subscriptions](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901250)
|
|
230
|
+
(MQTT 5.0) for load-balancing messages across multiple subscribers by specifying
|
|
231
|
+
a group name. This internally prefixes the event with `$share/<share>/`.
|
|
210
232
|
|
|
211
233
|
Internally, on the MQTT broker, the topics generated by
|
|
212
234
|
`topicMake(event, "event-emission")` (default: `${event}/event-emission/any` and
|
|
@@ -218,18 +240,29 @@ The **MQTT+** API provides the following methods:
|
|
|
218
240
|
/* (simplified TypeScript API method signature) */
|
|
219
241
|
register(
|
|
220
242
|
service: string,
|
|
221
|
-
options?: MQTT::IClientSubscribeOptions
|
|
222
243
|
callback: (
|
|
223
244
|
...params: any[],
|
|
224
245
|
info: { sender: string, receiver?: string }
|
|
225
246
|
) => any | Promise<any>
|
|
226
247
|
): Promise<Registration>
|
|
248
|
+
register({
|
|
249
|
+
service: string,
|
|
250
|
+
callback: (
|
|
251
|
+
...params: any[],
|
|
252
|
+
info: { sender: string, receiver?: string }
|
|
253
|
+
) => any | Promise<any>,
|
|
254
|
+
options?: MQTT::IClientSubscribeOptions,
|
|
255
|
+
share?: string
|
|
256
|
+
}): Promise<Registration>
|
|
227
257
|
|
|
228
258
|
Register a service.
|
|
229
259
|
The `service` has to be a valid MQTT topic name.
|
|
230
|
-
The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
|
|
231
260
|
The `callback` is called with the `params` passed to a remote `call()`.
|
|
232
261
|
The return value of `callback` will resolve the `Promise` returned by the remote `call()`.
|
|
262
|
+
The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
|
|
263
|
+
The optional `share` enables [MQTT Shared Subscriptions](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901250)
|
|
264
|
+
(MQTT 5.0) for load-balancing service calls across multiple registrants by specifying
|
|
265
|
+
a group name. This internally prefixes the service with `$share/<share>/`.
|
|
233
266
|
|
|
234
267
|
Internally, on the MQTT broker, the topics generated by
|
|
235
268
|
`topicMake(service, "service-call-request")` (default: `${service}/service-call-request/any` and
|
|
@@ -241,30 +274,47 @@ The **MQTT+** API provides the following methods:
|
|
|
241
274
|
/* (simplified TypeScript API method signature) */
|
|
242
275
|
provision(
|
|
243
276
|
resource: string,
|
|
244
|
-
options?: MQTT::IClientSubscribeOptions
|
|
245
277
|
callback: (
|
|
246
278
|
...params: any[],
|
|
247
279
|
info: {
|
|
248
|
-
sender:
|
|
280
|
+
sender: string,
|
|
249
281
|
receiver?: string,
|
|
250
|
-
meta?:
|
|
251
|
-
stream?:
|
|
252
|
-
buffer?:
|
|
282
|
+
meta?: Record<string, any>,
|
|
283
|
+
stream?: Readable,
|
|
284
|
+
buffer?: Promise<Uint8Array>
|
|
253
285
|
}
|
|
254
286
|
) => void | Promise<void>
|
|
255
287
|
): Promise<Provisioning>
|
|
288
|
+
provision({
|
|
289
|
+
resource: string,
|
|
290
|
+
callback: (
|
|
291
|
+
...params: any[],
|
|
292
|
+
info: {
|
|
293
|
+
sender: string,
|
|
294
|
+
receiver?: string,
|
|
295
|
+
meta?: Record<string, any>,
|
|
296
|
+
stream?: Readable,
|
|
297
|
+
buffer?: Promise<Uint8Array>
|
|
298
|
+
}
|
|
299
|
+
) => void | Promise<void>,
|
|
300
|
+
options?: MQTT::IClientSubscribeOptions,
|
|
301
|
+
share?: string
|
|
302
|
+
}): Promise<Provisioning>
|
|
256
303
|
|
|
257
304
|
Provision a resource for both fetch requests and pushed data.
|
|
258
305
|
The `resource` has to be a valid MQTT topic name.
|
|
259
306
|
The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
|
|
307
|
+
The optional `share` enables [MQTT Shared Subscriptions](https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901250)
|
|
308
|
+
(MQTT 5.0) for load-balancing resource requests across multiple provisioners by specifying
|
|
309
|
+
a group name. This internally prefixes the resource with `$share/<share>/`.
|
|
260
310
|
|
|
261
311
|
For **fetch requests**: The `callback` is called with the `params` passed to a remote `fetch()`.
|
|
262
|
-
The `callback` should set `info.stream` to a `Readable` or `info.buffer` to a `Promise<
|
|
312
|
+
The `callback` should set `info.stream` to a `Readable` or `info.buffer` to a `Promise<Uint8Array>` containing the resource data.
|
|
263
313
|
Optionally, the `callback` can set `info.meta` to a `Record<string, any>` to send metadata back with the response.
|
|
264
314
|
|
|
265
315
|
For **pushed data**: The `callback` is called with the `params` passed to a remote `push()`.
|
|
266
316
|
The `info.stream` provides a Node.js `Readable` stream for consuming the pushed data.
|
|
267
|
-
The `info.buffer` provides a lazy `Promise<
|
|
317
|
+
The `info.buffer` provides a lazy `Promise<Uint8Array>` that resolves to the complete data once the stream ends.
|
|
268
318
|
The `info.meta` contains optional metadata sent by the pusher via `push()`.
|
|
269
319
|
|
|
270
320
|
Internally, on the MQTT broker, the topics by
|
|
@@ -292,7 +342,7 @@ The **MQTT+** API provides the following methods:
|
|
|
292
342
|
receiver?: string,
|
|
293
343
|
options?: MQTT::IClientSubscribeOptions,
|
|
294
344
|
dry: true
|
|
295
|
-
}): { topic: string, payload:
|
|
345
|
+
}): { topic: string, payload: Uint8Array, options: IClientPublishOptions }
|
|
296
346
|
|
|
297
347
|
Emit an event to all subscribers or a specific subscriber ("fire and forget").
|
|
298
348
|
The optional `receiver` directs the event to a specific subscriber only.
|
|
@@ -307,7 +357,7 @@ The **MQTT+** API provides the following methods:
|
|
|
307
357
|
Internally, publishes to the MQTT topic by `topicMake(event, "event-emission", peerId)`
|
|
308
358
|
(default: `${event}/event-emission/any` or `${event}/event-emission/${peerId}`).
|
|
309
359
|
|
|
310
|
-
|
|
360
|
+
*Dry-Run Publishing for MQTT Last-Will:*
|
|
311
361
|
When you need to set up an MQTT "last will" message (automatically published
|
|
312
362
|
by the broker when a client disconnects *unexpectedly*), you can use `dry: true`
|
|
313
363
|
together with a `null` MQTT client:
|
|
@@ -367,7 +417,7 @@ The **MQTT+** API provides the following methods:
|
|
|
367
417
|
...params: any[]
|
|
368
418
|
): Promise<{
|
|
369
419
|
stream: Readable,
|
|
370
|
-
buffer: Promise<
|
|
420
|
+
buffer: Promise<Uint8Array>,
|
|
371
421
|
meta: Promise<Record<string, any> | undefined>
|
|
372
422
|
}>
|
|
373
423
|
fetch({
|
|
@@ -377,7 +427,7 @@ The **MQTT+** API provides the following methods:
|
|
|
377
427
|
options?: MQTT::IClientSubscribeOptions
|
|
378
428
|
}): Promise<{
|
|
379
429
|
stream: Readable,
|
|
380
|
-
buffer: Promise<
|
|
430
|
+
buffer: Promise<Uint8Array>,
|
|
381
431
|
meta: Promise<Record<string, any> | undefined>
|
|
382
432
|
}>
|
|
383
433
|
|
|
@@ -386,12 +436,12 @@ The **MQTT+** API provides the following methods:
|
|
|
386
436
|
The optional `options` allows setting MQTT.js `publish()` options like `qos` or `retain`.
|
|
387
437
|
|
|
388
438
|
Returns an object with a `stream` (`Readable`) for consuming the transferred data,
|
|
389
|
-
a lazy `buffer` (`Promise<
|
|
439
|
+
a lazy `buffer` (`Promise<Uint8Array>`) that resolves to the complete data once the stream ends,
|
|
390
440
|
and a `meta` (`Promise<Record<string, any> | undefined>`) that resolves to optional metadata
|
|
391
441
|
sent by the provisioner when the first chunk arrives.
|
|
392
442
|
|
|
393
443
|
The remote `provision()` `callback` is called with `params` and
|
|
394
|
-
should set `info.stream` to a `Readable` or `info.buffer` to a `Promise<
|
|
444
|
+
should set `info.stream` to a `Readable` or `info.buffer` to a `Promise<Uint8Array>` containing the resource data.
|
|
395
445
|
Optionally, the `callback` can set `info.meta` to send metadata back with the response.
|
|
396
446
|
If the remote `callback` throws an exception, this destroys the stream with the error.
|
|
397
447
|
|
|
@@ -405,12 +455,12 @@ The **MQTT+** API provides the following methods:
|
|
|
405
455
|
/* (simplified TypeScript API method signature) */
|
|
406
456
|
push(
|
|
407
457
|
resource: string,
|
|
408
|
-
streamOrBuffer: Readable |
|
|
458
|
+
streamOrBuffer: Readable | Uint8Array,
|
|
409
459
|
...params: any[]
|
|
410
460
|
): Promise<void>
|
|
411
461
|
push({
|
|
412
462
|
resource: string,
|
|
413
|
-
streamOrBuffer: Readable |
|
|
463
|
+
streamOrBuffer: Readable | Uint8Array,
|
|
414
464
|
params: any[]
|
|
415
465
|
meta?: Record<string, any>,
|
|
416
466
|
receiver?: string,
|
|
@@ -418,7 +468,7 @@ The **MQTT+** API provides the following methods:
|
|
|
418
468
|
}): Promise<void>
|
|
419
469
|
|
|
420
470
|
Pushes a resource to all provisioners or a specific provisioner.
|
|
421
|
-
The `streamOrBuffer` is either a Node.js `Readable` stream or a `
|
|
471
|
+
The `streamOrBuffer` is either a Node.js `Readable` stream or a `Uint8Array` providing the data to push.
|
|
422
472
|
The optional `meta` sends metadata alongside the resource data,
|
|
423
473
|
which becomes available on the provisioner side via `info.meta`.
|
|
424
474
|
The optional `receiver` directs the push to a specific provisioner only.
|
|
@@ -431,7 +481,7 @@ The **MQTT+** API provides the following methods:
|
|
|
431
481
|
|
|
432
482
|
The remote `provision()` `callback` is called with `params` and an `info` object
|
|
433
483
|
containing `stream` (`Readable`) for consuming the pushed data,
|
|
434
|
-
`buffer` (lazy `Promise<
|
|
484
|
+
`buffer` (lazy `Promise<Uint8Array>`) that resolves to the complete data once the stream ends,
|
|
435
485
|
and `meta` (`Record<string, any> | undefined`) containing the metadata sent by the pusher.
|
|
436
486
|
|
|
437
487
|
Internally, publishes to the MQTT topic by `topicMake(resource, "resource-transfer-response", peerId)`
|
|
@@ -446,8 +496,12 @@ In the following, we assume that an **MQTT+** instance is created with:
|
|
|
446
496
|
import MQTT from "mqtt"
|
|
447
497
|
import MQTTp from "mqtt-plus"
|
|
448
498
|
|
|
499
|
+
export type API = {
|
|
500
|
+
"example/sample": Event<(a1: string, a2: number) => void>
|
|
501
|
+
...
|
|
502
|
+
}
|
|
449
503
|
const mqtt = MQTT.connect("...", { ... })
|
|
450
|
-
const mqttp = new MQTTp(mqtt, { codec: "json" })
|
|
504
|
+
const mqttp = new MQTTp<API>(mqtt, { codec: "json" })
|
|
451
505
|
```
|
|
452
506
|
|
|
453
507
|
Internally, remote services are assigned to MQTT topics. When calling a
|
|
@@ -501,78 +555,18 @@ The `sender` field is the NanoID of the MQTT+ sender instance and
|
|
|
501
555
|
used for sending back the response message to the requestor only. The
|
|
502
556
|
`id` is used for correlating the response to the request only.
|
|
503
557
|
|
|
504
|
-
Broker Setup
|
|
505
|
-
------------
|
|
506
|
-
|
|
507
|
-
For a real test-drive of MQTT+, install the
|
|
508
|
-
[Mosquitto](https://mosquitto.org/) MQTT broker and a `mosquitto.conf`
|
|
509
|
-
file like...
|
|
510
|
-
|
|
511
|
-
```
|
|
512
|
-
[...]
|
|
513
|
-
|
|
514
|
-
password_file mosquitto-pwd.txt
|
|
515
|
-
acl_file mosquitto-acl.txt
|
|
516
|
-
|
|
517
|
-
[...]
|
|
518
|
-
|
|
519
|
-
# additional listener
|
|
520
|
-
listener 1883 127.0.0.1
|
|
521
|
-
max_connections -1
|
|
522
|
-
protocol mqtt
|
|
523
|
-
|
|
524
|
-
[...]
|
|
525
|
-
```
|
|
526
|
-
|
|
527
|
-
...and an access control list in `mosquitto-acl.txt` like...
|
|
528
|
-
|
|
529
|
-
```
|
|
530
|
-
# shared ACL
|
|
531
|
-
topic read $SYS/#
|
|
532
|
-
pattern write $SYS/broker/connection/%c/state
|
|
533
|
-
pattern readwrite %u/#
|
|
534
|
-
|
|
535
|
-
# anonymous ACL
|
|
536
|
-
topic read event/+
|
|
537
|
-
pattern read event/+/%c
|
|
538
|
-
topic write event/+/+
|
|
539
|
-
pattern read service/+/%c
|
|
540
|
-
topic write service/+
|
|
541
|
-
topic write service/+/+
|
|
542
|
-
|
|
543
|
-
# user ACL
|
|
544
|
-
user example
|
|
545
|
-
topic read event/+
|
|
546
|
-
pattern read event/+/%c
|
|
547
|
-
topic write event/+
|
|
548
|
-
topic write event/+/+
|
|
549
|
-
topic read service/+
|
|
550
|
-
pattern read service/+/%c
|
|
551
|
-
topic write service/+
|
|
552
|
-
topic write service/+/+
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
...and an `example` user (with password `example`) in `mosquitto-pwd.txt` like:
|
|
556
|
-
|
|
557
|
-
```
|
|
558
|
-
example:$6$awYNe6oCAi+xlvo5$mWIUqyy4I0O3nJ99lP1mkRVqsDGymF8en5NChQQxf7KrVJLUp1SzrrVDe94wWWJa3JGIbOXD9wfFGZdi948e6A==
|
|
559
|
-
```
|
|
560
|
-
|
|
561
|
-
Alternatively, you can use the [NPM package mosquitto](https://npmjs.com/mosquitto)
|
|
562
|
-
for an equal setup.
|
|
563
|
-
|
|
564
558
|
Example
|
|
565
559
|
-------
|
|
566
560
|
|
|
567
|
-
You can test-drive MQTT
|
|
568
|
-
|
|
569
|
-
|
|
561
|
+
You can test-drive MQTT+, in a temporary MQTT broker environment,
|
|
562
|
+
with a complete [sample](sample/sample.ts) to see it in action and
|
|
563
|
+
tracing its communication:
|
|
570
564
|
|
|
571
565
|
```ts
|
|
572
|
-
import Mosquitto
|
|
573
|
-
import MQTT
|
|
574
|
-
import MQTTp
|
|
575
|
-
import type
|
|
566
|
+
import Mosquitto from "mosquitto"
|
|
567
|
+
import MQTT from "mqtt"
|
|
568
|
+
import MQTTp from "mqtt-plus"
|
|
569
|
+
import type { Event, Service, Resource } from "mqtt-plus"
|
|
576
570
|
|
|
577
571
|
const mosquitto = new Mosquitto()
|
|
578
572
|
await mosquitto.start()
|
|
@@ -584,9 +578,9 @@ const mqtt = MQTT.connect("mqtt://127.0.0.1:1883", {
|
|
|
584
578
|
})
|
|
585
579
|
|
|
586
580
|
type API = {
|
|
587
|
-
"example/sample":
|
|
588
|
-
"example/hello":
|
|
589
|
-
"example/resource":
|
|
581
|
+
"example/sample": Event<(a1: string, a2: number) => void>
|
|
582
|
+
"example/hello": Service<(a1: string, a2: number) => string>
|
|
583
|
+
"example/resource": Resource<(filename: string) => void>
|
|
590
584
|
}
|
|
591
585
|
|
|
592
586
|
const mqttp = new MQTTp<API>(mqtt, { codec: "json" })
|
|
@@ -620,11 +614,12 @@ mqtt.on("connect", async () => {
|
|
|
620
614
|
/* resource fetch example */
|
|
621
615
|
const prov = await mqttp.provision("example/resource", async (filename, info) => {
|
|
622
616
|
console.log("example/resource: request:", filename, "from:", info.sender)
|
|
623
|
-
|
|
617
|
+
const data = `the ${filename} content`
|
|
618
|
+
info.buffer = Promise.resolve(new TextEncoder().encode(data))
|
|
624
619
|
})
|
|
625
620
|
const res = await mqttp.fetch("example/resource", "foo")
|
|
626
|
-
const data = await res.buffer
|
|
627
|
-
console.log("example/resource: result:", data
|
|
621
|
+
const data = new TextDecoder().decode(await res.buffer)
|
|
622
|
+
console.log("example/resource: result:", data)
|
|
628
623
|
await prov.unprovision()
|
|
629
624
|
|
|
630
625
|
mqtt.end()
|
|
@@ -650,11 +645,68 @@ example/resource: result: the foo content
|
|
|
650
645
|
CLOSE
|
|
651
646
|
```
|
|
652
647
|
|
|
648
|
+
Broker Setup
|
|
649
|
+
------------
|
|
650
|
+
|
|
651
|
+
For establishing your own permanent MQTT environment, install the
|
|
652
|
+
[Mosquitto](https://mosquitto.org/) MQTT broker yourself and a setup
|
|
653
|
+
a `mosquitto.conf` file like...
|
|
654
|
+
|
|
655
|
+
```
|
|
656
|
+
[...]
|
|
657
|
+
|
|
658
|
+
password_file mosquitto-pwd.txt
|
|
659
|
+
acl_file mosquitto-acl.txt
|
|
660
|
+
|
|
661
|
+
[...]
|
|
662
|
+
|
|
663
|
+
# additional listener
|
|
664
|
+
listener 1883 127.0.0.1
|
|
665
|
+
max_connections -1
|
|
666
|
+
protocol mqtt
|
|
667
|
+
|
|
668
|
+
[...]
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
...and an access control list in `mosquitto-acl.txt` like...
|
|
672
|
+
|
|
673
|
+
```
|
|
674
|
+
# shared ACL
|
|
675
|
+
topic read $SYS/#
|
|
676
|
+
pattern write $SYS/broker/connection/%c/state
|
|
677
|
+
pattern readwrite %u/#
|
|
678
|
+
|
|
679
|
+
# anonymous ACL
|
|
680
|
+
topic read event/+
|
|
681
|
+
pattern read event/+/%c
|
|
682
|
+
topic write event/+/+
|
|
683
|
+
pattern read service/+/%c
|
|
684
|
+
topic write service/+
|
|
685
|
+
topic write service/+/+
|
|
686
|
+
|
|
687
|
+
# user ACL
|
|
688
|
+
user example
|
|
689
|
+
topic read event/+
|
|
690
|
+
pattern read event/+/%c
|
|
691
|
+
topic write event/+
|
|
692
|
+
topic write event/+/+
|
|
693
|
+
topic read service/+
|
|
694
|
+
pattern read service/+/%c
|
|
695
|
+
topic write service/+
|
|
696
|
+
topic write service/+/+
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
...and an `example` user (with password `example`) in `mosquitto-pwd.txt` like:
|
|
700
|
+
|
|
701
|
+
```
|
|
702
|
+
example:$6$awYNe6oCAi+xlvo5$mWIUqyy4I0O3nJ99lP1mkRVqsDGymF8en5NChQQxf7KrVJLUp1SzrrVDe94wWWJa3JGIbOXD9wfFGZdi948e6A==
|
|
703
|
+
```
|
|
704
|
+
|
|
653
705
|
Notice
|
|
654
706
|
------
|
|
655
707
|
|
|
656
708
|
> [!Note]
|
|
657
|
-
> **MQTT+** is somewhat similar to and originally derived from
|
|
709
|
+
> **MQTT+** is somewhat similar to and originally derived from the weaker
|
|
658
710
|
> [MQTT-JSON-RPC](https://github.com/rse/mqtt-json-rpc) of the same
|
|
659
711
|
> author, but instead of just JSON, MQTT+ encodes packets as JSON
|
|
660
712
|
> or CBOR (default), uses an own packet format (allowing sender and
|
|
@@ -86,9 +86,7 @@ export default class Codec {
|
|
|
86
86
|
}
|
|
87
87
|
decode(data) {
|
|
88
88
|
let result;
|
|
89
|
-
if (this.type === "cbor"
|
|
90
|
-
&& typeof data === "object"
|
|
91
|
-
&& data instanceof Uint8Array) {
|
|
89
|
+
if (this.type === "cbor" && data instanceof Uint8Array) {
|
|
92
90
|
try {
|
|
93
91
|
result = CBOR.decode(data, { tags: this.tags });
|
|
94
92
|
}
|
|
@@ -9,7 +9,12 @@ export interface Subscription {
|
|
|
9
9
|
export declare class EventTrait<T extends APISchema = APISchema> extends BaseTrait<T> {
|
|
10
10
|
private subscriptions;
|
|
11
11
|
subscribe<K extends EventKeys<T> & string>(event: K, callback: WithInfo<T[K], InfoEvent>): Promise<Subscription>;
|
|
12
|
-
subscribe<K extends EventKeys<T> & string>(
|
|
12
|
+
subscribe<K extends EventKeys<T> & string>(config: {
|
|
13
|
+
event: K;
|
|
14
|
+
callback: WithInfo<T[K], InfoEvent>;
|
|
15
|
+
options?: Partial<IClientSubscribeOptions>;
|
|
16
|
+
share?: string;
|
|
17
|
+
}): Promise<Subscription>;
|
|
13
18
|
emit<K extends EventKeys<T> & string>(event: K, ...params: Parameters<T[K]>): void;
|
|
14
19
|
emit<K extends EventKeys<T> & string>(config: {
|
|
15
20
|
event: K;
|
|
@@ -34,20 +34,31 @@ export class EventTrait extends BaseTrait {
|
|
|
34
34
|
/* internal state */
|
|
35
35
|
this.subscriptions = new Map();
|
|
36
36
|
}
|
|
37
|
-
async subscribe(
|
|
38
|
-
/* determine parameters */
|
|
37
|
+
async subscribe(eventOrConfig, ...args) {
|
|
38
|
+
/* determine actual parameters */
|
|
39
|
+
let event;
|
|
40
|
+
let callback;
|
|
39
41
|
let options = {};
|
|
40
|
-
let
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
let share;
|
|
43
|
+
if (typeof eventOrConfig === "object" && eventOrConfig !== null) {
|
|
44
|
+
/* object-based API */
|
|
45
|
+
event = eventOrConfig.event;
|
|
46
|
+
callback = eventOrConfig.callback;
|
|
47
|
+
options = eventOrConfig.options ?? {};
|
|
48
|
+
share = eventOrConfig.share;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
/* positional API */
|
|
52
|
+
event = eventOrConfig;
|
|
53
|
+
callback = args[0];
|
|
44
54
|
}
|
|
45
55
|
/* sanity check situation */
|
|
46
56
|
if (this.subscriptions.has(event))
|
|
47
57
|
throw new Error(`subscribe: event "${event}" already subscribed`);
|
|
48
58
|
/* generate the corresponding MQTT topics for broadcast and direct use */
|
|
49
|
-
const
|
|
50
|
-
const
|
|
59
|
+
const name = share ? `$share/${share}/${event}` : event;
|
|
60
|
+
const topicB = this.options.topicMake(name, "event-emission");
|
|
61
|
+
const topicD = this.options.topicMake(name, "event-emission", this.options.id);
|
|
51
62
|
/* subscribe to MQTT topics */
|
|
52
63
|
await Promise.all([
|
|
53
64
|
this._subscribeTopic(topicB, { qos: 0, ...options }),
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { APISchema } from "./mqtt-plus-api";
|
|
2
2
|
import { CodecTrait } from "./mqtt-plus-codec";
|
|
3
|
+
type MessageType = "event-emission" | "service-call-request" | "service-call-response" | "resource-transfer-request" | "resource-transfer-response";
|
|
3
4
|
declare class Base {
|
|
4
|
-
type:
|
|
5
|
+
type: MessageType;
|
|
5
6
|
id: string;
|
|
6
7
|
sender?: string | undefined;
|
|
7
8
|
receiver?: string | undefined;
|
|
8
|
-
constructor(type:
|
|
9
|
+
constructor(type: MessageType, id: string, sender?: string | undefined, receiver?: string | undefined);
|
|
9
10
|
}
|
|
10
11
|
export declare class EventEmission extends Base {
|
|
11
12
|
event: string;
|
|
@@ -153,7 +153,7 @@ class Msg {
|
|
|
153
153
|
else if (obj.type === "resource-transfer-response") {
|
|
154
154
|
if (obj.resource !== undefined && typeof obj.resource !== "string")
|
|
155
155
|
throw new Error("invalid ResourceTransferResponse object: \"resource\" field must be a string");
|
|
156
|
-
if (obj.chunk !== undefined && typeof obj.chunk !== "object")
|
|
156
|
+
if (obj.chunk !== undefined && (obj.chunk === null || typeof obj.chunk !== "object"))
|
|
157
157
|
throw new Error("invalid ResourceTransferResponse object: \"chunk\" field must be an object");
|
|
158
158
|
if (obj.meta !== undefined && (typeof obj.meta !== "object" || obj.meta === null || Array.isArray(obj.meta)))
|
|
159
159
|
throw new Error("invalid ResourceTransferResponse object: \"meta\" field must be an object");
|
|
@@ -12,7 +12,12 @@ export declare class ResourceTrait<T extends APISchema = APISchema> extends Serv
|
|
|
12
12
|
private pushStreams;
|
|
13
13
|
private pushTimers;
|
|
14
14
|
provision<K extends ResourceKeys<T> & string>(resource: K, callback: WithInfo<T[K], InfoResource>): Promise<Provisioning>;
|
|
15
|
-
provision<K extends ResourceKeys<T> & string>(
|
|
15
|
+
provision<K extends ResourceKeys<T> & string>(config: {
|
|
16
|
+
resource: K;
|
|
17
|
+
callback: WithInfo<T[K], InfoResource>;
|
|
18
|
+
options?: Partial<IClientSubscribeOptions>;
|
|
19
|
+
share?: string;
|
|
20
|
+
}): Promise<Provisioning>;
|
|
16
21
|
push<K extends ResourceKeys<T> & string>(resource: K, data: Readable | Uint8Array, ...params: Parameters<T[K]>): Promise<void>;
|
|
17
22
|
push<K extends ResourceKeys<T> & string>(config: {
|
|
18
23
|
resource: K;
|