@seriousme/opifex 2.2.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 +57 -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/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 +74 -32
- 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 +20 -3
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +42 -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/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 +3 -3
- package/dist/web/wsClient.d.ts.map +1 -1
- package/dist/web/wsClient.js +19 -5
- 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/{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 +3 -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 +21 -8
- 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/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
package/node/tcpServer.test.ts
CHANGED
|
@@ -4,7 +4,9 @@ import { TcpClient } from "./tcpClient.ts";
|
|
|
4
4
|
import { TcpServer } from "./tcpServer.ts";
|
|
5
5
|
import { logger, LogLevel } from "../utils/mod.ts";
|
|
6
6
|
import { delay } from "../dev_utils/mod.ts";
|
|
7
|
-
import type { ProtocolLevel, PublishPacket
|
|
7
|
+
import type { ProtocolLevel, PublishPacket } from "../mqttPacket/mod.ts";
|
|
8
|
+
import { testClient } from "../dev_utils/testClient.ts";
|
|
9
|
+
import { MqttServer } from "../server/mod.ts";
|
|
8
10
|
|
|
9
11
|
logger.level(LogLevel.info);
|
|
10
12
|
|
|
@@ -25,64 +27,10 @@ test("Test pubSub using client and server", async () => {
|
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
const client = new TcpClient();
|
|
28
|
-
|
|
29
|
-
await client.connect(params);
|
|
30
|
-
assert(true, "Client connected to server");
|
|
31
|
-
|
|
32
|
-
const publishSet: { topic: string; qos: QoS }[] = [
|
|
33
|
-
{ topic: "t0@q0", qos: 0 },
|
|
34
|
-
{ topic: "t1@q0", qos: 0 },
|
|
35
|
-
{ topic: "t2@q0", qos: 0 },
|
|
36
|
-
{ topic: "t0@q1", qos: 1 },
|
|
37
|
-
{ topic: "t1@q1", qos: 1 },
|
|
38
|
-
{ topic: "t2@q1", qos: 1 },
|
|
39
|
-
{ topic: "t0@q2", qos: 2 },
|
|
40
|
-
{ topic: "t1@q2", qos: 2 },
|
|
41
|
-
{ topic: "t2@q2", qos: 2 },
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
const subscriptions = publishSet.map((item) => ({
|
|
45
|
-
topicFilter: item.topic,
|
|
46
|
-
qos: item.qos,
|
|
47
|
-
}));
|
|
48
|
-
await client.subscribe({
|
|
49
|
-
subscriptions,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// the IIFE ensures message reception runs in parallel
|
|
53
|
-
logger.verbose("Start receiving");
|
|
54
|
-
const received: PublishPacket[] = [];
|
|
55
|
-
(async () => {
|
|
56
|
-
for await (const item of client.messages()) {
|
|
57
|
-
logger.verbose("Receiving:", item.topic, "--", item.qos);
|
|
58
|
-
received.push(item);
|
|
59
|
-
}
|
|
60
|
-
})();
|
|
61
|
-
// end of IIFE
|
|
62
|
-
for (const item of publishSet) {
|
|
63
|
-
logger.verbose("Publishing:", item.topic, "--", item.qos);
|
|
64
|
-
await client.publish({
|
|
65
|
-
topic: item.topic,
|
|
66
|
-
qos: item.qos,
|
|
67
|
-
payload: new Uint8Array([0x01]),
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
await delay(100);
|
|
72
|
-
logger.verbose("Disconnect client");
|
|
73
|
-
await client.disconnect();
|
|
74
|
-
|
|
75
|
-
logger.verbose("Check completeness");
|
|
76
|
-
for (const item of publishSet) {
|
|
77
|
-
const found = received.find((f) =>
|
|
78
|
-
f.topic == item.topic && f.qos === item.qos
|
|
79
|
-
);
|
|
80
|
-
logger.verbose("Found:", item.topic, "--", item.qos);
|
|
81
|
-
assert(found, `${item.topic} -- ${item.qos}`);
|
|
82
|
-
}
|
|
30
|
+
await testClient(client, params);
|
|
83
31
|
|
|
84
32
|
logger.verbose("Stop server");
|
|
85
|
-
server.stop();
|
|
33
|
+
await server.stop();
|
|
86
34
|
});
|
|
87
35
|
|
|
88
36
|
test("Test subscription persistence after reconnect", async () => {
|
|
@@ -148,5 +96,11 @@ test("Test subscription persistence after reconnect", async () => {
|
|
|
148
96
|
"Should receive message on subscribed topic",
|
|
149
97
|
);
|
|
150
98
|
|
|
151
|
-
server.stop();
|
|
99
|
+
await server.stop();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("Passing a mqttServer works", () => {
|
|
103
|
+
const mqttServer = new MqttServer({});
|
|
104
|
+
const server = new TcpServer({ port: 0 }, mqttServer);
|
|
105
|
+
assert(server.mqttServer === mqttServer);
|
|
152
106
|
});
|
package/node/tcpServer.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { Server } from "node:net";
|
|
|
7
7
|
import { createServer } from "node:net";
|
|
8
8
|
import type { MqttServerOptions } from "../server/mod.ts";
|
|
9
9
|
import { MqttServer } from "../server/mod.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { nodeSocketToNext } from "./nodeSocketToNext.ts";
|
|
11
11
|
|
|
12
12
|
type ServerOptions = {
|
|
13
13
|
hostname?: string;
|
|
@@ -18,16 +18,24 @@ type ServerOptions = {
|
|
|
18
18
|
* TCP server that wraps a MqttServer, see the /examples folder
|
|
19
19
|
*/
|
|
20
20
|
export class TcpServer {
|
|
21
|
-
|
|
21
|
+
/** the MqttServer instance used by the server */
|
|
22
|
+
readonly mqttServer: MqttServer;
|
|
22
23
|
private server?: Server;
|
|
23
24
|
private serverOptions;
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Create a new TCP server
|
|
27
28
|
*/
|
|
28
|
-
constructor(
|
|
29
|
-
|
|
29
|
+
constructor(
|
|
30
|
+
serverOptions: ServerOptions,
|
|
31
|
+
mqttOptions: MqttServerOptions | MqttServer,
|
|
32
|
+
) {
|
|
30
33
|
this.serverOptions = serverOptions;
|
|
34
|
+
if (mqttOptions instanceof MqttServer) {
|
|
35
|
+
this.mqttServer = mqttOptions;
|
|
36
|
+
} else {
|
|
37
|
+
this.mqttServer = new MqttServer(mqttOptions);
|
|
38
|
+
}
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
/**
|
|
@@ -35,7 +43,7 @@ export class TcpServer {
|
|
|
35
43
|
*/
|
|
36
44
|
async start(): Promise<void> {
|
|
37
45
|
this.server = createServer((sock) =>
|
|
38
|
-
this.mqttServer.serve(
|
|
46
|
+
this.mqttServer.serve(nodeSocketToNext(sock))
|
|
39
47
|
);
|
|
40
48
|
const isListening = new Promise((resolve) => {
|
|
41
49
|
this.server?.on("listening", () => {
|
|
@@ -48,10 +56,12 @@ export class TcpServer {
|
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
/**
|
|
51
|
-
* Stop listening
|
|
59
|
+
* Stop listening, pass {closeMqtt:false} if you do not want the mqttServer to close
|
|
52
60
|
*/
|
|
53
|
-
stop(): void {
|
|
54
|
-
|
|
61
|
+
async stop(opts = { closeMqtt: true }): Promise<void> {
|
|
62
|
+
if (opts.closeMqtt) {
|
|
63
|
+
await this.mqttServer.close();
|
|
64
|
+
}
|
|
55
65
|
this.server?.close();
|
|
56
66
|
}
|
|
57
67
|
|
package/node/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
|
|
|
@@ -33,62 +34,14 @@ test("Test pubSub using client and server", async () => {
|
|
|
33
34
|
logger.verbose("client parameters: ", params);
|
|
34
35
|
|
|
35
36
|
const client = new TcpClient();
|
|
36
|
-
|
|
37
|
-
await client.connect(params);
|
|
38
|
-
assert(true, "Client connected to server");
|
|
39
|
-
|
|
40
|
-
const publishSet: { topic: string; qos: QoS }[] = [
|
|
41
|
-
{ topic: "t0@q0", qos: 0 },
|
|
42
|
-
{ topic: "t1@q0", qos: 0 },
|
|
43
|
-
{ topic: "t2@q0", qos: 0 },
|
|
44
|
-
{ topic: "t0@q1", qos: 1 },
|
|
45
|
-
{ topic: "t1@q1", qos: 1 },
|
|
46
|
-
{ topic: "t2@q1", qos: 1 },
|
|
47
|
-
{ topic: "t0@q2", qos: 2 },
|
|
48
|
-
{ topic: "t1@q2", qos: 2 },
|
|
49
|
-
{ topic: "t2@q2", qos: 2 },
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
const subscriptions = publishSet.map((item) => ({
|
|
53
|
-
topicFilter: item.topic,
|
|
54
|
-
qos: item.qos,
|
|
55
|
-
}));
|
|
56
|
-
await client.subscribe({
|
|
57
|
-
subscriptions,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// the IIFE ensures message reception runs in parallel
|
|
61
|
-
logger.verbose("Start receiving");
|
|
62
|
-
const received: PublishPacket[] = [];
|
|
63
|
-
(async () => {
|
|
64
|
-
for await (const item of client.messages()) {
|
|
65
|
-
logger.verbose("Receiving:", item.topic, "--", item.qos);
|
|
66
|
-
received.push(item);
|
|
67
|
-
}
|
|
68
|
-
})();
|
|
69
|
-
// end of IIFE
|
|
70
|
-
for (const item of publishSet) {
|
|
71
|
-
logger.verbose("Publishing:", item.topic, "--", item.qos);
|
|
72
|
-
await client.publish({
|
|
73
|
-
topic: item.topic,
|
|
74
|
-
qos: item.qos,
|
|
75
|
-
payload: new Uint8Array([0x01]),
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
await delay(100);
|
|
80
|
-
logger.verbose("Disconnect client");
|
|
81
|
-
await client.disconnect();
|
|
82
|
-
|
|
83
|
-
logger.verbose("Check completeness");
|
|
84
|
-
for (const item of publishSet) {
|
|
85
|
-
const found = received.find((f) =>
|
|
86
|
-
f.topic == item.topic && f.qos === item.qos
|
|
87
|
-
);
|
|
88
|
-
logger.verbose("Found:", item.topic, "--", item.qos);
|
|
89
|
-
assert(found, `${item.topic} -- ${item.qos}`);
|
|
90
|
-
}
|
|
37
|
+
await testClient(client, params);
|
|
91
38
|
|
|
92
39
|
logger.verbose("Stop server");
|
|
93
|
-
server.stop();
|
|
40
|
+
await server.stop();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("Passing a mqttServer works", () => {
|
|
44
|
+
const mqttServer = new MqttServer({});
|
|
45
|
+
const server = new TlsServer({ port: 0, key, cert }, mqttServer);
|
|
46
|
+
assert(server.mqttServer === mqttServer);
|
|
94
47
|
});
|
package/node/tlsServer.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { Server } from "node:net";
|
|
|
7
7
|
import { createServer } from "node:tls";
|
|
8
8
|
import type { MqttServerOptions } from "../server/mod.ts";
|
|
9
9
|
import { MqttServer } from "../server/mod.ts";
|
|
10
|
-
import {
|
|
10
|
+
import { nodeSocketToNext } from "./nodeSocketToNext.ts";
|
|
11
11
|
|
|
12
12
|
type ServerOptions = {
|
|
13
13
|
hostname?: string;
|
|
@@ -20,16 +20,24 @@ type ServerOptions = {
|
|
|
20
20
|
* TLS server that wraps a MqttServer, see the /examples folder
|
|
21
21
|
*/
|
|
22
22
|
export class TlsServer {
|
|
23
|
-
|
|
23
|
+
/** the MqttServer instance used by the server */
|
|
24
|
+
readonly mqttServer: MqttServer;
|
|
24
25
|
private server?: Server;
|
|
25
26
|
private serverOptions;
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Create a new TLS server
|
|
29
30
|
*/
|
|
30
|
-
constructor(
|
|
31
|
-
|
|
31
|
+
constructor(
|
|
32
|
+
serverOptions: ServerOptions,
|
|
33
|
+
mqttOptions: MqttServerOptions | MqttServer,
|
|
34
|
+
) {
|
|
32
35
|
this.serverOptions = serverOptions;
|
|
36
|
+
if (mqttOptions instanceof MqttServer) {
|
|
37
|
+
this.mqttServer = mqttOptions;
|
|
38
|
+
} else {
|
|
39
|
+
this.mqttServer = new MqttServer(mqttOptions);
|
|
40
|
+
}
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
/**
|
|
@@ -44,7 +52,7 @@ export class TlsServer {
|
|
|
44
52
|
|
|
45
53
|
this.server = createServer(
|
|
46
54
|
tlsOptions,
|
|
47
|
-
(sock) => this.mqttServer.serve(
|
|
55
|
+
(sock) => this.mqttServer.serve(nodeSocketToNext(sock)),
|
|
48
56
|
);
|
|
49
57
|
const isListening = new Promise((resolve) => {
|
|
50
58
|
this.server?.on("listening", () => {
|
|
@@ -59,8 +67,10 @@ export class TlsServer {
|
|
|
59
67
|
/**
|
|
60
68
|
* Stop listening
|
|
61
69
|
*/
|
|
62
|
-
stop(): void {
|
|
63
|
-
|
|
70
|
+
async stop(opts = { closeMqtt: true }): Promise<void> {
|
|
71
|
+
if (opts.closeMqtt) {
|
|
72
|
+
await this.mqttServer.close();
|
|
73
|
+
}
|
|
64
74
|
this.server?.close();
|
|
65
75
|
}
|
|
66
76
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seriousme/opifex",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "MQTT client & server for Deno & NodeJS and Bun",
|
|
5
5
|
"author": "Hans Klunder",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"build": "rm -rf dist && npx -p typescript tsc --build",
|
|
31
31
|
"format": "deno fmt",
|
|
32
32
|
"coverage": "deno test -A . --coverage --clean --coverage-raw-data-only && deno coverage --exclude=.config --exclude=dev_utils --html",
|
|
33
|
-
"
|
|
33
|
+
"bundle": "deno bundle --platform=browser --minify -o examples/public/opifex-wsclient.min.js web/wsClient.ts --sourcemap",
|
|
34
|
+
"preversion": "npm run bundle && npm run test"
|
|
34
35
|
},
|
|
35
36
|
"files": [
|
|
36
37
|
"package.json",
|
package/server/context.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import type {
|
|
11
11
|
AnyPacket,
|
|
12
12
|
ConnectPacket,
|
|
13
|
+
ConnNext,
|
|
13
14
|
ExtPublishPacket,
|
|
14
15
|
PacketId,
|
|
15
16
|
Persistence,
|
|
@@ -18,7 +19,6 @@ import type {
|
|
|
18
19
|
PubrelPacket,
|
|
19
20
|
ReasonCode,
|
|
20
21
|
ShareName,
|
|
21
|
-
SockConn,
|
|
22
22
|
Topic,
|
|
23
23
|
} from "./deps.ts";
|
|
24
24
|
|
|
@@ -64,11 +64,11 @@ export type SessionState = typeof SessionState[keyof typeof SessionState];
|
|
|
64
64
|
export type Handlers = {
|
|
65
65
|
/**
|
|
66
66
|
* Default preconnect handler that unconditionally permits all connections.
|
|
67
|
-
* @param
|
|
67
|
+
* @param conn - The connection context.
|
|
68
68
|
* @returns {boolean} false will close the connection
|
|
69
69
|
*/
|
|
70
70
|
preconnect?(
|
|
71
|
-
conn:
|
|
71
|
+
conn: ConnNext,
|
|
72
72
|
): boolean | Promise<boolean>;
|
|
73
73
|
|
|
74
74
|
/**
|
|
@@ -162,7 +162,7 @@ export class Context {
|
|
|
162
162
|
protocolLevel: ProtocolLevel;
|
|
163
163
|
|
|
164
164
|
/** The underlying raw socket connection. */
|
|
165
|
-
conn:
|
|
165
|
+
conn: ConnNext;
|
|
166
166
|
|
|
167
167
|
/** The wrapped MQTT connection wrapper handling frame reading and writing. */
|
|
168
168
|
mqttConn: MqttConn;
|
|
@@ -234,7 +234,7 @@ export class Context {
|
|
|
234
234
|
constructor(
|
|
235
235
|
configuration: Configuration, // all settings
|
|
236
236
|
persistence: Persistence, // The server persistence layer implementation.
|
|
237
|
-
conn:
|
|
237
|
+
conn: ConnNext, // The underlying socket connection.
|
|
238
238
|
handlers: Handlers, // The validation handlers
|
|
239
239
|
) {
|
|
240
240
|
this.config = configuration;
|
|
@@ -676,10 +676,13 @@ export class Context {
|
|
|
676
676
|
this.sessionEndsTimer.reset();
|
|
677
677
|
}
|
|
678
678
|
|
|
679
|
+
const reason = (!this.mqttConn.reason || executewill)
|
|
680
|
+
? "normal disconnect"
|
|
681
|
+
: this.mqttConn.reason;
|
|
679
682
|
logger.info(
|
|
680
683
|
`closing connection from ${
|
|
681
684
|
this.clientId || this.mqttConn.remoteAddress
|
|
682
|
-
} because of "${
|
|
685
|
+
} because of "${reason}"`,
|
|
683
686
|
);
|
|
684
687
|
if (!this.mqttConn.isClosed) {
|
|
685
688
|
this.mqttConn.close();
|
package/server/deps.ts
CHANGED
package/server/mod.ts
CHANGED
package/server/server.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { logger, MemoryPersistence, ReasonCode } from "./deps.ts";
|
|
2
2
|
import { Context } from "./context.ts";
|
|
3
3
|
import type { Handlers } from "./context.ts";
|
|
4
|
-
import type { Persistence, ShareName,
|
|
4
|
+
import type { ConnNext, Persistence, ShareName, Topic } from "./deps.ts";
|
|
5
5
|
import { handlePacket } from "./handlers/handlePacket.ts";
|
|
6
6
|
import { createConfiguration } from "./config.ts";
|
|
7
7
|
import type { Configuration, ConfigurationInput } from "./config.ts";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Default preconnect handler that unconditionally permits all connections.
|
|
11
|
-
* @param
|
|
12
|
-
* @returns
|
|
11
|
+
* @param _conn - The connection.
|
|
12
|
+
* @returns Always returns true
|
|
13
13
|
*/
|
|
14
14
|
const defaultPreconnect = (
|
|
15
|
-
_conn:
|
|
15
|
+
_conn: ConnNext,
|
|
16
16
|
) => (true) as const;
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -119,10 +119,10 @@ export class MqttServer {
|
|
|
119
119
|
|
|
120
120
|
/**
|
|
121
121
|
* Serve a new client connection.
|
|
122
|
-
* @param
|
|
122
|
+
* @param conn - The socket connection to serve.
|
|
123
123
|
* @returns {Promise<void>} A promise that resolves when the connection is closed.
|
|
124
124
|
*/
|
|
125
|
-
async serve(conn:
|
|
125
|
+
async serve(conn: ConnNext): Promise<void> {
|
|
126
126
|
if (this.handlers.preconnect) {
|
|
127
127
|
if (!await this.handlers.preconnect(conn)) {
|
|
128
128
|
conn.close();
|
package/utils/utils.ts
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* deserve their own dedicated files.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
export function delay(ms: number): Promise<void> {
|
|
7
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8
|
+
}
|
|
8
9
|
|
|
9
10
|
export const noop = () => {};
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { ConnAddr, ConnNext } from "../connNext/connNext.ts";
|
|
2
|
+
|
|
3
|
+
export async function webSocketToNext(
|
|
4
|
+
ws: WebSocket,
|
|
5
|
+
remoteAddr?: ConnAddr,
|
|
6
|
+
): Promise<ConnNext> {
|
|
7
|
+
ws.binaryType = "arraybuffer";
|
|
8
|
+
|
|
9
|
+
const messageQueue: Uint8Array[] = [];
|
|
10
|
+
let queueOffset = 0;
|
|
11
|
+
const readResolvers: Array<() => void> = [];
|
|
12
|
+
|
|
13
|
+
const notifyWaiters = () => {
|
|
14
|
+
while (readResolvers.length > 0) {
|
|
15
|
+
const resolve = readResolvers.shift();
|
|
16
|
+
resolve?.();
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// helper to check if websocket is Open
|
|
21
|
+
const ensureOpen = (): Promise<void> => {
|
|
22
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
23
|
+
return Promise.resolve();
|
|
24
|
+
}
|
|
25
|
+
if (ws.readyState >= WebSocket.CLOSING) {
|
|
26
|
+
return Promise.reject(new Error("WebSocket is closing or closed"));
|
|
27
|
+
}
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const onOpen = () => {
|
|
30
|
+
cleanup();
|
|
31
|
+
resolve();
|
|
32
|
+
};
|
|
33
|
+
const onError = (err: Event) => {
|
|
34
|
+
cleanup();
|
|
35
|
+
reject(err);
|
|
36
|
+
};
|
|
37
|
+
const onClose = () => {
|
|
38
|
+
cleanup();
|
|
39
|
+
reject(new Error("WebSocket closed before opening"));
|
|
40
|
+
};
|
|
41
|
+
const cleanup = () => {
|
|
42
|
+
ws.removeEventListener("open", onOpen);
|
|
43
|
+
ws.removeEventListener("error", onError);
|
|
44
|
+
ws.removeEventListener("close", onClose);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
ws.addEventListener("open", onOpen);
|
|
48
|
+
ws.addEventListener("error", onError);
|
|
49
|
+
ws.addEventListener("close", onClose);
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
ws.addEventListener("message", (event: MessageEvent) => {
|
|
54
|
+
let chunk: Uint8Array;
|
|
55
|
+
if (typeof event.data === "string") {
|
|
56
|
+
chunk = new TextEncoder().encode(event.data);
|
|
57
|
+
} else if (event.data instanceof ArrayBuffer) {
|
|
58
|
+
chunk = new Uint8Array(event.data);
|
|
59
|
+
} else {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
messageQueue.push(chunk);
|
|
63
|
+
notifyWaiters();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
ws.addEventListener("close", notifyWaiters);
|
|
67
|
+
ws.addEventListener("error", notifyWaiters);
|
|
68
|
+
|
|
69
|
+
const getBufferedBytes = (): number => {
|
|
70
|
+
let total = 0;
|
|
71
|
+
for (let i = 0; i < messageQueue.length; i++) {
|
|
72
|
+
total += i === 0
|
|
73
|
+
? messageQueue[i]!.byteLength - queueOffset
|
|
74
|
+
: messageQueue[i]!.byteLength;
|
|
75
|
+
}
|
|
76
|
+
return total;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const waitForData = (): Promise<void> => {
|
|
80
|
+
return new Promise<void>((resolve) => {
|
|
81
|
+
readResolvers.push(resolve);
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const connNext = {
|
|
86
|
+
remoteAddr,
|
|
87
|
+
localAddr: undefined,
|
|
88
|
+
|
|
89
|
+
async read(length?: number): Promise<Uint8Array | null> {
|
|
90
|
+
try {
|
|
91
|
+
await ensureOpen();
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (getBufferedBytes() === 0 && ws.readyState >= WebSocket.CLOSING) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
while (getBufferedBytes() === 0) {
|
|
101
|
+
if (ws.readyState >= WebSocket.CLOSING) return null;
|
|
102
|
+
await waitForData();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (length === undefined) {
|
|
106
|
+
const firstChunk = messageQueue.shift()!;
|
|
107
|
+
const result = firstChunk.subarray(queueOffset);
|
|
108
|
+
queueOffset = 0;
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
while (getBufferedBytes() < length) {
|
|
113
|
+
if (ws.readyState >= WebSocket.CLOSING) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
await waitForData();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const result = new Uint8Array(length);
|
|
120
|
+
let bytesCopied = 0;
|
|
121
|
+
|
|
122
|
+
while (bytesCopied < length && messageQueue.length > 0) {
|
|
123
|
+
const currentChunk = messageQueue[0]!;
|
|
124
|
+
const bytesNeeded = length - bytesCopied;
|
|
125
|
+
const bytesAvailable = currentChunk.byteLength - queueOffset;
|
|
126
|
+
|
|
127
|
+
if (bytesAvailable <= bytesNeeded) {
|
|
128
|
+
result.set(currentChunk.subarray(queueOffset), bytesCopied);
|
|
129
|
+
bytesCopied += bytesAvailable;
|
|
130
|
+
messageQueue.shift();
|
|
131
|
+
queueOffset = 0;
|
|
132
|
+
} else {
|
|
133
|
+
result.set(
|
|
134
|
+
currentChunk.subarray(queueOffset, queueOffset + bytesNeeded),
|
|
135
|
+
bytesCopied,
|
|
136
|
+
);
|
|
137
|
+
bytesCopied += bytesNeeded;
|
|
138
|
+
queueOffset += bytesNeeded;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return result;
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
async write(data: Uint8Array): Promise<void> {
|
|
146
|
+
await ensureOpen();
|
|
147
|
+
|
|
148
|
+
if (data instanceof Uint8Array) {
|
|
149
|
+
ws.send(data.buffer as ArrayBuffer);
|
|
150
|
+
} else {
|
|
151
|
+
ws.send(data);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const HIGH_WATER_MARK = 64 * 1024;
|
|
155
|
+
while (ws.bufferedAmount > HIGH_WATER_MARK) {
|
|
156
|
+
if (ws.readyState >= WebSocket.CLOSING) {
|
|
157
|
+
throw new Error("WebSocket closed while writing");
|
|
158
|
+
}
|
|
159
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
close(): void {
|
|
164
|
+
try {
|
|
165
|
+
ws.close();
|
|
166
|
+
} catch {
|
|
167
|
+
/* swallow */
|
|
168
|
+
}
|
|
169
|
+
notifyWaiters();
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
async *[Symbol.asyncIterator]() {
|
|
173
|
+
while (true) {
|
|
174
|
+
const chunk = await this.read();
|
|
175
|
+
if (chunk === null) break;
|
|
176
|
+
yield chunk;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
await ensureOpen();
|
|
182
|
+
return connNext;
|
|
183
|
+
}
|
package/web/wsClient.test.ts
CHANGED
|
@@ -24,3 +24,25 @@ test("Web: Test createConn - unsupported protocol", async () => {
|
|
|
24
24
|
"Should throw an error when an unsupported protocol is provided",
|
|
25
25
|
);
|
|
26
26
|
});
|
|
27
|
+
|
|
28
|
+
test("Web: Test createConn - unsupported host", async () => {
|
|
29
|
+
const client = new WsClient();
|
|
30
|
+
|
|
31
|
+
// Use an host protocol to trigger the ws error
|
|
32
|
+
const invalidParams = {
|
|
33
|
+
url: new URL("ws://test.invalid"),
|
|
34
|
+
numberOfRetries: 0,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Assert that connect rejects with the specific error
|
|
38
|
+
await assert.rejects(
|
|
39
|
+
async () => {
|
|
40
|
+
await client.connect(invalidParams);
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "Error",
|
|
44
|
+
message: /^Failed to connect to WebSocket/,
|
|
45
|
+
},
|
|
46
|
+
"Should throw an error when an unreachable host is provided",
|
|
47
|
+
);
|
|
48
|
+
});
|