{"_id":"@antseed/relay","_rev":"2-172bcc20504411d3179ea0af0ddb89f1","name":"@antseed/relay","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@antseed/relay","version":"0.1.0","_id":"@antseed/relay@0.1.0","maintainers":[{"name":"shahafan","email":"santwarg@gmail.com"}],"dist":{"shasum":"d13af14cea72e235b9cfc4d71fcccf8f1965889e","tarball":"https://registry.npmjs.org/@antseed/relay/-/relay-0.1.0.tgz","fileCount":19,"integrity":"sha512-Lxr9uwYEv++LBvGUj6cR0fvK4ZhZ1Md/o3SQ3WYz69XZwyLXeAG3mF+1j5vgMbQFq4MOkaoNF7JtLU7Yi/gxQw==","signatures":[{"sig":"MEUCIARWZ9jdM7rH87CdOL881fRDNTnPRVWhRo1V1cEmYQ97AiEAmLUmdB+0gdC848Ozwqor1iMplG7/miFBcPwEJw0BwqM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":90951},"main":"dist/index.js","type":"module","_from":"file:antseed-relay-0.1.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"scripts":{"test":"vitest run","build":"tsc","start":"node dist/index.js","prebuild":"node ../../scripts/remove-paths.mjs dist","typecheck":"tsc --noEmit"},"_npmUser":{"name":"shahafan","email":"santwarg@gmail.com"},"_resolved":"/tmp/201d62c751050e351957fbd013dca7eb/antseed-relay-0.1.0.tgz","_integrity":"sha512-Lxr9uwYEv++LBvGUj6cR0fvK4ZhZ1Md/o3SQ3WYz69XZwyLXeAG3mF+1j5vgMbQFq4MOkaoNF7JtLU7Yi/gxQw==","_npmVersion":"10.8.2","description":"Web relay for AntSeed: seller discovery cache + WebSocket-to-TCP signaling bridge for browser buyers","directories":{},"_nodeVersion":"20.20.2","dependencies":{"ws":"^8.18.0"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.0.0","@types/ws":"^8.5.12","typescript":"^5.5.0","@types/node":"^20.11.0","@antseed/node":"0.2.114"},"peerDependencies":{"@antseed/node":">=0.1.0"},"_npmOperationalInternal":{"tmp":"tmp/relay_0.1.0_1787134527600_0.3735383082379089","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@antseed/relay","version":"0.1.1","description":"Web relay for AntSeed: seller discovery cache + WebSocket-to-TCP signaling bridge for browser buyers","type":"module","engines":{"node":">=20.0.0"},"main":"dist/index.js","dependencies":{"ws":"^8.18.0"},"peerDependencies":{"@antseed/node":">=0.1.0"},"devDependencies":{"@types/node":"^20.11.0","@types/ws":"^8.5.12","typescript":"^5.5.0","vitest":"^2.0.0","@antseed/node":"0.2.115"},"scripts":{"prebuild":"node ../../scripts/remove-paths.mjs dist","build":"tsc","start":"node dist/index.js","test":"vitest run","typecheck":"tsc --noEmit"},"_id":"@antseed/relay@0.1.1","types":"./dist/index.d.ts","_integrity":"sha512-NWcu8OODteJLglf2sTX1QIiKd6nCS1Pfahn3RTGWJAzrWwGlJiY/uI0SVpE+VkAJ1gY+0iFca1B8qgo+lUmXvg==","_resolved":"/tmp/734e4f66b0bcd62d4f91d23c5b611348/antseed-relay-0.1.1.tgz","_from":"file:antseed-relay-0.1.1.tgz","_nodeVersion":"20.20.2","_npmVersion":"10.8.2","dist":{"integrity":"sha512-NWcu8OODteJLglf2sTX1QIiKd6nCS1Pfahn3RTGWJAzrWwGlJiY/uI0SVpE+VkAJ1gY+0iFca1B8qgo+lUmXvg==","shasum":"e6a1b871e09a1792f106ce841c2f493031f4a69e","tarball":"https://registry.npmjs.org/@antseed/relay/-/relay-0.1.1.tgz","fileCount":19,"unpackedSize":90951,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFq6ARxysuSvXvfHJYA+GMpMljZt2uANRjL6kaA4mQkTAiAu97Tz0rU/4lCWwNekL/SqCUZ7/Sm170ioDWuoeagxUg=="}]},"_npmUser":{"name":"shahafan","email":"santwarg@gmail.com"},"directories":{},"maintainers":[{"name":"shahafan","email":"santwarg@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/relay_0.1.1_1787228354621_0.0028756696787226588"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-19T10:15:27.361Z","modified":"2026-08-20T12:19:14.932Z","0.1.0":"2026-08-19T10:15:27.757Z","0.1.1":"2026-08-20T12:19:14.767Z"},"description":"Web relay for AntSeed: seller discovery cache + WebSocket-to-TCP signaling bridge for browser buyers","maintainers":[{"name":"shahafan","email":"santwarg@gmail.com"}],"readme":"# @antseed/relay\n\nWeb relay for browser buyers. Two jobs:\n\n1. **Seller cache** — periodically discovers sellers over the DHT (same flow\n   as network-stats) and serves the snapshot at `GET /sellers` (CORS `*`).\n2. **Signaling bridge** — `WS /bridge/<peerId>` pipes bytes verbatim to that\n   seller's TCP signaling port. The bridge never parses traffic; connection\n   auth is end-to-end (the buyer signs its own hello envelope). It only dials\n   endpoints present in the cache, and because discovered `publicAddress`\n   values are attacker-announced, it refuses to dial loopback, private,\n   link-local, and CGNAT ranges for non-static sellers (checked for IP\n   literals at upgrade time and again at DNS resolution, so rebinding does\n   not bypass it). Static sellers are operator-configured and exempt.\n\nBrowsers cannot open raw TCP sockets or join the UDP DHT — this service is\nthe minimal shim that lets a browser reach an **unmodified** seller. The\nWebRTC DataChannel itself is established directly between browser and seller;\nthe relay only carries signaling.\n\nThe service exposes `GET /healthz` for process liveness, `GET /readyz` for\nseller-cache freshness, and `GET /metrics` for aggregate JSON counters. The\nmetrics contain no client IPs, seller IDs, SDP, or ICE data.\n\n## Run\n\n```bash\npnpm --filter=@antseed/relay run build\nnode apps/relay/dist/index.js\n```\n\n## Configuration (env)\n\n| Variable | Default | Meaning |\n|---|---|---|\n| `RELAY_PORT` | `8917` | HTTP/WS listen port |\n| `RELAY_HOST` | `0.0.0.0` | Bind address |\n| `RELAY_DISABLE_DHT` | unset | `1` disables DHT discovery (static-only) |\n| `RELAY_POLL_MS` | `300000` | DHT poll interval |\n| `RELAY_STATIC_SELLERS` | empty | `peerId@host:port,...` extra sellers (dev/e2e) |\n| `RELAY_MAX_BRIDGES_PER_IP` | `16` | Concurrent bridges per client IP |\n| `RELAY_MAX_BRIDGES_GLOBAL` | `1024` | Global concurrent signaling bridges |\n| `RELAY_MAX_BRIDGES_PER_SELLER` | `8` | Concurrent bridges to one seller |\n| `RELAY_MAX_PAYLOAD_BYTES` | `1048576` | Maximum WebSocket signaling message size |\n| `RELAY_TCP_CONNECT_TIMEOUT_MS` | `10000` | Seller dial timeout |\n| `RELAY_IDLE_TIMEOUT_MS` | `600000` | Idle bridge teardown |\n| `RELAY_READINESS_MAX_AGE_MS` | `900000` | Maximum DHT snapshot age accepted by `/readyz` |\n| `RELAY_ALLOWED_ORIGINS` | empty | Optional comma-separated browser Origin allowlist |\n| `RELAY_TRUST_PROXY` | unset | `1` when behind a TLS terminator: per-IP limits use the last `X-Forwarded-For` entry |\n\nProduction note: browsers require `wss://` from HTTPS pages — terminate TLS\nin front of the relay (ALB, nginx, Caddy). Set `RELAY_TRUST_PROXY=1` there,\notherwise every client shares the proxy's IP and one per-IP bridge cap.\nSet `RELAY_ALLOWED_ORIGINS` for a product-specific deployment; it reduces\ndrive-by browser use but is not authentication because non-browser clients can\nforge `Origin`.\n","readmeFilename":"README.md"}