mqtt-plus 1.1.3 → 1.2.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 +24 -0
- package/README.md +49 -8
- package/dst-stage1/mqtt-plus-auth.d.ts +2 -1
- package/dst-stage1/mqtt-plus-auth.js +15 -7
- package/dst-stage1/mqtt-plus-base.js +3 -1
- package/dst-stage1/mqtt-plus-codec.d.ts +4 -3
- package/dst-stage1/mqtt-plus-codec.js +20 -19
- package/dst-stage1/mqtt-plus-encode.d.ts +1 -0
- package/dst-stage1/mqtt-plus-encode.js +3 -1
- package/dst-stage1/mqtt-plus-event.js +27 -20
- package/dst-stage1/mqtt-plus-meta.d.ts +1 -1
- package/dst-stage1/mqtt-plus-meta.js +2 -0
- package/dst-stage1/mqtt-plus-msg.js +110 -88
- package/dst-stage1/mqtt-plus-service.d.ts +4 -4
- package/dst-stage1/mqtt-plus-service.js +46 -42
- package/dst-stage1/mqtt-plus-sink.d.ts +3 -0
- package/dst-stage1/mqtt-plus-sink.js +80 -33
- package/dst-stage1/mqtt-plus-source.d.ts +6 -6
- package/dst-stage1/mqtt-plus-source.js +109 -42
- package/dst-stage1/mqtt-plus-trace.d.ts +2 -1
- package/dst-stage1/mqtt-plus-trace.js +1 -1
- package/dst-stage1/mqtt-plus-util.d.ts +4 -3
- package/dst-stage1/mqtt-plus-util.js +33 -21
- package/dst-stage2/mqtt-plus.cjs.js +428 -259
- package/dst-stage2/mqtt-plus.esm.js +425 -259
- package/dst-stage2/mqtt-plus.umd.js +12 -12
- package/etc/vite.mts +4 -1
- package/package.json +9 -6
- package/src/mqtt-plus-auth.ts +20 -11
- package/src/mqtt-plus-base.ts +3 -1
- package/src/mqtt-plus-codec.ts +18 -17
- package/src/mqtt-plus-encode.ts +11 -8
- package/src/mqtt-plus-event.ts +28 -21
- package/src/mqtt-plus-meta.ts +6 -4
- package/src/mqtt-plus-msg.ts +124 -92
- package/src/mqtt-plus-service.ts +56 -49
- package/src/mqtt-plus-sink.ts +83 -36
- package/src/mqtt-plus-source.ts +118 -49
- package/src/mqtt-plus-trace.ts +1 -1
- package/src/mqtt-plus-util.ts +38 -29
- package/tst/mqtt-plus-mosquitto.ts +1 -1
- package/tst/mqtt-plus.spec.ts +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
ChangeLog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
1.2.0 (2026-02-06)
|
|
6
|
+
------------------
|
|
7
|
+
|
|
8
|
+
- IMPROVEMENT: use Valibot for more robust object validation
|
|
9
|
+
- IMPROVEMENT: support concurrent operations
|
|
10
|
+
- IMPROVEMENT: improve chunk sending
|
|
11
|
+
- IMPROVEMENT: improve buffer handling and decoding
|
|
12
|
+
- IMPROVEMENT: use derived keys to have enough entropy
|
|
13
|
+
- IMPROVEMENT: report failures and log errors on dispatching messages
|
|
14
|
+
- IMPROVEMENT: await subscribes and super calls in dispatching messages
|
|
15
|
+
- IMPROVEMENT: log failing destroy operations
|
|
16
|
+
- BUGFIX: fix share option handling in event()
|
|
17
|
+
- BUGFIX: fix error handling
|
|
18
|
+
- BUGFIX: fix return values of promises
|
|
19
|
+
- CLEANUP: improve type safety (use unknown type, remove Awaited type)
|
|
20
|
+
- CLEANUP: simplify code by using closures and conversions
|
|
21
|
+
- CLEANUP: various code cleanups (rename variables, reduce whitespaces)
|
|
22
|
+
- UPDATE: upgrade NPM dependencies
|
|
23
|
+
|
|
24
|
+
1.1.4 (2026-02-02)
|
|
25
|
+
------------------
|
|
26
|
+
|
|
27
|
+
- CLEANUP: various cleanups
|
|
28
|
+
|
|
5
29
|
1.1.3 (2026-02-02)
|
|
6
30
|
------------------
|
|
7
31
|
|
package/README.md
CHANGED
|
@@ -238,6 +238,40 @@ The **MQTT+** API provides the following functionalities:
|
|
|
238
238
|
Call this method when the instance is no longer needed.
|
|
239
239
|
The companion MQTT.js instance has to be destroyed separately.
|
|
240
240
|
|
|
241
|
+
- **Event Handling**:<br/>
|
|
242
|
+
|
|
243
|
+
/* listen for error or log events */
|
|
244
|
+
on(event: "error", callback: (error: Error) => void): void
|
|
245
|
+
on(event: "log", callback: (log: LogEvent) => void): void
|
|
246
|
+
|
|
247
|
+
/* remove error or log event listener */
|
|
248
|
+
off(event: "error", callback: (error: Error) => void): void
|
|
249
|
+
off(event: "log", callback: (log: LogEvent) => void): void
|
|
250
|
+
|
|
251
|
+
MQTT+ emits `error` and `log` events for monitoring and debugging.
|
|
252
|
+
|
|
253
|
+
- The `on()` method registers an event listener.
|
|
254
|
+
The `"error"` event is emitted when an error occurs during
|
|
255
|
+
message processing, subscription, or publishing.
|
|
256
|
+
The `"log"` event is emitted for informational and debug-level
|
|
257
|
+
messages with a `LogEvent` object containing `timestamp`, `level`,
|
|
258
|
+
`msg`, and optional `data` fields.
|
|
259
|
+
|
|
260
|
+
- The `off()` method removes a previously registered event listener.
|
|
261
|
+
|
|
262
|
+
- The `LogEvent` object provides `resolve()` for resolving lazy
|
|
263
|
+
promise-based fields and `toString()` for rendering log entries
|
|
264
|
+
as formatted strings.
|
|
265
|
+
|
|
266
|
+
Example:
|
|
267
|
+
|
|
268
|
+
mqttp.on("error", (err) => {
|
|
269
|
+
console.error("MQTT+ error:", err.message)
|
|
270
|
+
})
|
|
271
|
+
mqttp.on("log", (log) => {
|
|
272
|
+
console.log(log.toString())
|
|
273
|
+
})
|
|
274
|
+
|
|
241
275
|
- **Authentication**:<br/>
|
|
242
276
|
|
|
243
277
|
/* store server-side secret credential */
|
|
@@ -282,8 +316,11 @@ The **MQTT+** API provides the following functionalities:
|
|
|
282
316
|
/* set meta information by key */
|
|
283
317
|
meta(key: string, value: any): void
|
|
284
318
|
|
|
319
|
+
/* retrieve meta information by key */
|
|
320
|
+
meta(key: string): any
|
|
321
|
+
|
|
285
322
|
/* delete meta information by key */
|
|
286
|
-
meta(key: string): void
|
|
323
|
+
meta(key: string, value: null): void
|
|
287
324
|
|
|
288
325
|
MQTT+ allows attaching persistent meta-data to an instance that is
|
|
289
326
|
automatically included in all outgoing messages. This is useful for
|
|
@@ -291,8 +328,9 @@ The **MQTT+** API provides the following functionalities:
|
|
|
291
328
|
identity to every request.
|
|
292
329
|
|
|
293
330
|
- The `meta()` method manages instance-level meta-data:
|
|
294
|
-
called with a key only,
|
|
295
|
-
called with a key and value, sets the meta-data entry
|
|
331
|
+
called with a key only, retrieves the meta-data entry for that key;
|
|
332
|
+
called with a key and non-null value, sets the meta-data entry;
|
|
333
|
+
called with a key and `null`, deletes the meta-data entry.
|
|
296
334
|
|
|
297
335
|
- Instance-level meta-data set via `meta()` is merged with any per-request
|
|
298
336
|
`meta` option passed to `emit()`, `call()`, `push()`, or `fetch()`.
|
|
@@ -309,8 +347,11 @@ The **MQTT+** API provides the following functionalities:
|
|
|
309
347
|
mqttp.meta("clientVersion", "1.0.0")
|
|
310
348
|
mqttp.meta("environment", "production")
|
|
311
349
|
|
|
350
|
+
/* client: retrieve a metadata entry */
|
|
351
|
+
const environment = mqttp.meta("environment")
|
|
352
|
+
|
|
312
353
|
/* client: delete a metadata entry */
|
|
313
|
-
mqttp.meta("environment")
|
|
354
|
+
mqttp.meta("environment", null)
|
|
314
355
|
|
|
315
356
|
/* client: per-request metadata (merged with instance-level) */
|
|
316
357
|
mqttp.call({ name: "example/hello", params: [ "world" ], meta: { requestId: "123" } })
|
|
@@ -590,14 +631,14 @@ The **MQTT+** API provides the following functionalities:
|
|
|
590
631
|
event: string,
|
|
591
632
|
params: any[],
|
|
592
633
|
receiver?: string,
|
|
593
|
-
options?: MQTT::
|
|
634
|
+
options?: MQTT::IClientPublishOptions,
|
|
594
635
|
meta?: Record<string, any>
|
|
595
636
|
}): void
|
|
596
637
|
emit({
|
|
597
638
|
event: string,
|
|
598
639
|
params: any[],
|
|
599
640
|
receiver?: string,
|
|
600
|
-
options?: MQTT::
|
|
641
|
+
options?: MQTT::IClientPublishOptions,
|
|
601
642
|
meta?: Record<string, any>,
|
|
602
643
|
dry: true
|
|
603
644
|
}): { topic: string, payload: string | Uint8Array, options: IClientPublishOptions }
|
|
@@ -695,7 +736,7 @@ The **MQTT+** API provides the following functionalities:
|
|
|
695
736
|
name: string,
|
|
696
737
|
params: any[],
|
|
697
738
|
receiver?: string,
|
|
698
|
-
options?: MQTT::
|
|
739
|
+
options?: MQTT::IClientPublishOptions,
|
|
699
740
|
meta?: Record<string, any>
|
|
700
741
|
}): Promise<{
|
|
701
742
|
stream: Readable,
|
|
@@ -974,7 +1015,7 @@ pattern read example/client/+/sink-push-request/%c
|
|
|
974
1015
|
pattern write example/client/+/sink-push-response/%c
|
|
975
1016
|
pattern read example/client/+/sink-push-chunk/%c
|
|
976
1017
|
|
|
977
|
-
# ==== server/
|
|
1018
|
+
# ==== server/authenticated ACL ====
|
|
978
1019
|
|
|
979
1020
|
user example
|
|
980
1021
|
|
|
@@ -6,7 +6,7 @@ export type AuthOption = AuthRole | {
|
|
|
6
6
|
mode: AuthMode;
|
|
7
7
|
roles: AuthRole[];
|
|
8
8
|
};
|
|
9
|
-
|
|
9
|
+
type TokenPayload = {
|
|
10
10
|
roles: AuthRole[];
|
|
11
11
|
id?: string;
|
|
12
12
|
};
|
|
@@ -21,3 +21,4 @@ export declare class AuthTrait<T extends APISchema = APISchema> extends MetaTrai
|
|
|
21
21
|
private validateToken;
|
|
22
22
|
protected authenticated(clientId: string | undefined, tokens: string[] | undefined, option: AuthOption): Promise<boolean>;
|
|
23
23
|
}
|
|
24
|
+
export {};
|
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
/* external requirements */
|
|
25
25
|
import { SignJWT } from "jose/jwt/sign";
|
|
26
26
|
import { jwtVerify } from "jose/jwt/verify";
|
|
27
|
+
import * as pbkdf2 from "@stablelib/pbkdf2";
|
|
28
|
+
import * as sha256 from "@stablelib/sha256";
|
|
27
29
|
import { MetaTrait } from "./mqtt-plus-meta";
|
|
28
30
|
/* authentication trait */
|
|
29
31
|
export class AuthTrait extends MetaTrait {
|
|
@@ -35,7 +37,13 @@ export class AuthTrait extends MetaTrait {
|
|
|
35
37
|
}
|
|
36
38
|
/* store server-side secret credential */
|
|
37
39
|
credential(credential) {
|
|
38
|
-
|
|
40
|
+
/* sanity check argument */
|
|
41
|
+
if (credential.length === 0)
|
|
42
|
+
throw new Error("credential must not be empty");
|
|
43
|
+
/* use a derived key with minimum length of 32 for JWT HS256 */
|
|
44
|
+
const pw = new TextEncoder().encode(credential);
|
|
45
|
+
const st = new TextEncoder().encode("mqtt-plus");
|
|
46
|
+
this._credential = pbkdf2.deriveKey(sha256.SHA256, pw, st, 100000, 32);
|
|
39
47
|
}
|
|
40
48
|
/* issue client-side token on server-side */
|
|
41
49
|
async issue(payload) {
|
|
@@ -43,8 +51,7 @@ export class AuthTrait extends MetaTrait {
|
|
|
43
51
|
throw new Error("credential has to be provided before issuing tokens");
|
|
44
52
|
const jwt = new SignJWT(payload);
|
|
45
53
|
jwt.setProtectedHeader({ alg: "HS256", typ: "JWT" });
|
|
46
|
-
const
|
|
47
|
-
const token = await jwt.sign(key);
|
|
54
|
+
const token = await jwt.sign(this._credential);
|
|
48
55
|
return token;
|
|
49
56
|
}
|
|
50
57
|
authenticate(token, remove) {
|
|
@@ -59,8 +66,7 @@ export class AuthTrait extends MetaTrait {
|
|
|
59
66
|
async validateToken(token) {
|
|
60
67
|
if (this._credential === null)
|
|
61
68
|
throw new Error("credential has to be provided before validating tokens");
|
|
62
|
-
const
|
|
63
|
-
const result = await jwtVerify(token, key).catch(() => null);
|
|
69
|
+
const result = await jwtVerify(token, this._credential).catch(() => null);
|
|
64
70
|
return result?.payload ?? null;
|
|
65
71
|
}
|
|
66
72
|
/* check whether request is authenticated */
|
|
@@ -77,9 +83,11 @@ export class AuthTrait extends MetaTrait {
|
|
|
77
83
|
mode = option.mode;
|
|
78
84
|
roles = option.roles;
|
|
79
85
|
}
|
|
80
|
-
/* iterate over all roles and try to authenticate token (first-match) */
|
|
86
|
+
/* iterate over all roles and try to authenticate token (first-match, max 8) */
|
|
81
87
|
if (tokens !== undefined) {
|
|
82
|
-
for (const token of tokens) {
|
|
88
|
+
for (const token of tokens.slice(0, 8)) {
|
|
89
|
+
if (token.length > 8192)
|
|
90
|
+
continue;
|
|
83
91
|
const payload = await this.validateToken(token);
|
|
84
92
|
if (payload === null)
|
|
85
93
|
continue;
|
|
@@ -154,7 +154,9 @@ export class BaseTrait extends TraceTrait {
|
|
|
154
154
|
}
|
|
155
155
|
this.log("debug", `received from MQTT topic "${topic}"`, { message: parsed });
|
|
156
156
|
/* dispatch to trait handlers */
|
|
157
|
-
this._dispatchMessage(topic, parsed).catch(() => {
|
|
157
|
+
this._dispatchMessage(topic, parsed).catch((err) => {
|
|
158
|
+
this.error(err, `dispatching message from MQTT topic "${topic}" failed`);
|
|
159
|
+
});
|
|
158
160
|
}
|
|
159
161
|
/* dispatch parsed message to appropriate handler
|
|
160
162
|
(base implementation, to be overridden in sub-traits) */
|
|
@@ -3,10 +3,10 @@ import { APIOptions, OptionsTrait } from "./mqtt-plus-options";
|
|
|
3
3
|
export declare class JSONX {
|
|
4
4
|
private static uint8ArrayToBase64;
|
|
5
5
|
private static base64ToUint8Array;
|
|
6
|
-
static stringify(obj:
|
|
7
|
-
static parse(json: string):
|
|
6
|
+
static stringify(obj: unknown): string;
|
|
7
|
+
static parse(json: string): unknown;
|
|
8
8
|
}
|
|
9
|
-
|
|
9
|
+
declare class Codec {
|
|
10
10
|
private type;
|
|
11
11
|
private types;
|
|
12
12
|
private tags;
|
|
@@ -18,3 +18,4 @@ export declare class CodecTrait<T extends APISchema = APISchema> extends Options
|
|
|
18
18
|
protected codec: Codec;
|
|
19
19
|
constructor(options?: Partial<APIOptions>);
|
|
20
20
|
}
|
|
21
|
+
export {};
|
|
@@ -22,19 +22,16 @@
|
|
|
22
22
|
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
23
|
*/
|
|
24
24
|
/* external requirements */
|
|
25
|
+
import { Buffer } from "node:buffer";
|
|
25
26
|
import * as CBOR from "cbor2";
|
|
26
27
|
import { OptionsTrait } from "./mqtt-plus-options";
|
|
27
28
|
/* JSON encode/decode with Uint8Array support */
|
|
28
29
|
export class JSONX {
|
|
29
30
|
static uint8ArrayToBase64(arr) {
|
|
30
|
-
return
|
|
31
|
+
return Buffer.from(arr.buffer, arr.byteOffset, arr.byteLength).toString("base64");
|
|
31
32
|
}
|
|
32
33
|
static base64ToUint8Array(base64) {
|
|
33
|
-
|
|
34
|
-
const arr = new Uint8Array(binary.length);
|
|
35
|
-
for (let i = 0; i < binary.length; i++)
|
|
36
|
-
arr[i] = binary.charCodeAt(i);
|
|
37
|
-
return arr;
|
|
34
|
+
return new Uint8Array(Buffer.from(base64, "base64"));
|
|
38
35
|
}
|
|
39
36
|
static stringify(obj) {
|
|
40
37
|
return JSON.stringify(obj, (_, value) => value instanceof Uint8Array
|
|
@@ -48,7 +45,7 @@ export class JSONX {
|
|
|
48
45
|
}
|
|
49
46
|
}
|
|
50
47
|
/* the encoder/decoder abstraction */
|
|
51
|
-
|
|
48
|
+
class Codec {
|
|
52
49
|
constructor(type) {
|
|
53
50
|
this.type = type;
|
|
54
51
|
this.types = new CBOR.TypeEncoderMap();
|
|
@@ -68,42 +65,46 @@ export default class Codec {
|
|
|
68
65
|
try {
|
|
69
66
|
result = CBOR.encode(data, { types: this.types });
|
|
70
67
|
}
|
|
71
|
-
catch (
|
|
72
|
-
throw new Error("failed to encode CBOR format");
|
|
68
|
+
catch (ex) {
|
|
69
|
+
throw new Error("failed to encode CBOR format", { cause: ex });
|
|
73
70
|
}
|
|
74
71
|
}
|
|
75
72
|
else if (this.type === "json") {
|
|
76
73
|
try {
|
|
77
74
|
result = JSONX.stringify(data);
|
|
78
75
|
}
|
|
79
|
-
catch (
|
|
80
|
-
throw new Error("failed to encode JSON format");
|
|
76
|
+
catch (ex) {
|
|
77
|
+
throw new Error("failed to encode JSON format", { cause: ex });
|
|
81
78
|
}
|
|
82
79
|
}
|
|
83
80
|
else
|
|
84
|
-
throw new Error(
|
|
81
|
+
throw new Error(`invalid format "${this.type}"`);
|
|
85
82
|
return result;
|
|
86
83
|
}
|
|
87
84
|
decode(data) {
|
|
88
85
|
let result;
|
|
89
|
-
if (this.type === "cbor"
|
|
86
|
+
if (this.type === "cbor") {
|
|
87
|
+
if (!(data instanceof Uint8Array))
|
|
88
|
+
throw new Error("failed to decode CBOR format (data type is not Uint8Array)");
|
|
90
89
|
try {
|
|
91
90
|
result = CBOR.decode(data, { tags: this.tags });
|
|
92
91
|
}
|
|
93
|
-
catch (
|
|
94
|
-
throw new Error("failed to decode CBOR format");
|
|
92
|
+
catch (ex) {
|
|
93
|
+
throw new Error("failed to decode CBOR format", { cause: ex });
|
|
95
94
|
}
|
|
96
95
|
}
|
|
97
|
-
else if (this.type === "json"
|
|
96
|
+
else if (this.type === "json") {
|
|
97
|
+
if (typeof data !== "string")
|
|
98
|
+
throw new Error("failed to decode JSON format (data type is not string)");
|
|
98
99
|
try {
|
|
99
100
|
result = JSONX.parse(data);
|
|
100
101
|
}
|
|
101
|
-
catch (
|
|
102
|
-
throw new Error("failed to decode JSON format");
|
|
102
|
+
catch (ex) {
|
|
103
|
+
throw new Error("failed to decode JSON format", { cause: ex });
|
|
103
104
|
}
|
|
104
105
|
}
|
|
105
106
|
else
|
|
106
|
-
throw new Error(
|
|
107
|
+
throw new Error(`invalid format "${this.type}"`);
|
|
107
108
|
return result;
|
|
108
109
|
}
|
|
109
110
|
}
|
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
22
|
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
23
|
*/
|
|
24
|
+
/* built-in requirements */
|
|
25
|
+
import { Buffer } from "node:buffer";
|
|
24
26
|
import { CodecTrait } from "./mqtt-plus-codec";
|
|
25
27
|
/* encoding trait */
|
|
26
28
|
export class EncodeTrait extends CodecTrait {
|
|
@@ -43,7 +45,7 @@ export class EncodeTrait extends CodecTrait {
|
|
|
43
45
|
}
|
|
44
46
|
buf2arr(data, cons) {
|
|
45
47
|
let arr;
|
|
46
|
-
if (cons === Buffer)
|
|
48
|
+
if (typeof Buffer !== "undefined" && cons === Buffer)
|
|
47
49
|
arr = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
48
50
|
else if (cons === Uint8Array)
|
|
49
51
|
arr = data;
|
|
@@ -44,6 +44,7 @@ export class EventTrait extends AuthTrait {
|
|
|
44
44
|
name = nameOrConfig.name;
|
|
45
45
|
callback = nameOrConfig.callback;
|
|
46
46
|
options = nameOrConfig.options ?? {};
|
|
47
|
+
share = nameOrConfig.share;
|
|
47
48
|
auth = nameOrConfig.auth;
|
|
48
49
|
}
|
|
49
50
|
else {
|
|
@@ -73,16 +74,17 @@ export class EventTrait extends AuthTrait {
|
|
|
73
74
|
auth
|
|
74
75
|
});
|
|
75
76
|
/* provide a registration for subsequent destruction */
|
|
76
|
-
const self = this;
|
|
77
77
|
const registration = {
|
|
78
|
-
async
|
|
79
|
-
if (!
|
|
78
|
+
destroy: async () => {
|
|
79
|
+
if (!this.events.has(name))
|
|
80
80
|
throw new Error(`destroy: event "${name}" not registered`);
|
|
81
|
-
|
|
81
|
+
this.events.delete(name);
|
|
82
82
|
return Promise.all([
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
]).then(() => { })
|
|
83
|
+
this._unsubscribeTopic(topicB),
|
|
84
|
+
this._unsubscribeTopic(topicD)
|
|
85
|
+
]).then(() => { }).catch((err) => {
|
|
86
|
+
this.error(err, `destroy: failed to unsubscribe from topics for event "${name}"`);
|
|
87
|
+
});
|
|
86
88
|
}
|
|
87
89
|
};
|
|
88
90
|
return registration;
|
|
@@ -110,11 +112,11 @@ export class EventTrait extends AuthTrait {
|
|
|
110
112
|
params = args;
|
|
111
113
|
}
|
|
112
114
|
/* generate unique request id */
|
|
113
|
-
const
|
|
115
|
+
const requestId = nanoid();
|
|
114
116
|
/* generate encoded message */
|
|
115
117
|
const auth = this.authenticate();
|
|
116
118
|
const metaStore = this.metaStore(meta);
|
|
117
|
-
const request = this.msg.makeEventEmission(
|
|
119
|
+
const request = this.msg.makeEventEmission(requestId, event, params, this.options.id, receiver, auth, metaStore);
|
|
118
120
|
const message = this.codec.encode(request);
|
|
119
121
|
/* generate corresponding MQTT topic */
|
|
120
122
|
const topic = this.options.topicMake(event, "event-emission", receiver);
|
|
@@ -124,17 +126,21 @@ export class EventTrait extends AuthTrait {
|
|
|
124
126
|
return { topic, payload: message, options: { qos: 0, ...options } };
|
|
125
127
|
else
|
|
126
128
|
/* publish message to MQTT topic */
|
|
127
|
-
this._publishToTopic(topic, message, { qos: 0, ...options }).catch(() => {
|
|
129
|
+
this._publishToTopic(topic, message, { qos: 0, ...options }).catch((err) => {
|
|
130
|
+
this.error(err, `emitting event "${event}" failed`);
|
|
131
|
+
});
|
|
128
132
|
}
|
|
129
133
|
/* dispatch message (Event pattern handling) */
|
|
130
134
|
async _dispatchMessage(topic, parsed) {
|
|
131
|
-
super._dispatchMessage(topic, parsed);
|
|
135
|
+
await super._dispatchMessage(topic, parsed);
|
|
132
136
|
const topicMatch = this.options.topicMatch(topic);
|
|
133
137
|
if (topicMatch !== null
|
|
134
138
|
&& topicMatch.operation === "event-emission"
|
|
135
139
|
&& parsed instanceof EventEmission) {
|
|
136
140
|
/* just deliver event */
|
|
137
141
|
const name = parsed.name;
|
|
142
|
+
if (topicMatch.name !== name)
|
|
143
|
+
throw new Error(`event name mismatch between topic "${topicMatch.name}" and payload "${name}"`);
|
|
138
144
|
const handler = this.events.get(name);
|
|
139
145
|
const params = parsed.params ?? [];
|
|
140
146
|
const info = { sender: parsed.sender ?? "" };
|
|
@@ -142,16 +148,17 @@ export class EventTrait extends AuthTrait {
|
|
|
142
148
|
info.receiver = parsed.receiver;
|
|
143
149
|
if (parsed.meta)
|
|
144
150
|
info.meta = parsed.meta;
|
|
145
|
-
if (handler
|
|
151
|
+
if (handler === undefined)
|
|
152
|
+
throw new Error(`handler for event "${name}" not found`);
|
|
153
|
+
if (handler.auth)
|
|
146
154
|
info.authenticated = await this.authenticated(parsed.sender, parsed.auth, handler.auth);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
});
|
|
155
|
+
Promise.resolve().then(() => {
|
|
156
|
+
if (info.authenticated !== undefined && !info.authenticated)
|
|
157
|
+
throw new Error(`authentication on event "${name}" failed`);
|
|
158
|
+
return handler.callback(...params, info);
|
|
159
|
+
}).catch((err) => {
|
|
160
|
+
this.error(err, `handler for event "${name}" failed`);
|
|
161
|
+
});
|
|
155
162
|
}
|
|
156
163
|
}
|
|
157
164
|
}
|
|
@@ -3,7 +3,7 @@ import { BaseTrait } from "./mqtt-plus-base";
|
|
|
3
3
|
export declare class MetaTrait<T extends APISchema = APISchema> extends BaseTrait<T> {
|
|
4
4
|
private _meta;
|
|
5
5
|
meta(): Record<string, any>;
|
|
6
|
-
meta(key: string):
|
|
6
|
+
meta(key: string): any;
|
|
7
7
|
meta(key: string, value: any): void;
|
|
8
8
|
protected metaStore(extra?: Record<string, any>): Record<string, any> | undefined;
|
|
9
9
|
}
|
|
@@ -32,6 +32,8 @@ export class MetaTrait extends BaseTrait {
|
|
|
32
32
|
meta(key, value) {
|
|
33
33
|
if (key === undefined)
|
|
34
34
|
return Object.fromEntries(this._meta);
|
|
35
|
+
else if (arguments.length === 1)
|
|
36
|
+
return this._meta.get(key);
|
|
35
37
|
else if (value === undefined || value === null)
|
|
36
38
|
this._meta.delete(key);
|
|
37
39
|
else
|