@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
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";
|
|
@@ -51,6 +51,8 @@ export type ConnectParameters = {
|
|
|
51
51
|
key?: string | undefined;
|
|
52
52
|
/** number of retries before stopping connection attempts */
|
|
53
53
|
numberOfRetries?: number | undefined;
|
|
54
|
+
/** maximum size of receive buffer, only used by webSockets for now */
|
|
55
|
+
maxBufferSize?: number | undefined;
|
|
54
56
|
/** connect options */
|
|
55
57
|
options?: ConnectOptions | undefined;
|
|
56
58
|
};
|
|
@@ -159,6 +161,8 @@ export class Client {
|
|
|
159
161
|
protected key?: string | undefined;
|
|
160
162
|
/** the MQTT clientId */
|
|
161
163
|
protected clientId: string;
|
|
164
|
+
/** the maximum number of bytes buffered by a ws/wss client */
|
|
165
|
+
protected maxBufferSize: number | undefined;
|
|
162
166
|
private ctx: Context;
|
|
163
167
|
private connectPacket?: ConnectPacket;
|
|
164
168
|
|
|
@@ -169,7 +173,6 @@ export class Client {
|
|
|
169
173
|
const cStore = store ? store : new MemoryStore();
|
|
170
174
|
this.ctx = new Context(cStore, this);
|
|
171
175
|
this.clientId = generateClientId(this.clientIdPrefix);
|
|
172
|
-
this.numberOfRetries = DEFAULT_RETRIES;
|
|
173
176
|
}
|
|
174
177
|
|
|
175
178
|
/**
|
|
@@ -188,14 +191,58 @@ export class Client {
|
|
|
188
191
|
return this.connectUrl;
|
|
189
192
|
}
|
|
190
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Placeholder for MQTT, override in subclass
|
|
196
|
+
* @param _hostname
|
|
197
|
+
* @param _port
|
|
198
|
+
*/
|
|
199
|
+
protected connectMQTT(
|
|
200
|
+
_hostname: string,
|
|
201
|
+
_port: number,
|
|
202
|
+
): Promise<ConnNext> {
|
|
203
|
+
throw new Error(`Unsupported protocol: mqtt`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Connect using MQTT over TLS
|
|
208
|
+
* @param hostname the name of the host to connect to
|
|
209
|
+
* @param port the port number
|
|
210
|
+
* @param caCerts the CA certificates to trust
|
|
211
|
+
* @param cert the clients certificate
|
|
212
|
+
* @param key the clients private key
|
|
213
|
+
* @returns a ConnNext object
|
|
214
|
+
*/
|
|
215
|
+
protected connectMQTTS(
|
|
216
|
+
_hostname: string,
|
|
217
|
+
_port: number,
|
|
218
|
+
_caCerts?: string[],
|
|
219
|
+
_cert?: string,
|
|
220
|
+
_key?: string,
|
|
221
|
+
): Promise<ConnNext> {
|
|
222
|
+
throw new Error(`Unsupported protocol: mqtts`);
|
|
223
|
+
}
|
|
224
|
+
|
|
191
225
|
/**
|
|
192
226
|
* Creates a new connection to the MQTT broker
|
|
193
|
-
*
|
|
227
|
+
* Override this in a subclass if you need other protocols
|
|
228
|
+
* @returns Promise resolving to a ConnNext connection
|
|
194
229
|
*/
|
|
195
|
-
protected createConn(): Promise<
|
|
230
|
+
protected createConn(): Promise<ConnNext> {
|
|
196
231
|
// if you need to support alternative connection types just
|
|
197
232
|
// overload this method in your subclass
|
|
198
|
-
|
|
233
|
+
const { protocol, hostname, port: portStr } = this.connectUrl;
|
|
234
|
+
const port = Number(portStr);
|
|
235
|
+
const caCerts = this.caCerts;
|
|
236
|
+
const cert = this.cert;
|
|
237
|
+
const key = this.key;
|
|
238
|
+
|
|
239
|
+
if (protocol === "mqtts:") {
|
|
240
|
+
return this.connectMQTTS(hostname, port, caCerts, cert, key);
|
|
241
|
+
}
|
|
242
|
+
if (protocol === "mqtt:") {
|
|
243
|
+
return this.connectMQTT(hostname, port);
|
|
244
|
+
}
|
|
245
|
+
throw new Error(`Unsupported protocol: ${protocol}`);
|
|
199
246
|
}
|
|
200
247
|
|
|
201
248
|
/**
|
|
@@ -233,7 +280,14 @@ export class Client {
|
|
|
233
280
|
logger.debug({ lastMessage });
|
|
234
281
|
}
|
|
235
282
|
|
|
236
|
-
|
|
283
|
+
logger.debug("tryConnect", {
|
|
284
|
+
tryConnect,
|
|
285
|
+
isReconnect,
|
|
286
|
+
attempt,
|
|
287
|
+
retries: this.numberOfRetries,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
if (tryConnect && (isReconnect || (attempt < this.numberOfRetries))) {
|
|
237
291
|
await backOffSleep(true, attempt);
|
|
238
292
|
if (!isReconnect) {
|
|
239
293
|
attempt++;
|
|
@@ -256,10 +310,11 @@ export class Client {
|
|
|
256
310
|
*/
|
|
257
311
|
connect(params: ConnectParameters = {}): Promise<AuthenticationResult> {
|
|
258
312
|
this.connectUrl = params?.url || this.connectUrl;
|
|
259
|
-
this.numberOfRetries = params.numberOfRetries
|
|
313
|
+
this.numberOfRetries = params.numberOfRetries ?? this.numberOfRetries;
|
|
260
314
|
this.caCerts = params?.caCerts;
|
|
261
315
|
this.cert = params?.cert;
|
|
262
316
|
this.key = params?.key;
|
|
317
|
+
this.maxBufferSize = params?.maxBufferSize;
|
|
263
318
|
const options = Object.assign(
|
|
264
319
|
{
|
|
265
320
|
keepAlive: this.keepAlive,
|
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,123 @@
|
|
|
1
|
+
import type { ConnAddr, ConnNext } from "../connNext/connNext.ts";
|
|
2
|
+
|
|
3
|
+
export abstract class BaseConnNext implements ConnNext {
|
|
4
|
+
abstract remoteAddr?: ConnAddr;
|
|
5
|
+
abstract localAddr?: ConnAddr;
|
|
6
|
+
|
|
7
|
+
protected chunks: Uint8Array[] = [];
|
|
8
|
+
protected queueOffset = 0;
|
|
9
|
+
protected closed = false;
|
|
10
|
+
private isPulling = false;
|
|
11
|
+
|
|
12
|
+
abstract write(data: Uint8Array): Promise<void>;
|
|
13
|
+
abstract close(): void;
|
|
14
|
+
|
|
15
|
+
/** Specifiek per bron: haal pas daadwerkelijk 1 chunk op als de basisklasse erom vraagt */
|
|
16
|
+
protected abstract pullNextChunk(): Promise<Uint8Array | null>;
|
|
17
|
+
|
|
18
|
+
isClosed(): boolean {
|
|
19
|
+
return this.closed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
protected getBufferedBytes(): number {
|
|
23
|
+
let total = 0;
|
|
24
|
+
for (let i = 0; i < this.chunks.length; i++) {
|
|
25
|
+
total += i === 0
|
|
26
|
+
? this.chunks[i]!.byteLength - this.queueOffset
|
|
27
|
+
: this.chunks[i]!.byteLength;
|
|
28
|
+
}
|
|
29
|
+
return total;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Zorgt ervoor dat we data ophalen op exact het moment dat het nodig is */
|
|
33
|
+
private async ensureData(length?: number): Promise<void> {
|
|
34
|
+
while (
|
|
35
|
+
!this.closed &&
|
|
36
|
+
(this.getBufferedBytes() === 0 ||
|
|
37
|
+
(length !== undefined && this.getBufferedBytes() < length))
|
|
38
|
+
) {
|
|
39
|
+
if (this.isPulling) {
|
|
40
|
+
// Voorkom gelijktijdige pulls als er al eentje loopt
|
|
41
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
this.isPulling = true;
|
|
46
|
+
try {
|
|
47
|
+
const chunk = await this.pullNextChunk();
|
|
48
|
+
if (chunk === null || chunk.byteLength === 0) {
|
|
49
|
+
this.closed = true;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
this.chunks.push(chunk);
|
|
53
|
+
} catch {
|
|
54
|
+
this.closed = true;
|
|
55
|
+
} finally {
|
|
56
|
+
this.isPulling = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async read(length?: number): Promise<Uint8Array | null> {
|
|
62
|
+
if (this.closed && this.getBufferedBytes() === 0) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (length === 0) {
|
|
67
|
+
return new Uint8Array(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Pull exact zolang tot we genoeg data hebben voor deze read()
|
|
71
|
+
await this.ensureData(length);
|
|
72
|
+
|
|
73
|
+
if (this.getBufferedBytes() === 0) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Onbegrensd lezen (geen lengte opgegeven)
|
|
78
|
+
if (length === undefined) {
|
|
79
|
+
const firstChunk = this.chunks.shift()!;
|
|
80
|
+
const result = firstChunk.subarray(this.queueOffset);
|
|
81
|
+
this.queueOffset = 0;
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Exacte lengte uitlezen uit de buffer
|
|
86
|
+
const bytesToTake = Math.min(length, this.getBufferedBytes());
|
|
87
|
+
const result = new Uint8Array(bytesToTake);
|
|
88
|
+
let bytesCopied = 0;
|
|
89
|
+
|
|
90
|
+
while (bytesCopied < bytesToTake && this.chunks.length > 0) {
|
|
91
|
+
const currentChunk = this.chunks[0]!;
|
|
92
|
+
const bytesNeeded = bytesToTake - bytesCopied;
|
|
93
|
+
const bytesAvailable = currentChunk.byteLength - this.queueOffset;
|
|
94
|
+
|
|
95
|
+
if (bytesAvailable <= bytesNeeded) {
|
|
96
|
+
result.set(currentChunk.subarray(this.queueOffset), bytesCopied);
|
|
97
|
+
bytesCopied += bytesAvailable;
|
|
98
|
+
this.chunks.shift();
|
|
99
|
+
this.queueOffset = 0;
|
|
100
|
+
} else {
|
|
101
|
+
result.set(
|
|
102
|
+
currentChunk.subarray(
|
|
103
|
+
this.queueOffset,
|
|
104
|
+
this.queueOffset + bytesNeeded,
|
|
105
|
+
),
|
|
106
|
+
bytesCopied,
|
|
107
|
+
);
|
|
108
|
+
bytesCopied += bytesNeeded;
|
|
109
|
+
this.queueOffset += bytesNeeded;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return bytesCopied === 0 ? null : result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async *[Symbol.asyncIterator]() {
|
|
117
|
+
while (true) {
|
|
118
|
+
const chunk = await this.read();
|
|
119
|
+
if (chunk === null) break;
|
|
120
|
+
yield chunk;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -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
|
}
|