@seriousme/opifex 2.1.0 → 2.3.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 (147) hide show
  1. package/README.md +7 -6
  2. package/bin/mqtt.ts +1 -1
  3. package/client/client.ts +58 -7
  4. package/client/context.ts +2 -2
  5. package/client/deps.ts +3 -3
  6. package/client/mod.ts +2 -2
  7. package/connNext/connNext.ts +44 -0
  8. package/deno/README.md +1 -1
  9. package/deno/denoConnToNext.ts +104 -0
  10. package/deno/mod.ts +7 -0
  11. package/deno/tcpClient.ts +8 -24
  12. package/deno/tcpServer.test.ts +14 -103
  13. package/deno/tcpServer.ts +14 -6
  14. package/deno/tlsServer.test.ts +14 -58
  15. package/deno/tlsServer.ts +14 -6
  16. package/deno/wsServer.test.ts +15 -61
  17. package/deno/wsServer.ts +77 -35
  18. package/dev_utils/asyncStreamPair.ts +114 -0
  19. package/dev_utils/asyncStreamToNext.ts +171 -0
  20. package/dev_utils/connNextPair.test.ts +179 -0
  21. package/dev_utils/mockConn.ts +115 -29
  22. package/dev_utils/mockServer.ts +3 -4
  23. package/dev_utils/mod.ts +1 -2
  24. package/dev_utils/testClient.ts +64 -0
  25. package/dev_utils/webStreamToNext.ts +187 -0
  26. package/dist/bin/mqtt.js +1 -1
  27. package/dist/bin/mqtt.js.map +1 -1
  28. package/dist/client/client.d.ts +21 -3
  29. package/dist/client/client.d.ts.map +1 -1
  30. package/dist/client/client.js +43 -5
  31. package/dist/client/client.js.map +1 -1
  32. package/dist/client/context.d.ts +2 -2
  33. package/dist/client/context.d.ts.map +1 -1
  34. package/dist/client/deps.d.ts +1 -1
  35. package/dist/client/deps.d.ts.map +1 -1
  36. package/dist/client/mod.d.ts +1 -1
  37. package/dist/client/mod.d.ts.map +1 -1
  38. package/dist/connNext/connNext.d.ts +43 -0
  39. package/dist/connNext/connNext.d.ts.map +1 -0
  40. package/dist/connNext/connNext.js +2 -0
  41. package/dist/connNext/connNext.js.map +1 -0
  42. package/dist/examples/authHandler.test.js +2 -2
  43. package/dist/examples/authHandler.test.js.map +1 -1
  44. package/dist/examples/mqtt+mqtts.test.d.ts +2 -0
  45. package/dist/examples/mqtt+mqtts.test.d.ts.map +1 -0
  46. package/dist/examples/mqtt+mqtts.test.js +104 -0
  47. package/dist/examples/mqtt+mqtts.test.js.map +1 -0
  48. package/dist/examples/mqtt.test.js +2 -2
  49. package/dist/examples/mqtt.test.js.map +1 -1
  50. package/dist/examples/mqtts.test.js +2 -2
  51. package/dist/examples/mqtts.test.js.map +1 -1
  52. package/dist/mqttConn/mqttConn.d.ts +5 -6
  53. package/dist/mqttConn/mqttConn.d.ts.map +1 -1
  54. package/dist/mqttConn/mqttConn.js +2 -3
  55. package/dist/mqttConn/mqttConn.js.map +1 -1
  56. package/dist/mqttPacket/protocolLevels.d.ts +2 -0
  57. package/dist/mqttPacket/protocolLevels.d.ts.map +1 -1
  58. package/dist/node/nodeSocketToNext.d.ts +20 -0
  59. package/dist/node/nodeSocketToNext.d.ts.map +1 -0
  60. package/dist/node/nodeSocketToNext.js +161 -0
  61. package/dist/node/nodeSocketToNext.js.map +1 -0
  62. package/dist/node/tcpClient.d.ts +4 -6
  63. package/dist/node/tcpClient.d.ts.map +1 -1
  64. package/dist/node/tcpClient.js +6 -21
  65. package/dist/node/tcpClient.js.map +1 -1
  66. package/dist/node/tcpServer.d.ts +8 -4
  67. package/dist/node/tcpServer.d.ts.map +1 -1
  68. package/dist/node/tcpServer.js +14 -6
  69. package/dist/node/tcpServer.js.map +1 -1
  70. package/dist/node/tlsServer.d.ts +7 -3
  71. package/dist/node/tlsServer.d.ts.map +1 -1
  72. package/dist/node/tlsServer.js +13 -5
  73. package/dist/node/tlsServer.js.map +1 -1
  74. package/dist/server/context.d.ts +5 -5
  75. package/dist/server/context.d.ts.map +1 -1
  76. package/dist/server/context.js +4 -1
  77. package/dist/server/context.js.map +1 -1
  78. package/dist/server/deps.d.ts +1 -1
  79. package/dist/server/deps.d.ts.map +1 -1
  80. package/dist/server/mod.d.ts +1 -1
  81. package/dist/server/mod.d.ts.map +1 -1
  82. package/dist/server/server.d.ts +3 -3
  83. package/dist/server/server.d.ts.map +1 -1
  84. package/dist/server/server.js +3 -3
  85. package/dist/utils/utils.d.ts +1 -2
  86. package/dist/utils/utils.d.ts.map +1 -1
  87. package/dist/utils/utils.js +3 -2
  88. package/dist/utils/utils.js.map +1 -1
  89. package/dist/web/webSocketToNext.d.ts +3 -0
  90. package/dist/web/webSocketToNext.d.ts.map +1 -0
  91. package/dist/web/webSocketToNext.js +160 -0
  92. package/dist/web/webSocketToNext.js.map +1 -0
  93. package/dist/web/wsClient.d.ts +8 -3
  94. package/dist/web/wsClient.d.ts.map +1 -1
  95. package/dist/web/wsClient.js +20 -6
  96. package/dist/web/wsClient.js.map +1 -1
  97. package/dist/web/wsClient.test.js +15 -0
  98. package/dist/web/wsClient.test.js.map +1 -1
  99. package/examples/README.md +21 -0
  100. package/examples/authHandler.test.ts +2 -2
  101. package/examples/mqtt+mqtts.test.ts +122 -0
  102. package/examples/mqtt.test.ts +2 -2
  103. package/examples/mqtts.test.ts +2 -2
  104. package/examples/webSocket.test.ts +10 -64
  105. package/examples/webSocketServer.ts +98 -0
  106. package/mqttConn/mqttConn.test.ts +6 -6
  107. package/mqttConn/mqttConn.ts +8 -9
  108. package/mqttPacket/protocolLevels.ts +2 -0
  109. package/node/README.md +1 -1
  110. package/node/mod.ts +7 -0
  111. package/node/{socket-node-stream.ts → nodeSocketToNext.ts} +78 -65
  112. package/node/tcpClient.ts +13 -27
  113. package/node/tcpServer.test.ts +12 -58
  114. package/node/tcpServer.ts +18 -8
  115. package/node/tlsServer.test.ts +11 -58
  116. package/node/tlsServer.ts +17 -7
  117. package/package.json +8 -2
  118. package/server/context.ts +9 -6
  119. package/server/deps.ts +1 -1
  120. package/server/mod.ts +1 -1
  121. package/server/server.ts +6 -6
  122. package/utils/utils.ts +3 -2
  123. package/web/webSocketToNext.ts +183 -0
  124. package/web/wsClient.test.ts +22 -0
  125. package/web/wsClient.ts +22 -9
  126. package/dist/node/wrapNodeSocket.d.ts +0 -12
  127. package/dist/node/wrapNodeSocket.d.ts.map +0 -1
  128. package/dist/node/wrapNodeSocket.js +0 -36
  129. package/dist/node/wrapNodeSocket.js.map +0 -1
  130. package/dist/socket/socket.d.ts +0 -40
  131. package/dist/socket/socket.d.ts.map +0 -1
  132. package/dist/socket/socket.js +0 -94
  133. package/dist/socket/socket.js.map +0 -1
  134. package/dist/web/webSocketStream.d.ts +0 -29
  135. package/dist/web/webSocketStream.d.ts.map +0 -1
  136. package/dist/web/webSocketStream.js +0 -69
  137. package/dist/web/webSocketStream.js.map +0 -1
  138. package/dist/web/wrapWebSocket.d.ts +0 -12
  139. package/dist/web/wrapWebSocket.d.ts.map +0 -1
  140. package/dist/web/wrapWebSocket.js +0 -41
  141. package/dist/web/wrapWebSocket.js.map +0 -1
  142. package/node/socket-node-stream.test.ts +0 -322
  143. package/node/wrapNodeSocket.ts +0 -44
  144. package/socket/socket.test.ts +0 -91
  145. package/socket/socket.ts +0 -124
  146. package/web/webSocketStream.ts +0 -97
  147. package/web/wrapWebSocket.ts +0 -52
