@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.
- package/README.md +7 -6
- package/bin/mqtt.ts +1 -1
- package/client/client.ts +58 -7
- package/client/context.ts +2 -2
- package/client/deps.ts +3 -3
- package/client/mod.ts +2 -2
- package/connNext/connNext.ts +44 -0
- package/deno/README.md +1 -1
- package/deno/denoConnToNext.ts +104 -0
- package/deno/mod.ts +7 -0
- package/deno/tcpClient.ts +8 -24
- package/deno/tcpServer.test.ts +14 -103
- package/deno/tcpServer.ts +14 -6
- package/deno/tlsServer.test.ts +14 -58
- package/deno/tlsServer.ts +14 -6
- package/deno/wsServer.test.ts +15 -61
- package/deno/wsServer.ts +77 -35
- package/dev_utils/asyncStreamPair.ts +114 -0
- package/dev_utils/asyncStreamToNext.ts +171 -0
- package/dev_utils/connNextPair.test.ts +179 -0
- package/dev_utils/mockConn.ts +115 -29
- package/dev_utils/mockServer.ts +3 -4
- package/dev_utils/mod.ts +1 -2
- package/dev_utils/testClient.ts +64 -0
- package/dev_utils/webStreamToNext.ts +187 -0
- package/dist/bin/mqtt.js +1 -1
- package/dist/bin/mqtt.js.map +1 -1
- package/dist/client/client.d.ts +21 -3
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +43 -5
- package/dist/client/client.js.map +1 -1
- package/dist/client/context.d.ts +2 -2
- package/dist/client/context.d.ts.map +1 -1
- package/dist/client/deps.d.ts +1 -1
- package/dist/client/deps.d.ts.map +1 -1
- package/dist/client/mod.d.ts +1 -1
- package/dist/client/mod.d.ts.map +1 -1
- package/dist/connNext/connNext.d.ts +43 -0
- package/dist/connNext/connNext.d.ts.map +1 -0
- package/dist/connNext/connNext.js +2 -0
- package/dist/connNext/connNext.js.map +1 -0
- package/dist/examples/authHandler.test.js +2 -2
- package/dist/examples/authHandler.test.js.map +1 -1
- package/dist/examples/mqtt+mqtts.test.d.ts +2 -0
- package/dist/examples/mqtt+mqtts.test.d.ts.map +1 -0
- package/dist/examples/mqtt+mqtts.test.js +104 -0
- package/dist/examples/mqtt+mqtts.test.js.map +1 -0
- package/dist/examples/mqtt.test.js +2 -2
- package/dist/examples/mqtt.test.js.map +1 -1
- package/dist/examples/mqtts.test.js +2 -2
- package/dist/examples/mqtts.test.js.map +1 -1
- package/dist/mqttConn/mqttConn.d.ts +5 -6
- package/dist/mqttConn/mqttConn.d.ts.map +1 -1
- package/dist/mqttConn/mqttConn.js +2 -3
- package/dist/mqttConn/mqttConn.js.map +1 -1
- package/dist/mqttPacket/protocolLevels.d.ts +2 -0
- package/dist/mqttPacket/protocolLevels.d.ts.map +1 -1
- package/dist/node/nodeSocketToNext.d.ts +20 -0
- package/dist/node/nodeSocketToNext.d.ts.map +1 -0
- package/dist/node/nodeSocketToNext.js +161 -0
- package/dist/node/nodeSocketToNext.js.map +1 -0
- package/dist/node/tcpClient.d.ts +4 -6
- package/dist/node/tcpClient.d.ts.map +1 -1
- package/dist/node/tcpClient.js +6 -21
- package/dist/node/tcpClient.js.map +1 -1
- package/dist/node/tcpServer.d.ts +8 -4
- package/dist/node/tcpServer.d.ts.map +1 -1
- package/dist/node/tcpServer.js +14 -6
- package/dist/node/tcpServer.js.map +1 -1
- package/dist/node/tlsServer.d.ts +7 -3
- package/dist/node/tlsServer.d.ts.map +1 -1
- package/dist/node/tlsServer.js +13 -5
- package/dist/node/tlsServer.js.map +1 -1
- package/dist/server/context.d.ts +5 -5
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/context.js +4 -1
- package/dist/server/context.js.map +1 -1
- package/dist/server/deps.d.ts +1 -1
- package/dist/server/deps.d.ts.map +1 -1
- package/dist/server/mod.d.ts +1 -1
- package/dist/server/mod.d.ts.map +1 -1
- package/dist/server/server.d.ts +3 -3
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +3 -3
- package/dist/utils/utils.d.ts +1 -2
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +3 -2
- package/dist/utils/utils.js.map +1 -1
- package/dist/web/webSocketToNext.d.ts +3 -0
- package/dist/web/webSocketToNext.d.ts.map +1 -0
- package/dist/web/webSocketToNext.js +160 -0
- package/dist/web/webSocketToNext.js.map +1 -0
- package/dist/web/wsClient.d.ts +8 -3
- package/dist/web/wsClient.d.ts.map +1 -1
- package/dist/web/wsClient.js +20 -6
- package/dist/web/wsClient.js.map +1 -1
- package/dist/web/wsClient.test.js +15 -0
- package/dist/web/wsClient.test.js.map +1 -1
- package/examples/README.md +21 -0
- package/examples/authHandler.test.ts +2 -2
- package/examples/mqtt+mqtts.test.ts +122 -0
- package/examples/mqtt.test.ts +2 -2
- package/examples/mqtts.test.ts +2 -2
- package/examples/webSocket.test.ts +10 -64
- package/examples/webSocketServer.ts +98 -0
- package/mqttConn/mqttConn.test.ts +6 -6
- package/mqttConn/mqttConn.ts +8 -9
- package/mqttPacket/protocolLevels.ts +2 -0
- package/node/README.md +1 -1
- package/node/mod.ts +7 -0
- package/node/{socket-node-stream.ts → nodeSocketToNext.ts} +78 -65
- package/node/tcpClient.ts +13 -27
- package/node/tcpServer.test.ts +12 -58
- package/node/tcpServer.ts +18 -8
- package/node/tlsServer.test.ts +11 -58
- package/node/tlsServer.ts +17 -7
- package/package.json +8 -2
- package/server/context.ts +9 -6
- package/server/deps.ts +1 -1
- package/server/mod.ts +1 -1
- package/server/server.ts +6 -6
- package/utils/utils.ts +3 -2
- package/web/webSocketToNext.ts +183 -0
- package/web/wsClient.test.ts +22 -0
- package/web/wsClient.ts +22 -9
- package/dist/node/wrapNodeSocket.d.ts +0 -12
- package/dist/node/wrapNodeSocket.d.ts.map +0 -1
- package/dist/node/wrapNodeSocket.js +0 -36
- package/dist/node/wrapNodeSocket.js.map +0 -1
- package/dist/socket/socket.d.ts +0 -40
- package/dist/socket/socket.d.ts.map +0 -1
- package/dist/socket/socket.js +0 -94
- package/dist/socket/socket.js.map +0 -1
- package/dist/web/webSocketStream.d.ts +0 -29
- package/dist/web/webSocketStream.d.ts.map +0 -1
- package/dist/web/webSocketStream.js +0 -69
- package/dist/web/webSocketStream.js.map +0 -1
- package/dist/web/wrapWebSocket.d.ts +0 -12
- package/dist/web/wrapWebSocket.d.ts.map +0 -1
- package/dist/web/wrapWebSocket.js +0 -41
- package/dist/web/wrapWebSocket.js.map +0 -1
- package/node/socket-node-stream.test.ts +0 -322
- package/node/wrapNodeSocket.ts +0 -44
- package/socket/socket.test.ts +0 -91
- package/socket/socket.ts +0 -124
- package/web/webSocketStream.ts +0 -97
- package/web/wrapWebSocket.ts +0 -52
package/deno/tlsServer.test.ts
CHANGED
|
@@ -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
|
|
7
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
/**
|
package/deno/wsServer.test.ts
CHANGED
|
@@ -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 {
|
|
6
|
-
import
|
|
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
|
-
|
|
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
|
@@ -1,70 +1,112 @@
|
|
|
1
|
-
|
|
2
|
-
*
|
|
3
|
-
*
|
|
1
|
+
/**
|
|
2
|
+
* This a Deno specific implementation of a WebSocket server
|
|
3
|
+
* @module
|
|
4
4
|
*/
|
|
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 {
|
|
9
|
-
import type {
|
|
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
|
+
) {
|
|
27
|
+
const conn = await webSocketToNext(socket, remoteAddr);
|
|
28
|
+
mqttServer.serve(conn);
|
|
29
|
+
logger.debug("serving mqtt");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function mqttOverWS(
|
|
33
|
+
mqttServer: MqttServer,
|
|
34
|
+
pathName: string,
|
|
35
|
+
next: RequestHandler,
|
|
36
|
+
): RequestHandler {
|
|
37
|
+
const wsRequestHandler: RequestHandler = (req, info) => {
|
|
38
|
+
const url = new URL(req.url);
|
|
39
|
+
if (url.pathname === pathName) {
|
|
40
|
+
if (req.headers.get("upgrade") !== "websocket") {
|
|
41
|
+
return new Response("Expected WebSocket connection.", { status: 426 });
|
|
42
|
+
}
|
|
43
|
+
logger.debug("starting upgrade to websocket");
|
|
44
|
+
const remoteAddr = info.remoteAddr;
|
|
45
|
+
const { socket, response } = Deno.upgradeWebSocket(req);
|
|
46
|
+
logger.debug("upgraded to websocket");
|
|
47
|
+
// do not await on purpose
|
|
48
|
+
handleWsClient(socket, mqttServer, remoteAddr);
|
|
49
|
+
return response;
|
|
50
|
+
}
|
|
51
|
+
return next(req, info);
|
|
52
|
+
};
|
|
53
|
+
return wsRequestHandler;
|
|
54
|
+
}
|
|
10
55
|
|
|
11
56
|
/**
|
|
12
57
|
* WebSocket server that wraps a MqttServer, see the /examples folder
|
|
13
58
|
*/
|
|
14
59
|
export class WsServer {
|
|
60
|
+
/** the MqttServer instance used by the server */
|
|
61
|
+
readonly mqttServer: MqttServer;
|
|
15
62
|
private server?: Deno.HttpServer;
|
|
16
|
-
private mqttServer: MqttServer;
|
|
17
63
|
private listenOptions: Deno.ServeOptions & {
|
|
18
64
|
port: number;
|
|
19
65
|
hostname?: string;
|
|
20
66
|
};
|
|
67
|
+
private pathName: string;
|
|
21
68
|
|
|
22
69
|
/**
|
|
23
70
|
* Create a new WebSocket server
|
|
24
71
|
*/
|
|
25
72
|
constructor(
|
|
26
|
-
serverOptions: Deno.ServeOptions & {
|
|
27
|
-
|
|
73
|
+
serverOptions: Deno.ServeOptions & {
|
|
74
|
+
port: number;
|
|
75
|
+
hostname?: string;
|
|
76
|
+
pathName?: string;
|
|
77
|
+
},
|
|
78
|
+
mqttOptions: MqttServerOptions | MqttServer,
|
|
28
79
|
) {
|
|
29
|
-
|
|
30
|
-
this.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
logger.debug("serving mqtt");
|
|
80
|
+
const { pathName, ...listenOptions } = serverOptions;
|
|
81
|
+
this.listenOptions = listenOptions;
|
|
82
|
+
this.pathName = pathName ?? "/mqtt";
|
|
83
|
+
if (mqttOptions instanceof MqttServer) {
|
|
84
|
+
this.mqttServer = mqttOptions;
|
|
85
|
+
} else {
|
|
86
|
+
this.mqttServer = new MqttServer(mqttOptions);
|
|
87
|
+
}
|
|
38
88
|
}
|
|
39
89
|
|
|
40
90
|
/**
|
|
41
91
|
* Start listening
|
|
42
92
|
*/
|
|
43
93
|
async start(): Promise<void> {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
94
|
+
const requestHandler = mqttOverWS(
|
|
95
|
+
this.mqttServer,
|
|
96
|
+
this.pathName,
|
|
97
|
+
pageNotFound,
|
|
98
|
+
);
|
|
99
|
+
this.server = Deno.serve(this.listenOptions, requestHandler);
|
|
60
100
|
await this.server.finished;
|
|
61
101
|
}
|
|
62
102
|
|
|
63
103
|
/**
|
|
64
104
|
* Stop listening
|
|
65
105
|
*/
|
|
66
|
-
stop(): void {
|
|
67
|
-
|
|
106
|
+
async stop(opts = { closeMqtt: true }): Promise<void> {
|
|
107
|
+
if (opts.closeMqtt) {
|
|
108
|
+
await this.mqttServer.close();
|
|
109
|
+
}
|
|
68
110
|
if (this.server) {
|
|
69
111
|
this.server.shutdown();
|
|
70
112
|
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Duplex AsyncStream
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface AsyncStreamPairEndpoint {
|
|
6
|
+
readable: AsyncIterable<Uint8Array>;
|
|
7
|
+
write: (chunk: Uint8Array) => Promise<void>;
|
|
8
|
+
close: () => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Interne push/pull queue to support AsyncIterable iteration.
|
|
13
|
+
*/
|
|
14
|
+
class AsyncQueue<T> implements AsyncIterable<T> {
|
|
15
|
+
private queue: T[] = [];
|
|
16
|
+
private resolvers: Array<(value: IteratorResult<T>) => void> = [];
|
|
17
|
+
private closed = false;
|
|
18
|
+
private error: unknown = null;
|
|
19
|
+
|
|
20
|
+
push(value: T): void {
|
|
21
|
+
if (this.closed) return;
|
|
22
|
+
|
|
23
|
+
if (this.resolvers.length > 0) {
|
|
24
|
+
const resolve = this.resolvers.shift()!;
|
|
25
|
+
resolve({ value, done: false });
|
|
26
|
+
} else {
|
|
27
|
+
this.queue.push(value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
close(): void {
|
|
32
|
+
if (this.closed) return;
|
|
33
|
+
this.closed = true;
|
|
34
|
+
|
|
35
|
+
while (this.resolvers.length > 0) {
|
|
36
|
+
const resolve = this.resolvers.shift()!;
|
|
37
|
+
if (this.error) {
|
|
38
|
+
resolve(Promise.reject(this.error) as unknown as IteratorResult<T>);
|
|
39
|
+
} else {
|
|
40
|
+
resolve({ value: undefined, done: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
abort(reason: unknown): void {
|
|
46
|
+
this.error = reason;
|
|
47
|
+
this.close();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async *[Symbol.asyncIterator](): AsyncIterator<T> {
|
|
51
|
+
while (true) {
|
|
52
|
+
if (this.queue.length > 0) {
|
|
53
|
+
yield this.queue.shift()!;
|
|
54
|
+
} else if (this.closed) {
|
|
55
|
+
if (this.error) {
|
|
56
|
+
throw this.error;
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
} else {
|
|
60
|
+
const nextResult = await new Promise<IteratorResult<T>>((resolve) => {
|
|
61
|
+
this.resolvers.push(resolve);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
if (nextResult.done) {
|
|
65
|
+
if (this.error) {
|
|
66
|
+
throw this.error;
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
yield nextResult.value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function createAsyncStreamPair(): {
|
|
78
|
+
input: AsyncStreamPairEndpoint;
|
|
79
|
+
output: AsyncStreamPairEndpoint;
|
|
80
|
+
} {
|
|
81
|
+
const queueA = new AsyncQueue<Uint8Array>();
|
|
82
|
+
const queueB = new AsyncQueue<Uint8Array>();
|
|
83
|
+
|
|
84
|
+
let closed = false;
|
|
85
|
+
|
|
86
|
+
const closeBoth = () => {
|
|
87
|
+
if (!closed) {
|
|
88
|
+
closed = true;
|
|
89
|
+
queueA.close();
|
|
90
|
+
queueB.close();
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
input: {
|
|
96
|
+
readable: queueA,
|
|
97
|
+
write: (chunk: Uint8Array): Promise<void> => {
|
|
98
|
+
if (closed) throw new Error("Stream is closed");
|
|
99
|
+
queueB.push(chunk);
|
|
100
|
+
return Promise.resolve();
|
|
101
|
+
},
|
|
102
|
+
close: closeBoth,
|
|
103
|
+
},
|
|
104
|
+
output: {
|
|
105
|
+
readable: queueB,
|
|
106
|
+
write: (chunk: Uint8Array): Promise<void> => {
|
|
107
|
+
if (closed) throw new Error("Stream is closed");
|
|
108
|
+
queueA.push(chunk);
|
|
109
|
+
return Promise.resolve();
|
|
110
|
+
},
|
|
111
|
+
close: closeBoth,
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import type { ConnAddr, ConnNext } from "../connNext/connNext.ts";
|
|
2
|
+
import { createAsyncStreamPair } from "./asyncStreamPair.ts";
|
|
3
|
+
import type { AsyncStreamPairEndpoint } from "./asyncStreamPair.ts";
|
|
4
|
+
|
|
5
|
+
/** wrapper class */
|
|
6
|
+
export class AsyncStreamConnNextWrapper {
|
|
7
|
+
closed: boolean;
|
|
8
|
+
private endPoint: AsyncStreamPairEndpoint;
|
|
9
|
+
private leftover: Uint8Array = new Uint8Array(0);
|
|
10
|
+
private reader: AsyncIterator<Uint8Array>;
|
|
11
|
+
|
|
12
|
+
/** Create a new Endpoint*/
|
|
13
|
+
constructor(endPoint: AsyncStreamPairEndpoint) {
|
|
14
|
+
this.endPoint = endPoint;
|
|
15
|
+
this.closed = false;
|
|
16
|
+
this.reader = endPoint.readable[Symbol.asyncIterator]();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Writes data chunk to the underlying AsyncStream
|
|
21
|
+
*/ async write(chunk: Uint8Array): Promise<void> {
|
|
22
|
+
if (this.closed) {
|
|
23
|
+
return Promise.reject(new Error("Pipe closed"));
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
await this.endPoint.write(chunk);
|
|
27
|
+
return;
|
|
28
|
+
} catch (err) {
|
|
29
|
+
this.closed = true;
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Reads data from the stream.
|
|
36
|
+
* If `length` is provided, waits until exact number of bytes are buffered.
|
|
37
|
+
*/
|
|
38
|
+
async read(length?: number): Promise<Uint8Array | null> {
|
|
39
|
+
if (this.closed || (length !== undefined && length < 0)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (length === 0) {
|
|
44
|
+
return new Uint8Array(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Helper to fetch the next chunk from the underlying iterable
|
|
48
|
+
const fetchNextChunk = async (): Promise<Uint8Array | null> => {
|
|
49
|
+
const { value, done } = await this.reader.next();
|
|
50
|
+
if (done) {
|
|
51
|
+
this.closed = true;
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Unbounded read (length === undefined)
|
|
58
|
+
if (length === undefined) {
|
|
59
|
+
if (this.leftover.length > 0) {
|
|
60
|
+
const result = this.leftover;
|
|
61
|
+
this.leftover = new Uint8Array(0);
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
return await fetchNextChunk();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Exact length read
|
|
68
|
+
const bytesRequested = length;
|
|
69
|
+
const result = new Uint8Array(bytesRequested);
|
|
70
|
+
let bytesAccumulated = 0;
|
|
71
|
+
|
|
72
|
+
// Use leftover data first if available
|
|
73
|
+
if (this.leftover.length > 0) {
|
|
74
|
+
const take = Math.min(this.leftover.length, bytesRequested);
|
|
75
|
+
result.set(this.leftover.subarray(0, take), bytesAccumulated);
|
|
76
|
+
bytesAccumulated += take;
|
|
77
|
+
this.leftover = this.leftover.subarray(take);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Read remaining required bytes
|
|
81
|
+
while (bytesAccumulated < bytesRequested) {
|
|
82
|
+
const value = await fetchNextChunk();
|
|
83
|
+
|
|
84
|
+
if (value === null) {
|
|
85
|
+
// Stream ended before collecting required bytes
|
|
86
|
+
if (bytesAccumulated === 0) return null;
|
|
87
|
+
return result.subarray(0, bytesAccumulated);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const needed = bytesRequested - bytesAccumulated;
|
|
91
|
+
|
|
92
|
+
if (value.length <= needed) {
|
|
93
|
+
result.set(value, bytesAccumulated);
|
|
94
|
+
bytesAccumulated += value.length;
|
|
95
|
+
} else {
|
|
96
|
+
result.set(value.subarray(0, needed), bytesAccumulated);
|
|
97
|
+
bytesAccumulated += needed;
|
|
98
|
+
this.leftover = value.subarray(needed);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Closes the reader, writer, and underlying AsyncStream pair endpoint.
|
|
107
|
+
*/
|
|
108
|
+
close(): void {
|
|
109
|
+
if (!this.closed) {
|
|
110
|
+
this.closed = true;
|
|
111
|
+
try {
|
|
112
|
+
this.endPoint.close();
|
|
113
|
+
} catch (_err) { /* swallow */ }
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async *[Symbol.asyncIterator]() {
|
|
117
|
+
while (true) {
|
|
118
|
+
const chunk = await this.read();
|
|
119
|
+
if (chunk === null) {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
yield chunk;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Creates a pair of cross-connected ConnNext sockets backed by AsyncStreams.
|
|
129
|
+
* Offers the exact same API as `createConnNextPair`.
|
|
130
|
+
*/
|
|
131
|
+
export function createConnNextPair(): {
|
|
132
|
+
input: ConnNext;
|
|
133
|
+
output: ConnNext;
|
|
134
|
+
} {
|
|
135
|
+
const { input: asyncInput, output: asyncOutput } = createAsyncStreamPair();
|
|
136
|
+
|
|
137
|
+
const mockAddr: ConnAddr = {
|
|
138
|
+
transport: "tcp",
|
|
139
|
+
hostname: "127.0.0.1",
|
|
140
|
+
port: 1883,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const inputWrapper = new AsyncStreamConnNextWrapper(asyncInput);
|
|
144
|
+
const outputWrapper = new AsyncStreamConnNextWrapper(asyncOutput);
|
|
145
|
+
|
|
146
|
+
const input: ConnNext = {
|
|
147
|
+
remoteAddr: mockAddr,
|
|
148
|
+
localAddr: mockAddr,
|
|
149
|
+
read: (length) => inputWrapper.read(length),
|
|
150
|
+
write: (data) => inputWrapper.write(data),
|
|
151
|
+
[Symbol.asyncIterator]: () => inputWrapper[Symbol.asyncIterator](),
|
|
152
|
+
close: () => {
|
|
153
|
+
inputWrapper.close();
|
|
154
|
+
outputWrapper.close();
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const output: ConnNext = {
|
|
159
|
+
remoteAddr: mockAddr,
|
|
160
|
+
localAddr: mockAddr,
|
|
161
|
+
read: (length) => outputWrapper.read(length),
|
|
162
|
+
write: (data) => outputWrapper.write(data),
|
|
163
|
+
[Symbol.asyncIterator]: () => outputWrapper[Symbol.asyncIterator](),
|
|
164
|
+
close: () => {
|
|
165
|
+
outputWrapper.close();
|
|
166
|
+
inputWrapper.close();
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
return { input, output };
|
|
171
|
+
}
|