@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
@@ -0,0 +1,122 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { TcpClient } from "@seriousme/opifex/tcpClient";
4
+ import { TlsServer } from "@seriousme/opifex/tlsServer";
5
+ import { MqttServer } from "@seriousme/opifex/server";
6
+ import { delay, logger, LogLevel } from "@seriousme/opifex/utils";
7
+ import type { PublishPacket, QoS } from "@seriousme/opifex/mqttPacket";
8
+ import { generateLocalhostCerts } from "../dev_utils/generateCert.ts";
9
+ import { TcpServer } from "@seriousme/opifex/tcpServer";
10
+
11
+ logger.level(LogLevel.info);
12
+
13
+ test("Test pubSub using TLS client and a TCP client", async function () {
14
+ // the localhost certs generate here are great for testing but lack securiity
15
+ // use something like LetsEncrypt for any serious server
16
+ // generateLocalhostCerts is a dev tool and relies on node-forge as a (dev)dependency
17
+ const { key, cert, caCert } = generateLocalhostCerts();
18
+ // first create the mqttServer, so we can pass it to multiple protocol servers
19
+ const mqttServer = new MqttServer({});
20
+
21
+ const tlsServer = new TlsServer({ port: 0, key, cert }, mqttServer);
22
+ const tcpServer = new TcpServer({ port: 0 }, mqttServer);
23
+ // .start() combines instance creation and async initialization
24
+ tlsServer.start();
25
+ tcpServer.start();
26
+
27
+ logger.info(
28
+ `TLS server running on port: ${tlsServer.port}, address: ${tlsServer.address}`,
29
+ );
30
+ logger.info(
31
+ `TCP server running on port: ${tcpServer.port}, address: ${tcpServer.address}`,
32
+ );
33
+ // pick the correct hostname
34
+ const tlsHostname = tlsServer.address === "0.0.0.0"
35
+ ? "127.0.0.1"
36
+ : tlsServer.address;
37
+ const tcpHostname = tcpServer.address === "0.0.0.0"
38
+ ? "127.0.0.1"
39
+ : tcpServer.address;
40
+
41
+ const tlsParams = {
42
+ url: new URL(`mqtts://${tlsHostname}:${tlsServer.port}`),
43
+ clientId: "I am a TLS client",
44
+ numberOfRetries: 0,
45
+ // since we signed our server cert ourselves we need to add the caCert
46
+ // this is not required if you use a public Certificate Authority
47
+ caCerts: [caCert],
48
+ };
49
+
50
+ const tcpParams = {
51
+ url: new URL(`mqtt://${tcpHostname}:${tcpServer.port}`),
52
+ clientId: "I am a TCP client",
53
+ numberOfRetries: 0,
54
+ };
55
+
56
+ const tlsClient = new TcpClient(); // TcpClient can also handle TLS
57
+ const tcpClient = new TcpClient();
58
+ await tlsClient.connect(tlsParams);
59
+ await tcpClient.connect(tcpParams);
60
+ logger.info("tlsClient connected to server at", tlsClient.url.toString());
61
+ logger.info("tcpClient connected to server at", tcpClient.url.toString());
62
+
63
+ const publishSet: { topic: string; qos: QoS }[] = [
64
+ { topic: "t0@q0", qos: 0 },
65
+ { topic: "t1@q0", qos: 0 },
66
+ { topic: "t2@q0", qos: 0 },
67
+ { topic: "t0@q1", qos: 1 },
68
+ { topic: "t1@q1", qos: 1 },
69
+ { topic: "t2@q1", qos: 1 },
70
+ { topic: "t0@q2", qos: 2 },
71
+ { topic: "t1@q2", qos: 2 },
72
+ { topic: "t2@q2", qos: 2 },
73
+ ];
74
+
75
+ const subscriptions = publishSet.map((item) => ({
76
+ topicFilter: item.topic,
77
+ qos: item.qos,
78
+ }));
79
+ await tcpClient.subscribe({
80
+ subscriptions,
81
+ });
82
+
83
+ // the IIFE ensures message reception runs in parallel
84
+ logger.info("TCP client Start receiving");
85
+ const received: PublishPacket[] = [];
86
+ (async function () {
87
+ for await (const item of tcpClient.messages()) {
88
+ logger.verbose("Receiving:", item.topic, "--", item.qos);
89
+ received.push(item);
90
+ }
91
+ })();
92
+ // end of IIFE
93
+ logger.info("TLS client Start publishing");
94
+ for (const item of publishSet) {
95
+ logger.verbose("Publishing:", item.topic, "--", item.qos);
96
+ await tlsClient.publish({
97
+ topic: item.topic,
98
+ qos: item.qos,
99
+ payload: new Uint8Array([0x01]),
100
+ });
101
+ }
102
+
103
+ await delay(100);
104
+ logger.info("Disconnect TLS client");
105
+ await tlsClient.disconnect();
106
+ logger.info("Disconnect TCP client");
107
+ await tcpClient.disconnect();
108
+ logger.info("Check completeness");
109
+ for (const item of publishSet) {
110
+ const found = received.find((f) =>
111
+ f.topic == item.topic && f.qos === item.qos
112
+ );
113
+ logger.verbose("Found:", item.topic, "--", item.qos);
114
+ assert(found, `${item.topic} -- ${item.qos}`);
115
+ }
116
+
117
+ // just for demo, you can keep the MQTT server alive on the TCP server wile the TLSserver closes
118
+ logger.info("Stop TLS server");
119
+ await tlsServer.stop();
120
+ logger.info("Stop TCP server");
121
+ await tcpServer.stop();
122
+ });
@@ -30,7 +30,7 @@ test("Test pubSub using TCP client and server using memoryPersistence", async fu
30
30
 
31
31
  const client = new TcpClient();
32
32
  await client.connect(params);
33
- logger.info("Client connected to server at", client.url);
33
+ logger.info("Client connected to server at", client.url.toString());
34
34
 
35
35
  const publishSet: { topic: string; qos: QoS }[] = [
36
36
  { topic: "t0@q0", qos: 0 },
@@ -85,5 +85,5 @@ test("Test pubSub using TCP client and server using memoryPersistence", async fu
85
85
  }
86
86
 
87
87
  logger.info("Stop server");
88
- server.stop();
88
+ await server.stop();
89
89
  });
