socketduct 0.1.15

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 (138) hide show
  1. package/.dockerignore +11 -0
  2. package/.env.example +131 -0
  3. package/AGENTS.md +168 -0
  4. package/Dockerfile +30 -0
  5. package/README.md +134 -0
  6. package/changelog.d/20260716083144-v2-ack-wire-format.md +3 -0
  7. package/changelog.d/20260716092044-v2-only-stream-framing.md +1 -0
  8. package/changelog.d/20260727113000-relay-connect-timeout-destroy.md +1 -0
  9. package/changelog.d/20260813093000-private-relay-hardening.md +1 -0
  10. package/changelog.d/20260824183412-initial-target-recovery-https.md +1 -0
  11. package/changelog.d/20260824230000-tls-relay-session-close.md +1 -0
  12. package/changelog.d/20260825120000-reconnect-close-target-timeout.md +1 -0
  13. package/changelog.d/20260829120000-protocol-v3-multiplexing.md +1 -0
  14. package/changelog.d/20260829160000-v3-review-corrections.md +1 -0
  15. package/changelog.d/20260830000000-fix-socket-idle-timeout.md +1 -0
  16. package/changelog.d/20260830090000-multi-gateway-transport-set.md +1 -0
  17. package/changelog.d/20260830093000-relay-close-settlement.md +1 -0
  18. package/changelog.d/20260830120000-immutable-gateway-image.md +1 -0
  19. package/changelog.d/20260902210000-native-reverse-gateway.md +1 -0
  20. package/changelog.d/20260902230000-relay-resume-terminal-order.md +1 -0
  21. package/changelog.d/20260902230708-multiplexed-open-ownership.md +1 -0
  22. package/changelog.d/20260902231541-multiplexed-terminal-close.md +1 -0
  23. package/changelog.d/20260902231542-reverse-close-position.md +1 -0
  24. package/changelog.d/20260902231543-reverse-terminal-connect.md +1 -0
  25. package/changelog.d/20260902233000-reverse-gateway-bounds.md +1 -0
  26. package/changelog.d/20260903001000-reverse-data-ordering.md +1 -0
  27. package/changelog.d/20260903003000-reverse-hub-restart-window.md +1 -0
  28. package/changelog.d/20260903010000-reverse-terminal-lifecycle.md +1 -0
  29. package/changelog.d/20260904142743-transport-set-readiness-wait.md +1 -0
  30. package/changelog.d/20260904152855-deferred-open-snapshots-binary-views.md +1 -0
  31. package/changelog.d/20260904163041-tensorbuzz-container-workflow.md +1 -0
  32. package/changelog.d/20260904174955-transport-set-terminal-causes.md +1 -0
  33. package/changelog.d/20260905081000-contain-reverse-auth-write-errors.md +1 -0
  34. package/changelog.d/20260905100520-reverse-hub-close-race.md +1 -0
  35. package/changelog.d/20260905120000-persistent-jsonl-logging.md +1 -0
  36. package/changelog.d/20260908071009-jsonl-partial-append-recovery.md +1 -0
  37. package/changelog.d/20260908074916-private-log-directory-setup.md +1 -0
  38. package/changelog.d/20260909093057-reverse-large-write-backpressure.md +1 -0
  39. package/changelog.d/20260909135830-actionable-error-diagnostics.md +1 -0
  40. package/changelog.d/20260909150000-transport-set-error-redaction.md +1 -0
  41. package/docker-compose.dev.yml +19 -0
  42. package/docker-compose.reverse-gateway.yml +36 -0
  43. package/docker-compose.yml +73 -0
  44. package/docs/architecture.md +264 -0
  45. package/docs/docker-topology.md +164 -0
  46. package/docs/docker.md +281 -0
  47. package/docs/logging.md +185 -0
  48. package/docs/plans/initial-implementation.md +205 -0
  49. package/docs/plans/socket-docker-integration.md +221 -0
  50. package/docs/protocol.md +558 -0
  51. package/docs/releasing.md +18 -0
  52. package/docs/reverse-gateway.md +186 -0
  53. package/docs/runbooks/jsonl-integrity-rollout.md +144 -0
  54. package/docs/running.md +215 -0
  55. package/docs/security.md +258 -0
  56. package/docs/session-lifecycle.md +127 -0
  57. package/docs/socket-api.md +448 -0
  58. package/eslint.config.js +36 -0
  59. package/package.json +44 -0
  60. package/scripts/deploy-gateway.js +194 -0
  61. package/scripts/test.js +28 -0
  62. package/src/auth/passwords.js +38 -0
  63. package/src/auth/static-token-store.js +41 -0
  64. package/src/auth/user-store.js +139 -0
  65. package/src/client/connect.js +76 -0
  66. package/src/client/deferred-virtual-socket.js +312 -0
  67. package/src/client/http-agent.js +101 -0
  68. package/src/client/https-agent.js +366 -0
  69. package/src/client/multiplexed-connection.js +1504 -0
  70. package/src/client/multiplexed-transport-set.js +917 -0
  71. package/src/client/multiplexed.js +3 -0
  72. package/src/client/socketduct-socket.js +1174 -0
  73. package/src/client/virtual-socket.js +256 -0
  74. package/src/config/environment.js +400 -0
  75. package/src/edge/tcp-edge.js +82 -0
  76. package/src/index.js +101 -0
  77. package/src/logging/structured-logger.js +745 -0
  78. package/src/protocol/compression.js +325 -0
  79. package/src/protocol/frame.js +341 -0
  80. package/src/protocol/v3-compression.js +44 -0
  81. package/src/protocol/v3-frame.js +209 -0
  82. package/src/proxy/multiplexed-relay.js +798 -0
  83. package/src/proxy/tcp-relay.js +1191 -0
  84. package/src/relay.js +3 -0
  85. package/src/reverse/client.js +721 -0
  86. package/src/reverse/common.js +88 -0
  87. package/src/reverse/gateway.js +663 -0
  88. package/src/reverse/hub.js +563 -0
  89. package/src/reverse/index.js +3 -0
  90. package/src/reverse/runtime.js +78 -0
  91. package/src/server/control-handler.js +170 -0
  92. package/src/server/primary.js +64 -0
  93. package/src/server/relay-runtime.js +124 -0
  94. package/src/server/worker.js +39 -0
  95. package/src/sessions/session.js +212 -0
  96. package/src/spool/spool-file.js +451 -0
  97. package/tensorbuzz.yml +36 -0
  98. package/test/e2e/gateway-process.test.js +262 -0
  99. package/test/e2e/reverse-gateway-process.test.js +285 -0
  100. package/test/fixtures/logging/enospc-matrix.mjs +238 -0
  101. package/test/fixtures/logging/enospc-recovery.mjs +146 -0
  102. package/test/fixtures/logging/process-writer.mjs +20 -0
  103. package/test/helpers/tls-certificates.js +115 -0
  104. package/test/integration/initial-target-recovery.test.js +518 -0
  105. package/test/integration/logging-recovery.test.js +163 -0
  106. package/test/integration/multiplexed-compatibility.test.js +254 -0
  107. package/test/integration/multiplexed-corrections.test.js +551 -0
  108. package/test/integration/multiplexed-failures.test.js +339 -0
  109. package/test/integration/multiplexed-transport-set.test.js +456 -0
  110. package/test/integration/multiplexed-transport.test.js +208 -0
  111. package/test/integration/reverse-gateway.test.js +1293 -0
  112. package/test/integration/socketduct-http-agent.test.js +435 -0
  113. package/test/integration/socketduct-https-agent.test.js +669 -0
  114. package/test/integration/stream-compression.test.js +1267 -0
  115. package/test/integration/tcp-forwarding.test.js +2878 -0
  116. package/test/integration/tls-forwarding.test.js +748 -0
  117. package/test/unit/auth/auth.test.js +64 -0
  118. package/test/unit/auth/static-token-store.test.js +14 -0
  119. package/test/unit/client/connect.test.js +58 -0
  120. package/test/unit/client/multiplexed-spool-budget.test.js +309 -0
  121. package/test/unit/client/multiplexed-transport-set.test.js +906 -0
  122. package/test/unit/client/socketduct-socket.test.js +1124 -0
  123. package/test/unit/client/virtual-socket.test.js +98 -0
  124. package/test/unit/config/docker-gateway-files.test.js +274 -0
  125. package/test/unit/config/environment.test.js +507 -0
  126. package/test/unit/index.test.js +78 -0
  127. package/test/unit/logging/structured-logger.test.js +674 -0
  128. package/test/unit/protocol/compression.test.js +421 -0
  129. package/test/unit/protocol/frame.test.js +336 -0
  130. package/test/unit/protocol/v3-compression.test.js +31 -0
  131. package/test/unit/protocol/v3-frame.test.js +57 -0
  132. package/test/unit/release-configuration.test.js +21 -0
  133. package/test/unit/server/control-handler.test.js +291 -0
  134. package/test/unit/server/primary.test.js +59 -0
  135. package/test/unit/server/relay-runtime.test.js +434 -0
  136. package/test/unit/sessions/session.test.js +84 -0
  137. package/test/unit/spool/spool-file.test.js +295 -0
  138. package/tsconfig.json +20 -0
