mqtt-plus 0.9.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/README.md +457 -0
- package/dst-stage1/mqtt-plus-codec.d.ts +6 -0
- package/dst-stage1/mqtt-plus-codec.js +74 -0
- package/dst-stage1/mqtt-plus-msg.d.ts +31 -0
- package/dst-stage1/mqtt-plus-msg.js +128 -0
- package/dst-stage1/mqtt-plus.d.ts +73 -0
- package/dst-stage1/mqtt-plus.js +408 -0
- package/dst-stage2/mqtt-plus.cjs.js +6422 -0
- package/dst-stage2/mqtt-plus.esm.js +6423 -0
- package/dst-stage2/mqtt-plus.umd.js +19 -0
- package/etc/eslint.mts +94 -0
- package/etc/logo.ai +5106 -2
- package/etc/logo.svg +17 -0
- package/etc/stx.conf +47 -0
- package/etc/tsc.json +27 -0
- package/etc/tsc.tsbuildinfo +1 -0
- package/etc/vite.mts +66 -0
- package/package.json +60 -0
- package/src/mqtt-plus-codec.ts +59 -0
- package/src/mqtt-plus-msg.ts +171 -0
- package/src/mqtt-plus.ts +598 -0
package/README.md
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
|
|
2
|
+
<img src="https://raw.githubusercontent.com/rse/mqtt-plus/master/etc/logo.svg" width="400" align="right" alt=""/>
|
|
3
|
+
|
|
4
|
+
MQTT+
|
|
5
|
+
=====
|
|
6
|
+
|
|
7
|
+
[MQTT](http://mqtt.org/) Communication Patterns
|
|
8
|
+
|
|
9
|
+
<p/>
|
|
10
|
+
<img src="https://nodei.co/npm/mqtt-plus.png?downloads=true&stars=true" alt=""/>
|
|
11
|
+
|
|
12
|
+
[](https://github.com/rse)
|
|
13
|
+
[](https://github.com/rse)
|
|
14
|
+
|
|
15
|
+
Installation
|
|
16
|
+
------------
|
|
17
|
+
|
|
18
|
+
```shell
|
|
19
|
+
$ npm install mqtt mqtt-plus
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
About
|
|
23
|
+
-----
|
|
24
|
+
|
|
25
|
+
This is **MQTT+**, an addon JavaScript/TypeScript API for the excellent
|
|
26
|
+
[MQTT.js](https://www.npmjs.com/package/mqtt), for additional
|
|
27
|
+
communication patterns like [Remote Procedure
|
|
28
|
+
Call](https://en.wikipedia.org/wiki/Remote_procedure_call) (RPC).
|
|
29
|
+
This allows a bi-directional request/response-style
|
|
30
|
+
communication over the technically uni-directional message protocol
|
|
31
|
+
[MQTT](http://mqtt.org).
|
|
32
|
+
|
|
33
|
+
Conceptually, the **MQTT+** API provides two types of communication patterns:
|
|
34
|
+
|
|
35
|
+
- **Event Emission**:
|
|
36
|
+
Event Emission is a *uni-directional* communication pattern.
|
|
37
|
+
An Event is the combination of an event name and optionally zero or more arguments.
|
|
38
|
+
You *subscribe* to events.
|
|
39
|
+
When an event is *emitted*, either a single particular subscriber (in case of
|
|
40
|
+
a directed event emission) or all subscribers are called and receive the
|
|
41
|
+
arguments as extra information.
|
|
42
|
+
In contrast to the regular MQTT message publish/subscribe, this
|
|
43
|
+
pattern allows to direct the event to particular subscribers and
|
|
44
|
+
provides optional information about the sender to subscribers.
|
|
45
|
+
|
|
46
|
+
- **Service Call**:
|
|
47
|
+
Service Call is a *bi-directional* communication pattern.
|
|
48
|
+
A Service is the combination of a service name and optionally zero or more arguments.
|
|
49
|
+
You *register* for a service.
|
|
50
|
+
When a service is *called*, a single particular registrator (in case
|
|
51
|
+
of a directed service call) or one arbitrary registrator is called and
|
|
52
|
+
receives the arguments as the request. The registrator then has to
|
|
53
|
+
provide the service response.
|
|
54
|
+
|
|
55
|
+
> [!Note]
|
|
56
|
+
> **MQTT+** is similar to and derived from
|
|
57
|
+
> [MQTT-JSON-RPC](https://github.com/rse/mqtt-json-rpc) of the same
|
|
58
|
+
> author, but instead of just JSON, MQTT+ encodes packets as JSON
|
|
59
|
+
> or CBOR (default), uses an own packet format (allowing sender and
|
|
60
|
+
> receiver information) and uses shorter NanoIDs instead of longer UUIDs
|
|
61
|
+
> for identification of sender, receiver and requests.
|
|
62
|
+
|
|
63
|
+
Usage
|
|
64
|
+
-----
|
|
65
|
+
|
|
66
|
+
### API:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
export type API = {
|
|
70
|
+
"example/sample": (a1: string, a2: boolean) => void
|
|
71
|
+
"example/hello": (a1: string, a2: number) => string
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Server:
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import MQTT from "mqtt"
|
|
79
|
+
import MQTTp from "mqtt-plus"
|
|
80
|
+
import type { API } from [...]
|
|
81
|
+
|
|
82
|
+
const mqtt = MQTT.connect("wss://127.0.0.1:8883", { ... })
|
|
83
|
+
const mqttp = new MQTTp<API>(mqtt)
|
|
84
|
+
|
|
85
|
+
mqtt.on("connect", async () => {
|
|
86
|
+
mqttp.subscribe("example/sample", (a1, a2) => {
|
|
87
|
+
console.log("example/sample: ", a1, a2)
|
|
88
|
+
})
|
|
89
|
+
mqttp.register("example/hello", (a1, a2) => {
|
|
90
|
+
console.log("example/hello: ", a1, a2)
|
|
91
|
+
return `${a1}:${a2}`
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Client:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import MQTT from "mqtt"
|
|
100
|
+
import MQTTp from "mqtt-plus"
|
|
101
|
+
import type { API } from [...]
|
|
102
|
+
|
|
103
|
+
const mqtt = MQTT.connect("wss://127.0.0.1:8883", { ... })
|
|
104
|
+
const mqttp = new MQTTp<API>(mqtt)
|
|
105
|
+
|
|
106
|
+
mqtt.on("connect", () => {
|
|
107
|
+
mqttp.emit("example/sample", "foo", true)
|
|
108
|
+
mqttp.call("example/hello", "world", 42).then((response) => {
|
|
109
|
+
console.log("example/hello response: ", response)
|
|
110
|
+
mqtt.end()
|
|
111
|
+
})
|
|
112
|
+
})
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Application Programming Interface
|
|
116
|
+
---------------------------------
|
|
117
|
+
|
|
118
|
+
The MQTT+ API provides the following methods:
|
|
119
|
+
|
|
120
|
+
- **Construction**:<br/>
|
|
121
|
+
|
|
122
|
+
constructor(
|
|
123
|
+
mqtt: MqttClient,
|
|
124
|
+
options?: {
|
|
125
|
+
id: string
|
|
126
|
+
codec: "cbor" | "json"
|
|
127
|
+
timeout: number
|
|
128
|
+
topicEventNoticeMake: (topic: string) => TopicMatching | null
|
|
129
|
+
topicServiceRequestMake: (topic: string) => TopicMatching | null
|
|
130
|
+
topicServiceResponseMake: (topic: string) => TopicMatching | null
|
|
131
|
+
topicEventNoticeMatch: { name: string, clientId?: string }
|
|
132
|
+
topicServiceRequestMatch: { name: string, clientId?: string }
|
|
133
|
+
topicServiceResponseMatch: { name: string, clientId?: string }
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
The `mqtt` is the [MQTT.js](https://www.npmjs.com/package/mqtt) instance,
|
|
138
|
+
which has to be establish separately.
|
|
139
|
+
The optional `options` object supports the following fields:
|
|
140
|
+
- `id`: Custom MQTT peer identifier (default: auto-generated NanoID).
|
|
141
|
+
- `codec`: Encoding format (default: `cbor`).
|
|
142
|
+
- `timeout`: Communication timeout in milliseconds (default: `10000`).
|
|
143
|
+
- `topicEventNoticeMake`: Custom topic generation for event notices.
|
|
144
|
+
(default: `` (name, clientId) => clientId ? `${name}/event-notice/${clientId}` : `${name}/event-notice` ``)
|
|
145
|
+
- `topicServiceRequestMake`: Custom topic generation for service requests.
|
|
146
|
+
(default: `` (name, clientId) => clientId ? `${name}/service-request/${clientId}` : `${name}/service-request` ``)
|
|
147
|
+
- `topicServiceResponseMake`): Custom topic generation for service responses.
|
|
148
|
+
(default: `` (name, clientId) => clientId ? `${name}/service-response/${clientId}` : `${name}/service-response` ``)
|
|
149
|
+
- `topicEventNoticeMatch`: Custom topic matching for event notices.
|
|
150
|
+
(default: `` (topic) => { const m = topic.match(/^(.+?)\/event-notice(?:\/(.+))?$/); return m ? { name: m[1], clientId: m[2] } : null } ``)
|
|
151
|
+
- `topicServiceRequestMatch`: Custom topic matching for service requests.
|
|
152
|
+
(default: `` (topic) => { const m = topic.match(/^(.+?)\/service-request(?:\/(.+))?$/); return m ? { name: m[1], clientId: m[2] } : null } ``)
|
|
153
|
+
- `topicServiceResponseMatch`: Custom topic matching for service responses.
|
|
154
|
+
(default: `` (topic) => { const m = topic.match(/^(.+?)\/service-response\/(.+)$/); return m ? { name: m[1], clientId: m[2] } : null } ``)
|
|
155
|
+
|
|
156
|
+
- **Event Subscription**:<br/>
|
|
157
|
+
|
|
158
|
+
/* (simplified TypeScript API method signature) */
|
|
159
|
+
subscribe(
|
|
160
|
+
event: string,
|
|
161
|
+
options?: MQTT::IClientSubscribeOptions
|
|
162
|
+
callback: (...params: any[], info?: sender: string, receiver?: string) => void
|
|
163
|
+
): Promise<Subscription>
|
|
164
|
+
|
|
165
|
+
Subscribe to an event.
|
|
166
|
+
The `event` has to be a valid MQTT topic name.
|
|
167
|
+
The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
|
|
168
|
+
The `callback` is called with the `params` passed to a remote `emit()`.
|
|
169
|
+
There is no return value of `callback`.
|
|
170
|
+
|
|
171
|
+
Internally, on the MQTT broker, the topics generated by
|
|
172
|
+
`topicEventNoticeMake()` (default: `${event}/event-notice` and
|
|
173
|
+
`${event}/event-notice/${clientId}`) are subscribed. Returns a
|
|
174
|
+
`Subscription` object with an `unsubscribe()` method.
|
|
175
|
+
|
|
176
|
+
- **Service Registration**:<br/>
|
|
177
|
+
|
|
178
|
+
/* (simplified TypeScript API method signature) */
|
|
179
|
+
register(
|
|
180
|
+
service: string,
|
|
181
|
+
options?: MQTT::IClientSubscribeOptions
|
|
182
|
+
callback: (...params: any[], info?: sender: string, receiver?: string) => any
|
|
183
|
+
): Promise<Registration>
|
|
184
|
+
|
|
185
|
+
Register a service.
|
|
186
|
+
The `service` has to be a valid MQTT topic name.
|
|
187
|
+
The optional `options` allows setting MQTT.js `subscribe()` options like `qos`.
|
|
188
|
+
The `callback` is called with the `params` passed to a remote `call()`.
|
|
189
|
+
The return value of `callback` will resolve the `Promise` returned by the remote `call()`.
|
|
190
|
+
|
|
191
|
+
Internally, on the MQTT broker, the topics by
|
|
192
|
+
`topicServiceRequestMake()` (default: `${service}/service-request` and
|
|
193
|
+
`${service}/service-request/${clientId}`) are subscribed. Returns a
|
|
194
|
+
`Registration` object with an `unregister()` method.
|
|
195
|
+
|
|
196
|
+
- **Event Emission**:<br/>
|
|
197
|
+
|
|
198
|
+
/* (simplified TypeScript API method signature) */
|
|
199
|
+
emit(
|
|
200
|
+
event: string,
|
|
201
|
+
receiver?: Receiver,
|
|
202
|
+
options?: MQTT::IClientSubscribeOptions,
|
|
203
|
+
...params: any[]
|
|
204
|
+
): void
|
|
205
|
+
|
|
206
|
+
Emit an event to all subscribers or a specific subscriber ("fire and forget").
|
|
207
|
+
The optional `receiver` directs the event to a specific subscriber only.
|
|
208
|
+
The optional `options` allows setting MQTT.js `publish()` options like `qos` or `retain`.
|
|
209
|
+
|
|
210
|
+
The remote `subscribe()` `callback` is called with `params` and its
|
|
211
|
+
return value is silently ignored.
|
|
212
|
+
|
|
213
|
+
Internally, publishes to the MQTT topic by `topicEventNoticeMake(event, clientId)`
|
|
214
|
+
(default: `${event}/event-notice` or `${event}/event-notice/${clientId}`).
|
|
215
|
+
|
|
216
|
+
- **Service Call**:<br/>
|
|
217
|
+
|
|
218
|
+
/* (simplified TypeScript API method signature) */
|
|
219
|
+
call(
|
|
220
|
+
service: string,
|
|
221
|
+
receiver?: Receiver,
|
|
222
|
+
options?: MQTT::IClientSubscribeOptions,
|
|
223
|
+
...params: any[]
|
|
224
|
+
): Promise<any>
|
|
225
|
+
|
|
226
|
+
Call a service on all registrants or on a specific registrant ("request and response").
|
|
227
|
+
The optional `receiver` directs the call to a specific registrant only.
|
|
228
|
+
The optional `options` allows setting MQTT.js `publish()` options like `qos` or `retain`.
|
|
229
|
+
|
|
230
|
+
The remote `register()` `callback` is called with `params` and its
|
|
231
|
+
return value resolves the returned `Promise`. If the remote `callback`
|
|
232
|
+
throws an exception, this rejects the returned `Promise`.
|
|
233
|
+
|
|
234
|
+
Internally, on the MQTT broker, the topic by `topicServiceResponseMake(service, clientId)`
|
|
235
|
+
(default: `${service}/service-response/${clientId}`) is temporarily subscribed
|
|
236
|
+
for receiving the response.
|
|
237
|
+
|
|
238
|
+
- **Receiver Wrapping**:<br/>
|
|
239
|
+
|
|
240
|
+
receiver(
|
|
241
|
+
id: string
|
|
242
|
+
): Receiver
|
|
243
|
+
|
|
244
|
+
Wrap a receiver ID string for use with `emit()` or `call()` to direct the
|
|
245
|
+
message to a specific receiver. Returns a `Receiver` object.
|
|
246
|
+
|
|
247
|
+
Internals
|
|
248
|
+
---------
|
|
249
|
+
|
|
250
|
+
In the following, assume that an MQTT+ instance is created with:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
import MQTT from "mqtt"
|
|
254
|
+
import MQTTp from "mqtt-plus"
|
|
255
|
+
|
|
256
|
+
const mqtt = MQTT.connect("...", { ... })
|
|
257
|
+
const mqttp = new MQTTp(mqtt, { codec: "json" })
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Internally, remote services are assigned to MQTT topics. When calling a
|
|
261
|
+
remote service named `example/hello` with parameters `"world"` and `42` via...
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
mqttp.call("example/hello", "world", 42).then((result) => {
|
|
265
|
+
...
|
|
266
|
+
})
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
...the following message is sent to the permanent MQTT topic
|
|
270
|
+
`example/hello/service-request` (the shown NanoIDs are just pseudo
|
|
271
|
+
ones):
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"id": "RRRRRRRRRRRRRRRRRRRRR",
|
|
276
|
+
"sender": "SSSSSSSSSSSSSSSSSSSSS",
|
|
277
|
+
"method": "example/hello",
|
|
278
|
+
"params": [ "world", 42 ]
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Beforehand, this `example/hello` service should have been registered with...
|
|
283
|
+
|
|
284
|
+
```ts
|
|
285
|
+
mqttp.register("example/hello", (a1, a2) => {
|
|
286
|
+
return `${a1}:${a2}`
|
|
287
|
+
})
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
...and then its result, in the above `mqttp.call()` example `"world:42"`, is then
|
|
291
|
+
sent back as the following success response
|
|
292
|
+
message to the temporary (client-specific) MQTT topic
|
|
293
|
+
`example/hello/service-response/SSSSSSSSSSSSSSSSSSSSS`:
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
{
|
|
297
|
+
"id": "RRRRRRRRRRRRRRRRRRRRR",
|
|
298
|
+
"sender": "SSSSSSSSSSSSSSSSSSSSS",
|
|
299
|
+
"result": "world:42"
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
The `sender` field is the NanoID of the MQTT+ sender instance and
|
|
304
|
+
`id` is the NanoID of the particular service request. The `sender` is
|
|
305
|
+
used for sending back the response message to the requestor only. The
|
|
306
|
+
`id` is used for correlating the response to the request only.
|
|
307
|
+
|
|
308
|
+
Broker Setup
|
|
309
|
+
------------
|
|
310
|
+
|
|
311
|
+
For a real test-drive of MQTT+, install the
|
|
312
|
+
[Mosquitto](https://mosquitto.org/) MQTT broker and a `mosquitto.conf`
|
|
313
|
+
file like...
|
|
314
|
+
|
|
315
|
+
```
|
|
316
|
+
[...]
|
|
317
|
+
|
|
318
|
+
password_file mosquitto-pwd.txt
|
|
319
|
+
acl_file mosquitto-acl.txt
|
|
320
|
+
|
|
321
|
+
[...]
|
|
322
|
+
|
|
323
|
+
# additional listener
|
|
324
|
+
listener 1883 127.0.0.1
|
|
325
|
+
max_connections -1
|
|
326
|
+
protocol mqtt
|
|
327
|
+
|
|
328
|
+
[...]
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
...and an access control list in `mosquitto-acl.txt` like...
|
|
332
|
+
|
|
333
|
+
```
|
|
334
|
+
# shared ACL
|
|
335
|
+
topic read $SYS/#
|
|
336
|
+
pattern write $SYS/broker/connection/%c/state
|
|
337
|
+
pattern readwrite %u/#
|
|
338
|
+
|
|
339
|
+
# anonymous ACL
|
|
340
|
+
topic read event/+
|
|
341
|
+
pattern read event/+/%c
|
|
342
|
+
topic write event/+/+
|
|
343
|
+
pattern read service/+/%c
|
|
344
|
+
topic write service/+
|
|
345
|
+
topic write service/+/+
|
|
346
|
+
|
|
347
|
+
# user ACL
|
|
348
|
+
user example
|
|
349
|
+
topic read event/+
|
|
350
|
+
pattern read event/+/%c
|
|
351
|
+
topic write event/+
|
|
352
|
+
topic write event/+/+
|
|
353
|
+
topic read service/+
|
|
354
|
+
pattern read service/+/%c
|
|
355
|
+
topic write service/+
|
|
356
|
+
topic write service/+/+
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
...and an `example` user (with password `example`) in `mosquitto-pwd.txt` like:
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
example:$6$awYNe6oCAi+xlvo5$mWIUqyy4I0O3nJ99lP1mkRVqsDGymF8en5NChQQxf7KrVJLUp1SzrrVDe94wWWJa3JGIbOXD9wfFGZdi948e6A==
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Alternatively, you can use the [NPM package mosquitto](https://npmjs.com/mosquitto)
|
|
366
|
+
for an equal setup.
|
|
367
|
+
|
|
368
|
+
Example
|
|
369
|
+
-------
|
|
370
|
+
|
|
371
|
+
You can test-drive MQTT+ with a complete [sample](sample/sample.ts) to see
|
|
372
|
+
it in action and tracing its communication (the typing of the `MQTTp`
|
|
373
|
+
class with `API` is optional, but strongly suggested):
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
import Mosquitto from "mosquitto"
|
|
377
|
+
import MQTT from "mqtt"
|
|
378
|
+
import MQTTp from "mqtt-plus"
|
|
379
|
+
|
|
380
|
+
const mosquitto = new Mosquitto()
|
|
381
|
+
await mosquitto.start()
|
|
382
|
+
await new Promise((resolve) => { setTimeout(resolve, 500) })
|
|
383
|
+
|
|
384
|
+
const mqtt = MQTT.connect("mqtt://127.0.0.1:1883", {
|
|
385
|
+
username: "example",
|
|
386
|
+
password: "example"
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
type API = {
|
|
390
|
+
"example/sample": (a1: string, a2: number) => void
|
|
391
|
+
"example/hello": (a1: string, a2: number) => string
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const mqttp = new MQTTp<API>(mqtt, { codec: "json" })
|
|
395
|
+
|
|
396
|
+
type Sample = (a: string, b: number) => string
|
|
397
|
+
|
|
398
|
+
mqtt.on("error", (err) => { console.log("ERROR", err) })
|
|
399
|
+
mqtt.on("offline", () => { console.log("OFFLINE") })
|
|
400
|
+
mqtt.on("close", () => { console.log("CLOSE") })
|
|
401
|
+
mqtt.on("reconnect", () => { console.log("RECONNECT") })
|
|
402
|
+
mqtt.on("message", (topic, message) => { console.log("RECEIVED", topic, message.toString()) })
|
|
403
|
+
|
|
404
|
+
mqtt.on("connect", () => {
|
|
405
|
+
console.log("CONNECT")
|
|
406
|
+
mqttp.register("example/hello", (a1, a2) => {
|
|
407
|
+
console.log("example/hello: request: ", a1, a2)
|
|
408
|
+
return `${a1}:${a2}`
|
|
409
|
+
})
|
|
410
|
+
mqttp.call("example/hello", "world", 42).then((result) => {
|
|
411
|
+
console.log("example/hello success: ", result)
|
|
412
|
+
mqtt.end()
|
|
413
|
+
await mosquitto.stop()
|
|
414
|
+
}).catch((err) => {
|
|
415
|
+
console.log("example/hello error: ", err)
|
|
416
|
+
})
|
|
417
|
+
})
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
The output will be:
|
|
421
|
+
|
|
422
|
+
```
|
|
423
|
+
$ node sample.ts
|
|
424
|
+
CONNECT
|
|
425
|
+
example/sample: info: world 42 undefined
|
|
426
|
+
RECEIVED example/sample/event-notice {"id":"GraDZnA4BLrF66g9qmpPX","sender":"2IBMSk0NPnrz1AeTERoea","event":"example/sample","params":["world",42]}
|
|
427
|
+
RECEIVED example/hello/service-request {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","service":"example/hello","params":["world",42]}
|
|
428
|
+
example/hello: request: world 42 undefined
|
|
429
|
+
RECEIVED example/hello/service-response/2IBMSk0NPnrz1AeTERoea {"id":"vwLzfQDu2uEeOdOfIlT42","sender":"2IBMSk0NPnrz1AeTERoea","receiver":"2IBMSk0NPnrz1AeTERoea","result":"world:42"}
|
|
430
|
+
example/hello success: world:42
|
|
431
|
+
CLOSE
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
License
|
|
435
|
+
-------
|
|
436
|
+
|
|
437
|
+
Copyright (c) 2018-2025 Dr. Ralf S. Engelschall (http://engelschall.com/)
|
|
438
|
+
|
|
439
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
440
|
+
a copy of this software and associated documentation files (the
|
|
441
|
+
"Software"), to deal in the Software without restriction, including
|
|
442
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
443
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
444
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
445
|
+
the following conditions:
|
|
446
|
+
|
|
447
|
+
The above copyright notice and this permission notice shall be included
|
|
448
|
+
in all copies or substantial portions of the Software.
|
|
449
|
+
|
|
450
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
451
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
452
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
453
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
454
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
455
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
456
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
457
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
import CBOR from "cbor";
|
|
25
|
+
/* the encoder/decoder abstraction */
|
|
26
|
+
export default class Codec {
|
|
27
|
+
constructor(type) {
|
|
28
|
+
this.type = type;
|
|
29
|
+
}
|
|
30
|
+
encode(data) {
|
|
31
|
+
let result;
|
|
32
|
+
if (this.type === "cbor") {
|
|
33
|
+
try {
|
|
34
|
+
result = CBOR.encode(data);
|
|
35
|
+
}
|
|
36
|
+
catch (_ex) {
|
|
37
|
+
throw new Error("failed to encode CBOR format");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else if (this.type === "json") {
|
|
41
|
+
try {
|
|
42
|
+
result = JSON.stringify(data);
|
|
43
|
+
}
|
|
44
|
+
catch (_ex) {
|
|
45
|
+
throw new Error("failed to encode JSON format");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else
|
|
49
|
+
throw new Error("invalid format");
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
decode(data) {
|
|
53
|
+
let result;
|
|
54
|
+
if (this.type === "cbor" && typeof data === "object" && data instanceof Buffer) {
|
|
55
|
+
try {
|
|
56
|
+
result = CBOR.decode(data);
|
|
57
|
+
}
|
|
58
|
+
catch (_ex) {
|
|
59
|
+
throw new Error("failed to decode CBOR format");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (this.type === "json" && typeof data === "string") {
|
|
63
|
+
try {
|
|
64
|
+
result = JSON.parse(data);
|
|
65
|
+
}
|
|
66
|
+
catch (_ex) {
|
|
67
|
+
throw new Error("failed to decode JSON format");
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else
|
|
71
|
+
throw new Error("invalid format or wrong data type");
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare class Base {
|
|
2
|
+
id: string;
|
|
3
|
+
sender?: string | undefined;
|
|
4
|
+
receiver?: string | undefined;
|
|
5
|
+
constructor(id: string, sender?: string | undefined, receiver?: string | undefined);
|
|
6
|
+
}
|
|
7
|
+
export declare class EventEmission extends Base {
|
|
8
|
+
event: string;
|
|
9
|
+
params?: any[] | undefined;
|
|
10
|
+
constructor(id: string, event: string, params?: any[] | undefined, sender?: string, receiver?: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class ServiceRequest extends Base {
|
|
13
|
+
service: string;
|
|
14
|
+
params?: any[] | undefined;
|
|
15
|
+
constructor(id: string, service: string, params?: any[] | undefined, sender?: string, receiver?: string);
|
|
16
|
+
}
|
|
17
|
+
export declare class ServiceResponseSuccess extends Base {
|
|
18
|
+
result: any;
|
|
19
|
+
constructor(id: string, result: any, sender?: string, receiver?: string);
|
|
20
|
+
}
|
|
21
|
+
export declare class ServiceResponseError extends Base {
|
|
22
|
+
error: string;
|
|
23
|
+
constructor(id: string, error: string, sender?: string, receiver?: string);
|
|
24
|
+
}
|
|
25
|
+
export default class Msg {
|
|
26
|
+
makeEventEmission(id: string, event: string, params?: any[], sender?: string, receiver?: string): EventEmission;
|
|
27
|
+
makeServiceRequest(id: string, service: string, params?: any[], sender?: string, receiver?: string): ServiceRequest;
|
|
28
|
+
makeServiceResponseSuccess(id: string, result: any, sender?: string, receiver?: string): ServiceResponseSuccess;
|
|
29
|
+
makeServiceResponseError(id: string, error: string, sender?: string, receiver?: string): ServiceResponseError;
|
|
30
|
+
parse(obj: any): EventEmission | ServiceRequest | ServiceResponseSuccess | ServiceResponseError;
|
|
31
|
+
}
|