@seriousme/opifex 1.6.1 → 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 (112) 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 +12 -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/tcpClient.ts +19 -2
  24. package/deno/tcpServer.test.ts +137 -2
  25. package/deno/tcpServer.ts +9 -2
  26. package/deno.json +2 -1
  27. package/dist/bin/conformanceServer.js +61 -15
  28. package/dist/bin/demoServer.js +63 -13
  29. package/dist/bin/mqtt.js +29 -0
  30. package/dist/client/ConnectionState.js +9 -0
  31. package/dist/client/client.js +56 -1
  32. package/dist/client/context.js +4 -0
  33. package/dist/client/handlers/handleConnack.js +6 -0
  34. package/dist/client/handlers/handlePacket.js +7 -0
  35. package/dist/client/handlers/handlePuback.js +8 -1
  36. package/dist/client/handlers/handlePubcomp.js +10 -3
  37. package/dist/client/handlers/handlePublish.js +6 -1
  38. package/dist/client/handlers/handlePubrec.js +13 -4
  39. package/dist/client/handlers/handlePubrel.js +16 -4
  40. package/dist/client/handlers/handleSuback.js +9 -4
  41. package/dist/client/handlers/handleUnsuback.js +10 -2
  42. package/dist/client/store/memoryStore.js +22 -0
  43. package/dist/client/store/store.js +3 -0
  44. package/dist/mqttPacket/AuthenticationResult.js +3 -0
  45. package/dist/mqttPacket/mod.js +5 -0
  46. package/dist/node/tcpClient.js +19 -2
  47. package/dist/node/tcpServer.js +12 -2
  48. package/dist/node/wrapNodeSocket.js +9 -0
  49. package/dist/persistence/memory/memoryPersistence.js +1 -0
  50. package/dist/server/handlers/handleConnect.js +39 -5
  51. package/dist/server/handlers/handleDisconnect.js +5 -0
  52. package/dist/server/handlers/handlePacket.js +7 -0
  53. package/dist/server/handlers/handlePingreq.js +5 -0
  54. package/dist/server/handlers/handlePuback.js +9 -1
  55. package/dist/server/handlers/handlePubcomp.js +8 -2
  56. package/dist/server/handlers/handlePublish.js +13 -0
  57. package/dist/server/handlers/handlePubrec.js +11 -3
  58. package/dist/server/handlers/handlePubrel.js +12 -3
  59. package/dist/server/handlers/handleSubscribe.js +27 -4
  60. package/dist/server/handlers/handleUnsubscribe.js +6 -0
  61. package/dist/utils/args.js +14 -0
  62. package/dist/utils/assert.js +1 -0
  63. package/dist/utils/asyncQueue.js +13 -0
  64. package/dist/utils/deferred.js +3 -0
  65. package/dist/utils/logger.js +29 -0
  66. package/dist/utils/mod.js +4 -0
  67. package/dist/utils/nextTick.js +15 -0
  68. package/dist/utils/platform.js +3 -0
  69. package/mqttPacket/AuthenticationResult.ts +3 -1
  70. package/mqttPacket/connack.ts +4 -0
  71. package/mqttPacket/connect.ts +3 -0
  72. package/mqttPacket/disconnect.ts +4 -0
  73. package/mqttPacket/mod.ts +9 -0
  74. package/mqttPacket/pingreq.ts +3 -0
  75. package/mqttPacket/pingres.ts +4 -0
  76. package/mqttPacket/puback.ts +3 -0
  77. package/mqttPacket/pubcomp.ts +4 -0
  78. package/mqttPacket/publish.ts +4 -0
  79. package/mqttPacket/pubrec.ts +4 -0
  80. package/mqttPacket/pubrel.ts +4 -0
  81. package/mqttPacket/suback.ts +4 -0
  82. package/mqttPacket/subscribe.ts +6 -0
  83. package/mqttPacket/types.ts +32 -1
  84. package/mqttPacket/unsuback.ts +4 -0
  85. package/mqttPacket/unsubscribe.ts +3 -0
  86. package/node/tcpClient.ts +20 -2
  87. package/node/tcpServer.test.ts +61 -6
  88. package/node/tcpServer.ts +17 -4
  89. package/node/wrapNodeSocket.ts +13 -2
  90. package/package.json +2 -2
  91. package/persistence/memory/memoryPersistence.test.ts +2 -1
  92. package/persistence/memory/memoryPersistence.ts +1 -0
  93. package/persistence/store.ts +1 -0
  94. package/server/handlers/handleConnect.ts +49 -6
  95. package/server/handlers/handleDisconnect.ts +5 -0
  96. package/server/handlers/handlePacket.ts +7 -0
  97. package/server/handlers/handlePingreq.ts +6 -0
  98. package/server/handlers/handlePuback.ts +9 -2
  99. package/server/handlers/handlePubcomp.ts +8 -3
  100. package/server/handlers/handlePublish.ts +13 -0
  101. package/server/handlers/handlePubrec.ts +11 -3
  102. package/server/handlers/handlePubrel.ts +12 -4
  103. package/server/handlers/handleSubscribe.ts +28 -5
  104. package/server/handlers/handleUnsubscribe.ts +6 -0
  105. package/utils/args.ts +17 -0
  106. package/utils/assert.ts +1 -0
  107. package/utils/asyncQueue.ts +13 -1
  108. package/utils/deferred.ts +4 -0
  109. package/utils/logger.ts +29 -0
  110. package/utils/mod.ts +4 -0
  111. package/utils/nextTick.ts +15 -0
  112. package/utils/platform.ts +4 -0
