@seriousme/opifex 2.1.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.
Files changed (147) hide show
  1. package/README.md +7 -6
  2. package/bin/mqtt.ts +1 -1
  3. package/client/client.ts +58 -7
  4. package/client/context.ts +2 -2
  5. package/client/deps.ts +3 -3
  6. package/client/mod.ts +2 -2
  7. package/connNext/connNext.ts +44 -0
  8. package/deno/README.md +1 -1
  9. package/deno/denoConnToNext.ts +104 -0
  10. package/deno/mod.ts +7 -0
  11. package/deno/tcpClient.ts +8 -24
  12. package/deno/tcpServer.test.ts +14 -103
  13. package/deno/tcpServer.ts +14 -6
  14. package/deno/tlsServer.test.ts +14 -58
  15. package/deno/tlsServer.ts +14 -6
  16. package/deno/wsServer.test.ts +15 -61
  17. package/deno/wsServer.ts +77 -35
  18. package/dev_utils/asyncStreamPair.ts +114 -0
  19. package/dev_utils/asyncStreamToNext.ts +171 -0
  20. package/dev_utils/connNextPair.test.ts +179 -0
  21. package/dev_utils/mockConn.ts +115 -29
  22. package/dev_utils/mockServer.ts +3 -4
  23. package/dev_utils/mod.ts +1 -2
  24. package/dev_utils/testClient.ts +64 -0
  25. package/dev_utils/webStreamToNext.ts +187 -0
  26. package/dist/bin/mqtt.js +1 -1
  27. package/dist/bin/mqtt.js.map +1 -1
  28. package/dist/client/client.d.ts +21 -3
  29. package/dist/client/client.d.ts.map +1 -1
  30. package/dist/client/client.js +43 -5
  31. package/dist/client/client.js.map +1 -1
  32. package/dist/client/context.d.ts +2 -2
  33. package/dist/client/context.d.ts.map +1 -1
  34. package/dist/client/deps.d.ts +1 -1
  35. package/dist/client/deps.d.ts.map +1 -1
  36. package/dist/client/mod.d.ts +1 -1
  37. package/dist/client/mod.d.ts.map +1 -1
  38. package/dist/connNext/connNext.d.ts +43 -0
  39. package/dist/connNext/connNext.d.ts.map +1 -0
  40. package/dist/connNext/connNext.js +2 -0
  41. package/dist/connNext/connNext.js.map +1 -0
  42. package/dist/examples/authHandler.test.js +2 -2
  43. package/dist/examples/authHandler.test.js.map +1 -1
  44. package/dist/examples/mqtt+mqtts.test.d.ts +2 -0
  45. package/dist/examples/mqtt+mqtts.test.d.ts.map +1 -0
  46. package/dist/examples/mqtt+mqtts.test.js +104 -0
  47. package/dist/examples/mqtt+mqtts.test.js.map +1 -0
  48. package/dist/examples/mqtt.test.js +2 -2
  49. package/dist/examples/mqtt.test.js.map +1 -1
  50. package/dist/examples/mqtts.test.js +2 -2
  51. package/dist/examples/mqtts.test.js.map +1 -1
  52. package/dist/mqttConn/mqttConn.d.ts +5 -6
  53. package/dist/mqttConn/mqttConn.d.ts.map +1 -1
  54. package/dist/mqttConn/mqttConn.js +2 -3
  55. package/dist/mqttConn/mqttConn.js.map +1 -1
  56. package/dist/mqttPacket/protocolLevels.d.ts +2 -0
  57. package/dist/mqttPacket/protocolLevels.d.ts.map +1 -1
  58. package/dist/node/nodeSocketToNext.d.ts +20 -0
  59. package/dist/node/nodeSocketToNext.d.ts.map +1 -0
  60. package/dist/node/nodeSocketToNext.js +161 -0
  61. package/dist/node/nodeSocketToNext.js.map +1 -0
  62. package/dist/node/tcpClient.d.ts +4 -6
  63. package/dist/node/tcpClient.d.ts.map +1 -1
  64. package/dist/node/tcpClient.js +6 -21
  65. package/dist/node/tcpClient.js.map +1 -1
  66. package/dist/node/tcpServer.d.ts +8 -4
  67. package/dist/node/tcpServer.d.ts.map +1 -1
  68. package/dist/node/tcpServer.js +14 -6
  69. package/dist/node/tcpServer.js.map +1 -1
  70. package/dist/node/tlsServer.d.ts +7 -3
  71. package/dist/node/tlsServer.d.ts.map +1 -1
  72. package/dist/node/tlsServer.js +13 -5
  73. package/dist/node/tlsServer.js.map +1 -1
  74. package/dist/server/context.d.ts +5 -5
  75. package/dist/server/context.d.ts.map +1 -1
  76. package/dist/server/context.js +4 -1
  77. package/dist/server/context.js.map +1 -1
  78. package/dist/server/deps.d.ts +1 -1
  79. package/dist/server/deps.d.ts.map +1 -1
  80. package/dist/server/mod.d.ts +1 -1
  81. package/dist/server/mod.d.ts.map +1 -1
  82. package/dist/server/server.d.ts +3 -3
  83. package/dist/server/server.d.ts.map +1 -1
  84. package/dist/server/server.js +3 -3
  85. package/dist/utils/utils.d.ts +1 -2
  86. package/dist/utils/utils.d.ts.map +1 -1
  87. package/dist/utils/utils.js +3 -2
  88. package/dist/utils/utils.js.map +1 -1
  89. package/dist/web/webSocketToNext.d.ts +3 -0
  90. package/dist/web/webSocketToNext.d.ts.map +1 -0
  91. package/dist/web/webSocketToNext.js +160 -0
  92. package/dist/web/webSocketToNext.js.map +1 -0
  93. package/dist/web/wsClient.d.ts +8 -3
  94. package/dist/web/wsClient.d.ts.map +1 -1
  95. package/dist/web/wsClient.js +20 -6
  96. package/dist/web/wsClient.js.map +1 -1
  97. package/dist/web/wsClient.test.js +15 -0
  98. package/dist/web/wsClient.test.js.map +1 -1
  99. package/examples/README.md +21 -0
  100. package/examples/authHandler.test.ts +2 -2
  101. package/examples/mqtt+mqtts.test.ts +122 -0
  102. package/examples/mqtt.test.ts +2 -2
  103. package/examples/mqtts.test.ts +2 -2
  104. package/examples/webSocket.test.ts +10 -64
  105. package/examples/webSocketServer.ts +98 -0
  106. package/mqttConn/mqttConn.test.ts +6 -6
  107. package/mqttConn/mqttConn.ts +8 -9
  108. package/mqttPacket/protocolLevels.ts +2 -0
  109. package/node/README.md +1 -1
  110. package/node/mod.ts +7 -0
  111. package/node/{socket-node-stream.ts → nodeSocketToNext.ts} +78 -65
  112. package/node/tcpClient.ts +13 -27
  113. package/node/tcpServer.test.ts +12 -58
  114. package/node/tcpServer.ts +18 -8
  115. package/node/tlsServer.test.ts +11 -58
  116. package/node/tlsServer.ts +17 -7
  117. package/package.json +8 -2
  118. package/server/context.ts +9 -6
  119. package/server/deps.ts +1 -1
  120. package/server/mod.ts +1 -1
  121. package/server/server.ts +6 -6
  122. package/utils/utils.ts +3 -2
  123. package/web/webSocketToNext.ts +183 -0
  124. package/web/wsClient.test.ts +22 -0
  125. package/web/wsClient.ts +22 -9
  126. package/dist/node/wrapNodeSocket.d.ts +0 -12
  127. package/dist/node/wrapNodeSocket.d.ts.map +0 -1
  128. package/dist/node/wrapNodeSocket.js +0 -36
  129. package/dist/node/wrapNodeSocket.js.map +0 -1
  130. package/dist/socket/socket.d.ts +0 -40
  131. package/dist/socket/socket.d.ts.map +0 -1
  132. package/dist/socket/socket.js +0 -94
  133. package/dist/socket/socket.js.map +0 -1
  134. package/dist/web/webSocketStream.d.ts +0 -29
  135. package/dist/web/webSocketStream.d.ts.map +0 -1
  136. package/dist/web/webSocketStream.js +0 -69
  137. package/dist/web/webSocketStream.js.map +0 -1
  138. package/dist/web/wrapWebSocket.d.ts +0 -12
  139. package/dist/web/wrapWebSocket.d.ts.map +0 -1
  140. package/dist/web/wrapWebSocket.js +0 -41
  141. package/dist/web/wrapWebSocket.js.map +0 -1
  142. package/node/socket-node-stream.test.ts +0 -322
  143. package/node/wrapNodeSocket.ts +0 -44
  144. package/socket/socket.test.ts +0 -91
  145. package/socket/socket.ts +0 -124
  146. package/web/webSocketStream.ts +0 -97
  147. package/web/wrapWebSocket.ts +0 -52
