@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/README.md
CHANGED
|
@@ -111,10 +111,11 @@ can be found in the [examples](/examples/) folder.
|
|
|
111
111
|
encode and decode packets.
|
|
112
112
|
|
|
113
113
|
2. On top of mqttPacket sits the MQTT connection module
|
|
114
|
-
([mqttConn/mod.ts](mqttConn/mod.ts)) that reads packets from
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
([mqttConn/mod.ts](mqttConn/mod.ts)) that reads packets from and writes them
|
|
115
|
+
to a [ConnNext](connNext) adapter. It will take care of incomplete and/or
|
|
116
|
+
malformed packets. mqttConn provides an async iterable that can be awaited
|
|
117
|
+
for new packets. A ConnNext adapter per transport provides async
|
|
118
|
+
read/write/close/address functionality on top of the various transports.
|
|
118
119
|
|
|
119
120
|
3. On top of mqttConn live the MQTT server ([server/mod.ts](server/mod.ts)) and
|
|
120
121
|
MQTT client ([client/mod.ts](client/mod.ts)) that take care of the MQTT
|
|
@@ -122,8 +123,8 @@ can be found in the [examples](/examples/) folder.
|
|
|
122
123
|
another type of packet will be accepted. Both follow a similar model of
|
|
123
124
|
implementation where for each packet that is received a handler is invoked
|
|
124
125
|
which then triggers the next step. Server and client are totally independent
|
|
125
|
-
of the technical implementation of the connection and only need a
|
|
126
|
-
|
|
126
|
+
of the technical implementation of the connection and only need a
|
|
127
|
+
[ConnNext](connNext) connection to be able to work.
|
|
127
128
|
|
|
128
129
|
4. As the server needs to be able to serve multiple clients at the same time, it
|
|
129
130
|
maintains a context ([server/context.ts](server/context.ts)) per client to
|
package/bin/mqtt.ts
CHANGED
package/client/client.ts
CHANGED
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
import type {
|
|
11
11
|
AuthenticationResult,
|
|
12
12
|
ConnectPacket,
|
|
13
|
+
ConnNext,
|
|
13
14
|
ProtocolLevel,
|
|
14
15
|
PublishPacket,
|
|
15
|
-
SockConn,
|
|
16
16
|
Store,
|
|
17
17
|
SubscribePacket,
|
|
18
18
|
} from "./deps.ts";
|
|
@@ -169,7 +169,6 @@ export class Client {
|
|
|
169
169
|
const cStore = store ? store : new MemoryStore();
|
|
170
170
|
this.ctx = new Context(cStore, this);
|
|
171
171
|
this.clientId = generateClientId(this.clientIdPrefix);
|
|
172
|
-
this.numberOfRetries = DEFAULT_RETRIES;
|
|
173
172
|
}
|
|
174
173
|
|
|
175
174
|
/**
|
|
@@ -188,14 +187,58 @@ export class Client {
|
|
|
188
187
|
return this.connectUrl;
|
|
189
188
|
}
|
|
190
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Placeholder for MQTT, override in subclass
|
|
192
|
+
* @param _hostname
|
|
193
|
+
* @param _port
|
|
194
|
+
*/
|
|
195
|
+
protected connectMQTT(
|
|
196
|
+
_hostname: string,
|
|
197
|
+
_port: number,
|
|
198
|
+
): Promise<ConnNext> {
|
|
199
|
+
throw new Error(`Unsupported protocol: mqtt`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Connect using MQTT over TLS
|
|
204
|
+
* @param hostname the name of the host to connect to
|
|
205
|
+
* @param port the port number
|
|
206
|
+
* @param caCerts the CA certificates to trust
|
|
207
|
+
* @param cert the clients certificate
|
|
208
|
+
* @param key the clients private key
|
|
209
|
+
* @returns a ConnNext object
|
|
210
|
+
*/
|
|
211
|
+
protected connectMQTTS(
|
|
212
|
+
_hostname: string,
|
|
213
|
+
_port: number,
|
|
214
|
+
_caCerts?: string[],
|
|
215
|
+
_cert?: string,
|
|
216
|
+
_key?: string,
|
|
217
|
+
): Promise<ConnNext> {
|
|
218
|
+
throw new Error(`Unsupported protocol: mqtts`);
|
|
219
|
+
}
|
|
220
|
+
|
|
191
221
|
/**
|
|
192
222
|
* Creates a new connection to the MQTT broker
|
|
193
|
-
*
|
|
223
|
+
* Override this in a subclass if you need other protocols
|
|
224
|
+
* @returns Promise resolving to a ConnNext connection
|
|
194
225
|
*/
|
|
195
|
-
protected createConn(): Promise<
|
|
226
|
+
protected createConn(): Promise<ConnNext> {
|
|
196
227
|
// if you need to support alternative connection types just
|
|
197
228
|
// overload this method in your subclass
|
|
198
|
-
|
|
229
|
+
const { protocol, hostname, port: portStr } = this.connectUrl;
|
|
230
|
+
const port = Number(portStr);
|
|
231
|
+
const caCerts = this.caCerts;
|
|
232
|
+
const cert = this.cert;
|
|
233
|
+
const key = this.key;
|
|
234
|
+
|
|
235
|
+
if (protocol === "mqtts:") {
|
|
236
|
+
return this.connectMQTTS(hostname, port, caCerts, cert, key);
|
|
237
|
+
}
|
|
238
|
+
if (protocol === "mqtt:") {
|
|
239
|
+
return this.connectMQTT(hostname, port);
|
|
240
|
+
}
|
|
241
|
+
throw new Error(`Unsupported protocol: ${protocol}`);
|
|
199
242
|
}
|
|
200
243
|
|
|
201
244
|
/**
|
|
@@ -233,7 +276,14 @@ export class Client {
|
|
|
233
276
|
logger.debug({ lastMessage });
|
|
234
277
|
}
|
|
235
278
|
|
|
236
|
-
|
|
279
|
+
logger.debug("tryConnect", {
|
|
280
|
+
tryConnect,
|
|
281
|
+
isReconnect,
|
|
282
|
+
attempt,
|
|
283
|
+
retries: this.numberOfRetries,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
if (tryConnect && (isReconnect || (attempt < this.numberOfRetries))) {
|
|
237
287
|
await backOffSleep(true, attempt);
|
|
238
288
|
if (!isReconnect) {
|
|
239
289
|
attempt++;
|
|
@@ -256,7 +306,7 @@ export class Client {
|
|
|
256
306
|
*/
|
|
257
307
|
connect(params: ConnectParameters = {}): Promise<AuthenticationResult> {
|
|
258
308
|
this.connectUrl = params?.url || this.connectUrl;
|
|
259
|
-
this.numberOfRetries = params.numberOfRetries
|
|
309
|
+
this.numberOfRetries = params.numberOfRetries ?? this.numberOfRetries;
|
|
260
310
|
this.caCerts = params?.caCerts;
|
|
261
311
|
this.cert = params?.cert;
|
|
262
312
|
this.key = params?.key;
|
package/client/context.ts
CHANGED
|
@@ -10,12 +10,12 @@ import type {
|
|
|
10
10
|
AnyPacket,
|
|
11
11
|
AuthenticationResult,
|
|
12
12
|
ConnectPacket,
|
|
13
|
+
ConnNext,
|
|
13
14
|
PacketId,
|
|
14
15
|
ProtocolLevel,
|
|
15
16
|
PublishPacket,
|
|
16
17
|
ReasonCode,
|
|
17
18
|
ReturnCodes,
|
|
18
|
-
SockConn,
|
|
19
19
|
Store,
|
|
20
20
|
SubscribePacket,
|
|
21
21
|
UnsubscribePacket,
|
|
@@ -169,7 +169,7 @@ export class Context {
|
|
|
169
169
|
}
|
|
170
170
|
|
|
171
171
|
async handleConnection(
|
|
172
|
-
conn:
|
|
172
|
+
conn: ConnNext,
|
|
173
173
|
connectPacket: ConnectPacket,
|
|
174
174
|
): Promise<boolean> {
|
|
175
175
|
this.mqttConn = new MqttConn({ conn });
|
package/client/deps.ts
CHANGED
|
@@ -36,12 +36,12 @@ export {
|
|
|
36
36
|
|
|
37
37
|
export { MqttConn } from "../mqttConn/mqttConn.ts";
|
|
38
38
|
export type {
|
|
39
|
+
ConnAddr,
|
|
40
|
+
ConnNext,
|
|
39
41
|
NetAddr,
|
|
40
|
-
SockAddr,
|
|
41
|
-
SockConn,
|
|
42
42
|
UnixAddr,
|
|
43
43
|
VsockAddr,
|
|
44
|
-
} from "../
|
|
44
|
+
} from "../connNext/connNext.ts";
|
|
45
45
|
export { Timer } from "../timer/timer.ts";
|
|
46
46
|
export { BufferedAsyncIterable, Deferred, logger } from "../utils/mod.ts";
|
|
47
47
|
export { MemoryStore } from "./store/memoryStore.ts";
|
package/client/mod.ts
CHANGED
|
@@ -11,11 +11,11 @@ export {
|
|
|
11
11
|
export { logger, MQTTLevel, ReasonCode } from "./deps.ts";
|
|
12
12
|
export type {
|
|
13
13
|
AuthenticationResult,
|
|
14
|
+
ConnAddr,
|
|
15
|
+
ConnNext,
|
|
14
16
|
NetAddr,
|
|
15
17
|
ProtocolLevel,
|
|
16
18
|
QoS,
|
|
17
|
-
SockAddr,
|
|
18
|
-
SockConn,
|
|
19
19
|
UnixAddr,
|
|
20
20
|
VsockAddr,
|
|
21
21
|
} from "./deps.ts";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** Net addresses */
|
|
2
|
+
export type NetAddr = {
|
|
3
|
+
/** the IP-based transport protocol */
|
|
4
|
+
transport: "tcp" | "udp";
|
|
5
|
+
/** the hostname */
|
|
6
|
+
hostname: string;
|
|
7
|
+
/** the port number */
|
|
8
|
+
port: number;
|
|
9
|
+
};
|
|
10
|
+
/** Unix addresses */
|
|
11
|
+
export type UnixAddr = {
|
|
12
|
+
/** the Unix domain socket transport protocol */
|
|
13
|
+
transport: "unix" | "unixpacket";
|
|
14
|
+
/** the file system path to the socket to connect to */
|
|
15
|
+
path: string;
|
|
16
|
+
};
|
|
17
|
+
/** vSock addresses */
|
|
18
|
+
export type VsockAddr = {
|
|
19
|
+
/** the VSOCK transport protocol */
|
|
20
|
+
transport: "vsock";
|
|
21
|
+
/** the context identifier (CID) of the peer */
|
|
22
|
+
cid: number;
|
|
23
|
+
/** the port number */
|
|
24
|
+
port: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** connection addresses */
|
|
28
|
+
export type ConnAddr = NetAddr | UnixAddr | VsockAddr | undefined;
|
|
29
|
+
/** connection descriptor. */
|
|
30
|
+
|
|
31
|
+
export type ConnNext = {
|
|
32
|
+
/** the "nextStream" to read from */
|
|
33
|
+
read(length?: number): Promise<Uint8Array | null>;
|
|
34
|
+
/** the "nextStream" to write to */
|
|
35
|
+
write(data: Uint8Array): Promise<void>;
|
|
36
|
+
/** close the connection */
|
|
37
|
+
close(): void;
|
|
38
|
+
/** Async Iterator for streaming per chunck */
|
|
39
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<Uint8Array>;
|
|
40
|
+
/** The local address of the connection */
|
|
41
|
+
localAddr?: ConnAddr;
|
|
42
|
+
/** The remote address of the connection */
|
|
43
|
+
remoteAddr?: ConnAddr;
|
|
44
|
+
};
|
package/deno/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Running Opifex server/client on Deno
|
|
2
2
|
|
|
3
|
-
These instructions specifify how to run [Opifex](README.md) on Deno. To run
|
|
3
|
+
These instructions specifify how to run [Opifex](../README.md) on Deno. To run
|
|
4
4
|
Opifex on NodeJS see the [instructions](../node/README.md)
|
|
5
5
|
|
|
6
6
|
## Playing around
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { ConnAddr, ConnNext } from "../connNext/connNext.ts";
|
|
2
|
+
|
|
3
|
+
export function denoConnToNext(conn: Deno.Conn): ConnNext {
|
|
4
|
+
// Convert Deno network addresses to custom ConnAddr format
|
|
5
|
+
const formatAddr = (addr: Deno.Addr): ConnAddr => {
|
|
6
|
+
if (addr.transport === "tcp" || addr.transport === "udp") {
|
|
7
|
+
return {
|
|
8
|
+
transport: addr.transport,
|
|
9
|
+
hostname: addr.hostname,
|
|
10
|
+
port: addr.port,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
const unixAddr = addr as Deno.UnixAddr;
|
|
14
|
+
return {
|
|
15
|
+
transport: "unix",
|
|
16
|
+
path: unixAddr.path,
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Utilize a BYOB (Bring Your Own Buffer) reader on Deno's readable stream
|
|
21
|
+
// This enables exact sizing and zero-copy reads on the kernel buffer
|
|
22
|
+
const reader = conn.readable.getReader({ mode: "byob" });
|
|
23
|
+
const writer = conn.writable.getWriter();
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
remoteAddr: formatAddr(conn.remoteAddr),
|
|
27
|
+
localAddr: formatAddr(conn.localAddr),
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Read exactly 'length' bytes directly into a Uint8Array buffer
|
|
31
|
+
*/
|
|
32
|
+
async read(length?: number): Promise<Uint8Array | null> {
|
|
33
|
+
try {
|
|
34
|
+
// If no length is provided, read a default chunk size (e.g., 64KB)
|
|
35
|
+
const bytesToRead = length ?? 65536;
|
|
36
|
+
let buffer = new Uint8Array(bytesToRead);
|
|
37
|
+
let bytesRead = 0;
|
|
38
|
+
|
|
39
|
+
while (bytesRead < bytesToRead) {
|
|
40
|
+
// Pass the unfilled section of the buffer to Deno's kernel reader
|
|
41
|
+
const view = new Uint8Array(
|
|
42
|
+
buffer.buffer,
|
|
43
|
+
buffer.byteOffset + bytesRead,
|
|
44
|
+
bytesToRead - bytesRead,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const { value, done } = await reader.read(view);
|
|
48
|
+
|
|
49
|
+
if (done) {
|
|
50
|
+
// Connection closed before all data could be read
|
|
51
|
+
if (bytesRead === 0) return null;
|
|
52
|
+
// Return any partial data read prior to closure
|
|
53
|
+
return buffer.subarray(0, bytesRead);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// BYOB returns the populated ArrayBufferView
|
|
57
|
+
buffer = new Uint8Array(value.buffer);
|
|
58
|
+
bytesRead += value.byteLength;
|
|
59
|
+
|
|
60
|
+
// If no explicit length was requested, return the first chunk immediately
|
|
61
|
+
if (length === undefined) {
|
|
62
|
+
return buffer.subarray(0, bytesRead);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return buffer;
|
|
67
|
+
} catch {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Write via Deno's WritableStream with built-in backpressure handling
|
|
74
|
+
*/
|
|
75
|
+
async write(data: Uint8Array): Promise<void> {
|
|
76
|
+
// writer.write automatically pauses the promise when the TCP pipe is full
|
|
77
|
+
await writer.write(data);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Gracefully close reader, writer, and socket connection
|
|
82
|
+
*/
|
|
83
|
+
close(): void {
|
|
84
|
+
try {
|
|
85
|
+
writer.releaseLock();
|
|
86
|
+
reader.releaseLock();
|
|
87
|
+
conn.close();
|
|
88
|
+
} catch {
|
|
89
|
+
/* swallow */
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Async Iterator for Deno
|
|
95
|
+
*/
|
|
96
|
+
async *[Symbol.asyncIterator]() {
|
|
97
|
+
while (true) {
|
|
98
|
+
const chunk = await this.read();
|
|
99
|
+
if (chunk === null) break;
|
|
100
|
+
yield chunk;
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
package/deno/tcpClient.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Client, logger } from "../client/mod.ts";
|
|
8
|
-
import type {
|
|
8
|
+
import type { ConnNext } from "../client/mod.ts";
|
|
9
|
+
import { denoConnToNext } from "./denoConnToNext.ts";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Fetches data from a file and returns it as a string
|
|
@@ -39,12 +40,12 @@ export class TcpClient extends Client {
|
|
|
39
40
|
* @param hostname the name of the host to connect to
|
|
40
41
|
* @param port the port number
|
|
41
42
|
*/
|
|
42
|
-
protected async connectMQTT(
|
|
43
|
+
protected override async connectMQTT(
|
|
43
44
|
hostname: string,
|
|
44
45
|
port = 1883,
|
|
45
|
-
): Promise<
|
|
46
|
+
): Promise<ConnNext> {
|
|
46
47
|
logger.debug({ hostname, port });
|
|
47
|
-
return await Deno.connect({ hostname, port });
|
|
48
|
+
return denoConnToNext(await Deno.connect({ hostname, port }));
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
/**
|
|
@@ -56,13 +57,13 @@ export class TcpClient extends Client {
|
|
|
56
57
|
* @param key the clients private key
|
|
57
58
|
* @returns a Deno.TlsConn object
|
|
58
59
|
*/
|
|
59
|
-
protected async connectMQTTS(
|
|
60
|
+
protected override async connectMQTTS(
|
|
60
61
|
hostname: string,
|
|
61
62
|
port = 8883,
|
|
62
63
|
caCerts?: string[],
|
|
63
64
|
cert?: string,
|
|
64
65
|
key?: string,
|
|
65
|
-
): Promise<
|
|
66
|
+
): Promise<ConnNext> {
|
|
66
67
|
logger.debug({ hostname, port, caCerts, cert });
|
|
67
68
|
const connectOpts = {
|
|
68
69
|
hostname,
|
|
@@ -71,23 +72,6 @@ export class TcpClient extends Client {
|
|
|
71
72
|
...(cert !== undefined && { cert }),
|
|
72
73
|
...(key !== undefined && { key }),
|
|
73
74
|
};
|
|
74
|
-
return await Deno.connectTls(connectOpts);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/** createConn is used by the base client class */
|
|
78
|
-
protected override createConn(): Promise<SockConn> {
|
|
79
|
-
const { protocol, hostname, port: portStr } = this.connectUrl;
|
|
80
|
-
const port = Number(portStr);
|
|
81
|
-
const caCerts = this.caCerts;
|
|
82
|
-
const cert = this.cert;
|
|
83
|
-
const key = this.key;
|
|
84
|
-
|
|
85
|
-
if (protocol === "mqtts:") {
|
|
86
|
-
return this.connectMQTTS(hostname, port, caCerts, cert, key);
|
|
87
|
-
}
|
|
88
|
-
if (protocol === "mqtt:") {
|
|
89
|
-
return this.connectMQTT(hostname, port);
|
|
90
|
-
}
|
|
91
|
-
throw new Error(`Unsupported protocol: ${protocol}`);
|
|
75
|
+
return denoConnToNext(await Deno.connectTls(connectOpts));
|
|
92
76
|
}
|
|
93
77
|
}
|
package/deno/tcpServer.test.ts
CHANGED
|
@@ -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 {
|
|
6
|
-
import type { ProtocolLevel
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
await
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|