@seriousme/opifex 1.7.1 → 1.8.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 +17 -2
- package/dist/mqttConn/mqttConn.d.ts +45 -2
- package/dist/mqttConn/mqttConn.d.ts.map +1 -1
- package/dist/mqttConn/mqttConn.js +48 -2
- package/dist/mqttConn/mqttConn.js.map +1 -1
- package/dist/persistence/persistence.d.ts +59 -2
- package/dist/persistence/persistence.d.ts.map +1 -1
- package/examples/tcpServer.test.ts +149 -0
- package/mqttConn/mqttConn.ts +58 -2
- package/package.json +13 -8
- package/persistence/persistence.ts +66 -2
package/README.md
CHANGED
|
@@ -13,8 +13,7 @@ persistence.
|
|
|
13
13
|
|
|
14
14
|
## Example
|
|
15
15
|
|
|
16
|
-
A simple server example using Deno
|
|
17
|
-
section below.
|
|
16
|
+
A simple server example using Deno.
|
|
18
17
|
|
|
19
18
|
```typescript
|
|
20
19
|
import { MqttServer } from "./server/mod.ts";
|
|
@@ -26,6 +25,9 @@ for await (const conn of this.listener) {
|
|
|
26
25
|
}
|
|
27
26
|
```
|
|
28
27
|
|
|
28
|
+
A more elaborate example including client and server can be found in the
|
|
29
|
+
[examples](/examples/) folder.
|
|
30
|
+
|
|
29
31
|
## Architecture
|
|
30
32
|
|
|
31
33
|
1. The basis of Opifex is the MQTT packet module
|
|
@@ -86,6 +88,19 @@ inspiration.
|
|
|
86
88
|
|
|
87
89
|
Bun (as of version 1.2) is capable of running the NodeJS version.
|
|
88
90
|
|
|
91
|
+
## Exports
|
|
92
|
+
|
|
93
|
+
| Export | Description |
|
|
94
|
+
| ---------------------------- | ------------------------------------------------------- |
|
|
95
|
+
| @seriousme/opifex/tcpClient | Exports a MQTT over TCP client |
|
|
96
|
+
| @seriousme/opifex/tcpServer | Exports a MQTT over TCP server |
|
|
97
|
+
| @seriousme/opifex/server | Exports a transport agnostic MQTT server |
|
|
98
|
+
| @seriousme/opifex/client | Exports a transport agnostic MQTT client |
|
|
99
|
+
| @seriousme/opifex/client | Exports an Typescript interface for storage persistence |
|
|
100
|
+
| @seriousme/opifex/mqttConn | Exports MQTT connection handling |
|
|
101
|
+
| @seriousme/opifex/mqttPacket | Exports MQTT packet handling |
|
|
102
|
+
| @seriousme/opifex/utils | Exports various utilities |
|
|
103
|
+
|
|
89
104
|
## Naming
|
|
90
105
|
|
|
91
106
|
Some MQTT servers have names like:
|
|
@@ -1,36 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module mqttConn
|
|
3
|
+
*/
|
|
1
4
|
import { type AnyPacket } from "../mqttPacket/mod.ts";
|
|
2
5
|
import type { SockConn } from "../socket/socket.ts";
|
|
3
6
|
import { Conn } from "../socket/socket.ts";
|
|
7
|
+
/**
|
|
8
|
+
* Common MQTT connection error messages
|
|
9
|
+
*/
|
|
4
10
|
export declare const MqttConnError: {
|
|
5
11
|
readonly invalidPacket: "Invalid Packet";
|
|
6
12
|
readonly packetTooLarge: "Packet too large";
|
|
7
13
|
readonly UnexpectedEof: "Unexpected EOF";
|
|
8
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Interface for MQTT connection handling
|
|
17
|
+
*/
|
|
9
18
|
export interface IMqttConn extends AsyncIterable<AnyPacket> {
|
|
19
|
+
/** Underlying connection */
|
|
10
20
|
readonly conn: Conn;
|
|
21
|
+
/** Whether connection is closed */
|
|
11
22
|
readonly isClosed: boolean;
|
|
23
|
+
/** Reason for connection closure if any */
|
|
12
24
|
readonly reason: string | undefined;
|
|
25
|
+
/** Async iterator for receiving packets */
|
|
13
26
|
[Symbol.asyncIterator](): AsyncIterableIterator<AnyPacket>;
|
|
27
|
+
/** Send an MQTT packet */
|
|
14
28
|
send(data: AnyPacket): Promise<void>;
|
|
29
|
+
/** Close the connection */
|
|
15
30
|
close(): void;
|
|
16
31
|
}
|
|
17
|
-
/**
|
|
18
|
-
*
|
|
32
|
+
/**
|
|
33
|
+
* Read a complete MQTT packet from the connection
|
|
34
|
+
* @param conn Connection to read from
|
|
35
|
+
* @param maxPacketSize Maximum allowed packet size
|
|
36
|
+
* @returns Decoded MQTT packet
|
|
37
|
+
* @throws Error if packet is invalid or too large
|
|
19
38
|
*/
|
|
20
39
|
export declare function readPacket(conn: Conn, maxPacketSize: number): Promise<AnyPacket>;
|
|
40
|
+
/**
|
|
41
|
+
* MQTT Connection class implementing IMqttConn interface
|
|
42
|
+
*/
|
|
21
43
|
export declare class MqttConn implements IMqttConn {
|
|
44
|
+
/** Underlying connection */
|
|
22
45
|
readonly conn: Conn;
|
|
46
|
+
/** Maximum allowed packet size */
|
|
23
47
|
private readonly maxPacketSize;
|
|
48
|
+
/** Reason for connection closure if any */
|
|
24
49
|
private _reason;
|
|
50
|
+
/** Whether connection is closed */
|
|
25
51
|
private _isClosed;
|
|
52
|
+
/**
|
|
53
|
+
* Create new MQTT connection
|
|
54
|
+
* @param options Connection options
|
|
55
|
+
* @param options.conn Underlying socket connection
|
|
56
|
+
* @param options.maxPacketSize Maximum allowed packet size (default 2MB)
|
|
57
|
+
*/
|
|
26
58
|
constructor({ conn, maxPacketSize, }: {
|
|
27
59
|
conn: SockConn;
|
|
28
60
|
maxPacketSize?: number;
|
|
29
61
|
});
|
|
62
|
+
/** Get reason for connection closure */
|
|
30
63
|
get reason(): string | undefined;
|
|
64
|
+
/**
|
|
65
|
+
* Async iterator for receiving packets
|
|
66
|
+
* @yields MQTT packets
|
|
67
|
+
*/
|
|
31
68
|
[Symbol.asyncIterator](): AsyncIterableIterator<AnyPacket>;
|
|
69
|
+
/**
|
|
70
|
+
* Send an MQTT packet
|
|
71
|
+
* @param data Packet to send
|
|
72
|
+
*/
|
|
32
73
|
send(data: AnyPacket): Promise<void>;
|
|
74
|
+
/** Whether connection is closed */
|
|
33
75
|
get isClosed(): boolean;
|
|
76
|
+
/** Close the connection */
|
|
34
77
|
close(): void;
|
|
35
78
|
}
|
|
36
79
|
//# sourceMappingURL=mqttConn.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mqttConn.d.ts","sourceRoot":"","sources":["../../mqttConn/mqttConn.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,SAAS,EAKf,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAE3C,eAAO,MAAM,aAAa;;;;CAIhB,CAAC;AAEX,MAAM,WAAW,SAAU,SAAQ,aAAa,CAAC,SAAS,CAAC;IACzD,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAC3D,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,KAAK,IAAI,IAAI,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"mqttConn.d.ts","sourceRoot":"","sources":["../../mqttConn/mqttConn.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,KAAK,SAAS,EAKf,MAAM,sBAAsB,CAAC;AAG9B,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAE3C;;GAEG;AACH,eAAO,MAAM,aAAa;;;;CAIhB,CAAC;AAEX;;GAEG;AACH,MAAM,WAAW,SAAU,SAAQ,aAAa,CAAC,SAAS,CAAC;IACzD,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,2CAA2C;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,2CAA2C;IAC3C,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,SAAS,CAAC,CAAC;IAC3D,0BAA0B;IAC1B,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,2BAA2B;IAC3B,KAAK,IAAI,IAAI,CAAC;CACf;AAgCD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,IAAI,EACV,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,SAAS,CAAC,CAmBpB;AAED;;GAEG;AACH,qBAAa,QAAS,YAAW,SAAS;IACxC,4BAA4B;IAC5B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,kCAAkC;IAClC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,2CAA2C;IAC3C,OAAO,CAAC,OAAO,CAAiC;IAChD,mCAAmC;IACnC,OAAO,CAAC,SAAS,CAAS;IAE1B;;;;;OAKG;gBACS,EACV,IAAI,EACJ,aAAa,GACd,EAAE;QACD,IAAI,EAAE,QAAQ,CAAC;QACf,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB;IAKD,wCAAwC;IACxC,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;IAED;;;OAGG;IACI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,SAAS,CAAC;IAkBjE;;;OAGG;IACG,IAAI,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAW1C,mCAAmC;IACnC,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,2BAA2B;IAC3B,KAAK,IAAI,IAAI;CAUd"}
|
|
@@ -1,11 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module mqttConn
|
|
3
|
+
*/
|
|
1
4
|
import { decodePayload, encode, getLengthDecoder, } from "../mqttPacket/mod.js";
|
|
2
5
|
import { assert } from "../utils/mod.js";
|
|
3
6
|
import { Conn } from "../socket/socket.js";
|
|
7
|
+
/**
|
|
8
|
+
* Common MQTT connection error messages
|
|
9
|
+
*/
|
|
4
10
|
export const MqttConnError = {
|
|
5
11
|
invalidPacket: "Invalid Packet",
|
|
6
12
|
packetTooLarge: "Packet too large",
|
|
7
13
|
UnexpectedEof: "Unexpected EOF",
|
|
8
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Read a single byte from the connection
|
|
17
|
+
* @param conn Connection to read from
|
|
18
|
+
* @returns Single byte as number
|
|
19
|
+
* @throws Error if EOF reached unexpectedly
|
|
20
|
+
*/
|
|
9
21
|
async function readByte(conn) {
|
|
10
22
|
const buf = new Uint8Array(1);
|
|
11
23
|
const bytesRead = await conn.read(buf);
|
|
@@ -13,6 +25,12 @@ async function readByte(conn) {
|
|
|
13
25
|
assert(bytesRead !== 0, MqttConnError.UnexpectedEof);
|
|
14
26
|
return buf[0];
|
|
15
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Read exact number of bytes into buffer
|
|
30
|
+
* @param conn Connection to read from
|
|
31
|
+
* @param buf Buffer to read into
|
|
32
|
+
* @throws Error if EOF reached unexpectedly
|
|
33
|
+
*/
|
|
16
34
|
async function readFull(conn, buf) {
|
|
17
35
|
let bytesRead = 0;
|
|
18
36
|
while (bytesRead < buf.length) {
|
|
@@ -22,8 +40,12 @@ async function readFull(conn, buf) {
|
|
|
22
40
|
bytesRead += read;
|
|
23
41
|
}
|
|
24
42
|
}
|
|
25
|
-
/**
|
|
26
|
-
*
|
|
43
|
+
/**
|
|
44
|
+
* Read a complete MQTT packet from the connection
|
|
45
|
+
* @param conn Connection to read from
|
|
46
|
+
* @param maxPacketSize Maximum allowed packet size
|
|
47
|
+
* @returns Decoded MQTT packet
|
|
48
|
+
* @throws Error if packet is invalid or too large
|
|
27
49
|
*/
|
|
28
50
|
export async function readPacket(conn, maxPacketSize) {
|
|
29
51
|
// fixed header is 1 byte of type + flags
|
|
@@ -44,18 +66,36 @@ export async function readPacket(conn, maxPacketSize) {
|
|
|
44
66
|
assert(packet !== null, MqttConnError.UnexpectedEof);
|
|
45
67
|
return packet;
|
|
46
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* MQTT Connection class implementing IMqttConn interface
|
|
71
|
+
*/
|
|
47
72
|
export class MqttConn {
|
|
73
|
+
/** Underlying connection */
|
|
48
74
|
conn;
|
|
75
|
+
/** Maximum allowed packet size */
|
|
49
76
|
maxPacketSize;
|
|
77
|
+
/** Reason for connection closure if any */
|
|
50
78
|
_reason = undefined;
|
|
79
|
+
/** Whether connection is closed */
|
|
51
80
|
_isClosed = false;
|
|
81
|
+
/**
|
|
82
|
+
* Create new MQTT connection
|
|
83
|
+
* @param options Connection options
|
|
84
|
+
* @param options.conn Underlying socket connection
|
|
85
|
+
* @param options.maxPacketSize Maximum allowed packet size (default 2MB)
|
|
86
|
+
*/
|
|
52
87
|
constructor({ conn, maxPacketSize, }) {
|
|
53
88
|
this.conn = new Conn(conn);
|
|
54
89
|
this.maxPacketSize = maxPacketSize || 2 * 1024 * 1024;
|
|
55
90
|
}
|
|
91
|
+
/** Get reason for connection closure */
|
|
56
92
|
get reason() {
|
|
57
93
|
return this._reason;
|
|
58
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* Async iterator for receiving packets
|
|
97
|
+
* @yields MQTT packets
|
|
98
|
+
*/
|
|
59
99
|
async *[Symbol.asyncIterator]() {
|
|
60
100
|
while (!this._isClosed) {
|
|
61
101
|
try {
|
|
@@ -74,6 +114,10 @@ export class MqttConn {
|
|
|
74
114
|
}
|
|
75
115
|
}
|
|
76
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Send an MQTT packet
|
|
119
|
+
* @param data Packet to send
|
|
120
|
+
*/
|
|
77
121
|
async send(data) {
|
|
78
122
|
try {
|
|
79
123
|
await this.conn.write(encode(data));
|
|
@@ -85,9 +129,11 @@ export class MqttConn {
|
|
|
85
129
|
this.close();
|
|
86
130
|
}
|
|
87
131
|
}
|
|
132
|
+
/** Whether connection is closed */
|
|
88
133
|
get isClosed() {
|
|
89
134
|
return this._isClosed;
|
|
90
135
|
}
|
|
136
|
+
/** Close the connection */
|
|
91
137
|
close() {
|
|
92
138
|
if (this.isClosed)
|
|
93
139
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mqttConn.js","sourceRoot":"","sources":["../../mqttConn/mqttConn.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EACb,MAAM,EACN,gBAAgB,GAEjB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAE3C,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,aAAa,EAAE,gBAAgB;IAC/B,cAAc,EAAE,kBAAkB;IAClC,aAAa,EAAE,gBAAgB;CACvB,CAAC;
|
|
1
|
+
{"version":3,"file":"mqttConn.js","sourceRoot":"","sources":["../../mqttConn/mqttConn.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,24 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module persistence
|
|
3
|
+
* @description Module for handling MQTT message persistence and client management
|
|
4
|
+
*/
|
|
1
5
|
import type { ClientId, PublishPacket, QoS, Topic } from "./deps.ts";
|
|
2
6
|
import type { IStore } from "./store.ts";
|
|
7
|
+
/**
|
|
8
|
+
* Handler function type for processing publish packets
|
|
9
|
+
* @callback Handler
|
|
10
|
+
* @param {PublishPacket} packet - The MQTT publish packet to handle
|
|
11
|
+
*/
|
|
3
12
|
export type Handler = (packet: PublishPacket) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Store type for retained messages mapped by topic
|
|
15
|
+
* @typedef {Map<Topic, PublishPacket>} RetainStore
|
|
16
|
+
*/
|
|
4
17
|
export type RetainStore = Map<Topic, PublishPacket>;
|
|
18
|
+
/**
|
|
19
|
+
* Client type containing message store and packet handler
|
|
20
|
+
* @typedef {Object} Client
|
|
21
|
+
* @property {IStore} store - The client's message store
|
|
22
|
+
* @property {Handler} handler - The client's packet handler function
|
|
23
|
+
*/
|
|
5
24
|
export type Client = {
|
|
6
25
|
store: IStore;
|
|
7
26
|
handler: Handler;
|
|
8
27
|
};
|
|
9
28
|
/**
|
|
10
|
-
*
|
|
11
|
-
* to store messages and subscriptions
|
|
29
|
+
* Interface for persistence implementations to store messages and subscriptions
|
|
12
30
|
* @interface IPersistence
|
|
13
31
|
*/
|
|
14
32
|
export interface IPersistence {
|
|
33
|
+
/**
|
|
34
|
+
* Map of connected clients by client ID
|
|
35
|
+
* @type {Map<ClientId, Client>}
|
|
36
|
+
*/
|
|
15
37
|
clientList: Map<ClientId, Client>;
|
|
38
|
+
/**
|
|
39
|
+
* Map of retained messages by topic
|
|
40
|
+
* @type {RetainStore}
|
|
41
|
+
*/
|
|
16
42
|
retained: RetainStore;
|
|
43
|
+
/**
|
|
44
|
+
* Register a new client with the persistence layer
|
|
45
|
+
* @param {ClientId} clientId - Unique identifier for the client
|
|
46
|
+
* @param {Handler} handler - Packet handler function for the client
|
|
47
|
+
* @param {boolean} clean - Whether to start with a clean session
|
|
48
|
+
* @returns {IStore} The client's message store
|
|
49
|
+
*/
|
|
17
50
|
registerClient(clientId: ClientId, handler: Handler, clean: boolean): IStore;
|
|
51
|
+
/**
|
|
52
|
+
* Remove a client from the persistence layer
|
|
53
|
+
* @param {ClientId} clientId - ID of client to deregister
|
|
54
|
+
*/
|
|
18
55
|
deregisterClient(clientId: ClientId): void;
|
|
56
|
+
/**
|
|
57
|
+
* Publish a message to all subscribed clients
|
|
58
|
+
* @param {Topic} topic - Topic to publish to
|
|
59
|
+
* @param {PublishPacket} packet - Packet containing the message
|
|
60
|
+
*/
|
|
19
61
|
publish(topic: Topic, packet: PublishPacket): void;
|
|
62
|
+
/**
|
|
63
|
+
* Subscribe a client to a topic
|
|
64
|
+
* @param {IStore} store - Client's message store
|
|
65
|
+
* @param {Topic} topic - Topic to subscribe to
|
|
66
|
+
* @param {QoS} qos - Quality of Service level
|
|
67
|
+
*/
|
|
20
68
|
subscribe(store: IStore, topic: Topic, qos: QoS): void;
|
|
69
|
+
/**
|
|
70
|
+
* Unsubscribe a client from a topic
|
|
71
|
+
* @param {IStore} store - Client's message store
|
|
72
|
+
* @param {Topic} topic - Topic to unsubscribe from
|
|
73
|
+
*/
|
|
21
74
|
unsubscribe(store: IStore, topic: Topic): void;
|
|
75
|
+
/**
|
|
76
|
+
* Send retained messages to a client
|
|
77
|
+
* @param {ClientId} clientId - ID of client to send retained messages to
|
|
78
|
+
*/
|
|
22
79
|
handleRetained(clientId: ClientId): void;
|
|
23
80
|
}
|
|
24
81
|
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../persistence/persistence.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;AAEtD,MAAM,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;AAEpD,MAAM,MAAM,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzD
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../../persistence/persistence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC;;;;GAIG;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;AAEtD;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,MAAM,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzD;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,UAAU,EAAE,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAElC;;;OAGG;IACH,QAAQ,EAAE,WAAW,CAAC;IAEtB;;;;;;OAMG;IACH,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC;IAE7E;;;OAGG;IACH,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAE3C;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IAEnD;;;;;OAKG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC;IAEvD;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IAE/C;;;OAGG;IACH,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;CAC1C"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { TcpClient } from "@seriousme/opifex/tcpClient";
|
|
4
|
+
import { TcpServer } from "@seriousme/opifex/tcpServer";
|
|
5
|
+
import { logger, LogLevel } from "@seriousme/opifex/utils";
|
|
6
|
+
import type { PublishPacket, QoS } from "@seriousme/opifex/mqttPacket";
|
|
7
|
+
|
|
8
|
+
logger.level(LogLevel.info);
|
|
9
|
+
export function sleep(ms: number): Promise<unknown> {
|
|
10
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
test("Test pubSub using client and server", async function () {
|
|
14
|
+
const server = new TcpServer({ port: 0 }, {});
|
|
15
|
+
server.start();
|
|
16
|
+
|
|
17
|
+
assert.deepStrictEqual(
|
|
18
|
+
server.port !== undefined,
|
|
19
|
+
true,
|
|
20
|
+
"server runs a a random port",
|
|
21
|
+
);
|
|
22
|
+
logger.verbose({ port: server.port, address: server.address });
|
|
23
|
+
|
|
24
|
+
const params = {
|
|
25
|
+
url: new URL(`mqtt://${server.address}:${server.port}`),
|
|
26
|
+
numberOfRetries: 0,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const client = new TcpClient();
|
|
30
|
+
|
|
31
|
+
await client.connect(params);
|
|
32
|
+
assert(true, "Client connected to server");
|
|
33
|
+
|
|
34
|
+
const publishSet: { topic: string; qos: QoS }[] = [
|
|
35
|
+
{ topic: "t0@q0", qos: 0 },
|
|
36
|
+
{ topic: "t1@q0", qos: 0 },
|
|
37
|
+
{ topic: "t2@q0", qos: 0 },
|
|
38
|
+
{ topic: "t0@q1", qos: 1 },
|
|
39
|
+
{ topic: "t1@q1", qos: 1 },
|
|
40
|
+
{ topic: "t2@q1", qos: 1 },
|
|
41
|
+
{ topic: "t0@q2", qos: 2 },
|
|
42
|
+
{ topic: "t1@q2", qos: 2 },
|
|
43
|
+
{ topic: "t2@q2", qos: 2 },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const subscriptions = publishSet.map((item) => ({
|
|
47
|
+
topicFilter: item.topic,
|
|
48
|
+
qos: item.qos,
|
|
49
|
+
}));
|
|
50
|
+
await client.subscribe({
|
|
51
|
+
subscriptions,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// the IIFE ensures message reception runs in parallel
|
|
55
|
+
logger.verbose(`Start receiving`);
|
|
56
|
+
const received: PublishPacket[] = [];
|
|
57
|
+
(async function () {
|
|
58
|
+
for await (const item of client.messages()) {
|
|
59
|
+
logger.verbose(`Receiving: ${item.topic} -- ${item.qos}`);
|
|
60
|
+
received.push(item);
|
|
61
|
+
}
|
|
62
|
+
})();
|
|
63
|
+
// end of IIFE
|
|
64
|
+
for (const item of publishSet) {
|
|
65
|
+
logger.verbose(`Publishing: ${item.topic} -- ${item.qos}`);
|
|
66
|
+
await client.publish({
|
|
67
|
+
topic: item.topic,
|
|
68
|
+
qos: item.qos,
|
|
69
|
+
payload: new Uint8Array([0x01]),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
await sleep(100);
|
|
74
|
+
logger.verbose(`Disconnect client`);
|
|
75
|
+
//client.closeMessages();
|
|
76
|
+
await client.disconnect();
|
|
77
|
+
|
|
78
|
+
logger.verbose(`Check completeness`);
|
|
79
|
+
for (const item of publishSet) {
|
|
80
|
+
const found = received.find((f) =>
|
|
81
|
+
f.topic == item.topic && f.qos === item.qos
|
|
82
|
+
);
|
|
83
|
+
logger.verbose(`Found: ${item.topic} -- ${item.qos}`);
|
|
84
|
+
assert(found, `${item.topic} -- ${item.qos}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
logger.verbose(`Stop server`);
|
|
88
|
+
server.stop();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("Test subscription persistence after reconnect", async function () {
|
|
92
|
+
// Start server
|
|
93
|
+
const server = new TcpServer({ port: 0 }, {});
|
|
94
|
+
server.start();
|
|
95
|
+
|
|
96
|
+
const params = {
|
|
97
|
+
url: new URL(`mqtt://${server.address}:${server.port}`),
|
|
98
|
+
numberOfRetries: 0,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const client = new TcpClient();
|
|
102
|
+
const testTopic = "test/topic";
|
|
103
|
+
const received: PublishPacket[] = [];
|
|
104
|
+
|
|
105
|
+
// First connection and subscription
|
|
106
|
+
await client.connect(params);
|
|
107
|
+
await client.subscribe({
|
|
108
|
+
subscriptions: [{
|
|
109
|
+
topicFilter: testTopic,
|
|
110
|
+
qos: 0,
|
|
111
|
+
}],
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Start receiving messages
|
|
115
|
+
(async function () {
|
|
116
|
+
for await (const item of client.messages()) {
|
|
117
|
+
received.push(item);
|
|
118
|
+
}
|
|
119
|
+
})();
|
|
120
|
+
|
|
121
|
+
// Disconnect client
|
|
122
|
+
await client.disconnect();
|
|
123
|
+
await sleep(100);
|
|
124
|
+
|
|
125
|
+
// Reconnect client
|
|
126
|
+
await client.connect(params);
|
|
127
|
+
await sleep(100);
|
|
128
|
+
|
|
129
|
+
// Publish test message
|
|
130
|
+
await client.publish({
|
|
131
|
+
topic: testTopic,
|
|
132
|
+
qos: 0,
|
|
133
|
+
payload: new Uint8Array([0x01]),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await sleep(100);
|
|
137
|
+
logger.verbose(`Disconnect client`);
|
|
138
|
+
await client.disconnect();
|
|
139
|
+
|
|
140
|
+
// Verify message was received
|
|
141
|
+
assert.equal(received.length, 1, "Should receive one message");
|
|
142
|
+
assert.equal(
|
|
143
|
+
received[0].topic,
|
|
144
|
+
testTopic,
|
|
145
|
+
"Should receive message on subscribed topic",
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
server.stop();
|
|
149
|
+
});
|
package/mqttConn/mqttConn.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module mqttConn
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import {
|
|
2
6
|
type AnyPacket,
|
|
3
7
|
decodePayload,
|
|
@@ -10,21 +14,39 @@ import { assert } from "../utils/mod.ts";
|
|
|
10
14
|
import type { SockConn } from "../socket/socket.ts";
|
|
11
15
|
import { Conn } from "../socket/socket.ts";
|
|
12
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Common MQTT connection error messages
|
|
19
|
+
*/
|
|
13
20
|
export const MqttConnError = {
|
|
14
21
|
invalidPacket: "Invalid Packet",
|
|
15
22
|
packetTooLarge: "Packet too large",
|
|
16
23
|
UnexpectedEof: "Unexpected EOF",
|
|
17
24
|
} as const;
|
|
18
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Interface for MQTT connection handling
|
|
28
|
+
*/
|
|
19
29
|
export interface IMqttConn extends AsyncIterable<AnyPacket> {
|
|
30
|
+
/** Underlying connection */
|
|
20
31
|
readonly conn: Conn;
|
|
32
|
+
/** Whether connection is closed */
|
|
21
33
|
readonly isClosed: boolean;
|
|
34
|
+
/** Reason for connection closure if any */
|
|
22
35
|
readonly reason: string | undefined;
|
|
36
|
+
/** Async iterator for receiving packets */
|
|
23
37
|
[Symbol.asyncIterator](): AsyncIterableIterator<AnyPacket>;
|
|
38
|
+
/** Send an MQTT packet */
|
|
24
39
|
send(data: AnyPacket): Promise<void>;
|
|
40
|
+
/** Close the connection */
|
|
25
41
|
close(): void;
|
|
26
42
|
}
|
|
27
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Read a single byte from the connection
|
|
46
|
+
* @param conn Connection to read from
|
|
47
|
+
* @returns Single byte as number
|
|
48
|
+
* @throws Error if EOF reached unexpectedly
|
|
49
|
+
*/
|
|
28
50
|
async function readByte(conn: Conn): Promise<number> {
|
|
29
51
|
const buf = new Uint8Array(1);
|
|
30
52
|
const bytesRead = await conn.read(buf);
|
|
@@ -33,6 +55,12 @@ async function readByte(conn: Conn): Promise<number> {
|
|
|
33
55
|
return buf[0];
|
|
34
56
|
}
|
|
35
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Read exact number of bytes into buffer
|
|
60
|
+
* @param conn Connection to read from
|
|
61
|
+
* @param buf Buffer to read into
|
|
62
|
+
* @throws Error if EOF reached unexpectedly
|
|
63
|
+
*/
|
|
36
64
|
async function readFull(conn: Conn, buf: Uint8Array): Promise<void> {
|
|
37
65
|
let bytesRead = 0;
|
|
38
66
|
while (bytesRead < buf.length) {
|
|
@@ -43,8 +71,12 @@ async function readFull(conn: Conn, buf: Uint8Array): Promise<void> {
|
|
|
43
71
|
}
|
|
44
72
|
}
|
|
45
73
|
|
|
46
|
-
/**
|
|
47
|
-
*
|
|
74
|
+
/**
|
|
75
|
+
* Read a complete MQTT packet from the connection
|
|
76
|
+
* @param conn Connection to read from
|
|
77
|
+
* @param maxPacketSize Maximum allowed packet size
|
|
78
|
+
* @returns Decoded MQTT packet
|
|
79
|
+
* @throws Error if packet is invalid or too large
|
|
48
80
|
*/
|
|
49
81
|
export async function readPacket(
|
|
50
82
|
conn: Conn,
|
|
@@ -70,12 +102,25 @@ export async function readPacket(
|
|
|
70
102
|
return packet;
|
|
71
103
|
}
|
|
72
104
|
|
|
105
|
+
/**
|
|
106
|
+
* MQTT Connection class implementing IMqttConn interface
|
|
107
|
+
*/
|
|
73
108
|
export class MqttConn implements IMqttConn {
|
|
109
|
+
/** Underlying connection */
|
|
74
110
|
readonly conn: Conn;
|
|
111
|
+
/** Maximum allowed packet size */
|
|
75
112
|
private readonly maxPacketSize: number;
|
|
113
|
+
/** Reason for connection closure if any */
|
|
76
114
|
private _reason: string | undefined = undefined;
|
|
115
|
+
/** Whether connection is closed */
|
|
77
116
|
private _isClosed = false;
|
|
78
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Create new MQTT connection
|
|
120
|
+
* @param options Connection options
|
|
121
|
+
* @param options.conn Underlying socket connection
|
|
122
|
+
* @param options.maxPacketSize Maximum allowed packet size (default 2MB)
|
|
123
|
+
*/
|
|
79
124
|
constructor({
|
|
80
125
|
conn,
|
|
81
126
|
maxPacketSize,
|
|
@@ -87,10 +132,15 @@ export class MqttConn implements IMqttConn {
|
|
|
87
132
|
this.maxPacketSize = maxPacketSize || 2 * 1024 * 1024;
|
|
88
133
|
}
|
|
89
134
|
|
|
135
|
+
/** Get reason for connection closure */
|
|
90
136
|
get reason(): string | undefined {
|
|
91
137
|
return this._reason;
|
|
92
138
|
}
|
|
93
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Async iterator for receiving packets
|
|
142
|
+
* @yields MQTT packets
|
|
143
|
+
*/
|
|
94
144
|
async *[Symbol.asyncIterator](): AsyncIterableIterator<AnyPacket> {
|
|
95
145
|
while (!this._isClosed) {
|
|
96
146
|
try {
|
|
@@ -109,6 +159,10 @@ export class MqttConn implements IMqttConn {
|
|
|
109
159
|
}
|
|
110
160
|
}
|
|
111
161
|
|
|
162
|
+
/**
|
|
163
|
+
* Send an MQTT packet
|
|
164
|
+
* @param data Packet to send
|
|
165
|
+
*/
|
|
112
166
|
async send(data: AnyPacket): Promise<void> {
|
|
113
167
|
try {
|
|
114
168
|
await this.conn.write(encode(data));
|
|
@@ -120,10 +174,12 @@ export class MqttConn implements IMqttConn {
|
|
|
120
174
|
}
|
|
121
175
|
}
|
|
122
176
|
|
|
177
|
+
/** Whether connection is closed */
|
|
123
178
|
get isClosed(): boolean {
|
|
124
179
|
return this._isClosed;
|
|
125
180
|
}
|
|
126
181
|
|
|
182
|
+
/** Close the connection */
|
|
127
183
|
close(): void {
|
|
128
184
|
if (this.isClosed) return;
|
|
129
185
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seriousme/opifex",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "MQTT client & server for Deno & NodeJS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"build": "npx -p typescript tsc --build",
|
|
10
10
|
"format": "deno fmt",
|
|
11
11
|
"preversion": "npm run test",
|
|
12
|
-
"postversion": "jq '( { name:.name , version:.version, license:.license } + .deno)' package.json > deno.json"
|
|
12
|
+
"postversion": "jq '( { name:.name , version:.version, license:.license } + .deno)' package.json > deno.json",
|
|
13
|
+
"postpostversion": "deno install"
|
|
13
14
|
},
|
|
14
15
|
"keywords": [
|
|
15
16
|
"MQTT",
|
|
@@ -31,21 +32,25 @@
|
|
|
31
32
|
"dist/**/*.d.ts.map"
|
|
32
33
|
],
|
|
33
34
|
"exports": {
|
|
35
|
+
"./tcpClient": "./dist/node/tcpClient.js",
|
|
36
|
+
"./tcpServer": "./dist/node/tcpServer.js",
|
|
34
37
|
"./server": "./dist/server/mod.js",
|
|
35
38
|
"./client": "./dist/client/mod.js",
|
|
39
|
+
"./persistence": "./dist/persistence/persistence.js",
|
|
40
|
+
"./mqttConn": "./dist/mqttConn/mod.js",
|
|
36
41
|
"./mqttPacket": "./dist/mqttPacket/mod.js",
|
|
37
|
-
"./utils": "./dist/utils/mod.js"
|
|
38
|
-
"./tcpClient": "./dist/node/tcpClient.js",
|
|
39
|
-
"./tcpServer": "./dist/node/tcpServer.js"
|
|
42
|
+
"./utils": "./dist/utils/mod.js"
|
|
40
43
|
},
|
|
41
44
|
"deno": {
|
|
42
45
|
"exports": {
|
|
46
|
+
"./tcpClient": "./deno/tcpClient.ts",
|
|
47
|
+
"./tcpServer": "./deno/tcpServer.ts",
|
|
43
48
|
"./server": "./server/mod.ts",
|
|
44
49
|
"./client": "./client/mod.ts",
|
|
50
|
+
"./persistence": "./persistence/persistence.ts",
|
|
51
|
+
"./mqttConn": "./mqttConn/mod.ts",
|
|
45
52
|
"./mqttPacket": "./mqttPacket/mod.ts",
|
|
46
|
-
"./utils": "./utils/mod.ts"
|
|
47
|
-
"./tcpClient": "./deno/tcpClient.ts",
|
|
48
|
-
"./tcpServer": "./deno/tcpServer.ts"
|
|
53
|
+
"./utils": "./utils/mod.ts"
|
|
49
54
|
},
|
|
50
55
|
"exclude": [
|
|
51
56
|
"dist"
|
|
@@ -1,24 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module persistence
|
|
3
|
+
* @description Module for handling MQTT message persistence and client management
|
|
4
|
+
*/
|
|
1
5
|
import type { ClientId, PublishPacket, QoS, Topic } from "./deps.ts";
|
|
2
6
|
import type { IStore } from "./store.ts";
|
|
3
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Handler function type for processing publish packets
|
|
10
|
+
* @callback Handler
|
|
11
|
+
* @param {PublishPacket} packet - The MQTT publish packet to handle
|
|
12
|
+
*/
|
|
4
13
|
export type Handler = (packet: PublishPacket) => void;
|
|
5
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Store type for retained messages mapped by topic
|
|
17
|
+
* @typedef {Map<Topic, PublishPacket>} RetainStore
|
|
18
|
+
*/
|
|
6
19
|
export type RetainStore = Map<Topic, PublishPacket>;
|
|
7
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Client type containing message store and packet handler
|
|
23
|
+
* @typedef {Object} Client
|
|
24
|
+
* @property {IStore} store - The client's message store
|
|
25
|
+
* @property {Handler} handler - The client's packet handler function
|
|
26
|
+
*/
|
|
8
27
|
export type Client = { store: IStore; handler: Handler };
|
|
9
28
|
|
|
10
29
|
/**
|
|
11
|
-
*
|
|
12
|
-
* to store messages and subscriptions
|
|
30
|
+
* Interface for persistence implementations to store messages and subscriptions
|
|
13
31
|
* @interface IPersistence
|
|
14
32
|
*/
|
|
15
33
|
export interface IPersistence {
|
|
34
|
+
/**
|
|
35
|
+
* Map of connected clients by client ID
|
|
36
|
+
* @type {Map<ClientId, Client>}
|
|
37
|
+
*/
|
|
16
38
|
clientList: Map<ClientId, Client>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Map of retained messages by topic
|
|
42
|
+
* @type {RetainStore}
|
|
43
|
+
*/
|
|
17
44
|
retained: RetainStore;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Register a new client with the persistence layer
|
|
48
|
+
* @param {ClientId} clientId - Unique identifier for the client
|
|
49
|
+
* @param {Handler} handler - Packet handler function for the client
|
|
50
|
+
* @param {boolean} clean - Whether to start with a clean session
|
|
51
|
+
* @returns {IStore} The client's message store
|
|
52
|
+
*/
|
|
18
53
|
registerClient(clientId: ClientId, handler: Handler, clean: boolean): IStore;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Remove a client from the persistence layer
|
|
57
|
+
* @param {ClientId} clientId - ID of client to deregister
|
|
58
|
+
*/
|
|
19
59
|
deregisterClient(clientId: ClientId): void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Publish a message to all subscribed clients
|
|
63
|
+
* @param {Topic} topic - Topic to publish to
|
|
64
|
+
* @param {PublishPacket} packet - Packet containing the message
|
|
65
|
+
*/
|
|
20
66
|
publish(topic: Topic, packet: PublishPacket): void;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Subscribe a client to a topic
|
|
70
|
+
* @param {IStore} store - Client's message store
|
|
71
|
+
* @param {Topic} topic - Topic to subscribe to
|
|
72
|
+
* @param {QoS} qos - Quality of Service level
|
|
73
|
+
*/
|
|
21
74
|
subscribe(store: IStore, topic: Topic, qos: QoS): void;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Unsubscribe a client from a topic
|
|
78
|
+
* @param {IStore} store - Client's message store
|
|
79
|
+
* @param {Topic} topic - Topic to unsubscribe from
|
|
80
|
+
*/
|
|
22
81
|
unsubscribe(store: IStore, topic: Topic): void;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Send retained messages to a client
|
|
85
|
+
* @param {ClientId} clientId - ID of client to send retained messages to
|
|
86
|
+
*/
|
|
23
87
|
handleRetained(clientId: ClientId): void;
|
|
24
88
|
}
|