@@ -1,322 +0,0 @@
1
- import { test } from "node:test";
2
- import assert from "node:assert/strict";
3
- import { PassThrough } from "node:stream";
4
- import { Conn, type SockConn } from "./socket-node-stream.ts";
5
-
6
- // Helper to create a mock SockConn using a PassThrough duplex stream
7
- function createMockConn(): {
8
- conn: Conn;
9
- stream: PassThrough;
10
- closeCalled: { count: number };
11
- } {
12
- const stream = new PassThrough();
13
- const closeCalled = { count: 0 };
14
-
15
- const sockConn: SockConn = {
16
- stream,
17
- close: () => {
18
- closeCalled.count++;
19
- },
20
- remoteAddr: {
21
- transport: "tcp",
22
- hostname: "127.0.0.1",
23
- port: 1883,
24
- },
25
- };
26
-
27
- const conn = new Conn(sockConn);
28
- return { conn, stream, closeCalled };
29
- }
30
-
31
- test("Conn.write - successfully writes data", async () => {
32
- const { conn, stream } = createMockConn();
33
- const data = new Uint8Array([0x01, 0x02, 0x03]);
34
-
35
- const bytesWritten = await conn.write(data);
36
- assert.equal(bytesWritten, data.length, "Number of written bytes matches");
37
-
38
- const chunk = stream.read();
39
- assert.deepStrictEqual(
40
- new Uint8Array(chunk),
41
- data,
42
- "The stream received the correct data",
43
- );
44
-
45
- conn.close();
46
- });
47
-
48
- test("Conn.write - respects backpressure and waits for drain event", async () => {
49
- const { conn, stream } = createMockConn();
50
- const data = new Uint8Array([0x01, 0x02, 0x03]);
51
-
52
- // Force stream.write to return false to simulate a full internal buffer (backpressure)
53
- stream.write = () => false;
54
-
55
- let writeResolved = false;
56
- const writePromise = conn.write(data).then((bytes) => {
57
- writeResolved = true;
58
- return bytes;
59
- });
60
-
61
- // Yield execution shortly to ensure the promise hasn't resolved prematurely
62
- await new Promise((resolve) => setImmediate(resolve));
63
- assert.equal(
64
- writeResolved,
65
- false,
66
- "Write promise should pend while backpressure is active",
67
- );
68
-
69
- // Emit the drain event to signal the buffer has cleared
70
- stream.emit("drain");
71
-
72
- const bytesWritten = await writePromise;
73
- assert.equal(bytesWritten, data.length, "Resolves after drain event fires");
74
- assert.equal(writeResolved, true);
75
-
76
- conn.close();
77
- });
78
-
79
- test("Conn.read - reads exact number of bytes", async () => {
80
- const { conn, stream } = createMockConn();
81
- const data = new Uint8Array([10, 20, 30, 40, 50]);
82
-
83
- stream.write(data);
84
-
85
- const result = await conn.read(3);
86
- assert.deepStrictEqual(
87
- result,
88
- new Uint8Array([10, 20, 30]),
89
- "First 3 bytes match",
90
- );
91
-
92
- const restResult = await conn.read(2);
93
- assert.deepStrictEqual(
94
- restResult,
95
- new Uint8Array([40, 50]),
96
- "Remaining bytes match via the leftover buffer",
97
- );
98
-
99
- conn.close();
100
- });
101
-
102
- test("Conn.read - waits for async data (readable event)", async () => {
103
- const { conn, stream } = createMockConn();
104
-
105
- const readPromise = conn.read(2);
106
-
107
- // Simulate network latency using setImmediate or setTimeout
108
- await new Promise((resolve) => setTimeout(resolve, 10));
109
- stream.write(new Uint8Array([0xAA, 0xBB]));
110
-
111
- const result = await readPromise;
112
- assert.deepStrictEqual(
113
- result,
114
- new Uint8Array([0xAA, 0xBB]),
115
- "Data correctly received after async wait",
116
- );
117
-
118
- conn.close();
119
- });
120
-
121
- test("Conn.close - gracefully closes the connection and streams", async () => {
122
- const { conn, stream, closeCalled } = createMockConn();
123
-
124
- assert.equal(closeCalled.count, 0, "Closer has not been called yet");
125
-
126
- conn.close();
127
-
128
- assert.equal(closeCalled.count, 1, "Closer is called exactly once");
129
- assert.equal(stream.destroyed, true, "The underlying stream is destroyed");
130
-
131
- await assert.rejects(
132
- conn.write(new Uint8Array([1])),
133
- /Stream is closed or not writable/,
134
- "Writing to a closed connection throws an error",
135
- );
136
-
137
- const readResult = await conn.read(1);
138
- assert.equal(
139
- readResult,
140
- null,
141
- "Reading from a closed connection returns null",
142
- );
143
- });
144
-
145
- test("Conn.read - length less than 0 returns null", async () => {
146
- const { conn } = createMockConn();
147
-
148
- const result = await conn.read(-5);
149
- assert.equal(result, null, "Reading with a negative length returns null");
150
-
151
- conn.close();
152
- });
153
-
154
- test("Conn.read - length equal to 0 returns empty Uint8Array", async () => {
155
- const { conn } = createMockConn();
156
-
157
- const result = await conn.read(0);
158
- assert.deepStrictEqual(
159
- result,
160
- new Uint8Array(0),
161
- "Reading 0 bytes returns an empty Uint8Array",
162
- );
163
-
164
- conn.close();
165
- });
166
-
167
- test("Conn.read - stream errors while waiting for data (returns null)", async () => {
168
- const { conn, stream } = createMockConn();
169
-
170
- // Prevent Node.js from crashing due to an unhandled stream error during test execution
171
- stream.on("error", () => {});
172
-
173
- const readPromise = conn.read(5);
174
-
175
- // Emit error event to invoke internal error tracking state
176
- stream.emit("error", new Error("Network failure"));
177
-
178
- const result = await readPromise;
179
- assert.equal(
180
- result,
181
- null,
182
- "Returns null when the stream errors out during an active read",
183
- );
184
-
185
- conn.close();
186
- });
187
-
188
- test("Conn.read - chunk is larger than requested (populates leftover)", async () => {
189
- const { conn, stream } = createMockConn();
190
- const largeChunk = new Uint8Array([1, 2, 3, 4, 5]);
191
-
192
- stream.write(largeChunk);
193
-
194
- const result1 = await conn.read(2);
195
- assert.deepStrictEqual(
196
- result1,
197
- new Uint8Array([1, 2]),
198
- "Returns the exact requested chunk size",
199
- );
200
-
201
- const result2 = await conn.read(2);
202
- assert.deepStrictEqual(
203
- result2,
204
- new Uint8Array([3, 4]),
205
- "Returns the next bytes from the leftover buffer",
206
- );
207
-
208
- const result3 = await conn.read(1);
209
- assert.deepStrictEqual(
210
- result3,
211
- new Uint8Array([5]),
212
- "Returns the last remaining byte from leftover",
213
- );
214
-
215
- conn.close();
216
- });
217
-
218
- test("Conn.read - partial leftover is not enough (combines leftover and new stream chunk)", async () => {
219
- const { conn, stream } = createMockConn();
220
-
221
- // 1. Write 3 bytes and read 2 (leaving 1 byte [0x03] in leftover)
222
- stream.write(new Uint8Array([0x01, 0x02, 0x03]));
223
- await conn.read(2);
224
-
225
- // 2. Write new data to the stream
226
- stream.write(new Uint8Array([0x04, 0x05]));
227
-
228
- // 3. Request 3 bytes (needs 1 from leftover + 2 from stream)
229
- const result = await conn.read(3);
230
- assert.deepStrictEqual(
231
- result,
232
- new Uint8Array([0x03, 0x04, 0x05]),
233
- "Combines leftover data with newly arrived stream data",
234
- );
235
-
236
- conn.close();
237
- });
238
-
239
- test("Conn.write - rejects immediately if stream.writable is false", async () => {
240
- const { conn, stream } = createMockConn();
241
-
242
- Object.defineProperty(stream, "writable", { value: false });
243
-
244
- await assert.rejects(
245
- conn.write(new Uint8Array([1, 2, 3])),
246
- /Stream is closed or not writable/,
247
- "Rejects immediately when stream.writable is false",
248
- );
249
-
250
- conn.close();
251
- });
252
-
253
- test("Conn.read - fulfilled entirely from leftover buffer (skips while loop)", async () => {
254
- const { conn, stream } = createMockConn();
255
- const data = new Uint8Array([10, 20, 30, 40, 50]);
256
-
257
- // 1. Populate the leftover buffer by reading less than what was written
258
- stream.write(data);
259
- await conn.read(2); // Leaves [30, 40, 50] in leftover
260
-
261
- // 2. Request an amount that can be fully satisfied by the leftover buffer
262
- const result = await conn.read(2);
263
- assert.deepStrictEqual(
264
- result,
265
- new Uint8Array([30, 40]),
266
- "Successfully fulfilled entirely from the leftover buffer",
267
- );
268
-
269
- // 3. Verify the remaining byte is still preserved
270
- const finalResult = await conn.read(1);
271
- assert.deepStrictEqual(
272
- finalResult,
273
- new Uint8Array([50]),
274
- "The remaining byte in leftover is still accessible",
275
- );
276
-
277
- conn.close();
278
- });
279
-
280
- test("Conn - handles unexpected stream close event from underlying resource", () => {
281
- const { conn, stream } = createMockConn();
282
-
283
- // Initially, the connection should not be marked as closed
284
- assert.equal(conn["closed"], false, "Connection is open initially");
285
-
286
- // Simulate the underlying socket/stream closing unexpectedly from the remote side
287
- stream.emit("close");
288
-
289
- // Verify that the permanent 'close' listener was triggered and updated the internal state
290
- assert.equal(
291
- conn["closed"],
292
- true,
293
- "Connection internally updates to closed when stream emits close",
294
- );
295
-
296
- // Clean up to ensure resources are disposed
297
- conn.close();
298
- });
299
-
300
- test("Conn.write - rejects the promise if stream.write invokes the callback with an error", async () => {
301
- const { conn, stream } = createMockConn();
302
- const data = new Uint8Array([1, 2, 3]);
303
-
304
- // Simulate a write error by overriding stream.write
305
- // It forces the callback to be invoked with an Error object
306
- stream.write = (_chunk, callback) => {
307
- if (typeof callback === "function") {
308
- // Execute the callback with an error asynchronously, mimicking Node.js behavior
309
- setImmediate(() => callback(new Error("Write pipeline failure")));
310
- }
311
- return false;
312
- };
313
-
314
- // Verify that the conn.write Promise rejects with the exact error
315
- await assert.rejects(
316
- conn.write(data),
317
- /Write pipeline failure/,
318
- "The write promise should reject when the stream callback receives an error",
319
- );
320
-
321
- conn.close();
322
- });
@@ -1,44 +0,0 @@
1
- /**
2
- * Utility functions for wrapping Node.js sockets into standard web streams.
3
- */
4
- import type { Socket } from "node:net";
5
- import { Readable, Writable } from "node:stream";
6
- import type { NetAddr, SockConn } from "../socket/socket.ts";
7
-
8
- /**
9
- * Closes a Node.js socket if it is not already closed
10
- * @param sock - The Node.js socket to close
11
- */
12
- function closer(sock: Socket): void {
13
- // deno-coverage-ignore-start
14
- if (!sock.closed) {
15
- sock.end();
16
- }
17
- // deno-coverage-ignore-stop
18
- }
19
-
20
- /**
21
- * Wraps a Node.js socket into a standard web streams-based socket connection
22
- * @param socket - The Node.js socket to wrap
23
- * @returns A socket connection object with readable/writable streams and close method
24
- */
25
- export function wrapNodeSocket(socket: Socket): SockConn {
26
- const readable = Readable.toWeb(socket);
27
- const writable = Writable.toWeb(socket);
28
-
29
- // deno-coverage-ignore-start
30
- const remoteAddr: NetAddr = {
31
- hostname: socket.remoteAddress || "",
32
- port: socket.remotePort || 0,
33
- transport: "tcp",
34
- };
35
- // deno-coverage-ignore-stop
36
-
37
- const conn: SockConn = {
38
- readable: readable as ReadableStream<Uint8Array>,
39
- writable: writable as WritableStream<Uint8Array>,
40
- close: () => closer(socket),
41
- remoteAddr,
42
- };
43
- return conn;
44
- }
@@ -1,91 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { test } from "node:test";
3
- import { Conn } from "./socket.ts";
4
- import { createMockSockConn } from "../dev_utils/mod.ts";
5
-
6
- test("Conn.read peaks and buffers partial data correctly", async () => {
7
- const chunk1 = new Uint8Array([1, 2, 3]);
8
- const chunk2 = new Uint8Array([4, 5, 6, 7]);
9
- const chunk3 = new Uint8Array([8, 9]);
10
-
11
- const mockTarget = new Uint8Array();
12
- const sockConn = createMockSockConn([chunk1, chunk2, chunk3], mockTarget);
13
- const conn = new Conn(sockConn);
14
-
15
- const res1 = await conn.read(2);
16
- assert.deepStrictEqual(res1, new Uint8Array([1, 2]));
17
-
18
- const res2 = await conn.read(3);
19
- assert.deepStrictEqual(res2, new Uint8Array([3, 4, 5]));
20
-
21
- const res3 = await conn.read(4);
22
- assert.deepStrictEqual(res3, new Uint8Array([6, 7, 8, 9]));
23
- });
24
-
25
- test("Conn.read returns null when stream ends before length is met", async () => {
26
- const chunk = new Uint8Array([1, 2, 3]);
27
- const mockTarget = new Uint8Array(0);
28
- const sockConn = createMockSockConn([chunk], mockTarget);
29
- const conn = new Conn(sockConn);
30
-
31
- // asking for5 bytes, but only 3 are available in the stream
32
- const res = await conn.read(5);
33
- assert.deepStrictEqual(res, null);
34
- });
35
-
36
- test("Conn.read returns null when length <= 0", async () => {
37
- const chunk = new Uint8Array([1, 2, 3]);
38
- const mockTarget = new Uint8Array(0);
39
- const sockConn = createMockSockConn([chunk], mockTarget);
40
- const conn = new Conn(sockConn);
41
-
42
- const res = await conn.read(-1);
43
- assert.deepStrictEqual(res, null);
44
- });
45
-
46
- test("Conn.read returns empty array when length = 0", async () => {
47
- const chunk = new Uint8Array([1, 2, 3]);
48
- const mockTarget = new Uint8Array(0);
49
- const sockConn = createMockSockConn([chunk], mockTarget);
50
- const conn = new Conn(sockConn);
51
-
52
- const res = await conn.read(0);
53
- assert.deepStrictEqual(res?.length, 0);
54
- });
55
-
56
- test("Conn.write successfully forwards data to writable stream", async () => {
57
- const dataToWrite = new Uint8Array([10, 20, 30, 40]);
58
- const mockTarget = new Uint8Array(4);
59
- const sockConn = createMockSockConn([], mockTarget);
60
- const conn = new Conn(sockConn);
61
-
62
- const bytesWritten = await conn.write(dataToWrite);
63
-
64
- assert.deepStrictEqual(bytesWritten, 4);
65
- assert.deepStrictEqual(mockTarget, dataToWrite);
66
- });
67
-
68
- test("Conn.write rejects when connection is closed", async () => {
69
- const mockTarget = new Uint8Array(4);
70
- const sockConn = createMockSockConn([], mockTarget);
71
- const conn = new Conn(sockConn);
72
-
73
- conn.close();
74
-
75
- await assert.rejects(
76
- conn.write(new Uint8Array([1, 2])),
77
- /Connection closed/,
78
- );
79
- });
80
-
81
- test("Conn.close closes writer, releases reader lock and sets closed flag", () => {
82
- const mockTarget = new Uint8Array(0);
83
- const sockConn = createMockSockConn([], mockTarget);
84
- const conn = new Conn(sockConn);
85
-
86
- assert.deepStrictEqual(conn.closed, false);
87
-
88
- conn.close();
89
-
90
- assert.deepStrictEqual(conn.closed, true);
91
- });
package/socket/socket.ts DELETED
@@ -1,124 +0,0 @@
1
- /** Net adresses */
2
- export type NetAddr = {
3
- transport: "tcp" | "udp";
4
- hostname: string;
5
- port: number;
6
- };
7
- /** Unix adresses */
8
- export type UnixAddr = { transport: "unix" | "unixpacket"; path: string };
9
- /** vSock adresses */
10
- export type VsockAddr = { transport: "vsock"; cid: number; port: number };
11
- /** Socket adresses */
12
- export type SockAddr = NetAddr | UnixAddr | VsockAddr;
13
- /** Socket connection descriptor. */
14
- export type SockConn = {
15
- readable: ReadableStream<Uint8Array>;
16
- writable: WritableStream<Uint8Array>;
17
- close: () => void;
18
- localAddr?: SockAddr;
19
- remoteAddr?: SockAddr;
20
- };
21
-
22
- export class Conn {
23
- reader: ReadableStreamDefaultReader<Uint8Array>;
24
- writer: WritableStreamDefaultWriter<Uint8Array>;
25
- closed: boolean;
26
- remoteAddr: SockAddr | undefined;
27
- closer: () => void;
28
-
29
- private leftover: Uint8Array = new Uint8Array(0);
30
-
31
- constructor(sockConn: SockConn) {
32
- this.closed = false;
33
- this.reader = sockConn.readable.getReader();
34
- this.writer = sockConn.writable.getWriter();
35
- this.closer = sockConn.close.bind(sockConn);
36
- this.reader.closed.catch(() => {});
37
- this.writer.closed.catch(() => {});
38
- this.remoteAddr = sockConn.remoteAddr;
39
- }
40
-
41
- async read(length: number): Promise<Uint8Array | null> {
42
- if (this.closed || length < 0) {
43
- return null;
44
- }
45
-
46
- if (length === 0) {
47
- return new Uint8Array(0);
48
- }
49
-
50
- const result = new Uint8Array(length);
51
- let bytesAccumulated = 0;
52
-
53
- // Do we have data from the previous read()
54
- if (this.leftover.length > 0) {
55
- const take = Math.min(this.leftover.length, length);
56
- result.set(this.leftover.subarray(0, take), bytesAccumulated);
57
- bytesAccumulated += take;
58
- this.leftover = this.leftover.subarray(take);
59
- }
60
-
61
- // Keep readin until we have enough data
62
- while (bytesAccumulated < length) {
63
- const { value, done } = await this.reader.read();
64
-
65
- if (value) {
66
- const needed = length - bytesAccumulated;
67
-
68
- if (value.length <= needed) {
69
- // We need the whole chunk
70
- result.set(value, bytesAccumulated);
71
- bytesAccumulated += value.length;
72
- } else {
73
- // Chunk is bigger than what we need
74
- result.set(value.subarray(0, needed), bytesAccumulated);
75
- bytesAccumulated += needed;
76
- // Save the rest for the next read() call
77
- this.leftover = value.subarray(needed);
78
- }
79
- }
80
- if (done) {
81
- // if the stream closed and not enough data was returned, return null
82
- return null;
83
- }
84
- }
85
- return result;
86
- }
87
-
88
- async write(data: Uint8Array): Promise<number> {
89
- if (this.closed) {
90
- return Promise.reject(new Error("Connection closed"));
91
- }
92
- try {
93
- await this.writer.write(data);
94
- return data.length;
95
- } catch (err) {
96
- this.closed = true;
97
- throw err;
98
- }
99
- }
100
-
101
- close() {
102
- if (!this.closed) {
103
- this.closed = true;
104
-
105
- // deno-coverage-ignore-start
106
- void this.writer.close().catch(() => {
107
- // ignore duplicate/late close attempts; the stream may already be closed
108
- });
109
- // deno-coverage-ignore-stop
110
-
111
- try {
112
- void this.reader.cancel().catch(() => {
113
- // ignore duplicate/late cancel attempts; the stream may already be closed
114
- });
115
- this.reader?.releaseLock();
116
- // deno-coverage-ignore
117
- } catch (_err) { /* swallow */ }
118
-
119
- try {
120
- this.closer();
121
- } catch (_err) { /* swallow */ }
122
- }
123
- }
124
- }
@@ -1,97 +0,0 @@
1
- /**
2
- * Utility functions for wrapping standard WebSockets into standard web streams.
3
- */
4
-
5
- export interface WebSocketConnection<
6
- T extends Uint8Array | string = Uint8Array | string,
7
- > {
8
- readable: ReadableStream<T>;
9
- writable: WritableStream<T>;
10
- protocol: string;
11
- extensions: string;
12
- }
13
-
14
- export interface WebSocketCloseInfo {
15
- closeCode?: number;
16
- reason?: string;
17
- }
18
-
19
- export interface WebSocketStreamOptions {
20
- protocols?: string[];
21
- signal?: AbortSignal;
22
- }
23
-
24
- /**
25
- * [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket) with [Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API)
26
- *
27
- * @see https://web.dev/websocketstream/
28
- */
29
- export class WebSocketStream<
30
- T extends Uint8Array | string = Uint8Array | string,
31
- > {
32
- readonly opened: Promise<WebSocketConnection<T>>;
33
-
34
- readonly closed: Promise<WebSocketCloseInfo>;
35
-
36
- readonly close: (closeInfo?: WebSocketCloseInfo) => void;
37
-
38
- constructor(ws: WebSocket, options: WebSocketStreamOptions = {}) {
39
- if (options.signal?.aborted) {
40
- throw new DOMException("This operation was aborted", "AbortError");
41
- }
42
- const closeWithInfo = (
43
- { closeCode: code, reason }: WebSocketCloseInfo = {},
44
- ) => ws.close(code, reason);
45
-
46
- this.opened = new Promise((resolve, reject) => {
47
- ws.onopen = () => {
48
- resolve({
49
- readable: new ReadableStream<T>({
50
- start(controller) {
51
- ws.onmessage = ({ data }) => {
52
- if (data instanceof ArrayBuffer) {
53
- controller.enqueue(new Uint8Array(data) as T);
54
- } else {
55
- controller.enqueue(data as T);
56
- }
57
- };
58
- ws.onerror = (e) => controller.error(e);
59
- },
60
- cancel: closeWithInfo,
61
- }),
62
- writable: new WritableStream<T>({
63
- write(chunk) {
64
- if (chunk instanceof Uint8Array) {
65
- ws.send(chunk.buffer as ArrayBuffer);
66
- } else {
67
- ws.send(chunk);
68
- }
69
- },
70
- abort() {
71
- ws.close();
72
- },
73
- close: closeWithInfo,
74
- }),
75
- protocol: ws.protocol,
76
- extensions: ws.extensions,
77
- });
78
- ws.removeEventListener("error", reject);
79
- };
80
- ws.addEventListener("error", reject);
81
- });
82
-
83
- this.closed = new Promise<WebSocketCloseInfo>((resolve, reject) => {
84
- ws.onclose = ({ code, reason }) => {
85
- resolve({ closeCode: code, reason });
86
- ws.removeEventListener("error", reject);
87
- };
88
- ws.addEventListener("error", reject);
89
- });
90
-
91
- if (options.signal) {
92
- options.signal.onabort = () => ws.close();
93
- }
94
-
95
- this.close = closeWithInfo;
96
- }
97
- }