@@ -0,0 +1,7 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ # Check for updates to GitHub Actions every week
7
+ interval: "weekly"
@@ -7,7 +7,7 @@ jobs:
7
7
  stale:
8
8
  runs-on: ubuntu-latest
9
9
  steps:
10
- - uses: actions/stale@v3
10
+ - uses: actions/stale@v9
11
11
  with:
12
12
  repo-token: ${{ secrets.GITHUB_TOKEN }}
13
13
  days-before-stale: 30
@@ -39,11 +39,11 @@ jobs:
39
39
 
40
40
  steps:
41
41
  - name: Checkout repository
42
- uses: actions/checkout@v2
42
+ uses: actions/checkout@v4
43
43
 
44
44
  # Initializes the CodeQL tools for scanning.
45
45
  - name: Initialize CodeQL
46
- uses: github/codeql-action/init@v1
46
+ uses: github/codeql-action/init@v3
47
47
  with:
48
48
  languages: ${{ matrix.language }}
49
49
  # If you wish to specify custom queries, you can do so here or in a config file.
@@ -54,7 +54,7 @@ jobs:
54
54
  # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55
55
  # If this step fails, then you should remove it and run the build manually (see below)
56
56
  - name: Autobuild
57
- uses: github/codeql-action/autobuild@v1
57
+ uses: github/codeql-action/autobuild@v3
58
58
 
59
59
  # ℹ️ Command-line programs to run using the OS shell.
60
60
  # 📚 https://git.io/JvXDl
@@ -68,4 +68,4 @@ jobs:
68
68
  # make release
69
69
 
70
70
  - name: Perform CodeQL Analysis
71
- uses: github/codeql-action/analyze@v1
71
+ uses: github/codeql-action/analyze@v3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@
4
4
 
5
5
  ### Changed
6
6
 
7
+ ## [v1.6.3] 31-01-2025
8
+
9
+ ### Changed
10
+
11
+ - fix: improve memory persistence backend on reconnects
12
+
13
+ ## [v1.6.2] 19-01-2025
14
+
15
+ ### Changed
16
+
17
+ - fix: added more JSdoc comments
18
+
7
19
  ## [v1.6.1] 18-01-2025
8
20
 
9
21
  ### Changed
package/README.md CHANGED
@@ -4,8 +4,9 @@
4
4
  # Opifex
5
5
 
6
6
  Opifex aims to provide a MQTT server and MQTT client in Typescript to be used
