@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.
- package/README.md +7 -6
- package/bin/mqtt.ts +1 -1
- package/client/client.ts +62 -7
- package/client/context.ts +2 -2
- package/client/deps.ts +3 -3
- package/client/mod.ts +2 -2
- package/connNext/baseConnNext.ts +123 -0
- package/connNext/connNext.ts +44 -0
- package/deno/README.md +1 -1
- package/deno/denoConnToNext.ts +104 -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 +82 -32
- package/dev_utils/WebStreamEndpoint.ts +5 -0
- package/dev_utils/asyncStreamEndpoint.ts +5 -0
- package/dev_utils/asyncStreamPair.ts +109 -0
- package/dev_utils/asyncStreamPairToNext.ts +45 -0
- package/dev_utils/asyncStreamToNext.ts +64 -0
- package/dev_utils/connNextPair.test.ts +179 -0
- package/dev_utils/mockConn.ts +59 -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/webStreamPair.ts +3 -7
- package/dev_utils/webStreamPairToNext.ts +48 -0
- package/dev_utils/webStreamToNext.ts +54 -0
- package/dist/bin/mqtt.js +1 -1
- package/dist/bin/mqtt.js.map +1 -1
- package/dist/client/client.d.ts +24 -3
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +45 -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/baseConnNext.d.ts +20 -0
- package/dist/connNext/baseConnNext.d.ts.map +1 -0
- package/dist/connNext/baseConnNext.js +95 -0
- package/dist/connNext/baseConnNext.js.map +1 -0
- 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/node/nodeSocketToNext.d.ts +17 -0
- package/dist/node/nodeSocketToNext.d.ts.map +1 -0
- package/dist/node/nodeSocketToNext.js +105 -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 +20 -0
- package/dist/web/webSocketToNext.d.ts.map +1 -0
- package/dist/web/webSocketToNext.js +125 -0
- package/dist/web/webSocketToNext.js.map +1 -0
- package/dist/web/wsClient.d.ts +3 -3
- package/dist/web/wsClient.d.ts.map +1 -1
- package/dist/web/wsClient.js +22 -7
- 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/node/README.md +1 -1
- package/node/nodeSocketToNext.ts +124 -0
- 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 +4 -3
- 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 +146 -0
- package/web/wsClient.test.ts +22 -0
- package/web/wsClient.ts +29 -9
- package/dev_utils/webStreamPair.test.ts +0 -171
- 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 -58
- package/dist/socket/socket.d.ts.map +0 -1
- package/dist/socket/socket.js +0 -99
- 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/socket-node-stream.ts +0 -174
- package/node/wrapNodeSocket.ts +0 -44
- package/socket/socket.test.ts +0 -91
- package/socket/socket.ts +0 -149
- package/web/webSocketStream.ts +0 -97
- 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
|
+
});
|
package/examples/mqtt.test.ts
CHANGED
|
@@ -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
|
});
|
package/examples/mqtts.test.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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);
|
package/mqttConn/mqttConn.ts
CHANGED
|
@@ -16,8 +16,7 @@ import {
|
|
|
16
16
|
} from "../mqttPacket/mod.ts";
|
|
17
17
|
|
|
18
18
|
import { assert } from "../utils/mod.ts";
|
|
19
|
-
import type {
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
136
|
+
& { conn: ConnNext; maxConnectPacketSize?: number }
|
|
138
137
|
& Partial<CodecOpts>,
|
|
139
138
|
) {
|
|
140
|
-
this.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
|
|
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
|
|
11
|
-
import type {
|
|
12
|
-
import {
|
|
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(
|
|
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(
|
|
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
|
|
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<
|
|
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 =
|
|
93
|
+
const socket = connect(opts, () => {
|
|
91
94
|
logger.debug("Connected to server");
|
|
92
|
-
resolve(
|
|
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
|
}
|