@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.
Files changed (128) hide show
  1. package/README.md +6 -7
  2. package/bin/conformanceServer.ts +67 -16
  3. package/bin/demoServer.ts +69 -14
  4. package/bin/mqtt.ts +30 -0
  5. package/client/ConnectionState.ts +12 -0
  6. package/client/client.ts +56 -2
  7. package/client/context.ts +5 -0
  8. package/client/handlers/handleConnack.ts +6 -0
  9. package/client/handlers/handlePacket.ts +7 -0
  10. package/client/handlers/handlePuback.ts +8 -2
  11. package/client/handlers/handlePubcomp.ts +10 -4
  12. package/client/handlers/handlePublish.ts +6 -2
  13. package/client/handlers/handlePubrec.ts +13 -6
  14. package/client/handlers/handlePubrel.ts +16 -6
  15. package/client/handlers/handleSuback.ts +9 -6
  16. package/client/handlers/handleUnsuback.ts +10 -3
  17. package/client/store/memoryStore.ts +25 -0
  18. package/client/store/store.ts +39 -0
  19. package/deno/tcpServer.test.ts +137 -2
  20. package/node/tcpServer.test.ts +61 -6
  21. package/node/tcpServer.ts +9 -2
  22. package/node/wrapNodeSocket.ts +13 -2
  23. package/package.json +13 -3
  24. package/persistence/memory/memoryPersistence.test.ts +2 -1
  25. package/persistence/memory/memoryPersistence.ts +1 -0
  26. package/persistence/store.ts +1 -0
  27. package/server/handlers/handleConnect.ts +49 -6
  28. package/server/handlers/handleDisconnect.ts +5 -0
  29. package/server/handlers/handlePacket.ts +7 -0
  30. package/server/handlers/handlePingreq.ts +6 -0
  31. package/server/handlers/handlePuback.ts +9 -2
  32. package/server/handlers/handlePubcomp.ts +8 -3
  33. package/server/handlers/handlePublish.ts +13 -0
  34. package/server/handlers/handlePubrec.ts +11 -3
  35. package/server/handlers/handlePubrel.ts +12 -4
  36. package/server/handlers/handleSubscribe.ts +28 -5
  37. package/server/handlers/handleUnsubscribe.ts +6 -0
  38. package/utils/asyncQueue.ts +2 -1
  39. package/utils/nextTick.ts +13 -1
  40. package/.github/workflows/closeStale.yml +0 -15
  41. package/.github/workflows/codeql-analysis.yml +0 -71
  42. package/.github/workflows/deno-ci.yml +0 -16
  43. package/.github/workflows/nodejs-ci.yml +0 -23
  44. package/.github/workflows/publish.yml +0 -16
  45. package/.github/workflows/update-license-year.sh +0 -14
  46. package/.github/workflows/update-license-year.yml +0 -13
  47. package/CHANGELOG.md +0 -74
  48. package/deno.json +0 -15
  49. package/deno.lock +0 -34
  50. package/dist/bin/conformanceServer.js +0 -51
  51. package/dist/bin/demoServer.js +0 -46
  52. package/dist/bin/mqtt.js +0 -227
  53. package/dist/client/ConnectionState.js +0 -7
  54. package/dist/client/client.js +0 -132
  55. package/dist/client/context.js +0 -148
  56. package/dist/client/deps.js +0 -5
  57. package/dist/client/handlers/handleConnack.js +0 -18
  58. package/dist/client/handlers/handlePacket.js +0 -50
  59. package/dist/client/handlers/handlePuback.js +0 -6
  60. package/dist/client/handlers/handlePubcomp.js +0 -9
  61. package/dist/client/handlers/handlePublish.js +0 -28
  62. package/dist/client/handlers/handlePubrec.js +0 -17
  63. package/dist/client/handlers/handlePubrel.js +0 -19
  64. package/dist/client/handlers/handleSuback.js +0 -9
  65. package/dist/client/handlers/handleUnsuback.js +0 -7
  66. package/dist/client/mod.js +0 -5
  67. package/dist/client/store/deps.js +0 -1
  68. package/dist/client/store/memoryStore.js +0 -36
  69. package/dist/client/store/store.js +0 -1
  70. package/dist/mqttConn/mqttConn.js +0 -104
  71. package/dist/mqttPacket/AuthenticationResult.js +0 -16
  72. package/dist/mqttPacket/BitMask.js +0 -10
  73. package/dist/mqttPacket/PacketType.js +0 -18
  74. package/dist/mqttPacket/connack.js +0 -27
  75. package/dist/mqttPacket/connect.js +0 -115
  76. package/dist/mqttPacket/decoder.js +0 -89
  77. package/dist/mqttPacket/disconnect.js +0 -14
  78. package/dist/mqttPacket/encoder.js +0 -56
  79. package/dist/mqttPacket/length.js +0 -43
  80. package/dist/mqttPacket/mod.js +0 -76
  81. package/dist/mqttPacket/pingreq.js +0 -15
  82. package/dist/mqttPacket/pingres.js +0 -14
  83. package/dist/mqttPacket/puback.js +0 -20
  84. package/dist/mqttPacket/pubcomp.js +0 -20
  85. package/dist/mqttPacket/publish.js +0 -51
  86. package/dist/mqttPacket/pubrec.js +0 -20
  87. package/dist/mqttPacket/pubrel.js +0 -20
  88. package/dist/mqttPacket/suback.js +0 -29
  89. package/dist/mqttPacket/subscribe.js +0 -52
  90. package/dist/mqttPacket/types.js +0 -1
  91. package/dist/mqttPacket/unsuback.js +0 -20
  92. package/dist/mqttPacket/unsubscribe.js +0 -36
  93. package/dist/mqttPacket/validators.js +0 -15
  94. package/dist/node/tcpClient.js +0 -81
  95. package/dist/node/tcpServer.js +0 -43
  96. package/dist/node/wrapNodeSocket.js +0 -47
  97. package/dist/persistence/deps.js +0 -3
  98. package/dist/persistence/memory/memoryPersistence.js +0 -117
  99. package/dist/persistence/persistence.js +0 -1
  100. package/dist/persistence/store.js +0 -1
  101. package/dist/server/context.js +0 -100
  102. package/dist/server/deps.js +0 -5
  103. package/dist/server/handlers/handleConnect.js +0 -46
  104. package/dist/server/handlers/handleDisconnect.js +0 -4
  105. package/dist/server/handlers/handlePacket.js +0 -58
  106. package/dist/server/handlers/handlePingreq.js +0 -6
  107. package/dist/server/handlers/handlePuback.js +0 -8
  108. package/dist/server/handlers/handlePubcomp.js +0 -8
  109. package/dist/server/handlers/handlePublish.js +0 -41
  110. package/dist/server/handlers/handlePubrec.js +0 -15
  111. package/dist/server/handlers/handlePubrel.js +0 -18
  112. package/dist/server/handlers/handleSubscribe.js +0 -33
  113. package/dist/server/handlers/handleUnsubscribe.js +0 -12
  114. package/dist/server/mod.js +0 -7
  115. package/dist/server/server.js +0 -47
  116. package/dist/socket/socket.js +0 -36
  117. package/dist/timer/timer.js +0 -40
  118. package/dist/trie/trie.js +0 -95
  119. package/dist/tsconfig.tsbuildinfo +0 -1
  120. package/dist/utils/args.js +0 -75
  121. package/dist/utils/assert.js +0 -20
  122. package/dist/utils/asyncQueue.js +0 -67
  123. package/dist/utils/deferred.js +0 -14
  124. package/dist/utils/logger.js +0 -58
  125. package/dist/utils/mod.js +0 -11
  126. package/dist/utils/nextTick.js +0 -6
  127. package/dist/utils/platform.js +0 -7
  128. 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,