7
- with [Deno](https://deno.land) or [NodeJS](https://nodejs.org) It has _no_ third
8
- party dependencies, it only relies on built in modules.
7
+ with [Deno](https://deno.land), [NodeJS](https://nodejs.org) or
8
+ [Bun](https://bun.sh) It has _no_ third party dependencies, it only relies on
9
+ built in modules.
9
10
 
10
11
  Its a work in progress, only does MQTT 3.1.1 and currently only has memory based
11
12
  persistence.
@@ -80,12 +81,10 @@ types of transport (e.g. Unix sockets or websocketstream) then its recommended
80
81
  to clone and modify the platform specific code in `/node` or `/deno` as well.
81
82
 
82
83
  If you want to port the platform independent client and server libs to another
83
- platform (e.g. Bun) then the platform specific code in `/node` or `/deno` might
84
- serve as inspiration.
84
+ platform then the platform specific code in `/node` or `/deno` might serve as
85
+ inspiration.
85
86
 
86
- The platform independent client and server libs should also work on Bun as they
87
- are engine independent, but the demo server and client currently do not work on
88
- Bun because of some socket incompatibilities between Bun and the rest.
87
+ Bun (as of version 1.2) is capable of running the NodeJS version.
89
88
 
90
89
  ## Naming
91
90
 
@@ -1,19 +1,48 @@
1
1
  #!/usr/bin/env -S node --experimental-strip-types
2
2
  // this server is intended to be used with https://github.com/eclipse/iottestware
3
3
 
4
- import { AuthenticationResult } from "../server/mod.ts";
4
+ /**
5
+ * MQTT Server implementation with authentication and authorization
6
+ * @module mqttServer
7
+ */
8
+ import { TcpServer } from "../node/tcpServer.ts";
5
9
  import type { Context, TAuthenticationResult, Topic } from "../server/mod.ts";
10
+ import { AuthenticationResult } from "../server/mod.ts";
6
11
  import { getArgs, logger, LogLevel, parseArgs } from "../utils/mod.ts";
7
- import { TcpServer } from "../node/tcpServer.ts";
8
12
 
13
+ /**
14
+ * UTF-8 decoder for parsing passwords
15
+ */
9
16
  const utf8Decoder = new TextDecoder();
17
+
18
+ /**
19
+ * Map storing valid username/password combinations
20
+ */
10
21
  const userTable = new Map();
11
22
  userTable.set("IoTester_1", "strong_password");
12
23
  userTable.set("IoTester_2", "strong_password");
24
+
25
+ /**
26
+ * Regular expression for validating usernames
27
+ * Allows alphanumeric characters up to 23 chars long
28
+ */
13
29
  const strictUsername = new RegExp(/^[a-zA-Z0-9]{0,23}$/);
14
- const notAuthorizedTable = new Set();
15
- notAuthorizedTable.add(["123-456-789", "eclipse/iot/tesware/0data"]);
16
30
 
31
+ /**
32
+ * Flag to enable/disable username checking
33
+ * If false, all users are allowed access
34
+ * If true, only users in userTable are allowed access
35
+ */
36
+ const checkUsername = true;
37
+
38
+ /**
39
+ * Authenticates MQTT clients
40
+ * @param {Context} _ctx - Connection context
41
+ * @param {string} clientId - Client identifier
42
+ * @param {string} username - Username to authenticate
43
+ * @param {Uint8Array} password - Password as byte array
44
+ * @returns {TAuthenticationResult} Authentication result
45
+ */
17
46
  function isAuthenticated(
18
47
  _ctx: Context,
19
48
  clientId: string,
@@ -21,31 +50,47 @@ function isAuthenticated(
21
50
  password: Uint8Array,
22
51
  ): TAuthenticationResult {
23
52
  const pwd = utf8Decoder.decode(password);
24
- logger.debug(
53
+ logger.info(
25
54
  `Verifying authentication of client '${clientId}' with username '${username}' and password '${pwd}'`,
26
55
  );
27
56
 
28
- if (!userTable.has(username)) {
29
- if (!strictUsername.test(username)) {
30
- return AuthenticationResult.badUsernameOrPassword;
31
- }
32
- }
33
- const pass = userTable.get(username);
34
- if (pwd === pass) {
57
+ if (!checkUsername) {
58
+ // allow all users access
35
59
  return AuthenticationResult.ok;
36
60
  }
61
+ // is the username valid according to MQTT specification
62
+ if (!strictUsername.test(username)) {
63
+ return AuthenticationResult.badUsernameOrPassword;
64
+ }
65
+ // Does user and password match an entry in userTable
66
+ if (userTable.has(username)) {
67
+ const pass = userTable.get(username);
68
+ if (pwd === pass) {
69
+ return AuthenticationResult.ok;
70
+ }
71
+ }
37
72
  return AuthenticationResult.notAuthorized;
38
73
  }
39
74
 
75
+ /**
76
+ * Checks if client is authorized to publish to a topic
77
+ * @param {Context} ctx - Connection context
78
+ * @param {Topic} topic - Topic to publish to
79
+ * @returns {boolean} True if authorized
80
+ */
40
81
  function isAuthorizedToPublish(ctx: Context, topic: Topic): boolean {
41
82
  logger.debug(
42
83
  `Checking authorization of client '${ctx.store?.clientId}' to publish on topic '${topic}'`,
43
84
  );
44
- // if (notAuthorizedTable.has([ctx.client,topic])){
45
- // return false;
46
- // }
47
85
  return true;
48
86
  }
87
+
88
+ /**
89
+ * Checks if client is authorized to subscribe to a topic
90
+ * @param {Context} ctx - Connection context
91
+ * @param {Topic} topic - Topic to subscribe to
92
+ * @returns {boolean} True if authorized
93
+ */
49
94
  function isAuthorizedToSubscribe(ctx: Context, topic: Topic): boolean {
50
95
  logger.debug(
51
96
  `Checking authorization of client '${ctx.store?.clientId}' to subscribe to topic '${topic}'`,
@@ -53,11 +98,17 @@ function isAuthorizedToSubscribe(ctx: Context, topic: Topic): boolean {
53
98
  return true;
54
99
  }
55
100
 
56
- /** start the server **/
101
+ /**
102
+ * Parse command line arguments and start server
103
+ */
57
104
  const { _: [portNum] } = parseArgs(getArgs());
58
105
  const port = Number(portNum ?? 1883);
59
106
  const hostname = "::";
60
107
  logger.level(LogLevel.info);
108
+
109
+ /**
110
+ * TCP Server instance with authentication and authorization handlers
111
+ */
61
112
  const tcpServer = new TcpServer({ port, hostname }, {
62
113
  handlers: {
63
114
  isAuthenticated,
package/bin/demoServer.ts CHANGED
@@ -1,15 +1,47 @@
1
1
  #!/usr/bin/env -S node --experimental-strip-types
2
+
3
+ /**
4
+ * MQTT Server implementation with authentication and authorization
5
+ * @module mqttServer
6
+ */
2
7
  import { TcpServer } from "../node/tcpServer.ts";
3
8
  import type { Context, TAuthenticationResult, Topic } from "../server/mod.ts";
4
9
  import { AuthenticationResult } from "../server/mod.ts";
5
10
  import { getArgs, logger, LogLevel, parseArgs } from "../utils/mod.ts";
6
11
 
12
+ /**
13
+ * UTF-8 decoder for parsing passwords
14
+ */
7
15
  const utf8Decoder = new TextDecoder();
16
+
17
+ /**
18
+ * Map storing valid username/password combinations
19
+ */
8
20
  const userTable = new Map();
9
21
  userTable.set("IoTester_1", "strong_password");
10
22
  userTable.set("IoTester_2", "strong_password");
11
- // const strictUsername = new RegExp(/^[a-zA-Z0-9]{0,23}$/);
12
23
 
24
+ /**
25
+ * Regular expression for validating usernames
26
+ * Allows alphanumeric characters up to 23 chars long
27
+ */
28
+ const strictUsername = new RegExp(/^[a-zA-Z0-9]{0,23}$/);
29
+
30
+ /**
31
+ * Flag to enable/disable username checking
32
+ * If false, all users are allowed access
33
+ * If true, only users in userTable are allowed access
34
+ */
35
+ const checkUsername = false;
36
+
37
+ /**
38
+ * Authenticates MQTT clients
39
+ * @param {Context} _ctx - Connection context
40
+ * @param {string} clientId - Client identifier
41
+ * @param {string} username - Username to authenticate
42
+ * @param {Uint8Array} password - Password as byte array
43
+ * @returns {TAuthenticationResult} Authentication result
44
+ */
13
45
  function isAuthenticated(
14
46
  _ctx: Context,
15
47
  clientId: string,
@@ -21,26 +53,43 @@ function isAuthenticated(
21
53
  `Verifying authentication of client '${clientId}' with username '${username}' and password '${pwd}'`,
22
54
  );
23
55
 
24
- return AuthenticationResult.ok;
25
- // if (!userTable.has(username)) {
26
- // if (!strictUsername.test(username)) {
27
- // return AuthenticationResult.badUsernameOrPassword;
28
- // }
29
- // }
30
-
31
- // const pass = userTable.get(username);
32
- // if (pwd === pass) {
33
- // return AuthenticationResult.ok;
34
- // }
35
- // return AuthenticationResult.badUsernameOrPassword;
56
+ if (!checkUsername) {
57
+ // allow all users access
58
+ return AuthenticationResult.ok;
59
+ }
60
+ // is the username valid according to MQTT specification
61
+ if (!strictUsername.test(username)) {
62
+ return AuthenticationResult.badUsernameOrPassword;
63
+ }
64
+ // Does user and password match an entry in userTable
65
+ if (userTable.has(username)) {
66
+ const pass = userTable.get(username);
67
+ if (pwd === pass) {
68
+ return AuthenticationResult.ok;
69
+ }
70
+ }
71
+ return AuthenticationResult.notAuthorized;
36
72
  }
37
73
 
74
+ /**
75
+ * Checks if client is authorized to publish to a topic
76
+ * @param {Context} ctx - Connection context
77
+ * @param {Topic} topic - Topic to publish to
78
+ * @returns {boolean} True if authorized
79
+ */
38
80
  function isAuthorizedToPublish(ctx: Context, topic: Topic): boolean {
39
81
  logger.debug(
40
82
  `Checking authorization of client '${ctx.store?.clientId}' to publish on topic '${topic}'`,
41
83
  );
42
84
  return true;
43
85
  }
86
+
87
+ /**
88
+ * Checks if client is authorized to subscribe to a topic
89
+ * @param {Context} ctx - Connection context
90
+ * @param {Topic} topic - Topic to subscribe to
91
+ * @returns {boolean} True if authorized
92
+ */
44
93
  function isAuthorizedToSubscribe(ctx: Context, topic: Topic): boolean {
45
94
  logger.debug(
46
95
  `Checking authorization of client '${ctx.store?.clientId}' to subscribe to topic '${topic}'`,
@@ -48,11 +97,17 @@ function isAuthorizedToSubscribe(ctx: Context, topic: Topic): boolean {
48
97
  return true;
49
98
  }
50
99
 
51
- /** start the server **/
100
+ /**
101
+ * Parse command line arguments and start server
102
+ */
52
103
  const { _: [portNum] } = parseArgs(getArgs());
53
104
  const port = Number(portNum ?? 1883);
54
105
  const hostname = "::";
55
106
  logger.level(LogLevel.info);
107
+
108
+ /**
109
+ * TCP Server instance with authentication and authorization handlers
110
+ */
56
111
  const tcpServer = new TcpServer({ port, hostname }, {
57
112
  handlers: {
58
113
  isAuthenticated,
package/bin/mqtt.ts CHANGED
@@ -1,4 +1,34 @@
1
1
  #!/usr/bin/env -S node --experimental-strip-types
2
+
3
+ /**
4
+ * @fileoverview MQTT command line interface that provides publish and subscribe functionality
5
+ * @module mqtt-cli
6
+ *
7
+ * @description
8
+ * This module implements a command line interface for MQTT operations:
9
+ * - Publish messages to an MQTT broker
10
+ * - Subscribe to topics on an MQTT broker
11
+ *
12
+ * Features:
13
+ * - TLS support with custom certificates
14
+ * - Authentication with username/password
15
+ * - Configurable QoS levels (0-2)
16
+ * - Message retention
17
+ * - Session persistence
18
+ * - Custom client IDs
19
+ *
20
+ * @example
21
+ * # Publish a message
22
+ * mqtt publish -t topic -m message
23
+ *
24
+ * # Subscribe to a topic
25
+ * mqtt subscribe -t topic
26
+ *
27
+ * @requires node
28
+ * @requires ../client/mod.ts
29
+ * @requires ../node/tcpClient.ts
30
+ * @requires ../utils/mod.ts
31
+ */
2
32
  import { DEFAULT_URL } from "../client/mod.ts";
3
33
  import { getFileData, TcpClient } from "../node/tcpClient.ts";
4
34
  import { getArgs, logger, LogLevel, parseArgs } from "../utils/mod.ts";
@@ -1,10 +1,22 @@
1
+ /**
2
+ * @description Object representing possible connection states
3
+ * @readonly
4
+ */
1
5
  export const ConnectionState = {
6
+ /** System is offline and not attempting connection */
2
7
  offline: "offline",
8
+ /** System is in the process of establishing a connection */
3
9
  connecting: "connecting",
10
+ /** System has an active connection */
4
11
  connected: "connected",
12
+ /** System is in the process of disconnecting */
5
13
  disconnecting: "disconnecting",
14
+ /** System has been disconnected */
6
15
  disconnected: "disconnected",
7
16
  } as const;
8
17
 
18
+ /**
19
+ * @description Type representing the possible values of ConnectionState
20
+ */
9
21
  export type TConnectionState =
10
22
  (typeof ConnectionState)[keyof typeof ConnectionState];
package/client/client.ts CHANGED
@@ -12,6 +12,11 @@ import {
12
12
 
13
13
  import { Context } from "./context.ts";
14
14
 
15
+ /**
16
+ * Generates a random client ID with the given prefix
17
+ * @param prefix - The prefix to use for the client ID
18
+ * @returns A string containing the prefix followed by a random number
19
+ */
15
20
  function generateClientId(prefix: string): string {
16
21
  return `${prefix}-${Math.random().toString().slice(-10)}`;
17
22
  }
@@ -36,8 +41,14 @@ export type PublishParameters = Omit<PublishPacket, "type" | "id">;
36
41
  /** SubscribeParameters define how to subscribe to a topic */
37
42
  export type SubscribeParameters = Omit<SubscribePacket, "type" | "id">;
38
43
 
44
+ /**
45
+ * Implements exponential backoff sleep with optional randomization
46
+ * based on https://dthain.blogspot.com/2009/02/exponential-backoff-in-distributed.html
47
+ * @param random - Whether to add randomization to the delay
48
+ * @param attempt - The attempt number (used to calculate delay)
49
+ * @returns Promise that resolves after the calculated delay
50
+ */
39
51
  function backOffSleep(random: boolean, attempt: number): Promise<void> {
40
- // based on https://dthain.blogspot.com/2009/02/exponential-backoff-in-distributed.html
41
52
  const factor = 1.5;
42
53
  const min = 1000;
43
54
  const max = 5000;
@@ -64,7 +75,6 @@ const CLIENTID_PREFIX = "opifex"; // on first connect
64
75
  * override the createConn() method to provide a
65
76
  * connection type that is supported by the subclass.
66
77
  */
67
-
68
78
  export class Client {
69
79
  protected clientIdPrefix = CLIENTID_PREFIX;
70
80
  protected numberOfRetries = DEFAULT_RETRIES;
@@ -78,11 +88,24 @@ export class Client {
78
88
  private ctx = new Context(new MemoryStore());
79
89
  private connectPacket?: ConnectPacket;
80
90
 
91
+ /**
92
+ * Creates a new MQTT client instance
93
+ */
81
94
  constructor() {
82
95
  this.clientId = generateClientId(this.clientIdPrefix);
83
96
  this.numberOfRetries = DEFAULT_RETRIES;
84
97
  }
85
98
 
99
+ /**
100
+ * Creates a new connection to the MQTT broker
101
+ * @param protocol - The protocol to use (mqtt, mqtts, etc)
102
+ * @param _hostname - The hostname to connect to
103
+ * @param _port - The port to connect to
104
+ * @param _caCerts - Optional CA certificates
105
+ * @param _cert - Optional client certificate
106
+ * @param _key - Optional client key
107
+ * @returns Promise resolving to a SockConn connection
108
+ */
86
109
  protected createConn(
87
110
  protocol: string,
88
111
  _hostname: string,
@@ -96,6 +119,10 @@ export class Client {
96
119
  throw `Unsupported protocol: ${protocol}`;
97
120
  }
98
121
 
122
+ /**
123
+ * Handles the connection process including retries and reconnection
124
+ * @returns Promise that resolves when connection is established or fails
125
+ */
99
126
  private async doConnect(): Promise<void> {
100
127
  if (!this.connectPacket) {
101
128
  return;
@@ -141,6 +168,11 @@ export class Client {
141
168
  }
142
169
  }
143
170
 
171
+ /**
172
+ * Connects to the MQTT broker
173
+ * @param params - Connection parameters
174
+ * @returns Promise resolving to authentication result
175
+ */
144
176
  connect(params: ConnectParameters = {}): Promise<TAuthenticationResult> {
145
177
  this.url = params?.url || this.url;
146
178
  this.numberOfRetries = params.numberOfRetries || this.numberOfRetries;
@@ -164,10 +196,19 @@ export class Client {
164
196
  return deferred.promise;
165
197
  }
166
198
 
199
+ /**
200
+ * Disconnects from the MQTT broker
201
+ * @returns Promise that resolves when disconnected
202
+ */
167
203
  async disconnect(): Promise<void> {
168
204
  await this.ctx.disconnect();
169
205
  }
170
206
 
207
+ /**
208
+ * Publishes a message to the MQTT broker
209
+ * @param params - Publish parameters including topic and payload
210
+ * @returns Promise that resolves when published
211
+ */
171
212
  async publish(params: PublishParameters): Promise<void> {
172
213
  const packet: PublishPacket = {
173
214
  type: PacketType.publish,
@@ -176,6 +217,11 @@ export class Client {
176
217
  await this.ctx.publish(packet);
177
218
  }
178
219
 
220
+ /**
221
+ * Subscribes to topics on the MQTT broker
222
+ * @param params - Subscribe parameters including topics
223
+ * @returns Promise that resolves when subscribed
224
+ */
179
225
  async subscribe(params: SubscribeParameters): Promise<void> {
180
226
  const packet: SubscribePacket = {
181
227
  type: PacketType.subscribe,
@@ -185,10 +231,18 @@ export class Client {
185
231
  await this.ctx.subscribe(packet);
186
232
  }
187
233
 
234
+ /**
235
+ * Gets an async iterator for received messages
236
+ * @returns AsyncGenerator yielding received publish packets
237
+ */
188
238
  async *messages(): AsyncGenerator<PublishPacket, void, unknown> {
189
239
  yield* this.ctx.incoming;
190
240
  }
191
241
 
242
+ /**
243
+ * Closes the message stream
244
+ * @param reason - Optional reason for closing
245
+ */
192
246
  closeMessages(reason?: string) {
193
247
  this.ctx.incoming.close(reason);
194
248
  }
package/client/context.ts CHANGED
@@ -166,6 +166,11 @@ export class Context {
166
166
  return false;
167
167
  }
168
168
 
169
+ // just an alias to clarify protocol flow
170
+ receivePubcomp(id: PacketId): boolean {
171
+ return this.receivePuback(id);
172
+ }
173
+
169
174
  receiveSuback(id: PacketId, returnCodes: ReturnCodes): boolean {
170
175
  const unresolvedMap = this.unresolvedSubscribe;
171
176
  if (unresolvedMap.has(id)) {
@@ -2,6 +2,12 @@ import type { Context } from "../context.ts";
2
2
  import { ConnectionState } from "../ConnectionState.ts";
3
3
  import { AuthenticationResultByNumber, type ConnackPacket } from "../deps.ts";
4
4
 
5
+ /**
6
+ * Handles the CONNACK packet received from the MQTT broker
7
+ * @param packet - The CONNACK packet containing the connection acknowledgment
8
+ * @param ctx - The connection context
9
+ * @returns Promise that resolves when handling is complete
10
+ */
5
11
  export async function handleConnack(packet: ConnackPacket, ctx: Context) {
6
12
  if (packet.returnCode === 0) {
7
13
  ctx.connectionState = ConnectionState.connected;
@@ -21,6 +21,13 @@ import type {
21
21
  UnsubackPacket,
22
22
  } from "../deps.ts";
23
23
 
24
+ /**
25
+ * Handles incoming MQTT packets based on the connection state and packet type
26
+ * @param ctx - The MQTT client context containing connection state and other information
27
+ * @param packet - The MQTT packet to handle
28
+ * @throws Error if an unexpected packet is received before connection is established
29
+ * @throws Error if an unexpected packet type is received after connection
30
+ */
24
31
  export async function handlePacket(
25
32
  ctx: Context,
26
33
  packet: AnyPacket,
@@ -1,8 +1,14 @@
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 packet processing for QoS level 1 PUBLISH responses
6
+ * @param ctx - The MQTT client context
7
+ * @param packet - The PUBACK packet received from the broker
8
+ * @description
9
+ * - Removes the packet ID from the list of pending outgoing messages
10
+ * - Completes the corresponding publish operation
11
+ */
6
12
  export function handlePuback(ctx: Context, packet: PubackPacket): void {
7
13
  const id = packet.id;
8
14
  ctx.store.pendingOutgoing.delete(id);
@@ -1,13 +1,19 @@
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 store
7
+ * @param packet - The PUBCOMP packet received from the broker
8
+ * @description
9
+ * This function processes PUBCOMP packets which are the fourth and final packet
10
+ * in the QoS 2 protocol exchange. It removes the message from pendingAckOutgoing queue
11
+ * Completes the corresponding publish operation
12
+ */
7
13
  export function handlePubcomp(ctx: Context, packet: PubcompPacket): void {
8
14
  const id = packet.id;
9
15
  if (ctx.store.pendingAckOutgoing.has(id)) {
10
16
  ctx.store.pendingAckOutgoing.delete(id);
11
- ctx.receivePuback(id);
17
+ ctx.receivePubcomp(id);
12
18
  }
13
19
  }
@@ -1,8 +1,12 @@
1
1
  import type { Context } from "../context.ts";
2
2
  import { PacketType, type PublishPacket } from "../deps.ts";
3
3
 
4
- // Incoming publish
5
-
4
+ /**
5
+ * Handles incoming MQTT publish packets based on their QoS level
6
+ * @param ctx - The MQTT connection context
7
+ * @param packet - The incoming publish packet to handle
8
+ * @returns Promise that resolves when packet is handled
9
+ */
6
10
  export async function handlePublish(ctx: Context, packet: PublishPacket) {
7
11
  const qos = packet.qos || 0;
8
12
  if (qos === 0) {
@@ -1,12 +1,19 @@
1
1
  import type { Context } from "../context.ts";
2
2
  import { PacketType, type PubrecPacket, type PubrelPacket } from "../deps.ts";
3
3
 
4
- // A PUBREC Packet is the response to a PUBLISH Packet with QoS 2.
5
- // It is the second packet of the QoS 2 protocol exchange.
6
-
7
- // Discard message, Store PUBREC received <Packet Identifier>
8
- // send PUBREL <Packet Identifier>
9
-
4
+ /**
5
+ * Handles PUBREC (Publish Received) packets for QoS 2 message flow
6
+ * It is the second packet of the QoS 2 protocol exchange.
7
+ * @param ctx - The connection context containing message stores and send function
8
+ * @param packet - The received PUBREC packet
9
+ * @description
10
+ * When a PUBREC packet is received:
11
+ * 1. Checks if there is a pending outgoing message with matching packet ID
12
+ * 2. Creates a PUBREL packet to acknowledge the PUBREC
13
+ * 3. Add the packet ID to pendingAckOutgoing store
14
+ * 4. Removes the message from pendingOutgoing store
15
+ * 5. Sends the PUBREL packet
16
+ */
10
17
  export async function handlePubrec(
11
18
  ctx: Context,
12
19
  packet: PubrecPacket,