@seriousme/opifex 2.2.0 → 2.4.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 (154) hide show
  1. package/README.md +7 -6
  2. package/bin/mqtt.ts +1 -1
  3. package/client/client.ts +62 -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/baseConnNext.ts +123 -0
  8. package/connNext/connNext.ts +44 -0
  9. package/deno/README.md +1 -1
  10. package/deno/denoConnToNext.ts +104 -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 +82 -32
  18. package/dev_utils/WebStreamEndpoint.ts +5 -0
  19. package/dev_utils/asyncStreamEndpoint.ts +5 -0
  20. package/dev_utils/asyncStreamPair.ts +109 -0
  21. package/dev_utils/asyncStreamPairToNext.ts +45 -0
  22. package/dev_utils/asyncStreamToNext.ts +64 -0
  23. package/dev_utils/connNextPair.test.ts +179 -0
  24. package/dev_utils/mockConn.ts +59 -29
  25. package/dev_utils/mockServer.ts +3 -4
  26. package/dev_utils/mod.ts +1 -2
  27. package/dev_utils/testClient.ts +64 -0
  28. package/dev_utils/webStreamPair.ts +3 -7
  29. package/dev_utils/webStreamPairToNext.ts +48 -0
  30. package/dev_utils/webStreamToNext.ts +54 -0
  31. package/dist/bin/mqtt.js +1 -1
  32. package/dist/bin/mqtt.js.map +1 -1
  33. package/dist/client/client.d.ts +24 -3
  34. package/dist/client/client.d.ts.map +1 -1
  35. package/dist/client/client.js +45 -5
  36. package/dist/client/client.js.map +1 -1
  37. package/dist/client/context.d.ts +2 -2
  38. package/dist/client/context.d.ts.map +1 -1
  39. package/dist/client/deps.d.ts +1 -1
  40. package/dist/client/deps.d.ts.map +1 -1
  41. package/dist/client/mod.d.ts +1 -1
  42. package/dist/client/mod.d.ts.map +1 -1
  43. package/dist/connNext/baseConnNext.d.ts +20 -0
  44. package/dist/connNext/baseConnNext.d.ts.map +1 -0
  45. package/dist/connNext/baseConnNext.js +95 -0
  46. package/dist/connNext/baseConnNext.js.map +1 -0
  47. package/dist/connNext/connNext.d.ts +43 -0
  48. package/dist/connNext/connNext.d.ts.map +1 -0
  49. package/dist/connNext/connNext.js +2 -0
  50. package/dist/connNext/connNext.js.map +1 -0
  51. package/dist/examples/authHandler.test.js +2 -2
  52. package/dist/examples/authHandler.test.js.map +1 -1
  53. package/dist/examples/mqtt+mqtts.test.d.ts +2 -0
  54. package/dist/examples/mqtt+mqtts.test.d.ts.map +1 -0
  55. package/dist/examples/mqtt+mqtts.test.js +104 -0
  56. package/dist/examples/mqtt+mqtts.test.js.map +1 -0
  57. package/dist/examples/mqtt.test.js +2 -2
  58. package/dist/examples/mqtt.test.js.map +1 -1
  59. package/dist/examples/mqtts.test.js +2 -2
  60. package/dist/examples/mqtts.test.js.map +1 -1
  61. package/dist/mqttConn/mqttConn.d.ts +5 -6
  62. package/dist/mqttConn/mqttConn.d.ts.map +1 -1
  63. package/dist/mqttConn/mqttConn.js +2 -3
  64. package/dist/mqttConn/mqttConn.js.map +1 -1
  65. package/dist/node/nodeSocketToNext.d.ts +17 -0
  66. package/dist/node/nodeSocketToNext.d.ts.map +1 -0
  67. package/dist/node/nodeSocketToNext.js +105 -0
  68. package/dist/node/nodeSocketToNext.js.map +1 -0
  69. package/dist/node/tcpClient.d.ts +4 -6
  70. package/dist/node/tcpClient.d.ts.map +1 -1
  71. package/dist/node/tcpClient.js +6 -21
  72. package/dist/node/tcpClient.js.map +1 -1
  73. package/dist/node/tcpServer.d.ts +8 -4
  74. package/dist/node/tcpServer.d.ts.map +1 -1
  75. package/dist/node/tcpServer.js +14 -6
  76. package/dist/node/tcpServer.js.map +1 -1
  77. package/dist/node/tlsServer.d.ts +7 -3
  78. package/dist/node/tlsServer.d.ts.map +1 -1
  79. package/dist/node/tlsServer.js +13 -5
  80. package/dist/node/tlsServer.js.map +1 -1
  81. package/dist/server/context.d.ts +5 -5
  82. package/dist/server/context.d.ts.map +1 -1
  83. package/dist/server/context.js +4 -1
  84. package/dist/server/context.js.map +1 -1
  85. package/dist/server/deps.d.ts +1 -1
  86. package/dist/server/deps.d.ts.map +1 -1
  87. package/dist/server/mod.d.ts +1 -1
  88. package/dist/server/mod.d.ts.map +1 -1
  89. package/dist/server/server.d.ts +3 -3
  90. package/dist/server/server.d.ts.map +1 -1
  91. package/dist/server/server.js +3 -3
  92. package/dist/utils/utils.d.ts +1 -2
  93. package/dist/utils/utils.d.ts.map +1 -1
  94. package/dist/utils/utils.js +3 -2
  95. package/dist/utils/utils.js.map +1 -1
  96. package/dist/web/webSocketToNext.d.ts +20 -0
  97. package/dist/web/webSocketToNext.d.ts.map +1 -0
  98. package/dist/web/webSocketToNext.js +125 -0
  99. package/dist/web/webSocketToNext.js.map +1 -0
  100. package/dist/web/wsClient.d.ts +3 -3
  101. package/dist/web/wsClient.d.ts.map +1 -1
  102. package/dist/web/wsClient.js +22 -7
  103. package/dist/web/wsClient.js.map +1 -1
  104. package/dist/web/wsClient.test.js +15 -0
  105. package/dist/web/wsClient.test.js.map +1 -1
  106. package/examples/README.md +21 -0
  107. package/examples/authHandler.test.ts +2 -2
  108. package/examples/mqtt+mqtts.test.ts +122 -0
  109. package/examples/mqtt.test.ts +2 -2
  110. package/examples/mqtts.test.ts +2 -2
  111. package/examples/webSocket.test.ts +10 -64
  112. package/examples/webSocketServer.ts +98 -0
  113. package/mqttConn/mqttConn.test.ts +6 -6
  114. package/mqttConn/mqttConn.ts +8 -9
  115. package/node/README.md +1 -1
  116. package/node/nodeSocketToNext.ts +124 -0
  117. package/node/tcpClient.ts +13 -27
  118. package/node/tcpServer.test.ts +12 -58
  119. package/node/tcpServer.ts +18 -8
  120. package/node/tlsServer.test.ts +11 -58
  121. package/node/tlsServer.ts +17 -7
  122. package/package.json +4 -3
  123. package/server/context.ts +9 -6
  124. package/server/deps.ts +1 -1
  125. package/server/mod.ts +1 -1
  126. package/server/server.ts +6 -6
  127. package/utils/utils.ts +3 -2
  128. package/web/webSocketToNext.ts +146 -0
  129. package/web/wsClient.test.ts +22 -0
  130. package/web/wsClient.ts +29 -9
  131. package/dev_utils/webStreamPair.test.ts +0 -171
  132. package/dist/node/wrapNodeSocket.d.ts +0 -12
  133. package/dist/node/wrapNodeSocket.d.ts.map +0 -1
  134. package/dist/node/wrapNodeSocket.js +0 -36
  135. package/dist/node/wrapNodeSocket.js.map +0 -1
  136. package/dist/socket/socket.d.ts +0 -58
  137. package/dist/socket/socket.d.ts.map +0 -1
  138. package/dist/socket/socket.js +0 -99
  139. package/dist/socket/socket.js.map +0 -1
  140. package/dist/web/webSocketStream.d.ts +0 -29
  141. package/dist/web/webSocketStream.d.ts.map +0 -1
  142. package/dist/web/webSocketStream.js +0 -69
  143. package/dist/web/webSocketStream.js.map +0 -1
  144. package/dist/web/wrapWebSocket.d.ts +0 -12
  145. package/dist/web/wrapWebSocket.d.ts.map +0 -1
  146. package/dist/web/wrapWebSocket.js +0 -41
  147. package/dist/web/wrapWebSocket.js.map +0 -1
  148. package/node/socket-node-stream.test.ts +0 -322
  149. package/node/socket-node-stream.ts +0 -174
  150. package/node/wrapNodeSocket.ts +0 -44
  151. package/socket/socket.test.ts +0 -91
  152. package/socket/socket.ts +0 -149
  153. package/web/webSocketStream.ts +0 -97
  154. 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";
