@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
@@ -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
 
@@ -3,8 +3,9 @@ import assert from "node:assert/strict";
3
3
  import { TcpClient } from "./tcpClient.ts";
4
4
  import { TlsServer } from "./tlsServer.ts";
5
5
  import { logger, LogLevel } from "../utils/mod.ts";
6
- import type { PublishPacket, QoS } from "../mqttPacket/mod.ts";
7
- import { delay, generateLocalhostCerts } from "../dev_utils/mod.ts";
6
+ import { generateLocalhostCerts } from "../dev_utils/mod.ts";
7
+ import { testClient } from "../dev_utils/testClient.ts";
8
+ import { MqttServer } from "../server/mod.ts";
8
9
 
9
10
  logger.level(LogLevel.info);
10
11
 
@@ -36,62 +37,17 @@ test("Deno: Test pubSub using client and server", async () => {
36
37
  logger.verbose("client parameters: ", params);
37
38
 
38
39
  const client = new TcpClient();
39
-
40
- await client.connect(params);
41
- assert(true, "Client connected to server");
42
-
43
- const publishSet: { topic: string; qos: QoS }[] = [
44
- { topic: "t0@q0", qos: 0 },
45
- { topic: "t1@q0", qos: 0 },
46
- { topic: "t2@q0", qos: 0 },
47
- { topic: "t0@q1", qos: 1 },
48
- { topic: "t1@q1", qos: 1 },
49
- { topic: "t2@q1", qos: 1 },
50
- { topic: "t0@q2", qos: 2 },
51
- { topic: "t1@q2", qos: 2 },
52
- { topic: "t2@q2", qos: 2 },
53
- ];
54
-
55
- const subscriptions = publishSet.map((item) => ({
56
- topicFilter: item.topic,
57
- qos: item.qos,
58
- }));
59
- await client.subscribe({
60
- subscriptions,
61
- });
62
-
63
- // the IIFE ensures message reception runs in parallel
64
- logger.verbose("Start receiving");
65
- const received: PublishPacket[] = [];
66
- (async () => {
67
- for await (const item of client.messages()) {
68
- logger.verbose("Receiving:", item.topic, "--", item.qos);
69
- received.push(item);
70
- }
71
- })();
72
- // end of IIFE
73
- for (const item of publishSet) {
74
- logger.verbose("Publishing:", item.topic, "--", item.qos);
75
- await client.publish({
76
- topic: item.topic,
77
- qos: item.qos,
78
- payload: new Uint8Array([0x01]),
79
- });
80
- }
81
-
82
- await delay(100);
83
- logger.verbose("Disconnect client");
84
- await client.disconnect();
85
-
86
- logger.verbose("Check completeness");
87
- for (const item of publishSet) {
88
- const found = received.find((f) =>
89
- f.topic == item.topic && f.qos === item.qos
90
- );
91
- logger.verbose("Found:", item.topic, "--", item.qos);
92
- assert(found, `${item.topic} -- ${item.qos}`);
93
- }
40
+ await testClient(client, params);
94
41
 
95
42
  logger.verbose("Stop server");
96
- server.stop();
43
+ await server.stop();
44
+ });
45
+
46
+ test("Deno: Passing a mqttServer works", () => {
47
+ const mqttServer = new MqttServer({});
48
+ const server = new TlsServer(
49
+ { hostname: "localhost", port: 0, key, cert },
50
+ mqttServer,
51
+ );
52
+ assert(server.mqttServer === mqttServer);
97
53
  });
package/deno/tlsServer.ts CHANGED
@@ -6,22 +6,28 @@
6
6
 
7
7
  import { MqttServer } from "../server/mod.ts";
8
8
  import type { MqttServerOptions } from "../server/mod.ts";
9
+ import { denoConnToNext } from "./denoConnToNext.ts";
9
10
 
10
11
  /**
11
12
  * TLS server that wraps a MqttServer, see the /examples folder
12
13
  */
