tunelo 0.0.0-stage → 1.6.2
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.
- package/README.md +101 -2
- package/cli.js +2163 -0
- package/cli.js.map +7 -0
- package/package.json +42 -4
package/cli.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/cli.ts", "../src/cli-login-command.ts", "../src/api-client.ts", "../src/config.ts", "../src/run-registry.ts", "../src/cli-status-commands.ts", "../src/tcp-proxy.ts", "../src/tunnel-client.ts", "../../shared/src/protocol.ts", "../../shared/src/constants.ts", "../../shared/src/http-headers.ts", "../src/http-stream.ts", "../src/ws-app-proxy.ts", "../src/version.ts", "../src/cli-tcp-command.ts", "../src/cli-update-command.ts", "../src/display.ts", "../src/inspector-server.ts", "../src/inspector-ui.ts", "../src/listen-with-port-fallback.ts", "../src/local-proxy.ts", "../src/request-store.ts", "../src/inspector-integration.ts", "../src/portal-server.ts", "../src/tunnel-config-parser.ts"],
|
|
4
|
+
"sourcesContent": ["#!/usr/bin/env node\nimport type { RegisterTunnelResult } from \"@tunelo/shared\";\nimport { program } from \"commander\";\nimport { registerLoginCommands } from \"./cli-login-command.js\";\nimport { registerStatusCommands } from \"./cli-status-commands.js\";\nimport { registerTcpCommand } from \"./cli-tcp-command.js\";\nimport { registerUpdateCommand } from \"./cli-update-command.js\";\nimport { getConfigPath, loadConfig, saveConfig } from \"./config.js\";\nimport { Display } from \"./display.js\";\nimport { createInspectorIntegration } from \"./inspector-integration.js\";\nimport { startPortalServer, stopPortalServer } from \"./portal-server.js\";\nimport type { InspectorEntry } from \"./request-store.js\";\nimport { type RunTunnel, startRunRecord } from \"./run-registry.js\";\nimport { TunnelClient } from \"./tunnel-client.js\";\nimport { parseTunnelArgs } from \"./tunnel-config-parser.js\";\nimport { VERSION } from \"./version.js\";\n\nconst display = new Display();\n\nprogram\n\t.name(\"tunelo\")\n\t.version(VERSION)\n\t.description(\"Expose local services to the internet\");\n\nprogram\n\t.command(\"http <ports...>\")\n\t.description(\"Create HTTP tunnel(s). Format: <port> or <port>:<subdomain>\")\n\t.option(\n\t\t\"-s, --subdomain <name>\",\n\t\t\"Request specific subdomain (single tunnel only)\",\n\t)\n\t.option(\"-k, --key <apikey>\", \"API key for authentication\")\n\t.option(\"--server <url>\", \"Tunnel server URL\")\n\t.option(\"-a, --auth <user:pass>\", \"Protect tunnel(s) with Basic Auth\")\n\t.option(\n\t\t\"--inspect-port <port>\",\n\t\t\"Inspector UI port; next free port is used if taken (default: 4040)\",\n\t\t\"4040\",\n\t)\n\t.option(\"--no-inspect\", \"Disable request inspector\")\n\t.option(\n\t\t\"--portal-port <port>\",\n\t\t\"Portal server port; next free port is used if taken (default: 4041)\",\n\t\t\"4041\",\n\t)\n\t.option(\"--no-portal\", \"Disable the portal server\")\n\t.option(\"-q, --quiet\", \"Suppress request logging\")\n\t.option(\"-v, --verbose\", \"Show extra details (body size)\")\n\t.action(\n\t\tasync (\n\t\t\tportArgs: string[],\n\t\t\toptions: {\n\t\t\t\tsubdomain?: string;\n\t\t\t\tkey?: string;\n\t\t\t\tauth?: string;\n\t\t\t\tserver?: string;\n\t\t\t\tinspectPort?: string;\n\t\t\t\tinspect?: boolean;\n\t\t\t\tportalPort?: string;\n\t\t\t\tportal?: boolean;\n\t\t\t\tquiet?: boolean;\n\t\t\t\tverbose?: boolean;\n\t\t\t},\n\t\t) => {\n\t\t\tlet tunnels: import(\"./tunnel-config-parser.js\").TunnelMapping[];\n\t\t\ttry {\n\t\t\t\ttunnels = parseTunnelArgs(portArgs);\n\t\t\t} catch (err) {\n\t\t\t\tdisplay.showError((err as Error).message);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\tconst config = loadConfig();\n\t\t\tconst apiKey = options.key ?? process.env.TUNELO_KEY ?? config.key;\n\t\t\tconst serverUrl =\n\t\t\t\toptions.server ??\n\t\t\t\tprocess.env.TUNELO_SERVER ??\n\t\t\t\tconfig.server ??\n\t\t\t\t\"wss://tunnel.inetdev.io.vn\";\n\n\t\t\tif (!apiKey) {\n\t\t\t\tdisplay.showError(\n\t\t\t\t\t\"API key required. Use --key, TUNELO_KEY env, or `tunelo config --key <key>`\",\n\t\t\t\t);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\tif (options.quiet) display.setLogLevel(\"quiet\");\n\t\t\telse if (options.verbose) display.setLogLevel(\"verbose\");\n\n\t\t\tdisplay.showBanner(VERSION);\n\t\t\tdisplay.showConnecting(serverUrl);\n\n\t\t\tconst primary = tunnels[0];\n\t\t\tconst additional = tunnels.slice(1);\n\t\t\tconst subdomainPortMap = new Map<string, number>();\n\t\t\tconst inspectPort = Number(options.inspectPort) || 4040;\n\n\t\t\tconst inspector =\n\t\t\t\toptions.inspect !== false\n\t\t\t\t\t? createInspectorIntegration({\n\t\t\t\t\t\t\tinspectPort,\n\t\t\t\t\t\t\tsubdomainPortMap,\n\t\t\t\t\t\t\tdefaultPort: primary.port,\n\t\t\t\t\t\t})\n\t\t\t\t\t: null;\n\n\t\t\tconst client = new TunnelClient({\n\t\t\t\tserverUrl,\n\t\t\t\tapiKey,\n\t\t\t\tsubdomain: options.subdomain ?? primary.subdomain,\n\t\t\t\tlocalPort: primary.port,\n\t\t\t\tauth: options.auth,\n\t\t\t\tadditionalTunnels: additional,\n\t\t\t});\n\n\t\t\t// Record this process so `tunelo status`/`kill` can see and stop it.\n\t\t\tconst run = startRunRecord(\"http\", serverUrl);\n\t\t\tconst runTunnels: RunTunnel[] = [];\n\n\t\t\tconst portalPort = Number(options.portalPort) || 4041;\n\t\t\tconst portalEnabled = options.portal !== false;\n\n\t\t\tclient.on(\"connected\", (result: { url: string; subdomain: string }) => {\n\t\t\t\tsubdomainPortMap.set(result.subdomain, primary.port);\n\t\t\t\trunTunnels.push({ url: result.url, localPort: primary.port });\n\t\t\t\trun.update(runTunnels);\n\t\t\t\tdisplay.showConnected(\n\t\t\t\t\tresult.url,\n\t\t\t\t\t`http://localhost:${primary.port}`,\n\t\t\t\t\tapiKey,\n\t\t\t\t\toptions.auth !== undefined,\n\t\t\t\t);\n\t\t\t\tif (inspector) {\n\t\t\t\t\tinspector\n\t\t\t\t\t\t.start()\n\t\t\t\t\t\t.then((boundPort) => {\n\t\t\t\t\t\t\tdisplay.showInspector(boundPort, inspectPort);\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch((err: Error) => {\n\t\t\t\t\t\t\tdisplay.showError(`Inspector failed to start: ${err.message}`);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tif (portalEnabled) {\n\t\t\t\t\tstartPortalServer(portalPort)\n\t\t\t\t\t\t.then((boundPort) => {\n\t\t\t\t\t\t\tdisplay.showPortal(boundPort, portalPort);\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch((err: Error) => {\n\t\t\t\t\t\t\tdisplay.showError(\n\t\t\t\t\t\t\t\t`Portal server failed to start: ${err.message}`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tclient.on(\"tunnel-registered\", (result: RegisterTunnelResult) => {\n\t\t\t\tsubdomainPortMap.set(result.subdomain, result.localPort);\n\t\t\t\trunTunnels.push({ url: result.url, localPort: result.localPort });\n\t\t\t\trun.update(runTunnels);\n\t\t\t\tdisplay.showTunnelRegistered(\n\t\t\t\t\tresult.subdomain,\n\t\t\t\t\tresult.url,\n\t\t\t\t\tresult.localPort,\n\t\t\t\t);\n\t\t\t});\n\n\t\t\tclient.on(\"tunnel-register-error\", (result: RegisterTunnelResult) => {\n\t\t\t\tdisplay.showError(`Tunnel ${result.subdomain}: ${result.error}`);\n\t\t\t});\n\n\t\t\tif (inspector) {\n\t\t\t\tclient.on(\"inspector-entry\", (entry: InspectorEntry) => {\n\t\t\t\t\tinspector.store.add(entry);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tclient.on(\"disconnected\", () => display.showDisconnected());\n\t\t\tclient.on(\"reconnecting\", (info: { attempt: number; delay: number }) => {\n\t\t\t\tdisplay.showReconnecting(info.attempt, info.delay);\n\t\t\t});\n\t\t\tclient.on(\n\t\t\t\t\"request\",\n\t\t\t\t(info: {\n\t\t\t\t\tmethod: string;\n\t\t\t\t\tpath: string;\n\t\t\t\t\tstatus: number;\n\t\t\t\t\tdurationMs: number;\n\t\t\t\t\tsubdomain?: string;\n\t\t\t\t}) => {\n\t\t\t\t\tdisplay.logRequest(\n\t\t\t\t\t\tinfo.method,\n\t\t\t\t\t\tinfo.path,\n\t\t\t\t\t\tinfo.status,\n\t\t\t\t\t\tinfo.durationMs,\n\t\t\t\t\t\tundefined,\n\t\t\t\t\t\tinfo.subdomain,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t);\n\t\t\tclient.on(\"error\", (err: Error) => display.showError(err.message));\n\n\t\t\ttry {\n\t\t\t\tawait client.connect();\n\t\t\t} catch (err) {\n\t\t\t\tdisplay.showError((err as Error).message);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\tprocess.on(\"SIGINT\", () => {\n\t\t\t\tclient.disconnect();\n\t\t\t\tif (portalEnabled) void stopPortalServer();\n\t\t\t\tdisplay.showStats();\n\t\t\t\tprocess.exit(0);\n\t\t\t});\n\t\t},\n\t);\n\nregisterTcpCommand(program, display);\nregisterLoginCommands(program);\nregisterStatusCommands(program);\nregisterUpdateCommand(program);\n\nprogram\n\t.command(\"config\")\n\t.description(\"Set default configuration\")\n\t.option(\"-k, --key <apikey>\", \"Set default API key\")\n\t.option(\"-s, --server <url>\", \"Set default server URL\")\n\t.option(\"--show\", \"Show current config\")\n\t.action((options: { key?: string; server?: string; show?: boolean }) => {\n\t\tif (options.show) {\n\t\t\tconst config = loadConfig();\n\t\t\tconsole.log(`Config path: ${getConfigPath()}`);\n\t\t\tconsole.log(JSON.stringify(config, null, 2));\n\t\t\treturn;\n\t\t}\n\n\t\tconst config = loadConfig();\n\t\tif (options.key) config.key = options.key;\n\t\tif (options.server) config.server = options.server;\n\t\tsaveConfig(config);\n\t\tconsole.log(\"Config saved.\");\n\t});\n\nprogram.parse();\n", "import { randomUUID } from \"node:crypto\";\nimport { hostname } from \"node:os\";\nimport type { Command } from \"commander\";\nimport {\n\tgetApiBaseUrl,\n\tinitDeviceLogin,\n\tpollDeviceLogin,\n\tverifyKey,\n} from \"./api-client.js\";\nimport { loadConfig, saveConfig } from \"./config.js\";\n\nfunction getServerBaseUrl(): string {\n\tconst config = loadConfig();\n\tconst serverUrl =\n\t\tprocess.env.TUNELO_SERVER ?? config.server ?? \"wss://tunnel.inetdev.io.vn\";\n\treturn getApiBaseUrl(serverUrl);\n}\n\n/** Stable per-machine id; created once and persisted in the config. */\nfunction getDeviceId(): string {\n\tconst config = loadConfig();\n\tif (config.deviceId) return config.deviceId;\n\tconst deviceId = randomUUID();\n\tsaveConfig({ ...config, deviceId });\n\treturn deviceId;\n}\n\nfunction saveKey(token: string): void {\n\tsaveConfig({ ...loadConfig(), key: token });\n}\n\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise((r) => setTimeout(r, ms));\n}\n\n/** Open a URL in the default browser \u2014 cross-platform, no deps. */\nasync function openBrowser(url: string): Promise<void> {\n\tconst { execFile } = await import(\"node:child_process\");\n\tconst { promisify } = await import(\"node:util\");\n\tconst run = promisify(execFile);\n\ttry {\n\t\tif (process.platform === \"darwin\") await run(\"open\", [url]);\n\t\telse if (process.platform === \"win32\")\n\t\t\tawait run(\"cmd\", [\"/c\", \"start\", \"\", url]);\n\t\telse await run(\"xdg-open\", [url]);\n\t} catch {\n\t\t// ignore \u2014 the URL is printed for manual opening\n\t}\n}\n\nexport function registerLoginCommands(program: Command): void {\n\tprogram\n\t\t.command(\"login\")\n\t\t.description(\"Authorize this device via the browser\")\n\t\t.option(\"-k, --key <token>\", \"Use an existing access token (skip browser)\")\n\t\t.option(\"-f, --force\", \"Re-authorize even if already signed in\")\n\t\t.action(async (options: { key?: string; force?: boolean }) => {\n\t\t\tconst baseUrl = getServerBaseUrl();\n\n\t\t\t// Headless path: verify and save a token directly.\n\t\t\tif (options.key) {\n\t\t\t\tif (!(await verifyKey(baseUrl, options.key).catch(() => false))) {\n\t\t\t\t\tconsole.error(\" Invalid access token.\");\n\t\t\t\t\tprocess.exit(1);\n\t\t\t\t}\n\t\t\t\tsaveKey(options.key);\n\t\t\t\tconsole.log(\" \u2713 Verified & saved. Run: tunelo http <port>\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Idempotent: a still-valid token on this device needs no re-auth.\n\t\t\tconst existing = loadConfig().key;\n\t\t\tif (existing && !options.force) {\n\t\t\t\tif (await verifyKey(baseUrl, existing).catch(() => false)) {\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\" Already authorized on this device. (use --force to redo)\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst deviceId = getDeviceId();\n\t\t\tlet requestId: string;\n\t\t\ttry {\n\t\t\t\trequestId = await initDeviceLogin(baseUrl, deviceId, hostname());\n\t\t\t} catch (err) {\n\t\t\t\tconsole.error(` Could not reach server: ${(err as Error).message}`);\n\t\t\t\tprocess.exit(1);\n\t\t\t}\n\n\t\t\tconst url = `${baseUrl}/login?req=${encodeURIComponent(requestId)}`;\n\t\t\tconsole.log();\n\t\t\tconsole.log(\" Opening browser to authorize this device...\");\n\t\t\tconsole.log(` ${url}`);\n\t\t\tconsole.log();\n\t\t\tconsole.log(\" Waiting for approval in the browser...\");\n\t\t\tawait openBrowser(url);\n\n\t\t\tconst deadline = Date.now() + 5 * 60 * 1000;\n\t\t\twhile (Date.now() < deadline) {\n\t\t\t\tawait sleep(2000);\n\t\t\t\tlet result: Awaited<ReturnType<typeof pollDeviceLogin>>;\n\t\t\t\ttry {\n\t\t\t\t\tresult = await pollDeviceLogin(baseUrl, requestId);\n\t\t\t\t} catch {\n\t\t\t\t\tcontinue; // transient \u2014 keep polling\n\t\t\t\t}\n\t\t\t\tif (result.status === \"approved\") {\n\t\t\t\t\tsaveKey(result.token);\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t\" \u2713 Device authorized & signed in. Run: tunelo http <port>\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (result.status === \"expired\") {\n\t\t\t\t\tconsole.error(\" Login request expired. Run `tunelo login` again.\");\n\t\t\t\t\tprocess.exit(1);\n\t\t\t\t}\n\t\t\t}\n\t\t\tconsole.error(\" Timed out waiting for approval.\");\n\t\t\tprocess.exit(1);\n\t\t});\n\n\tprogram\n\t\t.command(\"logout\")\n\t\t.description(\"Remove the saved access token\")\n\t\t.action(() => {\n\t\t\tconst config = loadConfig();\n\t\t\tif (!config.key) {\n\t\t\t\tconsole.log(\" Not logged in.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Keep deviceId so re-login stays idempotent for this machine.\n\t\t\tsaveConfig({ ...config, key: undefined });\n\t\t\tconsole.log(\" Logged out. Access token removed.\");\n\t\t});\n}\n", "/**\n * Minimal HTTP client for the Tunelo server API.\n * Surfaces after the account/admin removal: device pairing + token verify.\n */\n\ninterface ApiErrorResponse {\n\terror?: { code: string; message: string };\n}\n\nexport class ApiClientError extends Error {\n\tconstructor(\n\t\tpublic code: string,\n\t\tmessage: string,\n\t\tpublic statusCode: number,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = \"ApiClientError\";\n\t}\n}\n\n/** Derives HTTP API base URL from WS server URL */\nexport function getApiBaseUrl(serverUrl: string): string {\n\treturn serverUrl\n\t\t.replace(/^wss:\\/\\//, \"https://\")\n\t\t.replace(/^ws:\\/\\//, \"http://\")\n\t\t.replace(/\\/$/, \"\");\n}\n\nasync function request<T>(\n\tbaseUrl: string,\n\tpath: string,\n\toptions: { method?: string; apiKey?: string; body?: unknown } = {},\n): Promise<T> {\n\tconst headers: Record<string, string> = {};\n\tif (options.apiKey) headers.Authorization = `Bearer ${options.apiKey}`;\n\tif (options.body !== undefined) headers[\"Content-Type\"] = \"application/json\";\n\n\tconst res = await fetch(`${baseUrl}/api${path}`, {\n\t\tmethod: options.method ?? \"GET\",\n\t\theaders,\n\t\tbody: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n\t});\n\n\tconst text = await res.text();\n\tlet data: T & ApiErrorResponse;\n\ttry {\n\t\tdata = JSON.parse(text) as T & ApiErrorResponse;\n\t} catch {\n\t\tthrow new ApiClientError(\n\t\t\t\"TUNELO_UNKNOWN\",\n\t\t\ttext || `HTTP ${res.status}`,\n\t\t\tres.status,\n\t\t);\n\t}\n\n\tif (!res.ok) {\n\t\tthrow new ApiClientError(\n\t\t\tdata.error?.code ?? \"TUNELO_UNKNOWN\",\n\t\t\tdata.error?.message ?? `HTTP ${res.status}`,\n\t\t\tres.status,\n\t\t);\n\t}\n\n\treturn data;\n}\n\n/** Starts a device login; returns the request id the browser will approve. */\nexport async function initDeviceLogin(\n\tbaseUrl: string,\n\tdeviceId: string,\n\tname: string,\n): Promise<string> {\n\tconst data = await request<{ requestId: string }>(baseUrl, \"/device/init\", {\n\t\tmethod: \"POST\",\n\t\tbody: { deviceId, name },\n\t});\n\treturn data.requestId;\n}\n\nexport type DeviceLoginPoll =\n\t| { status: \"pending\" }\n\t| { status: \"approved\"; token: string }\n\t| { status: \"expired\" };\n\n/** Polls a device-login request; returns the token once approved. */\nexport async function pollDeviceLogin(\n\tbaseUrl: string,\n\trequestId: string,\n): Promise<DeviceLoginPoll> {\n\treturn request<DeviceLoginPoll>(baseUrl, \"/device/poll\", {\n\t\tmethod: \"POST\",\n\t\tbody: { req: requestId },\n\t});\n}\n\n/** Checks an access token with the server. true = valid, false = rejected (401). */\nexport async function verifyKey(\n\tbaseUrl: string,\n\tkey: string,\n): Promise<boolean> {\n\ttry {\n\t\tawait request<{ valid: boolean }>(baseUrl, \"/verify\", { apiKey: key });\n\t\treturn true;\n\t} catch (err) {\n\t\tif (err instanceof ApiClientError && err.statusCode === 401) return false;\n\t\tthrow err;\n\t}\n}\n", "import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface TuneloConfig {\n\tserver?: string;\n\tkey?: string;\n\t/** Stable per-machine id used for idempotent device login. */\n\tdeviceId?: string;\n}\n\nfunction getConfigDir(): string {\n\treturn join(homedir(), \".tunelo\");\n}\n\nexport function getConfigPath(): string {\n\treturn join(getConfigDir(), \"config.json\");\n}\n\nexport function loadConfig(): TuneloConfig {\n\ttry {\n\t\treturn JSON.parse(readFileSync(getConfigPath(), \"utf-8\")) as TuneloConfig;\n\t} catch {\n\t\treturn {};\n\t}\n}\n\nexport function saveConfig(config: TuneloConfig): void {\n\tconst dir = getConfigDir();\n\tif (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\twriteFileSync(getConfigPath(), JSON.stringify(config, null, 2));\n}\n", "import {\n\texistsSync,\n\tmkdirSync,\n\treadFileSync,\n\treaddirSync,\n\trmSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface RunTunnel {\n\turl: string;\n\tlocalPort: number;\n}\n\n/** On-disk record of a running `tunelo http`/`tcp` process. */\nexport interface RunRecord {\n\tpid: number;\n\tkind: \"http\" | \"tcp\";\n\tstartedAt: number;\n\tserver: string;\n\ttunnels: RunTunnel[];\n}\n\nfunction getRunDir(): string {\n\treturn join(homedir(), \".tunelo\", \"running\");\n}\n\nfunction recordPath(pid: number): string {\n\treturn join(getRunDir(), `${pid}.json`);\n}\n\n/** True if a process with this pid currently exists. */\nexport function isAlive(pid: number): boolean {\n\ttry {\n\t\tprocess.kill(pid, 0);\n\t\treturn true;\n\t} catch (err) {\n\t\t// ESRCH = no such process; EPERM = exists but not ours (still alive).\n\t\treturn (err as NodeJS.ErrnoException).code === \"EPERM\";\n\t}\n}\n\nfunction saveRecord(record: RunRecord): void {\n\tconst dir = getRunDir();\n\tif (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n\twriteFileSync(recordPath(record.pid), JSON.stringify(record));\n}\n\nexport function removeRecord(pid: number): void {\n\trmSync(recordPath(pid), { force: true });\n}\n\n/** List live tunnel processes, pruning records whose process has exited. */\nexport function listRecords(): RunRecord[] {\n\tconst dir = getRunDir();\n\tif (!existsSync(dir)) return [];\n\tconst records: RunRecord[] = [];\n\tfor (const file of readdirSync(dir)) {\n\t\tif (!file.endsWith(\".json\")) continue;\n\t\tconst path = join(dir, file);\n\t\tlet record: RunRecord;\n\t\ttry {\n\t\t\trecord = JSON.parse(readFileSync(path, \"utf-8\")) as RunRecord;\n\t\t} catch {\n\t\t\trmSync(path, { force: true });\n\t\t\tcontinue;\n\t\t}\n\t\tif (!isAlive(record.pid)) {\n\t\t\trmSync(path, { force: true });\n\t\t\tcontinue;\n\t\t}\n\t\trecords.push(record);\n\t}\n\treturn records.sort((a, b) => a.startedAt - b.startedAt);\n}\n\n/**\n * Register this process as a running tunnel and keep the record in sync.\n * Returns an `update` to refresh the tunnel list as tunnels connect.\n */\nexport function startRunRecord(\n\tkind: \"http\" | \"tcp\",\n\tserver: string,\n): { update: (tunnels: RunTunnel[]) => void } {\n\tconst pid = process.pid;\n\tconst record: RunRecord = {\n\t\tpid,\n\t\tkind,\n\t\tstartedAt: Date.now(),\n\t\tserver,\n\t\ttunnels: [],\n\t};\n\tsaveRecord(record);\n\n\tconst cleanup = (): void => removeRecord(pid);\n\tprocess.on(\"exit\", cleanup);\n\t// SIGINT is handled by the command (prints stats); SIGTERM needs its own exit\n\t// so `tunelo kill` leaves no stale record.\n\tprocess.on(\"SIGTERM\", () => {\n\t\tcleanup();\n\t\tprocess.exit(0);\n\t});\n\n\treturn {\n\t\tupdate(tunnels: RunTunnel[]): void {\n\t\t\trecord.tunnels = tunnels;\n\t\t\tsaveRecord(record);\n\t\t},\n\t};\n}\n", "import type { Command } from \"commander\";\nimport { listRecords, removeRecord } from \"./run-registry.js\";\n\nfunction formatUptime(ms: number): string {\n\tconst s = Math.floor(ms / 1000);\n\tif (s < 60) return `${s}s`;\n\tconst m = Math.floor(s / 60);\n\tif (m < 60) return `${m}m ${s % 60}s`;\n\treturn `${Math.floor(m / 60)}h ${m % 60}m`;\n}\n\nexport function registerStatusCommands(program: Command): void {\n\tprogram\n\t\t.command(\"status\")\n\t\t.description(\"List tunnels running on this machine\")\n\t\t.action(() => {\n\t\t\tconst records = listRecords();\n\t\t\tif (records.length === 0) {\n\t\t\t\tconsole.log(\"No tunnels running.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst now = Date.now();\n\t\t\tfor (const r of records) {\n\t\t\t\tconst tunnels = r.tunnels.length\n\t\t\t\t\t? r.tunnels.map((t) => `${t.url} \u2192 :${t.localPort}`).join(\", \")\n\t\t\t\t\t: \"(connecting\u2026)\";\n\t\t\t\tconsole.log(\n\t\t\t\t\t`PID ${r.pid} [${r.kind}] up ${formatUptime(now - r.startedAt)} ${tunnels}`,\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\n\tprogram\n\t\t.command(\"kill <target>\")\n\t\t.description(\"Stop a running tunnel by PID, subdomain/URL, or 'all'\")\n\t\t.action((target: string) => {\n\t\t\tconst records = listRecords();\n\t\t\tconst matches =\n\t\t\t\ttarget === \"all\"\n\t\t\t\t\t? records\n\t\t\t\t\t: records.filter(\n\t\t\t\t\t\t\t(r) =>\n\t\t\t\t\t\t\t\tString(r.pid) === target ||\n\t\t\t\t\t\t\t\tr.tunnels.some((t) => t.url.includes(target)),\n\t\t\t\t\t\t);\n\n\t\t\tif (matches.length === 0) {\n\t\t\t\tconsole.log(`No running tunnel matches \"${target}\".`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tfor (const r of matches) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(r.pid, \"SIGTERM\");\n\t\t\t\t\tconsole.log(`Stopped PID ${r.pid} (${r.kind}).`);\n\t\t\t\t} catch {\n\t\t\t\t\t// Process already gone \u2014 clear the stale record.\n\t\t\t\t\tremoveRecord(r.pid);\n\t\t\t\t\tconsole.log(`PID ${r.pid} was not running \u2014 removed stale record.`);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n}\n", "/** TCP proxy \u2014 connects to local TCP services and relays data via WS */\n\nimport net from \"node:net\";\nimport type {\n\tTcpConnectionClose,\n\tTcpConnectionOpen,\n\tTcpDataMessage,\n} from \"@tunelo/shared\";\nimport type { TunnelClient } from \"./tunnel-client.js\";\n\ninterface LocalTcpConnection {\n\tsocket: net.Socket;\n\tconnectionId: string;\n}\n\nexport class TcpProxy {\n\tprivate connections = new Map<string, LocalTcpConnection>();\n\tprivate readonly client: TunnelClient;\n\tprivate readonly localPort: number;\n\tprivate readonly localHost: string;\n\n\tconstructor(\n\t\tclient: TunnelClient,\n\t\tlocalPort: number,\n\t\tlocalHost = \"127.0.0.1\",\n\t) {\n\t\tthis.client = client;\n\t\tthis.localPort = localPort;\n\t\tthis.localHost = localHost;\n\t\tthis.setupHandlers();\n\t}\n\n\tprivate setupHandlers(): void {\n\t\tthis.client.on(\"tcp-connection-open\", (msg: TcpConnectionOpen) => {\n\t\t\tthis.handleConnectionOpen(msg);\n\t\t});\n\n\t\tthis.client.on(\"tcp-data\", (msg: TcpDataMessage) => {\n\t\t\tthis.handleData(msg);\n\t\t});\n\n\t\tthis.client.on(\"tcp-connection-close\", (msg: TcpConnectionClose) => {\n\t\t\tthis.handleConnectionClose(msg);\n\t\t});\n\t}\n\n\tprivate handleConnectionOpen(msg: TcpConnectionOpen): void {\n\t\tconst localSocket = net.createConnection({\n\t\t\thost: this.localHost,\n\t\t\tport: this.localPort,\n\t\t});\n\n\t\tconst conn: LocalTcpConnection = {\n\t\t\tsocket: localSocket,\n\t\t\tconnectionId: msg.connectionId,\n\t\t};\n\t\tthis.connections.set(msg.connectionId, conn);\n\n\t\t// Forward local data to server via WS\n\t\tlocalSocket.on(\"data\", (chunk: Buffer) => {\n\t\t\tthis.client.sendMsg({\n\t\t\t\ttype: \"tcp-data\",\n\t\t\t\tconnectionId: msg.connectionId,\n\t\t\t\tdata: chunk.toString(\"base64\"),\n\t\t\t});\n\t\t});\n\n\t\tlocalSocket.on(\"close\", () => {\n\t\t\tthis.connections.delete(msg.connectionId);\n\t\t\tthis.client.sendMsg({\n\t\t\t\ttype: \"tcp-connection-close\",\n\t\t\t\tconnectionId: msg.connectionId,\n\t\t\t});\n\t\t});\n\n\t\tlocalSocket.on(\"error\", () => {\n\t\t\tlocalSocket.destroy();\n\t\t\tthis.connections.delete(msg.connectionId);\n\t\t\tthis.client.sendMsg({\n\t\t\t\ttype: \"tcp-connection-close\",\n\t\t\t\tconnectionId: msg.connectionId,\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate handleData(msg: TcpDataMessage): void {\n\t\tconst conn = this.connections.get(msg.connectionId);\n\t\tif (conn) {\n\t\t\tconn.socket.write(Buffer.from(msg.data, \"base64\"));\n\t\t}\n\t}\n\n\tprivate handleConnectionClose(msg: TcpConnectionClose): void {\n\t\tconst conn = this.connections.get(msg.connectionId);\n\t\tif (conn) {\n\t\t\tconn.socket.destroy();\n\t\t\tthis.connections.delete(msg.connectionId);\n\t\t}\n\t}\n\n\tclose(): void {\n\t\tfor (const [, conn] of this.connections) {\n\t\t\tconn.socket.destroy();\n\t\t}\n\t\tthis.connections.clear();\n\t}\n}\n", "import { EventEmitter } from \"node:events\";\nimport {\n\ttype AuthMessage,\n\ttype AuthResult,\n\tDEFAULTS,\n\ttype RegisterTunnelMessage,\n\ttype RegisterTunnelResult,\n\ttype ServerToClientMessage,\n\ttype TcpConnectionClose,\n\ttype TcpConnectionOpen,\n\ttype TcpDataMessage,\n\ttype TcpRegisterMessage,\n\ttype TcpRegisterResult,\n\ttype TunnelRequest,\n\tparseMessage,\n} from \"@tunelo/shared\";\nimport WebSocket from \"ws\";\nimport { type ActiveStream, proxyRequestStream } from \"./http-stream.js\";\nimport type { TunnelMapping } from \"./tunnel-config-parser.js\";\nimport { WsAppProxy } from \"./ws-app-proxy.js\";\n\nexport interface TunnelClientOptions {\n\tserverUrl: string;\n\tapiKey: string;\n\tsubdomain?: string;\n\tlocalPort: number;\n\tlocalHost?: string;\n\t/** Basic auth credentials \"user:pass\" */\n\tauth?: string;\n\t/** Additional tunnels to register after initial auth */\n\tadditionalTunnels?: TunnelMapping[];\n\t/** Keepalive probe period (ms). Overridden only by tests \u2014 the wait for a\n\t * missed pong is two of these, and the default makes that a full minute. */\n\tpingIntervalMs?: number;\n\t/** Cap on one connect attempt (ms). Overridden only by tests. */\n\tconnectTimeoutMs?: number;\n}\n\nexport class TunnelClient extends EventEmitter {\n\tprivate socket: WebSocket | null = null;\n\tprivate stopped = false;\n\tprivate attempts = 0;\n\t/** Guards against more than one retry loop running at a time. */\n\tprivate reconnecting = false;\n\tprivate readonly options: TunnelClientOptions;\n\t/** Maps subdomain \u2192 local port for multi-tunnel routing */\n\tprivate subdomainPortMap = new Map<string, number>();\n\t/** In-flight response streams keyed by request id (for cancel/cleanup). */\n\tprivate activeStreams = new Map<string, ActiveStream>();\n\t/** Browser\u2192app WebSocket relay: opens a local WS per `ws-open` and bridges frames. */\n\tprivate readonly wsAppProxy = new WsAppProxy(\n\t\t(msg) => this.socket?.send(JSON.stringify(msg)),\n\t\t(subdomain) =>\n\t\t\tthis.subdomainPortMap.get(subdomain) ?? this.options.localPort,\n\t);\n\n\tconstructor(options: TunnelClientOptions) {\n\t\tsuper();\n\t\tthis.options = options;\n\t}\n\n\tconnect(): Promise<AuthResult> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst wsUrl = this.buildWsUrl();\n\t\t\t// Bind every handler to this exact socket. Reading `this.socket`\n\t\t\t// inside them lets a superseded socket's late events act on the\n\t\t\t// live one \u2014 an \"open\" arriving after a newer socket was assigned\n\t\t\t// used to send the auth frame into a still-CONNECTING socket and\n\t\t\t// throw out of the event loop, killing the CLI mid-reconnect.\n\t\t\t// Cap the handshake. A path that blackholes packets \u2014 the same one that\n\t\t\t// strands the keepalive \u2014 swallows our SYN too, and an uncapped attempt\n\t\t\t// then waits out the kernel's retry budget: measured at 135s against a\n\t\t\t// DROP rule, during which the backoff loop is stuck on that one attempt\n\t\t\t// and cannot retry. The path came back 42s before the client noticed.\n\t\t\tconst socket = new WebSocket(wsUrl, {\n\t\t\t\thandshakeTimeout:\n\t\t\t\t\tthis.options.connectTimeoutMs ?? DEFAULTS.CONNECT_TIMEOUT_MS,\n\t\t\t});\n\t\t\tthis.socket = socket;\n\n\t\t\tlet authResolved = false;\n\t\t\t/** Set only when the server actually granted a tunnel. */\n\t\t\tlet authAccepted = false;\n\t\t\t/** Cleared on close so a dead socket's probes cannot outlive it. */\n\t\t\tlet keepalive: ReturnType<typeof setInterval> | null = null;\n\t\t\t/** Flipped back by every pong; a probe finding it false kills the socket. */\n\t\t\tlet alive = true;\n\n\t\t\tsocket.on(\"pong\", () => {\n\t\t\t\talive = true;\n\t\t\t});\n\n\t\t\tsocket.on(\"open\", () => {\n\t\t\t\tconst authMsg: AuthMessage = {\n\t\t\t\t\ttype: \"auth\",\n\t\t\t\t\tkey: this.options.apiKey,\n\t\t\t\t\tsubdomain: this.options.subdomain ?? \"\",\n\t\t\t\t\tauth: this.options.auth,\n\t\t\t\t};\n\t\t\t\tsocket.send(JSON.stringify(authMsg));\n\n\t\t\t\t// The server probes us and drops the tunnel when we stop answering,\n\t\t\t\t// but nothing told us it had. A path that dies without a FIN \u2014 a NAT\n\t\t\t\t// or firewall dropping the flow, which is how every tunnel behind one\n\t\t\t\t// eventually goes \u2014 leaves this socket ESTABLISHED forever: no\n\t\t\t\t// \"close\" event, so no reconnect, while the server has long since\n\t\t\t\t// unregistered the subdomain and answers callers 502. Probe back so\n\t\t\t\t// we notice on our own instead of waiting for an event that is never\n\t\t\t\t// coming. `terminate` (not `close`) is what a dead peer needs: a\n\t\t\t\t// close handshake waits for a reply that will not arrive.\n\t\t\t\tkeepalive = setInterval(() => {\n\t\t\t\t\tif (socket.readyState !== WebSocket.OPEN) return;\n\t\t\t\t\tif (!alive) {\n\t\t\t\t\t\tsocket.terminate();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\talive = false;\n\t\t\t\t\tsocket.ping();\n\t\t\t\t}, this.options.pingIntervalMs ?? DEFAULTS.PING_INTERVAL_MS);\n\t\t\t\tkeepalive.unref?.();\n\t\t\t});\n\n\t\t\tsocket.on(\"message\", (data) => {\n\t\t\t\tlet msg: ServerToClientMessage;\n\t\t\t\ttry {\n\t\t\t\t\tmsg = parseMessage(data.toString()) as ServerToClientMessage;\n\t\t\t\t} catch {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tswitch (msg.type) {\n\t\t\t\t\tcase \"auth-result\":\n\t\t\t\t\t\tthis.handleAuthResult(msg, authResolved, socket, resolve, reject);\n\t\t\t\t\t\tif (!authResolved) authResolved = true;\n\t\t\t\t\t\tif (msg.success) authAccepted = true;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"register-tunnel-result\":\n\t\t\t\t\t\tthis.handleRegisterTunnelResult(msg);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"tcp-register-result\":\n\t\t\t\t\t\tthis.handleTcpRegisterResult(msg);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"request\":\n\t\t\t\t\t\tthis.handleRequest(msg);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"stream-error\":\n\t\t\t\t\t\t// Server signals the downstream consumer is gone \u2014 stop producing.\n\t\t\t\t\t\tthis.activeStreams.get(msg.id)?.cancel();\n\t\t\t\t\t\tthis.activeStreams.delete(msg.id);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"tcp-connection-open\":\n\t\t\t\t\t\tthis.emit(\"tcp-connection-open\", msg);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"tcp-data\":\n\t\t\t\t\t\tthis.emit(\"tcp-data\", msg);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"tcp-connection-close\":\n\t\t\t\t\t\tthis.emit(\"tcp-connection-close\", msg);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"ws-open\":\n\t\t\t\t\t\tthis.wsAppProxy.open(msg);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"ws-data\":\n\t\t\t\t\t\tthis.wsAppProxy.data(msg);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"ws-close\":\n\t\t\t\t\t\tthis.wsAppProxy.close(msg);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tdefault:\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tsocket.on(\"close\", () => {\n\t\t\t\t// Belongs to this socket, so it is cleared even when a superseded\n\t\t\t\t// socket closes \u2014 otherwise its probes keep firing forever.\n\t\t\t\tif (keepalive) {\n\t\t\t\t\tclearInterval(keepalive);\n\t\t\t\t\tkeepalive = null;\n\t\t\t\t}\n\t\t\t\t// A superseded socket's teardown must not touch live state.\n\t\t\t\tif (this.socket !== socket) return;\n\t\t\t\t// Abort in-flight local requests \u2014 their responses can't be delivered.\n\t\t\t\tfor (const stream of this.activeStreams.values()) stream.cancel();\n\t\t\t\tthis.activeStreams.clear();\n\t\t\t\tthis.wsAppProxy.closeAll();\n\t\t\t\tthis.emit(\"disconnected\");\n\t\t\t\t// Only chase a connection that was actually granted. A rejected auth\n\t\t\t\t// closes this socket too, but the caller's retry loop already owns\n\t\t\t\t// that case \u2014 reconnecting here as well forks a second loop, and each\n\t\t\t\t// fork restarts the backoff at 1s, so the delay never grows.\n\t\t\t\tif (!this.stopped && authAccepted) {\n\t\t\t\t\tthis.reconnect();\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tsocket.on(\"error\", (err: Error) => {\n\t\t\t\tif (!authResolved) {\n\t\t\t\t\tauthResolved = true;\n\t\t\t\t\treject(err);\n\t\t\t\t}\n\t\t\t\tthis.emit(\"error\", err);\n\t\t\t});\n\t\t});\n\t}\n\n\tdisconnect(): void {\n\t\tthis.stopped = true;\n\t\tthis.socket?.close();\n\t}\n\n\t/** Register a TCP tunnel after auth */\n\tregisterTcp(localPort: number, remotePort?: number): void {\n\t\tconst msg: TcpRegisterMessage = {\n\t\t\ttype: \"tcp-register\",\n\t\t\tlocalPort,\n\t\t\tremotePort,\n\t\t};\n\t\tthis.sendMsg(msg);\n\t}\n\n\t/** Get raw socket for TcpProxy to attach handlers */\n\tgetSocket(): WebSocket | null {\n\t\treturn this.socket;\n\t}\n\n\t/** Send a JSON message over the WebSocket */\n\tsendMsg(msg: unknown): void {\n\t\tif (this.socket?.readyState === WebSocket.OPEN) {\n\t\t\tthis.socket.send(JSON.stringify(msg));\n\t\t}\n\t}\n\n\tprivate buildWsUrl(): string {\n\t\t// Convert http(s) to ws(s) and append WS_PATH\n\t\tconst url = this.options.serverUrl\n\t\t\t.replace(/^https:\\/\\//, \"wss://\")\n\t\t\t.replace(/^http:\\/\\//, \"ws://\");\n\t\treturn `${url}${DEFAULTS.WS_PATH}`;\n\t}\n\n\tprivate handleAuthResult(\n\t\tresult: AuthResult,\n\t\talreadyResolved: boolean,\n\t\tsocket: WebSocket,\n\t\tresolve: (r: AuthResult) => void,\n\t\treject: (e: Error) => void,\n\t): void {\n\t\tif (alreadyResolved) return;\n\n\t\tif (!result.success) {\n\t\t\tsocket.close();\n\t\t\treject(new Error(result.error ?? \"Auth failed\"));\n\t\t\treturn;\n\t\t}\n\t\tthis.attempts = 0;\n\t\tthis.subdomainPortMap.set(result.subdomain, this.options.localPort);\n\t\tthis.emit(\"connected\", result);\n\t\tthis.registerAdditionalTunnels();\n\t\tresolve(result);\n\t}\n\n\tprivate handleRegisterTunnelResult(result: RegisterTunnelResult): void {\n\t\tif (result.success) {\n\t\t\tthis.subdomainPortMap.set(result.subdomain, result.localPort);\n\t\t\tthis.emit(\"tunnel-registered\", result);\n\t\t} else {\n\t\t\tthis.emit(\"tunnel-register-error\", result);\n\t\t}\n\t}\n\n\tprivate handleTcpRegisterResult(result: TcpRegisterResult): void {\n\t\tif (result.success) {\n\t\t\tthis.emit(\"tcp-registered\", result);\n\t\t} else {\n\t\t\tthis.emit(\"tcp-register-error\", result);\n\t\t}\n\t}\n\n\tprivate registerAdditionalTunnels(): void {\n\t\tconst tunnels = this.options.additionalTunnels;\n\t\tif (!tunnels?.length) return;\n\n\t\tfor (const tunnel of tunnels) {\n\t\t\tconst msg: RegisterTunnelMessage = {\n\t\t\t\ttype: \"register-tunnel\",\n\t\t\t\tsubdomain: tunnel.subdomain,\n\t\t\t\tlocalPort: tunnel.port,\n\t\t\t\tauth: this.options.auth,\n\t\t\t};\n\t\t\tthis.sendMsg(msg);\n\t\t}\n\t}\n\n\tprivate async handleRequest(request: TunnelRequest): Promise<void> {\n\t\tconst start = Date.now();\n\t\tconst targetPort = request.subdomain\n\t\t\t? (this.subdomainPortMap.get(request.subdomain) ?? this.options.localPort)\n\t\t\t: this.options.localPort;\n\n\t\tconst stream = proxyRequestStream(\n\t\t\trequest,\n\t\t\ttargetPort,\n\t\t\t{\n\t\t\t\tsend: (msg) => this.sendMsg(msg),\n\t\t\t\tbufferedAmount: () => this.socket?.bufferedAmount ?? 0,\n\t\t\t},\n\t\t\tthis.options.localHost,\n\t\t);\n\t\tthis.activeStreams.set(request.id, stream);\n\n\t\tconst result = await stream.done;\n\t\tthis.activeStreams.delete(request.id);\n\t\tconst durationMs = Date.now() - start;\n\n\t\tthis.emit(\"request\", {\n\t\t\tmethod: request.method,\n\t\t\tpath: request.path,\n\t\t\tstatus: result.status,\n\t\t\tdurationMs,\n\t\t\tsubdomain: request.subdomain,\n\t\t});\n\n\t\tthis.emit(\"inspector-entry\", {\n\t\t\tid: request.id,\n\t\t\ttimestamp: start,\n\t\t\tsubdomain: request.subdomain,\n\t\t\tmethod: request.method,\n\t\t\tpath: request.path,\n\t\t\trequestHeaders: request.headers,\n\t\t\trequestBody: request.body,\n\t\t\tstatus: result.status,\n\t\t\tresponseHeaders: result.headers,\n\t\t\tresponseBody: result.bodyPreview,\n\t\t\tdurationMs,\n\t\t\tentryType: \"normal\" as const,\n\t\t});\n\t}\n\n\tprivate async reconnect(): Promise<void> {\n\t\t// One retry loop at a time. Concurrent loops each hold their own `delay`,\n\t\t// so the backoff stops growing and the client hammers the server at the\n\t\t// base interval exactly when the server is least able to take it.\n\t\tif (this.reconnecting) return;\n\t\tthis.reconnecting = true;\n\t\tthis.subdomainPortMap.clear();\n\t\tlet delay: number = DEFAULTS.RECONNECT_BASE_MS;\n\t\ttry {\n\t\t\twhile (!this.stopped) {\n\t\t\t\tthis.attempts++;\n\t\t\t\t// Jitter: every tunnel on a restarted server wakes up together\n\t\t\t\t// otherwise, and they retry in lockstep for as long as it takes.\n\t\t\t\tconst wait = Math.round(delay * (0.5 + Math.random() / 2));\n\t\t\t\tthis.emit(\"reconnecting\", { attempt: this.attempts, delay: wait });\n\t\t\t\tawait new Promise((r) => setTimeout(r, wait));\n\t\t\t\ttry {\n\t\t\t\t\tawait this.connect();\n\t\t\t\t\treturn;\n\t\t\t\t} catch {\n\t\t\t\t\tdelay = Math.min(delay * 2, DEFAULTS.RECONNECT_MAX_MS);\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tthis.reconnecting = false;\n\t\t}\n\t}\n}\n", "/** WS message types for tunnel protocol \u2014 discriminated union on \"type\" field */\n\nexport interface TunnelRequest {\n\ttype: \"request\";\n\tid: string;\n\tmethod: string;\n\tpath: string;\n\theaders: Record<string, string | string[]>;\n\tbody: string | null;\n\t/** Subdomain this request is targeting \u2014 filled by server from Host header */\n\tsubdomain?: string;\n}\n\nexport interface TunnelResponse {\n\ttype: \"response\";\n\tid: string;\n\tstatus: number;\n\theaders: Record<string, string | string[]>;\n\tbody: string | null;\n}\n\n/**\n * Generic streaming frames, keyed by `id`, for relaying a body incrementally\n * (SSE, chunked responses, large downloads) instead of buffering it whole.\n *\n * Deliberately direction-agnostic and not named \"response\" so a future\n * bidirectional relay can reuse the same start \u2192 data* \u2192 end/error lifecycle\n * keyed by a stream id.\n */\nexport interface StreamStartMessage {\n\ttype: \"stream-start\";\n\tid: string;\n\t/** HTTP status for response streams (unused/0 for non-HTTP reuse). */\n\tstatus: number;\n\theaders: Record<string, string | string[]>;\n}\n\nexport interface StreamDataMessage {\n\ttype: \"stream-data\";\n\tid: string;\n\t/** base64-encoded chunk. */\n\tdata: string;\n}\n\nexport interface StreamEndMessage {\n\ttype: \"stream-end\";\n\tid: string;\n}\n\nexport interface StreamErrorMessage {\n\ttype: \"stream-error\";\n\tid: string;\n\tmessage: string;\n}\n\nexport interface AuthMessage {\n\ttype: \"auth\";\n\tkey: string;\n\tsubdomain: string;\n\t/** Basic auth credentials in \"user:pass\" format \u2014 sent during tunnel registration */\n\tauth?: string;\n}\n\nexport interface AuthResult {\n\ttype: \"auth-result\";\n\tsuccess: boolean;\n\tsubdomain: string;\n\turl: string;\n\terror?: string;\n}\n\nexport interface ErrorMessage {\n\ttype: \"error\";\n\tcode: ErrorCode;\n\tmessage: string;\n\trequestId?: string;\n}\n\nexport interface PingMessage {\n\ttype: \"ping\";\n\ttimestamp: number;\n}\n\nexport interface PongMessage {\n\ttype: \"pong\";\n\ttimestamp: number;\n}\n\n/** Register additional tunnel on existing WS connection (after initial auth) */\nexport interface RegisterTunnelMessage {\n\ttype: \"register-tunnel\";\n\tsubdomain?: string;\n\tlocalPort: number;\n\tauth?: string;\n}\n\n/** Server response to register-tunnel */\nexport interface RegisterTunnelResult {\n\ttype: \"register-tunnel-result\";\n\tsuccess: boolean;\n\tsubdomain: string;\n\tlocalPort: number;\n\turl: string;\n\terror?: string;\n}\n\n/** TCP tunnel \u2014 register a TCP port mapping */\nexport interface TcpRegisterMessage {\n\ttype: \"tcp-register\";\n\tlocalPort: number;\n\tremotePort?: number;\n}\n\n/** TCP tunnel \u2014 server response to tcp-register */\nexport interface TcpRegisterResult {\n\ttype: \"tcp-register-result\";\n\tsuccess: boolean;\n\tlocalPort: number;\n\tremotePort: number;\n\turl: string;\n\terror?: string;\n}\n\n/** TCP tunnel \u2014 bidirectional binary data relay (base64 encoded) */\nexport interface TcpDataMessage {\n\ttype: \"tcp-data\";\n\tconnectionId: string;\n\tdata: string;\n}\n\n/** TCP tunnel \u2014 new TCP connection opened on server */\nexport interface TcpConnectionOpen {\n\ttype: \"tcp-connection-open\";\n\tconnectionId: string;\n\tremotePort: number;\n\tsourceIp: string;\n}\n\n/** TCP tunnel \u2014 connection closed (bidirectional) */\nexport interface TcpConnectionClose {\n\ttype: \"tcp-connection-close\";\n\tconnectionId: string;\n}\n\n/** Browser WebSocket relay \u2014 ask the client to open a local WS (server \u2192 client) */\nexport interface WsOpenMessage {\n\ttype: \"ws-open\";\n\tid: string;\n\t/** Tunnel subdomain the browser connected to. */\n\tsubdomain: string;\n\t/** Request path/query of the upgrade (e.g. \"/socket.io?token=x\"). */\n\tpath: string;\n\theaders: Record<string, string | string[]>;\n\t/** Requested Sec-WebSocket-Protocol subprotocol(s), if any. */\n\tprotocol?: string;\n}\n/** Browser WebSocket relay \u2014 frame payload (bidirectional). */\nexport interface WsDataMessage {\n\ttype: \"ws-data\";\n\tid: string;\n\t/** base64-encoded frame payload. */\n\tdata: string;\n\t/** true = binary frame, false = UTF-8 text frame. */\n\tbinary: boolean;\n}\n/** Browser WebSocket relay \u2014 connection closed (bidirectional). */\nexport interface WsCloseMessage {\n\ttype: \"ws-close\";\n\tid: string;\n\tcode?: number;\n\treason?: string;\n}\n\n/** Streaming lifecycle frames for one relayed body (start \u2192 data* \u2192 end/error). */\nexport type StreamMessage =\n\t| StreamStartMessage\n\t| StreamDataMessage\n\t| StreamEndMessage\n\t| StreamErrorMessage;\n\nexport type ServerToClientMessage =\n\t| TunnelRequest\n\t| StreamMessage\n\t| AuthResult\n\t| RegisterTunnelResult\n\t| TcpRegisterResult\n\t| TcpConnectionOpen\n\t| TcpDataMessage\n\t| TcpConnectionClose\n\t| WsOpenMessage\n\t| WsDataMessage\n\t| WsCloseMessage\n\t| ErrorMessage\n\t| PingMessage;\nexport type ClientToServerMessage =\n\t| TunnelResponse\n\t| StreamMessage\n\t| AuthMessage\n\t| RegisterTunnelMessage\n\t| TcpRegisterMessage\n\t| TcpDataMessage\n\t| TcpConnectionClose\n\t| WsDataMessage\n\t| WsCloseMessage\n\t| ErrorMessage\n\t| PongMessage;\nexport type TunnelMessage = ServerToClientMessage | ClientToServerMessage;\n\n/** Parse raw WS text message into typed protocol message */\nexport function parseMessage(data: string): TunnelMessage {\n\treturn JSON.parse(data) as TunnelMessage;\n}\n\n/** WS close codes for clean disconnect reasons */\nexport const WS_CLOSE_CODES = {\n\tNORMAL: 1000,\n\tGOING_AWAY: 1001,\n\tAUTH_FAILED: 4001,\n\tAUTH_TIMEOUT: 4002,\n\tRATE_LIMITED: 4003,\n\tSUBDOMAIN_TAKEN: 4004,\n} as const;\n\nexport enum ErrorCode {\n\tAUTH_FAILED = \"AUTH_FAILED\",\n\tSUBDOMAIN_TAKEN = \"SUBDOMAIN_TAKEN\",\n\tSUBDOMAIN_INVALID = \"SUBDOMAIN_INVALID\",\n\tREQUEST_TIMEOUT = \"REQUEST_TIMEOUT\",\n\tTUNNEL_NOT_FOUND = \"TUNNEL_NOT_FOUND\",\n\tBODY_TOO_LARGE = \"BODY_TOO_LARGE\",\n\tUNAUTHORIZED = \"UNAUTHORIZED\",\n\tINTERNAL_ERROR = \"INTERNAL_ERROR\",\n\tINVALID_CREDENTIALS = \"INVALID_CREDENTIALS\",\n\tTOTP_REQUIRED = \"TOTP_REQUIRED\",\n\tTOTP_INVALID = \"TOTP_INVALID\",\n\tMAX_KEYS_REACHED = \"MAX_KEYS_REACHED\",\n\tKEY_NOT_FOUND = \"KEY_NOT_FOUND\",\n\tEMAIL_TAKEN = \"EMAIL_TAKEN\",\n\tADMIN_FORBIDDEN = \"ADMIN_FORBIDDEN\",\n\tMAX_TUNNELS_EXCEEDED = \"MAX_TUNNELS_EXCEEDED\",\n}\n", "/** Shared constants for tunnel protocol */\n\nexport const DEFAULTS = {\n\tSERVER_PORT: 3001,\n\tWS_PATH: \"/tunnel\",\n\tHEALTH_PATH: \"/health\",\n\tMAX_BODY_SIZE: 50 * 1024 * 1024,\n\tREQUEST_TIMEOUT_MS: 30_000,\n\t/** Idle timeout (ms) for a relayed response stream \u2014 reset on every frame so SSE/long downloads stay open */\n\tSTREAM_IDLE_TIMEOUT_MS: 60_000,\n\tPING_INTERVAL_MS: 30_000,\n\t/** Cap on the WS handshake, TCP connect included. Without one, a blackholed\n\t * SYN stalls the attempt for the kernel's whole retry budget (~135s\n\t * measured), and the reconnect backoff sits idle for all of it. */\n\tCONNECT_TIMEOUT_MS: 10_000,\n\tRECONNECT_BASE_MS: 1_000,\n\tRECONNECT_MAX_MS: 30_000,\n\tMAX_SUBDOMAIN_LENGTH: 63,\n\t/** Max WebSocket messages per second per connection */\n\tWS_RATE_LIMIT: 1000,\n\t/** Grace period (ms) to wait for client reconnect before rejecting pending requests */\n\tRECONNECT_GRACE_MS: 5_000,\n\t/** TCP tunnel port range start */\n\tTCP_PORT_MIN: 10_000,\n\t/** TCP tunnel port range end */\n\tTCP_PORT_MAX: 10_999,\n\t/** Max TCP tunnels per server */\n\tTCP_MAX_TUNNELS: 100,\n\t/** Max concurrent TCP connections per tunnel */\n\tTCP_MAX_CONNECTIONS_PER_TUNNEL: 50,\n\t/** TCP idle connection timeout (ms) \u2014 10 minutes */\n\tTCP_IDLE_TIMEOUT_MS: 10 * 60 * 1000,\n\t/** Max new TCP connections per second per tunnel */\n\tTCP_CONNECT_RATE_LIMIT: 10,\n\t/** Max concurrent browser WebSocket connections per tunnel */\n\tWS_APP_MAX_PER_TUNNEL: 50,\n\t/** Local WS handshake timeout (ms) for the browser\u2192app relay */\n\tWS_APP_HANDSHAKE_TIMEOUT_MS: 10_000,\n\t/** Max frames buffered before the local WS finishes opening */\n\tWS_APP_PREOPEN_QUEUE_MAX: 64,\n} as const;\n\nexport const SUBDOMAIN_REGEX = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;\n\nexport const TUNNEL_DOMAIN = \"tunnel.inetdev.io.vn\";\n\nexport function buildTunnelUrl(subdomain: string): string {\n\treturn `https://${subdomain}.${TUNNEL_DOMAIN}`;\n}\n\nexport function generateRequestId(): string {\n\treturn `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;\n}\n", "/** Hop-by-hop headers (RFC 2616 \u00A713.5.1) that must not be forwarded/relayed. */\nconst HOP_BY_HOP = new Set([\n\t\"connection\",\n\t\"keep-alive\",\n\t\"proxy-authenticate\",\n\t\"proxy-authorization\",\n\t\"te\",\n\t\"trailers\",\n\t\"transfer-encoding\",\n\t\"upgrade\",\n]);\n\n/**\n * Drop hop-by-hop headers. Applied to requests before proxying to the local\n * service, and to responses before re-streaming \u2014 otherwise a relayed\n * `transfer-encoding: chunked` collides with the relay's own framing.\n */\nexport function stripHopByHopHeaders(\n\theaders: Record<string, string | string[]>,\n): Record<string, string | string[]> {\n\tconst out: Record<string, string | string[]> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (!HOP_BY_HOP.has(key.toLowerCase())) out[key] = value;\n\t}\n\treturn out;\n}\n\n/**\n * True when a response must be relayed without buffering \u2014 Server-Sent Events,\n * or any body with no `content-length` (chunked / unknown-length stream). The\n * tunnel server sets `X-Accel-Buffering: no` on these so a fronting nginx\n * streams them straight through and skips caching, while length-bounded static\n * responses stay bufferable and cacheable.\n */\nexport function needsUnbufferedRelay(\n\theaders: Record<string, string | string[]>,\n): boolean {\n\tlet contentType = \"\";\n\tlet hasContentLength = false;\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tconst lower = key.toLowerCase();\n\t\tif (lower === \"content-type\") {\n\t\t\tcontentType = Array.isArray(value) ? value.join(\" \") : value;\n\t\t}\n\t\tif (lower === \"content-length\") hasContentLength = true;\n\t}\n\treturn contentType.includes(\"text/event-stream\") || !hasContentLength;\n}\n", "import http from \"node:http\";\nimport { DEFAULTS, stripHopByHopHeaders } from \"@tunelo/shared\";\nimport type {\n\tStreamDataMessage,\n\tStreamEndMessage,\n\tStreamErrorMessage,\n\tStreamStartMessage,\n\tTunnelRequest,\n} from \"@tunelo/shared\";\n\ntype StreamFrame =\n\t| StreamStartMessage\n\t| StreamDataMessage\n\t| StreamEndMessage\n\t| StreamErrorMessage;\n\n/** Transport the stream writes to \u2014 decoupled from ws so a WS relay can reuse it. */\nexport interface StreamTransport {\n\tsend(frame: StreamFrame): void;\n\t/** Bytes queued but not yet flushed \u2014 drives backpressure. */\n\tbufferedAmount(): number;\n}\n\n/** Summary of a completed stream \u2014 feeds the request inspector (body capped). */\nexport interface ProxyStreamResult {\n\tstatus: number;\n\theaders: Record<string, string | string[]>;\n\tbodyPreview: string | null;\n\ttruncated: boolean;\n}\n\nexport interface ActiveStream {\n\t/** Abort the local request (the downstream consumer went away). */\n\tcancel(): void;\n\t/** Resolves once the local response finishes, errors, or is cancelled. */\n\tdone: Promise<ProxyStreamResult>;\n}\n\n/** Cap on body bytes captured for the inspector \u2014 streams are not fully buffered. */\nconst INSPECTOR_PREVIEW_CAP = 256 * 1024;\n/** Coalesce chunks into frames of about this size to bound frames/sec. */\nconst FRAME_BYTES = 256 * 1024;\n/** Pause the local read when the transport queue grows past this (backpressure). */\nconst HIGH_WATER = 4 * 1024 * 1024;\nconst LOW_WATER = 1 * 1024 * 1024;\n\n/**\n * Relay one request to the local service and stream the response back as\n * stream-start \u2192 stream-data* \u2192 stream-end. Chunks are coalesced by size,\n * the local read is paused when the transport queue is full, and exactly one\n * terminal path (end / error / cancel) ever runs.\n */\nexport function proxyRequestStream(\n\trequest: TunnelRequest,\n\tlocalPort: number,\n\ttransport: StreamTransport,\n\tlocalHost = \"localhost\",\n): ActiveStream {\n\tconst id = request.id;\n\tlet settled = false;\n\tlet started = false;\n\tlet resStream: http.IncomingMessage | null = null;\n\tlet resolveDone!: (r: ProxyStreamResult) => void;\n\tconst done = new Promise<ProxyStreamResult>((r) => {\n\t\tresolveDone = r;\n\t});\n\n\tconst previewChunks: Buffer[] = [];\n\tlet previewSize = 0;\n\tlet truncated = false;\n\tlet status = 502;\n\tlet headers: Record<string, string | string[]> = {};\n\n\tfunction capturePreview(chunk: Buffer): void {\n\t\tif (truncated) return;\n\t\tconst remaining = INSPECTOR_PREVIEW_CAP - previewSize;\n\t\tif (remaining <= 0) {\n\t\t\ttruncated = true;\n\t\t\treturn;\n\t\t}\n\t\tconst slice =\n\t\t\tchunk.length > remaining ? chunk.subarray(0, remaining) : chunk;\n\t\tpreviewChunks.push(slice);\n\t\tpreviewSize += slice.length;\n\t\tif (chunk.length > remaining) truncated = true;\n\t}\n\n\tfunction finish(): void {\n\t\tif (settled) return;\n\t\tsettled = true;\n\t\tresolveDone({\n\t\t\tstatus,\n\t\t\theaders,\n\t\t\tbodyPreview: previewChunks.length\n\t\t\t\t? Buffer.concat(previewChunks).toString(\"base64\")\n\t\t\t\t: null,\n\t\t\ttruncated,\n\t\t});\n\t}\n\n\tfunction fail(message: string): void {\n\t\tif (settled) return;\n\t\tif (started) {\n\t\t\ttransport.send({ type: \"stream-error\", id, message });\n\t\t} else {\n\t\t\tstatus = 502;\n\t\t\theaders = { \"content-type\": \"text/plain\" };\n\t\t\tconst payload = Buffer.from(message);\n\t\t\tcapturePreview(payload);\n\t\t\ttransport.send({ type: \"stream-start\", id, status, headers });\n\t\t\ttransport.send({\n\t\t\t\ttype: \"stream-data\",\n\t\t\t\tid,\n\t\t\t\tdata: payload.toString(\"base64\"),\n\t\t\t});\n\t\t\ttransport.send({ type: \"stream-end\", id });\n\t\t}\n\t\tfinish();\n\t}\n\n\tlet pending: Buffer[] = [];\n\tlet pendingBytes = 0;\n\tlet flushScheduled = false;\n\tfunction flushNow(): void {\n\t\tif (pending.length === 0 || settled) return;\n\t\tconst buf = Buffer.concat(pending);\n\t\tpending = [];\n\t\tpendingBytes = 0;\n\t\ttransport.send({ type: \"stream-data\", id, data: buf.toString(\"base64\") });\n\t\tapplyBackpressure();\n\t}\n\tfunction scheduleFlush(): void {\n\t\tif (flushScheduled) return;\n\t\tflushScheduled = true;\n\t\tsetImmediate(() => {\n\t\t\tflushScheduled = false;\n\t\t\tflushNow();\n\t\t});\n\t}\n\tfunction applyBackpressure(): void {\n\t\tif (!resStream || resStream.isPaused()) return;\n\t\tif (transport.bufferedAmount() <= HIGH_WATER) return;\n\t\tresStream.pause();\n\t\tconst timer = setInterval(() => {\n\t\t\tif (settled) {\n\t\t\t\tclearInterval(timer);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (transport.bufferedAmount() <= LOW_WATER) {\n\t\t\t\tclearInterval(timer);\n\t\t\t\tresStream?.resume();\n\t\t\t}\n\t\t}, 25);\n\t}\n\n\tconst body = request.body ? Buffer.from(request.body, \"base64\") : null;\n\tconst proxyReq = http.request(\n\t\t{\n\t\t\thostname: localHost,\n\t\t\tport: localPort,\n\t\t\tpath: request.path,\n\t\t\tmethod: request.method,\n\t\t\theaders: stripHopByHopHeaders(request.headers),\n\t\t},\n\t\t(proxyRes) => {\n\t\t\tif (settled) return;\n\t\t\tresStream = proxyRes;\n\t\t\tstatus = proxyRes.statusCode ?? 502;\n\t\t\t// Strip hop-by-hop headers \u2014 the tunnel server applies its own framing.\n\t\t\theaders = stripHopByHopHeaders(\n\t\t\t\tproxyRes.headers as Record<string, string | string[]>,\n\t\t\t);\n\t\t\tstarted = true;\n\t\t\ttransport.send({ type: \"stream-start\", id, status, headers });\n\n\t\t\tproxyRes.on(\"data\", (chunk: Buffer) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tcapturePreview(chunk);\n\t\t\t\tpending.push(chunk);\n\t\t\t\tpendingBytes += chunk.length;\n\t\t\t\tif (pendingBytes >= FRAME_BYTES) flushNow();\n\t\t\t\telse scheduleFlush();\n\t\t\t});\n\t\t\tproxyRes.on(\"end\", () => {\n\t\t\t\tif (settled) return;\n\t\t\t\tflushNow();\n\t\t\t\ttransport.send({ type: \"stream-end\", id });\n\t\t\t\tfinish();\n\t\t\t});\n\t\t\tproxyRes.on(\"error\", () => fail(\"Local response error\"));\n\t\t\tproxyRes.on(\"aborted\", () => fail(\"Local response aborted\"));\n\t\t},\n\t);\n\n\tproxyReq.on(\"error\", (err) => {\n\t\tconst code = (err as NodeJS.ErrnoException).code;\n\t\tfail(code === \"ECONNREFUSED\" ? \"Local service unavailable\" : err.message);\n\t});\n\n\tproxyReq.setTimeout(DEFAULTS.STREAM_IDLE_TIMEOUT_MS, () => {\n\t\tfail(\"Local service timeout\");\n\t\tproxyReq.destroy();\n\t});\n\n\tif (body) proxyReq.write(body);\n\tproxyReq.end();\n\n\treturn {\n\t\tcancel(): void {\n\t\t\tif (settled) return;\n\t\t\tsettled = true;\n\t\t\tproxyReq.destroy();\n\t\t\tresolveDone({\n\t\t\t\tstatus,\n\t\t\t\theaders,\n\t\t\t\tbodyPreview: previewChunks.length\n\t\t\t\t\t? Buffer.concat(previewChunks).toString(\"base64\")\n\t\t\t\t\t: null,\n\t\t\t\ttruncated,\n\t\t\t});\n\t\t},\n\t\tdone,\n\t};\n}\n", "import {\n\tDEFAULTS,\n\ttype WsCloseMessage,\n\ttype WsDataMessage,\n\ttype WsOpenMessage,\n\tstripHopByHopHeaders,\n} from \"@tunelo/shared\";\nimport WebSocket, { type RawData } from \"ws\";\n\nexport type WsAppSend = (msg: WsDataMessage | WsCloseMessage) => void;\n\ninterface LocalConn {\n\tws: WebSocket;\n\tready: boolean;\n\t/** Frames from the browser that arrived before the local WS finished opening. */\n\tqueue: WsDataMessage[];\n}\n\nfunction toBuffer(data: RawData): Buffer {\n\tif (Buffer.isBuffer(data)) return data;\n\tif (Array.isArray(data)) return Buffer.concat(data);\n\treturn Buffer.from(data as ArrayBuffer);\n}\n\nfunction safeClose(ws: WebSocket, code?: number, reason?: string): void {\n\ttry {\n\t\tif (code && code >= 1000 && code <= 4999) ws.close(code, reason);\n\t\telse ws.close();\n\t} catch {\n\t\tws.terminate();\n\t}\n}\n\n/** Handshake headers the `ws` client sets itself \u2014 must not be forwarded. */\nconst WS_HANDSHAKE_HEADERS = [\n\t\"host\",\n\t\"sec-websocket-key\",\n\t\"sec-websocket-version\",\n\t\"sec-websocket-extensions\",\n\t\"sec-websocket-protocol\",\n\t\"content-length\",\n];\n\n/**\n * Client side of the WebSocket-to-app relay: opens a local WS per `ws-open`\n * and bridges frames both ways. Mirrors the server's connection model.\n */\nexport class WsAppProxy {\n\tprivate conns = new Map<string, LocalConn>();\n\n\tconstructor(\n\t\tprivate readonly send: WsAppSend,\n\t\tprivate readonly resolvePort: (subdomain: string) => number,\n\t) {}\n\n\topen(msg: WsOpenMessage): void {\n\t\tconst port = this.resolvePort(msg.subdomain);\n\t\tconst headers = stripHopByHopHeaders(msg.headers);\n\t\tfor (const h of WS_HANDSHAKE_HEADERS) delete headers[h];\n\n\t\tconst url = `ws://localhost:${port}${msg.path}`;\n\t\tconst protocols = msg.protocol\n\t\t\t? msg.protocol\n\t\t\t\t\t.split(\",\")\n\t\t\t\t\t.map((s) => s.trim())\n\t\t\t\t\t.filter(Boolean)\n\t\t\t: undefined;\n\t\tconst opts = {\n\t\t\theaders,\n\t\t\thandshakeTimeout: DEFAULTS.WS_APP_HANDSHAKE_TIMEOUT_MS,\n\t\t};\n\t\tconst local = protocols\n\t\t\t? new WebSocket(url, protocols, opts)\n\t\t\t: new WebSocket(url, opts);\n\n\t\tconst conn: LocalConn = { ws: local, ready: false, queue: [] };\n\t\tthis.conns.set(msg.id, conn);\n\n\t\tlocal.on(\"open\", () => {\n\t\t\tconn.ready = true;\n\t\t\tfor (const q of conn.queue) {\n\t\t\t\tlocal.send(Buffer.from(q.data, \"base64\"), { binary: q.binary });\n\t\t\t}\n\t\t\tconn.queue = [];\n\t\t});\n\t\tlocal.on(\"message\", (data: RawData, isBinary: boolean) => {\n\t\t\tthis.send({\n\t\t\t\ttype: \"ws-data\",\n\t\t\t\tid: msg.id,\n\t\t\t\tdata: toBuffer(data).toString(\"base64\"),\n\t\t\t\tbinary: isBinary,\n\t\t\t});\n\t\t});\n\t\tlocal.on(\"close\", (code: number, reason: Buffer) => {\n\t\t\tif (!this.conns.delete(msg.id)) return;\n\t\t\tthis.send({\n\t\t\t\ttype: \"ws-close\",\n\t\t\t\tid: msg.id,\n\t\t\t\tcode,\n\t\t\t\treason: reason.toString(),\n\t\t\t});\n\t\t});\n\t\tlocal.on(\"error\", () => {\n\t\t\t// A close event follows; nothing to forward here.\n\t\t});\n\t}\n\n\tdata(msg: WsDataMessage): void {\n\t\tconst conn = this.conns.get(msg.id);\n\t\tif (!conn) return;\n\t\tif (conn.ready && conn.ws.readyState === WebSocket.OPEN) {\n\t\t\tconn.ws.send(Buffer.from(msg.data, \"base64\"), { binary: msg.binary });\n\t\t\treturn;\n\t\t}\n\t\t// Local WS not open yet \u2014 buffer, but bound it so a stalled handshake\n\t\t// can't grow memory without limit.\n\t\tif (conn.queue.length >= DEFAULTS.WS_APP_PREOPEN_QUEUE_MAX) {\n\t\t\tconn.ws.close();\n\t\t\treturn;\n\t\t}\n\t\tconn.queue.push(msg);\n\t}\n\n\tclose(msg: WsCloseMessage): void {\n\t\tconst conn = this.conns.get(msg.id);\n\t\tif (!conn) return;\n\t\tthis.conns.delete(msg.id);\n\t\tsafeClose(conn.ws, msg.code, msg.reason);\n\t}\n\n\t/** Control socket dropped \u2014 tear down all local WS connections. */\n\tcloseAll(): void {\n\t\tfor (const conn of this.conns.values()) conn.ws.close();\n\t\tthis.conns.clear();\n\t}\n}\n", "/**\n * Single source of truth for the CLI version.\n * Must match packages/client/package.json \u2014 version.test.ts fails the build if it drifts.\n */\nexport const VERSION = \"1.6.2\";\n\n/** npm package name, used by `tunelo update` to talk to the registry */\nexport const PACKAGE_NAME = \"tunelo\";\n", "/** CLI handler for `tunelo tcp <port>` command */\n\nimport type { TcpRegisterResult } from \"@tunelo/shared\";\nimport type { Command } from \"commander\";\nimport { loadConfig } from \"./config.js\";\nimport type { Display } from \"./display.js\";\nimport { TcpProxy } from \"./tcp-proxy.js\";\nimport { TunnelClient } from \"./tunnel-client.js\";\nimport { VERSION } from \"./version.js\";\n\nexport function registerTcpCommand(program: Command, display: Display): void {\n\tprogram\n\t\t.command(\"tcp <port>\")\n\t\t.description(\"Create TCP tunnel to local port\")\n\t\t.option(\"-k, --key <apikey>\", \"API key for authentication\")\n\t\t.option(\"--server <url>\", \"Tunnel server URL\")\n\t\t.option(\"--remote-port <port>\", \"Request specific remote port\")\n\t\t.action(\n\t\t\tasync (\n\t\t\t\tportStr: string,\n\t\t\t\toptions: { key?: string; server?: string; remotePort?: string },\n\t\t\t) => {\n\t\t\t\tconst port = Number(portStr);\n\t\t\t\tif (Number.isNaN(port) || port < 1 || port > 65535) {\n\t\t\t\t\tdisplay.showError(\"Invalid port number\");\n\t\t\t\t\tprocess.exit(1);\n\t\t\t\t}\n\n\t\t\t\tconst config = loadConfig();\n\t\t\t\tconst apiKey = options.key ?? process.env.TUNELO_KEY ?? config.key;\n\t\t\t\tconst serverUrl =\n\t\t\t\t\toptions.server ??\n\t\t\t\t\tprocess.env.TUNELO_SERVER ??\n\t\t\t\t\tconfig.server ??\n\t\t\t\t\t\"wss://tunnel.inetdev.io.vn\";\n\n\t\t\t\tif (!apiKey) {\n\t\t\t\t\tdisplay.showError(\"API key required.\");\n\t\t\t\t\tprocess.exit(1);\n\t\t\t\t}\n\n\t\t\t\tdisplay.showBanner(VERSION);\n\t\t\t\tdisplay.showConnecting(serverUrl);\n\n\t\t\t\tconst client = new TunnelClient({ serverUrl, apiKey, localPort: port });\n\n\t\t\t\tclient.on(\"connected\", () => {\n\t\t\t\t\tconst remotePort = options.remotePort\n\t\t\t\t\t\t? Number(options.remotePort)\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tclient.registerTcp(port, remotePort);\n\t\t\t\t});\n\n\t\t\t\tclient.on(\"tcp-registered\", (result: TcpRegisterResult) => {\n\t\t\t\t\tdisplay.showTcpTunnel(result.url, port);\n\t\t\t\t\tnew TcpProxy(client, port);\n\t\t\t\t});\n\n\t\t\t\tclient.on(\"tcp-register-error\", (result: TcpRegisterResult) => {\n\t\t\t\t\tdisplay.showError(`TCP tunnel failed: ${result.error}`);\n\t\t\t\t});\n\n\t\t\t\tclient.on(\"disconnected\", () => display.showDisconnected());\n\t\t\t\tclient.on(\n\t\t\t\t\t\"reconnecting\",\n\t\t\t\t\t(info: { attempt: number; delay: number }) => {\n\t\t\t\t\t\tdisplay.showReconnecting(info.attempt, info.delay);\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tclient.on(\"error\", (err: Error) => display.showError(err.message));\n\n\t\t\t\ttry {\n\t\t\t\t\tawait client.connect();\n\t\t\t\t} catch (err) {\n\t\t\t\t\tdisplay.showError((err as Error).message);\n\t\t\t\t\tprocess.exit(1);\n\t\t\t\t}\n\n\t\t\t\tprocess.on(\"SIGINT\", () => {\n\t\t\t\t\tclient.disconnect();\n\t\t\t\t\tdisplay.showStats();\n\t\t\t\t\tprocess.exit(0);\n\t\t\t\t});\n\t\t\t},\n\t\t);\n}\n", "/** CLI handler for `tunelo update` \u2014 self-update from the configured registry */\n\nimport { type SpawnOptions, spawn } from \"node:child_process\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport chalk from \"chalk\";\nimport type { Command } from \"commander\";\nimport { PACKAGE_NAME, VERSION } from \"./version.js\";\n\n/** Windows ships npm/pnpm as .cmd shims, which Node refuses to spawn directly */\nconst isWindows = process.platform === \"win32\";\n\nexport type PackageManager = \"npm\" | \"pnpm\";\n\n/** Infers the installer from where this file ended up on disk */\nexport function detectPackageManager(modulePath: string): PackageManager {\n\tconst normalized = modulePath.split(path.win32.sep).join(path.posix.sep);\n\treturn normalized.includes(\"/pnpm/\") ? \"pnpm\" : \"npm\";\n}\n\n/** Returns >0 when `a` is newer than `b`, <0 when older, 0 when equal */\nexport function compareVersions(a: string, b: string): number {\n\tconst parse = (v: string) =>\n\t\tv\n\t\t\t.replace(/^v/, \"\")\n\t\t\t.split(\"-\")[0]\n\t\t\t.split(\".\")\n\t\t\t.map((part) => Number.parseInt(part, 10) || 0);\n\tconst left = parse(a);\n\tconst right = parse(b);\n\tfor (let i = 0; i < Math.max(left.length, right.length); i++) {\n\t\tconst diff = (left[i] ?? 0) - (right[i] ?? 0);\n\t\tif (diff !== 0) return diff;\n\t}\n\treturn 0;\n}\n\n/** Builds the install argv for the detected package manager */\nexport function buildInstallArgs(\n\tmanager: PackageManager,\n\ttarget: string,\n): string[] {\n\treturn manager === \"pnpm\" ? [\"add\", \"-g\", target] : [\"install\", \"-g\", target];\n}\n\n/**\n * Spawns npm/pnpm. On Windows the .cmd shim needs a shell, so the whole command\n * goes through as a single string \u2014 passing an argv array alongside `shell` would\n * concatenate it unescaped. Every token here is a compile-time constant: the\n * manager is a two-value union and the package name is hard-coded, so nothing\n * user-controlled ever reaches the shell.\n */\nfunction spawnManager(\n\tmanager: PackageManager,\n\targs: string[],\n\toptions: SpawnOptions,\n) {\n\treturn isWindows\n\t\t? spawn([manager, ...args].join(\" \"), { ...options, shell: true })\n\t\t: spawn(manager, args, options);\n}\n\n/** Asks the registry for the newest published version */\nfunction fetchLatestVersion(manager: PackageManager): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst child = spawnManager(manager, [\"view\", PACKAGE_NAME, \"version\"], {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t\ttimeout: 60_000,\n\t\t});\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\t\tchild.stdout?.on(\"data\", (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr?.on(\"data\", (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\t\tchild.on(\"error\", reject);\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (code !== 0) {\n\t\t\t\treject(new Error(stderr.trim().split(\"\\n\").pop() ?? `exit ${code}`));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst version = stdout.trim().split(\"\\n\").pop()?.trim();\n\t\t\tif (!version) {\n\t\t\t\treject(new Error(\"Registry returned no version\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve(version);\n\t\t});\n\t});\n}\n\n/** Runs the install and resolves with the child's exit code */\nfunction runInstall(manager: PackageManager, target: string): Promise<number> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst child = spawnManager(manager, buildInstallArgs(manager, target), {\n\t\t\tstdio: \"inherit\",\n\t\t});\n\t\tchild.on(\"error\", reject);\n\t\tchild.on(\"close\", (code) => resolve(code ?? 1));\n\t});\n}\n\nexport function registerUpdateCommand(program: Command): void {\n\tprogram\n\t\t.command(\"update\")\n\t\t.description(\"Update tunelo to the latest published version\")\n\t\t.option(\"--check\", \"Only report whether an update exists\")\n\t\t.action(async (options: { check?: boolean }) => {\n\t\t\tconst manager = detectPackageManager(fileURLToPath(import.meta.url));\n\n\t\t\tlet latest: string;\n\t\t\ttry {\n\t\t\t\tconsole.log(chalk.gray(\" Checking registry...\"));\n\t\t\t\tlatest = await fetchLatestVersion(manager);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.log(\n\t\t\t\t\tchalk.red(\n\t\t\t\t\t\t` Error: cannot reach registry \u2014 ${(err as Error).message}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tconsole.log(\n\t\t\t\t\tchalk.gray(\n\t\t\t\t\t\t` Update manually: ${manager} ${buildInstallArgs(manager, `${PACKAGE_NAME}@latest`).join(\" \")}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (compareVersions(latest, VERSION) <= 0) {\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ${chalk.green(\"Already up to date\")} \u2014 v${VERSION} is the latest.`,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconsole.log(\n\t\t\t\t` Update available: ${chalk.gray(`v${VERSION}`)} \u2192 ${chalk.cyan(`v${latest}`)}`,\n\t\t\t);\n\n\t\t\tif (options.check) {\n\t\t\t\tconsole.log(chalk.gray(\" Run `tunelo update` to install it.\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconsole.log(chalk.gray(` Installing via ${manager}...`));\n\t\t\tlet code: number;\n\t\t\ttry {\n\t\t\t\tcode = await runInstall(manager, `${PACKAGE_NAME}@latest`);\n\t\t\t} catch (err) {\n\t\t\t\tconsole.log(\n\t\t\t\t\tchalk.red(\n\t\t\t\t\t\t` Error: ${manager} failed to start \u2014 ${(err as Error).message}`,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tprocess.exitCode = 1;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (code !== 0) {\n\t\t\t\tconsole.log(chalk.red(` Update failed (${manager} exited ${code}).`));\n\t\t\t\tprocess.exitCode = code;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconsole.log(` ${chalk.green(\"Updated\")} to v${latest}.`);\n\t\t\t// Running tunnels keep the old code in memory \u2014 restart them to pick this up.\n\t\t\tconsole.log(chalk.gray(\" Restart any running tunnels to apply.\"));\n\t\t});\n}\n", "import chalk from \"chalk\";\n\nexport type LogLevel = \"quiet\" | \"normal\" | \"verbose\";\n\n/** Mask API key for display \u2014 show first 4 and last 4 chars */\nfunction maskKey(key: string): string {\n\tif (key.length <= 8) return \"****\";\n\treturn `${key.slice(0, 4)}...${key.slice(-4)}`;\n}\n\n/** Format duration in human-readable uptime */\nfunction formatUptime(ms: number): string {\n\tconst seconds = Math.floor(ms / 1000);\n\tconst minutes = Math.floor(seconds / 60);\n\tconst hours = Math.floor(minutes / 60);\n\tif (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s`;\n\tif (minutes > 0) return `${minutes}m ${seconds % 60}s`;\n\treturn `${seconds}s`;\n}\n\n/** Explains why a local UI is not on the port the user expected */\nfunction portFallbackNote(port: number, requestedPort?: number): string {\n\tif (requestedPort === undefined || requestedPort === port) return \"\";\n\treturn chalk.gray(` (${requestedPort} in use)`);\n}\n\nexport class Display {\n\tprivate logLevel: LogLevel = \"normal\";\n\tprivate startTime = 0;\n\tprivate totalRequests = 0;\n\tprivate errorRequests = 0;\n\n\tsetLogLevel(level: LogLevel): void {\n\t\tthis.logLevel = level;\n\t}\n\n\tshowBanner(version: string): void {\n\t\tconsole.log(chalk.bold(`\\n tunelo v${version}\\n`));\n\t}\n\n\tshowConnecting(server: string): void {\n\t\tconsole.log(chalk.yellow(` Connecting to ${server}...`));\n\t}\n\n\tshowConnected(\n\t\ttunnelUrl: string,\n\t\tlocalUrl: string,\n\t\tapiKey?: string,\n\t\tauthEnabled = false,\n\t): void {\n\t\tthis.startTime = Date.now();\n\t\tconsole.log(chalk.green(\" Status: \") + chalk.bold.green(\"Online\"));\n\t\tconsole.log(chalk.green(\" Tunnel: \") + chalk.cyan(tunnelUrl));\n\t\tconsole.log(`${chalk.green(\" Forwarding:\")} ${localUrl}`);\n\t\tif (apiKey) {\n\t\t\tconsole.log(chalk.green(\" API Key: \") + chalk.gray(maskKey(apiKey)));\n\t\t}\n\t\tif (authEnabled) {\n\t\t\tconsole.log(chalk.green(\" Auth: \") + chalk.yellow(\"enabled\"));\n\t\t}\n\t\tconsole.log(chalk.gray(\" \u2500\".repeat(20)));\n\t}\n\n\tshowInspector(port: number, requestedPort?: number): void {\n\t\tconsole.log(\n\t\t\tchalk.green(\" Inspector: \") +\n\t\t\t\tchalk.cyan(`http://localhost:${port}`) +\n\t\t\t\tportFallbackNote(port, requestedPort),\n\t\t);\n\t}\n\n\tshowPortal(port: number, requestedPort?: number): void {\n\t\tconsole.log(\n\t\t\tchalk.green(\" Portal: \") +\n\t\t\t\tchalk.cyan(`http://localhost:${port}`) +\n\t\t\t\tportFallbackNote(port, requestedPort),\n\t\t);\n\t}\n\n\tshowTunnelRegistered(\n\t\tsubdomain: string,\n\t\turl: string,\n\t\tlocalPort: number,\n\t): void {\n\t\tconsole.log(\n\t\t\t`${\n\t\t\t\tchalk.green(\" + Tunnel: \") + chalk.cyan(url)\n\t\t\t} \u2192 http://localhost:${localPort}`,\n\t\t);\n\t}\n\n\tshowTcpTunnel(url: string, localPort: number): void {\n\t\tconsole.log(\n\t\t\t`${\n\t\t\t\tchalk.green(\" TCP: \") + chalk.cyan(url)\n\t\t\t} \u2192 localhost:${localPort}`,\n\t\t);\n\t}\n\n\tshowDisconnected(reason?: string): void {\n\t\tconsole.log(chalk.red(` Disconnected${reason ? `: ${reason}` : \"\"}`));\n\t}\n\n\tshowReconnecting(attempt: number, delayMs: number): void {\n\t\tconsole.log(\n\t\t\tchalk.yellow(` Reconnecting (attempt ${attempt}, ${delayMs}ms)...`),\n\t\t);\n\t}\n\n\tlogRequest(\n\t\tmethod: string,\n\t\tpath: string,\n\t\tstatus: number,\n\t\tdurationMs: number,\n\t\tbodySize?: number,\n\t\tsubdomain?: string,\n\t): void {\n\t\tthis.totalRequests++;\n\t\tif (status >= 500) this.errorRequests++;\n\n\t\t// Quiet mode \u2014 skip request logging entirely\n\t\tif (this.logLevel === \"quiet\") return;\n\n\t\tconst statusColor =\n\t\t\tstatus < 300 ? chalk.green : status < 500 ? chalk.yellow : chalk.red;\n\t\tconst methodStr = method.padEnd(7);\n\t\tconst pathStr = path.length > 40 ? `${path.slice(0, 37)}...` : path;\n\n\t\tconst prefix = subdomain ? chalk.magenta(`[${subdomain}] `) : \"\";\n\t\tlet line = ` ${prefix}${chalk.bold(methodStr)} ${pathStr.padEnd(42)} ${statusColor(String(status))} ${chalk.gray(`${durationMs}ms`)}`;\n\n\t\t// Verbose mode \u2014 append body size\n\t\tif (this.logLevel === \"verbose\" && bodySize !== undefined) {\n\t\t\tconst sizeStr =\n\t\t\t\tbodySize > 1024 ? `${(bodySize / 1024).toFixed(1)}KB` : `${bodySize}B`;\n\t\t\tline += chalk.gray(` ${sizeStr}`);\n\t\t}\n\n\t\tconsole.log(line);\n\t}\n\n\tshowStats(): void {\n\t\tconst uptime = this.startTime\n\t\t\t? formatUptime(Date.now() - this.startTime)\n\t\t\t: \"0s\";\n\t\tconsole.log(\"\");\n\t\tconsole.log(chalk.gray(\" \u2500\".repeat(20)));\n\t\tconsole.log(chalk.bold(\" Session Summary\"));\n\t\tconsole.log(` Uptime: ${chalk.cyan(uptime)}`);\n\t\tconsole.log(` Requests: ${chalk.green(String(this.totalRequests))}`);\n\t\tif (this.errorRequests > 0) {\n\t\t\tconsole.log(` Errors: ${chalk.red(String(this.errorRequests))}`);\n\t\t}\n\t\tconsole.log(\"\");\n\t}\n\n\tshowError(message: string): void {\n\t\tconsole.log(chalk.red(` Error: ${message}`));\n\t}\n}\n", "/** HTTP server for the request inspector dashboard \u2014 serves UI + JSON API + SSE */\n\nimport http from \"node:http\";\nimport { getInspectorHtml } from \"./inspector-ui.js\";\nimport { listenWithPortFallback } from \"./listen-with-port-fallback.js\";\nimport type { InspectorEntry, RequestStore } from \"./request-store.js\";\n\ninterface InspectorServerOptions {\n\t/** Preferred port \u2014 the next free one is used when it is taken */\n\tport: number;\n\tstore: RequestStore;\n\t/** Callback to replay a request by ID \u2014 returns the new entry */\n\tonReplay?: (id: string) => Promise<InspectorEntry | null>;\n\t/** Callback to replay a custom request */\n\tonReplayCustom?: (req: {\n\t\tmethod: string;\n\t\tpath: string;\n\t\theaders: Record<string, string>;\n\t\tbody: string | null;\n\t}) => Promise<InspectorEntry | null>;\n\t/** Get active tunnel info */\n\tgetTunnels?: () => Array<{\n\t\tsubdomain: string;\n\t\tlocalPort: number;\n\t\turl: string;\n\t}>;\n}\n\n/** Simple sliding-window rate limiter */\nfunction createRateLimiter(maxPerSec: number) {\n\tlet count = 0;\n\tsetInterval(() => {\n\t\tcount = 0;\n\t}, 1000);\n\treturn {\n\t\tallow(): boolean {\n\t\t\tif (count >= maxPerSec) return false;\n\t\t\tcount++;\n\t\t\treturn true;\n\t\t},\n\t};\n}\n\nexport interface InspectorServerHandle {\n\tserver: http.Server;\n\t/** Port the inspector is actually reachable on */\n\tport: number;\n\t/** True when the preferred port was busy */\n\tfellBack: boolean;\n}\n\nexport async function startInspectorServer(\n\toptions: InspectorServerOptions,\n): Promise<InspectorServerHandle> {\n\tconst { port, store, onReplay, onReplayCustom, getTunnels } = options;\n\tconst sseClients = new Set<http.ServerResponse>();\n\tconst replayLimiter = createRateLimiter(10);\n\n\t// Push new entries to all SSE clients\n\tstore.onAdd((entry) => {\n\t\tconst data = `data: ${JSON.stringify(toSummary(entry))}\\n\\n`;\n\t\tfor (const client of sseClients) {\n\t\t\tclient.write(data);\n\t\t}\n\t});\n\n\tconst server = http.createServer((req, res) => {\n\t\t// Base is only needed so relative request targets parse; the host is irrelevant.\n\t\tconst url = new URL(req.url ?? \"/\", \"http://localhost\");\n\t\tconst pathname = url.pathname;\n\n\t\t// CORS for local dev\n\t\tres.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n\t\tres.setHeader(\"Access-Control-Allow-Methods\", \"GET,POST,DELETE,OPTIONS\");\n\t\tres.setHeader(\"Access-Control-Allow-Headers\", \"Content-Type\");\n\n\t\tif (req.method === \"OPTIONS\") {\n\t\t\tres.writeHead(204);\n\t\t\tres.end();\n\t\t\treturn;\n\t\t}\n\n\t\t// Dashboard HTML\n\t\tif (pathname === \"/\" && req.method === \"GET\") {\n\t\t\tres.writeHead(200, { \"content-type\": \"text/html; charset=utf-8\" });\n\t\t\tres.end(getInspectorHtml());\n\t\t\treturn;\n\t\t}\n\n\t\t// Active tunnels\n\t\tif (pathname === \"/api/tunnels\" && req.method === \"GET\") {\n\t\t\tconst tunnels = getTunnels?.() ?? [];\n\t\t\tsendJson(res, 200, tunnels);\n\t\t\treturn;\n\t\t}\n\n\t\t// Request list (summaries, no bodies)\n\t\tif (pathname === \"/api/requests\" && req.method === \"GET\") {\n\t\t\tconst filter = {\n\t\t\t\tsubdomain: url.searchParams.get(\"subdomain\") ?? undefined,\n\t\t\t\tstatus: url.searchParams.get(\"status\") ?? undefined,\n\t\t\t\tq: url.searchParams.get(\"q\") ?? undefined,\n\t\t\t};\n\t\t\tsendJson(res, 200, store.list(filter));\n\t\t\treturn;\n\t\t}\n\n\t\t// Clear all requests\n\t\tif (pathname === \"/api/requests\" && req.method === \"DELETE\") {\n\t\t\tstore.clear();\n\t\t\tsendJson(res, 200, { ok: true });\n\t\t\treturn;\n\t\t}\n\n\t\t// SSE stream\n\t\tif (pathname === \"/api/events\" && req.method === \"GET\") {\n\t\t\tres.writeHead(200, {\n\t\t\t\t\"content-type\": \"text/event-stream\",\n\t\t\t\t\"cache-control\": \"no-cache\",\n\t\t\t\tconnection: \"keep-alive\",\n\t\t\t});\n\t\t\tres.write(\":ok\\n\\n\");\n\t\t\tsseClients.add(res);\n\t\t\treq.on(\"close\", () => sseClients.delete(res));\n\t\t\treturn;\n\t\t}\n\n\t\t// Request detail\n\t\tconst detailMatch = pathname.match(/^\\/api\\/requests\\/([^/]+)$/);\n\t\tif (detailMatch && req.method === \"GET\") {\n\t\t\tconst entry = store.get(detailMatch[1]);\n\t\t\tif (!entry) {\n\t\t\t\tsendJson(res, 404, { error: \"Not found\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsendJson(res, 200, entry);\n\t\t\treturn;\n\t\t}\n\n\t\t// Replay by ID\n\t\tconst replayMatch = pathname.match(/^\\/api\\/requests\\/([^/]+)\\/replay$/);\n\t\tif (replayMatch && req.method === \"POST\") {\n\t\t\tif (!replayLimiter.allow()) {\n\t\t\t\tsendJson(res, 429, {\n\t\t\t\t\terror: \"Replay rate limit exceeded (max 10/sec)\",\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!onReplay) {\n\t\t\t\tsendJson(res, 501, { error: \"Replay not available\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tonReplay(replayMatch[1])\n\t\t\t\t.then((entry) => {\n\t\t\t\t\tif (!entry) {\n\t\t\t\t\t\tsendJson(res, 404, { error: \"Original request not found\" });\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tsendJson(res, 200, toSummary(entry));\n\t\t\t\t})\n\t\t\t\t.catch((err) => {\n\t\t\t\t\tsendJson(res, 500, { error: (err as Error).message });\n\t\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// Custom replay\n\t\tif (pathname === \"/api/replay\" && req.method === \"POST\") {\n\t\t\tif (!replayLimiter.allow()) {\n\t\t\t\tsendJson(res, 429, {\n\t\t\t\t\terror: \"Replay rate limit exceeded (max 10/sec)\",\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (!onReplayCustom) {\n\t\t\t\tsendJson(res, 501, { error: \"Custom replay not available\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcollectBody(req, (body) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst data = JSON.parse(body);\n\t\t\t\t\tonReplayCustom(data)\n\t\t\t\t\t\t.then((entry) => {\n\t\t\t\t\t\t\tif (!entry) {\n\t\t\t\t\t\t\t\tsendJson(res, 500, { error: \"Replay failed\" });\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tsendJson(res, 200, toSummary(entry));\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.catch((err) => {\n\t\t\t\t\t\t\tsendJson(res, 500, { error: (err as Error).message });\n\t\t\t\t\t\t});\n\t\t\t\t} catch {\n\t\t\t\t\tsendJson(res, 400, { error: \"Invalid JSON\" });\n\t\t\t\t}\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tres.writeHead(404, { \"content-type\": \"text/plain\" });\n\t\tres.end(\"Not Found\");\n\t});\n\n\tconst { port: boundPort, fellBack } = await listenWithPortFallback(\n\t\tserver,\n\t\tport,\n\t);\n\n\treturn { server, port: boundPort, fellBack };\n}\n\nfunction sendJson(\n\tres: http.ServerResponse,\n\tstatus: number,\n\tdata: unknown,\n): void {\n\tres.writeHead(status, { \"content-type\": \"application/json\" });\n\tres.end(JSON.stringify(data));\n}\n\nconst MAX_REPLAY_BODY = 1024 * 1024; // 1MB limit for replay requests\n\nfunction collectBody(\n\treq: http.IncomingMessage,\n\tcb: (body: string) => void,\n\tonError?: (msg: string) => void,\n): void {\n\tconst chunks: Buffer[] = [];\n\tlet size = 0;\n\treq.on(\"data\", (chunk: Buffer) => {\n\t\tsize += chunk.length;\n\t\tif (size > MAX_REPLAY_BODY) {\n\t\t\treq.destroy();\n\t\t\tonError?.(\"Request body too large\");\n\t\t\treturn;\n\t\t}\n\t\tchunks.push(chunk);\n\t});\n\treq.on(\"end\", () => cb(Buffer.concat(chunks).toString(\"utf-8\")));\n}\n\nfunction toSummary(entry: InspectorEntry): Record<string, unknown> {\n\tconst {\n\t\trequestBody,\n\t\tresponseBody,\n\t\trequestHeaders,\n\t\tresponseHeaders,\n\t\t...summary\n\t} = entry;\n\treturn summary;\n}\n", "/** Inline HTML dashboard for the request inspector \u2014 no external dependencies */\n\nexport function getInspectorHtml(): string {\n\treturn `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>Tunelo Inspector</title>\n<style>\n:root{--bg:#0f172a;--bg2:#1e293b;--bg3:#334155;--fg:#e2e8f0;--fg2:#94a3b8;--accent:#38bdf8;--green:#4ade80;--yellow:#fbbf24;--red:#f87171;--border:#475569;font-family:system-ui,-apple-system,sans-serif}\n*{margin:0;padding:0;box-sizing:border-box}\nbody{background:var(--bg);color:var(--fg);font-size:14px;height:100vh;display:flex;flex-direction:column}\nheader{background:var(--bg2);padding:8px 16px;display:flex;align-items:center;gap:12px;border-bottom:1px solid var(--border)}\nheader h1{font-size:16px;color:var(--accent);font-weight:600}\nheader .stats{color:var(--fg2);font-size:12px;margin-left:auto}\n.filters{background:var(--bg2);padding:6px 16px;display:flex;gap:8px;border-bottom:1px solid var(--border)}\n.filters select,.filters input{background:var(--bg3);color:var(--fg);border:1px solid var(--border);padding:4px 8px;border-radius:4px;font-size:12px}\n.filters input{flex:1;max-width:250px}\n.filters button{background:var(--bg3);color:var(--fg2);border:1px solid var(--border);padding:4px 12px;border-radius:4px;cursor:pointer;font-size:12px}\n.filters button:hover{background:var(--border)}\n.main{flex:1;display:flex;overflow:hidden}\n.list{flex:1;overflow-y:auto;min-width:0}\n.detail{width:45%;border-left:1px solid var(--border);overflow-y:auto;display:none}\n.detail.open{display:block}\ntable{width:100%;border-collapse:collapse}\nth{background:var(--bg2);position:sticky;top:0;text-align:left;padding:6px 10px;font-size:11px;color:var(--fg2);text-transform:uppercase;letter-spacing:.5px}\ntr.row{cursor:pointer;border-bottom:1px solid var(--bg2)}\ntr.row:hover{background:var(--bg2)}\ntr.row.active{background:var(--bg3)}\ntr.row.replay{opacity:.8;font-style:italic}\ntd{padding:6px 10px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:300px;font-size:13px}\n.method{font-weight:700;font-family:monospace}\n.s2xx{color:var(--green)}.s3xx{color:var(--accent)}.s4xx{color:var(--yellow)}.s5xx{color:var(--red)}\n.badge{font-size:10px;padding:1px 5px;border-radius:3px;background:var(--bg3);color:var(--fg2);margin-left:4px}\n.badge.replay{background:#7c3aed;color:#fff}\n.detail-header{padding:12px 16px;background:var(--bg2);border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}\n.detail-header h2{font-size:14px;font-weight:600}\n.tabs{display:flex;gap:0;border-bottom:1px solid var(--border)}\n.tabs button{background:none;border:none;color:var(--fg2);padding:8px 16px;cursor:pointer;font-size:12px;border-bottom:2px solid transparent}\n.tabs button.active{color:var(--accent);border-bottom-color:var(--accent)}\n.tab-content{padding:12px 16px;font-family:monospace;font-size:12px;white-space:pre-wrap;word-break:break-all;line-height:1.5}\n.empty{text-align:center;padding:60px 20px;color:var(--fg2)}\n.empty h2{font-size:18px;margin-bottom:8px;color:var(--fg)}\n.modal-overlay{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.6);z-index:100;align-items:center;justify-content:center}\n.modal-overlay.open{display:flex}\n.modal{background:var(--bg2);border:1px solid var(--border);border-radius:8px;width:600px;max-height:80vh;display:flex;flex-direction:column}\n.modal h3{padding:12px 16px;border-bottom:1px solid var(--border);font-size:14px}\n.modal-body{padding:12px 16px;overflow-y:auto;flex:1;display:flex;flex-direction:column;gap:8px}\n.modal-body label{font-size:11px;color:var(--fg2);text-transform:uppercase}\n.modal-body input,.modal-body textarea,.modal-body select{background:var(--bg3);color:var(--fg);border:1px solid var(--border);padding:6px 8px;border-radius:4px;font-family:monospace;font-size:12px;width:100%}\n.modal-body textarea{min-height:120px;resize:vertical}\n.modal-footer{padding:12px 16px;border-top:1px solid var(--border);display:flex;gap:8px;justify-content:flex-end}\n.modal-footer button{padding:6px 16px;border-radius:4px;cursor:pointer;font-size:12px;border:none}\n.btn-primary{background:var(--accent);color:#000}.btn-secondary{background:var(--bg3);color:var(--fg)}\n@media(prefers-color-scheme:light){:root{--bg:#f8fafc;--bg2:#e2e8f0;--bg3:#cbd5e1;--fg:#0f172a;--fg2:#475569;--border:#94a3b8}}\n</style>\n</head>\n<body>\n<header>\n<h1>tunelo inspector</h1>\n<span class=\"stats\" id=\"stats\">0 requests</span>\n</header>\n<div class=\"filters\">\n<select id=\"fSub\"><option value=\"\">All tunnels</option></select>\n<select id=\"fStatus\"><option value=\"\">All status</option><option value=\"2\">2xx</option><option value=\"3\">3xx</option><option value=\"4\">4xx</option><option value=\"5\">5xx</option></select>\n<input id=\"fSearch\" type=\"text\" placeholder=\"Search path...\">\n<button id=\"btnClear\">Clear</button>\n</div>\n<div class=\"main\">\n<div class=\"list\"><table><thead><tr><th>Method</th><th>Path</th><th>Status</th><th>Duration</th><th>Time</th><th>Subdomain</th></tr></thead><tbody id=\"tbody\"></tbody></table>\n<div class=\"empty\" id=\"empty\"><h2>Waiting for requests...</h2><p>Send a request through your tunnel to see it here</p></div>\n</div>\n<div class=\"detail\" id=\"detail\">\n<div class=\"detail-header\"><h2 id=\"dTitle\">Request</h2><div style=\"display:flex;gap:6px\"><button id=\"btnEditReplay\" style=\"background:var(--bg3);color:var(--fg);border:1px solid var(--border);padding:4px 12px;border-radius:4px;cursor:pointer;font-size:12px\">Edit & Replay</button><button id=\"btnReplay\" style=\"background:var(--accent);color:#000;border:none;padding:4px 12px;border-radius:4px;cursor:pointer;font-size:12px\">Replay</button></div></div>\n<div class=\"tabs\"><button class=\"active\" data-tab=\"req-headers\">Req Headers</button><button data-tab=\"req-body\">Req Body</button><button data-tab=\"res-headers\">Res Headers</button><button data-tab=\"res-body\">Res Body</button></div>\n<div class=\"tab-content\" id=\"tabContent\"></div>\n</div>\n</div>\n<div class=\"modal-overlay\" id=\"editModal\">\n<div class=\"modal\">\n<h3>Edit & Replay</h3>\n<div class=\"modal-body\">\n<label>Method</label><select id=\"mMethod\"><option>GET</option><option>POST</option><option>PUT</option><option>PATCH</option><option>DELETE</option></select>\n<label>Path</label><input id=\"mPath\" type=\"text\">\n<label>Headers (JSON)</label><textarea id=\"mHeaders\"></textarea>\n<label>Body</label><textarea id=\"mBody\"></textarea>\n</div>\n<div class=\"modal-footer\"><button class=\"btn-secondary\" id=\"mCancel\">Cancel</button><button class=\"btn-primary\" id=\"mSend\">Send</button></div>\n</div>\n</div>\n<script>\nconst tbody=document.getElementById(\"tbody\"),detail=document.getElementById(\"detail\"),empty=document.getElementById(\"empty\"),stats=document.getElementById(\"stats\"),tabContent=document.getElementById(\"tabContent\"),dTitle=document.getElementById(\"dTitle\"),fSub=document.getElementById(\"fSub\"),fStatus=document.getElementById(\"fStatus\"),fSearch=document.getElementById(\"fSearch\"),btnClear=document.getElementById(\"btnClear\"),btnReplay=document.getElementById(\"btnReplay\"),btnEditReplay=document.getElementById(\"btnEditReplay\"),editModal=document.getElementById(\"editModal\"),mMethod=document.getElementById(\"mMethod\"),mPath=document.getElementById(\"mPath\"),mHeaders=document.getElementById(\"mHeaders\"),mBody=document.getElementById(\"mBody\"),mCancel=document.getElementById(\"mCancel\"),mSend=document.getElementById(\"mSend\");\nlet entries=[],selected=null,activeTab=\"req-headers\",knownSubs=new Set();\n\nfunction statusClass(s){if(s<300)return\"s2xx\";if(s<400)return\"s3xx\";if(s<500)return\"s4xx\";return\"s5xx\"}\nfunction fmtTime(ts){return new Date(ts).toLocaleTimeString()}\nfunction tryJson(s){if(!s)return\"(empty)\";try{const b=atob(s);const j=JSON.parse(b);return JSON.stringify(j,null,2)}catch{try{return atob(s)}catch{return s}}}\nfunction fmtHeaders(h){if(!h)return\"(none)\";return Object.entries(h).map(([k,v])=>k+\": \"+(Array.isArray(v)?v.join(\", \"):v)).join(\"\\\\n\")}\n\nfunction render(){\nconst sub=fSub.value,st=fStatus.value,q=fSearch.value.toLowerCase();\nlet filtered=entries;\nif(sub)filtered=filtered.filter(e=>e.subdomain===sub);\nif(st)filtered=filtered.filter(e=>String(e.status).startsWith(st));\nif(q)filtered=filtered.filter(e=>e.path.toLowerCase().includes(q)||e.method.toLowerCase().includes(q));\ntbody.innerHTML=\"\";\nempty.style.display=filtered.length?\"none\":\"block\";\nstats.textContent=entries.length+\" requests\";\nfor(const e of filtered){\nconst tr=document.createElement(\"tr\");tr.className=\"row\"+(e.id===selected?\" active\":\"\")+(e.entryType===\"replay\"?\" replay\":\"\");\ntr.innerHTML='<td class=\"method\">'+e.method+'</td><td>'+e.path+(e.entryType===\"replay\"?'<span class=\"badge replay\">replay</span>':\"\")+'</td><td class=\"'+statusClass(e.status)+'\">'+e.status+\"</td><td>\"+e.durationMs+\"ms</td><td>\"+fmtTime(e.timestamp)+\"</td><td>\"+(e.subdomain||\"-\")+\"</td>\";\ntr.onclick=()=>showDetail(e.id);tbody.appendChild(tr);\n}}\n\nfunction showDetail(id){\nselected=id;render();\nconst e=entries.find(x=>x.id===id);if(!e){detail.classList.remove(\"open\");return}\ndetail.classList.add(\"open\");\ndTitle.textContent=e.method+\" \"+e.path;\nfetch(\"/api/requests/\"+id).then(r=>r.json()).then(full=>{\nwindow._detail=full;showTab(activeTab);\n}).catch(()=>{window._detail=e;showTab(activeTab)});\n}\n\nfunction showTab(tab){\nactiveTab=tab;\ndocument.querySelectorAll(\".tabs button\").forEach(b=>b.classList.toggle(\"active\",b.dataset.tab===tab));\nconst d=window._detail||{};\nif(tab===\"req-headers\")tabContent.textContent=fmtHeaders(d.requestHeaders);\nelse if(tab===\"req-body\")tabContent.textContent=tryJson(d.requestBody);\nelse if(tab===\"res-headers\")tabContent.textContent=fmtHeaders(d.responseHeaders);\nelse tabContent.textContent=tryJson(d.responseBody);\n}\n\ndocument.querySelector(\".tabs\").addEventListener(\"click\",e=>{if(e.target.dataset.tab)showTab(e.target.dataset.tab)});\nfSub.onchange=fStatus.onchange=fSearch.oninput=render;\nbtnClear.onclick=()=>fetch(\"/api/requests\",{method:\"DELETE\"}).then(()=>{entries=[];selected=null;detail.classList.remove(\"open\");render()});\nbtnReplay.onclick=()=>{if(!selected)return;fetch(\"/api/requests/\"+selected+\"/replay\",{method:\"POST\"}).catch(()=>{})};\nbtnEditReplay.onclick=()=>{\nif(!selected||!window._detail)return;\nconst d=window._detail;\nmMethod.value=d.method||\"GET\";mPath.value=d.path||\"/\";\ntry{mHeaders.value=JSON.stringify(d.requestHeaders||{},null,2)}catch{mHeaders.value=\"{}\"}\ntry{mBody.value=d.requestBody?atob(d.requestBody):\"\"}catch{mBody.value=d.requestBody||\"\"}\neditModal.classList.add(\"open\");\n};\nmCancel.onclick=()=>editModal.classList.remove(\"open\");\nmSend.onclick=()=>{\nlet headers;try{headers=JSON.parse(mHeaders.value)}catch{headers={}}\nconst body=mBody.value?btoa(mBody.value):null;\nfetch(\"/api/replay\",{method:\"POST\",headers:{\"Content-Type\":\"application/json\"},body:JSON.stringify({method:mMethod.value,path:mPath.value,headers,body})}).then(()=>editModal.classList.remove(\"open\")).catch(()=>{});\n};\n\n// SSE real-time updates\nconst sse=new EventSource(\"/api/events\");\nsse.onmessage=e=>{\nconst entry=JSON.parse(e.data);\nentries.unshift(entry);\nif(entry.subdomain&&!knownSubs.has(entry.subdomain)){knownSubs.add(entry.subdomain);const o=document.createElement(\"option\");o.value=entry.subdomain;o.textContent=entry.subdomain;fSub.appendChild(o)}\nrender();\n};\n\n// Load initial data\nfetch(\"/api/requests\").then(r=>r.json()).then(data=>{entries=data;data.forEach(e=>{if(e.subdomain)knownSubs.add(e.subdomain)});knownSubs.forEach(s=>{const o=document.createElement(\"option\");o.value=s;o.textContent=s;fSub.appendChild(o)});render()});\n</script>\n</body>\n</html>`;\n}\n", "/** Binds an HTTP server to the first free port, so a second CLI run never dies on EADDRINUSE */\n\nimport type http from \"node:http\";\n\nexport interface ListenResult {\n\t/** Port the server is actually bound to */\n\tport: number;\n\t/** True when the preferred port was taken and another one was used */\n\tfellBack: boolean;\n}\n\nexport interface ListenOptions {\n\thost?: string;\n\t/** How many consecutive ports to probe before falling back to an ephemeral one */\n\tmaxAttempts?: number;\n}\n\nconst DEFAULT_HOST = \"127.0.0.1\";\nconst DEFAULT_MAX_ATTEMPTS = 20;\nconst MAX_PORT = 65535;\n\n/**\n * Listens on `preferredPort`, then `preferredPort + 1`, ... until one binds.\n * If every candidate is taken, falls back to an OS-assigned ephemeral port.\n * Errors other than \"port unavailable\" reject so real failures stay visible.\n */\nexport async function listenWithPortFallback(\n\tserver: http.Server,\n\tpreferredPort: number,\n\toptions: ListenOptions = {},\n): Promise<ListenResult> {\n\tconst host = options.host ?? DEFAULT_HOST;\n\tconst maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;\n\n\tfor (let attempt = 0; attempt < maxAttempts; attempt++) {\n\t\tconst candidate = preferredPort + attempt;\n\t\tif (candidate > MAX_PORT) break;\n\t\tif (await tryListen(server, candidate, host)) {\n\t\t\treturn {\n\t\t\t\tport: resolveBoundPort(server, candidate),\n\t\t\t\tfellBack: attempt > 0,\n\t\t\t};\n\t\t}\n\t}\n\n\t// Every candidate was taken \u2014 let the OS pick any free port.\n\tif (await tryListen(server, 0, host)) {\n\t\treturn { port: resolveBoundPort(server, 0), fellBack: true };\n\t}\n\n\tthrow new Error(\n\t\t`No free port available near ${preferredPort} on ${host} (tried ${maxAttempts} ports + ephemeral)`,\n\t);\n}\n\n/** Resolves to false when the port is unusable, rejects on any other listen error */\nfunction tryListen(\n\tserver: http.Server,\n\tport: number,\n\thost: string,\n): Promise<boolean> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (err: NodeJS.ErrnoException) => {\n\t\t\tserver.removeListener(\"listening\", onListening);\n\t\t\tif (err.code === \"EADDRINUSE\" || err.code === \"EACCES\") {\n\t\t\t\tresolve(false);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treject(err);\n\t\t};\n\n\t\tconst onListening = () => {\n\t\t\tserver.removeListener(\"error\", onError);\n\t\t\tresolve(true);\n\t\t};\n\n\t\tserver.once(\"error\", onError);\n\t\tserver.once(\"listening\", onListening);\n\t\tserver.listen(port, host);\n\t});\n}\n\n/** address() is null for non-TCP handles; fall back to the requested port */\nfunction resolveBoundPort(server: http.Server, requested: number): number {\n\tconst address = server.address();\n\tif (address && typeof address === \"object\") return address.port;\n\treturn requested;\n}\n", "import http from \"node:http\";\nimport {\n\tDEFAULTS,\n\ttype TunnelRequest,\n\ttype TunnelResponse,\n} from \"@tunelo/shared\";\n\nconst HOP_HEADERS = new Set([\n\t\"connection\",\n\t\"keep-alive\",\n\t\"proxy-authenticate\",\n\t\"proxy-authorization\",\n\t\"te\",\n\t\"trailers\",\n\t\"transfer-encoding\",\n\t\"upgrade\",\n]);\n\nfunction filterHeaders(\n\theaders: Record<string, string | string[]>,\n): Record<string, string | string[]> {\n\tconst filtered: Record<string, string | string[]> = {};\n\tfor (const [key, value] of Object.entries(headers)) {\n\t\tif (!HOP_HEADERS.has(key.toLowerCase())) {\n\t\t\tfiltered[key] = value;\n\t\t}\n\t}\n\treturn filtered;\n}\n\nexport function proxyRequest(\n\trequest: TunnelRequest,\n\tlocalPort: number,\n\tlocalHost = \"localhost\",\n): Promise<TunnelResponse> {\n\treturn new Promise((resolve) => {\n\t\tconst body = request.body ? Buffer.from(request.body, \"base64\") : null;\n\t\tconst headers = filterHeaders(request.headers);\n\n\t\tconst proxyReq = http.request(\n\t\t\t{\n\t\t\t\thostname: localHost,\n\t\t\t\tport: localPort,\n\t\t\t\tpath: request.path,\n\t\t\t\tmethod: request.method,\n\t\t\t\theaders,\n\t\t\t},\n\t\t\t(proxyRes) => {\n\t\t\t\tconst chunks: Buffer[] = [];\n\t\t\t\tlet bodySize = 0;\n\t\t\t\tproxyRes.on(\"data\", (chunk: Buffer) => {\n\t\t\t\t\tbodySize += chunk.length;\n\t\t\t\t\tif (bodySize > DEFAULTS.MAX_BODY_SIZE) {\n\t\t\t\t\t\tproxyReq.destroy();\n\t\t\t\t\t\tresolve({\n\t\t\t\t\t\t\ttype: \"response\",\n\t\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\t\tstatus: 502,\n\t\t\t\t\t\t\theaders: { \"content-type\": \"text/plain\" },\n\t\t\t\t\t\t\tbody: Buffer.from(\"Response body too large\").toString(\"base64\"),\n\t\t\t\t\t\t});\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tchunks.push(chunk);\n\t\t\t\t});\n\t\t\t\tproxyRes.on(\"end\", () => {\n\t\t\t\t\tconst responseBody =\n\t\t\t\t\t\tchunks.length > 0 ? Buffer.concat(chunks).toString(\"base64\") : null;\n\t\t\t\t\tresolve({\n\t\t\t\t\t\ttype: \"response\",\n\t\t\t\t\t\tid: request.id,\n\t\t\t\t\t\tstatus: proxyRes.statusCode ?? 502,\n\t\t\t\t\t\theaders: proxyRes.headers as Record<string, string | string[]>,\n\t\t\t\t\t\tbody: responseBody,\n\t\t\t\t\t});\n\t\t\t\t});\n\t\t\t},\n\t\t);\n\n\t\tproxyReq.on(\"error\", (err) => {\n\t\t\tconst isRefused = (err as NodeJS.ErrnoException).code === \"ECONNREFUSED\";\n\t\t\tresolve({\n\t\t\t\ttype: \"response\",\n\t\t\t\tid: request.id,\n\t\t\t\tstatus: isRefused ? 502 : 502,\n\t\t\t\theaders: { \"content-type\": \"text/plain\" },\n\t\t\t\tbody: Buffer.from(\n\t\t\t\t\tisRefused ? \"Local service unavailable\" : (err as Error).message,\n\t\t\t\t).toString(\"base64\"),\n\t\t\t});\n\t\t});\n\n\t\tproxyReq.setTimeout(30_000, () => {\n\t\t\tproxyReq.destroy();\n\t\t\tresolve({\n\t\t\t\ttype: \"response\",\n\t\t\t\tid: request.id,\n\t\t\t\tstatus: 504,\n\t\t\t\theaders: { \"content-type\": \"text/plain\" },\n\t\t\t\tbody: Buffer.from(\"Local service timeout\").toString(\"base64\"),\n\t\t\t});\n\t\t});\n\n\t\tif (body) proxyReq.write(body);\n\t\tproxyReq.end();\n\t});\n}\n", "/** Ring buffer storage for inspector request/response entries */\n\nexport interface InspectorEntry {\n\tid: string;\n\ttimestamp: number;\n\tsubdomain?: string;\n\tmethod: string;\n\tpath: string;\n\trequestHeaders: Record<string, string | string[]>;\n\trequestBody: string | null;\n\tstatus: number;\n\tresponseHeaders: Record<string, string | string[]>;\n\tresponseBody: string | null;\n\tdurationMs: number;\n\t/** \"normal\" for tunnel traffic, \"replay\" for replayed requests */\n\tentryType: \"normal\" | \"replay\";\n\t/** Original entry ID if this is a replay */\n\toriginalId?: string;\n}\n\n/** Lightweight entry without bodies \u2014 for list API */\nexport type InspectorEntrySummary = Omit<\n\tInspectorEntry,\n\t\"requestBody\" | \"responseBody\" | \"requestHeaders\" | \"responseHeaders\"\n>;\n\nexport type RequestStoreListener = (entry: InspectorEntry) => void;\n\nconst DEFAULT_MAX_SIZE = 500;\nconst MAX_BODY_BYTES = 1024 * 1024; // 1MB per body\n\nexport class RequestStore {\n\tprivate entries: InspectorEntry[] = [];\n\tprivate readonly maxSize: number;\n\tprivate listeners: RequestStoreListener[] = [];\n\n\tconstructor(maxSize = DEFAULT_MAX_SIZE) {\n\t\tthis.maxSize = maxSize;\n\t}\n\n\tadd(entry: InspectorEntry): void {\n\t\t// Truncate bodies if too large\n\t\tif (\n\t\t\tentry.requestBody &&\n\t\t\tBuffer.byteLength(entry.requestBody, \"utf-8\") > MAX_BODY_BYTES\n\t\t) {\n\t\t\tentry.requestBody = entry.requestBody.slice(0, MAX_BODY_BYTES);\n\t\t}\n\t\tif (\n\t\t\tentry.responseBody &&\n\t\t\tBuffer.byteLength(entry.responseBody, \"utf-8\") > MAX_BODY_BYTES\n\t\t) {\n\t\t\tentry.responseBody = entry.responseBody.slice(0, MAX_BODY_BYTES);\n\t\t}\n\n\t\tthis.entries.push(entry);\n\t\t// Evict oldest if over capacity\n\t\tif (this.entries.length > this.maxSize) {\n\t\t\tthis.entries.shift();\n\t\t}\n\t\tfor (const listener of this.listeners) {\n\t\t\tlistener(entry);\n\t\t}\n\t}\n\n\tlist(filter?: {\n\t\tsubdomain?: string;\n\t\tstatus?: string;\n\t\tq?: string;\n\t}): InspectorEntrySummary[] {\n\t\tlet result = this.entries;\n\n\t\tif (filter?.subdomain) {\n\t\t\tresult = result.filter((e) => e.subdomain === filter.subdomain);\n\t\t}\n\t\tif (filter?.status) {\n\t\t\tconst statusPrefix = filter.status.replace(/x/gi, \"\");\n\t\t\tresult = result.filter((e) => String(e.status).startsWith(statusPrefix));\n\t\t}\n\t\tif (filter?.q) {\n\t\t\tconst q = filter.q.toLowerCase();\n\t\t\tresult = result.filter(\n\t\t\t\t(e) =>\n\t\t\t\t\te.path.toLowerCase().includes(q) ||\n\t\t\t\t\te.method.toLowerCase().includes(q),\n\t\t\t);\n\t\t}\n\n\t\t// Return summaries (no bodies) in reverse chronological order\n\t\treturn result\n\t\t\t.map(\n\t\t\t\t({\n\t\t\t\t\trequestBody,\n\t\t\t\t\tresponseBody,\n\t\t\t\t\trequestHeaders,\n\t\t\t\t\tresponseHeaders,\n\t\t\t\t\t...summary\n\t\t\t\t}) => summary,\n\t\t\t)\n\t\t\t.reverse();\n\t}\n\n\tget(id: string): InspectorEntry | undefined {\n\t\treturn this.entries.find((e) => e.id === id);\n\t}\n\n\tclear(): void {\n\t\tthis.entries = [];\n\t}\n\n\tonAdd(callback: RequestStoreListener): void {\n\t\tthis.listeners.push(callback);\n\t}\n\n\tremoveListener(callback: RequestStoreListener): void {\n\t\tthis.listeners = this.listeners.filter((l) => l !== callback);\n\t}\n\n\tget size(): number {\n\t\treturn this.entries.length;\n\t}\n\n\tgetSubdomains(): string[] {\n\t\tconst set = new Set<string>();\n\t\tfor (const entry of this.entries) {\n\t\t\tif (entry.subdomain) set.add(entry.subdomain);\n\t\t}\n\t\treturn [...set];\n\t}\n}\n", "/** Wires up inspector server with request store + replay capabilities */\n\nimport { startInspectorServer } from \"./inspector-server.js\";\nimport { proxyRequest } from \"./local-proxy.js\";\nimport type { InspectorEntry } from \"./request-store.js\";\nimport { RequestStore } from \"./request-store.js\";\n\ninterface InspectorIntegrationOptions {\n\t/** Preferred inspector port \u2014 the next free one is used when it is taken */\n\tinspectPort: number;\n\t/** Subdomain\u2192port map (mutated externally as tunnels register) */\n\tsubdomainPortMap: Map<string, number>;\n\t/** Fallback local port for replay */\n\tdefaultPort: number;\n}\n\nexport interface InspectorIntegration {\n\tstore: RequestStore;\n\t/** Resolves with the port the inspector actually bound to */\n\tstart(): Promise<number>;\n}\n\nexport function createInspectorIntegration(\n\toptions: InspectorIntegrationOptions,\n): InspectorIntegration {\n\tconst { inspectPort, subdomainPortMap, defaultPort } = options;\n\tconst store = new RequestStore();\n\n\tasync function start(): Promise<number> {\n\t\tconst handle = await startInspectorServer({\n\t\t\tport: inspectPort,\n\t\t\tstore,\n\t\t\tonReplay: async (id) => {\n\t\t\t\tconst original = store.get(id);\n\t\t\t\tif (!original) return null;\n\t\t\t\tconst port = original.subdomain\n\t\t\t\t\t? (subdomainPortMap.get(original.subdomain) ?? defaultPort)\n\t\t\t\t\t: defaultPort;\n\t\t\t\tconst start = Date.now();\n\t\t\t\tconst response = await proxyRequest(\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"request\",\n\t\t\t\t\t\tid: `replay_${Date.now().toString(36)}`,\n\t\t\t\t\t\tmethod: original.method,\n\t\t\t\t\t\tpath: original.path,\n\t\t\t\t\t\theaders: original.requestHeaders,\n\t\t\t\t\t\tbody: original.requestBody,\n\t\t\t\t\t},\n\t\t\t\t\tport,\n\t\t\t\t);\n\t\t\t\tconst entry: InspectorEntry = {\n\t\t\t\t\tid: `replay_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`,\n\t\t\t\t\ttimestamp: start,\n\t\t\t\t\tsubdomain: original.subdomain,\n\t\t\t\t\tmethod: original.method,\n\t\t\t\t\tpath: original.path,\n\t\t\t\t\trequestHeaders: original.requestHeaders,\n\t\t\t\t\trequestBody: original.requestBody,\n\t\t\t\t\tstatus: response.status,\n\t\t\t\t\tresponseHeaders: response.headers,\n\t\t\t\t\tresponseBody: response.body,\n\t\t\t\t\tdurationMs: Date.now() - start,\n\t\t\t\t\tentryType: \"replay\",\n\t\t\t\t\toriginalId: id,\n\t\t\t\t};\n\t\t\t\tstore.add(entry);\n\t\t\t\treturn entry;\n\t\t\t},\n\t\t\tonReplayCustom: async (req) => {\n\t\t\t\tconst start = Date.now();\n\t\t\t\tconst response = await proxyRequest(\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: \"request\",\n\t\t\t\t\t\tid: `custom_${Date.now().toString(36)}`,\n\t\t\t\t\t\tmethod: req.method,\n\t\t\t\t\t\tpath: req.path,\n\t\t\t\t\t\theaders: req.headers,\n\t\t\t\t\t\tbody: req.body,\n\t\t\t\t\t},\n\t\t\t\t\tdefaultPort,\n\t\t\t\t);\n\t\t\t\tconst entry: InspectorEntry = {\n\t\t\t\t\tid: `custom_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`,\n\t\t\t\t\ttimestamp: start,\n\t\t\t\t\tmethod: req.method,\n\t\t\t\t\tpath: req.path,\n\t\t\t\t\trequestHeaders: req.headers,\n\t\t\t\t\trequestBody: req.body,\n\t\t\t\t\tstatus: response.status,\n\t\t\t\t\tresponseHeaders: response.headers,\n\t\t\t\t\tresponseBody: response.body,\n\t\t\t\t\tdurationMs: Date.now() - start,\n\t\t\t\t\tentryType: \"replay\",\n\t\t\t\t};\n\t\t\t\tstore.add(entry);\n\t\t\t\treturn entry;\n\t\t\t},\n\t\t\tgetTunnels: () =>\n\t\t\t\t[...subdomainPortMap.entries()].map(([subdomain, localPort]) => ({\n\t\t\t\t\tsubdomain,\n\t\t\t\t\tlocalPort,\n\t\t\t\t\turl: `https://${subdomain}.tunnel.inetdev.io.vn`,\n\t\t\t\t})),\n\t\t});\n\t\treturn handle.port;\n\t}\n\n\treturn { store, start };\n}\n", "/** HTTP server that serves the portal SPA at localhost:<port> with SPA fallback */\n\nimport fs from \"node:fs\";\nimport http from \"node:http\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { listenWithPortFallback } from \"./listen-with-port-fallback.js\";\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\nconst MIME_TYPES: Record<string, string> = {\n\t\".html\": \"text/html; charset=utf-8\",\n\t\".js\": \"application/javascript\",\n\t\".css\": \"text/css\",\n\t\".json\": \"application/json\",\n\t\".png\": \"image/png\",\n\t\".svg\": \"image/svg+xml\",\n\t\".ico\": \"image/x-icon\",\n\t\".woff2\": \"font/woff2\",\n\t\".woff\": \"font/woff\",\n};\n\nlet activeServer: http.Server | null = null;\n\n/** Resolve portal-dist directory relative to this file at runtime */\nfunction resolveDistDir(): string {\n\t// When compiled: dist/portal-server.js \u2192 ../portal-dist\n\t// When running as tsx: src/portal-server.ts \u2192 ../portal-dist\n\treturn path.resolve(__dirname, \"..\", \"portal-dist\");\n}\n\nfunction serveFile(\n\tres: http.ServerResponse,\n\tfilePath: string,\n\tfallbackPath: string,\n): void {\n\tconst ext = path.extname(filePath);\n\tconst contentType = MIME_TYPES[ext] ?? \"application/octet-stream\";\n\n\tfs.readFile(filePath, (err, data) => {\n\t\tif (err) {\n\t\t\t// SPA fallback: serve index.html for unknown paths\n\t\t\tfs.readFile(fallbackPath, (fallbackErr, fallbackData) => {\n\t\t\t\tif (fallbackErr) {\n\t\t\t\t\tres.writeHead(404, { \"content-type\": \"text/plain\" });\n\t\t\t\t\tres.end(\"Portal not built. Run: pnpm build:portal\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"content-type\": \"text/html; charset=utf-8\",\n\t\t\t\t});\n\t\t\t\tres.end(fallbackData);\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tres.writeHead(200, { \"content-type\": contentType });\n\t\tres.end(data);\n\t});\n}\n\n/**\n * Serves the portal on `port`, or the next free port when it is taken,\n * so parallel CLI runs do not collide. Resolves with the bound port.\n */\nexport async function startPortalServer(port: number): Promise<number> {\n\tconst distDir = resolveDistDir();\n\tconst indexPath = path.join(distDir, \"index.html\");\n\n\tconst server = http.createServer((req, res) => {\n\t\tconst rawPath = new URL(req.url ?? \"/\", \"http://localhost\").pathname;\n\n\t\t// Sanitize path to prevent directory traversal\n\t\tconst safePath = path.normalize(rawPath).replace(/^(\\.\\.[/\\\\])+/, \"\");\n\t\tconst filePath = path.join(distDir, safePath);\n\n\t\t// Ensure resolved path stays within distDir\n\t\tif (!filePath.startsWith(distDir)) {\n\t\t\tres.writeHead(403, { \"content-type\": \"text/plain\" });\n\t\t\tres.end(\"Forbidden\");\n\t\t\treturn;\n\t\t}\n\n\t\t// If path is a directory, serve index.html inside it\n\t\tconst targetPath =\n\t\t\tsafePath === \"/\" || safePath.endsWith(\"/\") ? indexPath : filePath;\n\n\t\tserveFile(res, targetPath, indexPath);\n\t});\n\n\tconst { port: boundPort } = await listenWithPortFallback(server, port);\n\tactiveServer = server;\n\treturn boundPort;\n}\n\nexport function stopPortalServer(): Promise<void> {\n\treturn new Promise((resolve) => {\n\t\tif (!activeServer) {\n\t\t\tresolve();\n\t\t\treturn;\n\t\t}\n\t\tactiveServer.close(() => {\n\t\t\tactiveServer = null;\n\t\t\tresolve();\n\t\t});\n\t});\n}\n", "import { SUBDOMAIN_REGEX } from \"@tunelo/shared\";\n\nexport interface TunnelMapping {\n\tport: number;\n\tsubdomain?: string;\n}\n\n/** Parse \"port:subdomain\" or \"port\" syntax into TunnelMapping */\nexport function parseTunnelArg(arg: string): TunnelMapping {\n\tconst parts = arg.split(\":\");\n\n\tif (parts.length === 1) {\n\t\tconst port = validatePort(parts[0]);\n\t\treturn { port };\n\t}\n\n\tif (parts.length === 2) {\n\t\tconst port = validatePort(parts[0]);\n\t\tconst subdomain = parts[1];\n\t\tif (!SUBDOMAIN_REGEX.test(subdomain)) {\n\t\t\tthrow new Error(`Invalid subdomain format: \"${subdomain}\"`);\n\t\t}\n\t\treturn { port, subdomain };\n\t}\n\n\tthrow new Error(\n\t\t`Invalid tunnel format: \"${arg}\". Use <port> or <port>:<subdomain>`,\n\t);\n}\n\n/** Parse multiple tunnel args into TunnelMapping array */\nexport function parseTunnelArgs(args: string[]): TunnelMapping[] {\n\tif (args.length === 0) throw new Error(\"At least one port is required\");\n\treturn args.map(parseTunnelArg);\n}\n\nfunction validatePort(portStr: string): number {\n\tconst port = Number(portStr);\n\tif (Number.isNaN(port) || port < 1 || port > 65535) {\n\t\tthrow new Error(`Invalid port: \"${portStr}\" (must be 1-65535)`);\n\t}\n\treturn port;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["path", "program", "existsSync", "mkdirSync", "readFileSync", "writeFileSync", "homedir", "join", "path", "program", "WebSocket", "request", "WebSocket", "request", "program", "display", "program", "chalk", "formatUptime", "path", "http", "http", "http", "request", "http", "start", "http", "path", "fileURLToPath", "path", "fileURLToPath", "http", "config"]
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tunelo",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "1.6.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Self-hosted ngrok alternative — expose local services via public HTTPS tunnels",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"tunnel",
|
|
8
|
+
"ngrok",
|
|
9
|
+
"localhost",
|
|
10
|
+
"websocket",
|
|
11
|
+
"proxy",
|
|
12
|
+
"https",
|
|
13
|
+
"tcp"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/achoo254/tunelo.git",
|
|
18
|
+
"directory": "packages/client"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://www.npmjs.com/package/tunelo",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"bin": {
|
|
26
|
+
"tunelo": "cli.js"
|
|
27
|
+
},
|
|
28
|
+
"main": "cli.js",
|
|
29
|
+
"files": [
|
|
30
|
+
"cli.js",
|
|
31
|
+
"cli.js.map",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"chalk": "^5.3.0",
|
|
36
|
+
"commander": "^12.0.0",
|
|
37
|
+
"pino": "^9.0.0",
|
|
38
|
+
"ws": "^8.16.0"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public",
|
|
42
|
+
"registry": "https://registry.npmjs.org/"
|
|
43
|
+
}
|
|
44
|
+
}
|