@seriousme/opifex 1.6.2 → 1.6.3

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 (77) hide show
  1. package/.github/dependabot.yml +7 -0
  2. package/.github/workflows/closeStale.yml +1 -1
  3. package/.github/workflows/codeql-analysis.yml +4 -4
  4. package/CHANGELOG.md +6 -0
  5. package/README.md +6 -7
  6. package/bin/conformanceServer.ts +67 -16
  7. package/bin/demoServer.ts +69 -14
  8. package/bin/mqtt.ts +30 -0
  9. package/client/ConnectionState.ts +12 -0
  10. package/client/client.ts +56 -2
  11. package/client/context.ts +5 -0
  12. package/client/handlers/handleConnack.ts +6 -0
  13. package/client/handlers/handlePacket.ts +7 -0
  14. package/client/handlers/handlePuback.ts +8 -2
  15. package/client/handlers/handlePubcomp.ts +10 -4
  16. package/client/handlers/handlePublish.ts +6 -2
  17. package/client/handlers/handlePubrec.ts +13 -6
  18. package/client/handlers/handlePubrel.ts +16 -6
  19. package/client/handlers/handleSuback.ts +9 -6
  20. package/client/handlers/handleUnsuback.ts +10 -3
  21. package/client/store/memoryStore.ts +25 -0
  22. package/client/store/store.ts +39 -0
  23. package/deno/tcpServer.test.ts +137 -2
  24. package/deno.json +2 -1
  25. package/dist/bin/conformanceServer.js +61 -15
  26. package/dist/bin/demoServer.js +63 -13
  27. package/dist/bin/mqtt.js +29 -0
  28. package/dist/client/ConnectionState.js +9 -0
  29. package/dist/client/client.js +56 -1
  30. package/dist/client/context.js +4 -0
  31. package/dist/client/handlers/handleConnack.js +6 -0
  32. package/dist/client/handlers/handlePacket.js +7 -0
  33. package/dist/client/handlers/handlePuback.js +8 -1
  34. package/dist/client/handlers/handlePubcomp.js +10 -3
  35. package/dist/client/handlers/handlePublish.js +6 -1
  36. package/dist/client/handlers/handlePubrec.js +13 -4
  37. package/dist/client/handlers/handlePubrel.js +16 -4
  38. package/dist/client/handlers/handleSuback.js +9 -4
  39. package/dist/client/handlers/handleUnsuback.js +10 -2
  40. package/dist/client/store/memoryStore.js +22 -0
  41. package/dist/client/store/store.js +3 -0
  42. package/dist/node/tcpServer.js +9 -2
  43. package/dist/node/wrapNodeSocket.js +9 -0
  44. package/dist/persistence/memory/memoryPersistence.js +1 -0
  45. package/dist/server/handlers/handleConnect.js +39 -5
  46. package/dist/server/handlers/handleDisconnect.js +5 -0
  47. package/dist/server/handlers/handlePacket.js +7 -0
  48. package/dist/server/handlers/handlePingreq.js +5 -0
  49. package/dist/server/handlers/handlePuback.js +9 -1
  50. package/dist/server/handlers/handlePubcomp.js +8 -2
  51. package/dist/server/handlers/handlePublish.js +13 -0
  52. package/dist/server/handlers/handlePubrec.js +11 -3
  53. package/dist/server/handlers/handlePubrel.js +12 -3
  54. package/dist/server/handlers/handleSubscribe.js +27 -4
  55. package/dist/server/handlers/handleUnsubscribe.js +6 -0
  56. package/dist/utils/asyncQueue.js +2 -0
  57. package/dist/utils/nextTick.js +13 -1
  58. package/node/tcpServer.test.ts +61 -6
  59. package/node/tcpServer.ts +9 -2
  60. package/node/wrapNodeSocket.ts +13 -2
  61. package/package.json +2 -2
  62. package/persistence/memory/memoryPersistence.test.ts +2 -1
  63. package/persistence/memory/memoryPersistence.ts +1 -0
  64. package/persistence/store.ts +1 -0
  65. package/server/handlers/handleConnect.ts +49 -6
  66. package/server/handlers/handleDisconnect.ts +5 -0
  67. package/server/handlers/handlePacket.ts +7 -0
  68. package/server/handlers/handlePingreq.ts +6 -0
  69. package/server/handlers/handlePuback.ts +9 -2
  70. package/server/handlers/handlePubcomp.ts +8 -3
  71. package/server/handlers/handlePublish.ts +13 -0
  72. package/server/handlers/handlePubrec.ts +11 -3
  73. package/server/handlers/handlePubrel.ts +12 -4
  74. package/server/handlers/handleSubscribe.ts +28 -5
  75. package/server/handlers/handleUnsubscribe.ts +6 -0
  76. package/utils/asyncQueue.ts +2 -1
  77. package/utils/nextTick.ts +13 -1
