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,82 @@
1
+ import {createServer} from "node:net"
2
+
3
+ import {SocketductSocket} from "../client/socketduct-socket.js"
4
+
5
+ /**
6
+ * @typedef {import('../spool/spool-file.js').SessionSpool} SessionSpool
7
+ * @typedef {import('../client/socketduct-socket.js').SocketductTlsOptions} SocketductTlsOptions
8
+ * @typedef {object} TcpEdgeOptions
9
+ * @property {string} [host] - Edge listen host.
10
+ * @property {number} [port] - Edge listen port.
11
+ * @property {string} relayHost - Relay host.
12
+ * @property {number} relayPort - Relay port.
13
+ * @property {string} token - Auth token.
14
+ * @property {string} sessionName - Human-readable session name.
15
+ * @property {{host: string, port: number}} target - Target requested for this session.
16
+ * @property {SessionSpool} spool - Spool storage.
17
+ * @property {number} [reconnectDelayMs] - Reconnect delay.
18
+ * @property {SocketductTlsOptions} [tls] - TLS client options.
19
+ * @typedef {object} TcpEdgeHandle
20
+ * @property {import('node:net').Server} server - Edge server.
21
+ * @property {number} port - Bound edge port.
22
+ * @property {() => Promise<void>} close - Close function.
23
+ */
24
+
25
+ /**
26
+ * @param {TcpEdgeOptions} options - Edge options.
27
+ * @returns {Promise<TcpEdgeHandle>} - Started edge server.
28
+ */
29
+ export async function startTcpEdge(options) {
30
+ /** @type {Set<SocketductSocket>} */
31
+ const connections = new Set()
32
+ const server = createServer((appSocket) => {
33
+ const socketductSocket = new SocketductSocket(options)
34
+ connections.add(socketductSocket)
35
+
36
+ const closeBoth = () => {
37
+ connections.delete(socketductSocket)
38
+ socketductSocket.destroy()
39
+ appSocket.destroy()
40
+ }
41
+
42
+ appSocket.on("close", closeBoth)
43
+ appSocket.on("error", closeBoth)
44
+ socketductSocket.on("close", closeBoth)
45
+ socketductSocket.on("error", closeBoth)
46
+
47
+ appSocket.pipe(socketductSocket).pipe(appSocket)
48
+ socketductSocket.connect()
49
+ })
50
+
51
+ await new Promise((resolve, reject) => {
52
+ server.once("error", reject)
53
+ server.listen(options.port ?? 0, options.host ?? "127.0.0.1", () => {
54
+ server.off("error", reject)
55
+ resolve(undefined)
56
+ })
57
+ })
58
+
59
+ const address = server.address()
60
+ if (address === null || typeof address === "string") {
61
+ throw new Error("Edge did not bind to a TCP port")
62
+ }
63
+
64
+ return {
65
+ server,
66
+ port: address.port,
67
+ close: async () => {
68
+ for (const connection of connections) {
69
+ connection.destroy()
70
+ }
71
+ await new Promise((resolve, reject) => {
72
+ server.close((error) => {
73
+ if (error) {
74
+ reject(error)
75
+ return
76
+ }
77
+ resolve(undefined)
78
+ })
79
+ })
80
+ }
81
+ }
82
+ }
package/src/index.js ADDED
@@ -0,0 +1,101 @@
1
+ import {loadEnvironmentConfig} from "./config/environment.js"
2
+ import {createJsonlFileLogger} from "./logging/structured-logger.js"
3
+ import {startRelayRuntime} from "./server/relay-runtime.js"
4
+ import {startOutboundReverseGatewayRuntime, startReverseHubRuntime} from "./reverse/runtime.js"
5
+
6
+ export {createSocketductConnectionFactory} from "./client/connect.js"
7
+ export {createJsonlFileLogger, createStructuredLogger} from "./logging/structured-logger.js"
8
+ export {SocketductHttpAgent} from "./client/http-agent.js"
9
+ export {connectSocketduct, SocketductRelayError, SocketductSocket} from "./client/socketduct-socket.js"
10
+ export {createMultiplexedSocketductConnection, MultiplexedSocketductConnection} from "./client/multiplexed-connection.js"
11
+ export {createMultiplexedSocketductTransportSet, MultiplexedSocketductTransportSet, SocketductTransportSetLimitError, SocketductTransportSetNotReadyError, SocketductTransportSetTerminalError} from "./client/multiplexed-transport-set.js"
12
+ export {SocketductSessionLostError, SocketductStreamError, VirtualSocket} from "./client/virtual-socket.js"
13
+ export {createOutboundReverseGateway, createReverseGatewayConnection, OutboundReverseGateway, ReverseGatewayConnection, startReverseGatewayHub} from "./reverse/index.js"
14
+
15
+ /**
16
+ * @typedef {object} StartupSummary
17
+ * @property {'Socketduct'} name - name value.
18
+ * @property {'node'} runtime - runtime value.
19
+ * @property {'single-worker'} mode - mode value.
20
+ */
21
+
22
+ /** @returns {StartupSummary} - Startup summary. */
23
+ export function getStartupSummary() {
24
+ return {
25
+ name: "Socketduct",
26
+ runtime: "node",
27
+ mode: "single-worker"
28
+ }
29
+ }
30
+
31
+ /**
32
+ * @returns {Promise<void>} Startup completion promise.
33
+ */
34
+ async function main() {
35
+ const config = loadEnvironmentConfig()
36
+ const logger = config.fileLogging === undefined ? undefined : createJsonlFileLogger({...config.fileLogging, role: config.mode})
37
+ if (config.mode === "summary") {
38
+ const summary = getStartupSummary()
39
+ console.log(`${summary.name} starting in ${summary.mode} mode on ${summary.runtime}`)
40
+ logger?.log("process.started", {runtimeMode: config.mode})
41
+ return
42
+ }
43
+
44
+ /** @type {{status: () => object, port?: number}} */
45
+ let service
46
+ /** @type {() => Promise<void>} */
47
+ let closeRuntime
48
+ if (config.mode === "relay") {
49
+ const runtime = await startRelayRuntime(config)
50
+ service = runtime.relay
51
+ closeRuntime = runtime.close
52
+ } else if (config.mode === "reverse-hub") {
53
+ const runtime = await startReverseHubRuntime(config)
54
+ service = runtime.hub
55
+ closeRuntime = runtime.close
56
+ } else {
57
+ const runtime = await startOutboundReverseGatewayRuntime(config, logger)
58
+ service = runtime.gateway
59
+ closeRuntime = runtime.close
60
+ }
61
+ const tlsStatus = config.mode === "relay" && config.relayTls === undefined ? "plaintext" : "TLS"
62
+ console.log(JSON.stringify({
63
+ event: `${config.mode}.started`,
64
+ ...(config.mode === "reverse-gateway" ? {gatewayId: config.reverseGatewayId} : {host: config.relayHost, port: service.port}),
65
+ transportSecurity: tlsStatus,
66
+ compression: config.mode.startsWith("reverse-") ? "zstd" : config.compression
67
+ }))
68
+ logger?.log("process.started", {
69
+ runtimeMode: config.mode,
70
+ transportSecurity: tlsStatus,
71
+ compression: config.mode.startsWith("reverse-") ? "zstd" : config.compression
72
+ })
73
+ const statusIntervalMs = config.statusIntervalMs ?? 30_000
74
+ const statusTimer = statusIntervalMs > 0
75
+ ? setInterval(() => {
76
+ const status = service.status()
77
+ console.log(JSON.stringify({event: `${config.mode}.status`, ...status}))
78
+ logger?.log("runtime.status", /** @type {Record<string, unknown>} */ (status))
79
+ }, statusIntervalMs)
80
+ : undefined
81
+ statusTimer?.unref()
82
+
83
+ const shutdown = () => {
84
+ if (statusTimer !== undefined) clearInterval(statusTimer)
85
+ closeRuntime()
86
+ .then(() => process.exit(0))
87
+ .catch((error) => {
88
+ console.error(error)
89
+ process.exit(1)
90
+ })
91
+ }
92
+ process.once("SIGINT", shutdown)
93
+ process.once("SIGTERM", shutdown)
94
+ }
95
+
96
+ if (import.meta.url === `file://${process.argv[1]}`) {
97
+ main().catch((error) => {
98
+ console.error(error)
99
+ process.exit(1)
100
+ })
101
+ }