@@ -41,7 +41,7 @@ test("Test pubSub using TLS client and server and sqlitePersistence", async func
41
41
 
42
42
  const client = new TcpClient();
43
43
  await client.connect(params);
44
- logger.info("Client connected to server at", client.url);
44
+ logger.info("Client connected to server at", client.url.toString());
45
45
 
46
46
  const publishSet: { topic: string; qos: QoS }[] = [
47
47
  { topic: "t0@q0", qos: 0 },
@@ -96,5 +96,5 @@ test("Test pubSub using TLS client and server and sqlitePersistence", async func
96
96
  }
97
97
 
98
98
  logger.info("Stop server");
99
- server.stop();
99
+ await server.stop();
100
100
  });
@@ -1,8 +1,7 @@
1
1
  import { test } from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { delay, logger, LogLevel } from "@seriousme/opifex/utils";
4
-
5
- import type { PublishPacket, QoS } from "@seriousme/opifex/mqttPacket";
3
+ import { logger, LogLevel } from "@seriousme/opifex/utils";
4
+ import { runTest } from "./public/testRunner.js";
6
5
 
7
6
  logger.level(LogLevel.info);
8
7
 
@@ -27,68 +26,15 @@ test("Deno: Test pubSub using WebSocket client and server and memoryPersistence"
27
26
  ? "127.0.0.1"
28
27
  : server.address;
29
28
 
30
- const params = {
31
- url: new URL(`ws://${hostname}:${server.port}`),
32
- numberOfRetries: 0,
33
- };
34
-
35
- const client = new WsClient();
36
- await client.connect(params);
37
- logger.info("Client connected to server at", client.url);
38
-
39
- const publishSet: { topic: string; qos: QoS }[] = [
40
- { topic: "t0@q0", qos: 0 },
41
- { topic: "t1@q0", qos: 0 },
42
- { topic: "t2@q0", qos: 0 },
43
- { topic: "t0@q1", qos: 1 },
44
- { topic: "t1@q1", qos: 1 },
45
- { topic: "t2@q1", qos: 1 },
46
- { topic: "t0@q2", qos: 2 },
47
- { topic: "t1@q2", qos: 2 },
48
- { topic: "t2@q2", qos: 2 },
49
- ];
50
-
51
- const subscriptions = publishSet.map((item) => ({
52
- topicFilter: item.topic,
53
- qos: item.qos,
54
- }));
55
- await client.subscribe({
56
- subscriptions,
29
+ await runTest({
30
+ WsClient,
31
+ protocol: "ws",
32
+ hostname,
33
+ port: server.port,
34
+ logger: logger.verbose,
35
+ assert,
57
36
  });
58
-
59
- // the IIFE ensures message reception runs in parallel
60
- logger.info("Start receiving");
61
- const received: PublishPacket[] = [];
62
- (async function () {
63
- for await (const item of client.messages()) {
64
- logger.verbose("Receiving:", item.topic, "--", item.qos);
65
- received.push(item);
66
- }
67
- })();
68
- // end of IIFE
69
- for (const item of publishSet) {
70
- logger.verbose("Publishing:", item.topic, "--", item.qos);
71
- await client.publish({
72
- topic: item.topic,
73
- qos: item.qos,
74
- payload: new Uint8Array([0x01]),
75
- });
76
- }
77
-
78
- await delay(100);
79
- logger.info("Disconnect client");
80
- await client.disconnect();
81
-
82
- logger.info("Check completeness");
83
- for (const item of publishSet) {
84
- const found = received.find((f) =>
85
- f.topic == item.topic && f.qos === item.qos
86
- );
87
- logger.verbose("Found:", item.topic, "--", item.qos);
88
- assert(found, `${item.topic} -- ${item.qos}`);
89
- }
90
-
91
37
  logger.info("Stop server");
92
- server.stop();
38
+ await server.stop();
93
39
  }
94
40
  });
