untunneled.dev 0.3.1 → 0.3.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/dist/index.js +11 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -169,12 +169,12 @@ var TunnelUI = ({
|
|
|
169
169
|
});
|
|
170
170
|
const [relayConnected, setRelayConnected] = useState3(relayTunnel.isConnected());
|
|
171
171
|
const [uptime, setUptime] = useState3(0);
|
|
172
|
-
const [isModalOpen, setIsModalOpen] = useState3(
|
|
172
|
+
const [isModalOpen, setIsModalOpen] = useState3(Boolean(showQR));
|
|
173
173
|
useInput((input, key) => {
|
|
174
174
|
if (input === "q" || input === "c" || input === "Q" || input === "C") {
|
|
175
|
-
setIsModalOpen(!
|
|
175
|
+
setIsModalOpen((prev) => !prev);
|
|
176
176
|
}
|
|
177
|
-
if (key.escape
|
|
177
|
+
if (key.escape) {
|
|
178
178
|
setIsModalOpen(false);
|
|
179
179
|
}
|
|
180
180
|
});
|
|
@@ -208,7 +208,7 @@ var TunnelUI = ({
|
|
|
208
208
|
/* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: LOGO }),
|
|
209
209
|
/* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", paddingX: 1, children: [
|
|
210
210
|
/* @__PURE__ */ jsx4(ConnectionStatus, { status: connectionStatus }),
|
|
211
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "v0.3.
|
|
211
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "v0.3.2" })
|
|
212
212
|
] })
|
|
213
213
|
] }),
|
|
214
214
|
/* @__PURE__ */ jsxs4(
|
|
@@ -254,17 +254,6 @@ var TunnelUI = ({
|
|
|
254
254
|
/* @__PURE__ */ jsx4(Text4, { color: "white", bold: true, children: requests.length > 0 ? `${requests[requests.length - 1]?.duration}ms` : "-" })
|
|
255
255
|
] })
|
|
256
256
|
] }),
|
|
257
|
-
showQR && /* @__PURE__ */ jsx4(
|
|
258
|
-
Box4,
|
|
259
|
-
{
|
|
260
|
-
marginY: 1,
|
|
261
|
-
borderStyle: "single",
|
|
262
|
-
borderColor: "gray",
|
|
263
|
-
paddingX: 1,
|
|
264
|
-
alignSelf: "flex-start",
|
|
265
|
-
children: /* @__PURE__ */ jsx4(QRCode, { url: tunnelUrl })
|
|
266
|
-
}
|
|
267
|
-
),
|
|
268
257
|
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
269
258
|
/* @__PURE__ */ jsx4(Box4, { marginBottom: 1, children: /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "\u2500\u2500\u2500 Recent Requests \u2500\u2500\u2500" }) }),
|
|
270
259
|
/* @__PURE__ */ jsx4(RequestLog, { requests })
|
|
@@ -541,6 +530,11 @@ var RelayTunnel = class extends EventEmitter {
|
|
|
541
530
|
return header !== "connection" && header !== "upgrade" && header !== "host" && !header.startsWith("sec-websocket");
|
|
542
531
|
})
|
|
543
532
|
);
|
|
533
|
+
const localOrigin = `http://127.0.0.1:${this.localPort}`;
|
|
534
|
+
filteredHeaders["origin"] = localOrigin;
|
|
535
|
+
if (filteredHeaders["referer"]) {
|
|
536
|
+
filteredHeaders["referer"] = `${localOrigin}${frame.url}`;
|
|
537
|
+
}
|
|
544
538
|
const localWs = protocolHeader ? new WebSocket(localUrl, protocolHeader, { headers: filteredHeaders }) : new WebSocket(localUrl, { headers: filteredHeaders });
|
|
545
539
|
this.activeWebSockets.set(frame.id, localWs);
|
|
546
540
|
localWs.on("message", (data, isBinary) => {
|
|
@@ -771,8 +765,8 @@ async function stopCommand(tunnelId) {
|
|
|
771
765
|
|
|
772
766
|
// src/index.ts
|
|
773
767
|
var program = new Command();
|
|
774
|
-
program.name("untunneled").description("Fast, free localhost tunneling").version("0.3.
|
|
775
|
-
program.argument("<port>", "Local port to tunnel").option("--qr", "
|
|
768
|
+
program.name("untunneled").description("Fast, free localhost tunneling").version("0.3.2");
|
|
769
|
+
program.argument("<port>", "Local port to tunnel").option("--qr", "Open the QR code modal on start").option("--password <password>", "Password protect the tunnel").option("--allow <emails>", "Email whitelist (comma-separated)").option("--verbose", "Show detailed logs").option("--no-telemetry", "Disable anonymous usage tracking").action(startCommand);
|
|
776
770
|
program.command("list").description("List active tunnels").action(listCommand);
|
|
777
771
|
program.command("stop").argument("<tunnel-id>", "Tunnel ID to stop").description("Stop a running tunnel").action(stopCommand);
|
|
778
772
|
program.parse();
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/start.ts","../src/ui/TunnelUI.tsx","../src/ui/QRCode.tsx","../src/ui/RequestLog.tsx","../src/ui/ConnectionStatus.tsx","../src/tunnel/relay.ts","../src/config.ts","../src/analytics.ts","../src/commands/list.ts","../src/commands/stop.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { startCommand, listCommand, stopCommand } from './commands/index.js';\n\nconst program = new Command();\n\nprogram.name('untunneled').description('Fast, free localhost tunneling').version('0.3.1');\n\nprogram\n .argument('<port>', 'Local port to tunnel')\n .option('--qr', 'Show QR code for mobile access')\n .option('--password <password>', 'Password protect the tunnel')\n .option('--allow <emails>', 'Email whitelist (comma-separated)')\n .option('--verbose', 'Show detailed logs')\n .option('--no-telemetry', 'Disable anonymous usage tracking')\n .action(startCommand);\n\nprogram.command('list').description('List active tunnels').action(listCommand);\n\nprogram\n .command('stop')\n .argument('<tunnel-id>', 'Tunnel ID to stop')\n .description('Stop a running tunnel')\n .action(stopCommand);\n\nprogram.parse();\n","import { render } from 'ink';\nimport React from 'react';\nimport { TunnelUI } from '../ui/TunnelUI.js';\nimport { RelayTunnel } from '../tunnel/relay.js';\nimport { Analytics } from '../analytics.js';\nimport {\n generateTunnelId,\n getProjectName,\n getTunnelUrl,\n validatePort,\n parseAllowList,\n} from '../config.js';\nimport type { StartOptions } from '../types.js';\n\nexport async function startCommand(port: string, options: StartOptions): Promise<void> {\n let localPort: number;\n\n try {\n localPort = validatePort(port);\n } catch (error) {\n console.error((error as Error).message);\n process.exit(1);\n }\n\n const projectName = getProjectName();\n const tunnelId = generateTunnelId(projectName);\n const tunnelUrl = getTunnelUrl(tunnelId);\n\n const analytics = new Analytics({\n enabled: options.telemetry !== false,\n tunnelId,\n });\n\n await analytics.track('tunnel_start', {\n has_auth: !!options.password || !!options.allow,\n node_version: process.version,\n platform: process.platform,\n });\n\n const relayTunnel = new RelayTunnel({\n localPort,\n tunnelId,\n tunnelUrl,\n password: options.password,\n allowList: parseAllowList(options.allow),\n verbose: options.verbose,\n });\n\n try {\n await relayTunnel.connect();\n\n if (options.verbose) {\n console.log(`Tunnel URL: ${tunnelUrl}`);\n console.log(`Forwarding to localhost:${localPort}`);\n }\n } catch (error) {\n console.error('Failed to connect to relay server:', (error as Error).message);\n await analytics.shutdown();\n process.exit(1);\n }\n\n const { waitUntilExit } = render(\n React.createElement(TunnelUI, {\n tunnelUrl,\n localPort,\n showQR: options.qr,\n relayTunnel,\n })\n );\n\n const cleanup = async () => {\n const duration = relayTunnel.getDuration();\n const requests = relayTunnel.getRequestCount();\n\n await analytics.track('tunnel_end', {\n duration_seconds: duration,\n requests: requests,\n });\n\n await relayTunnel.disconnect();\n await analytics.shutdown();\n };\n\n process.on('SIGINT', async () => {\n await cleanup();\n process.exit(0);\n });\n\n process.on('SIGTERM', async () => {\n await cleanup();\n process.exit(0);\n });\n\n await waitUntilExit();\n}\n","import React, { useState, useEffect } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport { QRCode } from './QRCode.js';\nimport { RequestLog } from './RequestLog.js';\nimport { ConnectionStatus } from './ConnectionStatus.js';\nimport type { RelayTunnel } from '../tunnel/relay.js';\nimport type { RequestLog as RequestLogType, TunnelStats } from '../types.js';\n\ninterface TunnelUIProps {\n tunnelUrl: string;\n localPort: number;\n showQR?: boolean;\n relayTunnel: RelayTunnel;\n}\n\nconst MAX_LOGS = 10;\n\nconst LOGO = `\n ╦ ╦╔╗╔╔╦╗╦ ╦╔╗╔╔╗╔╔═╗╦ ╔═╗╔╦╗ ╔╦╗╔═╗╦ ╦\n ║ ║║║║ ║ ║ ║║║║║║║║╣ ║ ║╣ ║║ ║║║╣ ╚╗╔╝\n ╚═╝╝╚╝ ╩ ╚═╝╝╚╝╝╚╝╚═╝╩═╝╚═╝═╩╝o═╩╝╚═╝ ╚╝ \n`;\n\nconst formatUptime = (ms: number) => {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${minutes % 60}m ${seconds % 60}s`;\n};\n\nexport const TunnelUI: React.FC<TunnelUIProps> = ({\n tunnelUrl,\n localPort,\n showQR,\n relayTunnel,\n}) => {\n useApp();\n const [requests, setRequests] = useState<RequestLogType[]>([]);\n const [stats, setStats] = useState<TunnelStats>({\n total: 0,\n startTime: Date.now(),\n });\n const [relayConnected, setRelayConnected] = useState(relayTunnel.isConnected());\n const [uptime, setUptime] = useState(0);\n const [isModalOpen, setIsModalOpen] = useState(false);\n\n useInput((input, key) => {\n if (input === 'q' || input === 'c' || input === 'Q' || input === 'C') {\n setIsModalOpen(!isModalOpen);\n }\n if (key.escape && isModalOpen) {\n setIsModalOpen(false);\n }\n });\n\n useEffect(() => {\n const handleRequest = (req: RequestLogType) => {\n setRequests((prev) => [...prev.slice(-(MAX_LOGS - 1)), req]);\n setStats((prev) => ({\n ...prev,\n total: prev.total + 1,\n }));\n };\n\n const handleRelayConnected = () => setRelayConnected(true);\n const handleRelayDisconnected = () => setRelayConnected(false);\n\n relayTunnel.on('request', handleRequest);\n relayTunnel.on('connected', handleRelayConnected);\n relayTunnel.on('disconnected', handleRelayDisconnected);\n\n const uptimeTimer = setInterval(() => {\n setUptime(Date.now() - stats.startTime);\n }, 1000);\n\n return () => {\n relayTunnel.off('request', handleRequest);\n relayTunnel.off('connected', handleRelayConnected);\n relayTunnel.off('disconnected', handleRelayDisconnected);\n clearInterval(uptimeTimer);\n };\n }, [relayTunnel, stats.startTime]);\n\n const connectionStatus = relayConnected ? 'connected' : 'connecting';\n const accentColor = relayConnected ? 'cyan' : 'yellow';\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1}>\n {/* Header */}\n <Box flexDirection=\"column\" marginBottom={1}>\n <Text color=\"cyan\" bold>\n {LOGO}\n </Text>\n <Box justifyContent=\"space-between\" paddingX={1}>\n <ConnectionStatus status={connectionStatus} />\n <Text dimColor>v0.3.1</Text>\n </Box>\n </Box>\n\n {/* Main Connection Info */}\n <Box\n borderStyle=\"round\"\n borderColor={accentColor}\n paddingX={2}\n paddingY={1}\n flexDirection=\"column\"\n >\n <Box>\n <Text bold>Public URL: </Text>\n <Text bold color=\"black\" backgroundColor=\"cyan\">\n {' '}\n {tunnelUrl}{' '}\n </Text>\n </Box>\n <Box marginTop={1}>\n <Text dimColor>Forwarding: </Text>\n <Text color=\"yellow\">http://localhost:{localPort}</Text>\n <Text dimColor> → </Text>\n <Text color=\"cyan\">{tunnelUrl}</Text>\n </Box>\n </Box>\n\n {/* Stats Bar */}\n <Box marginTop={1} paddingX={1} gap={4}>\n <Box>\n <Text dimColor>Requests: </Text>\n <Text color=\"white\" bold>\n {stats.total}\n </Text>\n </Box>\n <Box>\n <Text dimColor>Uptime: </Text>\n <Text color=\"white\" bold>\n {formatUptime(uptime)}\n </Text>\n </Box>\n <Box>\n <Text dimColor>Latency: </Text>\n <Text color=\"white\" bold>\n {requests.length > 0 ? `${requests[requests.length - 1]?.duration}ms` : '-'}\n </Text>\n </Box>\n </Box>\n\n {/* QR Code */}\n {showQR && (\n <Box\n marginY={1}\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n alignSelf=\"flex-start\"\n >\n <QRCode url={tunnelUrl} />\n </Box>\n )}\n\n {/* Request Log */}\n <Box marginTop={1} flexDirection=\"column\">\n <Box marginBottom={1}>\n <Text bold color=\"cyan\">\n ─── Recent Requests ───\n </Text>\n </Box>\n <RequestLog requests={requests} />\n </Box>\n\n {/* Footer */}\n <Box\n marginTop={1}\n borderStyle=\"single\"\n borderTop={true}\n borderBottom={false}\n borderLeft={false}\n borderRight={false}\n borderColor=\"gray\"\n paddingTop={1}\n >\n <Box flexGrow={1} gap={2}>\n <Box>\n <Text dimColor>Press </Text>\n <Text color=\"yellow\" bold>\n Ctrl+C\n </Text>\n <Text dimColor> to stop</Text>\n </Box>\n <Box>\n <Text dimColor>Press </Text>\n <Text color=\"yellow\" bold>\n Q\n </Text>\n <Text dimColor> for QR</Text>\n </Box>\n </Box>\n <Box>\n <Text dimColor>Sponsor: </Text>\n <Text color=\"magenta\" bold>\n untunneled.dev/sponsor\n </Text>\n </Box>\n </Box>\n\n {isModalOpen && (\n <Box\n position=\"absolute\"\n width=\"100%\"\n height=\"100%\"\n alignItems=\"center\"\n justifyContent=\"center\"\n >\n <Box\n flexDirection=\"column\"\n alignItems=\"center\"\n borderStyle=\"round\"\n borderColor=\"cyan\"\n padding={1}\n backgroundColor=\"black\"\n >\n <Box marginBottom={1}>\n <Text bold color=\"cyan\">\n Scan to Open URL\n </Text>\n </Box>\n <QRCode url={tunnelUrl} />\n <Box marginTop={1}>\n <Text dimColor>Press </Text>\n <Text color=\"yellow\" bold>\n ESC\n </Text>\n <Text dimColor> or </Text>\n <Text color=\"yellow\" bold>\n Q\n </Text>\n <Text dimColor> to close</Text>\n </Box>\n </Box>\n </Box>\n )}\n </Box>\n );\n};\n","import React, { useState, useEffect } from 'react';\nimport { Box, Text } from 'ink';\nimport qrcode from 'qrcode-terminal';\n\ninterface QRCodeProps {\n url: string;\n}\n\nexport const QRCode: React.FC<QRCodeProps> = ({ url }) => {\n const [qrString, setQrString] = useState('');\n\n useEffect(() => {\n qrcode.generate(url, { small: true }, (qr: string) => {\n setQrString(qr);\n });\n }, [url]);\n\n if (!qrString) {\n return (\n <Box>\n <Text dimColor>Generating QR code...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" borderStyle=\"single\" paddingX={1}>\n <Text dimColor>Scan with mobile:</Text>\n <Text>{qrString}</Text>\n </Box>\n );\n};\n","import React from 'react';\nimport { Box, Text } from 'ink';\nimport type { RequestLog as RequestLogType } from '../types.js';\n\ninterface RequestLogProps {\n requests: RequestLogType[];\n}\n\nconst getStatusColor = (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 'gray';\n};\n\nconst getMethodColor = (method: string): string => {\n switch (method.toUpperCase()) {\n case 'GET':\n return 'cyan';\n case 'POST':\n return 'green';\n case 'PUT':\n return 'yellow';\n case 'DELETE':\n return 'red';\n case 'PATCH':\n return 'magenta';\n default:\n return 'white';\n }\n};\n\nexport const RequestLog: React.FC<RequestLogProps> = ({ requests }) => {\n if (requests.length === 0) {\n return (\n <Box\n borderStyle=\"single\"\n borderColor=\"gray\"\n flexDirection=\"column\"\n alignItems=\"center\"\n paddingX={3}\n paddingY={1}\n width={40}\n alignSelf=\"center\"\n >\n <Text color=\"cyan\">⏳ Waiting for requests...</Text>\n <Box height={1} />\n <Text dimColor>Open the URL in your browser</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\">\n {requests.map((req) => {\n const time = new Date(req.timestamp).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n });\n\n return (\n <Box key={req.id} gap={1}>\n <Text dimColor>[{time}]</Text>\n <Box width={8}>\n <Text color={getMethodColor(req.method)} bold>\n {req.method.padEnd(7)}\n </Text>\n </Box>\n <Box flexGrow={1}>\n <Text>{req.path.length > 40 ? req.path.substring(0, 37) + '...' : req.path}</Text>\n </Box>\n <Box width={6}>\n <Text color={getStatusColor(req.status)} bold>\n {req.status}\n </Text>\n </Box>\n <Box width={10} justifyContent=\"flex-end\">\n <Text color={req.duration > 500 ? 'yellow' : 'gray'}>\n {req.duration.toString().padStart(4)}ms\n </Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n );\n};\n","import React, { useState, useEffect } from 'react';\nimport { Box, Text } from 'ink';\nimport type { ConnectionStatus as ConnectionStatusType } from '../types.js';\n\ninterface ConnectionStatusProps {\n status: ConnectionStatusType;\n}\n\nconst SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nconst getStatusIndicator = (\n status: ConnectionStatusType,\n frame: string\n): { color: string; symbol: string; label: string } => {\n switch (status) {\n case 'connected':\n return { color: 'green', symbol: '✔', label: 'CONNECTED' };\n case 'connecting':\n return { color: 'yellow', symbol: frame, label: 'CONNECTING' };\n case 'disconnected':\n return { color: 'gray', symbol: '○', label: 'DISCONNECTED' };\n case 'error':\n return { color: 'red', symbol: '✘', label: 'ERROR' };\n default:\n return { color: 'white', symbol: '?', label: String(status).toUpperCase() };\n }\n};\n\nexport const ConnectionStatus: React.FC<ConnectionStatusProps> = ({ status }) => {\n const [frameIndex, setFrameIndex] = useState(0);\n\n useEffect(() => {\n if (status !== 'connecting') return;\n\n const timer = setInterval(() => {\n setFrameIndex((prev) => (prev + 1) % SPINNER_FRAMES.length);\n }, 80);\n\n return () => clearInterval(timer);\n }, [status]);\n\n const frame = SPINNER_FRAMES[frameIndex] ?? ' ';\n const indicator = getStatusIndicator(status, frame);\n\n return (\n <Box>\n <Text color={indicator.color} bold>\n {indicator.symbol} {indicator.label}\n </Text>\n </Box>\n );\n};\n","import { WebSocket } from 'ws';\nimport { EventEmitter } from 'node:events';\nimport { getRelayUrl, TIMEOUTS } from '../config.js';\nimport type {\n RelayFrame,\n WsOpen,\n WsMessage,\n WsClose,\n RelayResponse,\n RequestLog,\n} from '../types.js';\n\ninterface RelayTunnelOptions {\n localPort: number;\n tunnelId: string;\n tunnelUrl: string;\n password?: string;\n allowList?: string[];\n verbose?: boolean;\n}\n\nexport class RelayTunnel extends EventEmitter {\n private ws: WebSocket | null = null;\n private localPort: number;\n private tunnelId: string;\n private tunnelUrl: string;\n private options: RelayTunnelOptions;\n private startTime: number = 0;\n private requestCount: number = 0;\n private reconnectAttempts: number = 0;\n private isClosing: boolean = false;\n private pingInterval: NodeJS.Timeout | null = null;\n private activeWebSockets: Map<string, WebSocket> = new Map();\n\n constructor(options: RelayTunnelOptions) {\n super();\n this.options = options;\n this.localPort = options.localPort;\n this.tunnelId = options.tunnelId;\n this.tunnelUrl = options.tunnelUrl;\n }\n\n async connect(): Promise<void> {\n this.startTime = Date.now();\n this.isClosing = false;\n\n const wsUrl = getRelayUrl(this.tunnelId);\n\n if (this.options.verbose) {\n console.log(`[Relay] Connecting to ${wsUrl}`);\n }\n\n return new Promise((resolve, reject) => {\n try {\n this.ws = new WebSocket(wsUrl, {\n headers: {\n 'X-Tunnel-Auth': this.options.password || '',\n 'X-Tunnel-Allow': this.options.allowList?.join(',') || '',\n },\n });\n\n const connectionTimeout = setTimeout(() => {\n if (this.ws && this.ws.readyState !== WebSocket.OPEN) {\n this.ws.terminate();\n reject(new Error('Connection timeout'));\n }\n }, TIMEOUTS.RELAY_REQUEST);\n\n this.ws.on('open', () => {\n clearTimeout(connectionTimeout);\n this.reconnectAttempts = 0;\n\n if (this.options.verbose) {\n console.log('[Relay] Connected');\n }\n\n this.setupPing();\n this.emit('connected');\n resolve();\n });\n\n this.ws.on('error', (err) => {\n clearTimeout(connectionTimeout);\n\n if (this.options.verbose) {\n console.error('[Relay] WebSocket error:', err.message);\n }\n\n this.emit('error', err);\n reject(err);\n });\n\n this.ws.on('close', () => {\n this.clearPing();\n\n if (!this.isClosing) {\n this.emit('disconnected');\n this.attemptReconnect();\n }\n });\n\n this.ws.on('message', async (data) => {\n await this.handleRelayFrame(data);\n });\n } catch (error) {\n reject(error);\n }\n });\n }\n\n private async handleRelayFrame(data: Buffer | ArrayBuffer | Buffer[]): Promise<void> {\n let frame: RelayFrame;\n\n try {\n frame = JSON.parse(String(data)) as RelayFrame;\n } catch {\n if (this.options.verbose) {\n console.error('[Relay] Invalid frame data');\n }\n return;\n }\n\n switch (frame.type) {\n case 'http_request':\n await this.handleHttpRequest(frame);\n break;\n case 'ws_open':\n await this.handleWsOpen(frame);\n break;\n case 'ws_message':\n await this.handleWsMessage(frame);\n break;\n case 'ws_close':\n await this.handleWsClose(frame);\n break;\n }\n }\n\n private async handleHttpRequest(request: RelayFrame & { type: 'http_request' }): Promise<void> {\n const startTime = Date.now();\n\n try {\n const url = `http://127.0.0.1:${this.localPort}${request.path}`;\n\n const fetchOptions: RequestInit = {\n method: request.method,\n headers: request.headers,\n };\n\n if (request.body && !['GET', 'HEAD'].includes(request.method)) {\n fetchOptions.body = request.body;\n }\n\n const response = await fetch(url, fetchOptions);\n const body = await response.text();\n const duration = Date.now() - startTime;\n\n const relayResponse: RelayResponse = {\n type: 'http_response',\n id: request.id,\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n body,\n };\n\n this.ws?.send(JSON.stringify(relayResponse));\n\n const log: RequestLog = {\n id: request.id,\n timestamp: Date.now(),\n method: request.method,\n path: request.path,\n status: response.status,\n duration,\n };\n\n this.emit('request', log);\n this.requestCount++;\n } catch (error) {\n const err = error as any;\n\n if (this.options.verbose || err.code === 'ECONNREFUSED') {\n console.error(`[Relay] Proxy error: ${err.message}`);\n }\n\n const errorResponse: RelayResponse = {\n type: 'http_response',\n id: request.id,\n status: 502,\n statusText: 'Bad Gateway',\n headers: {},\n body: `Error: ${err.message}`,\n };\n\n this.ws?.send(JSON.stringify(errorResponse));\n }\n }\n\n private async handleWsOpen(frame: WsOpen): Promise<void> {\n const localUrl = `ws://127.0.0.1:${this.localPort}${frame.url}`;\n\n if (this.options.verbose) {\n console.log(`[Relay] Opening WebSocket to ${localUrl}`);\n }\n\n const protocolHeader =\n frame.headers['sec-websocket-protocol'] || frame.headers['Sec-WebSocket-Protocol'];\n const filteredHeaders = Object.fromEntries(\n Object.entries(frame.headers).filter(([key]) => {\n const header = key.toLowerCase();\n return (\n header !== 'connection' &&\n header !== 'upgrade' &&\n header !== 'host' &&\n !header.startsWith('sec-websocket')\n );\n })\n );\n\n const localWs = protocolHeader\n ? new WebSocket(localUrl, protocolHeader, { headers: filteredHeaders })\n : new WebSocket(localUrl, { headers: filteredHeaders });\n\n this.activeWebSockets.set(frame.id, localWs);\n\n localWs.on('message', (data, isBinary) => {\n const message: WsMessage = {\n type: 'ws_message',\n id: frame.id,\n data: isBinary ? (data as Buffer).toString('base64') : data.toString(),\n isBinary,\n };\n this.ws?.send(JSON.stringify(message));\n });\n\n localWs.on('close', () => {\n const closeFrame: WsClose = {\n type: 'ws_close',\n id: frame.id,\n };\n this.ws?.send(JSON.stringify(closeFrame));\n this.activeWebSockets.delete(frame.id);\n });\n\n localWs.on('error', (err) => {\n if (this.options.verbose) {\n console.error(`[Relay] Local WebSocket error (${frame.id}):`, err.message);\n }\n localWs.close();\n });\n }\n\n private async handleWsMessage(frame: WsMessage): Promise<void> {\n const localWs = this.activeWebSockets.get(frame.id);\n if (localWs && localWs.readyState === WebSocket.OPEN) {\n const data = frame.isBinary ? Buffer.from(frame.data, 'base64') : frame.data;\n localWs.send(data);\n }\n }\n\n private async handleWsClose(frame: WsClose): Promise<void> {\n const localWs = this.activeWebSockets.get(frame.id);\n if (localWs) {\n localWs.close();\n this.activeWebSockets.delete(frame.id);\n }\n }\n\n private setupPing(): void {\n this.pingInterval = setInterval(() => {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.ping();\n }\n }, TIMEOUTS.WEBSOCKET_PING);\n }\n\n private clearPing(): void {\n if (this.pingInterval) {\n clearInterval(this.pingInterval);\n this.pingInterval = null;\n }\n }\n\n private async attemptReconnect(): Promise<void> {\n if (this.isClosing) return;\n\n this.reconnectAttempts++;\n const delay = Math.min(\n TIMEOUTS.RECONNECT_BASE * Math.pow(2, this.reconnectAttempts - 1),\n TIMEOUTS.RECONNECT_MAX\n );\n\n if (this.options.verbose) {\n console.log(`[Relay] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n }\n\n setTimeout(async () => {\n if (this.isClosing) return;\n\n try {\n await this.connect();\n } catch (_) {}\n }, delay);\n }\n\n async disconnect(): Promise<void> {\n this.isClosing = true;\n this.clearPing();\n\n for (const ws of this.activeWebSockets.values()) {\n ws.close();\n }\n this.activeWebSockets.clear();\n\n if (this.ws) {\n this.ws.close(1000, 'Client closing');\n this.ws = null;\n }\n }\n\n getDuration(): number {\n if (this.startTime === 0) return 0;\n return Math.floor((Date.now() - this.startTime) / 1000);\n }\n\n getRequestCount(): number {\n return this.requestCount;\n }\n\n isConnected(): boolean {\n return this.ws !== null && this.ws.readyState === WebSocket.OPEN;\n }\n\n getTunnelUrl(): string {\n return this.tunnelUrl;\n }\n}\n","import { nanoid } from 'nanoid';\nimport path from 'node:path';\n\nexport const RELAY_DOMAIN = 'relay.untunneled.dev';\nexport const TUNNEL_DOMAIN = 'untunneled.dev';\n\nexport const getRelayUrl = (tunnelId: string): string => {\n const host = process.env['UNTUNNELED_RELAY_HOST'] || RELAY_DOMAIN;\n const protocol = host.includes('localhost') ? 'ws' : 'wss';\n return `${protocol}://${host}/${tunnelId}`;\n};\n\nexport const getTunnelUrl = (tunnelId: string): string => {\n const domain = process.env['UNTUNNELED_TUNNEL_DOMAIN'] || TUNNEL_DOMAIN;\n return `https://${tunnelId}.${domain}`;\n};\n\nexport function getProjectName(): string {\n const cwd = process.cwd();\n const folderName = path.basename(cwd);\n\n return (\n folderName\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, 20) || 'tunnel'\n );\n}\n\nexport function generateTunnelId(projectName?: string): string {\n const name = projectName || getProjectName();\n const suffix = nanoid(6).toLowerCase();\n return `${name}-${suffix}`;\n}\n\nexport function validatePort(port: string | number): number {\n const portNum = typeof port === 'string' ? parseInt(port, 10) : port;\n\n if (isNaN(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error(`Invalid port number: ${port}. Must be between 1 and 65535.`);\n }\n\n return portNum;\n}\n\nexport function parseAllowList(allow?: string): string[] | undefined {\n if (!allow) return undefined;\n\n return allow\n .split(',')\n .map((email) => email.trim().toLowerCase())\n .filter((email) => email.length > 0);\n}\n\nexport const TIMEOUTS = {\n RELAY_REQUEST: 30000,\n WEBSOCKET_PING: 30000,\n RECONNECT_BASE: 1000,\n RECONNECT_MAX: 30000,\n};\n","import { PostHog } from 'posthog-node';\n\nconst POSTHOG_API_KEY = process.env['POSTHOG_API_KEY'] || 'phc_placeholder';\nconst POSTHOG_HOST = 'https://app.posthog.com';\n\ninterface AnalyticsOptions {\n enabled: boolean;\n tunnelId: string;\n}\n\ntype EventName = 'tunnel_start' | 'tunnel_end' | 'request_handled';\n\ninterface EventProperties {\n tunnel_start: {\n has_auth: boolean;\n node_version: string;\n platform: string;\n };\n tunnel_end: {\n duration_seconds: number;\n requests: number;\n };\n request_handled: {\n method: string;\n status: number;\n duration_ms: number;\n };\n}\n\nexport class Analytics {\n private posthog: PostHog | null = null;\n private tunnelId: string;\n private enabled: boolean;\n\n constructor(options: AnalyticsOptions) {\n this.tunnelId = options.tunnelId;\n this.enabled = options.enabled;\n\n if (this.enabled && POSTHOG_API_KEY !== 'phc_placeholder') {\n this.posthog = new PostHog(POSTHOG_API_KEY, {\n host: POSTHOG_HOST,\n flushAt: 10,\n flushInterval: 10000,\n });\n }\n }\n\n async track<E extends EventName>(event: E, properties?: EventProperties[E]): Promise<void> {\n if (!this.posthog || !this.enabled) return;\n\n try {\n this.posthog.capture({\n distinctId: this.tunnelId,\n event,\n properties: {\n ...properties,\n version: '0.1.0',\n cli: true,\n },\n });\n } catch (_) {}\n }\n\n async shutdown(): Promise<void> {\n if (!this.posthog) return;\n\n try {\n await this.posthog.shutdown();\n } catch (_) {}\n }\n\n isEnabled(): boolean {\n return this.enabled && this.posthog !== null;\n }\n}\n","export async function listCommand(): Promise<void> {\n console.log('Active tunnels:');\n console.log(' (No active tunnels)');\n console.log('');\n console.log('Note: Tunnel list feature coming soon.');\n}\n","export async function stopCommand(tunnelId: string): Promise<void> {\n console.log(`Stopping tunnel: ${tunnelId}`);\n console.log('');\n console.log('Note: Remote stop feature coming soon.');\n}\n"],"mappings":";;;AAAA,SAAS,eAAe;;;ACAxB,SAAS,cAAc;AACvB,OAAOA,YAAW;;;ACDlB,SAAgB,YAAAC,WAAU,aAAAC,kBAAiB;AAC3C,SAAS,OAAAC,MAAK,QAAAC,OAAM,QAAQ,gBAAgB;;;ACD5C,SAAgB,UAAU,iBAAiB;AAC3C,SAAS,KAAK,YAAY;AAC1B,OAAO,YAAY;AAkBX,cAMJ,YANI;AAZD,IAAM,SAAgC,CAAC,EAAE,IAAI,MAAM;AACxD,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,EAAE;AAE3C,YAAU,MAAM;AACd,WAAO,SAAS,KAAK,EAAE,OAAO,KAAK,GAAG,CAAC,OAAe;AACpD,kBAAY,EAAE;AAAA,IAChB,CAAC;AAAA,EACH,GAAG,CAAC,GAAG,CAAC;AAER,MAAI,CAAC,UAAU;AACb,WACE,oBAAC,OACC,8BAAC,QAAK,UAAQ,MAAC,mCAAqB,GACtC;AAAA,EAEJ;AAEA,SACE,qBAAC,OAAI,eAAc,UAAS,aAAY,UAAS,UAAU,GACzD;AAAA,wBAAC,QAAK,UAAQ,MAAC,+BAAiB;AAAA,IAChC,oBAAC,QAAM,oBAAS;AAAA,KAClB;AAEJ;;;AC/BA,OAAkB;AAClB,SAAS,OAAAC,MAAK,QAAAC,aAAY;AAmCpB,SAUE,OAAAC,MAVF,QAAAC,aAAA;AA5BN,IAAM,iBAAiB,CAAC,WAA2B;AACjD,MAAI,UAAU,IAAK,QAAO;AAC1B,MAAI,UAAU,IAAK,QAAO;AAC1B,MAAI,UAAU,IAAK,QAAO;AAC1B,MAAI,UAAU,IAAK,QAAO;AAC1B,SAAO;AACT;AAEA,IAAM,iBAAiB,CAAC,WAA2B;AACjD,UAAQ,OAAO,YAAY,GAAG;AAAA,IAC5B,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAEO,IAAM,aAAwC,CAAC,EAAE,SAAS,MAAM;AACrE,MAAI,SAAS,WAAW,GAAG;AACzB,WACE,gBAAAA;AAAA,MAACH;AAAA,MAAA;AAAA,QACC,aAAY;AAAA,QACZ,aAAY;AAAA,QACZ,eAAc;AAAA,QACd,YAAW;AAAA,QACX,UAAU;AAAA,QACV,UAAU;AAAA,QACV,OAAO;AAAA,QACP,WAAU;AAAA,QAEV;AAAA,0BAAAE,KAACD,OAAA,EAAK,OAAM,QAAO,4CAAyB;AAAA,UAC5C,gBAAAC,KAACF,MAAA,EAAI,QAAQ,GAAG;AAAA,UAChB,gBAAAE,KAACD,OAAA,EAAK,UAAQ,MAAC,0CAA4B;AAAA;AAAA;AAAA,IAC7C;AAAA,EAEJ;AAEA,SACE,gBAAAC,KAACF,MAAA,EAAI,eAAc,UAChB,mBAAS,IAAI,CAAC,QAAQ;AACrB,UAAM,OAAO,IAAI,KAAK,IAAI,SAAS,EAAE,mBAAmB,CAAC,GAAG;AAAA,MAC1D,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,QAAQ;AAAA,MACR,QAAQ;AAAA,IACV,CAAC;AAED,WACE,gBAAAG,MAACH,MAAA,EAAiB,KAAK,GACrB;AAAA,sBAAAG,MAACF,OAAA,EAAK,UAAQ,MAAC;AAAA;AAAA,QAAE;AAAA,QAAK;AAAA,SAAC;AAAA,MACvB,gBAAAC,KAACF,MAAA,EAAI,OAAO,GACV,0BAAAE,KAACD,OAAA,EAAK,OAAO,eAAe,IAAI,MAAM,GAAG,MAAI,MAC1C,cAAI,OAAO,OAAO,CAAC,GACtB,GACF;AAAA,MACA,gBAAAC,KAACF,MAAA,EAAI,UAAU,GACb,0BAAAE,KAACD,OAAA,EAAM,cAAI,KAAK,SAAS,KAAK,IAAI,KAAK,UAAU,GAAG,EAAE,IAAI,QAAQ,IAAI,MAAK,GAC7E;AAAA,MACA,gBAAAC,KAACF,MAAA,EAAI,OAAO,GACV,0BAAAE,KAACD,OAAA,EAAK,OAAO,eAAe,IAAI,MAAM,GAAG,MAAI,MAC1C,cAAI,QACP,GACF;AAAA,MACA,gBAAAC,KAACF,MAAA,EAAI,OAAO,IAAI,gBAAe,YAC7B,0BAAAG,MAACF,OAAA,EAAK,OAAO,IAAI,WAAW,MAAM,WAAW,QAC1C;AAAA,YAAI,SAAS,SAAS,EAAE,SAAS,CAAC;AAAA,QAAE;AAAA,SACvC,GACF;AAAA,SAnBQ,IAAI,EAoBd;AAAA,EAEJ,CAAC,GACH;AAEJ;;;ACzFA,SAAgB,YAAAG,WAAU,aAAAC,kBAAiB;AAC3C,SAAS,OAAAC,MAAK,QAAAC,aAAY;AA4CtB,gBAAAC,MACE,QAAAC,aADF;AArCJ,IAAM,iBAAiB,CAAC,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,QAAG;AAExE,IAAM,qBAAqB,CACzB,QACA,UACqD;AACrD,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,EAAE,OAAO,SAAS,QAAQ,UAAK,OAAO,YAAY;AAAA,IAC3D,KAAK;AACH,aAAO,EAAE,OAAO,UAAU,QAAQ,OAAO,OAAO,aAAa;AAAA,IAC/D,KAAK;AACH,aAAO,EAAE,OAAO,QAAQ,QAAQ,UAAK,OAAO,eAAe;AAAA,IAC7D,KAAK;AACH,aAAO,EAAE,OAAO,OAAO,QAAQ,UAAK,OAAO,QAAQ;AAAA,IACrD;AACE,aAAO,EAAE,OAAO,SAAS,QAAQ,KAAK,OAAO,OAAO,MAAM,EAAE,YAAY,EAAE;AAAA,EAC9E;AACF;AAEO,IAAM,mBAAoD,CAAC,EAAE,OAAO,MAAM;AAC/E,QAAM,CAAC,YAAY,aAAa,IAAIL,UAAS,CAAC;AAE9C,EAAAC,WAAU,MAAM;AACd,QAAI,WAAW,aAAc;AAE7B,UAAM,QAAQ,YAAY,MAAM;AAC9B,oBAAc,CAAC,UAAU,OAAO,KAAK,eAAe,MAAM;AAAA,IAC5D,GAAG,EAAE;AAEL,WAAO,MAAM,cAAc,KAAK;AAAA,EAClC,GAAG,CAAC,MAAM,CAAC;AAEX,QAAM,QAAQ,eAAe,UAAU,KAAK;AAC5C,QAAM,YAAY,mBAAmB,QAAQ,KAAK;AAElD,SACE,gBAAAG,KAACF,MAAA,EACC,0BAAAG,MAACF,OAAA,EAAK,OAAO,UAAU,OAAO,MAAI,MAC/B;AAAA,cAAU;AAAA,IAAO;AAAA,IAAE,UAAU;AAAA,KAChC,GACF;AAEJ;;;AHuCQ,gBAAAG,MAGA,QAAAC,aAHA;AA3ER,IAAM,WAAW;AAEjB,IAAM,OAAO;AAAA;AAAA;AAAA;AAAA;AAMb,IAAM,eAAe,CAAC,OAAe;AACnC,QAAM,UAAU,KAAK,MAAM,KAAK,GAAI;AACpC,QAAM,UAAU,KAAK,MAAM,UAAU,EAAE;AACvC,QAAM,QAAQ,KAAK,MAAM,UAAU,EAAE;AACrC,SAAO,GAAG,KAAK,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE;AACnD;AAEO,IAAM,WAAoC,CAAC;AAAA,EAChD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,SAAO;AACP,QAAM,CAAC,UAAU,WAAW,IAAIC,UAA2B,CAAC,CAAC;AAC7D,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAsB;AAAA,IAC9C,OAAO;AAAA,IACP,WAAW,KAAK,IAAI;AAAA,EACtB,CAAC;AACD,QAAM,CAAC,gBAAgB,iBAAiB,IAAIA,UAAS,YAAY,YAAY,CAAC;AAC9E,QAAM,CAAC,QAAQ,SAAS,IAAIA,UAAS,CAAC;AACtC,QAAM,CAAC,aAAa,cAAc,IAAIA,UAAS,KAAK;AAEpD,WAAS,CAAC,OAAO,QAAQ;AACvB,QAAI,UAAU,OAAO,UAAU,OAAO,UAAU,OAAO,UAAU,KAAK;AACpE,qBAAe,CAAC,WAAW;AAAA,IAC7B;AACA,QAAI,IAAI,UAAU,aAAa;AAC7B,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF,CAAC;AAED,EAAAC,WAAU,MAAM;AACd,UAAM,gBAAgB,CAAC,QAAwB;AAC7C,kBAAY,CAAC,SAAS,CAAC,GAAG,KAAK,MAAM,EAAE,WAAW,EAAE,GAAG,GAAG,CAAC;AAC3D,eAAS,CAAC,UAAU;AAAA,QAClB,GAAG;AAAA,QACH,OAAO,KAAK,QAAQ;AAAA,MACtB,EAAE;AAAA,IACJ;AAEA,UAAM,uBAAuB,MAAM,kBAAkB,IAAI;AACzD,UAAM,0BAA0B,MAAM,kBAAkB,KAAK;AAE7D,gBAAY,GAAG,WAAW,aAAa;AACvC,gBAAY,GAAG,aAAa,oBAAoB;AAChD,gBAAY,GAAG,gBAAgB,uBAAuB;AAEtD,UAAM,cAAc,YAAY,MAAM;AACpC,gBAAU,KAAK,IAAI,IAAI,MAAM,SAAS;AAAA,IACxC,GAAG,GAAI;AAEP,WAAO,MAAM;AACX,kBAAY,IAAI,WAAW,aAAa;AACxC,kBAAY,IAAI,aAAa,oBAAoB;AACjD,kBAAY,IAAI,gBAAgB,uBAAuB;AACvD,oBAAc,WAAW;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,aAAa,MAAM,SAAS,CAAC;AAEjC,QAAM,mBAAmB,iBAAiB,cAAc;AACxD,QAAM,cAAc,iBAAiB,SAAS;AAE9C,SACE,gBAAAF,MAACG,MAAA,EAAI,eAAc,UAAS,UAAU,GAAG,UAAU,GAEjD;AAAA,oBAAAH,MAACG,MAAA,EAAI,eAAc,UAAS,cAAc,GACxC;AAAA,sBAAAJ,KAACK,OAAA,EAAK,OAAM,QAAO,MAAI,MACpB,gBACH;AAAA,MACA,gBAAAJ,MAACG,MAAA,EAAI,gBAAe,iBAAgB,UAAU,GAC5C;AAAA,wBAAAJ,KAAC,oBAAiB,QAAQ,kBAAkB;AAAA,QAC5C,gBAAAA,KAACK,OAAA,EAAK,UAAQ,MAAC,oBAAM;AAAA,SACvB;AAAA,OACF;AAAA,IAGA,gBAAAJ;AAAA,MAACG;AAAA,MAAA;AAAA,QACC,aAAY;AAAA,QACZ,aAAa;AAAA,QACb,UAAU;AAAA,QACV,UAAU;AAAA,QACV,eAAc;AAAA,QAEd;AAAA,0BAAAH,MAACG,MAAA,EACC;AAAA,4BAAAJ,KAACK,OAAA,EAAK,MAAI,MAAC,0BAAY;AAAA,YACvB,gBAAAJ,MAACI,OAAA,EAAK,MAAI,MAAC,OAAM,SAAQ,iBAAgB,QACtC;AAAA;AAAA,cACA;AAAA,cAAW;AAAA,eACd;AAAA,aACF;AAAA,UACA,gBAAAJ,MAACG,MAAA,EAAI,WAAW,GACd;AAAA,4BAAAJ,KAACK,OAAA,EAAK,UAAQ,MAAC,0BAAY;AAAA,YAC3B,gBAAAJ,MAACI,OAAA,EAAK,OAAM,UAAS;AAAA;AAAA,cAAkB;AAAA,eAAU;AAAA,YACjD,gBAAAL,KAACK,OAAA,EAAK,UAAQ,MAAC,sBAAG;AAAA,YAClB,gBAAAL,KAACK,OAAA,EAAK,OAAM,QAAQ,qBAAU;AAAA,aAChC;AAAA;AAAA;AAAA,IACF;AAAA,IAGA,gBAAAJ,MAACG,MAAA,EAAI,WAAW,GAAG,UAAU,GAAG,KAAK,GACnC;AAAA,sBAAAH,MAACG,MAAA,EACC;AAAA,wBAAAJ,KAACK,OAAA,EAAK,UAAQ,MAAC,wBAAU;AAAA,QACzB,gBAAAL,KAACK,OAAA,EAAK,OAAM,SAAQ,MAAI,MACrB,gBAAM,OACT;AAAA,SACF;AAAA,MACA,gBAAAJ,MAACG,MAAA,EACC;AAAA,wBAAAJ,KAACK,OAAA,EAAK,UAAQ,MAAC,sBAAQ;AAAA,QACvB,gBAAAL,KAACK,OAAA,EAAK,OAAM,SAAQ,MAAI,MACrB,uBAAa,MAAM,GACtB;AAAA,SACF;AAAA,MACA,gBAAAJ,MAACG,MAAA,EACC;AAAA,wBAAAJ,KAACK,OAAA,EAAK,UAAQ,MAAC,uBAAS;AAAA,QACxB,gBAAAL,KAACK,OAAA,EAAK,OAAM,SAAQ,MAAI,MACrB,mBAAS,SAAS,IAAI,GAAG,SAAS,SAAS,SAAS,CAAC,GAAG,QAAQ,OAAO,KAC1E;AAAA,SACF;AAAA,OACF;AAAA,IAGC,UACC,gBAAAL;AAAA,MAACI;AAAA,MAAA;AAAA,QACC,SAAS;AAAA,QACT,aAAY;AAAA,QACZ,aAAY;AAAA,QACZ,UAAU;AAAA,QACV,WAAU;AAAA,QAEV,0BAAAJ,KAAC,UAAO,KAAK,WAAW;AAAA;AAAA,IAC1B;AAAA,IAIF,gBAAAC,MAACG,MAAA,EAAI,WAAW,GAAG,eAAc,UAC/B;AAAA,sBAAAJ,KAACI,MAAA,EAAI,cAAc,GACjB,0BAAAJ,KAACK,OAAA,EAAK,MAAI,MAAC,OAAM,QAAO,mEAExB,GACF;AAAA,MACA,gBAAAL,KAAC,cAAW,UAAoB;AAAA,OAClC;AAAA,IAGA,gBAAAC;AAAA,MAACG;AAAA,MAAA;AAAA,QACC,WAAW;AAAA,QACX,aAAY;AAAA,QACZ,WAAW;AAAA,QACX,cAAc;AAAA,QACd,YAAY;AAAA,QACZ,aAAa;AAAA,QACb,aAAY;AAAA,QACZ,YAAY;AAAA,QAEZ;AAAA,0BAAAH,MAACG,MAAA,EAAI,UAAU,GAAG,KAAK,GACrB;AAAA,4BAAAH,MAACG,MAAA,EACC;AAAA,8BAAAJ,KAACK,OAAA,EAAK,UAAQ,MAAC,oBAAM;AAAA,cACrB,gBAAAL,KAACK,OAAA,EAAK,OAAM,UAAS,MAAI,MAAC,oBAE1B;AAAA,cACA,gBAAAL,KAACK,OAAA,EAAK,UAAQ,MAAC,sBAAQ;AAAA,eACzB;AAAA,YACA,gBAAAJ,MAACG,MAAA,EACC;AAAA,8BAAAJ,KAACK,OAAA,EAAK,UAAQ,MAAC,oBAAM;AAAA,cACrB,gBAAAL,KAACK,OAAA,EAAK,OAAM,UAAS,MAAI,MAAC,eAE1B;AAAA,cACA,gBAAAL,KAACK,OAAA,EAAK,UAAQ,MAAC,qBAAO;AAAA,eACxB;AAAA,aACF;AAAA,UACA,gBAAAJ,MAACG,MAAA,EACC;AAAA,4BAAAJ,KAACK,OAAA,EAAK,UAAQ,MAAC,uBAAS;AAAA,YACxB,gBAAAL,KAACK,OAAA,EAAK,OAAM,WAAU,MAAI,MAAC,oCAE3B;AAAA,aACF;AAAA;AAAA;AAAA,IACF;AAAA,IAEC,eACC,gBAAAL;AAAA,MAACI;AAAA,MAAA;AAAA,QACC,UAAS;AAAA,QACT,OAAM;AAAA,QACN,QAAO;AAAA,QACP,YAAW;AAAA,QACX,gBAAe;AAAA,QAEf,0BAAAH;AAAA,UAACG;AAAA,UAAA;AAAA,YACC,eAAc;AAAA,YACd,YAAW;AAAA,YACX,aAAY;AAAA,YACZ,aAAY;AAAA,YACZ,SAAS;AAAA,YACT,iBAAgB;AAAA,YAEhB;AAAA,8BAAAJ,KAACI,MAAA,EAAI,cAAc,GACjB,0BAAAJ,KAACK,OAAA,EAAK,MAAI,MAAC,OAAM,QAAO,8BAExB,GACF;AAAA,cACA,gBAAAL,KAAC,UAAO,KAAK,WAAW;AAAA,cACxB,gBAAAC,MAACG,MAAA,EAAI,WAAW,GACd;AAAA,gCAAAJ,KAACK,OAAA,EAAK,UAAQ,MAAC,oBAAM;AAAA,gBACrB,gBAAAL,KAACK,OAAA,EAAK,OAAM,UAAS,MAAI,MAAC,iBAE1B;AAAA,gBACA,gBAAAL,KAACK,OAAA,EAAK,UAAQ,MAAC,kBAAI;AAAA,gBACnB,gBAAAL,KAACK,OAAA,EAAK,OAAM,UAAS,MAAI,MAAC,eAE1B;AAAA,gBACA,gBAAAL,KAACK,OAAA,EAAK,UAAQ,MAAC,uBAAS;AAAA,iBAC1B;AAAA;AAAA;AAAA,QACF;AAAA;AAAA,IACF;AAAA,KAEJ;AAEJ;;;AIhPA,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;;;ACD7B,SAAS,cAAc;AACvB,OAAO,UAAU;AAEV,IAAM,eAAe;AACrB,IAAM,gBAAgB;AAEtB,IAAM,cAAc,CAAC,aAA6B;AACvD,QAAM,OAAO,QAAQ,IAAI,uBAAuB,KAAK;AACrD,QAAM,WAAW,KAAK,SAAS,WAAW,IAAI,OAAO;AACrD,SAAO,GAAG,QAAQ,MAAM,IAAI,IAAI,QAAQ;AAC1C;AAEO,IAAM,eAAe,CAAC,aAA6B;AACxD,QAAM,SAAS,QAAQ,IAAI,0BAA0B,KAAK;AAC1D,SAAO,WAAW,QAAQ,IAAI,MAAM;AACtC;AAEO,SAAS,iBAAyB;AACvC,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,aAAa,KAAK,SAAS,GAAG;AAEpC,SACE,WACG,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EAAE,EACpB,MAAM,GAAG,EAAE,KAAK;AAEvB;AAEO,SAAS,iBAAiB,aAA8B;AAC7D,QAAM,OAAO,eAAe,eAAe;AAC3C,QAAM,SAAS,OAAO,CAAC,EAAE,YAAY;AACrC,SAAO,GAAG,IAAI,IAAI,MAAM;AAC1B;AAEO,SAAS,aAAa,MAA+B;AAC1D,QAAM,UAAU,OAAO,SAAS,WAAW,SAAS,MAAM,EAAE,IAAI;AAEhE,MAAI,MAAM,OAAO,KAAK,UAAU,KAAK,UAAU,OAAO;AACpD,UAAM,IAAI,MAAM,wBAAwB,IAAI,gCAAgC;AAAA,EAC9E;AAEA,SAAO;AACT;AAEO,SAAS,eAAe,OAAsC;AACnE,MAAI,CAAC,MAAO,QAAO;AAEnB,SAAO,MACJ,MAAM,GAAG,EACT,IAAI,CAAC,UAAU,MAAM,KAAK,EAAE,YAAY,CAAC,EACzC,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AACvC;AAEO,IAAM,WAAW;AAAA,EACtB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,eAAe;AACjB;;;ADxCO,IAAM,cAAN,cAA0B,aAAa;AAAA,EACpC,KAAuB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAoB;AAAA,EACpB,eAAuB;AAAA,EACvB,oBAA4B;AAAA,EAC5B,YAAqB;AAAA,EACrB,eAAsC;AAAA,EACtC,mBAA2C,oBAAI,IAAI;AAAA,EAE3D,YAAY,SAA6B;AACvC,UAAM;AACN,SAAK,UAAU;AACf,SAAK,YAAY,QAAQ;AACzB,SAAK,WAAW,QAAQ;AACxB,SAAK,YAAY,QAAQ;AAAA,EAC3B;AAAA,EAEA,MAAM,UAAyB;AAC7B,SAAK,YAAY,KAAK,IAAI;AAC1B,SAAK,YAAY;AAEjB,UAAM,QAAQ,YAAY,KAAK,QAAQ;AAEvC,QAAI,KAAK,QAAQ,SAAS;AACxB,cAAQ,IAAI,yBAAyB,KAAK,EAAE;AAAA,IAC9C;AAEA,WAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAI;AACF,aAAK,KAAK,IAAI,UAAU,OAAO;AAAA,UAC7B,SAAS;AAAA,YACP,iBAAiB,KAAK,QAAQ,YAAY;AAAA,YAC1C,kBAAkB,KAAK,QAAQ,WAAW,KAAK,GAAG,KAAK;AAAA,UACzD;AAAA,QACF,CAAC;AAED,cAAM,oBAAoB,WAAW,MAAM;AACzC,cAAI,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACpD,iBAAK,GAAG,UAAU;AAClB,mBAAO,IAAI,MAAM,oBAAoB,CAAC;AAAA,UACxC;AAAA,QACF,GAAG,SAAS,aAAa;AAEzB,aAAK,GAAG,GAAG,QAAQ,MAAM;AACvB,uBAAa,iBAAiB;AAC9B,eAAK,oBAAoB;AAEzB,cAAI,KAAK,QAAQ,SAAS;AACxB,oBAAQ,IAAI,mBAAmB;AAAA,UACjC;AAEA,eAAK,UAAU;AACf,eAAK,KAAK,WAAW;AACrB,kBAAQ;AAAA,QACV,CAAC;AAED,aAAK,GAAG,GAAG,SAAS,CAAC,QAAQ;AAC3B,uBAAa,iBAAiB;AAE9B,cAAI,KAAK,QAAQ,SAAS;AACxB,oBAAQ,MAAM,4BAA4B,IAAI,OAAO;AAAA,UACvD;AAEA,eAAK,KAAK,SAAS,GAAG;AACtB,iBAAO,GAAG;AAAA,QACZ,CAAC;AAED,aAAK,GAAG,GAAG,SAAS,MAAM;AACxB,eAAK,UAAU;AAEf,cAAI,CAAC,KAAK,WAAW;AACnB,iBAAK,KAAK,cAAc;AACxB,iBAAK,iBAAiB;AAAA,UACxB;AAAA,QACF,CAAC;AAED,aAAK,GAAG,GAAG,WAAW,OAAO,SAAS;AACpC,gBAAM,KAAK,iBAAiB,IAAI;AAAA,QAClC,CAAC;AAAA,MACH,SAAS,OAAO;AACd,eAAO,KAAK;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,iBAAiB,MAAsD;AACnF,QAAI;AAEJ,QAAI;AACF,cAAQ,KAAK,MAAM,OAAO,IAAI,CAAC;AAAA,IACjC,QAAQ;AACN,UAAI,KAAK,QAAQ,SAAS;AACxB,gBAAQ,MAAM,4BAA4B;AAAA,MAC5C;AACA;AAAA,IACF;AAEA,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK;AACH,cAAM,KAAK,kBAAkB,KAAK;AAClC;AAAA,MACF,KAAK;AACH,cAAM,KAAK,aAAa,KAAK;AAC7B;AAAA,MACF,KAAK;AACH,cAAM,KAAK,gBAAgB,KAAK;AAChC;AAAA,MACF,KAAK;AACH,cAAM,KAAK,cAAc,KAAK;AAC9B;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,MAAc,kBAAkB,SAA+D;AAC7F,UAAM,YAAY,KAAK,IAAI;AAE3B,QAAI;AACF,YAAM,MAAM,oBAAoB,KAAK,SAAS,GAAG,QAAQ,IAAI;AAE7D,YAAM,eAA4B;AAAA,QAChC,QAAQ,QAAQ;AAAA,QAChB,SAAS,QAAQ;AAAA,MACnB;AAEA,UAAI,QAAQ,QAAQ,CAAC,CAAC,OAAO,MAAM,EAAE,SAAS,QAAQ,MAAM,GAAG;AAC7D,qBAAa,OAAO,QAAQ;AAAA,MAC9B;AAEA,YAAM,WAAW,MAAM,MAAM,KAAK,YAAY;AAC9C,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,YAAM,WAAW,KAAK,IAAI,IAAI;AAE9B,YAAM,gBAA+B;AAAA,QACnC,MAAM;AAAA,QACN,IAAI,QAAQ;AAAA,QACZ,QAAQ,SAAS;AAAA,QACjB,YAAY,SAAS;AAAA,QACrB,SAAS,OAAO,YAAY,SAAS,QAAQ,QAAQ,CAAC;AAAA,QACtD;AAAA,MACF;AAEA,WAAK,IAAI,KAAK,KAAK,UAAU,aAAa,CAAC;AAE3C,YAAM,MAAkB;AAAA,QACtB,IAAI,QAAQ;AAAA,QACZ,WAAW,KAAK,IAAI;AAAA,QACpB,QAAQ,QAAQ;AAAA,QAChB,MAAM,QAAQ;AAAA,QACd,QAAQ,SAAS;AAAA,QACjB;AAAA,MACF;AAEA,WAAK,KAAK,WAAW,GAAG;AACxB,WAAK;AAAA,IACP,SAAS,OAAO;AACd,YAAM,MAAM;AAEZ,UAAI,KAAK,QAAQ,WAAW,IAAI,SAAS,gBAAgB;AACvD,gBAAQ,MAAM,wBAAwB,IAAI,OAAO,EAAE;AAAA,MACrD;AAEA,YAAM,gBAA+B;AAAA,QACnC,MAAM;AAAA,QACN,IAAI,QAAQ;AAAA,QACZ,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,SAAS,CAAC;AAAA,QACV,MAAM,UAAU,IAAI,OAAO;AAAA,MAC7B;AAEA,WAAK,IAAI,KAAK,KAAK,UAAU,aAAa,CAAC;AAAA,IAC7C;AAAA,EACF;AAAA,EAEA,MAAc,aAAa,OAA8B;AACvD,UAAM,WAAW,kBAAkB,KAAK,SAAS,GAAG,MAAM,GAAG;AAE7D,QAAI,KAAK,QAAQ,SAAS;AACxB,cAAQ,IAAI,gCAAgC,QAAQ,EAAE;AAAA,IACxD;AAEA,UAAM,iBACJ,MAAM,QAAQ,wBAAwB,KAAK,MAAM,QAAQ,wBAAwB;AACnF,UAAM,kBAAkB,OAAO;AAAA,MAC7B,OAAO,QAAQ,MAAM,OAAO,EAAE,OAAO,CAAC,CAAC,GAAG,MAAM;AAC9C,cAAM,SAAS,IAAI,YAAY;AAC/B,eACE,WAAW,gBACX,WAAW,aACX,WAAW,UACX,CAAC,OAAO,WAAW,eAAe;AAAA,MAEtC,CAAC;AAAA,IACH;AAEA,UAAM,UAAU,iBACZ,IAAI,UAAU,UAAU,gBAAgB,EAAE,SAAS,gBAAgB,CAAC,IACpE,IAAI,UAAU,UAAU,EAAE,SAAS,gBAAgB,CAAC;AAExD,SAAK,iBAAiB,IAAI,MAAM,IAAI,OAAO;AAE3C,YAAQ,GAAG,WAAW,CAAC,MAAM,aAAa;AACxC,YAAM,UAAqB;AAAA,QACzB,MAAM;AAAA,QACN,IAAI,MAAM;AAAA,QACV,MAAM,WAAY,KAAgB,SAAS,QAAQ,IAAI,KAAK,SAAS;AAAA,QACrE;AAAA,MACF;AACA,WAAK,IAAI,KAAK,KAAK,UAAU,OAAO,CAAC;AAAA,IACvC,CAAC;AAED,YAAQ,GAAG,SAAS,MAAM;AACxB,YAAM,aAAsB;AAAA,QAC1B,MAAM;AAAA,QACN,IAAI,MAAM;AAAA,MACZ;AACA,WAAK,IAAI,KAAK,KAAK,UAAU,UAAU,CAAC;AACxC,WAAK,iBAAiB,OAAO,MAAM,EAAE;AAAA,IACvC,CAAC;AAED,YAAQ,GAAG,SAAS,CAAC,QAAQ;AAC3B,UAAI,KAAK,QAAQ,SAAS;AACxB,gBAAQ,MAAM,kCAAkC,MAAM,EAAE,MAAM,IAAI,OAAO;AAAA,MAC3E;AACA,cAAQ,MAAM;AAAA,IAChB,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,gBAAgB,OAAiC;AAC7D,UAAM,UAAU,KAAK,iBAAiB,IAAI,MAAM,EAAE;AAClD,QAAI,WAAW,QAAQ,eAAe,UAAU,MAAM;AACpD,YAAM,OAAO,MAAM,WAAW,OAAO,KAAK,MAAM,MAAM,QAAQ,IAAI,MAAM;AACxE,cAAQ,KAAK,IAAI;AAAA,IACnB;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,OAA+B;AACzD,UAAM,UAAU,KAAK,iBAAiB,IAAI,MAAM,EAAE;AAClD,QAAI,SAAS;AACX,cAAQ,MAAM;AACd,WAAK,iBAAiB,OAAO,MAAM,EAAE;AAAA,IACvC;AAAA,EACF;AAAA,EAEQ,YAAkB;AACxB,SAAK,eAAe,YAAY,MAAM;AACpC,UAAI,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACpD,aAAK,GAAG,KAAK;AAAA,MACf;AAAA,IACF,GAAG,SAAS,cAAc;AAAA,EAC5B;AAAA,EAEQ,YAAkB;AACxB,QAAI,KAAK,cAAc;AACrB,oBAAc,KAAK,YAAY;AAC/B,WAAK,eAAe;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,MAAc,mBAAkC;AAC9C,QAAI,KAAK,UAAW;AAEpB,SAAK;AACL,UAAM,QAAQ,KAAK;AAAA,MACjB,SAAS,iBAAiB,KAAK,IAAI,GAAG,KAAK,oBAAoB,CAAC;AAAA,MAChE,SAAS;AAAA,IACX;AAEA,QAAI,KAAK,QAAQ,SAAS;AACxB,cAAQ,IAAI,2BAA2B,KAAK,eAAe,KAAK,iBAAiB,GAAG;AAAA,IACtF;AAEA,eAAW,YAAY;AACrB,UAAI,KAAK,UAAW;AAEpB,UAAI;AACF,cAAM,KAAK,QAAQ;AAAA,MACrB,SAAS,GAAG;AAAA,MAAC;AAAA,IACf,GAAG,KAAK;AAAA,EACV;AAAA,EAEA,MAAM,aAA4B;AAChC,SAAK,YAAY;AACjB,SAAK,UAAU;AAEf,eAAW,MAAM,KAAK,iBAAiB,OAAO,GAAG;AAC/C,SAAG,MAAM;AAAA,IACX;AACA,SAAK,iBAAiB,MAAM;AAE5B,QAAI,KAAK,IAAI;AACX,WAAK,GAAG,MAAM,KAAM,gBAAgB;AACpC,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,cAAsB;AACpB,QAAI,KAAK,cAAc,EAAG,QAAO;AACjC,WAAO,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,aAAa,GAAI;AAAA,EACxD;AAAA,EAEA,kBAA0B;AACxB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,cAAuB;AACrB,WAAO,KAAK,OAAO,QAAQ,KAAK,GAAG,eAAe,UAAU;AAAA,EAC9D;AAAA,EAEA,eAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AACF;;;AEjVA,SAAS,eAAe;AAExB,IAAM,kBAAkB,QAAQ,IAAI,iBAAiB,KAAK;AAC1D,IAAM,eAAe;AA0Bd,IAAM,YAAN,MAAgB;AAAA,EACb,UAA0B;AAAA,EAC1B;AAAA,EACA;AAAA,EAER,YAAY,SAA2B;AACrC,SAAK,WAAW,QAAQ;AACxB,SAAK,UAAU,QAAQ;AAEvB,QAAI,KAAK,WAAW,oBAAoB,mBAAmB;AACzD,WAAK,UAAU,IAAI,QAAQ,iBAAiB;AAAA,QAC1C,MAAM;AAAA,QACN,SAAS;AAAA,QACT,eAAe;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAM,MAA2B,OAAU,YAAgD;AACzF,QAAI,CAAC,KAAK,WAAW,CAAC,KAAK,QAAS;AAEpC,QAAI;AACF,WAAK,QAAQ,QAAQ;AAAA,QACnB,YAAY,KAAK;AAAA,QACjB;AAAA,QACA,YAAY;AAAA,UACV,GAAG;AAAA,UACH,SAAS;AAAA,UACT,KAAK;AAAA,QACP;AAAA,MACF,CAAC;AAAA,IACH,SAAS,GAAG;AAAA,IAAC;AAAA,EACf;AAAA,EAEA,MAAM,WAA0B;AAC9B,QAAI,CAAC,KAAK,QAAS;AAEnB,QAAI;AACF,YAAM,KAAK,QAAQ,SAAS;AAAA,IAC9B,SAAS,GAAG;AAAA,IAAC;AAAA,EACf;AAAA,EAEA,YAAqB;AACnB,WAAO,KAAK,WAAW,KAAK,YAAY;AAAA,EAC1C;AACF;;;AP5DA,eAAsB,aAAa,MAAc,SAAsC;AACrF,MAAI;AAEJ,MAAI;AACF,gBAAY,aAAa,IAAI;AAAA,EAC/B,SAAS,OAAO;AACd,YAAQ,MAAO,MAAgB,OAAO;AACtC,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,cAAc,eAAe;AACnC,QAAM,WAAW,iBAAiB,WAAW;AAC7C,QAAM,YAAY,aAAa,QAAQ;AAEvC,QAAM,YAAY,IAAI,UAAU;AAAA,IAC9B,SAAS,QAAQ,cAAc;AAAA,IAC/B;AAAA,EACF,CAAC;AAED,QAAM,UAAU,MAAM,gBAAgB;AAAA,IACpC,UAAU,CAAC,CAAC,QAAQ,YAAY,CAAC,CAAC,QAAQ;AAAA,IAC1C,cAAc,QAAQ;AAAA,IACtB,UAAU,QAAQ;AAAA,EACpB,CAAC;AAED,QAAM,cAAc,IAAI,YAAY;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,QAAQ;AAAA,IAClB,WAAW,eAAe,QAAQ,KAAK;AAAA,IACvC,SAAS,QAAQ;AAAA,EACnB,CAAC;AAED,MAAI;AACF,UAAM,YAAY,QAAQ;AAE1B,QAAI,QAAQ,SAAS;AACnB,cAAQ,IAAI,eAAe,SAAS,EAAE;AACtC,cAAQ,IAAI,2BAA2B,SAAS,EAAE;AAAA,IACpD;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,sCAAuC,MAAgB,OAAO;AAC5E,UAAM,UAAU,SAAS;AACzB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,EAAE,cAAc,IAAI;AAAA,IACxBC,OAAM,cAAc,UAAU;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,UAAU,YAAY;AAC1B,UAAM,WAAW,YAAY,YAAY;AACzC,UAAM,WAAW,YAAY,gBAAgB;AAE7C,UAAM,UAAU,MAAM,cAAc;AAAA,MAClC,kBAAkB;AAAA,MAClB;AAAA,IACF,CAAC;AAED,UAAM,YAAY,WAAW;AAC7B,UAAM,UAAU,SAAS;AAAA,EAC3B;AAEA,UAAQ,GAAG,UAAU,YAAY;AAC/B,UAAM,QAAQ;AACd,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AAED,UAAQ,GAAG,WAAW,YAAY;AAChC,UAAM,QAAQ;AACd,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AAED,QAAM,cAAc;AACtB;;;AQ9FA,eAAsB,cAA6B;AACjD,UAAQ,IAAI,iBAAiB;AAC7B,UAAQ,IAAI,uBAAuB;AACnC,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,wCAAwC;AACtD;;;ACLA,eAAsB,YAAY,UAAiC;AACjE,UAAQ,IAAI,oBAAoB,QAAQ,EAAE;AAC1C,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,wCAAwC;AACtD;;;AVDA,IAAM,UAAU,IAAI,QAAQ;AAE5B,QAAQ,KAAK,YAAY,EAAE,YAAY,gCAAgC,EAAE,QAAQ,OAAO;AAExF,QACG,SAAS,UAAU,sBAAsB,EACzC,OAAO,QAAQ,gCAAgC,EAC/C,OAAO,yBAAyB,6BAA6B,EAC7D,OAAO,oBAAoB,mCAAmC,EAC9D,OAAO,aAAa,oBAAoB,EACxC,OAAO,kBAAkB,kCAAkC,EAC3D,OAAO,YAAY;AAEtB,QAAQ,QAAQ,MAAM,EAAE,YAAY,qBAAqB,EAAE,OAAO,WAAW;AAE7E,QACG,QAAQ,MAAM,EACd,SAAS,eAAe,mBAAmB,EAC3C,YAAY,uBAAuB,EACnC,OAAO,WAAW;AAErB,QAAQ,MAAM;","names":["React","useState","useEffect","Box","Text","Box","Text","jsx","jsxs","useState","useEffect","Box","Text","jsx","jsxs","jsx","jsxs","useState","useEffect","Box","Text","React"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/commands/start.ts","../src/ui/TunnelUI.tsx","../src/ui/QRCode.tsx","../src/ui/RequestLog.tsx","../src/ui/ConnectionStatus.tsx","../src/tunnel/relay.ts","../src/config.ts","../src/analytics.ts","../src/commands/list.ts","../src/commands/stop.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { startCommand, listCommand, stopCommand } from './commands/index.js';\n\nconst program = new Command();\n\nprogram.name('untunneled').description('Fast, free localhost tunneling').version('0.3.2');\n\nprogram\n .argument('<port>', 'Local port to tunnel')\n .option('--qr', 'Open the QR code modal on start')\n .option('--password <password>', 'Password protect the tunnel')\n .option('--allow <emails>', 'Email whitelist (comma-separated)')\n .option('--verbose', 'Show detailed logs')\n .option('--no-telemetry', 'Disable anonymous usage tracking')\n .action(startCommand);\n\nprogram.command('list').description('List active tunnels').action(listCommand);\n\nprogram\n .command('stop')\n .argument('<tunnel-id>', 'Tunnel ID to stop')\n .description('Stop a running tunnel')\n .action(stopCommand);\n\nprogram.parse();\n","import { render } from 'ink';\nimport React from 'react';\nimport { TunnelUI } from '../ui/TunnelUI.js';\nimport { RelayTunnel } from '../tunnel/relay.js';\nimport { Analytics } from '../analytics.js';\nimport {\n generateTunnelId,\n getProjectName,\n getTunnelUrl,\n validatePort,\n parseAllowList,\n} from '../config.js';\nimport type { StartOptions } from '../types.js';\n\nexport async function startCommand(port: string, options: StartOptions): Promise<void> {\n let localPort: number;\n\n try {\n localPort = validatePort(port);\n } catch (error) {\n console.error((error as Error).message);\n process.exit(1);\n }\n\n const projectName = getProjectName();\n const tunnelId = generateTunnelId(projectName);\n const tunnelUrl = getTunnelUrl(tunnelId);\n\n const analytics = new Analytics({\n enabled: options.telemetry !== false,\n tunnelId,\n });\n\n await analytics.track('tunnel_start', {\n has_auth: !!options.password || !!options.allow,\n node_version: process.version,\n platform: process.platform,\n });\n\n const relayTunnel = new RelayTunnel({\n localPort,\n tunnelId,\n tunnelUrl,\n password: options.password,\n allowList: parseAllowList(options.allow),\n verbose: options.verbose,\n });\n\n try {\n await relayTunnel.connect();\n\n if (options.verbose) {\n console.log(`Tunnel URL: ${tunnelUrl}`);\n console.log(`Forwarding to localhost:${localPort}`);\n }\n } catch (error) {\n console.error('Failed to connect to relay server:', (error as Error).message);\n await analytics.shutdown();\n process.exit(1);\n }\n\n const { waitUntilExit } = render(\n React.createElement(TunnelUI, {\n tunnelUrl,\n localPort,\n showQR: options.qr,\n relayTunnel,\n })\n );\n\n const cleanup = async () => {\n const duration = relayTunnel.getDuration();\n const requests = relayTunnel.getRequestCount();\n\n await analytics.track('tunnel_end', {\n duration_seconds: duration,\n requests: requests,\n });\n\n await relayTunnel.disconnect();\n await analytics.shutdown();\n };\n\n process.on('SIGINT', async () => {\n await cleanup();\n process.exit(0);\n });\n\n process.on('SIGTERM', async () => {\n await cleanup();\n process.exit(0);\n });\n\n await waitUntilExit();\n}\n","import React, { useState, useEffect } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport { QRCode } from './QRCode.js';\nimport { RequestLog } from './RequestLog.js';\nimport { ConnectionStatus } from './ConnectionStatus.js';\nimport type { RelayTunnel } from '../tunnel/relay.js';\nimport type { RequestLog as RequestLogType, TunnelStats } from '../types.js';\n\ninterface TunnelUIProps {\n tunnelUrl: string;\n localPort: number;\n showQR?: boolean;\n relayTunnel: RelayTunnel;\n}\n\nconst MAX_LOGS = 10;\n\nconst LOGO = `\n ╦ ╦╔╗╔╔╦╗╦ ╦╔╗╔╔╗╔╔═╗╦ ╔═╗╔╦╗ ╔╦╗╔═╗╦ ╦\n ║ ║║║║ ║ ║ ║║║║║║║║╣ ║ ║╣ ║║ ║║║╣ ╚╗╔╝\n ╚═╝╝╚╝ ╩ ╚═╝╝╚╝╝╚╝╚═╝╩═╝╚═╝═╩╝o═╩╝╚═╝ ╚╝ \n`;\n\nconst formatUptime = (ms: number) => {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n return `${hours}h ${minutes % 60}m ${seconds % 60}s`;\n};\n\nexport const TunnelUI: React.FC<TunnelUIProps> = ({\n tunnelUrl,\n localPort,\n showQR,\n relayTunnel,\n}) => {\n useApp();\n const [requests, setRequests] = useState<RequestLogType[]>([]);\n const [stats, setStats] = useState<TunnelStats>({\n total: 0,\n startTime: Date.now(),\n });\n const [relayConnected, setRelayConnected] = useState(relayTunnel.isConnected());\n const [uptime, setUptime] = useState(0);\n const [isModalOpen, setIsModalOpen] = useState(Boolean(showQR));\n\n useInput((input, key) => {\n if (input === 'q' || input === 'c' || input === 'Q' || input === 'C') {\n setIsModalOpen((prev) => !prev);\n }\n if (key.escape) {\n setIsModalOpen(false);\n }\n });\n\n useEffect(() => {\n const handleRequest = (req: RequestLogType) => {\n setRequests((prev) => [...prev.slice(-(MAX_LOGS - 1)), req]);\n setStats((prev) => ({\n ...prev,\n total: prev.total + 1,\n }));\n };\n\n const handleRelayConnected = () => setRelayConnected(true);\n const handleRelayDisconnected = () => setRelayConnected(false);\n\n relayTunnel.on('request', handleRequest);\n relayTunnel.on('connected', handleRelayConnected);\n relayTunnel.on('disconnected', handleRelayDisconnected);\n\n const uptimeTimer = setInterval(() => {\n setUptime(Date.now() - stats.startTime);\n }, 1000);\n\n return () => {\n relayTunnel.off('request', handleRequest);\n relayTunnel.off('connected', handleRelayConnected);\n relayTunnel.off('disconnected', handleRelayDisconnected);\n clearInterval(uptimeTimer);\n };\n }, [relayTunnel, stats.startTime]);\n\n const connectionStatus = relayConnected ? 'connected' : 'connecting';\n const accentColor = relayConnected ? 'cyan' : 'yellow';\n\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1}>\n {/* Header */}\n <Box flexDirection=\"column\" marginBottom={1}>\n <Text color=\"cyan\" bold>\n {LOGO}\n </Text>\n <Box justifyContent=\"space-between\" paddingX={1}>\n <ConnectionStatus status={connectionStatus} />\n <Text dimColor>v0.3.2</Text>\n </Box>\n </Box>\n\n {/* Main Connection Info */}\n <Box\n borderStyle=\"round\"\n borderColor={accentColor}\n paddingX={2}\n paddingY={1}\n flexDirection=\"column\"\n >\n <Box>\n <Text bold>Public URL: </Text>\n <Text bold color=\"black\" backgroundColor=\"cyan\">\n {' '}\n {tunnelUrl}{' '}\n </Text>\n </Box>\n <Box marginTop={1}>\n <Text dimColor>Forwarding: </Text>\n <Text color=\"yellow\">http://localhost:{localPort}</Text>\n <Text dimColor> → </Text>\n <Text color=\"cyan\">{tunnelUrl}</Text>\n </Box>\n </Box>\n\n {/* Stats Bar */}\n <Box marginTop={1} paddingX={1} gap={4}>\n <Box>\n <Text dimColor>Requests: </Text>\n <Text color=\"white\" bold>\n {stats.total}\n </Text>\n </Box>\n <Box>\n <Text dimColor>Uptime: </Text>\n <Text color=\"white\" bold>\n {formatUptime(uptime)}\n </Text>\n </Box>\n <Box>\n <Text dimColor>Latency: </Text>\n <Text color=\"white\" bold>\n {requests.length > 0 ? `${requests[requests.length - 1]?.duration}ms` : '-'}\n </Text>\n </Box>\n </Box>\n\n {/* Request Log */}\n <Box marginTop={1} flexDirection=\"column\">\n <Box marginBottom={1}>\n <Text bold color=\"cyan\">\n ─── Recent Requests ───\n </Text>\n </Box>\n <RequestLog requests={requests} />\n </Box>\n\n {/* Footer */}\n <Box\n marginTop={1}\n borderStyle=\"single\"\n borderTop={true}\n borderBottom={false}\n borderLeft={false}\n borderRight={false}\n borderColor=\"gray\"\n paddingTop={1}\n >\n <Box flexGrow={1} gap={2}>\n <Box>\n <Text dimColor>Press </Text>\n <Text color=\"yellow\" bold>\n Ctrl+C\n </Text>\n <Text dimColor> to stop</Text>\n </Box>\n <Box>\n <Text dimColor>Press </Text>\n <Text color=\"yellow\" bold>\n Q\n </Text>\n <Text dimColor> for QR</Text>\n </Box>\n </Box>\n <Box>\n <Text dimColor>Sponsor: </Text>\n <Text color=\"magenta\" bold>\n untunneled.dev/sponsor\n </Text>\n </Box>\n </Box>\n\n {isModalOpen && (\n <Box\n position=\"absolute\"\n width=\"100%\"\n height=\"100%\"\n alignItems=\"center\"\n justifyContent=\"center\"\n >\n <Box\n flexDirection=\"column\"\n alignItems=\"center\"\n borderStyle=\"round\"\n borderColor=\"cyan\"\n padding={1}\n backgroundColor=\"black\"\n >\n <Box marginBottom={1}>\n <Text bold color=\"cyan\">\n Scan to Open URL\n </Text>\n </Box>\n <QRCode url={tunnelUrl} />\n <Box marginTop={1}>\n <Text dimColor>Press </Text>\n <Text color=\"yellow\" bold>\n ESC\n </Text>\n <Text dimColor> or </Text>\n <Text color=\"yellow\" bold>\n Q\n </Text>\n <Text dimColor> to close</Text>\n </Box>\n </Box>\n </Box>\n )}\n </Box>\n );\n};\n","import React, { useState, useEffect } from 'react';\nimport { Box, Text } from 'ink';\nimport qrcode from 'qrcode-terminal';\n\ninterface QRCodeProps {\n url: string;\n}\n\nexport const QRCode: React.FC<QRCodeProps> = ({ url }) => {\n const [qrString, setQrString] = useState('');\n\n useEffect(() => {\n qrcode.generate(url, { small: true }, (qr: string) => {\n setQrString(qr);\n });\n }, [url]);\n\n if (!qrString) {\n return (\n <Box>\n <Text dimColor>Generating QR code...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" borderStyle=\"single\" paddingX={1}>\n <Text dimColor>Scan with mobile:</Text>\n <Text>{qrString}</Text>\n </Box>\n );\n};\n","import React from 'react';\nimport { Box, Text } from 'ink';\nimport type { RequestLog as RequestLogType } from '../types.js';\n\ninterface RequestLogProps {\n requests: RequestLogType[];\n}\n\nconst getStatusColor = (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 'gray';\n};\n\nconst getMethodColor = (method: string): string => {\n switch (method.toUpperCase()) {\n case 'GET':\n return 'cyan';\n case 'POST':\n return 'green';\n case 'PUT':\n return 'yellow';\n case 'DELETE':\n return 'red';\n case 'PATCH':\n return 'magenta';\n default:\n return 'white';\n }\n};\n\nexport const RequestLog: React.FC<RequestLogProps> = ({ requests }) => {\n if (requests.length === 0) {\n return (\n <Box\n borderStyle=\"single\"\n borderColor=\"gray\"\n flexDirection=\"column\"\n alignItems=\"center\"\n paddingX={3}\n paddingY={1}\n width={40}\n alignSelf=\"center\"\n >\n <Text color=\"cyan\">⏳ Waiting for requests...</Text>\n <Box height={1} />\n <Text dimColor>Open the URL in your browser</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\">\n {requests.map((req) => {\n const time = new Date(req.timestamp).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: false,\n });\n\n return (\n <Box key={req.id} gap={1}>\n <Text dimColor>[{time}]</Text>\n <Box width={8}>\n <Text color={getMethodColor(req.method)} bold>\n {req.method.padEnd(7)}\n </Text>\n </Box>\n <Box flexGrow={1}>\n <Text>{req.path.length > 40 ? req.path.substring(0, 37) + '...' : req.path}</Text>\n </Box>\n <Box width={6}>\n <Text color={getStatusColor(req.status)} bold>\n {req.status}\n </Text>\n </Box>\n <Box width={10} justifyContent=\"flex-end\">\n <Text color={req.duration > 500 ? 'yellow' : 'gray'}>\n {req.duration.toString().padStart(4)}ms\n </Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n );\n};\n","import React, { useState, useEffect } from 'react';\nimport { Box, Text } from 'ink';\nimport type { ConnectionStatus as ConnectionStatusType } from '../types.js';\n\ninterface ConnectionStatusProps {\n status: ConnectionStatusType;\n}\n\nconst SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n\nconst getStatusIndicator = (\n status: ConnectionStatusType,\n frame: string\n): { color: string; symbol: string; label: string } => {\n switch (status) {\n case 'connected':\n return { color: 'green', symbol: '✔', label: 'CONNECTED' };\n case 'connecting':\n return { color: 'yellow', symbol: frame, label: 'CONNECTING' };\n case 'disconnected':\n return { color: 'gray', symbol: '○', label: 'DISCONNECTED' };\n case 'error':\n return { color: 'red', symbol: '✘', label: 'ERROR' };\n default:\n return { color: 'white', symbol: '?', label: String(status).toUpperCase() };\n }\n};\n\nexport const ConnectionStatus: React.FC<ConnectionStatusProps> = ({ status }) => {\n const [frameIndex, setFrameIndex] = useState(0);\n\n useEffect(() => {\n if (status !== 'connecting') return;\n\n const timer = setInterval(() => {\n setFrameIndex((prev) => (prev + 1) % SPINNER_FRAMES.length);\n }, 80);\n\n return () => clearInterval(timer);\n }, [status]);\n\n const frame = SPINNER_FRAMES[frameIndex] ?? ' ';\n const indicator = getStatusIndicator(status, frame);\n\n return (\n <Box>\n <Text color={indicator.color} bold>\n {indicator.symbol} {indicator.label}\n </Text>\n </Box>\n );\n};\n","import { WebSocket } from 'ws';\nimport { EventEmitter } from 'node:events';\nimport { getRelayUrl, TIMEOUTS } from '../config.js';\nimport type {\n RelayFrame,\n WsOpen,\n WsMessage,\n WsClose,\n RelayResponse,\n RequestLog,\n} from '../types.js';\n\ninterface RelayTunnelOptions {\n localPort: number;\n tunnelId: string;\n tunnelUrl: string;\n password?: string;\n allowList?: string[];\n verbose?: boolean;\n}\n\nexport class RelayTunnel extends EventEmitter {\n private ws: WebSocket | null = null;\n private localPort: number;\n private tunnelId: string;\n private tunnelUrl: string;\n private options: RelayTunnelOptions;\n private startTime: number = 0;\n private requestCount: number = 0;\n private reconnectAttempts: number = 0;\n private isClosing: boolean = false;\n private pingInterval: NodeJS.Timeout | null = null;\n private activeWebSockets: Map<string, WebSocket> = new Map();\n\n constructor(options: RelayTunnelOptions) {\n super();\n this.options = options;\n this.localPort = options.localPort;\n this.tunnelId = options.tunnelId;\n this.tunnelUrl = options.tunnelUrl;\n }\n\n async connect(): Promise<void> {\n this.startTime = Date.now();\n this.isClosing = false;\n\n const wsUrl = getRelayUrl(this.tunnelId);\n\n if (this.options.verbose) {\n console.log(`[Relay] Connecting to ${wsUrl}`);\n }\n\n return new Promise((resolve, reject) => {\n try {\n this.ws = new WebSocket(wsUrl, {\n headers: {\n 'X-Tunnel-Auth': this.options.password || '',\n 'X-Tunnel-Allow': this.options.allowList?.join(',') || '',\n },\n });\n\n const connectionTimeout = setTimeout(() => {\n if (this.ws && this.ws.readyState !== WebSocket.OPEN) {\n this.ws.terminate();\n reject(new Error('Connection timeout'));\n }\n }, TIMEOUTS.RELAY_REQUEST);\n\n this.ws.on('open', () => {\n clearTimeout(connectionTimeout);\n this.reconnectAttempts = 0;\n\n if (this.options.verbose) {\n console.log('[Relay] Connected');\n }\n\n this.setupPing();\n this.emit('connected');\n resolve();\n });\n\n this.ws.on('error', (err) => {\n clearTimeout(connectionTimeout);\n\n if (this.options.verbose) {\n console.error('[Relay] WebSocket error:', err.message);\n }\n\n this.emit('error', err);\n reject(err);\n });\n\n this.ws.on('close', () => {\n this.clearPing();\n\n if (!this.isClosing) {\n this.emit('disconnected');\n this.attemptReconnect();\n }\n });\n\n this.ws.on('message', async (data) => {\n await this.handleRelayFrame(data);\n });\n } catch (error) {\n reject(error);\n }\n });\n }\n\n private async handleRelayFrame(data: Buffer | ArrayBuffer | Buffer[]): Promise<void> {\n let frame: RelayFrame;\n\n try {\n frame = JSON.parse(String(data)) as RelayFrame;\n } catch {\n if (this.options.verbose) {\n console.error('[Relay] Invalid frame data');\n }\n return;\n }\n\n switch (frame.type) {\n case 'http_request':\n await this.handleHttpRequest(frame);\n break;\n case 'ws_open':\n await this.handleWsOpen(frame);\n break;\n case 'ws_message':\n await this.handleWsMessage(frame);\n break;\n case 'ws_close':\n await this.handleWsClose(frame);\n break;\n }\n }\n\n private async handleHttpRequest(request: RelayFrame & { type: 'http_request' }): Promise<void> {\n const startTime = Date.now();\n\n try {\n const url = `http://127.0.0.1:${this.localPort}${request.path}`;\n\n const fetchOptions: RequestInit = {\n method: request.method,\n headers: request.headers,\n };\n\n if (request.body && !['GET', 'HEAD'].includes(request.method)) {\n fetchOptions.body = request.body;\n }\n\n const response = await fetch(url, fetchOptions);\n const body = await response.text();\n const duration = Date.now() - startTime;\n\n const relayResponse: RelayResponse = {\n type: 'http_response',\n id: request.id,\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n body,\n };\n\n this.ws?.send(JSON.stringify(relayResponse));\n\n const log: RequestLog = {\n id: request.id,\n timestamp: Date.now(),\n method: request.method,\n path: request.path,\n status: response.status,\n duration,\n };\n\n this.emit('request', log);\n this.requestCount++;\n } catch (error) {\n const err = error as any;\n\n if (this.options.verbose || err.code === 'ECONNREFUSED') {\n console.error(`[Relay] Proxy error: ${err.message}`);\n }\n\n const errorResponse: RelayResponse = {\n type: 'http_response',\n id: request.id,\n status: 502,\n statusText: 'Bad Gateway',\n headers: {},\n body: `Error: ${err.message}`,\n };\n\n this.ws?.send(JSON.stringify(errorResponse));\n }\n }\n\n private async handleWsOpen(frame: WsOpen): Promise<void> {\n const localUrl = `ws://127.0.0.1:${this.localPort}${frame.url}`;\n\n if (this.options.verbose) {\n console.log(`[Relay] Opening WebSocket to ${localUrl}`);\n }\n\n const protocolHeader =\n frame.headers['sec-websocket-protocol'] || frame.headers['Sec-WebSocket-Protocol'];\n const filteredHeaders = Object.fromEntries(\n Object.entries(frame.headers).filter(([key]) => {\n const header = key.toLowerCase();\n return (\n header !== 'connection' &&\n header !== 'upgrade' &&\n header !== 'host' &&\n !header.startsWith('sec-websocket')\n );\n })\n );\n\n const localOrigin = `http://127.0.0.1:${this.localPort}`;\n filteredHeaders['origin'] = localOrigin;\n if (filteredHeaders['referer']) {\n filteredHeaders['referer'] = `${localOrigin}${frame.url}`;\n }\n\n const localWs = protocolHeader\n ? new WebSocket(localUrl, protocolHeader, { headers: filteredHeaders })\n : new WebSocket(localUrl, { headers: filteredHeaders });\n\n this.activeWebSockets.set(frame.id, localWs);\n\n localWs.on('message', (data, isBinary) => {\n const message: WsMessage = {\n type: 'ws_message',\n id: frame.id,\n data: isBinary ? (data as Buffer).toString('base64') : data.toString(),\n isBinary,\n };\n this.ws?.send(JSON.stringify(message));\n });\n\n localWs.on('close', () => {\n const closeFrame: WsClose = {\n type: 'ws_close',\n id: frame.id,\n };\n this.ws?.send(JSON.stringify(closeFrame));\n this.activeWebSockets.delete(frame.id);\n });\n\n localWs.on('error', (err) => {\n if (this.options.verbose) {\n console.error(`[Relay] Local WebSocket error (${frame.id}):`, err.message);\n }\n localWs.close();\n });\n }\n\n private async handleWsMessage(frame: WsMessage): Promise<void> {\n const localWs = this.activeWebSockets.get(frame.id);\n if (localWs && localWs.readyState === WebSocket.OPEN) {\n const data = frame.isBinary ? Buffer.from(frame.data, 'base64') : frame.data;\n localWs.send(data);\n }\n }\n\n private async handleWsClose(frame: WsClose): Promise<void> {\n const localWs = this.activeWebSockets.get(frame.id);\n if (localWs) {\n localWs.close();\n this.activeWebSockets.delete(frame.id);\n }\n }\n\n private setupPing(): void {\n this.pingInterval = setInterval(() => {\n if (this.ws && this.ws.readyState === WebSocket.OPEN) {\n this.ws.ping();\n }\n }, TIMEOUTS.WEBSOCKET_PING);\n }\n\n private clearPing(): void {\n if (this.pingInterval) {\n clearInterval(this.pingInterval);\n this.pingInterval = null;\n }\n }\n\n private async attemptReconnect(): Promise<void> {\n if (this.isClosing) return;\n\n this.reconnectAttempts++;\n const delay = Math.min(\n TIMEOUTS.RECONNECT_BASE * Math.pow(2, this.reconnectAttempts - 1),\n TIMEOUTS.RECONNECT_MAX\n );\n\n if (this.options.verbose) {\n console.log(`[Relay] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);\n }\n\n setTimeout(async () => {\n if (this.isClosing) return;\n\n try {\n await this.connect();\n } catch (_) {}\n }, delay);\n }\n\n async disconnect(): Promise<void> {\n this.isClosing = true;\n this.clearPing();\n\n for (const ws of this.activeWebSockets.values()) {\n ws.close();\n }\n this.activeWebSockets.clear();\n\n if (this.ws) {\n this.ws.close(1000, 'Client closing');\n this.ws = null;\n }\n }\n\n getDuration(): number {\n if (this.startTime === 0) return 0;\n return Math.floor((Date.now() - this.startTime) / 1000);\n }\n\n getRequestCount(): number {\n return this.requestCount;\n }\n\n isConnected(): boolean {\n return this.ws !== null && this.ws.readyState === WebSocket.OPEN;\n }\n\n getTunnelUrl(): string {\n return this.tunnelUrl;\n }\n}\n","import { nanoid } from 'nanoid';\nimport path from 'node:path';\n\nexport const RELAY_DOMAIN = 'relay.untunneled.dev';\nexport const TUNNEL_DOMAIN = 'untunneled.dev';\n\nexport const getRelayUrl = (tunnelId: string): string => {\n const host = process.env['UNTUNNELED_RELAY_HOST'] || RELAY_DOMAIN;\n const protocol = host.includes('localhost') ? 'ws' : 'wss';\n return `${protocol}://${host}/${tunnelId}`;\n};\n\nexport const getTunnelUrl = (tunnelId: string): string => {\n const domain = process.env['UNTUNNELED_TUNNEL_DOMAIN'] || TUNNEL_DOMAIN;\n return `https://${tunnelId}.${domain}`;\n};\n\nexport function getProjectName(): string {\n const cwd = process.cwd();\n const folderName = path.basename(cwd);\n\n return (\n folderName\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, 20) || 'tunnel'\n );\n}\n\nexport function generateTunnelId(projectName?: string): string {\n const name = projectName || getProjectName();\n const suffix = nanoid(6).toLowerCase();\n return `${name}-${suffix}`;\n}\n\nexport function validatePort(port: string | number): number {\n const portNum = typeof port === 'string' ? parseInt(port, 10) : port;\n\n if (isNaN(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error(`Invalid port number: ${port}. Must be between 1 and 65535.`);\n }\n\n return portNum;\n}\n\nexport function parseAllowList(allow?: string): string[] | undefined {\n if (!allow) return undefined;\n\n return allow\n .split(',')\n .map((email) => email.trim().toLowerCase())\n .filter((email) => email.length > 0);\n}\n\nexport const TIMEOUTS = {\n RELAY_REQUEST: 30000,\n WEBSOCKET_PING: 30000,\n RECONNECT_BASE: 1000,\n RECONNECT_MAX: 30000,\n};\n","import { PostHog } from 'posthog-node';\n\nconst POSTHOG_API_KEY = process.env['POSTHOG_API_KEY'] || 'phc_placeholder';\nconst POSTHOG_HOST = 'https://app.posthog.com';\n\ninterface AnalyticsOptions {\n enabled: boolean;\n tunnelId: string;\n}\n\ntype EventName = 'tunnel_start' | 'tunnel_end' | 'request_handled';\n\ninterface EventProperties {\n tunnel_start: {\n has_auth: boolean;\n node_version: string;\n platform: string;\n };\n tunnel_end: {\n duration_seconds: number;\n requests: number;\n };\n request_handled: {\n method: string;\n status: number;\n duration_ms: number;\n };\n}\n\nexport class Analytics {\n private posthog: PostHog | null = null;\n private tunnelId: string;\n private enabled: boolean;\n\n constructor(options: AnalyticsOptions) {\n this.tunnelId = options.tunnelId;\n this.enabled = options.enabled;\n\n if (this.enabled && POSTHOG_API_KEY !== 'phc_placeholder') {\n this.posthog = new PostHog(POSTHOG_API_KEY, {\n host: POSTHOG_HOST,\n flushAt: 10,\n flushInterval: 10000,\n });\n }\n }\n\n async track<E extends EventName>(event: E, properties?: EventProperties[E]): Promise<void> {\n if (!this.posthog || !this.enabled) return;\n\n try {\n this.posthog.capture({\n distinctId: this.tunnelId,\n event,\n properties: {\n ...properties,\n version: '0.1.0',\n cli: true,\n },\n });\n } catch (_) {}\n }\n\n async shutdown(): Promise<void> {\n if (!this.posthog) return;\n\n try {\n await this.posthog.shutdown();\n } catch (_) {}\n }\n\n isEnabled(): boolean {\n return this.enabled && this.posthog !== null;\n }\n}\n","export async function listCommand(): Promise<void> {\n console.log('Active tunnels:');\n console.log(' (No active tunnels)');\n console.log('');\n console.log('Note: Tunnel list feature coming soon.');\n}\n","export async function stopCommand(tunnelId: string): Promise<void> {\n console.log(`Stopping tunnel: ${tunnelId}`);\n console.log('');\n console.log('Note: Remote stop feature coming soon.');\n}\n"],"mappings":"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","names":["React","useState","useEffect","Box","Text","Box","Text","jsx","jsxs","useState","useEffect","Box","Text","jsx","jsxs","jsx","jsxs","useState","useEffect","Box","Text","React"]}
|