@@ -0,0 +1,339 @@
1
+ /* eslint-disable jsdoc/escape-inline-tags, jsdoc/require-param-type, jsdoc/require-returns */
2
+ import assert from "node:assert/strict"
3
+ import {createConnection, createServer} from "node:net"
4
+ import {Duplex} from "node:stream"
5
+ import test from "node:test"
6
+
7
+ import {StaticTokenStore} from "../../src/auth/static-token-store.js"
8
+ import {MultiplexedSocketductConnection} from "../../src/client/multiplexed-connection.js"
9
+ import {SocketductSessionLostError, SocketductStreamError} from "../../src/client/virtual-socket.js"
10
+ import {startMultiplexedTcpRelay} from "../../src/proxy/multiplexed-relay.js"
11
+
12
+ /** @type {WeakMap<import('node:net').Server, Set<import('node:net').Socket>>} */
13
+ const socketsByServer = new WeakMap()
14
+
15
+ /** @param {import('node:net').Server} server - Server. @returns {Promise<number>} */
16
+ async function listen(server) {
17
+ const sockets = new Set()
18
+ socketsByServer.set(server, sockets)
19
+ server.on("connection", (socket) => {
20
+ sockets.add(socket)
21
+ socket.once("close", () => sockets.delete(socket))
22
+ })
23
+ await new Promise((resolve, reject) => {
24
+ server.once("error", reject)
25
+ server.listen(0, "127.0.0.1", () => { server.off("error", reject); resolve(undefined) })
26
+ })
27
+ const address = server.address()
28
+ assert(address !== null && typeof address !== "string")
29
+ return address.port
30
+ }
31
+
32
+ /** @param {import('node:net').Server} server - Server. @returns {Promise<void>} */
33
+ async function closeServer(server) {
34
+ for (const socket of socketsByServer.get(server) ?? []) socket.destroy()
35
+ if (server.listening) await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve(undefined)))
36
+ }
37
+
38
+ /**
39
+ * @param {import('node:events').EventEmitter} emitter - Emitter. @param {string} event - Event. @returns {Promise<unknown[]>}
40
+ * @param event
41
+ */
42
+ async function onceEvent(emitter, event) {
43
+ return await new Promise((resolve, reject) => {
44
+ emitter.once("error", reject)
45
+ /** @param {...unknown} values - Event values. */
46
+ const onEvent = (...values) => { emitter.off("error", reject); resolve(values) }
47
+ emitter.once(event, onEvent)
48
+ })
49
+ }
50
+
51
+ /** @param {import('node:stream').Duplex} socket - Socket. @returns {Promise<Error & {code?: string}>} */
52
+ async function nextError(socket) {
53
+ return await new Promise((resolve) => socket.once("error", (error) => resolve(/** @type {Error & {code?: string}} */ (error))))
54
+ }
55
+
56
+ /** @param {import('node:stream').Duplex} socket - Socket. @returns {Promise<string>} */
57
+ async function readText(socket) {
58
+ return Buffer.from(/** @type {Uint8Array} */ ((await onceEvent(socket, "data"))[0])).toString()
59
+ }
60
+
61
+ /**
62
+ * @param {() => boolean} predicate - Condition. @param {number} [timeoutMs] - Deadline. @returns {Promise<void>}
63
+ * @param timeoutMs
64
+ */
65
+ async function waitUntil(predicate, timeoutMs = 2_000) {
66
+ const deadline = Date.now() + timeoutMs
67
+ while (!predicate()) {
68
+ if (Date.now() >= deadline) throw new Error("timed out waiting for condition")
69
+ await new Promise((resolve) => setTimeout(resolve, 5))
70
+ }
71
+ }
72
+
73
+ /**
74
+ *
75
+ */
76
+ function tokens() {
77
+ return new StaticTokenStore([{username: "edge", token: "valid-token"}])
78
+ }
79
+
80
+ test("authentication and exact-target authorization fail closed", async (t) => {
81
+ const relay = await startMultiplexedTcpRelay({tokens: tokens(), authorizeTarget: () => false})
82
+ t.after(() => relay.close())
83
+ const unauthorizedConnection = new MultiplexedSocketductConnection({
84
+ relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"}
85
+ })
86
+ t.after(() => unauthorizedConnection.close())
87
+ const denied = unauthorizedConnection.createConnection({host: "127.0.0.1", port: 9})
88
+ const deniedError = await nextError(denied)
89
+ assert(deniedError instanceof SocketductStreamError)
90
+ assert.equal(deniedError.code, "TARGET_UNAUTHORIZED")
91
+ assert.equal(relay.status().activeTargets, 0)
92
+
93
+ const invalidConnection = new MultiplexedSocketductConnection({
94
+ relay: {host: "127.0.0.1", port: relay.port, token: "wrong-token"},
95
+ maxReconnectAttempts: 2
96
+ })
97
+ t.after(() => invalidConnection.close())
98
+ const invalid = invalidConnection.createConnection({host: "127.0.0.1", port: 9})
99
+ const authError = await nextError(invalid)
100
+ assert(authError instanceof SocketductSessionLostError)
101
+ assert.equal(authError.code, "AUTH_REQUIRED")
102
+ await new Promise((resolve) => setTimeout(resolve, 30))
103
+ assert.equal(invalidConnection.status().reconnectAttempts, 0)
104
+ })
105
+
106
+ test("target open failure and stream reset are isolated from siblings", async (t) => {
107
+ const echo = createServer((socket) => socket.on("data", (chunk) => socket.write(chunk)))
108
+ const echoPort = await listen(echo)
109
+ t.after(() => closeServer(echo))
110
+ const unused = createServer()
111
+ const failedPort = await listen(unused)
112
+ await closeServer(unused)
113
+ const relay = await startMultiplexedTcpRelay({
114
+ tokens: tokens(),
115
+ authorizeTarget: ({target}) => target.host === "127.0.0.1" && (target.port === echoPort || target.port === failedPort)
116
+ })
117
+ t.after(() => relay.close())
118
+ const connection = new MultiplexedSocketductConnection({relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"}})
119
+ t.after(() => connection.close())
120
+ const failed = connection.createConnection({host: "127.0.0.1", port: failedPort})
121
+ const sibling = connection.createConnection({host: "127.0.0.1", port: echoPort})
122
+ const failure = await nextError(failed)
123
+ assert.equal(failure.code, "TARGET_OPEN_FAILED")
124
+ await onceEvent(sibling, "connect")
125
+ const response = readText(sibling)
126
+ sibling.write("still-open")
127
+ assert.equal(await response, "still-open")
128
+ assert.equal(relay.status().physicalConnections, 1)
129
+ sibling.destroy()
130
+ })
131
+
132
+ test("resume loss resets only the affected stream while its sibling resumes", async (t) => {
133
+ /** @type {import('node:net').Socket[]} */
134
+ const accepted = []
135
+ const target = createServer((socket) => {
136
+ accepted.push(socket)
137
+ socket.on("data", (chunk) => socket.write(chunk))
138
+ })
139
+ const targetPort = await listen(target)
140
+ t.after(() => closeServer(target))
141
+ const relay = await startMultiplexedTcpRelay({
142
+ tokens: tokens(),
143
+ authorizeTarget: ({target: requested}) => requested.port === targetPort,
144
+ resumeWindowMs: 2_000
145
+ })
146
+ t.after(() => relay.close())
147
+ const connection = new MultiplexedSocketductConnection({
148
+ relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"},
149
+ reconnectDelayMs: 40
150
+ })
151
+ t.after(() => connection.close())
152
+ const lost = connection.createConnection({host: "127.0.0.1", port: targetPort, sessionName: "lost"})
153
+ const survivor = connection.createConnection({host: "127.0.0.1", port: targetPort, sessionName: "survivor"})
154
+ await Promise.all([onceEvent(lost, "connect"), onceEvent(survivor, "connect")])
155
+ assert.equal(accepted.length, 2)
156
+ const lostError = nextError(lost)
157
+ const survivorReady = onceEvent(survivor, "ready")
158
+ await relay.closeActiveTransport()
159
+ const targetToLose = accepted[0]
160
+ assert(targetToLose !== undefined)
161
+ targetToLose.destroy()
162
+ const error = await lostError
163
+ assert(error instanceof SocketductSessionLostError)
164
+ assert.equal(error.code, "RESUME_SESSION_LOST")
165
+ await survivorReady
166
+ const response = readText(survivor)
167
+ survivor.write("resumed")
168
+ assert.equal(await response, "resumed")
169
+ assert.equal(connection.status().reconnectAttempts, 1)
170
+ survivor.destroy()
171
+ })
172
+
173
+ test("a drop between OPEN and OPENED resumes the same stream ID safely", async (t) => {
174
+ /** @type {Duplex | undefined} */
175
+ let delayedTarget
176
+ const relay = await startMultiplexedTcpRelay({
177
+ tokens: tokens(),
178
+ authorizeTarget: () => true,
179
+ createTargetConnection: () => {
180
+ delayedTarget = new Duplex({
181
+ read() {},
182
+ write(_chunk, _encoding, callback) { callback() }
183
+ })
184
+ return /** @type {import('node:net').Socket} */ (/** @type {unknown} */ (delayedTarget))
185
+ }
186
+ })
187
+ t.after(() => relay.close())
188
+ const connection = new MultiplexedSocketductConnection({
189
+ relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"},
190
+ reconnectDelayMs: 30
191
+ })
192
+ t.after(() => connection.close())
193
+ const socket = connection.createConnection({host: "target.internal", port: 1234})
194
+ const connected = onceEvent(socket, "connect")
195
+ await waitUntil(() => delayedTarget !== undefined)
196
+ await relay.closeActiveTransport()
197
+ delayedTarget?.emit("connect")
198
+ await connected
199
+ assert.equal(socket.streamId, 1)
200
+ assert.equal(connection.status().reconnectAttempts, 1)
201
+ assert.equal(relay.status().resumeSuccesses, 1)
202
+ socket.destroy()
203
+ })
204
+
205
+ test("bounded per-stream and aggregate queues fail only the offending stream", async (t) => {
206
+ const target = createServer((socket) => socket.pause())
207
+ const targetPort = await listen(target)
208
+ t.after(() => closeServer(target))
209
+ const relay = await startMultiplexedTcpRelay({tokens: tokens(), authorizeTarget: ({target: requested}) => requested.port === targetPort})
210
+ t.after(() => relay.close())
211
+ const connection = new MultiplexedSocketductConnection({
212
+ relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"},
213
+ maxStreamBufferBytes: 32,
214
+ maxConnectionBufferBytes: 48,
215
+ maxFrameBytes: 16,
216
+ initialWindowBytes: 32
217
+ })
218
+ t.after(() => connection.close())
219
+ const offender = connection.createConnection({host: "127.0.0.1", port: targetPort})
220
+ const sibling = connection.createConnection({host: "127.0.0.1", port: targetPort})
221
+ await Promise.all([onceEvent(offender, "connect"), onceEvent(sibling, "connect")])
222
+ const errorPromise = nextError(offender)
223
+ offender.write(Buffer.alloc(33))
224
+ const error = await errorPromise
225
+ assert.equal(error.code, "SOCKETDUCT_BUFFER_LIMIT")
226
+ assert.equal(sibling.destroyed, false)
227
+ assert(connection.status().dataQueuedBytes <= 48)
228
+ assert(connection.status().controlQueuedBytes <= 1024 * 1024)
229
+ sibling.destroy()
230
+ })
231
+
232
+ test("round-robin data scheduling lets a small control flow pass a large upload", async (t) => {
233
+ let largeBytesObserved = 0
234
+ let largeCompleted = false
235
+ let smallObservedAfterLargeCompleted
236
+ const largeTarget = createServer((socket) => socket.on("data", (chunk) => {
237
+ largeBytesObserved += chunk.length
238
+ if (largeBytesObserved === 64 * 1024) largeCompleted = true
239
+ }))
240
+ const largePort = await listen(largeTarget)
241
+ t.after(() => closeServer(largeTarget))
242
+ const smallTarget = createServer((socket) => socket.on("data", (chunk) => {
243
+ smallObservedAfterLargeCompleted = largeCompleted
244
+ socket.write(chunk)
245
+ }))
246
+ const smallPort = await listen(smallTarget)
247
+ t.after(() => closeServer(smallTarget))
248
+ const relay = await startMultiplexedTcpRelay({
249
+ tokens: tokens(),
250
+ authorizeTarget: ({target}) => target.port === largePort || target.port === smallPort,
251
+ maxFrameBytes: 1024
252
+ })
253
+ t.after(() => relay.close())
254
+ const connection = new MultiplexedSocketductConnection({
255
+ relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"},
256
+ maxFrameBytes: 1024
257
+ })
258
+ t.after(() => connection.close())
259
+ const large = connection.createConnection({host: "127.0.0.1", port: largePort})
260
+ const small = connection.createConnection({host: "127.0.0.1", port: smallPort})
261
+ await Promise.all([onceEvent(large, "connect"), onceEvent(small, "connect")])
262
+ const smallResponse = readText(small)
263
+ large.write(Buffer.alloc(64 * 1024))
264
+ small.write("control")
265
+ assert.equal(await smallResponse, "control")
266
+ assert.equal(smallObservedAfterLargeCompleted, false)
267
+ assert(largeBytesObserved < 64 * 1024)
268
+ large.destroy()
269
+ small.destroy()
270
+ })
271
+
272
+ test("per-stream receive windows bound a paused consumer and resume independently", async (t) => {
273
+ const payload = Buffer.alloc(4 * 1024, 0x61)
274
+ const target = createServer((socket) => socket.write(payload))
275
+ const targetPort = await listen(target)
276
+ t.after(() => closeServer(target))
277
+ const relay = await startMultiplexedTcpRelay({
278
+ tokens: tokens(),
279
+ authorizeTarget: ({target: requested}) => requested.port === targetPort,
280
+ initialWindowBytes: 1024,
281
+ maxFrameBytes: 512,
282
+ maxStreamBufferBytes: 8 * 1024
283
+ })
284
+ t.after(() => relay.close())
285
+ const connection = new MultiplexedSocketductConnection({
286
+ relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"},
287
+ initialWindowBytes: 1024,
288
+ maxFrameBytes: 512
289
+ })
290
+ t.after(() => connection.close())
291
+ const paused = connection.createConnection({
292
+ host: "127.0.0.1",
293
+ port: targetPort,
294
+ readableHighWaterMark: 256
295
+ })
296
+ await onceEvent(paused, "connect")
297
+ await waitUntil(() => relay.status().queuedBytes === payload.length)
298
+ assert.equal(relay.status().resets, 0)
299
+ assert(relay.status().queuedBytes <= 8 * 1024)
300
+
301
+ const received = await new Promise((resolve, reject) => {
302
+ /** @type {Buffer[]} */
303
+ const chunks = []
304
+ let bytes = 0
305
+ paused.on("data", (chunk) => {
306
+ chunks.push(chunk)
307
+ bytes += chunk.length
308
+ if (bytes === payload.length) resolve(Buffer.concat(chunks))
309
+ })
310
+ paused.once("error", reject)
311
+ paused.resume()
312
+ })
313
+ assert.deepEqual(received, payload)
314
+ await waitUntil(() => relay.status().queuedBytes === 0)
315
+ paused.destroy()
316
+ })
317
+
318
+ test("relay accepts local target sockets without creating extra WAN transports", async (t) => {
319
+ const target = createServer((socket) => socket.end())
320
+ const port = await listen(target)
321
+ t.after(() => closeServer(target))
322
+ let localConnects = 0
323
+ const relay = await startMultiplexedTcpRelay({
324
+ tokens: tokens(),
325
+ authorizeTarget: ({target: requested}) => requested.port === port,
326
+ createTargetConnection: (requested) => {
327
+ localConnects += 1
328
+ return createConnection(requested)
329
+ }
330
+ })
331
+ t.after(() => relay.close())
332
+ const connection = new MultiplexedSocketductConnection({relay: {host: "127.0.0.1", port: relay.port, token: "valid-token"}})
333
+ t.after(() => connection.close())
334
+ const streams = Array.from({length: 4}, () => connection.createConnection({host: "127.0.0.1", port}))
335
+ await Promise.all(streams.map((socket) => onceEvent(socket, "connect")))
336
+ assert.equal(localConnects, 4)
337
+ assert.equal(relay.status().physicalAccepts, 1)
338
+ streams.forEach((socket) => socket.destroy())
339
+ })