@@ -1,4 +1,10 @@
1
1
  import { PacketType } from "../deps.js";
2
+ /**
3
+ * Handles MQTT unsubscribe packets by removing subscriptions and sending acknowledgement
4
+ * @param ctx - The connection context containing client information and methods
5
+ * @param packet - The MQTT unsubscribe packet containing topics to unsubscribe from
6
+ * @returns Promise that resolves when unsubscribe is complete and acknowledged
7
+ */
2
8
  export async function handleUnsubscribe(ctx, packet) {
3
9
  for (const topic of packet.topicFilters) {
4
10
  if (ctx.store) {
@@ -4,11 +4,13 @@ import { nextTick } from "./nextTick.js";
4
4
  * for them to be consumed.
5
5
  *
6
6
  * @example
7
+ * ```ts
7
8
  * const queue = new AsyncQueue<string>();
8
9
  * queue.push("hello");
9
10
  * for await (const item of queue) {
10
11
  * console.log(item); // "hello"
11
12
  * }
13
+ * ```
12
14
  */
13
15
  export class AsyncQueue {
14
16
  queue = [];
@@ -1,5 +1,17 @@
1
1
  /**
2
- * @returns a promise that resolves on the nextTick of the event loop
2
+ * Returns a promise that resolves on the next tick of the Node.js event loop.
3
+ * This is useful for deferring execution of code until after the current call stack is cleared.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * async function example() {
8
+ * console.log('Before nextTick');
9
+ * await nextTick();
10
+ * console.log('After nextTick');
11
+ * }
12
+ * ```
13
+ *
14
+ * @returns {Promise<void>} A promise that resolves on the next tick of the event loop
3
15
  */
4
16
  export function nextTick() {
5
17
  return new Promise((resolve) => setTimeout(resolve, 0));
@@ -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.6.3",
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,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,
@@ -1,9 +1,17 @@
1
1
  import type { Context } from "../context.ts";
2
2
  import { PacketType, type PubrecPacket } from "../deps.ts";
3
3
 
4
- // qos 2
5
- // Discard message, Store PUBREC received <Packet Identifier>
6
- // send PUBREL <Packet Identifier>
4
+ /**
5
+ * Handles PUBREC (QoS 2 Publish Received) packets
6
+ * @param ctx - The connection context
7
+ * @param packet - The PUBREC packet received from the client
8
+ * @returns Promise that resolves when handling is complete
9
+ * @description
10
+ * For QoS 2 message flow:
11
+ * 1. Discards the original publish message
12
+ * 2. Stores that PUBREC was received for the packet ID
13
+ * 3. Sends PUBREL packet in response
14
+ */
7
15
  export async function handlePubrec(
8
16
  ctx: Context,
9
17
  packet: PubrecPacket,
@@ -1,10 +1,18 @@
1
1
  import type { Context } from "../context.ts";
2
2
  import { PacketType, type PubrelPacket } from "../deps.ts";
3
3
 
4
- // qos 2 only
5
- // Method A, Initiate onward delivery of the Application Message1 then discard message
6
- // Send PUBCOMP <Packet Identifier>
7
-
4
+ /**
5
+ * Handles PUBREL (QoS 2 publish release) packets
6
+ *
7
+ * @param ctx - The connection context
8
+ * @param packet - The PUBREL packet received from the client
9
+ * @returns Promise that resolves when handling is complete
10
+ * @description
11
+ * For QoS 2 message delivery:
12
+ * 1. Initiates onward delivery of the Application Message
13
+ * 2. Discards the stored message
14
+ * 3. Sends PUBCOMP packet with the Packet Identifier
15
+ */
8
16
  export async function handlePubrel(
9
17
  ctx: Context,
10
18
  packet: PubrelPacket,
@@ -6,7 +6,18 @@ import {
6
6
  type Topic,
7
7
  } from "../deps.ts";
8
8
 
9
+ /**
10
+ * @constant {number} SubscriptionFailure
11
+ * @description Code indicating a failed subscription attempt
12
+ */
9
13
  const SubscriptionFailure = 0x80;
14
+
15
+ /**
16
+ * Checks if a client is authorized to subscribe to a topic
17
+ * @param ctx - The connection context
18
+ * @param topicFilter - The topic filter to check authorization for
19
+ * @returns True if authorized, false otherwise
20
+ */
10
21
  function authorizedToSubscribe(ctx: Context, topicFilter: Topic) {
11
22
  if (ctx.handlers.isAuthorizedToSubscribe) {
12
23
  return ctx.handlers.isAuthorizedToSubscribe(ctx, topicFilter);
@@ -14,13 +25,23 @@ function authorizedToSubscribe(ctx: Context, topicFilter: Topic) {
14
25
  return true;
15
26
  }
16
27
 
28
+ /**
29
+ * @function handleSubscribe
30
+ * @description Processes an MQTT SUBSCRIBE packet
31
+ * @param {Context} ctx - The connection context
32
+ * @param {SubscribePacket} packet - The SUBSCRIBE packet received from the client
33
+ * @returns {Promise<void>}
34
+ * @throws {Error} If subscription processing fails
35
+ * @remarks The order of return codes in the SUBACK Packet MUST match the order of Topic Filters in the SUBSCRIBE Packet [MQTT-3.9.3-1]
36
+ */
17
37
  export async function handleSubscribe(
18
38
  ctx: Context,
19
39
  packet: SubscribePacket,
20
40
  ): Promise<void> {
21
- // The order of return codes in the SUBACK Packet MUST match the order of
22
- // Topic Filters in the SUBSCRIBE Packet [MQTT-3.9.3-1].
23
-
41
+ /*
42
+ * The order of return codes in the SUBACK Packet MUST match the order of
43
+ * Topic Filters in the SUBSCRIBE Packet [MQTT-3.9.3-1].
44
+ */
24
45
  const validSubscriptions: Subscription[] = [];
25
46
  const returnCodes = packet.subscriptions.map((sub) => {
26
47
  if (ctx.store) {
@@ -31,7 +52,7 @@ export async function handleSubscribe(
31
52
  validSubscriptions.push(sub);
32
53
  return sub.qos;
33
54
  }
34
- return SubscriptionFailure; // failure
55
+ return SubscriptionFailure;
35
56
  });
36
57
 
37
58
  await ctx.send({
@@ -40,7 +61,9 @@ export async function handleSubscribe(
40
61
  returnCodes: returnCodes,
41
62
  });
42
63
 
43
- // send any retained messages that match these subscriptions
64
+ /*
65
+ * send any retained messages that match these subscriptions
66
+ */
44
67
  if (ctx.store) {
45
68
  ctx.persistence.handleRetained(ctx.store.clientId);
46
69
  }
@@ -1,6 +1,12 @@
1
1
  import type { Context } from "../context.ts";
2
2
  import { PacketType, type UnsubscribePacket } from "../deps.ts";
3
3
 
4
+ /**
5
+ * Handles MQTT unsubscribe packets by removing subscriptions and sending acknowledgement
6
+ * @param ctx - The connection context containing client information and methods
7
+ * @param packet - The MQTT unsubscribe packet containing topics to unsubscribe from
8
+ * @returns Promise that resolves when unsubscribe is complete and acknowledged
9
+ */
4
10
  export async function handleUnsubscribe(
5
11
  ctx: Context,
6
12
  packet: UnsubscribePacket,
@@ -1,15 +1,16 @@
1
1
  import { nextTick } from "./nextTick.ts";
2
-
3
2
  /**
4
3
  * An Async Queue is a queue that can be used to push items to it and then wait
5
4
  * for them to be consumed.
6
5
  *
7
6
  * @example
7
+ * ```ts
8
8
  * const queue = new AsyncQueue<string>();
9
9
  * queue.push("hello");
10
10
  * for await (const item of queue) {
11
11
  * console.log(item); // "hello"
12
12
  * }
13
+ * ```
13
14
  */
14
15
  export class AsyncQueue<T> {
15
16
  private queue: T[] = [];
package/utils/nextTick.ts CHANGED
@@ -1,5 +1,17 @@
1
1
  /**
2
- * @returns a promise that resolves on the nextTick of the event loop
2
+ * Returns a promise that resolves on the next tick of the Node.js event loop.
3
+ * This is useful for deferring execution of code until after the current call stack is cleared.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * async function example() {
8
+ * console.log('Before nextTick');
9
+ * await nextTick();
10
+ * console.log('After nextTick');
11
+ * }
12
+ * ```
13
+ *
14
+ * @returns {Promise<void>} A promise that resolves on the next tick of the event loop
3
15
  */
4
16
  export function nextTick(): Promise<void> {
5
17
  return new Promise((resolve) => setTimeout(resolve, 0));