@@ -0,0 +1,98 @@
1
+ /**
2
+ * a simple websocket server to verify browser compatibility
3
+ */
4
+ import { extname, join, resolve } from "node:path";
5
+ import { logger } from "@seriousme/opifex/utils";
6
+ import { MqttServer } from "@seriousme/opifex/server";
7
+ import { mqttOverWS } from "@seriousme/opifex/wsServer";
8
+ import { LogLevel } from "@seriousme/opifex/utils";
9
+ import type { RequestHandler } from "@seriousme/opifex/wsServer";
10
+
11
+ logger.level(LogLevel.info);
12
+
13
+ // Nodejs does not natively support WebSockets so we only continue running Deno
14
+ if (typeof Deno === "undefined") {
15
+ logger.error("Native WebSocket server only supported on Deno");
16
+ process.exit(1);
17
+ }
18
+ const currentDir = import.meta.dirname || "./";
19
+ const PUBLIC_DIR = resolve(join(currentDir, "/public"));
20
+ const listenOptions = { port: 8080 };
21
+ const mqttOptions = {};
22
+ const mqttServer = new MqttServer(mqttOptions);
23
+
24
+ const mimeTypes = new Map<string, string>([
25
+ [".html", "text/html; charset=utf-8"],
26
+ [".css", "text/css; charset=utf-8"],
27
+ [".js", "text/javascript; charset=utf-8"],
28
+ ]);
29
+
30
+ function w3cLogger(handler: RequestHandler): RequestHandler {
31
+ return async (req, info) => {
32
+ const res = await handler(req, info);
33
+ const hostname = res.headers.get("upgrade")
34
+ ? "unknown"
35
+ : info.remoteAddr.hostname;
36
+ logger.info(W3CLogLine(req, res, hostname));
37
+ return res;
38
+ };
39
+ }
40
+
41
+ function W3CLogLine(req: Request, res: Response, hostname: string): string {
42
+ const now = new Date();
43
+
44
+ // format date to UTC (YYYY-MM-DD en HH:MM:SS)
45
+ const date = now.toISOString().split("T")[0];
46
+ const time = now.toISOString().split("T")[1]?.substring(0, 8);
47
+
48
+ const url = new URL(req.url);
49
+ const clientIp = hostname ?? "127.0.0.1";
50
+ const method = req.method;
51
+ const uriStem = url.pathname;
52
+ const uriQuery = url.search ? url.search : "-";
53
+ const status = res.status;
54
+ const bytesSent = res.headers.get("content-length") ?? "-";
55
+
56
+ return `${date} ${time} ${clientIp} ${method} ${uriStem} ${uriQuery} ${status} ${bytesSent}`;
57
+ }
58
+
59
+ const requestHandler: RequestHandler = async (req) => {
60
+ const url = new URL(req.url);
61
+
62
+ const pathname = url.pathname.endsWith("/")
63
+ ? url.pathname + "index.html"
64
+ : url.pathname;
65
+
66
+ const ext = extname(pathname);
67
+ const mimeType = mimeTypes.get(ext) ?? "application/octet-stream";
68
+
69
+ const targetPath = resolve(join(PUBLIC_DIR, pathname));
70
+ logger.debug({ targetPath });
71
+
72
+ try {
73
+ if (!targetPath.startsWith(PUBLIC_DIR)) {
74
+ // someone is trying to escape the public folder
75
+ return new Response("403 Forbidden", { status: 403 });
76
+ }
77
+
78
+ const file = await Deno.readFile(targetPath);
79
+ return new Response(file, {
80
+ status: 200,
81
+ headers: {
82
+ "content-type": mimeType,
83
+ },
84
+ });
85
+ } catch (err) {
86
+ if (err instanceof Deno.errors.NotFound) {
87
+ return new Response("404 Not Found", { status: 404 });
88
+ }
89
+ return new Response("500 Internal Server Error", { status: 500 });
90
+ }
91
+ };
92
+
93
+ const server = Deno.serve(
94
+ listenOptions,
95
+ w3cLogger(mqttOverWS(mqttServer, "/mqtt", requestHandler)),
96
+ );
97
+
98
+ await server.finished;
@@ -1,6 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import { test } from "node:test";
3
- import { createMockSockConn } from "../dev_utils/mod.ts";
3
+ import { createMockConn } from "../dev_utils/mod.ts";
4
4
  import { encode, MQTTLevel, PacketType } from "../mqttPacket/mod.ts";
