@seriousme/opifex 2.3.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/client/client.ts +5 -0
- package/connNext/baseConnNext.ts +123 -0
- package/deno/wsServer.ts +11 -3
- package/dev_utils/WebStreamEndpoint.ts +5 -0
- package/dev_utils/asyncStreamEndpoint.ts +5 -0
- package/dev_utils/asyncStreamPair.ts +4 -9
- package/dev_utils/asyncStreamPairToNext.ts +45 -0
- package/dev_utils/asyncStreamToNext.ts +30 -137
- package/dev_utils/connNextPair.test.ts +2 -2
- package/dev_utils/mockConn.ts +37 -93
- package/dev_utils/mockServer.ts +1 -1
- package/dev_utils/webStreamPair.ts +3 -7
- package/dev_utils/webStreamPairToNext.ts +48 -0
- package/dev_utils/webStreamToNext.ts +28 -161
- package/dist/client/client.d.ts +4 -0
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +3 -0
- package/dist/client/client.js.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/node/nodeSocketToNext.d.ts +10 -13
- package/dist/node/nodeSocketToNext.d.ts.map +1 -1
- package/dist/node/nodeSocketToNext.js +75 -131
- package/dist/node/nodeSocketToNext.js.map +1 -1
- package/dist/web/webSocketToNext.d.ts +18 -1
- package/dist/web/webSocketToNext.d.ts.map +1 -1
- package/dist/web/webSocketToNext.js +92 -127
- package/dist/web/webSocketToNext.js.map +1 -1
- package/dist/web/wsClient.d.ts.map +1 -1
- package/dist/web/wsClient.js +4 -3
- package/dist/web/wsClient.js.map +1 -1
- package/node/nodeSocketToNext.ts +85 -148
- package/package.json +2 -2
- package/web/webSocketToNext.ts +108 -145
- package/web/wsClient.ts +10 -3
- package/dev_utils/webStreamPair.test.ts +0 -171
package/client/client.ts
CHANGED
|
@@ -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
|
|
|
@@ -310,6 +314,7 @@ export class Client {
|
|
|
310
314
|
this.caCerts = params?.caCerts;
|
|
311
315
|
this.cert = params?.cert;
|
|
312
316
|
this.key = params?.key;
|
|
317
|
+
this.maxBufferSize = params?.maxBufferSize;
|
|
313
318
|
const options = Object.assign(
|
|
314
319
|
{
|
|
315
320
|
keepAlive: this.keepAlive,
|
|
@@ -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
|
+
}
|
package/deno/wsServer.ts
CHANGED
|
@@ -23,8 +23,9 @@ async function handleWsClient(
|
|
|
23
23
|
socket: WebSocket,
|
|
24
24
|
mqttServer: MqttServer,
|
|
25
25
|
remoteAddr: NetAddr,
|
|
26
|
+
maxBufferSize: number | undefined,
|
|
26
27
|
) {
|
|
27
|
-
const conn = await webSocketToNext(socket, remoteAddr);
|
|
28
|
+
const conn = await webSocketToNext(socket, remoteAddr, maxBufferSize);
|
|
28
29
|
mqttServer.serve(conn);
|
|
29
30
|
logger.debug("serving mqtt");
|
|
30
31
|
}
|
|
@@ -33,6 +34,9 @@ export function mqttOverWS(
|
|
|
33
34
|
mqttServer: MqttServer,
|
|
34
35
|
pathName: string,
|
|
35
36
|
next: RequestHandler,
|
|
37
|
+
opts?: {
|
|
38
|
+
maxBufferSize?: number | undefined;
|
|
39
|
+
},
|
|
36
40
|
): RequestHandler {
|
|
37
41
|
const wsRequestHandler: RequestHandler = (req, info) => {
|
|
38
42
|
const url = new URL(req.url);
|
|
@@ -45,7 +49,7 @@ export function mqttOverWS(
|
|
|
45
49
|
const { socket, response } = Deno.upgradeWebSocket(req);
|
|
46
50
|
logger.debug("upgraded to websocket");
|
|
47
51
|
// do not await on purpose
|
|
48
|
-
handleWsClient(socket, mqttServer, remoteAddr);
|
|
52
|
+
handleWsClient(socket, mqttServer, remoteAddr, opts?.maxBufferSize);
|
|
49
53
|
return response;
|
|
50
54
|
}
|
|
51
55
|
return next(req, info);
|
|
@@ -65,6 +69,7 @@ export class WsServer {
|
|
|
65
69
|
hostname?: string;
|
|
66
70
|
};
|
|
67
71
|
private pathName: string;
|
|
72
|
+
private maxBufferSize: number | undefined;
|
|
68
73
|
|
|
69
74
|
/**
|
|
70
75
|
* Create a new WebSocket server
|
|
@@ -74,12 +79,14 @@ export class WsServer {
|
|
|
74
79
|
port: number;
|
|
75
80
|
hostname?: string;
|
|
76
81
|
pathName?: string;
|
|
82
|
+
maxBufferSize?: number;
|
|
77
83
|
},
|
|
78
84
|
mqttOptions: MqttServerOptions | MqttServer,
|
|
79
85
|
) {
|
|
80
|
-
const { pathName, ...listenOptions } = serverOptions;
|
|
86
|
+
const { pathName, maxBufferSize, ...listenOptions } = serverOptions;
|
|
81
87
|
this.listenOptions = listenOptions;
|
|
82
88
|
this.pathName = pathName ?? "/mqtt";
|
|
89
|
+
this.maxBufferSize = maxBufferSize;
|
|
83
90
|
if (mqttOptions instanceof MqttServer) {
|
|
84
91
|
this.mqttServer = mqttOptions;
|
|
85
92
|
} else {
|
|
@@ -95,6 +102,7 @@ export class WsServer {
|
|
|
95
102
|
this.mqttServer,
|
|
96
103
|
this.pathName,
|
|
97
104
|
pageNotFound,
|
|
105
|
+
{ maxBufferSize: this.maxBufferSize },
|
|
98
106
|
);
|
|
99
107
|
this.server = Deno.serve(this.listenOptions, requestHandler);
|
|
100
108
|
await this.server.finished;
|
|
@@ -2,14 +2,9 @@
|
|
|
2
2
|
* Duplex AsyncStream
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
readable: AsyncIterable<Uint8Array>;
|
|
7
|
-
write: (chunk: Uint8Array) => Promise<void>;
|
|
8
|
-
close: () => void;
|
|
9
|
-
}
|
|
10
|
-
|
|
5
|
+
import type { AsyncStreamEndpoint } from "./asyncStreamEndpoint.ts";
|
|
11
6
|
/**
|
|
12
|
-
*
|
|
7
|
+
* Internal push/pull queue to support AsyncIterable iteration.
|
|
13
8
|
*/
|
|
14
9
|
class AsyncQueue<T> implements AsyncIterable<T> {
|
|
15
10
|
private queue: T[] = [];
|
|
@@ -75,8 +70,8 @@ class AsyncQueue<T> implements AsyncIterable<T> {
|
|
|
75
70
|
}
|
|
76
71
|
|
|
77
72
|
export function createAsyncStreamPair(): {
|
|
78
|
-
input:
|
|
79
|
-
output:
|
|
73
|
+
input: AsyncStreamEndpoint;
|
|
74
|
+
output: AsyncStreamEndpoint;
|
|
80
75
|
} {
|
|
81
76
|
const queueA = new AsyncQueue<Uint8Array>();
|
|
82
77
|
const queueB = new AsyncQueue<Uint8Array>();
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ConnAddr, ConnNext } from "../connNext/connNext.ts";
|
|
2
|
+
import { createAsyncStreamPair } from "./asyncStreamPair.ts";
|
|
3
|
+
import { asyncStreamToNext } from "./asyncStreamToNext.ts";
|
|
4
|
+
|
|
5
|
+
export function createConnNextPair(): {
|
|
6
|
+
input: ConnNext;
|
|
7
|
+
output: ConnNext;
|
|
8
|
+
} {
|
|
9
|
+
const { input: asyncInput, output: asyncOutput } = createAsyncStreamPair();
|
|
10
|
+
|
|
11
|
+
const mockAddr: ConnAddr = {
|
|
12
|
+
transport: "tcp",
|
|
13
|
+
hostname: "127.0.0.1",
|
|
14
|
+
port: 1883,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const inputWrapper = asyncStreamToNext(asyncInput);
|
|
18
|
+
const outputWrapper = asyncStreamToNext(asyncOutput);
|
|
19
|
+
|
|
20
|
+
const input: ConnNext = {
|
|
21
|
+
remoteAddr: mockAddr,
|
|
22
|
+
localAddr: mockAddr,
|
|
23
|
+
read: (length: number | undefined) => inputWrapper.read(length),
|
|
24
|
+
write: (data: Uint8Array<ArrayBufferLike>) => inputWrapper.write(data),
|
|
25
|
+
[Symbol.asyncIterator]: () => inputWrapper[Symbol.asyncIterator](),
|
|
26
|
+
close: () => {
|
|
27
|
+
inputWrapper.close();
|
|
28
|
+
outputWrapper.close();
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const output: ConnNext = {
|
|
33
|
+
remoteAddr: mockAddr,
|
|
34
|
+
localAddr: mockAddr,
|
|
35
|
+
read: (length: number | undefined) => outputWrapper.read(length),
|
|
36
|
+
write: (data: Uint8Array<ArrayBufferLike>) => outputWrapper.write(data),
|
|
37
|
+
[Symbol.asyncIterator]: () => outputWrapper[Symbol.asyncIterator](),
|
|
38
|
+
close: () => {
|
|
39
|
+
outputWrapper.close();
|
|
40
|
+
inputWrapper.close();
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
return { input, output };
|
|
45
|
+
}
|
|
@@ -1,171 +1,64 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import type { AsyncStreamPairEndpoint } from "./asyncStreamPair.ts";
|
|
1
|
+
import { BaseConnNext } from "../connNext/baseConnNext.ts";
|
|
2
|
+
import type { AsyncStreamEndpoint } from "./asyncStreamEndpoint.ts";
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
private endPoint: AsyncStreamPairEndpoint;
|
|
9
|
-
private leftover: Uint8Array = new Uint8Array(0);
|
|
4
|
+
export class AsyncStreamConnNext extends BaseConnNext {
|
|
5
|
+
remoteAddr = undefined;
|
|
6
|
+
localAddr = undefined;
|
|
10
7
|
private reader: AsyncIterator<Uint8Array>;
|
|
8
|
+
private endPoint: AsyncStreamEndpoint;
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
constructor(endPoint: AsyncStreamEndpoint) {
|
|
11
|
+
super();
|
|
14
12
|
this.endPoint = endPoint;
|
|
15
|
-
|
|
13
|
+
// Haal de async iterator op uit de readable stream
|
|
16
14
|
this.reader = endPoint.readable[Symbol.asyncIterator]();
|
|
17
15
|
}
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
|
-
*
|
|
21
|
-
*/ async write(chunk: Uint8Array): Promise<void> {
|
|
22
|
-
if (this.closed) {
|
|
23
|
-
return Promise.reject(new Error("Pipe closed"));
|
|
24
|
-
}
|
|
25
|
-
try {
|
|
26
|
-
await this.endPoint.write(chunk);
|
|
27
|
-
return;
|
|
28
|
-
} catch (err) {
|
|
29
|
-
this.closed = true;
|
|
30
|
-
throw err;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Reads data from the stream.
|
|
36
|
-
* If `length` is provided, waits until exact number of bytes are buffered.
|
|
18
|
+
* Haalt op exact moment van aanvragen één nieuwe chunk op via de iterator.
|
|
37
19
|
*/
|
|
38
|
-
async
|
|
39
|
-
if (this.closed
|
|
20
|
+
protected async pullNextChunk(): Promise<Uint8Array | null> {
|
|
21
|
+
if (this.closed) {
|
|
40
22
|
return null;
|
|
41
23
|
}
|
|
42
24
|
|
|
43
|
-
|
|
44
|
-
return new Uint8Array(0);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Helper to fetch the next chunk from the underlying iterable
|
|
48
|
-
const fetchNextChunk = async (): Promise<Uint8Array | null> => {
|
|
25
|
+
try {
|
|
49
26
|
const { value, done } = await this.reader.next();
|
|
50
|
-
if (done) {
|
|
27
|
+
if (done || !value) {
|
|
51
28
|
this.closed = true;
|
|
52
29
|
return null;
|
|
53
30
|
}
|
|
54
31
|
return value;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (length === undefined) {
|
|
59
|
-
if (this.leftover.length > 0) {
|
|
60
|
-
const result = this.leftover;
|
|
61
|
-
this.leftover = new Uint8Array(0);
|
|
62
|
-
return result;
|
|
63
|
-
}
|
|
64
|
-
return await fetchNextChunk();
|
|
32
|
+
} catch {
|
|
33
|
+
this.closed = true;
|
|
34
|
+
return null;
|
|
65
35
|
}
|
|
36
|
+
}
|
|
66
37
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let bytesAccumulated = 0;
|
|
71
|
-
|
|
72
|
-
// Use leftover data first if available
|
|
73
|
-
if (this.leftover.length > 0) {
|
|
74
|
-
const take = Math.min(this.leftover.length, bytesRequested);
|
|
75
|
-
result.set(this.leftover.subarray(0, take), bytesAccumulated);
|
|
76
|
-
bytesAccumulated += take;
|
|
77
|
-
this.leftover = this.leftover.subarray(take);
|
|
38
|
+
async write(chunk: Uint8Array): Promise<void> {
|
|
39
|
+
if (this.closed) {
|
|
40
|
+
return Promise.reject(new Error("Pipe closed"));
|
|
78
41
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (value === null) {
|
|
85
|
-
// Stream ended before collecting required bytes
|
|
86
|
-
if (bytesAccumulated === 0) return null;
|
|
87
|
-
return result.subarray(0, bytesAccumulated);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const needed = bytesRequested - bytesAccumulated;
|
|
91
|
-
|
|
92
|
-
if (value.length <= needed) {
|
|
93
|
-
result.set(value, bytesAccumulated);
|
|
94
|
-
bytesAccumulated += value.length;
|
|
95
|
-
} else {
|
|
96
|
-
result.set(value.subarray(0, needed), bytesAccumulated);
|
|
97
|
-
bytesAccumulated += needed;
|
|
98
|
-
this.leftover = value.subarray(needed);
|
|
99
|
-
}
|
|
42
|
+
try {
|
|
43
|
+
await this.endPoint.write(chunk);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
this.closed = true;
|
|
46
|
+
throw err;
|
|
100
47
|
}
|
|
101
|
-
|
|
102
|
-
return result;
|
|
103
48
|
}
|
|
104
49
|
|
|
105
|
-
/**
|
|
106
|
-
* Closes the reader, writer, and underlying AsyncStream pair endpoint.
|
|
107
|
-
*/
|
|
108
50
|
close(): void {
|
|
109
51
|
if (!this.closed) {
|
|
110
52
|
this.closed = true;
|
|
111
53
|
try {
|
|
112
54
|
this.endPoint.close();
|
|
113
|
-
} catch (_err) {
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
async *[Symbol.asyncIterator]() {
|
|
117
|
-
while (true) {
|
|
118
|
-
const chunk = await this.read();
|
|
119
|
-
if (chunk === null) {
|
|
120
|
-
break;
|
|
55
|
+
} catch (_err) {
|
|
56
|
+
/* swallow */
|
|
121
57
|
}
|
|
122
|
-
yield chunk;
|
|
123
58
|
}
|
|
124
59
|
}
|
|
125
60
|
}
|
|
126
61
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
* Offers the exact same API as `createConnNextPair`.
|
|
130
|
-
*/
|
|
131
|
-
export function createConnNextPair(): {
|
|
132
|
-
input: ConnNext;
|
|
133
|
-
output: ConnNext;
|
|
134
|
-
} {
|
|
135
|
-
const { input: asyncInput, output: asyncOutput } = createAsyncStreamPair();
|
|
136
|
-
|
|
137
|
-
const mockAddr: ConnAddr = {
|
|
138
|
-
transport: "tcp",
|
|
139
|
-
hostname: "127.0.0.1",
|
|
140
|
-
port: 1883,
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const inputWrapper = new AsyncStreamConnNextWrapper(asyncInput);
|
|
144
|
-
const outputWrapper = new AsyncStreamConnNextWrapper(asyncOutput);
|
|
145
|
-
|
|
146
|
-
const input: ConnNext = {
|
|
147
|
-
remoteAddr: mockAddr,
|
|
148
|
-
localAddr: mockAddr,
|
|
149
|
-
read: (length) => inputWrapper.read(length),
|
|
150
|
-
write: (data) => inputWrapper.write(data),
|
|
151
|
-
[Symbol.asyncIterator]: () => inputWrapper[Symbol.asyncIterator](),
|
|
152
|
-
close: () => {
|
|
153
|
-
inputWrapper.close();
|
|
154
|
-
outputWrapper.close();
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
const output: ConnNext = {
|
|
159
|
-
remoteAddr: mockAddr,
|
|
160
|
-
localAddr: mockAddr,
|
|
161
|
-
read: (length) => outputWrapper.read(length),
|
|
162
|
-
write: (data) => outputWrapper.write(data),
|
|
163
|
-
[Symbol.asyncIterator]: () => outputWrapper[Symbol.asyncIterator](),
|
|
164
|
-
close: () => {
|
|
165
|
-
outputWrapper.close();
|
|
166
|
-
inputWrapper.close();
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
return { input, output };
|
|
62
|
+
export function asyncStreamToNext(endPoint: AsyncStreamEndpoint) {
|
|
63
|
+
return new AsyncStreamConnNext(endPoint);
|
|
171
64
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { test } from "node:test";
|
|
2
2
|
import assert from "node:assert";
|
|
3
|
-
import { createConnNextPair as asyncPair } from "./
|
|
4
|
-
import { createConnNextPair as webPair } from "./
|
|
3
|
+
import { createConnNextPair as asyncPair } from "./asyncStreamPairToNext.ts";
|
|
4
|
+
import { createConnNextPair as webPair } from "./webStreamPairToNext.ts";
|
|
5
5
|
|
|
6
6
|
const pairs = {
|
|
7
7
|
asyncStream: asyncPair,
|
package/dev_utils/mockConn.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ConnAddr, ConnNext } from "../connNext/connNext.ts";
|
|
2
|
+
import { createAsyncStreamPair } from "./asyncStreamPair.ts";
|
|
3
|
+
import { AsyncStreamConnNext } from "./asyncStreamToNext.ts";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Creates an in-memory mock ConnNext object for unit testing.
|
|
@@ -12,21 +14,8 @@ export function createMockConn(
|
|
|
12
14
|
writerBuf: Uint8Array,
|
|
13
15
|
close = () => {},
|
|
14
16
|
): ConnNext {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
let writerPos = 0;
|
|
18
|
-
let isClosed = false;
|
|
19
|
-
|
|
20
|
-
// Helper function to calculate total remaining bytes across readerBuffs
|
|
21
|
-
const getRemainingBytes = (): number => {
|
|
22
|
-
let total = 0;
|
|
23
|
-
for (let i = readIndex; i < readerBuffs.length; i++) {
|
|
24
|
-
total += i === readIndex
|
|
25
|
-
? readerBuffs[i].byteLength - readOffset
|
|
26
|
-
: readerBuffs[i].byteLength;
|
|
27
|
-
}
|
|
28
|
-
return total;
|
|
29
|
-
};
|
|
17
|
+
// Maak een async stream pair aan
|
|
18
|
+
const { input, output } = createAsyncStreamPair();
|
|
30
19
|
|
|
31
20
|
const mockAddr: ConnAddr = {
|
|
32
21
|
transport: "tcp",
|
|
@@ -34,89 +23,44 @@ export function createMockConn(
|
|
|
34
23
|
port: 1883,
|
|
35
24
|
};
|
|
36
25
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
* Read exactly 'length' bytes from the provided readerBuffs
|
|
43
|
-
*/
|
|
44
|
-
read(length?: number): Promise<Uint8Array | null> {
|
|
45
|
-
if (isClosed) return Promise.resolve(null);
|
|
46
|
-
|
|
47
|
-
// No specific length requested: return the next available chunk immediately
|
|
48
|
-
if (length === undefined) {
|
|
49
|
-
if (readIndex >= readerBuffs.length) Promise.resolve(null);
|
|
50
|
-
const chunk = readerBuffs[readIndex].subarray(readOffset);
|
|
51
|
-
readIndex++;
|
|
52
|
-
readOffset = 0;
|
|
53
|
-
Promise.resolve(chunk);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
length = length ?? 0;
|
|
57
|
-
|
|
58
|
-
// EOF reached / not enough remaining bytes in the mock buffers
|
|
59
|
-
if (getRemainingBytes() < length) {
|
|
60
|
-
return Promise.resolve(null);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const result = new Uint8Array(length);
|
|
64
|
-
let bytesCopied = 0;
|
|
65
|
-
|
|
66
|
-
while (bytesCopied < length && readIndex < readerBuffs.length) {
|
|
67
|
-
const current = readerBuffs[readIndex];
|
|
68
|
-
const available = current.byteLength - readOffset;
|
|
69
|
-
const needed = length - bytesCopied;
|
|
70
|
-
|
|
71
|
-
if (available <= needed) {
|
|
72
|
-
result.set(current.subarray(readOffset), bytesCopied);
|
|
73
|
-
bytesCopied += available;
|
|
74
|
-
readIndex++;
|
|
75
|
-
readOffset = 0;
|
|
76
|
-
} else {
|
|
77
|
-
result.set(
|
|
78
|
-
current.subarray(readOffset, readOffset + needed),
|
|
79
|
-
bytesCopied,
|
|
80
|
-
);
|
|
81
|
-
bytesCopied += needed;
|
|
82
|
-
readOffset += needed;
|
|
83
|
-
}
|
|
26
|
+
// Push all input directly to the input of the StreamPair
|
|
27
|
+
(async () => {
|
|
28
|
+
try {
|
|
29
|
+
for (const buf of readerBuffs) {
|
|
30
|
+
await input.write(buf);
|
|
84
31
|
}
|
|
32
|
+
input.close();
|
|
33
|
+
} catch {
|
|
34
|
+
// ignore errors
|
|
35
|
+
}
|
|
36
|
+
})();
|
|
85
37
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (isClosed) {
|
|
94
|
-
throw new Error("Connection closed");
|
|
38
|
+
// Capture written data and store it in the write buffer
|
|
39
|
+
(async () => {
|
|
40
|
+
try {
|
|
41
|
+
let writerPos = 0;
|
|
42
|
+
for await (const chunk of input.readable) {
|
|
43
|
+
writerBuf.set(chunk, writerPos);
|
|
44
|
+
writerPos += chunk.byteLength;
|
|
95
45
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
46
|
+
} catch {
|
|
47
|
+
// ignore errors
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
100
50
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
*/
|
|
104
|
-
close(): void {
|
|
105
|
-
if (!isClosed) {
|
|
106
|
-
isClosed = true;
|
|
107
|
-
close();
|
|
108
|
-
}
|
|
109
|
-
},
|
|
51
|
+
// Return a ConnNext wrapper connected to the output side
|
|
52
|
+
const wrapper = new AsyncStreamConnNext(output);
|
|
110
53
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
54
|
+
return {
|
|
55
|
+
remoteAddr: mockAddr,
|
|
56
|
+
localAddr: mockAddr,
|
|
57
|
+
read: (length) => wrapper.read(length),
|
|
58
|
+
write: (data) => wrapper.write(data),
|
|
59
|
+
[Symbol.asyncIterator]: () => wrapper[Symbol.asyncIterator](),
|
|
60
|
+
close: () => {
|
|
61
|
+
wrapper.close();
|
|
62
|
+
output.close();
|
|
63
|
+
close();
|
|
120
64
|
},
|
|
121
65
|
};
|
|
122
66
|
}
|
package/dev_utils/mockServer.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createConnNextPair } from "./
|
|
1
|
+
import { createConnNextPair } from "./asyncStreamPairToNext.ts";
|
|
2
2
|
import { MqttConn } from "../mqttConn/mqttConn.ts";
|
|
3
3
|
import { Context, MqttServer } from "../server/mod.ts";
|
|
4
4
|
import { handlers } from "./test-handlers.ts";
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
readable: ReadableStream<Uint8Array>;
|
|
3
|
-
writable: WritableStream<Uint8Array>;
|
|
4
|
-
close: () => void;
|
|
5
|
-
}
|
|
1
|
+
import type { WebStreamEndpoint } from "./WebStreamEndpoint.ts";
|
|
6
2
|
|
|
7
3
|
export function createWebStreamPair(): {
|
|
8
|
-
input:
|
|
9
|
-
output:
|
|
4
|
+
input: WebStreamEndpoint;
|
|
5
|
+
output: WebStreamEndpoint;
|
|
10
6
|
} {
|
|
11
7
|
let aController: ReadableStreamDefaultController | null = null;
|
|
12
8
|
let bController: ReadableStreamDefaultController | null = null;
|