@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.
Files changed (38) hide show
  1. package/client/client.ts +5 -0
  2. package/connNext/baseConnNext.ts +123 -0
  3. package/deno/wsServer.ts +11 -3
  4. package/dev_utils/WebStreamEndpoint.ts +5 -0
  5. package/dev_utils/asyncStreamEndpoint.ts +5 -0
  6. package/dev_utils/asyncStreamPair.ts +4 -9
  7. package/dev_utils/asyncStreamPairToNext.ts +45 -0
  8. package/dev_utils/asyncStreamToNext.ts +30 -137
  9. package/dev_utils/connNextPair.test.ts +2 -2
  10. package/dev_utils/mockConn.ts +37 -93
  11. package/dev_utils/mockServer.ts +1 -1
  12. package/dev_utils/webStreamPair.ts +3 -7
  13. package/dev_utils/webStreamPairToNext.ts +48 -0
  14. package/dev_utils/webStreamToNext.ts +28 -161
  15. package/dist/client/client.d.ts +4 -0
  16. package/dist/client/client.d.ts.map +1 -1
  17. package/dist/client/client.js +3 -0
  18. package/dist/client/client.js.map +1 -1
  19. package/dist/connNext/baseConnNext.d.ts +20 -0
  20. package/dist/connNext/baseConnNext.d.ts.map +1 -0
  21. package/dist/connNext/baseConnNext.js +95 -0
  22. package/dist/connNext/baseConnNext.js.map +1 -0
  23. package/dist/node/nodeSocketToNext.d.ts +10 -13
  24. package/dist/node/nodeSocketToNext.d.ts.map +1 -1
  25. package/dist/node/nodeSocketToNext.js +75 -131
  26. package/dist/node/nodeSocketToNext.js.map +1 -1
  27. package/dist/web/webSocketToNext.d.ts +18 -1
  28. package/dist/web/webSocketToNext.d.ts.map +1 -1
  29. package/dist/web/webSocketToNext.js +92 -127
  30. package/dist/web/webSocketToNext.js.map +1 -1
  31. package/dist/web/wsClient.d.ts.map +1 -1
  32. package/dist/web/wsClient.js +4 -3
  33. package/dist/web/wsClient.js.map +1 -1
  34. package/node/nodeSocketToNext.ts +85 -148
  35. package/package.json +2 -2
  36. package/web/webSocketToNext.ts +108 -145
  37. package/web/wsClient.ts +10 -3
  38. 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;
@@ -0,0 +1,5 @@
1
+ export interface WebStreamEndpoint {
2
+ readable: ReadableStream<Uint8Array>;
3
+ writable: WritableStream<Uint8Array>;
4
+ close: () => void;
5
+ }
@@ -0,0 +1,5 @@
1
+ export interface AsyncStreamEndpoint {
2
+ readable: AsyncIterable<Uint8Array>;
3
+ write: (chunk: Uint8Array) => Promise<void>;
4
+ close: () => void;
5
+ }
@@ -2,14 +2,9 @@
2
2
  * Duplex AsyncStream
3
3
  */
4
4
 
5
- export interface AsyncStreamPairEndpoint {
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
- * Interne push/pull queue to support AsyncIterable iteration.
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: AsyncStreamPairEndpoint;
79
- output: AsyncStreamPairEndpoint;
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 type { ConnAddr, ConnNext } from "../connNext/connNext.ts";
2
- import { createAsyncStreamPair } from "./asyncStreamPair.ts";
3
- import type { AsyncStreamPairEndpoint } from "./asyncStreamPair.ts";
1
+ import { BaseConnNext } from "../connNext/baseConnNext.ts";
2
+ import type { AsyncStreamEndpoint } from "./asyncStreamEndpoint.ts";
4
3
 
5
- /** wrapper class */
6
- export class AsyncStreamConnNextWrapper {
7
- closed: boolean;
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
- /** Create a new Endpoint*/
13
- constructor(endPoint: AsyncStreamPairEndpoint) {
10
+ constructor(endPoint: AsyncStreamEndpoint) {
11
+ super();
14
12
  this.endPoint = endPoint;
15
- this.closed = false;
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
- * Writes data chunk to the underlying AsyncStream
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 read(length?: number): Promise<Uint8Array | null> {
39
- if (this.closed || (length !== undefined && length < 0)) {
20
+ protected async pullNextChunk(): Promise<Uint8Array | null> {
21
+ if (this.closed) {
40
22
  return null;
41
23
  }
42
24
 
43
- if (length === 0) {
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
- // Unbounded read (length === undefined)
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
- // Exact length read
68
- const bytesRequested = length;
69
- const result = new Uint8Array(bytesRequested);
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
- // Read remaining required bytes
81
- while (bytesAccumulated < bytesRequested) {
82
- const value = await fetchNextChunk();
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) { /* swallow */ }
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
- * Creates a pair of cross-connected ConnNext sockets backed by AsyncStreams.
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 "./asyncStreamToNext.ts";
4
- import { createConnNextPair as webPair } from "./webStreamToNext.ts";
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,
@@ -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
- let readIndex = 0;
16
- let readOffset = 0;
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
- return {
38
- remoteAddr: mockAddr,
39
- localAddr: mockAddr,
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
- return Promise.resolve(result);
87
- },
88
-
89
- /**
90
- * Write data directly at the current offset in writerBuf
91
- */
92
- write(data: Uint8Array): Promise<void> {
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
- writerBuf.set(data, writerPos);
97
- writerPos += data.byteLength;
98
- return Promise.resolve();
99
- },
46
+ } catch {
47
+ // ignore errors
48
+ }
49
+ })();
100
50
 
101
- /**
102
- * Close the mock connection
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
- * Async iterator for streaming test scenarios
113
- */
114
- async *[Symbol.asyncIterator]() {
115
- while (true) {
116
- const chunk = await this.read();
117
- if (chunk === null) break;
118
- yield chunk;
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
  }
@@ -1,4 +1,4 @@
1
- import { createConnNextPair } from "./webStreamToNext.ts";
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
- export interface WebStreamPairEndpoint {
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: WebStreamPairEndpoint;
9
- output: WebStreamPairEndpoint;
4
+ input: WebStreamEndpoint;
5
+ output: WebStreamEndpoint;
10
6
  } {
11
7
  let aController: ReadableStreamDefaultController | null = null;
12
8
  let bController: ReadableStreamDefaultController | null = null;