@clanker-chain/mqtt-node-client 0.0.1
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 +34 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +166 -0
- package/dist/types.d.ts +38 -0
- package/dist/types.js +1 -0
- package/package.json +18 -0
- package/src/index.ts +185 -0
- package/src/types.ts +41 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# mqtt-node-client
|
|
2
|
+
|
|
3
|
+
Thin MQTT client for clanker-chain bots. Connect with a JWT password (from identity_issue_mqtt_token), publish/subscribe using the topic layout in bot-comms.md, and poll for received messages.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
import { MqttClient, topicForInbox, topicForAnnounce } from "mqtt-node-client";
|
|
9
|
+
import { IdentityClient } from "identity-node-client";
|
|
10
|
+
|
|
11
|
+
const identity = new IdentityClient({ botId: "openclaw.france.prod-1", operatorId: "org.openclaw.pat" });
|
|
12
|
+
const mqtt = new MqttClient();
|
|
13
|
+
|
|
14
|
+
await mqtt.connect({
|
|
15
|
+
brokerUrl: "mqtt://localhost:1883",
|
|
16
|
+
clientId: "openclaw.france.prod-1",
|
|
17
|
+
getPassword: () => identity.issueMqttToken(300),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
await mqtt.subscribe(["bots/france-bot/inbox", "bots/all/announce"]);
|
|
21
|
+
mqtt.publishToInbox("tooter-bot", { type: "coordination", body: { action: "ping" } });
|
|
22
|
+
const messages = await mqtt.poll(500);
|
|
23
|
+
await mqtt.disconnect();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## API
|
|
27
|
+
|
|
28
|
+
- `connect(options)` — options: brokerUrl, clientId, getPassword (async), optional username.
|
|
29
|
+
- `publish(topic, payload, { qos, retain })`
|
|
30
|
+
- `publishToInbox(toBotName, envelope)`, `publishAnnounce(envelope)`
|
|
31
|
+
- `subscribe(topics[])`, `unsubscribe(topics[])`
|
|
32
|
+
- `poll(timeoutMs)` — returns received messages since last poll; waits up to timeoutMs if buffer empty.
|
|
33
|
+
- `disconnect()`
|
|
34
|
+
- Helpers: `topicForInbox(botName)`, `topicForAnnounce()`, `topicForDmCoordination(bot1, bot2)`, `topicForStatus(botName)`
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { MqttConnectOptions, MqttPublishOptions, ReceivedMessage } from "./types.js";
|
|
2
|
+
export type { MqttConnectOptions, MqttMessageEnvelope, MqttPublishOptions, ReceivedMessage } from "./types.js";
|
|
3
|
+
/** Topic for a bot's inbox (direct messages to that bot). */
|
|
4
|
+
export declare function topicForInbox(botName: string): string;
|
|
5
|
+
/** Topic for bot join/leave announcements. */
|
|
6
|
+
export declare function topicForAnnounce(): string;
|
|
7
|
+
/** Topic for private coordination between two bots (display names). */
|
|
8
|
+
export declare function topicForDmCoordination(bot1: string, bot2: string): string;
|
|
9
|
+
/** Topic for a bot's status (heartbeat, retained). */
|
|
10
|
+
export declare function topicForStatus(botName: string): string;
|
|
11
|
+
export declare class MqttClient {
|
|
12
|
+
private client;
|
|
13
|
+
private received;
|
|
14
|
+
/**
|
|
15
|
+
* Connect to the broker. Uses getPassword() for the CONNECT password (e.g. JWT from identity_issue_mqtt_token).
|
|
16
|
+
*/
|
|
17
|
+
connect(options: MqttConnectOptions): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Publish a payload (object or string) to a topic.
|
|
20
|
+
*/
|
|
21
|
+
publish(topic: string, payload: unknown, options?: MqttPublishOptions): void;
|
|
22
|
+
/**
|
|
23
|
+
* Publish to a bot's inbox (convenience).
|
|
24
|
+
*/
|
|
25
|
+
publishToInbox(toBotName: string, envelope: unknown): void;
|
|
26
|
+
/**
|
|
27
|
+
* Publish to bots/all/announce (convenience).
|
|
28
|
+
*/
|
|
29
|
+
publishAnnounce(envelope: unknown): void;
|
|
30
|
+
/**
|
|
31
|
+
* Subscribe to one or more topics.
|
|
32
|
+
*/
|
|
33
|
+
subscribe(topics: string[]): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Unsubscribe from one or more topics.
|
|
36
|
+
*/
|
|
37
|
+
unsubscribe(topics: string[]): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Return messages received since last poll and clear the buffer.
|
|
40
|
+
* Waits up to timeoutMs for at least one message if the buffer is empty.
|
|
41
|
+
*/
|
|
42
|
+
poll(timeoutMs?: number): Promise<ReceivedMessage[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Disconnect from the broker.
|
|
45
|
+
*/
|
|
46
|
+
disconnect(): Promise<void>;
|
|
47
|
+
get connected(): boolean;
|
|
48
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import mqtt from "mqtt";
|
|
2
|
+
/** Topic for a bot's inbox (direct messages to that bot). */
|
|
3
|
+
export function topicForInbox(botName) {
|
|
4
|
+
return `bots/${botName}/inbox`;
|
|
5
|
+
}
|
|
6
|
+
/** Topic for bot join/leave announcements. */
|
|
7
|
+
export function topicForAnnounce() {
|
|
8
|
+
return "bots/all/announce";
|
|
9
|
+
}
|
|
10
|
+
/** Topic for private coordination between two bots (display names). */
|
|
11
|
+
export function topicForDmCoordination(bot1, bot2) {
|
|
12
|
+
const segment = [bot1, bot2].sort().join("-");
|
|
13
|
+
return `dm/${segment}/coordination`;
|
|
14
|
+
}
|
|
15
|
+
/** Topic for a bot's status (heartbeat, retained). */
|
|
16
|
+
export function topicForStatus(botName) {
|
|
17
|
+
return `bots/${botName}/status`;
|
|
18
|
+
}
|
|
19
|
+
const defaultPublishOptions = { qos: 1, retain: false };
|
|
20
|
+
export class MqttClient {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.client = null;
|
|
23
|
+
this.received = [];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Connect to the broker. Uses getPassword() for the CONNECT password (e.g. JWT from identity_issue_mqtt_token).
|
|
27
|
+
*/
|
|
28
|
+
async connect(options) {
|
|
29
|
+
if (this.client) {
|
|
30
|
+
throw new Error("Already connected");
|
|
31
|
+
}
|
|
32
|
+
const password = await options.getPassword();
|
|
33
|
+
const username = options.username ?? options.clientId;
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const c = mqtt.connect(options.brokerUrl, {
|
|
36
|
+
clientId: options.clientId,
|
|
37
|
+
username,
|
|
38
|
+
password,
|
|
39
|
+
clean: false,
|
|
40
|
+
reconnectPeriod: 0,
|
|
41
|
+
});
|
|
42
|
+
c.on("message", (topic, payload) => {
|
|
43
|
+
let parsed;
|
|
44
|
+
try {
|
|
45
|
+
parsed = JSON.parse(payload.toString());
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
parsed = payload.toString();
|
|
49
|
+
}
|
|
50
|
+
this.received.push({
|
|
51
|
+
topic,
|
|
52
|
+
payload: parsed,
|
|
53
|
+
qos: 0,
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
c.once("connect", () => {
|
|
58
|
+
this.client = c;
|
|
59
|
+
resolve();
|
|
60
|
+
});
|
|
61
|
+
c.once("error", (err) => {
|
|
62
|
+
this.client = null;
|
|
63
|
+
reject(err);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Publish a payload (object or string) to a topic.
|
|
69
|
+
*/
|
|
70
|
+
publish(topic, payload, options = {}) {
|
|
71
|
+
if (!this.client?.connected) {
|
|
72
|
+
throw new Error("Not connected");
|
|
73
|
+
}
|
|
74
|
+
const opts = { ...defaultPublishOptions, ...options };
|
|
75
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
76
|
+
this.client.publish(topic, body, {
|
|
77
|
+
qos: opts.qos ?? 1,
|
|
78
|
+
retain: opts.retain ?? false,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Publish to a bot's inbox (convenience).
|
|
83
|
+
*/
|
|
84
|
+
publishToInbox(toBotName, envelope) {
|
|
85
|
+
this.publish(topicForInbox(toBotName), envelope, { qos: 1 });
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Publish to bots/all/announce (convenience).
|
|
89
|
+
*/
|
|
90
|
+
publishAnnounce(envelope) {
|
|
91
|
+
this.publish(topicForAnnounce(), envelope, { qos: 1 });
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Subscribe to one or more topics.
|
|
95
|
+
*/
|
|
96
|
+
subscribe(topics) {
|
|
97
|
+
if (!this.client?.connected) {
|
|
98
|
+
return Promise.reject(new Error("Not connected"));
|
|
99
|
+
}
|
|
100
|
+
return new Promise((resolve, reject) => {
|
|
101
|
+
this.client.subscribe(topics, (err) => {
|
|
102
|
+
if (err)
|
|
103
|
+
reject(err);
|
|
104
|
+
else
|
|
105
|
+
resolve();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Unsubscribe from one or more topics.
|
|
111
|
+
*/
|
|
112
|
+
unsubscribe(topics) {
|
|
113
|
+
if (!this.client?.connected) {
|
|
114
|
+
return Promise.reject(new Error("Not connected"));
|
|
115
|
+
}
|
|
116
|
+
return new Promise((resolve, reject) => {
|
|
117
|
+
this.client.unsubscribe(topics, (err) => {
|
|
118
|
+
if (err)
|
|
119
|
+
reject(err);
|
|
120
|
+
else
|
|
121
|
+
resolve();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Return messages received since last poll and clear the buffer.
|
|
127
|
+
* Waits up to timeoutMs for at least one message if the buffer is empty.
|
|
128
|
+
*/
|
|
129
|
+
poll(timeoutMs = 100) {
|
|
130
|
+
if (!this.client?.connected) {
|
|
131
|
+
return Promise.reject(new Error("Not connected"));
|
|
132
|
+
}
|
|
133
|
+
if (this.received.length > 0) {
|
|
134
|
+
const out = this.received;
|
|
135
|
+
this.received = [];
|
|
136
|
+
return Promise.resolve(out);
|
|
137
|
+
}
|
|
138
|
+
return new Promise((resolve) => {
|
|
139
|
+
const t = setTimeout(() => resolve([]), timeoutMs);
|
|
140
|
+
const handler = () => {
|
|
141
|
+
clearTimeout(t);
|
|
142
|
+
this.client.removeListener("message", handler);
|
|
143
|
+
const out = this.received;
|
|
144
|
+
this.received = [];
|
|
145
|
+
resolve(out);
|
|
146
|
+
};
|
|
147
|
+
this.client.once("message", handler);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Disconnect from the broker.
|
|
152
|
+
*/
|
|
153
|
+
async disconnect() {
|
|
154
|
+
if (!this.client)
|
|
155
|
+
return;
|
|
156
|
+
return new Promise((resolve) => {
|
|
157
|
+
this.client.end(false, {}, () => {
|
|
158
|
+
this.client = null;
|
|
159
|
+
resolve();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
get connected() {
|
|
164
|
+
return this.client?.connected ?? false;
|
|
165
|
+
}
|
|
166
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message envelope shape per bot-comms.md (subset used by the client).
|
|
3
|
+
*/
|
|
4
|
+
export interface MqttMessageEnvelope {
|
|
5
|
+
from?: string;
|
|
6
|
+
from_id?: string;
|
|
7
|
+
operator_id?: string;
|
|
8
|
+
to?: string;
|
|
9
|
+
to_id?: string;
|
|
10
|
+
type: string;
|
|
11
|
+
subtype?: string;
|
|
12
|
+
channel?: string;
|
|
13
|
+
timestamp?: string;
|
|
14
|
+
message_id?: string;
|
|
15
|
+
correlation_id?: string;
|
|
16
|
+
body?: unknown;
|
|
17
|
+
signature?: string;
|
|
18
|
+
signature_scheme?: string;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
export interface MqttConnectOptions {
|
|
22
|
+
brokerUrl: string;
|
|
23
|
+
clientId: string;
|
|
24
|
+
/** Called to get the MQTT password (e.g. JWT from identity_issue_mqtt_token). */
|
|
25
|
+
getPassword: () => Promise<string>;
|
|
26
|
+
/** Optional username; defaults to clientId. */
|
|
27
|
+
username?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface MqttPublishOptions {
|
|
30
|
+
qos?: 0 | 1 | 2;
|
|
31
|
+
retain?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export interface ReceivedMessage {
|
|
34
|
+
topic: string;
|
|
35
|
+
payload: unknown;
|
|
36
|
+
qos: number;
|
|
37
|
+
timestamp: string;
|
|
38
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clanker-chain/mqtt-node-client",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc -p tsconfig.json"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"mqtt": "^5.12.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"typescript": "^5.7.0",
|
|
16
|
+
"@types/node": "^22.10.2"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import mqtt from "mqtt";
|
|
2
|
+
import type {
|
|
3
|
+
MqttConnectOptions,
|
|
4
|
+
MqttPublishOptions,
|
|
5
|
+
ReceivedMessage,
|
|
6
|
+
} from "./types.js";
|
|
7
|
+
|
|
8
|
+
export type { MqttConnectOptions, MqttMessageEnvelope, MqttPublishOptions, ReceivedMessage } from "./types.js";
|
|
9
|
+
|
|
10
|
+
/** Topic for a bot's inbox (direct messages to that bot). */
|
|
11
|
+
export function topicForInbox(botName: string): string {
|
|
12
|
+
return `bots/${botName}/inbox`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Topic for bot join/leave announcements. */
|
|
16
|
+
export function topicForAnnounce(): string {
|
|
17
|
+
return "bots/all/announce";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Topic for private coordination between two bots (display names). */
|
|
21
|
+
export function topicForDmCoordination(bot1: string, bot2: string): string {
|
|
22
|
+
const segment = [bot1, bot2].sort().join("-");
|
|
23
|
+
return `dm/${segment}/coordination`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Topic for a bot's status (heartbeat, retained). */
|
|
27
|
+
export function topicForStatus(botName: string): string {
|
|
28
|
+
return `bots/${botName}/status`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const defaultPublishOptions: MqttPublishOptions = { qos: 1, retain: false };
|
|
32
|
+
|
|
33
|
+
export class MqttClient {
|
|
34
|
+
private client: mqtt.MqttClient | null = null;
|
|
35
|
+
private received: ReceivedMessage[] = [];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Connect to the broker. Uses getPassword() for the CONNECT password (e.g. JWT from identity_issue_mqtt_token).
|
|
39
|
+
*/
|
|
40
|
+
async connect(options: MqttConnectOptions): Promise<void> {
|
|
41
|
+
if (this.client) {
|
|
42
|
+
throw new Error("Already connected");
|
|
43
|
+
}
|
|
44
|
+
const password = await options.getPassword();
|
|
45
|
+
const username = options.username ?? options.clientId;
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const c = mqtt.connect(options.brokerUrl, {
|
|
48
|
+
clientId: options.clientId,
|
|
49
|
+
username,
|
|
50
|
+
password,
|
|
51
|
+
clean: false,
|
|
52
|
+
reconnectPeriod: 0,
|
|
53
|
+
});
|
|
54
|
+
c.on("message", (topic: string, payload: Buffer) => {
|
|
55
|
+
let parsed: unknown;
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(payload.toString());
|
|
58
|
+
} catch {
|
|
59
|
+
parsed = payload.toString();
|
|
60
|
+
}
|
|
61
|
+
this.received.push({
|
|
62
|
+
topic,
|
|
63
|
+
payload: parsed,
|
|
64
|
+
qos: 0,
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
c.once("connect", () => {
|
|
69
|
+
this.client = c;
|
|
70
|
+
resolve();
|
|
71
|
+
});
|
|
72
|
+
c.once("error", (err) => {
|
|
73
|
+
this.client = null;
|
|
74
|
+
reject(err);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Publish a payload (object or string) to a topic.
|
|
81
|
+
*/
|
|
82
|
+
publish(
|
|
83
|
+
topic: string,
|
|
84
|
+
payload: unknown,
|
|
85
|
+
options: MqttPublishOptions = {}
|
|
86
|
+
): void {
|
|
87
|
+
if (!this.client?.connected) {
|
|
88
|
+
throw new Error("Not connected");
|
|
89
|
+
}
|
|
90
|
+
const opts = { ...defaultPublishOptions, ...options };
|
|
91
|
+
const body =
|
|
92
|
+
typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
93
|
+
this.client.publish(topic, body, {
|
|
94
|
+
qos: opts.qos ?? 1,
|
|
95
|
+
retain: opts.retain ?? false,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Publish to a bot's inbox (convenience).
|
|
101
|
+
*/
|
|
102
|
+
publishToInbox(toBotName: string, envelope: unknown): void {
|
|
103
|
+
this.publish(topicForInbox(toBotName), envelope, { qos: 1 });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Publish to bots/all/announce (convenience).
|
|
108
|
+
*/
|
|
109
|
+
publishAnnounce(envelope: unknown): void {
|
|
110
|
+
this.publish(topicForAnnounce(), envelope, { qos: 1 });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Subscribe to one or more topics.
|
|
115
|
+
*/
|
|
116
|
+
subscribe(topics: string[]): Promise<void> {
|
|
117
|
+
if (!this.client?.connected) {
|
|
118
|
+
return Promise.reject(new Error("Not connected"));
|
|
119
|
+
}
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
this.client!.subscribe(topics, (err) => {
|
|
122
|
+
if (err) reject(err);
|
|
123
|
+
else resolve();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Unsubscribe from one or more topics.
|
|
130
|
+
*/
|
|
131
|
+
unsubscribe(topics: string[]): Promise<void> {
|
|
132
|
+
if (!this.client?.connected) {
|
|
133
|
+
return Promise.reject(new Error("Not connected"));
|
|
134
|
+
}
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
this.client!.unsubscribe(topics, (err) => {
|
|
137
|
+
if (err) reject(err);
|
|
138
|
+
else resolve();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Return messages received since last poll and clear the buffer.
|
|
145
|
+
* Waits up to timeoutMs for at least one message if the buffer is empty.
|
|
146
|
+
*/
|
|
147
|
+
poll(timeoutMs: number = 100): Promise<ReceivedMessage[]> {
|
|
148
|
+
if (!this.client?.connected) {
|
|
149
|
+
return Promise.reject(new Error("Not connected"));
|
|
150
|
+
}
|
|
151
|
+
if (this.received.length > 0) {
|
|
152
|
+
const out = this.received;
|
|
153
|
+
this.received = [];
|
|
154
|
+
return Promise.resolve(out);
|
|
155
|
+
}
|
|
156
|
+
return new Promise((resolve) => {
|
|
157
|
+
const t = setTimeout(() => resolve([]), timeoutMs);
|
|
158
|
+
const handler = () => {
|
|
159
|
+
clearTimeout(t);
|
|
160
|
+
this.client!.removeListener("message", handler);
|
|
161
|
+
const out = this.received;
|
|
162
|
+
this.received = [];
|
|
163
|
+
resolve(out);
|
|
164
|
+
};
|
|
165
|
+
this.client!.once("message", handler);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Disconnect from the broker.
|
|
171
|
+
*/
|
|
172
|
+
async disconnect(): Promise<void> {
|
|
173
|
+
if (!this.client) return;
|
|
174
|
+
return new Promise((resolve) => {
|
|
175
|
+
this.client!.end(false, {}, () => {
|
|
176
|
+
this.client = null;
|
|
177
|
+
resolve();
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
get connected(): boolean {
|
|
183
|
+
return this.client?.connected ?? false;
|
|
184
|
+
}
|
|
185
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message envelope shape per bot-comms.md (subset used by the client).
|
|
3
|
+
*/
|
|
4
|
+
export interface MqttMessageEnvelope {
|
|
5
|
+
from?: string;
|
|
6
|
+
from_id?: string;
|
|
7
|
+
operator_id?: string;
|
|
8
|
+
to?: string;
|
|
9
|
+
to_id?: string;
|
|
10
|
+
type: string;
|
|
11
|
+
subtype?: string;
|
|
12
|
+
channel?: string;
|
|
13
|
+
timestamp?: string;
|
|
14
|
+
message_id?: string;
|
|
15
|
+
correlation_id?: string;
|
|
16
|
+
body?: unknown;
|
|
17
|
+
signature?: string;
|
|
18
|
+
signature_scheme?: string;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MqttConnectOptions {
|
|
23
|
+
brokerUrl: string;
|
|
24
|
+
clientId: string;
|
|
25
|
+
/** Called to get the MQTT password (e.g. JWT from identity_issue_mqtt_token). */
|
|
26
|
+
getPassword: () => Promise<string>;
|
|
27
|
+
/** Optional username; defaults to clientId. */
|
|
28
|
+
username?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MqttPublishOptions {
|
|
32
|
+
qos?: 0 | 1 | 2;
|
|
33
|
+
retain?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ReceivedMessage {
|
|
37
|
+
topic: string;
|
|
38
|
+
payload: unknown;
|
|
39
|
+
qos: number;
|
|
40
|
+
timestamp: string;
|
|
41
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"types": ["node"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*.ts"]
|
|
15
|
+
}
|