@@ -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,
@@ -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
- test("Deno testServer", () => {
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
  });
@@ -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
- export async function serverTest() {
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 pubSub using client and server", async function () {
92
- await serverTest();
93
- logger.verbose("End of test");
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;
@@ -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
- function closer(sock: Socket) {
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.6.2",
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.10.0"
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
- "Registring a client should register the client and return a Store Object",
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
 
@@ -26,6 +26,7 @@ type ClientSubscription = {
26
26
  };
27
27
 
28
28
  export class MemoryStore implements IStore {
29
+ existingSession: boolean = false;
29
30
  clientId: ClientId;
30
31
  private packetId: PacketId;
31
32
  pendingIncoming: PacketStore;
@@ -5,6 +5,7 @@ export type PacketStore = Map<PacketId, PublishPacket>;
5
5
  export type SubscriptionStore = Map<Topic, QoS>;
6
6
 
7
7
  export interface IStore {
8
+ existingSession: boolean;
8
9
  clientId: ClientId;
9
10
  pendingIncoming: PacketStore;
10
11
  pendingOutgoing: PacketStore;
@@ -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
- export function handleConnect(ctx: Context, packet: ConnectPacket): void {
37
- const clientId = packet.clientId || `Opifex-${crypto.randomUUID()}`;
38
- const returnCode = validateConnect(ctx, packet);
39
- // connect is ok
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
- const sessionPresent = false;
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
- // A PUBACK Packet is the response to a PUBLISH Packet with QoS level 1.
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
- // The PUBCOMP Packet is the response to a PUBREL Packet.
5
- // It is the fourth and final packet of the QoS 2 protocol exchange.
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,