5
5
  import { MqttConn, MqttConnError } from "./mqttConn.ts";
6
6
  import type { AnyPacket, CodecOpts } from "../mqttPacket/mod.ts";
@@ -50,7 +50,7 @@ test("MqttConn should act as asyncIterator", async () => {
50
50
  const publish = encode(publishPacket, codecOptsV4);
51
51
  const disconnect = encode(disconnectPacket, codecOptsV4);
52
52
 
53
- const conn = createMockSockConn(
53
+ const conn = createMockConn(
54
54
  [connect, publish, disconnect],
55
55
  new Uint8Array(),
56
56
  );
@@ -68,7 +68,7 @@ test("MqttConn should act as asyncIterator", async () => {
68
68
  });
69
69
 
70
70
  test("MqttConn should close on malformed length", async () => {
71
- const conn = createMockSockConn([new Uint8Array([1, 175])], new Uint8Array());
71
+ const conn = createMockConn([new Uint8Array([1, 175])], new Uint8Array());
72
72
  const mqttConn = new MqttConn({ conn });
73
73
 
74
74
  const packets: AnyPacket[] = [];
@@ -86,7 +86,7 @@ test("MqttConn should close on failed packets", async () => {
86
86
  const publish = encode(publishPacket, codecOptsV4);
87
87
  const brokenPublish = publish.slice(0, 7);
88
88
 
89
- const conn = createMockSockConn([connect, brokenPublish], new Uint8Array());
89
+ const conn = createMockConn([connect, brokenPublish], new Uint8Array());
90
90
  const mqttConn = new MqttConn({ conn });
91
91
 
92
92
  const packets: AnyPacket[] = [];
@@ -103,7 +103,7 @@ test("MqttConn should close on failed packets", async () => {
103
103
  test("MqttConn should close on packets too large", async () => {
104
104
  const connect = encode(connectPacket, codecOptsV4);
105
105
 
106
- const conn = createMockSockConn([connect], new Uint8Array());
106
+ const conn = createMockConn([connect], new Uint8Array());
107
107
  const mqttConn = new MqttConn({ conn, maxIncomingPacketSize: 20 });
108
108
 
109
109
  const packets: AnyPacket[] = [];
@@ -119,7 +119,7 @@ test("MqttConn should close on packets too large", async () => {
119
119
  test("MqttConn should be writable", async () => {
120
120
  const connect = encode(connectPacket, codecOptsV4);
121
121
  const writer = new Uint8Array(24);
122
- const conn = createMockSockConn([connect], writer);
122
+ const conn = createMockConn([connect], writer);
123
123
  const mqttConn = new MqttConn({ conn });
124
124
  await mqttConn.send(connectPacket);
125
125
  assert.deepStrictEqual(writer, connect);
@@ -16,8 +16,7 @@ import {
16
16
  } from "../mqttPacket/mod.ts";
17
17
 
18
18
  import { assert } from "../utils/mod.ts";
19
- import type { SockConn } from "../socket/socket.ts";
20
- import { Conn } from "../socket/socket.ts";
19
+ import type { ConnNext } from "../connNext/connNext.ts";
21
20
 
22
21
  /**
23
22
  * Common MQTT connection error messages
@@ -45,7 +44,7 @@ export type SendResult =
45
44
  * @returns Single byte as number
46
45
  * @throws Error if EOF reached unexpectedly
47
46
  */
48
- async function readByte(conn: Conn): Promise<number> {
47
+ async function readByte(conn: ConnNext): Promise<number> {
49
48
  const buff = await conn.read(1);
50
49
  assert(buff !== null, MqttConnError.UnexpectedEof);
51
50
  assert(buff !== undefined, MqttConnError.UnexpectedEof);
@@ -59,7 +58,7 @@ async function readByte(conn: Conn): Promise<number> {
59
58
  * @param buf Buffer to read into
60
59
  * @throws Error if EOF reached unexpectedly
61
60
  */
62
- async function readFull(conn: Conn, length: number): Promise<Uint8Array> {
61
+ async function readFull(conn: ConnNext, length: number): Promise<Uint8Array> {
63
62
  const buff = await conn.read(length);
64
63
  assert(buff !== null, MqttConnError.UnexpectedEof);
65
64
  assert(buff.byteLength === length, MqttConnError.UnexpectedEof);
@@ -74,7 +73,7 @@ async function readFull(conn: Conn, length: number): Promise<Uint8Array> {
74
73
  * @throws Error if packet is invalid or too large
75
74
  */
76
75
  export async function readPacket(
77
- conn: Conn,
76
+ conn: ConnNext,
78
77
  codecOpts: CodecOpts,
79
78
  maxConnectPacketSize: number,
80
79
  ): Promise<AnyPacket> {
@@ -113,7 +112,7 @@ export async function readPacket(
113
112
  */
114
113
  export class MqttConn {
115
114
  /** Underlying connection */
116
- readonly conn: Conn;
115
+ readonly conn: ConnNext;
117
116
  /** Maximum Connect packet size */
118
117
  readonly maxConnectPacketSize: number;
119
118
  /** codecOpts */
@@ -134,10 +133,10 @@ export class MqttConn {
134
133
  */
135
134
  constructor(
136
135
  options:
137
- & { conn: SockConn; maxConnectPacketSize?: number }
136
+ & { conn: ConnNext; maxConnectPacketSize?: number }
138
137
  & Partial<CodecOpts>,
139
138
  ) {
140
- this.conn = new Conn(options.conn);
139
+ this.conn = options.conn;
141
140
  this.maxConnectPacketSize = options.maxConnectPacketSize ??
142
141
  DEFAULT_MAX_PACKETSIZE;
143
142
  this.codecOpts = {
@@ -154,7 +153,7 @@ export class MqttConn {
154
153
  return this._reason;
155
154
  }
156
155
 
157
- /** Get remoteAdress */
156
+ /** Get remoteAddress */
158
157
  get remoteAddress(): string {
159
158
  if (this.conn.remoteAddr?.transport === "tcp") {
160
159
  return this.conn.remoteAddr.hostname;
package/node/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Running Opifex server/client on NodeJS
2
2
 
3
- These instructions specifify how to run [Opifex](README.md) on NodeJS. To run
3
+ These instructions specifify how to run [Opifex](../README.md) on NodeJS. To run
4
4
  Opifex on Deno see the [instructions](../deno/README.md)
5
5
 
6
6
  ## Playing around
@@ -0,0 +1,124 @@
1
+ import type { Socket } from "node:net";
2
+ import type { ConnAddr, ConnNext } from "../connNext/connNext.ts";
3
+ import { BaseConnNext } from "../connNext/baseConnNext.ts";
4
+
5
+ export class NodeSocketConnNext extends BaseConnNext {
6
+ private socket: Socket;
7
+
8
+ constructor(socket: Socket) {
9
+ super();
10
+ this.socket = socket;
11
+
12
+ const markClosed = () => {
13
+ this.closed = true;
14
+ };
15
+
16
+ this.socket.on("end", markClosed);
17
+ this.socket.on("close", markClosed);
18
+ this.socket.on("error", markClosed);
19
+ }
20
+
21
+ get localAddr(): ConnAddr | undefined {
22
+ return this.socket.localAddress
23
+ ? {
24
+ transport: "tcp",
25
+ hostname: this.socket.localAddress,
26
+ port: this.socket.localPort ?? 0,
27
+ }
28
+ : undefined;
29
+ }
30
+
31
+ get remoteAddr(): ConnAddr | undefined {
32
+ return this.socket.remoteAddress
33
+ ? {
34
+ transport: "tcp",
35
+ hostname: this.socket.remoteAddress,
36
+ port: this.socket.remotePort ?? 0,
37
+ }
38
+ : undefined;
39
+ }
40
+
41
+ /**
42
+ * Haalt direct op verzoek een chunk op via Node's ingebouwde socket.read() mechanisme.
43
+ */
44
+ protected pullNextChunk(): Promise<Uint8Array | null> {
45
+ if (this.closed || !this.socket.readable) {
46
+ return Promise.resolve(null);
47
+ }
48
+
49
+ // Vraag direct om data uit de Node socket buffer
50
+ const chunk = this.socket.read();
51
+
52
+ if (chunk !== null) {
53
+ return Promise.resolve(
54
+ new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength),
55
+ );
56
+ }
57
+
58
+ // Als er nog geen data is ('null'), wachten we op het 'readable' (of 'end'/'close') event van Node
59
+ return new Promise<Uint8Array | null>((resolve) => {
60
+ const cleanup = () => {
61
+ this.socket.off("readable", onReadable);
62
+ this.socket.off("end", onEnd);
63
+ this.socket.off("error", onClose);
64
+ this.socket.off("close", onClose);
65
+ };
66
+
67
+ const onReadable = () => {
68
+ cleanup();
69
+ // Roep zichzelf direct weer aan om de data op te halen
70
+ resolve(this.pullNextChunk());
71
+ };
72
+
73
+ const onEnd = () => {
74
+ cleanup();
75
+ this.closed = true;
76
+ resolve(null);
77
+ };
78
+
79
+ const onClose = () => {
80
+ cleanup();
81
+ this.closed = true;
82
+ resolve(null);
83
+ };
84
+
85
+ this.socket.on("readable", onReadable);
86
+ this.socket.on("end", onEnd);
87
+ this.socket.on("error", onClose);
88
+ this.socket.on("close", onClose);
89
+ });
90
+ }
91
+
92
+ write(data: Uint8Array): Promise<void> {
93
+ if (this.isClosed() || !this.socket.writable) {
94
+ return Promise.reject(new Error("Socket is closed or not writable"));
95
+ }
96
+
97
+ return new Promise((resolve, reject) => {
98
+ const canWriteMore = this.socket.write(data, (err) => {
99
+ if (err) reject(err);
100
+ });
101
+
102
+ if (canWriteMore) {
103
+ resolve();
104
+ } else {
105
+ this.socket.once("drain", resolve);
106
+ }
107
+ });
108
+ }
109
+
110
+ close(): void {
111
+ if (!this.closed) {
112
+ this.closed = true;
113
+ try {
114
+ this.socket.destroy();
115
+ } catch {
116
+ /* swallow */
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ export function nodeSocketToNext(socket: Socket): ConnNext {
123
+ return new NodeSocketConnNext(socket);
124
+ }
package/node/tcpClient.ts CHANGED
@@ -7,9 +7,9 @@ import { Client } from "../client/client.ts";
7
7
  import { logger } from "../client/deps.ts";
8
8
  import { readFile } from "node:fs/promises";
9
9
  import { createConnection } from "node:net";
10
- import * as tls from "node:tls";
11
- import type { SockConn } from "../socket/socket.ts";
12
- import { wrapNodeSocket } from "./wrapNodeSocket.ts";
10
+ import { connect } from "node:tls";
11
+ import type { ConnNext } from "../client/mod.ts";
12
+ import { nodeSocketToNext } from "./nodeSocketToNext.ts";
13
13
 
14
14
  /**
15
15
  * Fetches data from a file and returns it as a string
@@ -44,14 +44,17 @@ export class TcpClient extends Client {
44
44
  * @param hostname the name of the host to connect to
45
45
  * @param port the port number
46
46
  */
47
- protected connectMQTT(hostname: string, port = 1883): Promise<SockConn> {
47
+ protected override connectMQTT(
48
+ hostname: string,
49
+ port = 1883,
50
+ ): Promise<ConnNext> {
48
51
  logger.debug({ hostname, port });
49
52
  return new Promise((resolve, reject) => {
50
53
  const socket = createConnection(
51
54
  { port, host: hostname },
52
55
  () => {
53
56
  logger.debug("Connected to server");
54
- resolve(wrapNodeSocket(socket));
57
+ resolve(nodeSocketToNext(socket));
55
58
  },
56
59
  );
57
60
  socket.once("error", (err) => {
@@ -68,15 +71,15 @@ export class TcpClient extends Client {
68
71
  * @param caCerts the CA certificates to trust
69
72
  * @param cert the clients certificate
70
73
  * @param key the clients private key
71
- * @returns a SockConn object
74
+ * @returns a ConnNext object
72
75
  */
73
- protected connectMQTTS(
76
+ protected override connectMQTTS(
74
77
  hostname: string,
75
78
  port = 8883,
76
79
  ca?: string[],
77
80
  cert?: string,
78
81
  key?: string,
79
- ): Promise<SockConn> {
82
+ ): Promise<ConnNext> {
80
83
  const opts = {
81
84
  host: hostname,
82
85
  port,
@@ -87,9 +90,9 @@ export class TcpClient extends Client {
87
90
  };
88
91
  logger.debug({ hostname, port, ca, cert });
89
92
  return new Promise((resolve, reject) => {
90
- const socket = tls.connect(opts, () => {
93
+ const socket = connect(opts, () => {
91
94
  logger.debug("Connected to server");
92
- resolve(wrapNodeSocket(socket));
95
+ resolve(nodeSocketToNext(socket));
93
96
  });
94
97
  socket.once("error", (err) => {
95
98
  logger.debug("Connection failed", err);
@@ -97,21 +100,4 @@ export class TcpClient extends Client {
97
100
  });
98
101
  });
99
102
  }
100
-
101
- /** createConn is used by the base client class */
102
- protected override createConn(): Promise<SockConn> {
103
- const { protocol, hostname, port: portStr } = this.connectUrl;
104
- const port = Number(portStr);
105
- const caCerts = this.caCerts;
106
- const cert = this.cert;
107
- const key = this.key;
108
-
109
- if (protocol === "mqtts:") {
110
- return this.connectMQTTS(hostname, port, caCerts, cert, key);
111
- }
112
- if (protocol === "mqtt:") {
113
- return this.connectMQTT(hostname, port);
114
- }
115
- throw new Error(`Unsupported protocol: ${protocol}`);
116
- }
117
103
  }