package/README.md CHANGED
@@ -111,10 +111,11 @@ can be found in the [examples](/examples/) folder.
111
111
  encode and decode packets.
112
112
 
113
113
  2. On top of mqttPacket sits the MQTT connection module
114
- ([mqttConn/mod.ts](mqttConn/mod.ts)) that reads packets from a Readable
115
- stream and writes them to Writable stream. It will take care of incomplete
116
- and/or malformed packets. mqttConn provides an async iterable that can be
117
- awaited for new packets.
114
+ ([mqttConn/mod.ts](mqttConn/mod.ts)) that reads packets from and writes them
115
+ to a [ConnNext](connNext) adapter. It will take care of incomplete and/or
116
+ malformed packets. mqttConn provides an async iterable that can be awaited
117
+ for new packets. A ConnNext adapter per transport provides async
118
+ read/write/close/address functionality on top of the various transports.
118
119
 
119
120
  3. On top of mqttConn live the MQTT server ([server/mod.ts](server/mod.ts)) and
120
121
  MQTT client ([client/mod.ts](client/mod.ts)) that take care of the MQTT
@@ -122,8 +123,8 @@ can be found in the [examples](/examples/) folder.
122
123
  another type of packet will be accepted. Both follow a similar model of
123
124
  implementation where for each packet that is received a handler is invoked