13
14
  export class TlsServer {
15
+ /** the MqttServer instance used by the server */
16
+ readonly mqttServer: MqttServer;
14
17
  private listener: Deno.TlsListener;
15
- private mqttServer: MqttServer;
16
18
  /**
17
19
  * Create a new TLS server
18
20
  */
19
21
  constructor(
20
22
  serverOptions: Deno.ListenTlsOptions & Deno.TlsCertifiedKeyPem,
21
- mqttOptions: MqttServerOptions,
23
+ mqttOptions: MqttServerOptions | MqttServer,
22
24
  ) {
23
25
  this.listener = Deno.listenTls(serverOptions);
24
- this.mqttServer = new MqttServer(mqttOptions);
26
+ if (mqttOptions instanceof MqttServer) {
27
+ this.mqttServer = mqttOptions;
28
+ } else {
29
+ this.mqttServer = new MqttServer(mqttOptions);
30
+ }
25
31
  }
26
32
 
27
33
  /**
@@ -29,15 +35,17 @@ export class TlsServer {
29
35
  */
30
36
  async start(): Promise<void> {
31
37
  for await (const conn of this.listener) {
32
- this.mqttServer.serve(conn);
38
+ this.mqttServer.serve(denoConnToNext(conn));
33
39
  }
34
40
  }
35
41
 
36
42
  /**
37
43
  * Stop listening
38
44
  */
39
- stop(): void {
40
- this.mqttServer.close();
45
+ async stop(opts = { closeMqtt: true }): Promise<void> {
46
+ if (opts.closeMqtt) {
47
+ await this.mqttServer.close();
48
+ }
41
49
  this.listener.close();
42
50
  }
43
51
  /**
@@ -2,8 +2,9 @@ import { test } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { WsClient } from "../web/wsClient.ts";
4
4
  import { WsServer } from "./wsServer.ts";
5
- import { delay, logger, LogLevel } from "../utils/mod.ts";
6
- import type { PublishPacket, QoS } from "../mqttPacket/mod.ts";
5
+ import { logger, LogLevel } from "../utils/mod.ts";
6
+ import { testClient } from "../dev_utils/testClient.ts";
7
+ import { MqttServer } from "../server/mod.ts";
7
8
 
8
9
  logger.level(LogLevel.info);
9
10
 
@@ -25,69 +26,22 @@ test("Deno: Test pubSub using client and server over webSockets", async () => {
25
26
  });
26
27
 
27
28
  const params = {
28
- url: new URL(`ws://localhost:${server.port}`),
29
+ url: new URL(`ws://localhost:${server.port}/mqtt`),
29
30
  numberOfRetries: 0,
30
31
  };
31
32
 
32
- logger.verbose("client parameters: ", params);
33
-
34
33
  const client = new WsClient();
35
- client.onConnected = () => logger.verbose("Client connected to server");
36
-
37
- await client.connect(params);
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,
57
- });
58
-
59
- // the IIFE ensures message reception runs in parallel
60
- logger.verbose("Start receiving");
61
- const received: PublishPacket[] = [];
62
- (async () => {
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.verbose("Disconnect client");
80
- await client.disconnect();
81
-
82
- logger.verbose("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
- }
34
+ await testClient(client, params);
90
35
 
91
36
  logger.verbose("Stop server");
92
- server.stop();
37
+ await server.stop();
38
+ });
39
+
40
+ test("Deno: Passing a mqttServer works", () => {
41
+ const mqttServer = new MqttServer({});
42
+ const server = new WsServer(
43
+ { hostname: "localhost", port: 0 },
44
+ mqttServer,
45
+ );
46
+ assert(server.mqttServer === mqttServer);
93
47
  });
package/deno/wsServer.ts CHANGED
@@ -5,66 +5,116 @@
5
5
  import { MqttServer } from "../server/mod.ts";
6
6
  import type { MqttServerOptions } from "../server/mod.ts";
7
7
  import { logger } from "../utils/logger.ts";
8
- import { wrapWebSocket } from "../web/wrapWebSocket.ts";
9
- import type { HostnamePort } from "../web/wrapWebSocket.ts";
8
+ import { webSocketToNext } from "../web/webSocketToNext.ts";
9
+ import type { NetAddr } from "../connNext/connNext.ts";
10
+
11
+ /** for those who want wrap their own websockets */
12
+ export { webSocketToNext };
13
+ export type RequestHandler = (
14
+ req: Request,
15
+ info: Deno.ServeHandlerInfo<NetAddr>,
16
+ ) => Response | Promise<Response>;
17
+
18
+ export const pageNotFound: RequestHandler = (_req, _info) => {
19
+ return new Response("Page not found", { status: 404 });
20
+ };
21
+
22
+ async function handleWsClient(
23
+ socket: WebSocket,
24
+ mqttServer: MqttServer,
25
+ remoteAddr: NetAddr,
26
+ maxBufferSize: number | undefined,
27
+ ) {
28
+ const conn = await webSocketToNext(socket, remoteAddr, maxBufferSize);
29
+ mqttServer.serve(conn);
30
+ logger.debug("serving mqtt");
31
+ }
32
+
33
+ export function mqttOverWS(
34
+ mqttServer: MqttServer,
35
+ pathName: string,
36
+ next: RequestHandler,
37
+ opts?: {
38
+ maxBufferSize?: number | undefined;
39
+ },
40
+ ): RequestHandler {
41
+ const wsRequestHandler: RequestHandler = (req, info) => {
42
+ const url = new URL(req.url);
43
+ if (url.pathname === pathName) {
44
+ if (req.headers.get("upgrade") !== "websocket") {
45
+ return new Response("Expected WebSocket connection.", { status: 426 });
46
+ }
47
+ logger.debug("starting upgrade to websocket");
48
+ const remoteAddr = info.remoteAddr;
49
+ const { socket, response } = Deno.upgradeWebSocket(req);
50
+ logger.debug("upgraded to websocket");
51
+ // do not await on purpose
52
+ handleWsClient(socket, mqttServer, remoteAddr, opts?.maxBufferSize);
53
+ return response;
54
+ }
55
+ return next(req, info);
56
+ };
57
+ return wsRequestHandler;
58
+ }
10
59
 
11
60
  /**
12
61
  * WebSocket server that wraps a MqttServer, see the /examples folder
13
62
  */
14
63
  export class WsServer {
64
+ /** the MqttServer instance used by the server */
65
+ readonly mqttServer: MqttServer;
15
66
  private server?: Deno.HttpServer;
16
- private mqttServer: MqttServer;
17
67
  private listenOptions: Deno.ServeOptions & {
18
68
  port: number;
19
69
  hostname?: string;
20
70
  };
71
+ private pathName: string;
72
+ private maxBufferSize: number | undefined;
21
73
 
22
74
  /**
23
75
  * Create a new WebSocket server
24
76
  */
25
77
  constructor(
26
- serverOptions: Deno.ServeOptions & { port: number; hostname?: string },
27
- mqttOptions: MqttServerOptions,
78
+ serverOptions: Deno.ServeOptions & {
79
+ port: number;
80
+ hostname?: string;
81
+ pathName?: string;
82
+ maxBufferSize?: number;
83
+ },
84
+ mqttOptions: MqttServerOptions | MqttServer,
28
85
  ) {
29
- this.listenOptions = serverOptions;
30
- this.mqttServer = new MqttServer(mqttOptions);
31
- }
32
-
33
- private async handleWsClient(socket: WebSocket, remoteAddr: HostnamePort) {
34
- const conn = await wrapWebSocket(socket, remoteAddr);
35
- logger.debug("created conn");
36
- this.mqttServer.serve(conn);
37
- logger.debug("serving mqtt");
86
+ const { pathName, maxBufferSize, ...listenOptions } = serverOptions;
87
+ this.listenOptions = listenOptions;
88
+ this.pathName = pathName ?? "/mqtt";
89
+ this.maxBufferSize = maxBufferSize;
90
+ if (mqttOptions instanceof MqttServer) {
91
+ this.mqttServer = mqttOptions;
92
+ } else {
93
+ this.mqttServer = new MqttServer(mqttOptions);
94
+ }
38
95
  }
39
96
 
40
97
  /**
41
98
  * Start listening
42
99
  */
43
100
  async start(): Promise<void> {
44
- this.server = Deno.serve(this.listenOptions, (req, info) => {
45
- const { hostname, port } = info.remoteAddr;
46
- logger.debug(
47
- `Server received http connection from ${hostname}:${port}`,
48
- );
49
-
50
- if (req.headers.get("upgrade") !== "websocket") {
51
- return new Response("Expected WebSocket connection.", { status: 426 });
52
- }
53
- logger.debug("starting upgrade to websocket");
54
- const { socket, response } = Deno.upgradeWebSocket(req);
55
- logger.debug("upgraded to websocket");
56
- this.handleWsClient(socket, { hostname, port });
57
- return response;
58
- });
59
-
101
+ const requestHandler = mqttOverWS(
102
+ this.mqttServer,
103
+ this.pathName,
104
+ pageNotFound,
105
+ { maxBufferSize: this.maxBufferSize },
106
+ );
107
+ this.server = Deno.serve(this.listenOptions, requestHandler);
60
108
  await this.server.finished;
61
109
  }
62
110
 
63
111
  /**
64
112
  * Stop listening
65
113
  */
66
- stop(): void {
67
- this.mqttServer.close(true);
114
+ async stop(opts = { closeMqtt: true }): Promise<void> {
115
+ if (opts.closeMqtt) {
116
+ await this.mqttServer.close();
117
+ }
68
118
  if (this.server) {
69
119
  this.server.shutdown();
70
120
  }
@@ -0,0 +1,5 @@
1
+ export interface WebStreamEndpoint {
2
+ readable: ReadableStream<Uint8Array>;
3
+ writable: WritableStream<Uint8Array>;
4
+ close: () => void;
5
+ }
@@ -0,0 +1,5 @@
1
+ export interface AsyncStreamEndpoint {
2
+ readable: AsyncIterable<Uint8Array>;
3
+ write: (chunk: Uint8Array) => Promise<void>;
4
+ close: () => void;
5
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Duplex AsyncStream
3
+ */
4
+
5
+ import type { AsyncStreamEndpoint } from "./asyncStreamEndpoint.ts";
6
+ /**
7
+ * Internal push/pull queue to support AsyncIterable iteration.
8
+ */
9
+ class AsyncQueue<T> implements AsyncIterable<T> {
10
+ private queue: T[] = [];
11
+ private resolvers: Array<(value: IteratorResult<T>) => void> = [];
12
+ private closed = false;
13
+ private error: unknown = null;
14
+
15
+ push(value: T): void {
16
+ if (this.closed) return;
17
+
18
+ if (this.resolvers.length > 0) {
19
+ const resolve = this.resolvers.shift()!;
20
+ resolve({ value, done: false });
21
+ } else {
22
+ this.queue.push(value);
23
+ }
24
+ }
25
+
26
+ close(): void {
27
+ if (this.closed) return;
28
+ this.closed = true;
29
+
30
+ while (this.resolvers.length > 0) {
31
+ const resolve = this.resolvers.shift()!;
32
+ if (this.error) {
33
+ resolve(Promise.reject(this.error) as unknown as IteratorResult<T>);
34
+ } else {
35
+ resolve({ value: undefined, done: true });
36
+ }
37
+ }
38
+ }
39
+
40
+ abort(reason: unknown): void {
41
+ this.error = reason;
42
+ this.close();
43
+ }
44
+
45
+ async *[Symbol.asyncIterator](): AsyncIterator<T> {
46
+ while (true) {
47
+ if (this.queue.length > 0) {
48
+ yield this.queue.shift()!;
49
+ } else if (this.closed) {
50
+ if (this.error) {
51
+ throw this.error;
52
+ }
53
+ return;
54
+ } else {
55
+ const nextResult = await new Promise<IteratorResult<T>>((resolve) => {
56
+ this.resolvers.push(resolve);
57
+ });
58
+
59
+ if (nextResult.done) {
60
+ if (this.error) {
61
+ throw this.error;
62
+ }
63
+ return;
64
+ }
65
+
66
+ yield nextResult.value;
67
+ }
68
+ }
69
+ }
70
+ }
71
+
72
+ export function createAsyncStreamPair(): {
73
+ input: AsyncStreamEndpoint;
74
+ output: AsyncStreamEndpoint;
75
+ } {
76
+ const queueA = new AsyncQueue<Uint8Array>();
77
+ const queueB = new AsyncQueue<Uint8Array>();
78
+
79
+ let closed = false;
80
+
81
+ const closeBoth = () => {
82
+ if (!closed) {
83
+ closed = true;
84
+ queueA.close();
85
+ queueB.close();
86
+ }
87
+ };
88
+
89
+ return {
90
+ input: {
91
+ readable: queueA,
92
+ write: (chunk: Uint8Array): Promise<void> => {
93
+ if (closed) throw new Error("Stream is closed");
94
+ queueB.push(chunk);
95
+ return Promise.resolve();
96
+ },
97
+ close: closeBoth,
98
+ },
99
+ output: {
100
+ readable: queueB,
101
+ write: (chunk: Uint8Array): Promise<void> => {
102
+ if (closed) throw new Error("Stream is closed");
103
+ queueA.push(chunk);
104
+ return Promise.resolve();
105
+ },
106
+ close: closeBoth,
107
+ },
108
+ };
109
+ }