xpose-dev 1.0.0
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 +134 -0
- package/dist/index.js +1494 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../node_modules/.bun/picocolors@1.1.1/node_modules/picocolors/picocolors.js","../src/index.ts","../../../packages/protocol/src/messages.ts","../../../packages/protocol/src/constants.ts","../../../packages/protocol/src/binary.ts","../../../packages/protocol/src/subdomain.ts","../src/tunnel-client.ts","../src/ws-relay.ts","../../../packages/tunnel-core/src/logger.ts","../../../packages/tunnel-core/src/turborepo.ts","../../../packages/tunnel-core/src/domain.ts","../../../packages/tunnel-core/src/session.ts","../src/tui/app.tsx"],"sourcesContent":["let p = process || {}, argv = p.argv || [], env = p.env || {}\nlet isColorSupported =\n\t!(!!env.NO_COLOR || argv.includes(\"--no-color\")) &&\n\t(!!env.FORCE_COLOR || argv.includes(\"--color\") || p.platform === \"win32\" || ((p.stdout || {}).isTTY && env.TERM !== \"dumb\") || !!env.CI)\n\nlet formatter = (open, close, replace = open) =>\n\tinput => {\n\t\tlet string = \"\" + input, index = string.indexOf(close, open.length)\n\t\treturn ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close\n\t}\n\nlet replaceClose = (string, close, replace, index) => {\n\tlet result = \"\", cursor = 0\n\tdo {\n\t\tresult += string.substring(cursor, index) + replace\n\t\tcursor = index + close.length\n\t\tindex = string.indexOf(close, cursor)\n\t} while (~index)\n\treturn result + string.substring(cursor)\n}\n\nlet createColors = (enabled = isColorSupported) => {\n\tlet f = enabled ? formatter : () => String\n\treturn {\n\t\tisColorSupported: enabled,\n\t\treset: f(\"\\x1b[0m\", \"\\x1b[0m\"),\n\t\tbold: f(\"\\x1b[1m\", \"\\x1b[22m\", \"\\x1b[22m\\x1b[1m\"),\n\t\tdim: f(\"\\x1b[2m\", \"\\x1b[22m\", \"\\x1b[22m\\x1b[2m\"),\n\t\titalic: f(\"\\x1b[3m\", \"\\x1b[23m\"),\n\t\tunderline: f(\"\\x1b[4m\", \"\\x1b[24m\"),\n\t\tinverse: f(\"\\x1b[7m\", \"\\x1b[27m\"),\n\t\thidden: f(\"\\x1b[8m\", \"\\x1b[28m\"),\n\t\tstrikethrough: f(\"\\x1b[9m\", \"\\x1b[29m\"),\n\n\t\tblack: f(\"\\x1b[30m\", \"\\x1b[39m\"),\n\t\tred: f(\"\\x1b[31m\", \"\\x1b[39m\"),\n\t\tgreen: f(\"\\x1b[32m\", \"\\x1b[39m\"),\n\t\tyellow: f(\"\\x1b[33m\", \"\\x1b[39m\"),\n\t\tblue: f(\"\\x1b[34m\", \"\\x1b[39m\"),\n\t\tmagenta: f(\"\\x1b[35m\", \"\\x1b[39m\"),\n\t\tcyan: f(\"\\x1b[36m\", \"\\x1b[39m\"),\n\t\twhite: f(\"\\x1b[37m\", \"\\x1b[39m\"),\n\t\tgray: f(\"\\x1b[90m\", \"\\x1b[39m\"),\n\n\t\tbgBlack: f(\"\\x1b[40m\", \"\\x1b[49m\"),\n\t\tbgRed: f(\"\\x1b[41m\", \"\\x1b[49m\"),\n\t\tbgGreen: f(\"\\x1b[42m\", \"\\x1b[49m\"),\n\t\tbgYellow: f(\"\\x1b[43m\", \"\\x1b[49m\"),\n\t\tbgBlue: f(\"\\x1b[44m\", \"\\x1b[49m\"),\n\t\tbgMagenta: f(\"\\x1b[45m\", \"\\x1b[49m\"),\n\t\tbgCyan: f(\"\\x1b[46m\", \"\\x1b[49m\"),\n\t\tbgWhite: f(\"\\x1b[47m\", \"\\x1b[49m\"),\n\n\t\tblackBright: f(\"\\x1b[90m\", \"\\x1b[39m\"),\n\t\tredBright: f(\"\\x1b[91m\", \"\\x1b[39m\"),\n\t\tgreenBright: f(\"\\x1b[92m\", \"\\x1b[39m\"),\n\t\tyellowBright: f(\"\\x1b[93m\", \"\\x1b[39m\"),\n\t\tblueBright: f(\"\\x1b[94m\", \"\\x1b[39m\"),\n\t\tmagentaBright: f(\"\\x1b[95m\", \"\\x1b[39m\"),\n\t\tcyanBright: f(\"\\x1b[96m\", \"\\x1b[39m\"),\n\t\twhiteBright: f(\"\\x1b[97m\", \"\\x1b[39m\"),\n\n\t\tbgBlackBright: f(\"\\x1b[100m\", \"\\x1b[49m\"),\n\t\tbgRedBright: f(\"\\x1b[101m\", \"\\x1b[49m\"),\n\t\tbgGreenBright: f(\"\\x1b[102m\", \"\\x1b[49m\"),\n\t\tbgYellowBright: f(\"\\x1b[103m\", \"\\x1b[49m\"),\n\t\tbgBlueBright: f(\"\\x1b[104m\", \"\\x1b[49m\"),\n\t\tbgMagentaBright: f(\"\\x1b[105m\", \"\\x1b[49m\"),\n\t\tbgCyanBright: f(\"\\x1b[106m\", \"\\x1b[49m\"),\n\t\tbgWhiteBright: f(\"\\x1b[107m\", \"\\x1b[49m\"),\n\t}\n}\n\nmodule.exports = createColors()\nmodule.exports.createColors = createColors\n","import { defineCommand, runMain } from \"citty\";\nimport {\n PROTOCOL,\n generateSubdomainId,\n buildCustomSubdomain,\n} from \"@xpose/protocol\";\nimport { createTunnelClient } from \"./tunnel-client.js\";\nimport { existsSync, statSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport {\n printError,\n discoverTurboPorts,\n normalizeDomain,\n saveSession,\n loadSession,\n type TunnelEntry,\n} from \"@xpose/tunnel-core\";\nimport React from \"react\";\nimport { render } from \"ink\";\nimport { App } from \"./tui/app.js\";\n\n/**\n * Create tunnel clients from entries, save the session, run the TUI,\n * and print the resume hint on exit.\n */\nfunction runTunnels(entries: TunnelEntry[], rawTtl: number): void {\n const tunnelTtl = Math.min(rawTtl, PROTOCOL.MAX_TTL_SECONDS);\n\n // Save session so it can be resumed after exit\n saveSession({\n tunnels: entries,\n createdAt: new Date().toISOString(),\n });\n\n const clients = entries.map(({ subdomain, port, domain }) =>\n createTunnelClient({\n subdomain,\n port,\n ttl: tunnelTtl,\n host: \"localhost\",\n domain,\n }),\n );\n\n const ports = entries.map((e) => e.port);\n\n // Start all tunnel connections\n for (const client of clients) {\n client.connect();\n }\n\n function shutdown() {\n // Update session timestamp so resume window starts from exit time\n saveSession({\n tunnels: entries,\n createdAt: new Date().toISOString(),\n });\n\n for (const client of clients) {\n client.disconnect();\n }\n\n const minutes = PROTOCOL.SESSION_RESUME_WINDOW_SECONDS / 60;\n console.error(\n `\\n Session saved. Resume within ${minutes} minutes with: xpose-dev -r\\n`,\n );\n }\n\n // Render the ink TUI\n const inkApp = render(\n React.createElement(App, {\n clients,\n ports,\n onQuit: shutdown,\n }),\n );\n\n inkApp.waitUntilExit().then(() => {\n process.exit(0);\n });\n}\n\nconst main = defineCommand({\n meta: {\n name: \"xpose-dev\",\n version: \"0.0.1\",\n description: \"Expose local servers to the internet via Cloudflare\",\n },\n args: {\n port: {\n type: \"positional\",\n description: \"Local port to expose (optional when using --from-turbo)\",\n required: false,\n },\n fromTurbo: {\n type: \"boolean\",\n description: \"Auto-detect ports from `turbo run <task> --dry=json`\",\n },\n turboTask: {\n type: \"string\",\n description: \"Turborepo task to inspect when using --from-turbo\",\n default: \"dev\",\n },\n turboFilter: {\n type: \"string\",\n description: \"Optional Turborepo filter when using --from-turbo\",\n },\n turboPath: {\n type: \"string\",\n alias: \"path\",\n description: \"Path to the Turborepo project root when using --from-turbo\",\n },\n ttl: {\n type: \"string\",\n description: `Tunnel TTL in seconds (default: ${PROTOCOL.DEFAULT_TTL_SECONDS})`,\n default: String(PROTOCOL.DEFAULT_TTL_SECONDS),\n },\n subdomain: {\n type: \"string\",\n description: \"Custom subdomain (default: random)\",\n },\n domain: {\n type: \"string\",\n description: \"Public tunnel domain (default: xpose.dev)\",\n default: PROTOCOL.DEFAULT_PUBLIC_DOMAIN,\n },\n resume: {\n type: \"boolean\",\n alias: \"r\",\n description: \"Resume the previous tunnel session\",\n },\n },\n async run({ args }) {\n const ttl = parseInt(args.ttl, 10);\n if (isNaN(ttl) || ttl < 1) {\n printError(\"Invalid TTL. Must be a positive number of seconds.\");\n process.exit(1);\n }\n\n // --- Resume mode ---\n if (args.resume) {\n const manualRawPorts = (\n args._.length > 0 ? args._ : args.port ? [args.port] : []\n ).map(String);\n if (manualRawPorts.length > 0 || args.fromTurbo) {\n printError(\"Cannot use --resume with port arguments or --from-turbo.\");\n process.exit(1);\n }\n\n const prev = loadSession();\n if (!prev) {\n const minutes = PROTOCOL.SESSION_RESUME_WINDOW_SECONDS / 60;\n printError(\n `No session to resume (sessions expire after ${minutes} minutes).`,\n );\n process.exit(1);\n }\n\n return runTunnels(prev.tunnels, ttl);\n }\n\n // --- Normal mode: resolve ports ---\n const manualRawPorts = (\n args._.length > 0 ? args._ : args.port ? [args.port] : []\n ).map(String);\n const parsedManualPorts = manualRawPorts.map((raw) =>\n Number.parseInt(raw, 10),\n );\n const invalidManualPort = parsedManualPorts.find(\n (port) => Number.isNaN(port) || port < 1 || port > 65535,\n );\n if (invalidManualPort !== undefined) {\n printError(\"Invalid port number. Ports must be between 1 and 65535.\");\n process.exit(1);\n }\n\n const ports = new Set(parsedManualPorts);\n if (args.fromTurbo) {\n const turboTask = args.turboTask?.trim() || \"dev\";\n const turboCwd = args.turboPath\n ? resolve(process.cwd(), args.turboPath)\n : process.cwd();\n\n if (!existsSync(turboCwd) || !statSync(turboCwd).isDirectory()) {\n printError(`Invalid --path. Directory does not exist: ${turboCwd}`);\n process.exit(1);\n }\n\n try {\n const discovered = await discoverTurboPorts({\n cwd: turboCwd,\n task: turboTask,\n filter: args.turboFilter,\n });\n\n if (discovered.length === 0) {\n printError(`No ports detected from Turborepo task \"${turboTask}\".`);\n } else {\n console.log(\n ` Discovered from Turborepo (${turboTask}): ${discovered.map((entry) => `${entry.port} [${entry.packageName}]`).join(\", \")}`,\n );\n }\n\n for (const entry of discovered) {\n ports.add(entry.port);\n }\n } catch (err) {\n printError(`Failed to inspect Turborepo: ${(err as Error).message}`);\n process.exit(1);\n }\n }\n\n if (ports.size === 0) {\n printError(\n \"No ports provided. Pass ports directly (e.g. `xpose-dev 3000 8787`) or use --from-turbo.\",\n );\n process.exit(1);\n }\n\n const resolvedPorts = [...ports];\n const baseSubdomain = args.subdomain?.trim();\n const tunnelDomain = normalizeDomain(\n args.domain ?? PROTOCOL.DEFAULT_PUBLIC_DOMAIN,\n );\n if (!tunnelDomain) {\n printError(\"Invalid domain. Pass a hostname like xpose.dev.\");\n process.exit(1);\n }\n\n // Build tunnel entries with generated subdomains\n const entries: TunnelEntry[] = resolvedPorts.map((port) => {\n const subdomain = baseSubdomain\n ? resolvedPorts.length === 1\n ? buildCustomSubdomain(baseSubdomain)\n : buildCustomSubdomain(`${baseSubdomain}-${port}`)\n : generateSubdomainId();\n return { subdomain, port, domain: tunnelDomain };\n });\n\n return runTunnels(entries, ttl);\n },\n});\n\nrunMain(main);\n","// ---- Text frame messages (sent as JSON strings over WebSocket) ----\n\nexport interface AuthMessage {\n type: \"auth\";\n subdomain: string;\n ttl?: number;\n sessionId?: string;\n}\n\nexport interface AuthAckMessage {\n type: \"auth-ack\";\n subdomain: string;\n url: string;\n ttl: number;\n /** Actual seconds remaining on the alarm (may be less than ttl on resume) */\n remainingTtl: number;\n sessionId: string;\n maxBodySizeBytes: number;\n}\n\nexport interface HttpRequestMessage {\n type: \"http-request\";\n id: string;\n method: string;\n path: string;\n headers: Record<string, string>;\n hasBody: boolean;\n}\n\nexport interface HttpResponseMetaMessage {\n type: \"http-response-meta\";\n id: string;\n status: number;\n headers: Record<string, string>;\n hasBody: boolean;\n}\n\nexport interface HttpBodyChunkMessage {\n type: \"http-body-chunk\";\n id: string;\n done: boolean;\n}\n\nexport interface HttpRequestEndMessage {\n type: \"http-request-end\";\n id: string;\n}\n\nexport interface HttpResponseEndMessage {\n type: \"http-response-end\";\n id: string;\n}\n\nexport interface PingMessage {\n type: \"ping\";\n}\n\nexport interface PongMessage {\n type: \"pong\";\n}\n\nexport interface ErrorMessage {\n type: \"error\";\n message: string;\n requestId?: string;\n status?: number;\n}\n\n// ---- WebSocket relay messages (for proxying browser WS through tunnel) ----\n\nexport interface WsUpgradeMessage {\n type: \"ws-upgrade\";\n streamId: string;\n path: string;\n headers: Record<string, string>;\n}\n\nexport interface WsUpgradeAckMessage {\n type: \"ws-upgrade-ack\";\n streamId: string;\n ok: boolean;\n error?: string;\n}\n\nexport interface WsFrameMessage {\n type: \"ws-frame\";\n streamId: string;\n /** \"text\" or \"binary\" */\n frameType: \"text\" | \"binary\";\n}\n\nexport interface WsCloseMessage {\n type: \"ws-close\";\n streamId: string;\n code: number;\n reason: string;\n}\n\nexport type TunnelMessage =\n | AuthMessage\n | AuthAckMessage\n | HttpRequestMessage\n | HttpResponseMetaMessage\n | HttpBodyChunkMessage\n | HttpRequestEndMessage\n | HttpResponseEndMessage\n | PingMessage\n | PongMessage\n | ErrorMessage\n | WsUpgradeMessage\n | WsUpgradeAckMessage\n | WsFrameMessage\n | WsCloseMessage;\n\nexport function isTunnelMessage(data: unknown): data is TunnelMessage {\n return (\n typeof data === \"object\" &&\n data !== null &&\n \"type\" in data &&\n typeof (data as TunnelMessage).type === \"string\"\n );\n}\n\nexport function parseTextMessage(raw: string): TunnelMessage | null {\n try {\n const parsed = JSON.parse(raw);\n if (isTunnelMessage(parsed)) return parsed;\n return null;\n } catch {\n return null;\n }\n}\n","export const PROTOCOL = {\n /** Length of generated subdomain IDs */\n SUBDOMAIN_LENGTH: 12,\n /** Length of random suffix appended to custom subdomains */\n SUBDOMAIN_SUFFIX_LENGTH: 6,\n /** Characters used in subdomain IDs (lowercase + digits only for DNS compatibility) */\n SUBDOMAIN_ALPHABET: \"abcdefghijklmnopqrstuvwxyz0123456789\",\n\n /** Length of request IDs for multiplexing */\n REQUEST_ID_LENGTH: 12,\n\n /** How long to wait for a response from the CLI before timing out */\n REQUEST_TIMEOUT_MS: 30_000,\n /** How long to wait for CLI reconnection before rejecting queued requests */\n RECONNECT_GRACE_PERIOD_MS: 5_000,\n\n /** Default request/response body limit: 5MB */\n DEFAULT_MAX_BODY_SIZE_BYTES: 5 * 1024 * 1024,\n\n /** Reconnection backoff parameters */\n BACKOFF_BASE_MS: 1_000,\n BACKOFF_MULTIPLIER: 2,\n BACKOFF_MAX_MS: 30_000,\n BACKOFF_MAX_ATTEMPTS: 15,\n BACKOFF_JITTER_MIN: 0.1,\n BACKOFF_JITTER_MAX: 0.2,\n\n /** Default tunnel lifetime: 4 hours */\n DEFAULT_TTL_SECONDS: 14_400,\n /** Maximum tunnel lifetime: 24 hours */\n MAX_TTL_SECONDS: 86_400,\n\n /** Path where CLI connects via WebSocket */\n TUNNEL_CONNECT_PATH: \"/_tunnel/connect\",\n\n /** Default public tunnel domain */\n DEFAULT_PUBLIC_DOMAIN: \"xpose.dev\",\n\n /** Auto ping/pong strings for DO WebSocket hibernation */\n PING_MESSAGE: \"ping\",\n PONG_MESSAGE: \"pong\",\n\n /** How long a CLI session can be resumed after exit (seconds) */\n SESSION_RESUME_WINDOW_SECONDS: 600,\n} as const;\n","import { PROTOCOL } from \"./constants.js\";\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder();\n\n/**\n * Encode a binary body chunk: [requestId (12 bytes ASCII)][body bytes]\n */\nexport function encodeBinaryFrame(\n requestId: string,\n body: Uint8Array,\n): ArrayBuffer {\n const idBytes = encoder.encode(requestId);\n const frame = new Uint8Array(PROTOCOL.REQUEST_ID_LENGTH + body.byteLength);\n frame.set(idBytes, 0);\n frame.set(body, PROTOCOL.REQUEST_ID_LENGTH);\n return frame.buffer;\n}\n\n/**\n * Decode a binary frame into requestId and body\n */\nexport function decodeBinaryFrame(buffer: ArrayBuffer): {\n requestId: string;\n body: Uint8Array;\n} {\n const view = new Uint8Array(buffer);\n const requestId = decoder.decode(\n view.slice(0, PROTOCOL.REQUEST_ID_LENGTH),\n );\n const body = view.slice(PROTOCOL.REQUEST_ID_LENGTH);\n return { requestId, body };\n}\n","import { customAlphabet } from \"nanoid\";\nimport { PROTOCOL } from \"./constants.js\";\n\nexport const generateSubdomainId = customAlphabet(\n PROTOCOL.SUBDOMAIN_ALPHABET,\n PROTOCOL.SUBDOMAIN_LENGTH,\n);\n\nexport const generateRequestId = customAlphabet(\n PROTOCOL.SUBDOMAIN_ALPHABET,\n PROTOCOL.REQUEST_ID_LENGTH,\n);\n\nconst generateSuffix = customAlphabet(\n PROTOCOL.SUBDOMAIN_ALPHABET,\n PROTOCOL.SUBDOMAIN_SUFFIX_LENGTH,\n);\n\n/**\n * Build a subdomain from a user-provided prefix by sanitizing it and\n * appending a random suffix: `my-app` → `my-app-x7k2m4`.\n * Falls back to a fully random ID if the prefix is empty after sanitizing.\n */\nexport function buildCustomSubdomain(prefix: string): string {\n const sanitized = prefix\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, \"\")\n .replace(/^-+|-+$/g, \"\");\n if (!sanitized) return generateSubdomainId();\n return `${sanitized}-${generateSuffix()}`;\n}\n\nconst SUBDOMAIN_PATTERN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;\nconst MAX_SUBDOMAIN_LENGTH = 63; // DNS label limit\n\nexport function validateSubdomain(\n subdomain: string,\n): { ok: true } | { ok: false; reason: string } {\n if (subdomain.length < 1 || subdomain.length > MAX_SUBDOMAIN_LENGTH) {\n return { ok: false, reason: \"Subdomain must be 1-63 characters\" };\n }\n if (!SUBDOMAIN_PATTERN.test(subdomain)) {\n return {\n ok: false,\n reason:\n \"Subdomain must contain only lowercase letters, digits, and hyphens\",\n };\n }\n return { ok: true };\n}\n","import WebSocket from \"ws\";\nimport { EventEmitter } from \"node:events\";\nimport {\n PROTOCOL,\n parseTextMessage,\n encodeBinaryFrame,\n decodeBinaryFrame,\n type TunnelMessage,\n type HttpRequestMessage,\n type WsFrameMessage,\n} from \"@xpose/protocol\";\nimport { WsRelayManager } from \"./ws-relay.js\";\nimport type { TrafficEntry, TunnelStatus } from \"@xpose/tunnel-core\";\n\ninterface BufferedBody {\n chunks: Uint8Array[];\n totalBytes: number;\n}\n\nexport interface TunnelClientOptions {\n subdomain: string;\n port: number;\n ttl: number;\n host: string;\n domain?: string;\n}\n\nexport interface TunnelClient {\n connect(): void;\n disconnect(): void;\n on(event: \"status\", listener: (status: TunnelStatus) => void): void;\n on(\n event: \"authenticated\",\n listener: (data: {\n url: string;\n ttl: number;\n sessionId: string;\n maxBodySizeBytes: number;\n }) => void,\n ): void;\n on(event: \"traffic\", listener: (entry: TrafficEntry) => void): void;\n on(event: \"error\", listener: (err: Error) => void): void;\n on(event: \"expired\", listener: () => void): void;\n}\n\nfunction parseContentLength(raw: string | undefined): number | null {\n if (!raw) return null;\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isFinite(parsed) || parsed < 0) return null;\n return parsed;\n}\n\nfunction headerValue(\n headers: Record<string, string>,\n name: string,\n): string | undefined {\n const target = name.toLowerCase();\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() === target) return value;\n }\n return undefined;\n}\n\nfunction toArrayBuffer(view: Uint8Array): ArrayBuffer {\n return view.buffer.slice(\n view.byteOffset,\n view.byteOffset + view.byteLength,\n ) as ArrayBuffer;\n}\n\nexport function createTunnelClient(opts: TunnelClientOptions): TunnelClient {\n const emitter = new EventEmitter();\n let ws: WebSocket | null = null;\n let reconnectAttempts = 0;\n let sessionId: string | null = null;\n let disconnectedIntentionally = false;\n let maxBodySizeBytes = PROTOCOL.DEFAULT_MAX_BODY_SIZE_BYTES;\n\n // Track incoming request bodies and metadata\n const requestBodyChunks = new Map<string, Uint8Array[]>();\n const requestBodySizes = new Map<string, number>();\n const oversizedRequestIds = new Set<string>();\n const pendingRequestMeta = new Map<string, HttpRequestMessage>();\n\n // WebSocket relay support\n const wsRelayMgr = new WsRelayManager(opts.host, opts.port);\n // Track pending WS frame types (set by ws-frame text message, consumed by next binary frame)\n const pendingWsFrameTypes = new Map<string, \"text\" | \"binary\">();\n\n const tunnelDomain = opts.domain?.trim() || PROTOCOL.DEFAULT_PUBLIC_DOMAIN;\n const wsUrl = `wss://${opts.subdomain}.${tunnelDomain}${PROTOCOL.TUNNEL_CONNECT_PATH}`;\n\n function emitStatus(status: TunnelStatus) {\n emitter.emit(\"status\", status);\n }\n\n function sendMessage(message: TunnelMessage) {\n ws?.send(JSON.stringify(message));\n }\n\n async function readBodyWithinLimit(\n body: ReadableStream<Uint8Array> | null,\n ): Promise<BufferedBody | null> {\n if (!body) {\n return { chunks: [], totalBytes: 0 };\n }\n\n const chunks: Uint8Array[] = [];\n let totalBytes = 0;\n const reader = body.getReader();\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n totalBytes += value.byteLength;\n if (totalBytes > maxBodySizeBytes) {\n return null;\n }\n chunks.push(value);\n }\n } finally {\n reader.releaseLock();\n }\n\n return { chunks, totalBytes };\n }\n\n function respondWith413(requestId: string, message: string) {\n sendMessage({\n type: \"error\",\n message,\n requestId,\n status: 413,\n } satisfies TunnelMessage);\n }\n\n function handleBinaryFrame(data: ArrayBuffer) {\n const { requestId, body } = decodeBinaryFrame(data);\n\n // Check if this is a WS relay frame\n const frameType = pendingWsFrameTypes.get(requestId);\n if (frameType !== undefined) {\n pendingWsFrameTypes.delete(requestId);\n wsRelayMgr.handleFrame(\n {\n type: \"ws-frame\",\n streamId: requestId,\n frameType,\n } satisfies WsFrameMessage,\n body,\n );\n return;\n }\n\n // Otherwise it's an HTTP request body chunk\n const chunks = requestBodyChunks.get(requestId);\n if (!chunks) return;\n if (oversizedRequestIds.has(requestId)) return;\n\n const nextSize = (requestBodySizes.get(requestId) ?? 0) + body.byteLength;\n requestBodySizes.set(requestId, nextSize);\n\n if (nextSize > maxBodySizeBytes) {\n oversizedRequestIds.add(requestId);\n requestBodyChunks.delete(requestId);\n return;\n }\n\n chunks.push(body);\n }\n\n function connect() {\n emitStatus(reconnectAttempts > 0 ? \"reconnecting\" : \"connecting\");\n disconnectedIntentionally = false;\n\n ws = new WebSocket(wsUrl);\n ws.binaryType = \"arraybuffer\";\n\n ws.on(\"open\", () => {\n reconnectAttempts = 0;\n\n sendMessage({\n type: \"auth\",\n subdomain: opts.subdomain,\n ttl: opts.ttl,\n sessionId: sessionId ?? undefined,\n } satisfies TunnelMessage);\n });\n\n ws.on(\"message\", (data: WebSocket.Data) => {\n // Binary frame: body chunk for an incoming request or WS relay\n if (data instanceof ArrayBuffer) {\n handleBinaryFrame(data);\n return;\n }\n\n // Node.js ws delivers binary as Buffer, not ArrayBuffer\n if (Buffer.isBuffer(data)) {\n const str = data.toString(\"utf8\");\n if (!str.startsWith(\"{\")) {\n const ab = data.buffer.slice(\n data.byteOffset,\n data.byteOffset + data.byteLength,\n );\n handleBinaryFrame(ab as ArrayBuffer);\n return;\n }\n // Otherwise it's a JSON text message in Buffer form\n const msg = parseTextMessage(str);\n if (msg) handleTextMessage(msg);\n return;\n }\n\n // String text frame\n const raw = typeof data === \"string\" ? data : String(data);\n const msg = parseTextMessage(raw);\n if (msg) handleTextMessage(msg);\n });\n\n ws.on(\"close\", () => {\n if (disconnectedIntentionally) return;\n scheduleReconnect();\n });\n\n ws.on(\"error\", (err) => {\n emitter.emit(\"error\", err);\n });\n }\n\n function handleTextMessage(msg: TunnelMessage) {\n switch (msg.type) {\n case \"auth-ack\": {\n sessionId = msg.sessionId;\n maxBodySizeBytes = msg.maxBodySizeBytes;\n // Use remainingTtl for countdown if the server provided it\n const displayTtl = msg.remainingTtl > 0 ? msg.remainingTtl : msg.ttl;\n emitStatus(\"connected\");\n emitter.emit(\"authenticated\", {\n url: msg.url,\n ttl: displayTtl,\n sessionId: msg.sessionId,\n maxBodySizeBytes: msg.maxBodySizeBytes,\n });\n break;\n }\n\n case \"http-request\": {\n const contentLength = parseContentLength(\n headerValue(msg.headers, \"content-length\"),\n );\n if (contentLength !== null && contentLength > maxBodySizeBytes) {\n respondWith413(\n msg.id,\n `Request body exceeds ${maxBodySizeBytes} byte limit`,\n );\n break;\n }\n\n if (msg.hasBody) {\n // Store metadata; wait for body chunks + http-request-end\n requestBodyChunks.set(msg.id, []);\n requestBodySizes.set(msg.id, 0);\n pendingRequestMeta.set(msg.id, msg);\n } else {\n // No body, handle immediately\n handleHttpRequest(msg, null);\n }\n break;\n }\n\n case \"http-request-end\": {\n const chunks = requestBodyChunks.get(msg.id);\n requestBodyChunks.delete(msg.id);\n\n const reqMeta = pendingRequestMeta.get(msg.id);\n pendingRequestMeta.delete(msg.id);\n requestBodySizes.delete(msg.id);\n\n if (!reqMeta) break;\n\n if (oversizedRequestIds.delete(msg.id)) {\n respondWith413(\n msg.id,\n `Request body exceeds ${maxBodySizeBytes} byte limit`,\n );\n break;\n }\n\n const body = concatChunks(chunks ?? []);\n handleHttpRequest(reqMeta, body.byteLength > 0 ? body : null);\n break;\n }\n\n case \"http-body-chunk\": {\n // Binary data follows, handled in binary frame branch.\n break;\n }\n\n case \"ping\": {\n sendMessage({ type: \"pong\" } satisfies TunnelMessage);\n break;\n }\n\n case \"ws-upgrade\": {\n // Server asks us to open a local WebSocket connection\n wsRelayMgr.handleUpgrade(ws!, msg, sendMessage);\n break;\n }\n\n case \"ws-frame\": {\n // A WS frame header from the server. The actual payload follows as a binary frame.\n // Store the frame type so handleBinaryFrame can dispatch correctly.\n pendingWsFrameTypes.set(msg.streamId, msg.frameType);\n break;\n }\n\n case \"ws-close\": {\n wsRelayMgr.handleClose(msg);\n break;\n }\n\n case \"error\": {\n if (msg.message === \"Tunnel TTL expired\") {\n emitStatus(\"expired\");\n emitter.emit(\"expired\");\n } else {\n emitter.emit(\"error\", new Error(msg.message));\n }\n break;\n }\n }\n }\n\n async function handleHttpRequest(\n msg: HttpRequestMessage,\n body: Uint8Array | null,\n ) {\n const startTime = Date.now();\n const localUrl = `http://${opts.host}:${opts.port}${msg.path}`;\n\n try {\n const reqHeaders = new Headers();\n for (const [key, value] of Object.entries(msg.headers)) {\n const lower = key.toLowerCase();\n if (\n lower === \"host\" ||\n lower === \"connection\" ||\n lower === \"transfer-encoding\"\n )\n continue;\n reqHeaders.set(key, value);\n }\n\n const response = await fetch(localUrl, {\n method: msg.method,\n headers: reqHeaders,\n body: body ? toArrayBuffer(body) : undefined,\n redirect: \"manual\",\n });\n\n const responseHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value;\n });\n\n const contentLength = parseContentLength(\n headerValue(responseHeaders, \"content-length\"),\n );\n if (contentLength !== null && contentLength > maxBodySizeBytes) {\n respondWith413(\n msg.id,\n `Response body exceeds ${maxBodySizeBytes} byte limit`,\n );\n emitter.emit(\"traffic\", {\n id: msg.id,\n method: msg.method,\n path: msg.path,\n status: 413,\n duration: Date.now() - startTime,\n timestamp: new Date(),\n } satisfies TrafficEntry);\n return;\n }\n\n const bufferedResponse = await readBodyWithinLimit(response.body);\n if (!bufferedResponse) {\n respondWith413(\n msg.id,\n `Response body exceeds ${maxBodySizeBytes} byte limit`,\n );\n emitter.emit(\"traffic\", {\n id: msg.id,\n method: msg.method,\n path: msg.path,\n status: 413,\n duration: Date.now() - startTime,\n timestamp: new Date(),\n } satisfies TrafficEntry);\n return;\n }\n\n const hasBody = bufferedResponse.totalBytes > 0;\n\n sendMessage({\n type: \"http-response-meta\",\n id: msg.id,\n status: response.status,\n headers: responseHeaders,\n hasBody,\n } satisfies TunnelMessage);\n\n if (hasBody) {\n const chunkSize = 64 * 1024; // 64KB chunks\n for (const chunk of bufferedResponse.chunks) {\n // Split large chunks into 64KB pieces\n for (let offset = 0; offset < chunk.byteLength; offset += chunkSize) {\n const end = Math.min(offset + chunkSize, chunk.byteLength);\n const piece = chunk.slice(offset, end);\n\n sendMessage({\n type: \"http-body-chunk\",\n id: msg.id,\n done: false,\n } satisfies TunnelMessage);\n ws?.send(encodeBinaryFrame(msg.id, piece));\n }\n }\n }\n\n sendMessage({\n type: \"http-response-end\",\n id: msg.id,\n } satisfies TunnelMessage);\n\n emitter.emit(\"traffic\", {\n id: msg.id,\n method: msg.method,\n path: msg.path,\n status: response.status,\n duration: Date.now() - startTime,\n timestamp: new Date(),\n } satisfies TrafficEntry);\n } catch (err) {\n const errMessage = (err as Error).message;\n const isConnectionRefused =\n errMessage.includes(\"ECONNREFUSED\") ||\n errMessage.includes(\"fetch failed\");\n\n sendMessage({\n type: \"error\",\n message: isConnectionRefused\n ? `Could not connect to localhost:${opts.port} — is your server running?`\n : `Failed to reach localhost:${opts.port}: ${errMessage}`,\n requestId: msg.id,\n status: 502,\n } satisfies TunnelMessage);\n\n emitter.emit(\"traffic\", {\n id: msg.id,\n method: msg.method,\n path: msg.path,\n status: 502,\n duration: Date.now() - startTime,\n timestamp: new Date(),\n } satisfies TrafficEntry);\n }\n }\n\n function concatChunks(chunks: Uint8Array[]): Uint8Array {\n const totalLength = chunks.reduce((sum, c) => sum + c.byteLength, 0);\n const result = new Uint8Array(totalLength);\n let offset = 0;\n for (const chunk of chunks) {\n result.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return result;\n }\n\n function scheduleReconnect() {\n if (reconnectAttempts >= PROTOCOL.BACKOFF_MAX_ATTEMPTS) {\n emitStatus(\"disconnected\");\n return;\n }\n\n emitStatus(\"reconnecting\");\n\n const baseDelay =\n PROTOCOL.BACKOFF_BASE_MS *\n Math.pow(PROTOCOL.BACKOFF_MULTIPLIER, reconnectAttempts);\n const delay = Math.min(baseDelay, PROTOCOL.BACKOFF_MAX_MS);\n const jitter =\n delay *\n (PROTOCOL.BACKOFF_JITTER_MIN +\n Math.random() *\n (PROTOCOL.BACKOFF_JITTER_MAX - PROTOCOL.BACKOFF_JITTER_MIN));\n\n reconnectAttempts++;\n setTimeout(() => connect(), delay + jitter);\n }\n\n function disconnect() {\n disconnectedIntentionally = true;\n emitStatus(\"disconnected\");\n wsRelayMgr.closeAll();\n ws?.close(1000, \"Client disconnect\");\n ws = null;\n }\n\n return {\n connect,\n disconnect,\n on: emitter.on.bind(emitter) as TunnelClient[\"on\"],\n };\n}\n","import WebSocket from \"ws\";\nimport {\n encodeBinaryFrame,\n type TunnelMessage,\n type WsUpgradeMessage,\n type WsFrameMessage,\n type WsCloseMessage,\n} from \"@xpose/protocol\";\n\n/**\n * A single active WebSocket relay: browser <-> tunnel <-> local WS server.\n */\ninterface WsRelay {\n streamId: string;\n localConn: WebSocket;\n}\n\n/**\n * Manages active WebSocket relay connections, forwarding frames between\n * the tunnel WebSocket and local WebSocket servers.\n *\n * WebSocket relay manager for proxying WebSocket connections through the tunnel.\n */\nexport class WsRelayManager {\n private relays = new Map<string, WsRelay>();\n private host: string;\n private port: number;\n\n constructor(host: string, port: number) {\n this.host = host;\n this.port = port;\n }\n\n /**\n * Handle a ws-upgrade request from the server: dial the local WebSocket\n * endpoint and start relaying frames.\n */\n handleUpgrade(\n tunnelWs: WebSocket,\n msg: WsUpgradeMessage,\n sendMessage: (message: TunnelMessage) => void,\n ): void {\n const localUrl = `ws://${this.host}:${this.port}${msg.path}`;\n\n // Build headers, skipping WebSocket handshake headers the local dial creates\n const skipHeaders = new Set([\n \"host\",\n \"upgrade\",\n \"connection\",\n \"sec-websocket-key\",\n \"sec-websocket-version\",\n \"sec-websocket-extensions\",\n ]);\n\n const headers: Record<string, string> = {};\n const subprotocols: string[] = [];\n\n for (const [key, value] of Object.entries(msg.headers)) {\n const lower = key.toLowerCase();\n if (skipHeaders.has(lower)) continue;\n if (lower === \"sec-websocket-protocol\") {\n for (const p of value.split(\",\")) {\n const trimmed = p.trim();\n if (trimmed) subprotocols.push(trimmed);\n }\n continue;\n }\n headers[lower] = value;\n }\n\n const localConn = new WebSocket(localUrl, subprotocols, { headers });\n localConn.binaryType = \"arraybuffer\";\n\n localConn.on(\"error\", () => {\n sendMessage({\n type: \"ws-upgrade-ack\",\n streamId: msg.streamId,\n ok: false,\n error: `Failed to connect to ${localUrl}`,\n } satisfies TunnelMessage);\n this.relays.delete(msg.streamId);\n });\n\n localConn.on(\"open\", () => {\n const relay: WsRelay = {\n streamId: msg.streamId,\n localConn,\n };\n this.relays.set(msg.streamId, relay);\n\n // Confirm success\n sendMessage({\n type: \"ws-upgrade-ack\",\n streamId: msg.streamId,\n ok: true,\n } satisfies TunnelMessage);\n\n // Start reading from local WS and forwarding to tunnel\n this.readLocalAndForward(tunnelWs, relay, sendMessage);\n });\n\n localConn.on(\"close\", () => {\n if (!this.relays.has(msg.streamId)) return;\n\n sendMessage({\n type: \"ws-close\",\n streamId: msg.streamId,\n code: 1000,\n reason: \"Local WebSocket closed\",\n } satisfies TunnelMessage);\n this.relays.delete(msg.streamId);\n });\n }\n\n /**\n * Read frames from the local WebSocket and forward them through the tunnel.\n */\n private readLocalAndForward(\n tunnelWs: WebSocket,\n relay: WsRelay,\n sendMessage: (message: TunnelMessage) => void,\n ): void {\n relay.localConn.on(\"message\", (data: WebSocket.Data, isBinary: boolean) => {\n if (!this.relays.has(relay.streamId)) return;\n\n const frameType = isBinary ? \"binary\" : \"text\";\n\n // Send ws-frame header\n sendMessage({\n type: \"ws-frame\",\n streamId: relay.streamId,\n frameType,\n } satisfies TunnelMessage);\n\n // Send binary frame with streamId prefix + data\n let body: Uint8Array;\n if (Buffer.isBuffer(data)) {\n body = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n } else if (data instanceof ArrayBuffer) {\n body = new Uint8Array(data);\n } else if (typeof data === \"string\") {\n body = new TextEncoder().encode(data);\n } else {\n // Array of Buffers\n body = Buffer.concat(data as Buffer[]);\n }\n\n const frame = encodeBinaryFrame(relay.streamId, body);\n tunnelWs.send(frame);\n });\n }\n\n /**\n * Forward a WS frame from the browser (via tunnel) to the local WS.\n */\n handleFrame(msg: WsFrameMessage, body: Uint8Array): void {\n const relay = this.relays.get(msg.streamId);\n if (!relay) return;\n\n if (msg.frameType === \"text\") {\n // Send as text string\n const text = new TextDecoder().decode(body);\n relay.localConn.send(text);\n } else {\n // Send as binary\n relay.localConn.send(body);\n }\n }\n\n /**\n * Close a relay stream.\n */\n handleClose(msg: WsCloseMessage): void {\n this.closeRelay(msg.streamId);\n }\n\n /**\n * Tear down a single relay connection.\n */\n private closeRelay(streamId: string): void {\n const relay = this.relays.get(streamId);\n if (!relay) return;\n\n this.relays.delete(streamId);\n try {\n relay.localConn.close(1000, \"Stream closed\");\n } catch {\n // Ignore errors on close\n }\n }\n\n /**\n * Tear down all active relay connections.\n */\n closeAll(): void {\n for (const relay of this.relays.values()) {\n try {\n relay.localConn.close(1000, \"All streams closed\");\n } catch {\n // Ignore errors on close\n }\n }\n this.relays.clear();\n }\n}\n","import pc from \"picocolors\";\n\nexport interface TrafficEntry {\n id: string;\n method: string;\n path: string;\n status: number;\n duration: number;\n timestamp: Date;\n}\n\nexport type TunnelStatus =\n | \"connecting\"\n | \"connected\"\n | \"reconnecting\"\n | \"disconnected\"\n | \"expired\";\n\nfunction methodColor(method: string): string {\n const colored: Record<string, (s: string) => string> = {\n GET: pc.cyan,\n POST: pc.green,\n PUT: pc.yellow,\n DELETE: pc.red,\n PATCH: pc.magenta,\n HEAD: pc.cyan,\n OPTIONS: pc.gray,\n };\n return (colored[method] ?? pc.white)(method.padEnd(7));\n}\n\nfunction statusColor(status: number): string {\n if (status >= 500) return pc.red(String(status));\n if (status >= 400) return pc.yellow(String(status));\n if (status >= 300) return pc.cyan(String(status));\n if (status >= 200) return pc.green(String(status));\n return pc.white(String(status));\n}\n\nfunction timestamp(): string {\n const now = new Date();\n return pc.gray(\n `${String(now.getHours()).padStart(2, \"0\")}:${String(now.getMinutes()).padStart(2, \"0\")}:${String(now.getSeconds()).padStart(2, \"0\")}`,\n );\n}\n\nfunction formatTtl(seconds: number): string {\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = seconds % 60;\n return `${h}h ${m}m ${s}s`;\n}\n\nexport function printBanner(url: string, port: number, ttl: number): void {\n console.log();\n console.log(` ${pc.bold(pc.green(\"xpose\"))}`);\n console.log();\n console.log(\n ` ${pc.gray(\"Forwarding\")} ${pc.bold(pc.cyan(url))} ${pc.gray(\"->\")} ${pc.white(`localhost:${port}`)}`,\n );\n console.log(\n ` ${pc.gray(\"TTL\")} ${pc.yellow(formatTtl(ttl))} remaining`,\n );\n console.log(` ${pc.gray(\"Status\")} ${pc.green(\"Connected\")}`);\n console.log();\n console.log(\n pc.gray(\" ─────────────────────────────────────────────────────────\"),\n );\n console.log();\n}\n\nexport function printTraffic(entry: TrafficEntry): void {\n const duration = pc.gray(`${String(entry.duration).padStart(5)}ms`);\n console.log(\n ` ${timestamp()} ${methodColor(entry.method)} ${entry.path.padEnd(30).slice(0, 30)} ${statusColor(entry.status)} ${duration}`,\n );\n}\n\nexport function printStatus(status: TunnelStatus): void {\n const labels: Record<TunnelStatus, string> = {\n connecting: pc.yellow(\" Connecting...\"),\n connected: pc.green(\" Connected\"),\n reconnecting: pc.yellow(\" Reconnecting...\"),\n disconnected: pc.red(\" Disconnected\"),\n expired: pc.red(\" Tunnel expired\"),\n };\n console.log(labels[status]);\n}\n\nexport function printError(message: string): void {\n console.log(` ${pc.red(\"Error:\")} ${message}`);\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nexport interface DiscoverTurboPortsOptions {\n cwd: string;\n task: string;\n filter?: string;\n}\n\nexport interface DiscoveredTurboPort {\n port: number;\n packageName: string;\n directory: string;\n command: string;\n reason: \"explicit\" | \"default\";\n}\n\ninterface TurboDryRunTask {\n command?: string;\n package?: string;\n directory?: string;\n}\n\ninterface TurboDryRunResponse {\n tasks?: TurboDryRunTask[];\n}\n\nfunction isValidPort(value: number): boolean {\n return Number.isInteger(value) && value >= 1 && value <= 65535;\n}\n\nfunction parseTurboDryRunOutput(output: string): TurboDryRunTask[] {\n const start = output.indexOf(\"{\");\n const end = output.lastIndexOf(\"}\");\n if (start < 0 || end < start) {\n throw new Error(\"Could not find JSON payload in Turborepo dry-run output\");\n }\n\n const rawJson = output.slice(start, end + 1);\n const parsed = JSON.parse(rawJson) as TurboDryRunResponse;\n if (!Array.isArray(parsed.tasks)) {\n throw new Error(\n \"Unexpected Turborepo dry-run format (missing tasks array)\",\n );\n }\n return parsed.tasks;\n}\n\nfunction collectRegexPorts(command: string, pattern: RegExp): number[] {\n const ports: number[] = [];\n let match: RegExpExecArray | null = null;\n while ((match = pattern.exec(command)) !== null) {\n const port = Number.parseInt(match[1], 10);\n if (isValidPort(port)) {\n ports.push(port);\n }\n }\n return ports;\n}\n\nfunction extractExplicitPorts(command: string): number[] {\n const patterns = [\n /(?:^|\\s)PORT=(\\d{2,5})(?=\\s|$)/g,\n /--port(?:=|\\s+)(\\d{2,5})(?=\\s|$)/g,\n /(?:^|\\s)-p\\s+(\\d{2,5})(?=\\s|$)/g,\n /(?:^|\\s)-p(\\d{2,5})(?=\\s|$)/g,\n /--listen(?:=|\\s+)(?:[^\\s:]+:)?(\\d{2,5})(?=\\s|$)/g,\n /https?:\\/\\/[^\\s/:]+:(\\d{2,5})(?=[/\\s]|$)/g,\n ];\n\n const discovered = new Set<number>();\n for (const pattern of patterns) {\n for (const port of collectRegexPorts(command, pattern)) {\n discovered.add(port);\n }\n }\n return [...discovered];\n}\n\nfunction inferDefaultPort(command: string): number | null {\n const normalized = command.toLowerCase();\n\n if (/\\bwrangler\\s+dev\\b/.test(normalized)) return 8787;\n if (/\\bnext\\s+dev\\b/.test(normalized)) return 3000;\n if (/\\bnuxt\\s+dev\\b/.test(normalized)) return 3000;\n if (/\\bremix\\s+dev\\b/.test(normalized)) return 3000;\n if (/\\bastro\\s+dev\\b/.test(normalized)) return 4321;\n if (/\\bstart-storybook\\b/.test(normalized)) return 6006;\n if (/\\bstorybook\\b.*\\bdev\\b/.test(normalized)) return 6006;\n if (/\\bvite(?:\\s|$)/.test(normalized) && !/\\bvitest\\b/.test(normalized)) {\n return 5173;\n }\n\n return null;\n}\n\nexport async function discoverTurboPorts(\n options: DiscoverTurboPortsOptions,\n): Promise<DiscoveredTurboPort[]> {\n const args = [\"turbo\", \"run\", options.task, \"--dry=json\"];\n const filter = options.filter?.trim();\n if (filter) {\n args.push(`--filter=${filter}`);\n }\n\n let combinedOutput = \"\";\n try {\n const { stdout, stderr } = await execFileAsync(\"bunx\", args, {\n cwd: options.cwd,\n maxBuffer: 10 * 1024 * 1024,\n });\n combinedOutput = `${stdout}\\n${stderr}`;\n } catch (err) {\n const error = err as Error & { stdout?: string; stderr?: string };\n const extra = [error.stdout, error.stderr].filter(Boolean).join(\"\\n\");\n throw new Error(\n `Failed to run \\`bunx ${args.join(\" \")}\\`${extra ? `\\n${extra}` : \"\"}`,\n );\n }\n\n const tasks = parseTurboDryRunOutput(combinedOutput);\n const discovered: DiscoveredTurboPort[] = [];\n\n for (const task of tasks) {\n const command = task.command?.trim();\n if (!command) continue;\n\n const explicitPorts = extractExplicitPorts(command);\n if (explicitPorts.length > 0) {\n for (const port of explicitPorts) {\n discovered.push({\n port,\n packageName: task.package ?? \"unknown\",\n directory: task.directory ?? \"unknown\",\n command,\n reason: \"explicit\",\n });\n }\n continue;\n }\n\n const defaultPort = inferDefaultPort(command);\n if (defaultPort !== null) {\n discovered.push({\n port: defaultPort,\n packageName: task.package ?? \"unknown\",\n directory: task.directory ?? \"unknown\",\n command,\n reason: \"default\",\n });\n }\n }\n\n const deduped = new Map<number, DiscoveredTurboPort>();\n for (const entry of discovered) {\n if (!deduped.has(entry.port)) {\n deduped.set(entry.port, entry);\n }\n }\n\n return [...deduped.values()].sort((a, b) => a.port - b.port);\n}\n","/**\n * Normalize a raw domain string by stripping protocol, trailing slashes/dots,\n * and converting to lowercase.\n */\nexport function normalizeDomain(raw: string): string {\n return raw\n .trim()\n .toLowerCase()\n .replace(/^https?:\\/\\//, \"\")\n .replace(/\\/.*$/, \"\")\n .replace(/\\.$/, \"\");\n}\n","import { readFileSync, writeFileSync, mkdirSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { PROTOCOL } from \"@xpose/protocol\";\n\nconst SESSION_FILE_NAME = \"session.json\";\n\nexport interface TunnelEntry {\n subdomain: string;\n port: number;\n domain: string;\n}\n\nexport interface Session {\n tunnels: TunnelEntry[];\n createdAt: string; // ISO 8601\n}\n\n/** Allow tests to override the config directory. */\nlet configDirOverride: string | undefined;\n\nexport function setConfigDirOverride(dir: string | undefined): void {\n configDirOverride = dir;\n}\n\nfunction getConfigDir(create: boolean): string {\n const dir = configDirOverride ?? join(homedir(), \".config\", \"xpose\");\n if (create) {\n mkdirSync(dir, { recursive: true });\n }\n return dir;\n}\n\nfunction getSessionPath(create: boolean): string {\n return join(getConfigDir(create), SESSION_FILE_NAME);\n}\n\n/** Save a session to disk. */\nexport function saveSession(session: Session): void {\n const path = getSessionPath(true);\n writeFileSync(path, JSON.stringify(session, null, 2), \"utf-8\");\n}\n\n/**\n * Load a session from disk.\n * Returns `null` if the file does not exist, is corrupt, or has expired.\n */\nexport function loadSession(): Session | null {\n const path = getSessionPath(false);\n\n let data: string;\n try {\n data = readFileSync(path, \"utf-8\");\n } catch {\n return null; // file doesn't exist\n }\n\n let session: Session;\n try {\n session = JSON.parse(data);\n } catch {\n return null; // corrupt file\n }\n\n if (!session.createdAt || !Array.isArray(session.tunnels)) {\n return null;\n }\n\n const elapsed = Date.now() - new Date(session.createdAt).getTime();\n const windowMs = PROTOCOL.SESSION_RESUME_WINDOW_SECONDS * 1000;\n if (elapsed > windowMs) {\n return null; // expired\n }\n\n return session;\n}\n\n/** Remove the session file. */\nexport function clearSession(): void {\n try {\n unlinkSync(getSessionPath(false));\n } catch {\n // ignore — file may not exist\n }\n}\n","import React, { useState, useEffect, useCallback } from \"react\";\nimport { Box, Text, useApp, useInput, useStdout } from \"ink\";\nimport Spinner from \"ink-spinner\";\nimport type { TunnelClient } from \"../tunnel-client.js\";\nimport type { TrafficEntry, TunnelStatus } from \"@xpose/tunnel-core\";\n\n// --- Constants ---\n\nconst MAX_TRAFFIC_ENTRIES = 100;\nconst MIN_SPLIT_WIDTH = 80;\n\n// --- Types ---\n\ninterface TunnelState {\n port: number;\n status: TunnelStatus;\n url: string;\n ttlRemaining: number;\n maxBodySizeBytes: number;\n lastError: string;\n}\n\ninterface AppProps {\n clients: TunnelClient[];\n ports: number[];\n onQuit: () => void;\n}\n\n// --- Helpers ---\n\nfunction formatTtl(seconds: number): string {\n if (seconds < 0) seconds = 0;\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = seconds % 60;\n return `${h}h ${m}m ${s}s`;\n}\n\nfunction formatTime(date: Date): string {\n const hh = String(date.getHours()).padStart(2, \"0\");\n const mm = String(date.getMinutes()).padStart(2, \"0\");\n const ss = String(date.getSeconds()).padStart(2, \"0\");\n return `${hh}:${mm}:${ss}`;\n}\n\nfunction openBrowser(url: string): void {\n const { exec } = require(\"node:child_process\");\n const platform = process.platform;\n const cmd =\n platform === \"darwin\"\n ? `open \"${url}\"`\n : platform === \"win32\"\n ? `start \"${url}\"`\n : `xdg-open \"${url}\"`;\n exec(cmd);\n}\n\n// --- Tunnel Card Component ---\n\nfunction TunnelCard({\n tunnel,\n showPortPrefix,\n}: {\n tunnel: TunnelState;\n showPortPrefix: boolean;\n}) {\n const portLabel = showPortPrefix ? ` :${tunnel.port}` : \"\";\n\n switch (tunnel.status) {\n case \"connected\":\n return (\n <Box flexDirection=\"column\" paddingLeft={1}>\n <Text>\n <Text color=\"green\" bold>\n {\"✓ \"}\n </Text>\n <Text color=\"green\">Connected</Text>\n <Text dimColor>{portLabel}</Text>\n </Text>\n <Text>\n <Text color=\"cyan\" bold>\n {\"→ \"}\n </Text>\n <Text color=\"cyan\" bold>\n {tunnel.url}\n </Text>\n </Text>\n <Text dimColor>\n {\" \"}Forwarding to localhost:{tunnel.port}\n </Text>\n <Text>\n {\" \"}TTL:{\" \"}\n <Text color=\"yellow\">{formatTtl(tunnel.ttlRemaining)}</Text>\n </Text>\n </Box>\n );\n\n case \"connecting\":\n return (\n <Box flexDirection=\"column\" paddingLeft={1}>\n <Text>\n <Text color=\"yellow\">\n <Spinner type=\"dots\" />\n </Text>\n <Text color=\"yellow\"> Connecting...</Text>\n <Text dimColor>{portLabel}</Text>\n </Text>\n <Text dimColor>\n {\" \"}Port {tunnel.port}\n </Text>\n </Box>\n );\n\n case \"reconnecting\":\n return (\n <Box flexDirection=\"column\" paddingLeft={1}>\n <Text>\n <Text color=\"yellow\">\n <Spinner type=\"dots\" />\n </Text>\n <Text color=\"yellow\"> Reconnecting...</Text>\n <Text dimColor>{portLabel}</Text>\n </Text>\n <Text dimColor>\n {\" \"}Port {tunnel.port}\n </Text>\n {tunnel.lastError && (\n <Text>\n {\" \"}\n <Text color=\"red\">Error: </Text>\n <Text>{tunnel.lastError}</Text>\n </Text>\n )}\n </Box>\n );\n\n case \"disconnected\":\n return (\n <Box flexDirection=\"column\" paddingLeft={1}>\n <Text>\n <Text color=\"red\" bold>\n {\"✗ \"}\n </Text>\n <Text color=\"red\">Disconnected</Text>\n <Text dimColor>{portLabel}</Text>\n </Text>\n <Text dimColor>\n {\" \"}Port {tunnel.port}\n </Text>\n {tunnel.lastError && (\n <Text>\n {\" \"}\n <Text color=\"red\">Error: </Text>\n <Text>{tunnel.lastError}</Text>\n </Text>\n )}\n </Box>\n );\n\n case \"expired\":\n return (\n <Box flexDirection=\"column\" paddingLeft={1}>\n <Text>\n <Text color=\"red\" bold>\n {\"✗ \"}\n </Text>\n <Text color=\"red\">Tunnel expired</Text>\n <Text dimColor>{portLabel}</Text>\n </Text>\n <Text dimColor>\n {\" \"}Port {tunnel.port}\n </Text>\n </Box>\n );\n }\n}\n\n// --- Traffic Line Component ---\n\nconst METHOD_COLORS: Record<string, string> = {\n GET: \"cyan\",\n HEAD: \"cyan\",\n POST: \"green\",\n PUT: \"yellow\",\n DELETE: \"red\",\n PATCH: \"magenta\",\n OPTIONS: \"gray\",\n};\n\nfunction statusColor(status: number): string {\n if (status >= 500) return \"red\";\n if (status >= 400) return \"yellow\";\n if (status >= 300) return \"cyan\";\n if (status >= 200) return \"green\";\n return \"white\";\n}\n\nfunction TrafficLine({ entry }: { entry: TrafficEntry }) {\n const method = entry.method.padEnd(7);\n const path =\n entry.path.length > 30 ? entry.path.slice(0, 30) : entry.path.padEnd(30);\n const duration = `${String(entry.duration).padStart(5)}ms`;\n const time = formatTime(entry.timestamp);\n\n return (\n <Text>\n {\" \"}\n <Text dimColor>{time}</Text>\n {\" \"}\n <Text color={METHOD_COLORS[entry.method] ?? \"white\"}>{method}</Text>\n {\" \"}\n <Text>{path}</Text>\n {\" \"}\n <Text color={statusColor(entry.status)}>{entry.status}</Text>\n {\" \"}\n <Text dimColor>{duration}</Text>\n </Text>\n );\n}\n\n// --- Panel Component ---\n\nfunction Panel({\n title,\n focused,\n width,\n height,\n children,\n}: {\n title: string;\n focused: boolean;\n width: number | string;\n height: number | string;\n children: React.ReactNode;\n}) {\n const borderColor = focused ? \"#3b82f6\" : \"gray\";\n const titleColor = focused ? \"#3b82f6\" : \"gray\";\n\n return (\n <Box\n flexDirection=\"column\"\n width={width}\n height={height}\n borderStyle=\"round\"\n borderColor={borderColor}\n >\n <Box position=\"absolute\" marginLeft={1} marginTop={-1}>\n <Text color={titleColor} bold={focused}>\n {\" \"}\n {title}{\" \"}\n </Text>\n </Box>\n {children}\n </Box>\n );\n}\n\n// --- Main App Component ---\n\nexport function App({ clients, ports, onQuit }: AppProps) {\n const { exit } = useApp();\n const { stdout } = useStdout();\n\n // Terminal dimensions\n const [columns, setColumns] = useState(stdout.columns || 80);\n const [rows, setRows] = useState(stdout.rows || 24);\n\n // Tunnel states\n const [tunnels, setTunnels] = useState<TunnelState[]>(() =>\n ports.map((port) => ({\n port,\n status: \"connecting\" as TunnelStatus,\n url: \"\",\n ttlRemaining: 0,\n maxBodySizeBytes: 0,\n lastError: \"\",\n })),\n );\n\n // Traffic log — each entry gets a monotonic sequence number for stable React keys\n const [traffic, setTraffic] = useState<(TrafficEntry & { seq: number })[]>(\n [],\n );\n const seqRef = React.useRef(0);\n\n // Scroll state for traffic panel\n const [scrollOffset, setScrollOffset] = useState(0);\n const [autoScroll, setAutoScroll] = useState(true);\n const autoScrollRef = React.useRef(true);\n\n // Focus state\n const [focusedPanel, setFocusedPanel] = useState<\"left\" | \"right\">(\"right\");\n\n const showSplit = columns >= MIN_SPLIT_WIDTH;\n\n // Track terminal resize\n useEffect(() => {\n function onResize() {\n setColumns(stdout.columns);\n setRows(stdout.rows);\n }\n stdout.on(\"resize\", onResize);\n return () => {\n stdout.off(\"resize\", onResize);\n };\n }, [stdout]);\n\n // TTL countdown timer\n useEffect(() => {\n const interval = setInterval(() => {\n setTunnels((prev) =>\n prev.map((t) =>\n t.status === \"connected\" && t.ttlRemaining > 0\n ? { ...t, ttlRemaining: t.ttlRemaining - 1 }\n : t,\n ),\n );\n }, 1000);\n return () => clearInterval(interval);\n }, []);\n\n // Wire up tunnel client events\n useEffect(() => {\n clients.forEach((client, idx) => {\n client.on(\"authenticated\", ({ url, ttl, maxBodySizeBytes }) => {\n setTunnels((prev) =>\n prev.map((t, i) =>\n i === idx\n ? {\n ...t,\n status: \"connected\" as TunnelStatus,\n url,\n ttlRemaining: ttl,\n maxBodySizeBytes,\n lastError: \"\",\n }\n : t,\n ),\n );\n });\n\n client.on(\"traffic\", (entry) => {\n // Add port prefix if multiple tunnels\n const portPrefix = clients.length > 1 ? `[${ports[idx]}] ` : \"\";\n const seq = ++seqRef.current;\n setTraffic((prev) => {\n const next = [\n ...prev,\n { ...entry, path: `${portPrefix}${entry.path}`, seq },\n ];\n if (next.length > MAX_TRAFFIC_ENTRIES) {\n return next.slice(next.length - MAX_TRAFFIC_ENTRIES);\n }\n return next;\n });\n // Auto-scroll to bottom only when the user hasn't manually scrolled up\n if (autoScrollRef.current) {\n setScrollOffset(Number.MAX_SAFE_INTEGER);\n }\n });\n\n client.on(\"status\", (status) => {\n setTunnels((prev) =>\n prev.map((t, i) => (i === idx ? { ...t, status } : t)),\n );\n });\n\n client.on(\"error\", (err) => {\n setTunnels((prev) =>\n prev.map((t, i) =>\n i === idx ? { ...t, lastError: err.message } : t,\n ),\n );\n });\n\n client.on(\"expired\", () => {\n setTunnels((prev) =>\n prev.map((t, i) =>\n i === idx ? { ...t, status: \"expired\" as TunnelStatus } : t,\n ),\n );\n });\n });\n }, [clients, ports]);\n\n // Check if all tunnels expired\n useEffect(() => {\n if (tunnels.length > 0 && tunnels.every((t) => t.status === \"expired\")) {\n onQuit();\n exit();\n }\n }, [tunnels, exit, onQuit]);\n\n // Calculate traffic viewport dimensions (must be before useInput so the closure has access)\n const footerHeight = 1;\n const availableRows = rows - footerHeight;\n\n // In split mode: panel outer height = availableRows.\n // Border top (1) + border bottom (1) = 2 rows for border.\n // Inner paddingTop (1) = 1 row.\n // Usable content rows = availableRows - 3.\n // In narrow mode: tunnel cards (~6 rows) + 1 marginTop, rest is traffic.\n const trafficViewportHeight = showSplit\n ? Math.max(1, availableRows - 3)\n : Math.max(1, availableRows - 7);\n\n // Keyboard input — only active when stdin supports raw mode (TTY)\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useInput(\n (input, key) => {\n if (input === \"q\" || (input === \"c\" && key.ctrl)) {\n onQuit();\n exit();\n return;\n }\n\n if (input === \"b\") {\n const connected = tunnels.find(\n (t) => t.status === \"connected\" && t.url,\n );\n if (connected) {\n openBrowser(connected.url);\n }\n return;\n }\n\n if (key.tab && showSplit) {\n setFocusedPanel((prev) => (prev === \"left\" ? \"right\" : \"left\"));\n return;\n }\n\n // Scroll traffic (in split mode: only when right panel focused; in narrow mode: always)\n if (!showSplit || focusedPanel === \"right\") {\n const maxOff = Math.max(0, traffic.length - trafficViewportHeight);\n if (key.upArrow && maxOff > 0) {\n setScrollOffset((prev) => {\n // Clamp prev first so we scroll from the real position, not MAX_SAFE_INTEGER\n const current = Math.min(prev, maxOff);\n return Math.max(0, current - 1);\n });\n setAutoScroll(false);\n autoScrollRef.current = false;\n }\n if (key.downArrow) {\n setScrollOffset((prev) => {\n const current = Math.min(prev, maxOff);\n const next = current + 1;\n // Re-enable auto-scroll when reaching the bottom\n if (next >= maxOff) {\n setAutoScroll(true);\n autoScrollRef.current = true;\n }\n return Math.min(next, maxOff);\n });\n }\n }\n },\n { isActive: isRawModeSupported },\n );\n\n // Clamp scroll offset\n const maxScroll = Math.max(0, traffic.length - trafficViewportHeight);\n const clampedOffset = Math.min(scrollOffset, maxScroll);\n const visibleTraffic = traffic.slice(\n clampedOffset,\n clampedOffset + trafficViewportHeight,\n );\n\n const scrollPercent =\n traffic.length <= trafficViewportHeight\n ? 100\n : Math.round((clampedOffset / maxScroll) * 100);\n\n // Build footer\n const footerParts = [\"q quit\", \"b open browser\"];\n if (showSplit) {\n footerParts.push(\"tab switch panel\");\n }\n const canScroll = !showSplit || focusedPanel === \"right\";\n if (canScroll && traffic.length > 0) {\n const scrollLabel = autoScroll\n ? `↑↓ scroll ${scrollPercent}%`\n : `↑↓ scroll ${scrollPercent}% (paused)`;\n footerParts.push(scrollLabel);\n }\n const footer = ` ${footerParts.join(\" | \")}`;\n\n if (!showSplit) {\n // Narrow mode: tunnel cards stacked, then traffic below\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexDirection=\"column\" flexShrink={0}>\n {tunnels.map((tunnel, i) => (\n <Box key={i} flexDirection=\"column\">\n {i > 0 && <Text>{\"\"}</Text>}\n <TunnelCard tunnel={tunnel} showPortPrefix={tunnels.length > 1} />\n </Box>\n ))}\n </Box>\n <Box flexDirection=\"column\" flexGrow={1} marginTop={1}>\n {traffic.length === 0 ? (\n <Text dimColor>{\" \"}Waiting for requests...</Text>\n ) : (\n visibleTraffic.map((entry) => (\n <TrafficLine key={entry.seq} entry={entry} />\n ))\n )}\n </Box>\n <Box flexShrink={0}>\n <Text dimColor>{footer}</Text>\n </Box>\n </Box>\n );\n }\n\n // Split mode: left (tunnels) + right (traffic)\n const leftWidth = Math.floor(columns * 0.35);\n const rightWidth = columns - leftWidth;\n\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexGrow={1}>\n <Panel\n title=\"Tunnels\"\n focused={focusedPanel === \"left\"}\n width={leftWidth}\n height={availableRows}\n >\n <Box flexDirection=\"column\" paddingTop={1}>\n {tunnels.map((tunnel, i) => (\n <Box key={i} flexDirection=\"column\">\n {i > 0 && <Text>{\"\"}</Text>}\n <TunnelCard\n tunnel={tunnel}\n showPortPrefix={tunnels.length > 1}\n />\n </Box>\n ))}\n </Box>\n </Panel>\n <Panel\n title=\"Traffic\"\n focused={focusedPanel === \"right\"}\n width={rightWidth}\n height={availableRows}\n >\n <Box\n flexDirection=\"column\"\n paddingTop={1}\n paddingLeft={1}\n overflowY=\"hidden\"\n >\n {traffic.length === 0 ? (\n <Text dimColor>Waiting for requests...</Text>\n ) : (\n visibleTraffic.map((entry) => (\n <TrafficLine key={entry.seq} entry={entry} />\n ))\n )}\n </Box>\n </Panel>\n </Box>\n <Box flexShrink={0}>\n <Text dimColor>{footer}</Text>\n </Box>\n </Box>\n );\n}\n"],"mappings":"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","names":["WebSocket","WebSocket","msg","pc","React","React","manualRawPorts"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "xpose-dev",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Expose local servers to the internet via Cloudflare — zero config, instant public URLs",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"xpose-dev": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"dev": "tsup --watch",
|
|
15
|
+
"start": "node dist/index.js",
|
|
16
|
+
"check-types": "tsc --noEmit",
|
|
17
|
+
"test": "vitest run"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"tunnel",
|
|
21
|
+
"ngrok",
|
|
22
|
+
"cloudflare",
|
|
23
|
+
"localhost",
|
|
24
|
+
"dev",
|
|
25
|
+
"proxy",
|
|
26
|
+
"expose",
|
|
27
|
+
"public-url"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/nkootstra/xpose.git",
|
|
33
|
+
"directory": "apps/cli"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://xpose.dev",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"citty": "^0.1.6",
|
|
41
|
+
"ink": "^6.7.0",
|
|
42
|
+
"ink-spinner": "^5.0.0",
|
|
43
|
+
"nanoid": "^5.1.5",
|
|
44
|
+
"react": "^19.2.4",
|
|
45
|
+
"ws": "^8.18.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/react": "^19.2.14",
|
|
49
|
+
"@types/ws": "^8.5.14",
|
|
50
|
+
"tsup": "^8.4.0",
|
|
51
|
+
"typescript": "5.9.2",
|
|
52
|
+
"vitest": "^3.2.0"
|
|
53
|
+
}
|
|
54
|
+
}
|