@seriousme/opifex 1.6.2 → 1.7.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 +6 -7
- package/bin/conformanceServer.ts +67 -16
- package/bin/demoServer.ts +69 -14
- package/bin/mqtt.ts +30 -0
- package/client/ConnectionState.ts +12 -0
- package/client/client.ts +56 -2
- package/client/context.ts +5 -0
- package/client/handlers/handleConnack.ts +6 -0
- package/client/handlers/handlePacket.ts +7 -0
- package/client/handlers/handlePuback.ts +8 -2
- package/client/handlers/handlePubcomp.ts +10 -4
- package/client/handlers/handlePublish.ts +6 -2
- package/client/handlers/handlePubrec.ts +13 -6
- package/client/handlers/handlePubrel.ts +16 -6
- package/client/handlers/handleSuback.ts +9 -6
- package/client/handlers/handleUnsuback.ts +10 -3
- package/client/store/memoryStore.ts +25 -0
- package/client/store/store.ts +39 -0
- package/deno/tcpServer.test.ts +137 -2
- package/node/tcpServer.test.ts +61 -6
- package/node/tcpServer.ts +9 -2
- package/node/wrapNodeSocket.ts +13 -2
- package/package.json +13 -3
- package/persistence/memory/memoryPersistence.test.ts +2 -1
- package/persistence/memory/memoryPersistence.ts +1 -0
- package/persistence/store.ts +1 -0
- package/server/handlers/handleConnect.ts +49 -6
- package/server/handlers/handleDisconnect.ts +5 -0
- package/server/handlers/handlePacket.ts +7 -0
- package/server/handlers/handlePingreq.ts +6 -0
- package/server/handlers/handlePuback.ts +9 -2
- package/server/handlers/handlePubcomp.ts +8 -3
- package/server/handlers/handlePublish.ts +13 -0
- package/server/handlers/handlePubrec.ts +11 -3
- package/server/handlers/handlePubrel.ts +12 -4
- package/server/handlers/handleSubscribe.ts +28 -5
- package/server/handlers/handleUnsubscribe.ts +6 -0
- package/utils/asyncQueue.ts +2 -1
- package/utils/nextTick.ts +13 -1
- package/.github/workflows/closeStale.yml +0 -15
- package/.github/workflows/codeql-analysis.yml +0 -71
- package/.github/workflows/deno-ci.yml +0 -16
- package/.github/workflows/nodejs-ci.yml +0 -23
- package/.github/workflows/publish.yml +0 -16
- package/.github/workflows/update-license-year.sh +0 -14
- package/.github/workflows/update-license-year.yml +0 -13
- package/CHANGELOG.md +0 -74
- package/deno.json +0 -15
- package/deno.lock +0 -34
- package/dist/bin/conformanceServer.js +0 -51
- package/dist/bin/demoServer.js +0 -46
- package/dist/bin/mqtt.js +0 -227
- package/dist/client/ConnectionState.js +0 -7
- package/dist/client/client.js +0 -132
- package/dist/client/context.js +0 -148
- package/dist/client/deps.js +0 -5
- package/dist/client/handlers/handleConnack.js +0 -18
- package/dist/client/handlers/handlePacket.js +0 -50
- package/dist/client/handlers/handlePuback.js +0 -6
- package/dist/client/handlers/handlePubcomp.js +0 -9
- package/dist/client/handlers/handlePublish.js +0 -28
- package/dist/client/handlers/handlePubrec.js +0 -17
- package/dist/client/handlers/handlePubrel.js +0 -19
- package/dist/client/handlers/handleSuback.js +0 -9
- package/dist/client/handlers/handleUnsuback.js +0 -7
- package/dist/client/mod.js +0 -5
- package/dist/client/store/deps.js +0 -1
- package/dist/client/store/memoryStore.js +0 -36
- package/dist/client/store/store.js +0 -1
- package/dist/mqttConn/mqttConn.js +0 -104
- package/dist/mqttPacket/AuthenticationResult.js +0 -16
- package/dist/mqttPacket/BitMask.js +0 -10
- package/dist/mqttPacket/PacketType.js +0 -18
- package/dist/mqttPacket/connack.js +0 -27
- package/dist/mqttPacket/connect.js +0 -115
- package/dist/mqttPacket/decoder.js +0 -89
- package/dist/mqttPacket/disconnect.js +0 -14
- package/dist/mqttPacket/encoder.js +0 -56
- package/dist/mqttPacket/length.js +0 -43
- package/dist/mqttPacket/mod.js +0 -76
- package/dist/mqttPacket/pingreq.js +0 -15
- package/dist/mqttPacket/pingres.js +0 -14
- package/dist/mqttPacket/puback.js +0 -20
- package/dist/mqttPacket/pubcomp.js +0 -20
- package/dist/mqttPacket/publish.js +0 -51
- package/dist/mqttPacket/pubrec.js +0 -20
- package/dist/mqttPacket/pubrel.js +0 -20
- package/dist/mqttPacket/suback.js +0 -29
- package/dist/mqttPacket/subscribe.js +0 -52
- package/dist/mqttPacket/types.js +0 -1
- package/dist/mqttPacket/unsuback.js +0 -20
- package/dist/mqttPacket/unsubscribe.js +0 -36
- package/dist/mqttPacket/validators.js +0 -15
- package/dist/node/tcpClient.js +0 -81
- package/dist/node/tcpServer.js +0 -43
- package/dist/node/wrapNodeSocket.js +0 -47
- package/dist/persistence/deps.js +0 -3
- package/dist/persistence/memory/memoryPersistence.js +0 -117
- package/dist/persistence/persistence.js +0 -1
- package/dist/persistence/store.js +0 -1
- package/dist/server/context.js +0 -100
- package/dist/server/deps.js +0 -5
- package/dist/server/handlers/handleConnect.js +0 -46
- package/dist/server/handlers/handleDisconnect.js +0 -4
- package/dist/server/handlers/handlePacket.js +0 -58
- package/dist/server/handlers/handlePingreq.js +0 -6
- package/dist/server/handlers/handlePuback.js +0 -8
- package/dist/server/handlers/handlePubcomp.js +0 -8
- package/dist/server/handlers/handlePublish.js +0 -41
- package/dist/server/handlers/handlePubrec.js +0 -15
- package/dist/server/handlers/handlePubrel.js +0 -18
- package/dist/server/handlers/handleSubscribe.js +0 -33
- package/dist/server/handlers/handleUnsubscribe.js +0 -12
- package/dist/server/mod.js +0 -7
- package/dist/server/server.js +0 -47
- package/dist/socket/socket.js +0 -36
- package/dist/timer/timer.js +0 -40
- package/dist/trie/trie.js +0 -95
- package/dist/tsconfig.tsbuildinfo +0 -1
- package/dist/utils/args.js +0 -75
- package/dist/utils/assert.js +0 -20
- package/dist/utils/asyncQueue.js +0 -67
- package/dist/utils/deferred.js +0 -14
- package/dist/utils/logger.js +0 -58
- package/dist/utils/mod.js +0 -11
- package/dist/utils/nextTick.js +0 -6
- package/dist/utils/platform.js +0 -7
- package/tsconfig.json +0 -22
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module memoryStore
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
import { assert, type PacketId } from "./deps.ts";
|
|
2
6
|
|
|
3
7
|
import {
|
|
@@ -10,13 +14,25 @@ import {
|
|
|
10
14
|
type PendingOutgoingPackets,
|
|
11
15
|
} from "./store.ts";
|
|
12
16
|
|
|
17
|
+
/**
|
|
18
|
+
* In-memory implementation of the IStore interface for managing MQTT packet storage
|
|
19
|
+
*/
|
|
13
20
|
export class MemoryStore implements IStore {
|
|
21
|
+
/** Current packet ID counter */
|
|
14
22
|
private packetId: PacketId;
|
|
15
23
|
|
|
24
|
+
/** Map of pending incoming packets */
|
|
16
25
|
pendingIncoming: PacketStore<pendingIncoming>;
|
|
26
|
+
|
|
27
|
+
/** Map of pending outgoing packets */
|
|
17
28
|
pendingOutgoing: PacketStore<PendingOutgoing>;
|
|
29
|
+
|
|
30
|
+
/** Map of pending acknowledgment outgoing packets */
|
|
18
31
|
pendingAckOutgoing: PacketStore<PendingAckOutgoing>;
|
|
19
32
|
|
|
33
|
+
/**
|
|
34
|
+
* Creates a new MemoryStore instance
|
|
35
|
+
*/
|
|
20
36
|
constructor() {
|
|
21
37
|
this.packetId = 0;
|
|
22
38
|
this.pendingIncoming = new Map();
|
|
@@ -24,6 +40,11 @@ export class MemoryStore implements IStore {
|
|
|
24
40
|
this.pendingAckOutgoing = new Map();
|
|
25
41
|
}
|
|
26
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Generates the next available packet ID
|
|
45
|
+
* @returns {PacketId} The next available packet ID
|
|
46
|
+
* @throws {Error} If no unused packet ID is available
|
|
47
|
+
*/
|
|
27
48
|
nextId(): PacketId {
|
|
28
49
|
const currentId = this.packetId;
|
|
29
50
|
do {
|
|
@@ -41,6 +62,10 @@ export class MemoryStore implements IStore {
|
|
|
41
62
|
return this.packetId;
|
|
42
63
|
}
|
|
43
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Asynchronously yields all pending outgoing packets
|
|
67
|
+
* @yields {PendingOutgoingPackets} Pending outgoing packets
|
|
68
|
+
*/
|
|
44
69
|
async *pendingOutgoingPackets(): AsyncGenerator<
|
|
45
70
|
PendingOutgoingPackets,
|
|
46
71
|
void,
|
package/client/store/store.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module MQTT Store Types
|
|
3
|
+
*/
|
|
1
4
|
import type {
|
|
2
5
|
PacketId,
|
|
3
6
|
PublishPacket,
|
|
@@ -5,22 +8,58 @@ import type {
|
|
|
5
8
|
SubscribePacket,
|
|
6
9
|
UnsubscribePacket,
|
|
7
10
|
} from "./deps.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Maximum value for MQTT packet IDs (65535)
|
|
14
|
+
*/
|
|
8
15
|
export const maxPacketId = 0xffff;
|
|
9
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Generic type for storing MQTT packets by their packet ID
|
|
19
|
+
* @template T The packet type to store
|
|
20
|
+
*/
|
|
10
21
|
export type PacketStore<T> = Map<PacketId, T>;
|
|
11
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Type for incoming PUBLISH packets that are pending processing
|
|
25
|
+
*/
|
|
12
26
|
export type pendingIncoming = PublishPacket;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Type for outgoing packets (PUBLISH, SUBSCRIBE, UNSUBSCRIBE) that are pending sending
|
|
30
|
+
*/
|
|
13
31
|
export type PendingOutgoing =
|
|
14
32
|
| PublishPacket
|
|
15
33
|
| SubscribePacket
|
|
16
34
|
| UnsubscribePacket;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Type for outgoing PUBREL packets that are pending acknowledgement
|
|
38
|
+
*/
|
|
17
39
|
export type PendingAckOutgoing = PubrelPacket;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Union type of all pending outgoing packet types
|
|
43
|
+
*/
|
|
18
44
|
export type PendingOutgoingPackets = PendingAckOutgoing | PendingOutgoing;
|
|
19
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Interface defining the storage requirements for MQTT packet state management
|
|
48
|
+
*/
|
|
20
49
|
export interface IStore {
|
|
50
|
+
/** Store for incoming packets pending processing */
|
|
21
51
|
pendingIncoming: PacketStore<pendingIncoming>;
|
|
52
|
+
|
|
53
|
+
/** Store for outgoing packets pending sending */
|
|
22
54
|
pendingOutgoing: PacketStore<PendingOutgoing>;
|
|
55
|
+
|
|
56
|
+
/** Store for outgoing packets pending acknowledgement */
|
|
23
57
|
pendingAckOutgoing: PacketStore<PendingAckOutgoing>;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Generator that yields pending outgoing packets
|
|
61
|
+
* @returns AsyncGenerator that yields PendingOutgoing or PubrelPacket packets
|
|
62
|
+
*/
|
|
24
63
|
pendingOutgoingPackets(): AsyncGenerator<
|
|
25
64
|
PendingOutgoing | PubrelPacket,
|
|
26
65
|
void,
|
package/deno/tcpServer.test.ts
CHANGED
|
@@ -1,14 +1,149 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
1
|
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { TcpClient } from "./tcpClient.ts";
|
|
3
4
|
import { TcpServer } from "./tcpServer.ts";
|
|
5
|
+
import { logger, LogLevel } from "../utils/mod.ts";
|
|
6
|
+
import type { PublishPacket, QoS } from "../mqttPacket/mod.ts";
|
|
4
7
|
|
|
5
|
-
|
|
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("Deno Test pubSub using client and server", async function () {
|
|
6
14
|
const server = new TcpServer({ port: 0 }, {});
|
|
7
15
|
server.start();
|
|
16
|
+
|
|
8
17
|
assert.deepStrictEqual(
|
|
9
18
|
server.port !== undefined,
|
|
10
19
|
true,
|
|
11
20
|
"server runs a a random port",
|
|
12
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("Deno 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
|
+
|
|
13
148
|
server.stop();
|
|
14
149
|
});
|
package/node/tcpServer.test.ts
CHANGED
|
@@ -9,10 +9,10 @@ logger.level(LogLevel.info);
|
|
|
9
9
|
export function sleep(ms: number): Promise<unknown> {
|
|
10
10
|
return new Promise((r) => setTimeout(r, ms));
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
test("Test pubSub using client and server", async function () {
|
|
13
14
|
const server = new TcpServer({ port: 0 }, {});
|
|
14
15
|
server.start();
|
|
15
|
-
await sleep(1000);
|
|
16
16
|
|
|
17
17
|
assert.deepStrictEqual(
|
|
18
18
|
server.port !== undefined,
|
|
@@ -86,9 +86,64 @@ export async function serverTest() {
|
|
|
86
86
|
|
|
87
87
|
logger.verbose(`Stop server`);
|
|
88
88
|
server.stop();
|
|
89
|
-
}
|
|
89
|
+
});
|
|
90
90
|
|
|
91
|
-
test("Test
|
|
92
|
-
|
|
93
|
-
|
|
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();
|
|
94
149
|
});
|
package/node/tcpServer.ts
CHANGED
|
@@ -26,11 +26,18 @@ export class TcpServer {
|
|
|
26
26
|
this.serverOptions = serverOptions;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
start() {
|
|
29
|
+
async start() {
|
|
30
30
|
this.server = createServer((sock) =>
|
|
31
31
|
this.mqttServer.serve(wrapNodeSocket(sock))
|
|
32
32
|
);
|
|
33
|
+
const isListening = new Promise((resolve) => {
|
|
34
|
+
this.server?.on("listening", () => {
|
|
35
|
+
resolve(true);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
33
38
|
this.server.listen(this.serverOptions.port, this.serverOptions.hostname);
|
|
39
|
+
await isListening;
|
|
40
|
+
return;
|
|
34
41
|
}
|
|
35
42
|
stop() {
|
|
36
43
|
this.server?.close();
|
|
@@ -38,7 +45,7 @@ export class TcpServer {
|
|
|
38
45
|
|
|
39
46
|
get port() {
|
|
40
47
|
const address = this.server?.address();
|
|
41
|
-
if (typeof address === "object") {
|
|
48
|
+
if (typeof address === "object" && address !== null) {
|
|
42
49
|
return address?.port;
|
|
43
50
|
}
|
|
44
51
|
return this.serverOptions?.port;
|
package/node/wrapNodeSocket.ts
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for wrapping Node.js sockets into standard web streams.
|
|
3
|
+
*/
|
|
1
4
|
import type { Socket } from "node:net";
|
|
2
5
|
import { Writable } from "node:stream";
|
|
3
6
|
import type { SockConn } from "../socket/socket.ts";
|
|
4
|
-
|
|
5
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Closes a Node.js socket if it is not already closed
|
|
9
|
+
* @param sock - The Node.js socket to close
|
|
10
|
+
*/
|
|
11
|
+
function closer(sock: Socket): void {
|
|
6
12
|
if (!sock.closed) {
|
|
7
13
|
sock.end();
|
|
8
14
|
}
|
|
9
15
|
}
|
|
10
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Wraps a Node.js socket into a standard web streams-based socket connection
|
|
19
|
+
* @param socket - The Node.js socket to wrap
|
|
20
|
+
* @returns A socket connection object with readable/writable streams and close method
|
|
21
|
+
*/
|
|
11
22
|
export function wrapNodeSocket(socket: Socket): SockConn {
|
|
12
23
|
const readable = new ReadableStream(
|
|
13
24
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seriousme/opifex",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "MQTT client & server for Deno & NodeJS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"postbuild": "deno fmt",
|
|
11
11
|
"preversion": "npm run test",
|
|
12
12
|
"version": "npm run build",
|
|
13
|
-
"postversion": "jq '( { name:.name , version:.version} + .deno)' package.json > deno.json"
|
|
13
|
+
"postversion": "jq '( { name:.name , version:.version, license:.license } + .deno)' package.json > deno.json"
|
|
14
14
|
},
|
|
15
15
|
"keywords": [
|
|
16
16
|
"MQTT",
|
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
"mqtt": "dist/bin/mqtt.js",
|
|
22
22
|
"demoServer": "dist/bin/demoServer.js"
|
|
23
23
|
},
|
|
24
|
+
"files": [
|
|
25
|
+
"package.json",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE.txt",
|
|
28
|
+
"**/*.ts",
|
|
29
|
+
"dist/**/*.js",
|
|
30
|
+
"dist/**/*.js.map",
|
|
31
|
+
"dist/**/*.d.ts",
|
|
32
|
+
"dist/**/*.d.ts.map"
|
|
33
|
+
],
|
|
24
34
|
"exports": {
|
|
25
35
|
"./server": "./dist/server/mod.js",
|
|
26
36
|
"./client": "./dist/client/mod.js",
|
|
@@ -53,6 +63,6 @@
|
|
|
53
63
|
},
|
|
54
64
|
"homepage": "https://github.com/seriousme/opifex#readme",
|
|
55
65
|
"devDependencies": {
|
|
56
|
-
"@types/node": "^22.
|
|
66
|
+
"@types/node": "^22.13.1"
|
|
57
67
|
}
|
|
58
68
|
}
|
|
@@ -21,7 +21,7 @@ test("new should create new Persistence object", () => {
|
|
|
21
21
|
});
|
|
22
22
|
|
|
23
23
|
test(
|
|
24
|
-
"
|
|
24
|
+
"Registering a client should register the client and return a Store Object",
|
|
25
25
|
() => {
|
|
26
26
|
const persistence = new Persistence();
|
|
27
27
|
const clientId = "myClient";
|
|
@@ -29,6 +29,7 @@ test(
|
|
|
29
29
|
assert.deepStrictEqual(persistence.clientList.has(clientId), true);
|
|
30
30
|
assert.deepStrictEqual(typeof client, "object");
|
|
31
31
|
assert.deepStrictEqual(client instanceof Store, true);
|
|
32
|
+
assert.deepStrictEqual(client.existingSession, false);
|
|
32
33
|
},
|
|
33
34
|
);
|
|
34
35
|
|
package/persistence/store.ts
CHANGED
|
@@ -8,6 +8,12 @@ import {
|
|
|
8
8
|
Timer,
|
|
9
9
|
} from "../deps.ts";
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Checks if the client is authenticated based on the provided credentials
|
|
13
|
+
* @param ctx - The connection context
|
|
14
|
+
* @param packet - The MQTT CONNECT packet
|
|
15
|
+
* @returns Authentication result indicating if the client is authenticated
|
|
16
|
+
*/
|
|
11
17
|
function isAuthenticated(
|
|
12
18
|
ctx: Context,
|
|
13
19
|
packet: ConnectPacket,
|
|
@@ -23,6 +29,12 @@ function isAuthenticated(
|
|
|
23
29
|
return AuthenticationResult.ok;
|
|
24
30
|
}
|
|
25
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Validates the CONNECT packet
|
|
34
|
+
* @param ctx - The connection context
|
|
35
|
+
* @param packet - The MQTT CONNECT packet to validate
|
|
36
|
+
* @returns Authentication result indicating if the CONNECT packet is valid
|
|
37
|
+
*/
|
|
26
38
|
function validateConnect(
|
|
27
39
|
ctx: Context,
|
|
28
40
|
packet: ConnectPacket,
|
|
@@ -33,10 +45,18 @@ function validateConnect(
|
|
|
33
45
|
return isAuthenticated(ctx, packet);
|
|
34
46
|
}
|
|
35
47
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Processes the validated CONNECT packet
|
|
50
|
+
* @param packet - The MQTT CONNECT packet
|
|
51
|
+
* @param ctx - The connection context
|
|
52
|
+
* @param clientId - The client ID
|
|
53
|
+
*/
|
|
54
|
+
function processValidatedConnect(
|
|
55
|
+
returnCode: TAuthenticationResult,
|
|
56
|
+
packet: ConnectPacket,
|
|
57
|
+
ctx: Context,
|
|
58
|
+
clientId: string,
|
|
59
|
+
): boolean {
|
|
40
60
|
if (returnCode === AuthenticationResult.ok) {
|
|
41
61
|
if (packet.will) {
|
|
42
62
|
ctx.will = {
|
|
@@ -57,10 +77,33 @@ export function handleConnect(ctx: Context, packet: ConnectPacket): void {
|
|
|
57
77
|
ctx.close();
|
|
58
78
|
}, Math.floor(keepAlive * 1500));
|
|
59
79
|
}
|
|
80
|
+
// is this a new session?
|
|
81
|
+
// either because its the first time for the client
|
|
82
|
+
// or it specifically asked for a clean one
|
|
83
|
+
const previousSession = ctx.store?.existingSession;
|
|
84
|
+
// client now has a history
|
|
85
|
+
if (!previousSession && ctx.store) {
|
|
86
|
+
ctx.store.existingSession = true;
|
|
87
|
+
}
|
|
88
|
+
return previousSession || false;
|
|
60
89
|
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
61
92
|
|
|
62
|
-
|
|
63
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Handles the MQTT CONNECT packet
|
|
95
|
+
* @param ctx - The connection context
|
|
96
|
+
* @param packet - The MQTT CONNECT packet to handle
|
|
97
|
+
*/
|
|
98
|
+
export function handleConnect(ctx: Context, packet: ConnectPacket): void {
|
|
99
|
+
const clientId = packet.clientId || `Opifex-${crypto.randomUUID()}`;
|
|
100
|
+
const returnCode = validateConnect(ctx, packet);
|
|
101
|
+
const sessionPresent = processValidatedConnect(
|
|
102
|
+
returnCode,
|
|
103
|
+
packet,
|
|
104
|
+
ctx,
|
|
105
|
+
clientId,
|
|
106
|
+
);
|
|
64
107
|
ctx.send({
|
|
65
108
|
type: PacketType.connack,
|
|
66
109
|
sessionPresent,
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Context } from "../context.ts";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Handles client disconnection by clearing the will message and closing the connection
|
|
5
|
+
* @param {Context} ctx - The connection context object
|
|
6
|
+
* @returns {void}
|
|
7
|
+
*/
|
|
3
8
|
export function handleDisconnect(ctx: Context): void {
|
|
4
9
|
ctx.will = undefined;
|
|
5
10
|
ctx.close();
|
|
@@ -23,6 +23,13 @@ import { handleUnsubscribe } from "./handleUnsubscribe.ts";
|
|
|
23
23
|
import { handleDisconnect } from "./handleDisconnect.ts";
|
|
24
24
|
import { logger } from "../deps.ts";
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Handles incoming MQTT packets based on their type and connection state
|
|
28
|
+
* @param ctx - The connection context containing client state and configuration
|
|
29
|
+
* @param packet - The MQTT packet to handle
|
|
30
|
+
* @throws Error if receiving unexpected packet types or packets before connect
|
|
31
|
+
* @returns Promise that resolves when packet handling is complete
|
|
32
|
+
*/
|
|
26
33
|
export async function handlePacket(
|
|
27
34
|
ctx: Context,
|
|
28
35
|
packet: AnyPacket,
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { Context } from "../context.ts";
|
|
2
2
|
import { PacketType } from "../deps.ts";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Handles PINGREQ packet by responding with a PINGRESP packet
|
|
6
|
+
* @param ctx - The connection context containing send method
|
|
7
|
+
* @returns Promise that resolves when PINGRESP is sent
|
|
8
|
+
*/
|
|
9
|
+
|
|
4
10
|
export async function handlePingreq(ctx: Context): Promise<void> {
|
|
5
11
|
await ctx.send({
|
|
6
12
|
type: PacketType.pingres,
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { Context } from "../context.ts";
|
|
2
2
|
import type { PubackPacket } from "../deps.ts";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Handles PUBACK (Publish Acknowledgment) packets in MQTT protocol
|
|
6
|
+
* @param ctx - The connection context containing the client's state and configuration
|
|
7
|
+
* @param packet - The PUBACK packet received from the client
|
|
8
|
+
* @description
|
|
9
|
+
* PUBACK packets are sent in response to PUBLISH packets with QoS level 1.
|
|
10
|
+
* This function removes the original PUBLISH packet from the pending outgoing messages store
|
|
11
|
+
* once acknowledgment is received.
|
|
12
|
+
*/
|
|
6
13
|
export function handlePuback(ctx: Context, packet: PubackPacket) {
|
|
7
14
|
// qos 1 only
|
|
8
15
|
const id = packet.id;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { Context } from "../context.ts";
|
|
2
2
|
import type { PubcompPacket } from "../deps.ts";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Handles PUBCOMP packets which are the response to PUBREL packets in QoS 2 flow
|
|
6
|
+
* @param ctx - The connection context containing the client state and configuration
|
|
7
|
+
* @param packet - The PUBCOMP packet received from the client
|
|
8
|
+
* @description
|
|
9
|
+
* This is the fourth and final packet of the QoS 2 protocol exchange.
|
|
10
|
+
* When received, it removes the message from the pendingAckOutgoing store.
|
|
11
|
+
*/
|
|
7
12
|
export function handlePubcomp(ctx: Context, packet: PubcompPacket): void {
|
|
8
13
|
const id = packet.id;
|
|
9
14
|
if (ctx.store?.pendingAckOutgoing.has(id)) {
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { type Context, SysPrefix } from "../context.ts";
|
|
2
2
|
import { PacketType, type PublishPacket, type Topic } from "../deps.ts";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Checks if a client is authorized to publish to a given topic
|
|
6
|
+
* @param ctx - The connection context
|
|
7
|
+
* @param topic - The topic to check authorization for
|
|
8
|
+
* @returns boolean indicating if client is authorized to publish
|
|
9
|
+
*/
|
|
4
10
|
function authorizedToPublish(ctx: Context, topic: Topic) {
|
|
5
11
|
if (topic.startsWith(SysPrefix)) {
|
|
6
12
|
return false;
|
|
@@ -11,6 +17,13 @@ function authorizedToPublish(ctx: Context, topic: Topic) {
|
|
|
11
17
|
return true;
|
|
12
18
|
}
|
|
13
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Handles MQTT PUBLISH packets
|
|
22
|
+
* @param ctx - The connection context
|
|
23
|
+
* @param packet - The PUBLISH packet to process
|
|
24
|
+
* @returns Promise that resolves when packet is processed
|
|
25
|
+
* @throws Error if packet processing fails
|
|
26
|
+
*/
|
|
14
27
|
export async function handlePublish(
|
|
15
28
|
ctx: Context,
|
|
16
29
|
packet: PublishPacket,
|