@@ -51,6 +51,8 @@ export type ConnectParameters = {
51
51
  key?: string | undefined;
52
52
  /** number of retries before stopping connection attempts */
53
53
  numberOfRetries?: number | undefined;
54
+ /** maximum size of receive buffer, only used by webSockets for now */
55
+ maxBufferSize?: number | undefined;
54
56
  /** connect options */
55
57
  options?: ConnectOptions | undefined;
56
58
  };
@@ -159,6 +161,8 @@ export class Client {
159
161
  protected key?: string | undefined;
160
162
  /** the MQTT clientId */
161
163
  protected clientId: string;
164
+ /** the maximum number of bytes buffered by a ws/wss client */
165
+ protected maxBufferSize: number | undefined;
162
166
  private ctx: Context;
163
167
  private connectPacket?: ConnectPacket;
164
168
 
@@ -169,7 +173,6 @@ export class Client {
169
173
  const cStore = store ? store : new MemoryStore();
170
174
  this.ctx = new Context(cStore, this);
171
175
  this.clientId = generateClientId(this.clientIdPrefix);
172
- this.numberOfRetries = DEFAULT_RETRIES;
173
176
  }
174
177
 
175
178
  /**
@@ -188,14 +191,58 @@ export class Client {
188
191
  return this.connectUrl;
189
192
  }
190
193
 
194
+ /**
195
+ * Placeholder for MQTT, override in subclass
196
+ * @param _hostname
197
+ * @param _port
198
+ */
199
+ protected connectMQTT(
200
+ _hostname: string,
201
+ _port: number,
202
+ ): Promise<ConnNext> {
203
+ throw new Error(`Unsupported protocol: mqtt`);
204
+ }
205
+
206
+ /**
207
+ * Connect using MQTT over TLS
208
+ * @param hostname the name of the host to connect to
209
+ * @param port the port number
210
+ * @param caCerts the CA certificates to trust
211
+ * @param cert the clients certificate
212
+ * @param key the clients private key
213
+ * @returns a ConnNext object
214
+ */
215
+ protected connectMQTTS(
216
+ _hostname: string,
217
+ _port: number,
218
+ _caCerts?: string[],
219
+ _cert?: string,
220
+ _key?: string,
221
+ ): Promise<ConnNext> {
222
+ throw new Error(`Unsupported protocol: mqtts`);
223
+ }
224
+
191
225
  /**
192
226
  * Creates a new connection to the MQTT broker
193
- * @returns Promise resolving to a SockConn connection
227
+ * Override this in a subclass if you need other protocols
228
+ * @returns Promise resolving to a ConnNext connection
194
229
  */
195
- protected createConn(): Promise<SockConn> {
230
+ protected createConn(): Promise<ConnNext> {
196
231
  // if you need to support alternative connection types just
197
232
  // overload this method in your subclass
198
- throw new Error(`Unsupported protocol: ${this.url.protocol}`);
233
+ const { protocol, hostname, port: portStr } = this.connectUrl;
234
+ const port = Number(portStr);
235
+ const caCerts = this.caCerts;
236
+ const cert = this.cert;
237
+ const key = this.key;
238
+
239
+ if (protocol === "mqtts:") {
240
+ return this.connectMQTTS(hostname, port, caCerts, cert, key);
241
+ }
242
+ if (protocol === "mqtt:") {
243
+ return this.connectMQTT(hostname, port);
244
+ }
245
+ throw new Error(`Unsupported protocol: ${protocol}`);
199
246
  }
200
247
 
201
248
  /**
@@ -233,7 +280,14 @@ export class Client {
233
280
  logger.debug({ lastMessage });
234
281
  }
235
282
 
236
- if (tryConnect && (isReconnect || attempt < this.numberOfRetries)) {
283
+ logger.debug("tryConnect", {
284
+ tryConnect,
285
+ isReconnect,
286
+ attempt,
287
+ retries: this.numberOfRetries,
288
+ });
289
+
290
+ if (tryConnect && (isReconnect || (attempt < this.numberOfRetries))) {
237
291
  await backOffSleep(true, attempt);
238
292
  if (!isReconnect) {
239
293
  attempt++;
@@ -256,10 +310,11 @@ export class Client {
256
310
  */
257
311
  connect(params: ConnectParameters = {}): Promise<AuthenticationResult> {
258
312
  this.connectUrl = params?.url || this.connectUrl;
259
- this.numberOfRetries = params.numberOfRetries || this.numberOfRetries;
313
+ this.numberOfRetries = params.numberOfRetries ?? this.numberOfRetries;
260
314
  this.caCerts = params?.caCerts;
261
315
  this.cert = params?.cert;
262
316
  this.key = params?.key;
317
+ this.maxBufferSize = params?.maxBufferSize;
263
318
  const options = Object.assign(
264
319
  {
265
320
  keepAlive: this.keepAlive,
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,123 @@
1
+ import type { ConnAddr, ConnNext } from "../connNext/connNext.ts";
2
+
3
+ export abstract class BaseConnNext implements ConnNext {
4
+ abstract remoteAddr?: ConnAddr;
5
+ abstract localAddr?: ConnAddr;
6
+
7
+ protected chunks: Uint8Array[] = [];
8
+ protected queueOffset = 0;
9
+ protected closed = false;
10
+ private isPulling = false;
11
+
12
+ abstract write(data: Uint8Array): Promise<void>;
13
+ abstract close(): void;
14
+
15
+ /** Specifiek per bron: haal pas daadwerkelijk 1 chunk op als de basisklasse erom vraagt */
16
+ protected abstract pullNextChunk(): Promise<Uint8Array | null>;
17
+
18
+ isClosed(): boolean {
19
+ return this.closed;
20
+ }
21
+
22
+ protected getBufferedBytes(): number {
23
+ let total = 0;
24
+ for (let i = 0; i < this.chunks.length; i++) {
25
+ total += i === 0
26
+ ? this.chunks[i]!.byteLength - this.queueOffset
27
+ : this.chunks[i]!.byteLength;
28
+ }
29
+ return total;
30
+ }
31
+
32
+ /** Zorgt ervoor dat we data ophalen op exact het moment dat het nodig is */
33
+ private async ensureData(length?: number): Promise<void> {
34
+ while (
35
+ !this.closed &&
36
+ (this.getBufferedBytes() === 0 ||
37
+ (length !== undefined && this.getBufferedBytes() < length))
38
+ ) {
39
+ if (this.isPulling) {
40
+ // Voorkom gelijktijdige pulls als er al eentje loopt
41
+ await new Promise((resolve) => setTimeout(resolve, 5));
42
+ continue;
43
+ }
44
+
45
+ this.isPulling = true;
46
+ try {
47
+ const chunk = await this.pullNextChunk();
48
+ if (chunk === null || chunk.byteLength === 0) {
49
+ this.closed = true;
50
+ break;
51
+ }
52
+ this.chunks.push(chunk);
53
+ } catch {
54
+ this.closed = true;
55
+ } finally {
56
+ this.isPulling = false;
57
+ }
58
+ }
59
+ }
60
+
61
+ async read(length?: number): Promise<Uint8Array | null> {
62
+ if (this.closed && this.getBufferedBytes() === 0) {
63
+ return null;
64
+ }
65
+
66
+ if (length === 0) {
67
+ return new Uint8Array(0);
68
+ }
69
+
70
+ // Pull exact zolang tot we genoeg data hebben voor deze read()
71
+ await this.ensureData(length);
72
+
73
+ if (this.getBufferedBytes() === 0) {
74
+ return null;
75
+ }
76
+
77
+ // Onbegrensd lezen (geen lengte opgegeven)
78
+ if (length === undefined) {
79
+ const firstChunk = this.chunks.shift()!;
80
+ const result = firstChunk.subarray(this.queueOffset);
81
+ this.queueOffset = 0;
82
+ return result;
83
+ }
84
+
85
+ // Exacte lengte uitlezen uit de buffer
86
+ const bytesToTake = Math.min(length, this.getBufferedBytes());
87
+ const result = new Uint8Array(bytesToTake);
88
+ let bytesCopied = 0;
89
+
90
+ while (bytesCopied < bytesToTake && this.chunks.length > 0) {
91
+ const currentChunk = this.chunks[0]!;
92
+ const bytesNeeded = bytesToTake - bytesCopied;
93
+ const bytesAvailable = currentChunk.byteLength - this.queueOffset;
94
+
95
+ if (bytesAvailable <= bytesNeeded) {
96
+ result.set(currentChunk.subarray(this.queueOffset), bytesCopied);
97
+ bytesCopied += bytesAvailable;
98
+ this.chunks.shift();
99
+ this.queueOffset = 0;
100
+ } else {
101
+ result.set(
102
+ currentChunk.subarray(
103
+ this.queueOffset,
104
+ this.queueOffset + bytesNeeded,
105
+ ),
106
+ bytesCopied,
107
+ );
108
+ bytesCopied += bytesNeeded;
109
+ this.queueOffset += bytesNeeded;
110
+ }
111
+ }
112
+
113
+ return bytesCopied === 0 ? null : result;
114
+ }
115
+
116
+ async *[Symbol.asyncIterator]() {
117
+ while (true) {
118
+ const chunk = await this.read();
119
+ if (chunk === null) break;
120
+ yield chunk;
121
+ }
122
+ }
123
+ }
@@ -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/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
  }