124
125
  which then triggers the next step. Server and client are totally independent
125
- of the technical implementation of the connection and only need a socketConn
126
- ([socket](socket)) to be able to work.
126
+ of the technical implementation of the connection and only need a
127
+ [ConnNext](connNext) connection to be able to work.
127
128
 
128
129
  4. As the server needs to be able to serve multiple clients at the same time, it
129
130
  maintains a context ([server/context.ts](server/context.ts)) per client to
package/bin/mqtt.ts CHANGED
@@ -278,7 +278,7 @@ async function publish() {
278
278
  qos: parseQos(publishArgs.qos),
279
279
  });
280
280
  logger.debug("Published!");
281
- client.disconnect();
281
+ await client.disconnect();
282
282
  logger.debug("Disconnected !");
283
283
  } catch (err) {
284
284
  if (err instanceof Error) {
package/client/client.ts CHANGED
@@ -10,9 +10,9 @@ import {
10
10
  import type {
11
11
  AuthenticationResult,
12
12
  ConnectPacket,
13
+ ConnNext,
13
14
  ProtocolLevel,
14
15
  PublishPacket,
15
- SockConn,
16
16
  Store,
17
17
  SubscribePacket,
18
18
  } from "./deps.ts";
@@ -118,6 +118,7 @@ function normalizeError(err: unknown): Error {
118
118
  * connection type that is supported by the subclass.
119
119
  */
120
120
  export class Client {
121
+ /** callback to handle v5 extended authentication, see /examples folder */
121
122
  public onAuth: (
122
123
  authMethod: string,
123
124
  authData: Uint8Array,
@@ -168,7 +169,6 @@ export class Client {
168
169
  const cStore = store ? store : new MemoryStore();
169
170
  this.ctx = new Context(cStore, this);
170
171
  this.clientId = generateClientId(this.clientIdPrefix);
171
- this.numberOfRetries = DEFAULT_RETRIES;
172
172
  }
173
173
 
174
174
  /**
@@ -187,14 +187,58 @@ export class Client {
187
187
  return this.connectUrl;
188
188
  }
189
189
 
190
+ /**
191
+ * Placeholder for MQTT, override in subclass
192
+ * @param _hostname
193
+ * @param _port
194
+ */
195
+ protected connectMQTT(
196
+ _hostname: string,
197
+ _port: number,
198
+ ): Promise<ConnNext> {
199
+ throw new Error(`Unsupported protocol: mqtt`);
200
+ }
201
+
202
+ /**
203
+ * Connect using MQTT over TLS
204
+ * @param hostname the name of the host to connect to
205
+ * @param port the port number
206
+ * @param caCerts the CA certificates to trust
207
+ * @param cert the clients certificate
208
+ * @param key the clients private key
209
+ * @returns a ConnNext object
210
+ */
211
+ protected connectMQTTS(
212
+ _hostname: string,
213
+ _port: number,
214
+ _caCerts?: string[],
215
+ _cert?: string,
216
+ _key?: string,
217
+ ): Promise<ConnNext> {
218
+ throw new Error(`Unsupported protocol: mqtts`);
219
+ }
220
+
190
221
  /**
191
222
  * Creates a new connection to the MQTT broker
192
- * @returns Promise resolving to a SockConn connection
223
+ * Override this in a subclass if you need other protocols
224
+ * @returns Promise resolving to a ConnNext connection
193
225
  */
194
- protected createConn(): Promise<SockConn> {
226
+ protected createConn(): Promise<ConnNext> {
195
227
  // if you need to support alternative connection types just
196
228
  // overload this method in your subclass
197
- throw new Error(`Unsupported protocol: ${this.url.protocol}`);
229
+ const { protocol, hostname, port: portStr } = this.connectUrl;
230
+ const port = Number(portStr);
231
+ const caCerts = this.caCerts;
232
+ const cert = this.cert;
233
+ const key = this.key;
234
+
235
+ if (protocol === "mqtts:") {
236
+ return this.connectMQTTS(hostname, port, caCerts, cert, key);
237
+ }
238
+ if (protocol === "mqtt:") {
239
+ return this.connectMQTT(hostname, port);
240
+ }
241
+ throw new Error(`Unsupported protocol: ${protocol}`);
198
242
  }
199
243
 
200
244
  /**
@@ -232,7 +276,14 @@ export class Client {
232
276
  logger.debug({ lastMessage });
233
277
  }
234
278
 
235
- if (tryConnect && (isReconnect || attempt < this.numberOfRetries)) {
279
+ logger.debug("tryConnect", {
280
+ tryConnect,
281
+ isReconnect,
282
+ attempt,
283
+ retries: this.numberOfRetries,
284
+ });
285
+
286
+ if (tryConnect && (isReconnect || (attempt < this.numberOfRetries))) {
236
287
  await backOffSleep(true, attempt);
237
288
  if (!isReconnect) {
238
289
  attempt++;
@@ -255,7 +306,7 @@ export class Client {
255
306
  */
256
307
  connect(params: ConnectParameters = {}): Promise<AuthenticationResult> {
257
308
  this.connectUrl = params?.url || this.connectUrl;
258
- this.numberOfRetries = params.numberOfRetries || this.numberOfRetries;
309
+ this.numberOfRetries = params.numberOfRetries ?? this.numberOfRetries;
259
310
  this.caCerts = params?.caCerts;
260
311
  this.cert = params?.cert;
261
312
  this.key = params?.key;
package/client/context.ts CHANGED
@@ -10,12 +10,12 @@ import type {
10
10
  AnyPacket,
11
11
  AuthenticationResult,
12
12
  ConnectPacket,
13
+ ConnNext,
13
14
  PacketId,
14
15
  ProtocolLevel,
15
16
  PublishPacket,
16
17
  ReasonCode,
17
18
  ReturnCodes,
18
- SockConn,
19
19
  Store,
20
20
  SubscribePacket,
21
21
  UnsubscribePacket,
@@ -169,7 +169,7 @@ export class Context {
169
169
  }
170
170
 
171
171
  async handleConnection(
172
- conn: SockConn,
172
+ conn: ConnNext,
173
173
  connectPacket: ConnectPacket,
174
174
  ): Promise<boolean> {
175
175
  this.mqttConn = new MqttConn({ conn });
package/client/deps.ts CHANGED
@@ -36,12 +36,12 @@ export {
36
36
 
37
37
  export { MqttConn } from "../mqttConn/mqttConn.ts";
38
38
  export type {
39
+ ConnAddr,
40
+ ConnNext,
39
41
  NetAddr,
40
- SockAddr,
41
- SockConn,
42
42
  UnixAddr,
43
43
  VsockAddr,
44
- } from "../socket/socket.ts";
44
+ } from "../connNext/connNext.ts";
45
45
  export { Timer } from "../timer/timer.ts";
46
46
  export { BufferedAsyncIterable, Deferred, logger } from "../utils/mod.ts";
47
47
  export { MemoryStore } from "./store/memoryStore.ts";
package/client/mod.ts CHANGED
@@ -11,11 +11,11 @@ export {
11
11
  export { logger, MQTTLevel, ReasonCode } from "./deps.ts";
12
12
  export type {
13
13
  AuthenticationResult,
14
+ ConnAddr,
15
+ ConnNext,
14
16
  NetAddr,
15
17
  ProtocolLevel,
16
18
  QoS,
17
- SockAddr,
18
- SockConn,
19
19
  UnixAddr,
20
20
  VsockAddr,
21
21
  } from "./deps.ts";
@@ -0,0 +1,44 @@
1
+ /** Net addresses */
2
+ export type NetAddr = {
3
+ /** the IP-based transport protocol */
4
+ transport: "tcp" | "udp";
5
+ /** the hostname */
6
+ hostname: string;
7
+ /** the port number */
8
+ port: number;
9
+ };
10
+ /** Unix addresses */
11
+ export type UnixAddr = {
12
+ /** the Unix domain socket transport protocol */
13
+ transport: "unix" | "unixpacket";
14
+ /** the file system path to the socket to connect to */
15
+ path: string;
16
+ };
17
+ /** vSock addresses */
18
+ export type VsockAddr = {
19
+ /** the VSOCK transport protocol */
20
+ transport: "vsock";
21
+ /** the context identifier (CID) of the peer */
22
+ cid: number;
23
+ /** the port number */
24
+ port: number;
25
+ };
26
+
27
+ /** connection addresses */
28
+ export type ConnAddr = NetAddr | UnixAddr | VsockAddr | undefined;
29
+ /** connection descriptor. */
30
+
31
+ export type ConnNext = {
32
+ /** the "nextStream" to read from */
33
+ read(length?: number): Promise<Uint8Array | null>;
34
+ /** the "nextStream" to write to */
35
+ write(data: Uint8Array): Promise<void>;
36
+ /** close the connection */
37
+ close(): void;
38
+ /** Async Iterator for streaming per chunck */
39
+ [Symbol.asyncIterator](): AsyncIterableIterator<Uint8Array>;
40
+ /** The local address of the connection */
41
+ localAddr?: ConnAddr;
42
+ /** The remote address of the connection */
43
+ remoteAddr?: ConnAddr;
44
+ };
package/deno/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Running Opifex server/client on Deno
2
2
 
3
- These instructions specifify how to run [Opifex](README.md) on Deno. To run
3
+ These instructions specifify how to run [Opifex](../README.md) on Deno. To run
4
4
  Opifex on NodeJS see the [instructions](../node/README.md)
5
5
 
6
6
  ## Playing around
@@ -0,0 +1,104 @@
1
+ import type { ConnAddr, ConnNext } from "../connNext/connNext.ts";
2
+
3
+ export function denoConnToNext(conn: Deno.Conn): ConnNext {
4
+ // Convert Deno network addresses to custom ConnAddr format
5
+ const formatAddr = (addr: Deno.Addr): ConnAddr => {
6
+ if (addr.transport === "tcp" || addr.transport === "udp") {
7
+ return {
8
+ transport: addr.transport,
9
+ hostname: addr.hostname,
10
+ port: addr.port,
11
+ };
12
+ }
13
+ const unixAddr = addr as Deno.UnixAddr;
14
+ return {
15
+ transport: "unix",
16
+ path: unixAddr.path,
17
+ };
18
+ };
19
+
20
+ // Utilize a BYOB (Bring Your Own Buffer) reader on Deno's readable stream
21
+ // This enables exact sizing and zero-copy reads on the kernel buffer
22
+ const reader = conn.readable.getReader({ mode: "byob" });
23
+ const writer = conn.writable.getWriter();
24
+
25
+ return {
26
+ remoteAddr: formatAddr(conn.remoteAddr),
27
+ localAddr: formatAddr(conn.localAddr),
28
+
29
+ /**
30
+ * Read exactly 'length' bytes directly into a Uint8Array buffer
31
+ */
32
+ async read(length?: number): Promise<Uint8Array | null> {
33
+ try {
34
+ // If no length is provided, read a default chunk size (e.g., 64KB)
35
+ const bytesToRead = length ?? 65536;
36
+ let buffer = new Uint8Array(bytesToRead);
37
+ let bytesRead = 0;
38
+
39
+ while (bytesRead < bytesToRead) {
40
+ // Pass the unfilled section of the buffer to Deno's kernel reader
41
+ const view = new Uint8Array(
42
+ buffer.buffer,
43
+ buffer.byteOffset + bytesRead,
44
+ bytesToRead - bytesRead,
45
+ );
46
+
47
+ const { value, done } = await reader.read(view);
48
+
49
+ if (done) {
50
+ // Connection closed before all data could be read
51
+ if (bytesRead === 0) return null;
52
+ // Return any partial data read prior to closure
53
+ return buffer.subarray(0, bytesRead);
54
+ }
55
+
56
+ // BYOB returns the populated ArrayBufferView
57
+ buffer = new Uint8Array(value.buffer);
58
+ bytesRead += value.byteLength;
59
+
60
+ // If no explicit length was requested, return the first chunk immediately
61
+ if (length === undefined) {
62
+ return buffer.subarray(0, bytesRead);
63
+ }
64
+ }
65
+
66
+ return buffer;
67
+ } catch {
68
+ return null;
69
+ }
70
+ },
71
+
72
+ /**
73
+ * Write via Deno's WritableStream with built-in backpressure handling
74
+ */
75
+ async write(data: Uint8Array): Promise<void> {
76
+ // writer.write automatically pauses the promise when the TCP pipe is full
77
+ await writer.write(data);
78
+ },
79
+
80
+ /**
81
+ * Gracefully close reader, writer, and socket connection
82
+ */
83
+ close(): void {
84
+ try {
85
+ writer.releaseLock();
86
+ reader.releaseLock();
87
+ conn.close();
88
+ } catch {
89
+ /* swallow */
90
+ }
91
+ },
92
+
93
+ /**
94
+ * Async Iterator for Deno
95
+ */
96
+ async *[Symbol.asyncIterator]() {
97
+ while (true) {
98
+ const chunk = await this.read();
99
+ if (chunk === null) break;
100
+ yield chunk;
101
+ }
102
+ },
103
+ };
104
+ }
package/deno/mod.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Opifex default exports for Deno
3
+ * @module
4
+ */
5
+
6
+ export * from "../client/mod.ts";
7
+ export * from "../server/mod.ts";
package/deno/tcpClient.ts CHANGED
@@ -5,7 +5,8 @@
5
5
  */
6
6
 
7
7
  import { Client, logger } from "../client/mod.ts";
8
- import type { SockConn } from "../client/mod.ts";
8
+ import type { ConnNext } from "../client/mod.ts";
9
+ import { denoConnToNext } from "./denoConnToNext.ts";
9
10
 
10
11
  /**
11
12
  * Fetches data from a file and returns it as a string
@@ -39,12 +40,12 @@ export class TcpClient extends Client {
39
40
  * @param hostname the name of the host to connect to
40
41
  * @param port the port number
41
42
  */
42
- protected async connectMQTT(
43
+ protected override async connectMQTT(
43
44
  hostname: string,
44
45
  port = 1883,
45
- ): Promise<Deno.TcpConn> {
46
+ ): Promise<ConnNext> {
46
47
  logger.debug({ hostname, port });
47
- return await Deno.connect({ hostname, port });
48
+ return denoConnToNext(await Deno.connect({ hostname, port }));
48
49
  }
49
50
 
50
51
  /**
@@ -56,13 +57,13 @@ export class TcpClient extends Client {
56
57
  * @param key the clients private key
57
58
  * @returns a Deno.TlsConn object
58
59
  */
59
- protected async connectMQTTS(
60
+ protected override async connectMQTTS(
60
61
  hostname: string,
61
62
  port = 8883,
62
63
  caCerts?: string[],
63
64
  cert?: string,
64
65
  key?: string,
65
- ): Promise<Deno.TlsConn> {
66
+ ): Promise<ConnNext> {
66
67
  logger.debug({ hostname, port, caCerts, cert });
67
68
  const connectOpts = {
68
69
  hostname,
@@ -71,23 +72,6 @@ export class TcpClient extends Client {
71
72
  ...(cert !== undefined && { cert }),
72
73
  ...(key !== undefined && { key }),
73
74
  };
74
- return await Deno.connectTls(connectOpts);
75
- }
76
-
77
- /** createConn is used by the base client class */
78
- protected override createConn(): Promise<SockConn> {
79
- const { protocol, hostname, port: portStr } = this.connectUrl;
80
- const port = Number(portStr);
81
- const caCerts = this.caCerts;
82
- const cert = this.cert;
83
- const key = this.key;
84
-
85
- if (protocol === "mqtts:") {
86
- return this.connectMQTTS(hostname, port, caCerts, cert, key);
87
- }
88
- if (protocol === "mqtt:") {
89
- return this.connectMQTT(hostname, port);
90
- }
91
- throw new Error(`Unsupported protocol: ${protocol}`);
75
+ return denoConnToNext(await Deno.connectTls(connectOpts));
92
76
  }
93
77
  }
@@ -2,8 +2,10 @@ import { test } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { TcpClient } from "./tcpClient.ts";
4
4
  import { TcpServer } from "./tcpServer.ts";
5
- import { delay, logger, LogLevel } from "../utils/mod.ts";
6
- import type { ProtocolLevel, PublishPacket, QoS } from "../mqttPacket/mod.ts";
5
+ import { logger, LogLevel } from "../utils/mod.ts";
6
+ import type { ProtocolLevel } from "../mqttPacket/mod.ts";
7
+ import { testClient } from "../dev_utils/testClient.ts";
8
+ import { MqttServer } from "../server/mod.ts";
7
9
 
8
10
  logger.level(LogLevel.info);
9
11
 
@@ -24,64 +26,10 @@ test("Deno: Test pubSub using client and server", async () => {
24
26
  };
25
27
 
26
28
  const client = new TcpClient();
27
-
28
- await client.connect(params);
29
- assert(true, "Client connected to server");
30
-
31
- const publishSet: { topic: string; qos: QoS }[] = [
32
- { topic: "t0@q0", qos: 0 },
33
- { topic: "t1@q0", qos: 0 },
34
- { topic: "t2@q0", qos: 0 },
35
- { topic: "t0@q1", qos: 1 },
36
- { topic: "t1@q1", qos: 1 },
37
- { topic: "t2@q1", qos: 1 },
38
- { topic: "t0@q2", qos: 2 },
39
- { topic: "t1@q2", qos: 2 },
40
- { topic: "t2@q2", qos: 2 },
41
- ];
42
-
43
- const subscriptions = publishSet.map((item) => ({
44
- topicFilter: item.topic,
45
- qos: item.qos,
46
- }));
47
- await client.subscribe({
48
- subscriptions,
49
- });
50
-
51
- // the IIFE ensures message reception runs in parallel
52
- logger.verbose("Start receiving");
53
- const received: PublishPacket[] = [];
54
- (async () => {
55
- for await (const item of client.messages()) {
56
- logger.verbose("Receiving:", item.topic, "--", item.qos);
57
- received.push(item);
58
- }
59
- })();
60
- // end of IIFE
61
- for (const item of publishSet) {
62
- logger.verbose("Publishing:", item.topic, "--", item.qos);
63
- await client.publish({
64
- topic: item.topic,
65
- qos: item.qos,
66
- payload: new Uint8Array([0x01]),
67
- });
68
- }
69
-
70
- await delay(100);
71
- logger.verbose("Disconnect client");
72
- await client.disconnect();
73
-
74
- logger.verbose("Check completeness");
75
- for (const item of publishSet) {
76
- const found = received.find((f) =>
77
- f.topic == item.topic && f.qos === item.qos
78
- );
79
- logger.verbose("Found:", item.topic, "--", item.qos);
80
- assert(found, `${item.topic} -- ${item.qos}`);
81
- }
29
+ await testClient(client, params);
82
30
 
83
31
  logger.verbose("Stop server");
84
- server.stop();
32
+ await server.stop();
85
33
  });
86
34
 
87
35
  test("Deno: Test subscription persistence after reconnect", async () => {
@@ -99,51 +47,14 @@ test("Deno: Test subscription persistence after reconnect", async () => {
99
47
  };
100
48
 
101
49
  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 () => {
116
- for await (const item of client.messages()) {
117
- received.push(item);
118
- }
119
- })();
50
+ await testClient(client, params);
120
51
 
121
- // Disconnect client
122
- await client.disconnect();
123
- await delay(100);
124
-
125
- // Reconnect client
126
- await client.connect(params);
127
- await delay(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 delay(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
- );
52
+ logger.verbose("Stop server");
53
+ await server.stop();
54
+ });
147
55
 
148
- server.stop();
56
+ test("Deno: Passing a mqttServer works", () => {
57
+ const mqttServer = new MqttServer({});
58
+ const server = new TcpServer({ port: 0 }, mqttServer);
59
+ assert(server.mqttServer === mqttServer);
149
60
  });
package/deno/tcpServer.ts CHANGED
@@ -5,24 +5,30 @@
5
5
  */
6
6
 
7
7
  import { MqttServer } from "../server/mod.ts";
8
+ import { denoConnToNext } from "./denoConnToNext.ts";
8
9
  import type { MqttServerOptions } from "../server/mod.ts";
9
10
 
10
11
  /**
11
12
  * TCP server that wraps a MqttServer, see the /examples folder
12
13
  */
13
14
  export class TcpServer {
15
+ /** the MqttServer instance used by the server */
16
+ readonly mqttServer: MqttServer;
14
17
  private listener: Deno.Listener<Deno.Conn>;
15
- private mqttServer: MqttServer;
16
18
 
17
19
  /**
18
20
  * Create a new TCP server
19
21
  */
20
22
  constructor(
21
23
  serverOptions: Deno.TcpListenOptions,
22
- mqttOptions: MqttServerOptions,
24
+ mqttOptions: MqttServerOptions | MqttServer,
23
25
  ) {
24
26
  this.listener = Deno.listen(serverOptions);
25
- this.mqttServer = new MqttServer(mqttOptions);
27
+ if (mqttOptions instanceof MqttServer) {
28
+ this.mqttServer = mqttOptions;
29
+ } else {
30
+ this.mqttServer = new MqttServer(mqttOptions);
31
+ }
26
32
  }
27
33
 
28
34
  /**
@@ -30,15 +36,17 @@ export class TcpServer {
30
36
  */
31
37
  async start(): Promise<void> {
32
38
  for await (const conn of this.listener) {
33
- this.mqttServer.serve(conn);
39
+ this.mqttServer.serve(denoConnToNext(conn));
34
40
  }
35
41
  }
36
42
 
37
43
  /**
38
44
  * Stop listening
39
45
  */
40
- stop(): void {
41
- this.mqttServer.close(true);
46
+ async stop(opts = { closeMqtt: true }): Promise<void> {
47
+ if (opts.closeMqtt) {
48
+ await this.mqttServer.close();
49
+ }
42
50
  this.listener.close();
43
51
  }
44
52