@seedprotocol/gateway-hyper 0.7.10-next.1353.g74aeeed → 0.7.10

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"src-DHEkxp5F.js","names":["URL","fs","path","HyperDHT","ID","http","URL$1","net","toHex","buf","fromHex","hex","encodePublicKeyZ32","publicKey","decodePublicKey","trimmed","key","loadOrCreateOperatorKeypair","resolved","keyFile","raw","parsed","dir","kp","persisted","toOperatorKeypair","keyPair","secretKey","resolveClientKeypair","options","createEphemeralClientKeypair","MAX_FRAME_BYTES","WRITE_CHUNK_BYTES","StreamByteReader","stream","chunk","err","length","resolve","waiters","w","next","out","readers","getReader","reader","readExact","writeBuffer","cleanup","onDrain","onError","onClose","reject","assertFrameSize","writeFrame","payload","header","first","offset","readFrame","writeTunnelRequest","body","meta","readTunnelRequest","metaBuf","writeTunnelResponse","readTunnelResponse","flattenRequestHeaders","value","headers","filterResponseHeaders","skip","TunnelTimeoutError","timeoutMs","TunnelConnectTimeoutError","UpstreamTimeoutError","DEFAULT_UPSTREAM_TIMEOUT_MS","withHeaders","extra","names","HOP_BY_HOP_HEADERS","upstreamRequestHeaders","named","token","host","lower","proxyTunnelRequest","base","upstreamBase","targetUrl","req","observer","res","chunks","received","ipFamily","addr","parseIps","list","entry","entries","bits","family","prefix","max","parseKeys","keys","normalizeHost","mapped","cleanEntries","createAllowlist","keyEntries","ipEntries","ips","enabled","keyAllowed","ipAllowed","remotePublicKey","safeEmitter","onEvent","event","errorFields","error","code","createIdSource","run","streams","requests","errorResponse","message","serveTunnelConnection","emit","streamId","nextRequestId","requestIdHeader","closed","onGone","socket","opened","bytesIn","firstByteAt","inflight","pendingRequest","requestId","t0","upstreamStart","phase","upstreamMs","result","upstream","serveTunnel","allowlist","keypair","ids","dht","server","refuse","verdict","RAW_CLOSE_GRACE_MS","errorLabel","side","pipeRaw","graceMs","started","hyper","tcp","closeOther","other","label","DEFAULT_RAW_OPERATOR_KEY_FILE","parseTcpUpstream","text","url","defaultPort","port","serveRawTunnel","open","connections","conn","DEFAULT_REQUEST_TIMEOUT_MS","DEFAULT_CONNECT_TIMEOUT_MS","DEFAULT_MAX_STREAMS","abortError","reason","signal","raceAbort","onAbort","promise","createTunnelQueue","runGuarded","opts","requestTimeoutMs","getSocket","settled","timer","armTimer","abandon","fn","tunnelQueue","closedError","createTunnelPool","maxStreams","slots","waiting","addSlot","slot","openStream","takeSlot","idle","s","release","acquire","index","waiter","createTunnelSession","clientKey","connectTimeoutMs","pool","fail","onReady","runOnTunnel","DEFAULT_RAW_PORT","DEFAULT_MAX_RAW_CONNECTIONS","connectRawTunnel","maxConnections","pipe","address","stripMountPath","urlPath","qIndex","pathname","query","mount","mountPath","rest","readNodeBody","tunnelErrorStatus","tunnelErrorMessage","forwardNodeRequest","controller","responseMeta","responseBody","session","writeNodeResponse","forwardFetchRequest","request","bodyBuf","headerRecord","connectTunnel","boundPort","baseUrl","localGatewayUrl","resolveProxyKey","createGatewayProxy","starting","ensureSession","proxy","DEFAULT_SEED_GATEWAY_HYPER_KEY"],"sources":["../src/keys.ts","../src/tunnel/protocol.ts","../src/tunnel/errors.ts","../src/tunnel/proxy.ts","../src/tunnel/allowlist.ts","../src/tunnel/events.ts","../src/tunnel/serve.ts","../src/tunnel/rawPipe.ts","../src/tunnel/rawServe.ts","../src/tunnel/session.ts","../src/tunnel/rawConnect.ts","../src/tunnel/mountPath.ts","../src/tunnel/forward.ts","../src/tunnel/connect.ts","../src/tunnel/proxyApp.ts","../src/constants.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport HyperDHT from 'hyperdht'\nimport ID from 'hypercore-id-encoding'\nimport type { OperatorKeyPair } from './types'\n\nexport interface PersistedKeyFile {\n publicKey: string\n secretKey: string\n encoding: 'hex'\n}\n\nfunction toHex(buf: Buffer): string {\n return buf.toString('hex')\n}\n\nfunction fromHex(hex: string): Buffer {\n return Buffer.from(hex, 'hex')\n}\n\nexport function encodePublicKeyZ32(publicKey: Buffer): string {\n return ID.encode(publicKey)\n}\n\nexport function decodePublicKey(key: string): Buffer {\n const trimmed = key.trim()\n if (!trimmed) {\n throw new Error('Gateway Hyper key is empty')\n }\n try {\n return Buffer.from(ID.decode(trimmed))\n } catch {\n if (/^[a-fA-F0-9]{64}$/.test(trimmed)) {\n return fromHex(trimmed)\n }\n throw new Error(`Invalid Gateway Hyper key (expected z32 or 64-char hex): ${trimmed.slice(0, 12)}…`)\n }\n}\n\nexport function loadOrCreateOperatorKeypair(keyFile: string): OperatorKeyPair {\n const resolved = path.resolve(keyFile)\n if (fs.existsSync(resolved)) {\n const raw = fs.readFileSync(resolved, 'utf8')\n const parsed = JSON.parse(raw) as PersistedKeyFile\n if (!parsed.publicKey || !parsed.secretKey) {\n throw new Error(`Invalid key file at ${resolved}: missing publicKey or secretKey`)\n }\n const publicKey = fromHex(parsed.publicKey)\n const secretKey = fromHex(parsed.secretKey)\n return {\n publicKey,\n secretKey,\n z32: encodePublicKeyZ32(publicKey),\n }\n }\n\n const dir = path.dirname(resolved)\n fs.mkdirSync(dir, { recursive: true })\n const kp = HyperDHT.keyPair()\n const persisted: PersistedKeyFile = {\n publicKey: toHex(kp.publicKey),\n secretKey: toHex(kp.secretKey),\n encoding: 'hex',\n }\n fs.writeFileSync(resolved, `${JSON.stringify(persisted, null, 2)}\\n`, { mode: 0o600 })\n return {\n publicKey: kp.publicKey,\n secretKey: kp.secretKey,\n z32: encodePublicKeyZ32(kp.publicKey),\n }\n}\n\n/** Check a keypair passed in code and add its z32 key. */\nexport function toOperatorKeypair(keyPair: { publicKey: Buffer; secretKey: Buffer }): OperatorKeyPair {\n const { publicKey, secretKey } = keyPair\n if (publicKey?.length !== 32 || secretKey?.length !== 64) {\n throw new Error('Invalid keyPair: expected a 32-byte publicKey and a 64-byte secretKey')\n }\n // An Ed25519 secret key in libsodium form is the seed followed by the public key.\n if (!Buffer.from(secretKey.subarray(32)).equals(Buffer.from(publicKey))) {\n throw new Error('Invalid keyPair: publicKey does not belong to secretKey')\n }\n return { publicKey, secretKey, z32: encodePublicKeyZ32(publicKey) }\n}\n\n/**\n * The keypair a client presents to the operator: `keyPair` if given, else the one in\n * `keyFile` (created on first use), else a new one for this run only.\n */\nexport function resolveClientKeypair(options: {\n keyFile?: string\n keyPair?: { publicKey: Buffer; secretKey: Buffer }\n}): OperatorKeyPair {\n if (options.keyFile && options.keyPair) {\n throw new Error('Pass keyFile or keyPair, not both')\n }\n if (options.keyPair) return toOperatorKeypair(options.keyPair)\n if (options.keyFile) return loadOrCreateOperatorKeypair(options.keyFile)\n return createEphemeralClientKeypair()\n}\n\nexport function createEphemeralClientKeypair(): OperatorKeyPair {\n const kp = HyperDHT.keyPair()\n return {\n publicKey: kp.publicKey,\n secretKey: kp.secretKey,\n z32: encodePublicKeyZ32(kp.publicKey),\n }\n}\n","import type { Readable, Writable } from 'node:stream'\nimport type { TunnelMeta, TunnelResponseMeta } from '../types'\n\n/** Maximum single frame payload (64 MiB). */\nexport const MAX_FRAME_BYTES = 64 * 1024 * 1024\n\n/** Largest single stream write. Secret-stream's limit is 16 MiB - 1. */\nexport const WRITE_CHUNK_BYTES = 1024 * 1024\n\ntype ByteWaiter = {\n length: number\n resolve: (buf: Buffer) => void\n reject: (err: Error) => void\n}\n\n/**\n * Per-stream byte buffer. HyperDHT duplexes do not reliably support `unshift`,\n * so we accumulate and slice locally instead of pushing leftovers back.\n *\n * Once the stream ends, errors or closes, every pending and later read that the\n * buffer cannot satisfy rejects. A destroyed stream emits `close` without `end`,\n * so listening for `end` alone would leave reads waiting forever.\n */\nclass StreamByteReader {\n private buffer = Buffer.alloc(0)\n private waiters: ByteWaiter[] = []\n private failure: Error | null = null\n\n constructor(stream: Readable) {\n stream.on('data', (chunk: Buffer) => {\n this.buffer = Buffer.concat([this.buffer, chunk])\n this.drain()\n })\n stream.on('error', (err: Error) => this.fail(err))\n stream.on('end', () => this.fail(new Error('Unexpected end of stream')))\n stream.on('close', () => this.fail(new Error('Stream closed')))\n }\n\n readExact(length: number): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n if (length === 0) {\n resolve(Buffer.alloc(0))\n return\n }\n this.waiters.push({ length, resolve, reject })\n this.drain()\n this.rejectWaiters()\n })\n }\n\n private fail(err: Error): void {\n this.failure ??= err\n this.drain()\n this.rejectWaiters()\n }\n\n private rejectWaiters(): void {\n if (!this.failure || this.waiters.length === 0) return\n const waiters = this.waiters.splice(0)\n for (const w of waiters) {\n w.reject(\n new Error(\n `${this.failure.message} (expected ${w.length} bytes, got ${this.buffer.length})`,\n ),\n )\n }\n }\n\n private drain(): void {\n while (this.waiters.length > 0) {\n const next = this.waiters[0]\n if (!next || this.buffer.length < next.length) break\n this.waiters.shift()\n const out = this.buffer.subarray(0, next.length)\n this.buffer = this.buffer.subarray(next.length)\n next.resolve(out)\n }\n }\n}\n\nconst readers = new WeakMap<Readable, StreamByteReader>()\n\nfunction getReader(stream: Readable): StreamByteReader {\n let reader = readers.get(stream)\n if (!reader) {\n reader = new StreamByteReader(stream)\n readers.set(stream, reader)\n }\n return reader\n}\n\nfunction readExact(stream: Readable, length: number): Promise<Buffer> {\n return getReader(stream).readExact(length)\n}\n\nfunction writeBuffer(stream: Writable, buf: Buffer): Promise<void> {\n return new Promise((resolve, reject) => {\n if (stream.destroyed) {\n reject(new Error('Stream closed'))\n return\n }\n // HyperDHT duplexes often never invoke write(callback). Use return value + drain.\n try {\n const ok = stream.write(buf)\n if (ok) {\n resolve()\n return\n }\n const cleanup = () => {\n stream.off('drain', onDrain)\n stream.off('error', onError)\n stream.off('close', onClose)\n }\n const onDrain = () => {\n cleanup()\n resolve()\n }\n const onError = (err: Error) => {\n cleanup()\n reject(err)\n }\n const onClose = () => {\n cleanup()\n reject(new Error('Stream closed'))\n }\n stream.once('drain', onDrain)\n stream.once('error', onError)\n stream.once('close', onClose)\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)))\n }\n })\n}\n\nfunction assertFrameSize(length: number): void {\n if (length > MAX_FRAME_BYTES) {\n throw new Error(`Frame too large: ${length} bytes (max ${MAX_FRAME_BYTES})`)\n }\n}\n\nexport async function writeFrame(stream: Writable, payload: Buffer): Promise<void> {\n assertFrameSize(payload.length)\n const header = Buffer.alloc(4)\n header.writeUInt32BE(payload.length, 0)\n // The header goes out with the first chunk. Secret-stream rejects any single write over\n // 16 MiB, so larger payloads are split, waiting for drain between chunks.\n const first = payload.subarray(0, WRITE_CHUNK_BYTES)\n await writeBuffer(stream, Buffer.concat([header, first]))\n for (let offset = first.length; offset < payload.length; offset += WRITE_CHUNK_BYTES) {\n await writeBuffer(stream, payload.subarray(offset, offset + WRITE_CHUNK_BYTES))\n }\n}\n\nexport async function readFrame(stream: Readable): Promise<Buffer> {\n const header = await readExact(stream, 4)\n const length = header.readUInt32BE(0)\n if (length === 0) {\n return Buffer.alloc(0)\n }\n assertFrameSize(length)\n return readExact(stream, length)\n}\n\nexport async function writeTunnelRequest(\n stream: Writable,\n meta: TunnelMeta,\n body: Buffer,\n): Promise<void> {\n // Check the body before writing meta so an oversized body leaves the stream in sync.\n assertFrameSize(body.length)\n const metaFrame = Buffer.from(JSON.stringify(meta), 'utf8')\n await writeFrame(stream, metaFrame)\n await writeFrame(stream, body)\n}\n\nexport async function readTunnelRequest(\n stream: Readable,\n): Promise<{ meta: TunnelMeta; body: Buffer }> {\n const metaBuf = await readFrame(stream)\n const meta = JSON.parse(metaBuf.toString('utf8')) as TunnelMeta\n if (!meta.method || !meta.path) {\n throw new Error('Invalid tunnel request meta: missing method or path')\n }\n meta.headers = meta.headers ?? {}\n const body = await readFrame(stream)\n return { meta, body }\n}\n\nexport async function writeTunnelResponse(\n stream: Writable,\n meta: TunnelResponseMeta,\n body: Buffer,\n): Promise<void> {\n // Check the body before writing meta so an oversized body leaves the stream in sync.\n assertFrameSize(body.length)\n const metaFrame = Buffer.from(JSON.stringify(meta), 'utf8')\n await writeFrame(stream, metaFrame)\n await writeFrame(stream, body)\n}\n\nexport async function readTunnelResponse(\n stream: Readable,\n): Promise<{ meta: TunnelResponseMeta; body: Buffer }> {\n const metaBuf = await readFrame(stream)\n const meta = JSON.parse(metaBuf.toString('utf8')) as TunnelResponseMeta\n if (typeof meta.status !== 'number') {\n throw new Error('Invalid tunnel response meta: missing status')\n }\n meta.headers = meta.headers ?? {}\n const body = await readFrame(stream)\n return { meta, body }\n}\n\n/** Normalize header values to strings for tunnel transport. */\nexport function flattenRequestHeaders(\n headers: Record<string, string | string[] | undefined>,\n): Record<string, string> {\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (value == null) continue\n out[key] = Array.isArray(value) ? value.join(', ') : value\n }\n return out\n}\n\n/** Pick response headers safe to forward to the local HTTP client. */\nexport function filterResponseHeaders(\n headers: Record<string, string | string[] | undefined>,\n): Record<string, string> {\n const skip = new Set([\n 'connection',\n 'keep-alive',\n 'transfer-encoding',\n 'upgrade',\n ])\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (skip.has(key.toLowerCase())) continue\n if (value == null) continue\n out[key] = Array.isArray(value) ? value.join(', ') : value\n }\n return out\n}\n","/** Client side: the tunnel stream was idle longer than `requestTimeoutMs`, so the request was abandoned. */\nexport class TunnelTimeoutError extends Error {\n readonly code = 'TUNNEL_TIMEOUT'\n\n constructor(readonly timeoutMs: number) {\n super(`No bytes sent or received on the tunnel for ${timeoutMs} ms`)\n this.name = 'TunnelTimeoutError'\n }\n}\n\n/** Client side: the HyperDHT connection to the operator did not open within `connectTimeoutMs`. */\nexport class TunnelConnectTimeoutError extends Error {\n readonly code = 'TUNNEL_CONNECT_TIMEOUT'\n\n constructor(readonly timeoutMs: number) {\n super(`Could not connect to the operator within ${timeoutMs} ms`)\n this.name = 'TunnelConnectTimeoutError'\n }\n}\n\n/** Operator side: the upstream socket was idle longer than `upstreamTimeoutMs`. */\nexport class UpstreamTimeoutError extends Error {\n readonly code = 'UPSTREAM_TIMEOUT'\n\n constructor(readonly timeoutMs: number) {\n super(`No response from upstream for ${timeoutMs} ms`)\n this.name = 'UpstreamTimeoutError'\n }\n}\n","import http from 'node:http'\nimport { URL } from 'node:url'\nimport { UpstreamTimeoutError } from './errors'\nimport { filterResponseHeaders, MAX_FRAME_BYTES } from './protocol'\nimport type { UpstreamPhase } from './events'\nimport type { TunnelMeta } from '../types'\n\n/** Default idle limit on the operator's upstream socket. */\nexport const DEFAULT_UPSTREAM_TIMEOUT_MS = 30_000\n\nexport type ProxyTunnelRequestOptions = {\n /** Fail with `UpstreamTimeoutError` once the upstream socket is idle this long. 0 disables. */\n timeoutMs?: number\n /** Abort the upstream request, e.g. when the tunnel stream closes. */\n signal?: AbortSignal\n /** Headers set on the upstream request, replacing any the client sent under the same name. */\n extraHeaders?: Record<string, string>\n /** Progress callbacks, for tracing. */\n observer?: ProxyObserver\n}\n\nexport type ProxyObserver = {\n /** The request moved to `phase`: `send` from the start, `wait` once the body is sent, `body` on headers. */\n onPhase?: (phase: UpstreamPhase) => void\n onResponse?: (status: number) => void\n onDone?: (status: number, bytes: number) => void\n}\n\nfunction withHeaders(\n headers: Record<string, string>,\n extra: Record<string, string> | undefined,\n): Record<string, string> {\n if (!extra) return headers\n const names = new Set(Object.keys(extra).map((key) => key.toLowerCase()))\n const out: Record<string, string> = {}\n for (const [key, value] of Object.entries(headers)) {\n if (!names.has(key.toLowerCase())) out[key] = value\n }\n return { ...out, ...extra }\n}\n\n/** Headers that describe one hop and must not be forwarded (RFC 9110 section 7.6.1). */\nconst HOP_BY_HOP_HEADERS = new Set([\n 'connection',\n 'keep-alive',\n 'proxy-connection',\n 'te',\n 'trailer',\n 'transfer-encoding',\n 'upgrade',\n])\n\n/**\n * Drop hop-by-hop headers and the client's `host`, which names the app server rather than\n * the upstream; it is passed on as `x-forwarded-host`. `content-length` is dropped because\n * Node sets it from the buffered body.\n */\nexport function upstreamRequestHeaders(headers: Record<string, string>): Record<string, string> {\n const named = new Set(\n (Object.entries(headers).find(([key]) => key.toLowerCase() === 'connection')?.[1] ?? '')\n .split(',')\n .map((token) => token.trim().toLowerCase())\n .filter(Boolean),\n )\n const out: Record<string, string> = {}\n let host: string | undefined\n for (const [key, value] of Object.entries(headers)) {\n const lower = key.toLowerCase()\n if (lower === 'host') {\n host = value\n continue\n }\n if (lower === 'content-length' || HOP_BY_HOP_HEADERS.has(lower) || named.has(lower)) continue\n out[key] = value\n }\n if (host && !Object.keys(out).some((key) => key.toLowerCase() === 'x-forwarded-host')) {\n out['x-forwarded-host'] = host\n }\n return out\n}\n\nexport async function proxyTunnelRequest(\n upstreamBase: string,\n meta: TunnelMeta,\n body: Buffer,\n options: ProxyTunnelRequestOptions = {},\n): Promise<{ status: number; headers: Record<string, string>; body: Buffer }> {\n const base = upstreamBase.replace(/\\/$/, '')\n const targetUrl = new URL(meta.path, `${base}/`)\n const timeoutMs = options.timeoutMs ?? DEFAULT_UPSTREAM_TIMEOUT_MS\n const { observer } = options\n\n return new Promise((resolve, reject) => {\n observer?.onPhase?.('send')\n const req = http.request(\n targetUrl,\n {\n method: meta.method,\n headers: withHeaders(upstreamRequestHeaders(meta.headers), options.extraHeaders),\n signal: options.signal,\n },\n (res) => {\n if (observer) {\n observer.onPhase?.('body')\n observer.onResponse?.(res.statusCode ?? 502)\n }\n const chunks: Buffer[] = []\n let received = 0\n res.on('data', (chunk: Buffer) => {\n received += chunk.length\n if (received > MAX_FRAME_BYTES) {\n req.destroy(\n new Error(`Upstream response exceeds ${MAX_FRAME_BYTES} bytes, the tunnel's limit`),\n )\n return\n }\n chunks.push(chunk)\n })\n res.on('end', () => {\n observer?.onDone?.(res.statusCode ?? 502, received)\n resolve({\n status: res.statusCode ?? 502,\n headers: filterResponseHeaders(res.headers as Record<string, string | string[] | undefined>),\n body: Buffer.concat(chunks),\n })\n })\n res.on('error', reject)\n },\n )\n if (timeoutMs > 0) {\n req.setTimeout(timeoutMs, () => req.destroy(new UpstreamTimeoutError(timeoutMs)))\n }\n req.on('error', reject)\n if (observer?.onPhase) req.once('finish', () => observer.onPhase?.('wait'))\n if (body.length > 0) {\n req.end(body)\n } else {\n req.end()\n }\n })\n}\n","import net from 'node:net'\nimport { decodePublicKey } from '../keys'\n\nexport type AllowlistOptions = {\n /** Client public keys (z32 or hex) */\n keys?: string[]\n /** Client IPs or CIDR ranges, IPv4 or IPv6 */\n ips?: string[]\n}\n\n/** How a stream was let in, or `none` when it was refused. */\nexport type AllowedBy = 'open' | 'key' | 'ip' | 'none'\n\nexport type AllowlistVerdict = {\n ok: boolean\n /** The UDP address the connection uses, IPv4-mapped prefix removed */\n host: string | null\n by: AllowedBy\n}\n\n/** The parts of a HyperDHT stream the allowlist reads. */\nexport type AllowlistStream = {\n remotePublicKey?: Uint8Array | null\n rawStream?: { remoteHost?: string | null } | null\n}\n\nexport type Allowlist = {\n /** False when neither list has entries, so every client is let in. */\n enabled: boolean\n /** One line for logs, e.g. `keys=2 ips=192.168.1.0/24` or `off`. */\n summary: string\n /**\n * For HyperDHT's `firewall` server option: true refuses the client before it gets a stream.\n * Refuses only on the key, and only when no IP list is set, since the client's real address\n * is not known until the connection is up.\n */\n firewall: (remotePublicKey: Uint8Array) => boolean\n /** Check an established stream by key, then by address. */\n check: (stream: AllowlistStream) => AllowlistVerdict\n}\n\nfunction ipFamily(addr: string): 'ipv4' | 'ipv6' | null {\n if (net.isIPv4(addr)) return 'ipv4'\n if (net.isIPv6(addr)) return 'ipv6'\n return null\n}\n\nfunction parseIps(entries: string[]): net.BlockList {\n const list = new net.BlockList()\n for (const entry of entries) {\n const [addr = '', bits, extra] = entry.split('/')\n const family = ipFamily(addr)\n if (!family || extra !== undefined) {\n throw new Error(`Invalid allowlist IP (expected an address or CIDR): ${entry}`)\n }\n if (bits === undefined) {\n list.addAddress(addr, family)\n continue\n }\n const prefix = Number(bits)\n const max = family === 'ipv4' ? 32 : 128\n if (!/^\\d+$/.test(bits) || prefix > max) {\n throw new Error(`Invalid allowlist CIDR prefix (expected 0–${max}): ${entry}`)\n }\n list.addSubnet(addr, prefix, family)\n }\n return list\n}\n\nfunction parseKeys(entries: string[]): Set<string> {\n const keys = new Set<string>()\n for (const entry of entries) {\n keys.add(decodePublicKey(entry).toString('hex'))\n }\n return keys\n}\n\n/** Strip the IPv4-mapped IPv6 prefix so `::ffff:1.2.3.4` matches an IPv4 entry. */\nfunction normalizeHost(host: string | null | undefined): string | null {\n if (!host) return null\n const mapped = host.toLowerCase().startsWith('::ffff:') ? host.slice(7) : null\n return mapped && net.isIPv4(mapped) ? mapped : host\n}\n\nfunction cleanEntries(entries: string[] | undefined): string[] {\n return (entries ?? []).map((entry) => entry.trim()).filter(Boolean)\n}\n\n/**\n * Decide which HyperDHT clients may use a tunnel. A client is let in when its public key or\n * its address is listed; with both lists empty, every client is. Invalid entries throw.\n *\n * The address is the UDP address the connection uses. A client on the operator's LAN\n * connects over the LAN and shows a private address, and a connection relayed through\n * another HyperDHT node shows the relay's address, so it is refused unless its key is listed.\n */\nexport function createAllowlist(options: AllowlistOptions = {}): Allowlist {\n const keyEntries = cleanEntries(options.keys)\n const ipEntries = cleanEntries(options.ips)\n const keys = parseKeys(keyEntries)\n const ips = parseIps(ipEntries)\n const enabled = keys.size > 0 || ipEntries.length > 0\n\n const keyAllowed = (publicKey: Uint8Array | null | undefined): boolean =>\n publicKey != null && keys.has(Buffer.from(publicKey).toString('hex'))\n\n const ipAllowed = (host: string | null): boolean => {\n if (!host) return false\n const family = ipFamily(host)\n return family !== null && ips.check(host, family)\n }\n\n return {\n enabled,\n summary: enabled ? `keys=${keys.size} ips=${ipEntries.join(',') || 'none'}` : 'off',\n firewall: (remotePublicKey) => {\n if (!enabled || ipEntries.length > 0) return false\n return !keyAllowed(remotePublicKey)\n },\n check: (stream) => {\n const host = normalizeHost(stream.rawStream?.remoteHost)\n if (!enabled) return { ok: true, host, by: 'open' }\n if (keyAllowed(stream.remotePublicKey)) return { ok: true, host, by: 'key' }\n if (ipAllowed(host)) return { ok: true, host, by: 'ip' }\n return { ok: false, host, by: 'none' }\n },\n }\n}\n","import type { AllowedBy } from './allowlist'\n\n/** Where an upstream request was when it failed. */\nexport type UpstreamPhase = 'send' | 'wait' | 'body'\n\n/**\n * Events from `serveTunnel`'s `onEvent`. Streams are `s<N>`; requests carry the id that\n * `requestIdHeader` sends upstream. Times are milliseconds; `totalMs` and `receiveMs` count\n * from the request's first byte on the stream.\n */\nexport type TunnelEvent =\n | {\n name: 'stream.open'\n stream: string\n /** Client public key (z32) */\n remoteKey: string\n /** UDP address the connection uses */\n host: string | null\n by: AllowedBy\n }\n | {\n name: 'stream.refused'\n remoteKey: string\n /**\n * `handshake`: refused by key in HyperDHT's firewall, before a stream exists, so the\n * client's address is unknown and `host` is null. `connection`: refused once connected.\n */\n stage: 'handshake' | 'connection'\n host: string | null\n }\n | {\n name: 'stream.close'\n stream: string\n requests: number\n bytesIn: number\n durationMs: number\n /** The request in flight when the stream closed, if any */\n inflight: string | null\n }\n | { name: 'stream.error'; stream: string; error: string; code?: string }\n | {\n name: 'req.recv'\n request: string\n stream: string\n method: string\n path: string\n bodyBytes: number\n /** From the request's first byte to its last */\n receiveMs: number\n }\n | { name: 'up.response'; request: string; status: number; upstreamMs: number }\n | { name: 'up.done'; request: string; status: number; bytes: number; upstreamMs: number }\n | {\n name: 'up.error'\n request: string\n phase: UpstreamPhase\n error: string\n code?: string\n /** True when the tunnel stream closed and the operator aborted the upstream request */\n aborted: boolean\n }\n | { name: 'up.timeout'; request: string; phase: UpstreamPhase; timeoutMs: number }\n | { name: 'res.sent'; request: string; status: number; bytes: number; totalMs: number }\n\nexport type TunnelEventName = TunnelEvent['name']\n\n/** Called synchronously; a throw is caught and dropped so tracing can't break the tunnel. */\nexport type TunnelEventListener = (event: TunnelEvent) => void\n\n/** Bytes are counted from the HTTP client's side: up is toward the upstream. */\ntype RawCloseFields = {\n conn: string\n bytesUp: number\n bytesDown: number\n durationMs: number\n /** First error on either side, as `hyper:<code>`, `tcp:<code>` or `connect-timeout` */\n error: string | null\n}\n\n/** Events from `serveRawTunnel`'s `onEvent`. Connections are `c<N>`. */\nexport type RawTunnelEvent =\n | {\n name: 'conn.open'\n conn: string\n remoteKey: string\n host: string | null\n by: AllowedBy\n }\n | Extract<TunnelEvent, { name: 'stream.refused' }>\n | ({ name: 'conn.close' } & RawCloseFields)\n\nexport type RawTunnelEventListener = (event: RawTunnelEvent) => void\n\n/** Events from `connectRawTunnel`'s `onEvent`, one TCP connection each. */\nexport type RawClientEvent =\n | { name: 'conn.open'; conn: string; connectMs: number }\n /** A TCP connection refused because `maxConnections` were already open */\n | { name: 'conn.refused'; open: number; max: number }\n | ({ name: 'conn.close' } & RawCloseFields)\n\nexport type RawClientEventListener = (event: RawClientEvent) => void\n\n/** Wrap `onEvent` so a throw is dropped; undefined when there is no listener. */\nexport function safeEmitter<E>(onEvent: ((event: E) => void) | undefined): ((event: E) => void) | undefined {\n if (!onEvent) return undefined\n return (event) => {\n try {\n onEvent(event)\n } catch {\n // Tracing must not break the tunnel.\n }\n }\n}\n\nexport function errorFields(err: unknown): { error: string; code?: string } {\n const error = err instanceof Error ? err.message : String(err)\n const code = (err as { code?: unknown } | null)?.code\n return typeof code === 'string' ? { error, code } : { error }\n}\n\n/** A run-unique prefix, so request ids from different operator runs never collide. */\nexport function createIdSource(): { stream: () => string; request: () => string } {\n const run = Date.now().toString(36)\n let streams = 0\n let requests = 0\n return {\n stream: () => `s${++streams}`,\n request: () => `${run}-r${++requests}`,\n }\n}\n","import type { Duplex } from 'node:stream'\nimport HyperDHT from 'hyperdht'\nimport { readTunnelRequest, writeTunnelResponse } from './protocol'\nimport { UpstreamTimeoutError } from './errors'\nimport { proxyTunnelRequest } from './proxy'\nimport { createAllowlist } from './allowlist'\nimport {\n createIdSource,\n errorFields,\n safeEmitter,\n type TunnelEventListener,\n type UpstreamPhase,\n} from './events'\nimport { encodePublicKeyZ32, loadOrCreateOperatorKeypair } from '../keys'\nimport type { ServeTunnelOptions, ServeTunnelResult } from '../types'\n\nexport type TunnelConnectionOptions = {\n /** Idle limit on each upstream request. Default 30 000; 0 disables. */\n upstreamTimeoutMs?: number\n /** Receives this stream's events; see `TunnelEvent`. */\n onEvent?: TunnelEventListener\n /** Send each request's id upstream under this header. */\n requestIdHeader?: string\n /** Id for this stream in events. Default `s0`. */\n streamId?: string\n /** Makes request ids. Default: run-unique ids from `createIdSource`. */\n nextRequestId?: () => string\n}\n\nfunction errorResponse(\n status: number,\n message: string,\n): { status: number; headers: Record<string, string>; body: Buffer } {\n return {\n status,\n headers: { 'content-type': 'text/plain; charset=utf-8' },\n body: Buffer.from(message, 'utf8'),\n }\n}\n\n/**\n * Answer framed requests on one tunnel stream, in order, until it closes. Upstream timeouts\n * become 504 and other upstream failures 502, so the stream stays usable; only a broken\n * stream ends the loop. The stream ending or closing aborts the upstream request in flight.\n *\n * Without `onEvent` or `requestIdHeader`, no tracing state is kept and no listeners are added.\n */\nexport async function serveTunnelConnection(\n socket: Duplex,\n upstream: string,\n options: TunnelConnectionOptions = {},\n): Promise<void> {\n const emit = safeEmitter(options.onEvent)\n const { requestIdHeader } = options\n const streamId = options.streamId ?? 's0'\n const nextRequestId =\n emit || requestIdHeader ? (options.nextRequestId ?? createIdSource().request) : null\n\n // Clients never half-close, so `end` means the peer is gone, same as `close` or `error`.\n const closed = new AbortController()\n const onGone = () => closed.abort()\n socket.once('end', onGone)\n socket.once('close', onGone)\n socket.on('error', onGone)\n\n // Tracing state, kept only when someone listens.\n const opened = performance.now()\n let requests = 0\n let bytesIn = 0\n let firstByteAt: number | null = null\n let inflight: string | null = null\n if (emit) {\n // A second data listener beside the frame reader's: byte counts, and when each request's\n // first byte arrived, so receive time leaves out idle time between requests.\n socket.on('data', (chunk: Buffer) => {\n bytesIn += chunk.length\n firstByteAt ??= performance.now()\n })\n socket.on('error', (err: unknown) => emit({ name: 'stream.error', stream: streamId, ...errorFields(err) }))\n socket.once('close', () =>\n emit({\n name: 'stream.close',\n stream: streamId,\n requests,\n bytesIn,\n durationMs: Math.round(performance.now() - opened),\n inflight,\n }),\n )\n }\n\n // Attach framed readers synchronously so HyperDHT early bytes are not lost.\n let pendingRequest = readTunnelRequest(socket)\n try {\n while (true) {\n const { meta, body } = await pendingRequest\n const requestId = nextRequestId?.() ?? null\n const t0 = firstByteAt ?? performance.now()\n firstByteAt = null\n let upstreamStart = 0\n let phase: UpstreamPhase = 'send'\n if (emit && requestId) {\n requests++\n inflight = requestId\n emit({\n name: 'req.recv',\n request: requestId,\n stream: streamId,\n method: meta.method,\n path: meta.path,\n bodyBytes: body.length,\n receiveMs: Math.round(performance.now() - t0),\n })\n upstreamStart = performance.now()\n }\n const upstreamMs = () => Math.round(performance.now() - upstreamStart)\n\n let result: { status: number; headers: Record<string, string>; body: Buffer }\n try {\n result = await proxyTunnelRequest(upstream, meta, body, {\n timeoutMs: options.upstreamTimeoutMs,\n signal: closed.signal,\n extraHeaders:\n requestIdHeader && requestId ? { [requestIdHeader]: requestId } : undefined,\n observer:\n emit && requestId\n ? {\n onPhase: (next) => {\n // `finish` can land after the response started; keep the later phase.\n if (!(next === 'wait' && phase === 'body')) phase = next\n },\n onResponse: (status) =>\n emit({ name: 'up.response', request: requestId, status, upstreamMs: upstreamMs() }),\n onDone: (status, bytes) =>\n emit({ name: 'up.done', request: requestId, status, bytes, upstreamMs: upstreamMs() }),\n }\n : undefined,\n })\n } catch (err) {\n if (emit && requestId) {\n emit(\n err instanceof UpstreamTimeoutError && !closed.signal.aborted\n ? { name: 'up.timeout', request: requestId, phase, timeoutMs: err.timeoutMs }\n : {\n name: 'up.error',\n request: requestId,\n phase,\n ...errorFields(err),\n aborted: closed.signal.aborted,\n },\n )\n }\n if (closed.signal.aborted) return\n const message = err instanceof Error ? err.message : String(err)\n result =\n err instanceof UpstreamTimeoutError\n ? errorResponse(504, `Gateway Hyper upstream timeout: ${message}`)\n : errorResponse(502, `Gateway Hyper upstream error: ${message}`)\n }\n await writeTunnelResponse(\n socket,\n { status: result.status, headers: result.headers },\n result.body,\n )\n if (emit && requestId) {\n inflight = null\n emit({\n name: 'res.sent',\n request: requestId,\n status: result.status,\n bytes: result.body.length,\n totalMs: Math.round(performance.now() - t0),\n })\n }\n pendingRequest = readTunnelRequest(socket)\n }\n } catch {\n socket.destroy()\n }\n}\n\n/**\n * Operator: accept Hyperswarm/DHT connections and proxy framed HTTP to upstream Traefik.\n */\nexport async function serveTunnel(options: ServeTunnelOptions): Promise<ServeTunnelResult> {\n const upstream = options.upstream.replace(/\\/$/, '')\n const keyFile = options.keyFile ?? '.seed/gateway-tunnel/operator.key.json'\n const allowlist = createAllowlist(options.allowlist)\n const keypair = loadOrCreateOperatorKeypair(keyFile)\n const emit = safeEmitter(options.onEvent)\n const ids = createIdSource()\n\n const dht = new HyperDHT(options.bootstrap ? { bootstrap: options.bootstrap } : {})\n const server = dht.createServer(\n {\n firewall: (remotePublicKey) => {\n const refuse = allowlist.firewall(remotePublicKey)\n if (refuse && emit) {\n emit({\n name: 'stream.refused',\n remoteKey: encodePublicKeyZ32(remotePublicKey),\n stage: 'handshake',\n host: null,\n })\n }\n return refuse\n },\n },\n (socket) => {\n const verdict = allowlist.check(socket)\n // Refuse before serveTunnelConnection attaches a reader, so nothing is read.\n if (!verdict.ok) {\n emit?.({\n name: 'stream.refused',\n remoteKey: encodePublicKeyZ32(socket.remotePublicKey),\n stage: 'connection',\n host: verdict.host,\n })\n socket.on('error', () => {})\n socket.destroy()\n return\n }\n const streamId = ids.stream()\n emit?.({\n name: 'stream.open',\n stream: streamId,\n remoteKey: encodePublicKeyZ32(socket.remotePublicKey),\n host: verdict.host,\n by: verdict.by,\n })\n void serveTunnelConnection(socket, upstream, {\n upstreamTimeoutMs: options.upstreamTimeoutMs,\n onEvent: options.onEvent,\n requestIdHeader: options.requestIdHeader,\n streamId,\n nextRequestId: ids.request,\n })\n },\n )\n\n await server.listen(keypair)\n\n return {\n key: keypair.z32,\n upstream,\n close: async () => {\n await server.close()\n await dht.destroy()\n },\n }\n}\n","import type net from 'node:net'\nimport type { Duplex } from 'node:stream'\n\n/** How long a side may stay open after the other closed cleanly before it is destroyed. */\nexport const RAW_CLOSE_GRACE_MS = 30_000\n\nexport type RawPipeResult = {\n /** Bytes read from the TCP socket and sent over the HyperDHT stream */\n tcpBytesRead: number\n /** Bytes from the HyperDHT stream written to the TCP socket */\n tcpBytesWritten: number\n durationMs: number\n /** First error on either side, as `hyper:<code>` or `tcp:<code>`, or null */\n error: string | null\n}\n\nexport type RawPipe = {\n /** Record an error that is about to destroy one side, e.g. a connect timeout. */\n fail: (label: string) => void\n}\n\nfunction errorLabel(side: 'hyper' | 'tcp', err: unknown): string {\n const code = (err as { code?: unknown } | null)?.code\n const message = err instanceof Error ? err.message : String(err)\n return `${side}:${typeof code === 'string' ? code : message}`\n}\n\n/**\n * Copy bytes both ways between a HyperDHT stream and a TCP socket until both close.\n *\n * HyperDHT → TCP is forwarded by hand with backpressure, not with `stream.pipe(socket)`:\n * streamx's pipe destroys its source with PREMATURE_CLOSE when the destination closes first,\n * so the far end would see a reset even though every byte arrived. TCP → HyperDHT uses Node's\n * `pipe`, which has no such problem. Half-closes pass through, so an HTTP connection ends the\n * way its client or server ended it; `tcp` must be created with `allowHalfOpen: true` for that.\n *\n * When one side closes cleanly, the other is ended so data already written still arrives,\n * and destroyed only if it is still open `graceMs` later. After an error on either side, the\n * other is destroyed at once.\n */\nexport function pipeRaw(\n hyper: Duplex,\n tcp: net.Socket,\n options: { graceMs?: number; onClose?: (result: RawPipeResult) => void } = {},\n): RawPipe {\n const graceMs = options.graceMs ?? RAW_CLOSE_GRACE_MS\n const started = performance.now()\n let error: string | null = null\n let closed = 0\n\n hyper.on('data', (chunk: Buffer) => {\n if (!tcp.write(chunk)) hyper.pause()\n })\n tcp.on('drain', () => hyper.resume())\n hyper.on('end', () => tcp.end())\n tcp.pipe(hyper)\n\n hyper.on('error', (err: unknown) => {\n error ??= errorLabel('hyper', err)\n })\n tcp.on('error', (err: unknown) => {\n error ??= errorLabel('tcp', err)\n })\n\n const closeOther = (other: Duplex) => () => {\n if (other.destroyed) return\n if (error) {\n other.destroy()\n return\n }\n other.end()\n setTimeout(() => other.destroy(), graceMs).unref()\n }\n const onClose = () => {\n if (++closed < 2) return\n options.onClose?.({\n tcpBytesRead: tcp.bytesRead,\n tcpBytesWritten: tcp.bytesWritten,\n durationMs: Math.round(performance.now() - started),\n error,\n })\n }\n hyper.on('close', closeOther(tcp))\n tcp.on('close', closeOther(hyper))\n hyper.on('close', onClose)\n tcp.on('close', onClose)\n\n return {\n fail: (label) => {\n error ??= label\n },\n }\n}\n","import net from 'node:net'\nimport type { Duplex } from 'node:stream'\nimport HyperDHT from 'hyperdht'\nimport { createAllowlist } from './allowlist'\nimport { safeEmitter } from './events'\nimport { pipeRaw } from './rawPipe'\nimport { encodePublicKeyZ32, loadOrCreateOperatorKeypair, toOperatorKeypair } from '../keys'\nimport type { ServeRawTunnelOptions, ServeRawTunnelResult } from '../types'\n\n/** Default operator key file for raw mode, apart from the framed tunnel's key. */\nexport const DEFAULT_RAW_OPERATOR_KEY_FILE = '.seed/gateway-tunnel/raw-operator.key.json'\n\n/** `http://host:port`, `https://host`, or `host:port`, as a TCP address. */\nexport function parseTcpUpstream(upstream: string): { host: string; port: number } {\n const text = upstream.trim()\n const url = new URL(/^[a-z][a-z0-9+.-]*:\\/\\//i.test(text) ? text : `tcp://${text}`)\n const defaultPort = url.protocol === 'http:' ? 80 : url.protocol === 'https:' ? 443 : NaN\n const port = url.port ? Number(url.port) : defaultPort\n const host = url.hostname.replace(/^\\[(.*)\\]$/, '$1')\n if (!host || !Number.isInteger(port)) {\n throw new Error(`Invalid raw upstream (expected http://host:port or host:port): ${upstream}`)\n }\n return { host, port }\n}\n\n/**\n * Operator, raw mode: pipe each HyperDHT stream byte for byte to a new TCP connection to the\n * upstream. There is no framing, so HTTP/1.1 runs end to end between the client's HTTP client\n * and the upstream: keep-alive, streamed bodies of any size, `Range` and WebSockets all work.\n *\n * Clients must use `connectRawTunnel`; a framed `connectTunnel` client can't talk to this\n * server, so it listens on its own key.\n */\nexport async function serveRawTunnel(options: ServeRawTunnelOptions): Promise<ServeRawTunnelResult> {\n const upstream = parseTcpUpstream(options.upstream)\n const allowlist = createAllowlist(options.allowlist)\n const keypair = options.keyPair\n ? toOperatorKeypair(options.keyPair)\n : loadOrCreateOperatorKeypair(options.keyFile ?? DEFAULT_RAW_OPERATOR_KEY_FILE)\n const emit = safeEmitter(options.onEvent)\n const open = new Set<Duplex>()\n let connections = 0\n\n const dht = new HyperDHT(options.bootstrap ? { bootstrap: options.bootstrap } : {})\n const server = dht.createServer(\n {\n firewall: (remotePublicKey) => {\n const refuse = allowlist.firewall(remotePublicKey)\n if (refuse && emit) {\n emit({\n name: 'stream.refused',\n remoteKey: encodePublicKeyZ32(remotePublicKey),\n stage: 'handshake',\n host: null,\n })\n }\n return refuse\n },\n },\n (stream) => {\n const verdict = allowlist.check(stream)\n if (!verdict.ok) {\n emit?.({\n name: 'stream.refused',\n remoteKey: encodePublicKeyZ32(stream.remotePublicKey),\n stage: 'connection',\n host: verdict.host,\n })\n stream.on('error', () => {})\n stream.destroy()\n return\n }\n const conn = `c${++connections}`\n emit?.({\n name: 'conn.open',\n conn,\n remoteKey: encodePublicKeyZ32(stream.remotePublicKey),\n host: verdict.host,\n by: verdict.by,\n })\n // allowHalfOpen, so an upstream that half-closes can still read what the client sends.\n const socket = net.connect({ host: upstream.host, port: upstream.port, allowHalfOpen: true })\n socket.setNoDelay(true)\n open.add(stream)\n open.add(socket)\n pipeRaw(stream, socket, {\n graceMs: options.closeGraceMs,\n onClose: (result) => {\n open.delete(stream)\n open.delete(socket)\n emit?.({\n name: 'conn.close',\n conn,\n bytesUp: result.tcpBytesWritten,\n bytesDown: result.tcpBytesRead,\n durationMs: result.durationMs,\n error: result.error,\n })\n },\n })\n },\n )\n\n await server.listen(keypair)\n\n return {\n key: keypair.z32,\n upstream: `${upstream.host}:${upstream.port}`,\n close: async () => {\n for (const side of open) side.destroy()\n await server.close()\n await dht.destroy()\n },\n }\n}\n","import type { Duplex } from 'node:stream'\nimport HyperDHT from 'hyperdht'\nimport { decodePublicKey, resolveClientKeypair } from '../keys'\nimport type { DhtBootstrapNode, TunnelMeta, TunnelResponseMeta } from '../types'\nimport { TunnelConnectTimeoutError, TunnelTimeoutError } from './errors'\nimport { readTunnelResponse, writeTunnelRequest } from './protocol'\n\n/** Default idle limit for one request on the tunnel stream. */\nexport const DEFAULT_REQUEST_TIMEOUT_MS = 60_000\n\n/** Default limit for opening the HyperDHT connection. */\nexport const DEFAULT_CONNECT_TIMEOUT_MS = 30_000\n\n/**\n * Default number of streams a session opens to the operator. Streams open only when every\n * open one is busy, and each costs a DHT lookup and handshake plus an upstream request on\n * the operator, so a small pool keeps that cost down. Four leaves room for a page's burst of\n * gateway reads after one or two requests hang until their timeout.\n */\nexport const DEFAULT_MAX_STREAMS = 4\n\nexport type TunnelSessionOptions = {\n /** Operator public key (z32 or hex) */\n key: string\n /**\n * Keypair file (created on first run if missing) for the key this client presents to the\n * operator. Without `keyFile` or `keyPair`, each session uses a new key.\n */\n keyFile?: string\n /** The client keypair itself. Exclusive with `keyFile`. */\n keyPair?: { publicKey: Buffer; secretKey: Buffer }\n /** Optional store path for DHT client state */\n storePath?: string\n /** HyperDHT bootstrap nodes. Defaults to the public network. */\n bootstrap?: DhtBootstrapNode[]\n /**\n * Abandon a request once the stream has sent and received nothing for this long, then\n * reconnect for the next request. Counts from when the request gets a stream.\n * Default 60 000; 0 disables.\n */\n requestTimeoutMs?: number\n /** Give up opening the HyperDHT connection after this long. Default 30 000; 0 disables. */\n connectTimeoutMs?: number\n /**\n * Most HyperDHT streams open to the operator at once; each carries one request at a time.\n * Default 4.\n */\n maxStreams?: number\n}\n\nexport type TunnelRequestOptions = {\n /** Aborting rejects the request; if it is already on the stream, the stream is destroyed. */\n signal?: AbortSignal\n /** Overrides the session's `requestTimeoutMs` for this request. */\n timeoutMs?: number\n}\n\nexport type TunnelSession = {\n key: string\n /** The public key (z32) this client presents to the operator */\n clientKey: string\n runOnTunnel: <T>(\n fn: (socket: Duplex) => Promise<T>,\n options?: TunnelRequestOptions,\n ) => Promise<T>\n forwardHttp: (\n meta: TunnelMeta,\n body: Buffer,\n options?: TunnelRequestOptions,\n ) => Promise<{ meta: TunnelResponseMeta; body: Buffer }>\n close: () => Promise<void>\n}\n\nfunction abortError(signal: AbortSignal): Error {\n const reason: unknown = signal.reason\n if (reason instanceof Error) return reason\n const err = new Error('The request was aborted')\n err.name = 'AbortError'\n return err\n}\n\n/** Reject as soon as `signal` aborts, even while `promise` is still queued. */\nfunction raceAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {\n if (signal.aborted) return Promise.reject(abortError(signal))\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(abortError(signal))\n signal.addEventListener('abort', onAbort, { once: true })\n promise.then(\n (value) => {\n signal.removeEventListener('abort', onAbort)\n resolve(value)\n },\n (err: unknown) => {\n signal.removeEventListener('abort', onAbort)\n reject(err)\n },\n )\n })\n}\n\n/**\n * Serialize transactions on the socket `getSocket` returns. Each runs under an idle timeout\n * and an optional abort signal; one that is abandoned or fails destroys the socket, so\n * `getSocket` must open a new one once the current one is destroyed.\n */\nexport function createTunnelQueue(\n getSocket: () => Promise<Duplex>,\n requestTimeoutMs: number = DEFAULT_REQUEST_TIMEOUT_MS,\n): TunnelSession['runOnTunnel'] {\n const runGuarded = async <T>(\n fn: (socket: Duplex) => Promise<T>,\n opts: TunnelRequestOptions,\n ): Promise<T> => {\n const { signal } = opts\n const timeoutMs = opts.timeoutMs ?? requestTimeoutMs\n if (signal?.aborted) throw abortError(signal)\n const socket = await getSocket()\n if (signal?.aborted) throw abortError(signal)\n\n return new Promise<T>((resolve, reject) => {\n let settled = false\n let timer: ReturnType<typeof setTimeout> | null = null\n const armTimer = () => {\n if (timeoutMs <= 0) return\n if (timer) clearTimeout(timer)\n timer = setTimeout(() => abandon(new TunnelTimeoutError(timeoutMs)), timeoutMs)\n }\n const cleanup = () => {\n if (timer) clearTimeout(timer)\n socket.off('data', armTimer)\n socket.off('drain', armTimer)\n signal?.removeEventListener('abort', onAbort)\n }\n // A late response would be read as the next request's reply, so the stream goes too.\n const abandon = (err: unknown) => {\n if (settled) return\n settled = true\n cleanup()\n socket.destroy()\n reject(err)\n }\n const onAbort = () => abandon(abortError(signal as AbortSignal))\n\n armTimer()\n // Bytes moving either way keep the request alive, so large bodies are not cut off.\n socket.on('data', armTimer)\n socket.on('drain', armTimer)\n signal?.addEventListener('abort', onAbort, { once: true })\n\n fn(socket).then((value) => {\n if (settled) return\n settled = true\n cleanup()\n resolve(value)\n }, abandon)\n })\n }\n\n let tunnelQueue: Promise<unknown> = Promise.resolve()\n return <T>(\n fn: (socket: Duplex) => Promise<T>,\n opts: TunnelRequestOptions = {},\n ): Promise<T> => {\n const run = tunnelQueue.then(() => runGuarded(fn, opts))\n tunnelQueue = run.catch(() => {})\n return opts.signal ? raceAbort(run, opts.signal) : run\n }\n}\n\ntype PoolSlot = {\n run: TunnelSession['runOnTunnel']\n socket: Duplex | null\n busy: boolean\n}\n\ntype PoolWaiter = {\n resolve: (slot: PoolSlot) => void\n reject: (err: Error) => void\n}\n\nexport type TunnelPoolOptions = {\n /** Most streams open at once. Default `DEFAULT_MAX_STREAMS`. */\n maxStreams?: number\n /** Idle limit for each request, as in `createTunnelQueue`. */\n requestTimeoutMs?: number\n}\n\nexport type TunnelPool = {\n runOnTunnel: TunnelSession['runOnTunnel']\n /** Reject queued requests and destroy every stream. */\n close: () => void\n}\n\nfunction closedError(): Error {\n return new Error('Tunnel session is closed')\n}\n\n/**\n * Run each transaction alone on one of up to `maxStreams` sockets from `openStream`. A request\n * takes an idle open stream if there is one, else opens a new stream while fewer than\n * `maxStreams` exist, else waits for the first stream to come free. Each stream is a\n * `createTunnelQueue`, so a request that is abandoned destroys only its own stream, and that\n * stream reopens for the next request it takes.\n */\nexport function createTunnelPool(\n openStream: () => Promise<Duplex>,\n options: TunnelPoolOptions = {},\n): TunnelPool {\n const maxStreams = Math.max(1, Math.floor(options.maxStreams ?? DEFAULT_MAX_STREAMS))\n const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS\n const slots: PoolSlot[] = []\n const waiting: PoolWaiter[] = []\n let closed = false\n\n const addSlot = (): PoolSlot => {\n const slot: PoolSlot = { run: null as never, socket: null, busy: false }\n // The slot runs one request at a time, so there is never a second open in progress.\n slot.run = createTunnelQueue(async () => {\n if (closed) throw closedError()\n if (slot.socket && !slot.socket.destroyed) return slot.socket\n const socket = await openStream()\n if (closed) {\n socket.destroy()\n throw closedError()\n }\n slot.socket = socket\n return socket\n }, requestTimeoutMs)\n slots.push(slot)\n return slot\n }\n\n const takeSlot = (): PoolSlot | null => {\n const idle = slots.filter((slot) => !slot.busy)\n // An idle slot whose stream died reopens it, same as a new slot would.\n const slot =\n idle.find((s) => s.socket !== null && !s.socket.destroyed) ??\n idle[0] ??\n (slots.length < maxStreams ? addSlot() : undefined)\n if (!slot) return null\n slot.busy = true\n return slot\n }\n\n const release = (slot: PoolSlot) => {\n const next = waiting.shift()\n if (next) next.resolve(slot)\n else slot.busy = false\n }\n\n const acquire = (signal: AbortSignal | undefined): Promise<PoolSlot> => {\n const slot = takeSlot()\n if (slot) return Promise.resolve(slot)\n return new Promise<PoolSlot>((resolve, reject) => {\n const onAbort = () => {\n const index = waiting.indexOf(waiter)\n if (index >= 0) waiting.splice(index, 1)\n reject(abortError(signal as AbortSignal))\n }\n const waiter: PoolWaiter = {\n resolve: (s) => {\n signal?.removeEventListener('abort', onAbort)\n resolve(s)\n },\n reject: (err) => {\n signal?.removeEventListener('abort', onAbort)\n reject(err)\n },\n }\n waiting.push(waiter)\n signal?.addEventListener('abort', onAbort, { once: true })\n })\n }\n\n const runOnTunnel = async <T>(\n fn: (socket: Duplex) => Promise<T>,\n opts: TunnelRequestOptions = {},\n ): Promise<T> => {\n if (opts.signal?.aborted) throw abortError(opts.signal)\n if (closed) throw closedError()\n const slot = await acquire(opts.signal)\n try {\n return await slot.run(fn, opts)\n } finally {\n release(slot)\n }\n }\n\n return {\n runOnTunnel,\n close: () => {\n closed = true\n for (const waiter of waiting.splice(0)) waiter.reject(closedError())\n for (const slot of slots) slot.socket?.destroy()\n },\n }\n}\n\n/**\n * Dial the operator HyperDHT key and expose a request/response forwarder.\n *\n * Requests run on a pool of up to `maxStreams` streams to the operator, one request per\n * stream at a time (see `createTunnelPool`). The framing has no request ids, so a request\n * that is abandoned mid-flight (timeout, abort, or error) destroys its stream; the next\n * request on that stream opens a new one.\n */\nexport async function createTunnelSession(options: TunnelSessionOptions): Promise<TunnelSession> {\n const remotePublicKey = decodePublicKey(options.key)\n const clientKey = resolveClientKeypair(options)\n const connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS\n\n const dht = new HyperDHT(options.bootstrap ? { bootstrap: options.bootstrap } : {})\n\n // Without a `pool` option, each connect is its own handshake and encrypted stream.\n const openStream = () =>\n new Promise<Duplex>((resolve, reject) => {\n const socket = dht.connect(remotePublicKey, {\n keyPair: { publicKey: clientKey.publicKey, secretKey: clientKey.secretKey },\n }) as Duplex\n let settled = false\n const timer =\n connectTimeoutMs > 0\n ? setTimeout(() => fail(new TunnelConnectTimeoutError(connectTimeoutMs)), connectTimeoutMs)\n : null\n const fail = (err: Error) => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n socket.destroy()\n reject(err)\n }\n const onReady = () => {\n if (settled) return\n settled = true\n if (timer) clearTimeout(timer)\n resolve(socket)\n }\n // Keep a listener for the socket's whole life so a late error never goes unhandled;\n // the frame reader sees errors through its own listener.\n socket.on('error', (err: Error) => fail(err))\n socket.once('close', () => fail(new Error('Tunnel connection closed before it opened')))\n socket.once('open', onReady)\n socket.once('connect', onReady)\n })\n\n const pool = createTunnelPool(openStream, {\n maxStreams: options.maxStreams,\n requestTimeoutMs: options.requestTimeoutMs,\n })\n const { runOnTunnel } = pool\n\n const forwardHttp = async (\n meta: TunnelMeta,\n body: Buffer,\n opts?: TunnelRequestOptions,\n ): Promise<{ meta: TunnelResponseMeta; body: Buffer }> => {\n return runOnTunnel(async (socket) => {\n await writeTunnelRequest(socket, meta, body)\n return readTunnelResponse(socket)\n }, opts)\n }\n\n return {\n key: options.key,\n clientKey: clientKey.z32,\n runOnTunnel,\n forwardHttp,\n close: async () => {\n pool.close()\n await dht.destroy()\n },\n }\n}\n","import net from 'node:net'\nimport HyperDHT from 'hyperdht'\nimport { decodePublicKey, resolveClientKeypair } from '../keys'\nimport { safeEmitter } from './events'\nimport { TunnelConnectTimeoutError } from './errors'\nimport { pipeRaw } from './rawPipe'\nimport { DEFAULT_CONNECT_TIMEOUT_MS } from './session'\nimport type { ConnectRawTunnelOptions, ConnectRawTunnelResult } from '../types'\n\n/** Default local port for `connectRawTunnel`, next to the framed sidecar's 1984. */\nexport const DEFAULT_RAW_PORT = 1985\n\n/** Default limit on TCP connections open through one raw client at once. */\nexport const DEFAULT_MAX_RAW_CONNECTIONS = 64\n\n/**\n * Client, raw mode: listen on a local TCP port and carry each connection over its own\n * HyperDHT stream to a `serveRawTunnel` operator, which pipes it to its upstream.\n *\n * Each connection costs a DHT lookup and handshake (about 100–400 ms), so put an HTTP client\n * that keeps connections alive in front, such as nginx with `keepalive` on its upstream.\n */\nexport async function connectRawTunnel(options: ConnectRawTunnelOptions): Promise<ConnectRawTunnelResult> {\n const remotePublicKey = decodePublicKey(options.key)\n const clientKey = resolveClientKeypair(options)\n const host = options.host ?? '127.0.0.1'\n const maxConnections = options.maxConnections ?? DEFAULT_MAX_RAW_CONNECTIONS\n const connectTimeoutMs = options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS\n const emit = safeEmitter(options.onEvent)\n const open = new Set<net.Socket>()\n let connections = 0\n\n const dht = new HyperDHT(options.bootstrap ? { bootstrap: options.bootstrap } : {})\n\n // allowHalfOpen: a client that half-closes after its request still gets the response.\n // Node's default would end our side as soon as the client's side ended.\n const server = net.createServer({ allowHalfOpen: true }, (socket) => {\n if (open.size >= maxConnections) {\n emit?.({ name: 'conn.refused', open: open.size, max: maxConnections })\n socket.destroy()\n return\n }\n const conn = `c${++connections}`\n const started = performance.now()\n open.add(socket)\n socket.setNoDelay(true)\n\n const stream = dht.connect(remotePublicKey, {\n keyPair: { publicKey: clientKey.publicKey, secretKey: clientKey.secretKey },\n })\n let timer: ReturnType<typeof setTimeout> | null = null\n const pipe = pipeRaw(stream, socket, {\n graceMs: options.closeGraceMs,\n onClose: (result) => {\n if (timer) clearTimeout(timer)\n open.delete(socket)\n emit?.({\n name: 'conn.close',\n conn,\n bytesUp: result.tcpBytesRead,\n bytesDown: result.tcpBytesWritten,\n durationMs: result.durationMs,\n error: result.error,\n })\n },\n })\n // A refused or unreachable operator can leave the stream opening forever.\n if (connectTimeoutMs > 0) {\n timer = setTimeout(() => {\n pipe.fail('connect-timeout')\n stream.destroy(new TunnelConnectTimeoutError(connectTimeoutMs))\n }, connectTimeoutMs)\n }\n stream.once('open', () => {\n if (timer) clearTimeout(timer)\n emit?.({ name: 'conn.open', conn, connectMs: Math.round(performance.now() - started) })\n })\n })\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(options.port ?? DEFAULT_RAW_PORT, host, () => resolve())\n })\n const address = server.address()\n const port = address && typeof address === 'object' ? address.port : (options.port ?? DEFAULT_RAW_PORT)\n\n return {\n key: options.key,\n clientKey: clientKey.z32,\n host,\n port,\n close: async () => {\n for (const socket of open) socket.destroy()\n await new Promise<void>((resolve) => server.close(() => resolve()))\n await dht.destroy()\n },\n }\n}\n","/**\n * Strip a mount path prefix from an incoming request URL path (including query string).\n * If the path is already unmounted (framework stripped it), returns it unchanged (with leading `/`).\n */\nexport function stripMountPath(urlPath: string, mountPath: string): string {\n const raw = urlPath || '/'\n const qIndex = raw.indexOf('?')\n const pathname = qIndex === -1 ? raw : raw.slice(0, qIndex)\n const query = qIndex === -1 ? '' : raw.slice(qIndex)\n\n const mount = (mountPath || '').replace(/\\/$/, '')\n if (!mount || mount === '/') {\n const path = pathname.startsWith('/') ? pathname : `/${pathname}`\n return `${path}${query}` || '/'\n }\n\n if (pathname === mount || pathname.startsWith(`${mount}/`)) {\n const rest = pathname.slice(mount.length)\n const path = rest.startsWith('/') ? rest : rest ? `/${rest}` : '/'\n return `${path}${query}`\n }\n\n // Already stripped by the framework (e.g. Express mount)\n const path = pathname.startsWith('/') ? pathname : `/${pathname}`\n return `${path}${query}`\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport {\n filterResponseHeaders,\n flattenRequestHeaders,\n} from './protocol'\nimport { stripMountPath } from './mountPath'\nimport { TunnelTimeoutError } from './errors'\nimport type { TunnelSession } from './session'\nimport type { TunnelMeta, TunnelResponseMeta } from '../types'\n\nasync function readNodeBody(req: IncomingMessage): Promise<Buffer> {\n const chunks: Buffer[] = []\n for await (const chunk of req) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))\n }\n return Buffer.concat(chunks)\n}\n\n/** 504 when the tunnel timed out, 502 for any other tunnel failure. */\nfunction tunnelErrorStatus(err: unknown): number {\n return err instanceof TunnelTimeoutError ? 504 : 502\n}\n\nfunction tunnelErrorMessage(err: unknown): string {\n const message = err instanceof Error ? err.message : String(err)\n return `Gateway Hyper tunnel error: ${message}`\n}\n\nexport async function forwardNodeRequest(\n session: TunnelSession,\n req: IncomingMessage,\n res: ServerResponse,\n mountPath: string,\n): Promise<void> {\n // The caller hanging up abandons the request instead of leaving it on the tunnel.\n const controller = new AbortController()\n const onClose = () => {\n if (!res.writableEnded) controller.abort()\n }\n res.once('close', onClose)\n try {\n const body = await readNodeBody(req)\n const path = stripMountPath(req.url ?? '/', mountPath)\n const meta: TunnelMeta = {\n method: req.method ?? 'GET',\n path,\n headers: flattenRequestHeaders(req.headers as Record<string, string | string[] | undefined>),\n }\n const { meta: responseMeta, body: responseBody } = await session.forwardHttp(meta, body, {\n signal: controller.signal,\n })\n writeNodeResponse(res, responseMeta, responseBody)\n } catch (err) {\n if (controller.signal.aborted) return\n if (!res.headersSent) {\n res.writeHead(tunnelErrorStatus(err), { 'Content-Type': 'text/plain; charset=utf-8' })\n }\n res.end(tunnelErrorMessage(err))\n } finally {\n res.off('close', onClose)\n }\n}\n\nfunction writeNodeResponse(\n res: ServerResponse,\n meta: TunnelResponseMeta,\n body: Buffer,\n): void {\n res.writeHead(meta.status, filterResponseHeaders(meta.headers))\n res.end(body)\n}\n\nexport async function forwardFetchRequest(\n session: TunnelSession,\n request: Request,\n mountPath: string,\n): Promise<Response> {\n try {\n const url = new URL(request.url)\n const path = stripMountPath(`${url.pathname}${url.search}`, mountPath)\n const bodyBuf = Buffer.from(await request.arrayBuffer())\n const headerRecord: Record<string, string> = {}\n request.headers.forEach((value, key) => {\n headerRecord[key] = value\n })\n const meta: TunnelMeta = {\n method: request.method || 'GET',\n path,\n headers: flattenRequestHeaders(headerRecord),\n }\n const { meta: responseMeta, body: responseBody } = await session.forwardHttp(meta, bodyBuf, {\n signal: request.signal,\n })\n return new Response(responseBody, {\n status: responseMeta.status,\n headers: filterResponseHeaders(responseMeta.headers),\n })\n } catch (err) {\n return new Response(tunnelErrorMessage(err), {\n status: tunnelErrorStatus(err),\n headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n })\n }\n}\n","import http from 'node:http'\nimport { forwardNodeRequest } from './forward'\nimport { createTunnelSession } from './session'\nimport type { ConnectTunnelOptions, ConnectTunnelResult } from '../types'\n\n/**\n * Client: dial operator key and expose a localhost HTTP server that forwards through the tunnel.\n */\nexport async function connectTunnel(options: ConnectTunnelOptions): Promise<ConnectTunnelResult> {\n const host = options.host ?? '127.0.0.1'\n const port = options.port ?? 1984\n\n const session = await createTunnelSession({\n key: options.key,\n keyFile: options.keyFile,\n keyPair: options.keyPair,\n storePath: options.storePath,\n bootstrap: options.bootstrap,\n requestTimeoutMs: options.requestTimeoutMs,\n connectTimeoutMs: options.connectTimeoutMs,\n maxStreams: options.maxStreams,\n })\n\n const server = http.createServer((req, res) => {\n void forwardNodeRequest(session, req, res, '')\n })\n\n await new Promise<void>((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, host, () => resolve())\n })\n\n const address = server.address()\n const boundPort =\n address && typeof address === 'object' ? address.port : port\n\n const baseUrl = `http://${host}:${boundPort}`\n\n return {\n key: options.key,\n clientKey: session.clientKey,\n host,\n port: boundPort,\n baseUrl,\n close: async () => {\n await new Promise<void>((resolve) => server.close(() => resolve()))\n await session.close()\n },\n }\n}\n\nexport function localGatewayUrl(baseUrl: string, path = ''): string {\n const base = baseUrl.replace(/\\/$/, '')\n if (!path) return base\n return path.startsWith('/') ? `${base}${path}` : `${base}/${path}`\n}\n","import type { IncomingMessage, ServerResponse } from 'node:http'\nimport { createTunnelSession, type TunnelSession } from './session'\nimport { forwardFetchRequest, forwardNodeRequest } from './forward'\nimport type { CreateGatewayProxyOptions, GatewayProxy } from '../types'\n\nfunction resolveProxyKey(options: CreateGatewayProxyOptions): string {\n const key =\n options.key?.trim() ||\n (typeof process !== 'undefined' ? process.env.SEED_GATEWAY_HYPER_KEY?.trim() : '') ||\n ''\n if (!key) {\n throw new Error(\n 'createGatewayProxy requires options.key or SEED_GATEWAY_HYPER_KEY environment variable',\n )\n }\n return key\n}\n\n/**\n * App-server Hyper gateway proxy for hosted web apps (Path B).\n *\n * Mount on a Node route (Next.js Node runtime, Express, etc.). Keep the operator\n * Hyper key on the server only — browsers talk HTTP to `proxyBaseUrl`.\n *\n * Prefer Node.js over Bun for Holepunch natives.\n */\nexport function createGatewayProxy(options: CreateGatewayProxyOptions = {}): GatewayProxy {\n const mountPath = options.mountPath?.trim() || '/api/seed-gateway'\n const key = resolveProxyKey(options)\n\n let session: TunnelSession | null = null\n let starting: Promise<TunnelSession> | null = null\n\n const ensureSession = async (): Promise<TunnelSession> => {\n if (session) return session\n if (starting) return starting\n starting = createTunnelSession({\n key,\n keyFile: options.keyFile,\n keyPair: options.keyPair,\n storePath: options.storePath,\n bootstrap: options.bootstrap,\n requestTimeoutMs: options.requestTimeoutMs,\n connectTimeoutMs: options.connectTimeoutMs,\n maxStreams: options.maxStreams,\n }).then((s) => {\n session = s\n starting = null\n return s\n })\n try {\n return await starting\n } catch (err) {\n starting = null\n throw err\n }\n }\n\n const proxy: GatewayProxy = {\n mountPath,\n start: async () => {\n await ensureSession()\n },\n close: async () => {\n if (starting) {\n try {\n await starting\n } catch {\n /* ignore start failure during close */\n }\n }\n if (session) {\n await session.close()\n session = null\n }\n },\n handleNode: (req: IncomingMessage, res: ServerResponse) => {\n void (async () => {\n try {\n const s = await ensureSession()\n await forwardNodeRequest(s, req, res, mountPath)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n if (!res.headersSent) {\n res.writeHead(502, { 'Content-Type': 'text/plain; charset=utf-8' })\n }\n res.end(`Gateway Hyper tunnel error: ${message}`)\n }\n })()\n },\n handleFetch: async (request: Request): Promise<Response> => {\n try {\n const s = await ensureSession()\n return await forwardFetchRequest(s, request, mountPath)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return new Response(`Gateway Hyper tunnel error: ${message}`, {\n status: 502,\n headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n })\n }\n },\n }\n\n if (options.eager) {\n void proxy.start().catch(() => {\n /* first request will surface the error */\n })\n }\n\n return proxy\n}\n","/**\n * Official Seed Protocol gateway Hyper tunnel operator key (z32).\n * Empty until ops generate and back up the first operator keypair.\n * Also defined in packages/arweave/src/constants.ts; keep both equal (gateway-hyper __tests__/constants.test.ts checks).\n */\nexport const DEFAULT_SEED_GATEWAY_HYPER_KEY = ''\n"],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ export type AllowlistOptions = {
2
+ /** Client public keys (z32 or hex) */
3
+ keys?: string[];
4
+ /** Client IPs or CIDR ranges, IPv4 or IPv6 */
5
+ ips?: string[];
6
+ };
7
+ /** How a stream was let in, or `none` when it was refused. */
8
+ export type AllowedBy = 'open' | 'key' | 'ip' | 'none';
9
+ export type AllowlistVerdict = {
10
+ ok: boolean;
11
+ /** The UDP address the connection uses, IPv4-mapped prefix removed */
12
+ host: string | null;
13
+ by: AllowedBy;
14
+ };
15
+ /** The parts of a HyperDHT stream the allowlist reads. */
16
+ export type AllowlistStream = {
17
+ remotePublicKey?: Uint8Array | null;
18
+ rawStream?: {
19
+ remoteHost?: string | null;
20
+ } | null;
21
+ };
22
+ export type Allowlist = {
23
+ /** False when neither list has entries, so every client is let in. */
24
+ enabled: boolean;
25
+ /** One line for logs, e.g. `keys=2 ips=192.168.1.0/24` or `off`. */
26
+ summary: string;
27
+ /**
28
+ * For HyperDHT's `firewall` server option: true refuses the client before it gets a stream.
29
+ * Refuses only on the key, and only when no IP list is set, since the client's real address
30
+ * is not known until the connection is up.
31
+ */
32
+ firewall: (remotePublicKey: Uint8Array) => boolean;
33
+ /** Check an established stream by key, then by address. */
34
+ check: (stream: AllowlistStream) => AllowlistVerdict;
35
+ };
36
+ /**
37
+ * Decide which HyperDHT clients may use a tunnel. A client is let in when its public key or
38
+ * its address is listed; with both lists empty, every client is. Invalid entries throw.
39
+ *
40
+ * The address is the UDP address the connection uses. A client on the operator's LAN
41
+ * connects over the LAN and shows a private address, and a connection relayed through
42
+ * another HyperDHT node shows the relay's address, so it is refused unless its key is listed.
43
+ */
44
+ export declare function createAllowlist(options?: AllowlistOptions): Allowlist;
45
+ //# sourceMappingURL=allowlist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"allowlist.d.ts","sourceRoot":"","sources":["../../src/tunnel/allowlist.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;IACf,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;CACf,CAAA;AAED,8DAA8D;AAC9D,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,IAAI,GAAG,MAAM,CAAA;AAEtD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,EAAE,EAAE,OAAO,CAAA;IACX,sEAAsE;IACtE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,EAAE,EAAE,SAAS,CAAA;CACd,CAAA;AAED,0DAA0D;AAC1D,MAAM,MAAM,eAAe,GAAG;IAC5B,eAAe,CAAC,EAAE,UAAU,GAAG,IAAI,CAAA;IACnC,SAAS,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;CAClD,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACtB,sEAAsE;IACtE,OAAO,EAAE,OAAO,CAAA;IAChB,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,QAAQ,EAAE,CAAC,eAAe,EAAE,UAAU,KAAK,OAAO,CAAA;IAClD,2DAA2D;IAC3D,KAAK,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,gBAAgB,CAAA;CACrD,CAAA;AAiDD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS,CA+BzE"}
@@ -1 +1 @@
1
- {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/tunnel/connect.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAEzE;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAqC/F;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM,CAIlE"}
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/tunnel/connect.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAEzE;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAyC/F;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM,CAIlE"}
@@ -0,0 +1,131 @@
1
+ import type { AllowedBy } from './allowlist';
2
+ /** Where an upstream request was when it failed. */
3
+ export type UpstreamPhase = 'send' | 'wait' | 'body';
4
+ /**
5
+ * Events from `serveTunnel`'s `onEvent`. Streams are `s<N>`; requests carry the id that
6
+ * `requestIdHeader` sends upstream. Times are milliseconds; `totalMs` and `receiveMs` count
7
+ * from the request's first byte on the stream.
8
+ */
9
+ export type TunnelEvent = {
10
+ name: 'stream.open';
11
+ stream: string;
12
+ /** Client public key (z32) */
13
+ remoteKey: string;
14
+ /** UDP address the connection uses */
15
+ host: string | null;
16
+ by: AllowedBy;
17
+ } | {
18
+ name: 'stream.refused';
19
+ remoteKey: string;
20
+ /**
21
+ * `handshake`: refused by key in HyperDHT's firewall, before a stream exists, so the
22
+ * client's address is unknown and `host` is null. `connection`: refused once connected.
23
+ */
24
+ stage: 'handshake' | 'connection';
25
+ host: string | null;
26
+ } | {
27
+ name: 'stream.close';
28
+ stream: string;
29
+ requests: number;
30
+ bytesIn: number;
31
+ durationMs: number;
32
+ /** The request in flight when the stream closed, if any */
33
+ inflight: string | null;
34
+ } | {
35
+ name: 'stream.error';
36
+ stream: string;
37
+ error: string;
38
+ code?: string;
39
+ } | {
40
+ name: 'req.recv';
41
+ request: string;
42
+ stream: string;
43
+ method: string;
44
+ path: string;
45
+ bodyBytes: number;
46
+ /** From the request's first byte to its last */
47
+ receiveMs: number;
48
+ } | {
49
+ name: 'up.response';
50
+ request: string;
51
+ status: number;
52
+ upstreamMs: number;
53
+ } | {
54
+ name: 'up.done';
55
+ request: string;
56
+ status: number;
57
+ bytes: number;
58
+ upstreamMs: number;
59
+ } | {
60
+ name: 'up.error';
61
+ request: string;
62
+ phase: UpstreamPhase;
63
+ error: string;
64
+ code?: string;
65
+ /** True when the tunnel stream closed and the operator aborted the upstream request */
66
+ aborted: boolean;
67
+ } | {
68
+ name: 'up.timeout';
69
+ request: string;
70
+ phase: UpstreamPhase;
71
+ timeoutMs: number;
72
+ } | {
73
+ name: 'res.sent';
74
+ request: string;
75
+ status: number;
76
+ bytes: number;
77
+ totalMs: number;
78
+ };
79
+ export type TunnelEventName = TunnelEvent['name'];
80
+ /** Called synchronously; a throw is caught and dropped so tracing can't break the tunnel. */
81
+ export type TunnelEventListener = (event: TunnelEvent) => void;
82
+ /** Bytes are counted from the HTTP client's side: up is toward the upstream. */
83
+ type RawCloseFields = {
84
+ conn: string;
85
+ bytesUp: number;
86
+ bytesDown: number;
87
+ durationMs: number;
88
+ /** First error on either side, as `hyper:<code>`, `tcp:<code>` or `connect-timeout` */
89
+ error: string | null;
90
+ };
91
+ /** Events from `serveRawTunnel`'s `onEvent`. Connections are `c<N>`. */
92
+ export type RawTunnelEvent = {
93
+ name: 'conn.open';
94
+ conn: string;
95
+ remoteKey: string;
96
+ host: string | null;
97
+ by: AllowedBy;
98
+ } | Extract<TunnelEvent, {
99
+ name: 'stream.refused';
100
+ }> | ({
101
+ name: 'conn.close';
102
+ } & RawCloseFields);
103
+ export type RawTunnelEventListener = (event: RawTunnelEvent) => void;
104
+ /** Events from `connectRawTunnel`'s `onEvent`, one TCP connection each. */
105
+ export type RawClientEvent = {
106
+ name: 'conn.open';
107
+ conn: string;
108
+ connectMs: number;
109
+ }
110
+ /** A TCP connection refused because `maxConnections` were already open */
111
+ | {
112
+ name: 'conn.refused';
113
+ open: number;
114
+ max: number;
115
+ } | ({
116
+ name: 'conn.close';
117
+ } & RawCloseFields);
118
+ export type RawClientEventListener = (event: RawClientEvent) => void;
119
+ /** Wrap `onEvent` so a throw is dropped; undefined when there is no listener. */
120
+ export declare function safeEmitter<E>(onEvent: ((event: E) => void) | undefined): ((event: E) => void) | undefined;
121
+ export declare function errorFields(err: unknown): {
122
+ error: string;
123
+ code?: string;
124
+ };
125
+ /** A run-unique prefix, so request ids from different operator runs never collide. */
126
+ export declare function createIdSource(): {
127
+ stream: () => string;
128
+ request: () => string;
129
+ };
130
+ export {};
131
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/tunnel/events.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import type { UpstreamPhase } from './events';
1
2
  import type { TunnelMeta } from '../types';
2
3
  /** Default idle limit on the operator's upstream socket. */
3
4
  export declare const DEFAULT_UPSTREAM_TIMEOUT_MS = 30000;
@@ -6,6 +7,16 @@ export type ProxyTunnelRequestOptions = {
6
7
  timeoutMs?: number;
7
8
  /** Abort the upstream request, e.g. when the tunnel stream closes. */
8
9
  signal?: AbortSignal;
10
+ /** Headers set on the upstream request, replacing any the client sent under the same name. */
11
+ extraHeaders?: Record<string, string>;
12
+ /** Progress callbacks, for tracing. */
13
+ observer?: ProxyObserver;
14
+ };
15
+ export type ProxyObserver = {
16
+ /** The request moved to `phase`: `send` from the start, `wait` once the body is sent, `body` on headers. */
17
+ onPhase?: (phase: UpstreamPhase) => void;
18
+ onResponse?: (status: number) => void;
19
+ onDone?: (status: number, bytes: number) => void;
9
20
  };
10
21
  /**
11
22
  * Drop hop-by-hop headers and the client's `host`, which names the app server rather than
@@ -1 +1 @@
1
- {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/tunnel/proxy.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,4DAA4D;AAC5D,eAAO,MAAM,2BAA2B,QAAS,CAAA;AAEjD,MAAM,MAAM,yBAAyB,GAAG;IACtC,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sEAAsE;IACtE,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB,CAAA;AAaD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAsB9F;AAED,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA8C5E"}
1
+ {"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../../src/tunnel/proxy.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAE1C,4DAA4D;AAC5D,eAAO,MAAM,2BAA2B,QAAS,CAAA;AAEjD,MAAM,MAAM,yBAAyB,GAAG;IACtC,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sEAAsE;IACtE,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,8FAA8F;IAC9F,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,uCAAuC;IACvC,QAAQ,CAAC,EAAE,aAAa,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,4GAA4G;IAC5G,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IACxC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACrC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACjD,CAAA;AA0BD;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAsB9F;AAED,wBAAsB,kBAAkB,CACtC,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAsD5E"}
@@ -1 +1 @@
1
- {"version":3,"file":"proxyApp.d.ts","sourceRoot":"","sources":["../../src/tunnel/proxyApp.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAevE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,YAAY,CAkFxF"}
1
+ {"version":3,"file":"proxyApp.d.ts","sourceRoot":"","sources":["../../src/tunnel/proxyApp.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,yBAAyB,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAevE;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,yBAA8B,GAAG,YAAY,CAqFxF"}
@@ -0,0 +1,14 @@
1
+ import type { ConnectRawTunnelOptions, ConnectRawTunnelResult } from '../types';
2
+ /** Default local port for `connectRawTunnel`, next to the framed sidecar's 1984. */
3
+ export declare const DEFAULT_RAW_PORT = 1985;
4
+ /** Default limit on TCP connections open through one raw client at once. */
5
+ export declare const DEFAULT_MAX_RAW_CONNECTIONS = 64;
6
+ /**
7
+ * Client, raw mode: listen on a local TCP port and carry each connection over its own
8
+ * HyperDHT stream to a `serveRawTunnel` operator, which pipes it to its upstream.
9
+ *
10
+ * Each connection costs a DHT lookup and handshake (about 100–400 ms), so put an HTTP client
11
+ * that keeps connections alive in front, such as nginx with `keepalive` on its upstream.
12
+ */
13
+ export declare function connectRawTunnel(options: ConnectRawTunnelOptions): Promise<ConnectRawTunnelResult>;
14
+ //# sourceMappingURL=rawConnect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rawConnect.d.ts","sourceRoot":"","sources":["../../src/tunnel/rawConnect.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,uBAAuB,EAAE,sBAAsB,EAAE,MAAM,UAAU,CAAA;AAE/E,oFAAoF;AACpF,eAAO,MAAM,gBAAgB,OAAO,CAAA;AAEpC,4EAA4E;AAC5E,eAAO,MAAM,2BAA2B,KAAK,CAAA;AAE7C;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA2ExG"}
@@ -0,0 +1,35 @@
1
+ import type net from 'node:net';
2
+ import type { Duplex } from 'node:stream';
3
+ /** How long a side may stay open after the other closed cleanly before it is destroyed. */
4
+ export declare const RAW_CLOSE_GRACE_MS = 30000;
5
+ export type RawPipeResult = {
6
+ /** Bytes read from the TCP socket and sent over the HyperDHT stream */
7
+ tcpBytesRead: number;
8
+ /** Bytes from the HyperDHT stream written to the TCP socket */
9
+ tcpBytesWritten: number;
10
+ durationMs: number;
11
+ /** First error on either side, as `hyper:<code>` or `tcp:<code>`, or null */
12
+ error: string | null;
13
+ };
14
+ export type RawPipe = {
15
+ /** Record an error that is about to destroy one side, e.g. a connect timeout. */
16
+ fail: (label: string) => void;
17
+ };
18
+ /**
19
+ * Copy bytes both ways between a HyperDHT stream and a TCP socket until both close.
20
+ *
21
+ * HyperDHT → TCP is forwarded by hand with backpressure, not with `stream.pipe(socket)`:
22
+ * streamx's pipe destroys its source with PREMATURE_CLOSE when the destination closes first,
23
+ * so the far end would see a reset even though every byte arrived. TCP → HyperDHT uses Node's
24
+ * `pipe`, which has no such problem. Half-closes pass through, so an HTTP connection ends the
25
+ * way its client or server ended it; `tcp` must be created with `allowHalfOpen: true` for that.
26
+ *
27
+ * When one side closes cleanly, the other is ended so data already written still arrives,
28
+ * and destroyed only if it is still open `graceMs` later. After an error on either side, the
29
+ * other is destroyed at once.
30
+ */
31
+ export declare function pipeRaw(hyper: Duplex, tcp: net.Socket, options?: {
32
+ graceMs?: number;
33
+ onClose?: (result: RawPipeResult) => void;
34
+ }): RawPipe;
35
+ //# sourceMappingURL=rawPipe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rawPipe.d.ts","sourceRoot":"","sources":["../../src/tunnel/rawPipe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,2FAA2F;AAC3F,eAAO,MAAM,kBAAkB,QAAS,CAAA;AAExC,MAAM,MAAM,aAAa,GAAG;IAC1B,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,OAAO,GAAG;IACpB,iFAAiF;IACjF,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAC9B,CAAA;AAQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CACrB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,GAAG,CAAC,MAAM,EACf,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAA;CAAO,GAC5E,OAAO,CAgDT"}
@@ -0,0 +1,18 @@
1
+ import type { ServeRawTunnelOptions, ServeRawTunnelResult } from '../types';
2
+ /** Default operator key file for raw mode, apart from the framed tunnel's key. */
3
+ export declare const DEFAULT_RAW_OPERATOR_KEY_FILE = ".seed/gateway-tunnel/raw-operator.key.json";
4
+ /** `http://host:port`, `https://host`, or `host:port`, as a TCP address. */
5
+ export declare function parseTcpUpstream(upstream: string): {
6
+ host: string;
7
+ port: number;
8
+ };
9
+ /**
10
+ * Operator, raw mode: pipe each HyperDHT stream byte for byte to a new TCP connection to the
11
+ * upstream. There is no framing, so HTTP/1.1 runs end to end between the client's HTTP client
12
+ * and the upstream: keep-alive, streamed bodies of any size, `Range` and WebSockets all work.
13
+ *
14
+ * Clients must use `connectRawTunnel`; a framed `connectTunnel` client can't talk to this
15
+ * server, so it listens on its own key.
16
+ */
17
+ export declare function serveRawTunnel(options: ServeRawTunnelOptions): Promise<ServeRawTunnelResult>;
18
+ //# sourceMappingURL=rawServe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rawServe.d.ts","sourceRoot":"","sources":["../../src/tunnel/rawServe.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAA;AAE3E,kFAAkF;AAClF,eAAO,MAAM,6BAA6B,+CAA+C,CAAA;AAEzF,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAUjF;AAED;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAiFlG"}
@@ -1,13 +1,24 @@
1
1
  import type { Duplex } from 'node:stream';
2
+ import { type TunnelEventListener } from './events';
2
3
  import type { ServeTunnelOptions, ServeTunnelResult } from '../types';
3
4
  export type TunnelConnectionOptions = {
4
5
  /** Idle limit on each upstream request. Default 30 000; 0 disables. */
5
6
  upstreamTimeoutMs?: number;
7
+ /** Receives this stream's events; see `TunnelEvent`. */
8
+ onEvent?: TunnelEventListener;
9
+ /** Send each request's id upstream under this header. */
10
+ requestIdHeader?: string;
11
+ /** Id for this stream in events. Default `s0`. */
12
+ streamId?: string;
13
+ /** Makes request ids. Default: run-unique ids from `createIdSource`. */
14
+ nextRequestId?: () => string;
6
15
  };
7
16
  /**
8
17
  * Answer framed requests on one tunnel stream, in order, until it closes. Upstream timeouts
9
18
  * become 504 and other upstream failures 502, so the stream stays usable; only a broken
10
19
  * stream ends the loop. The stream ending or closing aborts the upstream request in flight.
20
+ *
21
+ * Without `onEvent` or `requestIdHeader`, no tracing state is kept and no listeners are added.
11
22
  */
12
23
  export declare function serveTunnelConnection(socket: Duplex, upstream: string, options?: TunnelConnectionOptions): Promise<void>;
13
24
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/tunnel/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAMzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAErE,MAAM,MAAM,uBAAuB,GAAG;IACpC,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,MAAM,CAAA;CAC3B,CAAA;AAaD;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,IAAI,CAAC,CAoCf;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsBzF"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/tunnel/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAMzC,OAAO,EAIL,KAAK,mBAAmB,EAEzB,MAAM,UAAU,CAAA;AAEjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAErE,MAAM,MAAM,uBAAuB,GAAG;IACpC,uEAAuE;IACvE,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,wDAAwD;IACxD,OAAO,CAAC,EAAE,mBAAmB,CAAA;IAC7B,yDAAyD;IACzD,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,MAAM,CAAA;CAC7B,CAAA;AAaD;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,IAAI,CAAC,CAgIf;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkEzF"}