untunneled.dev 0.3.5 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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.4" })
211
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "v0.3.6" })
212
212
  ] })
213
213
  ] }),
214
214
  /* @__PURE__ */ jsxs4(
@@ -330,6 +330,8 @@ import { EventEmitter } from "events";
330
330
 
331
331
  // src/config.ts
332
332
  import { nanoid } from "nanoid";
333
+ import { execSync } from "child_process";
334
+ import os from "os";
333
335
  import path from "path";
334
336
  var RELAY_DOMAIN = "relay.untunneled.dev";
335
337
  var TUNNEL_DOMAIN = "untunneled.dev";
@@ -342,15 +344,84 @@ var getTunnelUrl = (tunnelId) => {
342
344
  const domain = process.env["UNTUNNELED_TUNNEL_DOMAIN"] || TUNNEL_DOMAIN;
343
345
  return `https://${tunnelId}.${domain}`;
344
346
  };
347
+ var slugifyName = (value, maxLength = 20) => {
348
+ return value.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, maxLength);
349
+ };
350
+ var getGitRepoName = () => {
351
+ try {
352
+ const root = execSync("git rev-parse --show-toplevel", {
353
+ cwd: process.cwd(),
354
+ stdio: ["ignore", "pipe", "ignore"]
355
+ }).toString().trim();
356
+ return root ? path.basename(root) : null;
357
+ } catch {
358
+ return null;
359
+ }
360
+ };
361
+ var getGitUserName = () => {
362
+ try {
363
+ const name = execSync("git config user.name", {
364
+ cwd: process.cwd(),
365
+ stdio: ["ignore", "pipe", "ignore"]
366
+ }).toString().trim();
367
+ if (name) return name;
368
+ const email = execSync("git config user.email", {
369
+ cwd: process.cwd(),
370
+ stdio: ["ignore", "pipe", "ignore"]
371
+ }).toString().trim();
372
+ if (!email) return null;
373
+ const [user] = email.split("@");
374
+ return user || null;
375
+ } catch {
376
+ return null;
377
+ }
378
+ };
379
+ var getLocalUsername = () => {
380
+ try {
381
+ return os.userInfo().username || null;
382
+ } catch {
383
+ return null;
384
+ }
385
+ };
386
+ var addCandidate = (candidates, value) => {
387
+ if (!value) return;
388
+ const slug = slugifyName(value);
389
+ if (!slug || candidates.includes(slug)) return;
390
+ candidates.push(slug);
391
+ };
345
392
  function getProjectName() {
346
393
  const cwd = process.cwd();
347
394
  const folderName = path.basename(cwd);
348
- return folderName.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 20) || "tunnel";
395
+ return slugifyName(folderName) || "tunnel";
349
396
  }
350
- function generateTunnelId(projectName) {
351
- const name = projectName || getProjectName();
352
- const suffix = nanoid(6).toLowerCase();
353
- return `${name}-${suffix}`;
397
+ function getTunnelIdCandidates(options) {
398
+ const candidates = [];
399
+ if (options?.name) {
400
+ addCandidate(candidates, options.name);
401
+ const base = candidates[0];
402
+ if (base) {
403
+ candidates.push(`${base}-${nanoid(6).toLowerCase()}`);
404
+ return candidates;
405
+ }
406
+ }
407
+ const gitRepoName = getGitRepoName();
408
+ const directoryName = getProjectName();
409
+ const gitUserName = getGitUserName();
410
+ const localUserName = getLocalUsername();
411
+ addCandidate(candidates, gitRepoName);
412
+ addCandidate(candidates, directoryName);
413
+ if (gitUserName && gitRepoName) {
414
+ addCandidate(candidates, `${gitUserName}-${gitRepoName}`);
415
+ }
416
+ if (gitUserName && directoryName) {
417
+ addCandidate(candidates, `${gitUserName}-${directoryName}`);
418
+ }
419
+ if (localUserName && directoryName) {
420
+ addCandidate(candidates, `${localUserName}-${directoryName}`);
421
+ }
422
+ const fallbackBase = directoryName || gitRepoName || "tunnel";
423
+ candidates.push(`${fallbackBase}-${nanoid(6).toLowerCase()}`);
424
+ return candidates;
354
425
  }
355
426
  function validatePort(port) {
356
427
  const portNum = typeof port === "string" ? parseInt(port, 10) : port;
@@ -398,6 +469,17 @@ var RelayTunnel = class extends EventEmitter {
398
469
  console.log(`[Relay] Connecting to ${wsUrl}`);
399
470
  }
400
471
  return new Promise((resolve, reject) => {
472
+ let didOpen = false;
473
+ let settled = false;
474
+ const finish = (error) => {
475
+ if (settled) return;
476
+ settled = true;
477
+ if (error) {
478
+ reject(error);
479
+ } else {
480
+ resolve();
481
+ }
482
+ };
401
483
  try {
402
484
  this.ws = new WebSocket(wsUrl, {
403
485
  headers: {
@@ -406,19 +488,21 @@ var RelayTunnel = class extends EventEmitter {
406
488
  });
407
489
  const connectionTimeout = setTimeout(() => {
408
490
  if (this.ws && this.ws.readyState !== WebSocket.OPEN) {
491
+ this.isClosing = true;
409
492
  this.ws.terminate();
410
- reject(new Error("Connection timeout"));
493
+ finish(new Error("Connection timeout"));
411
494
  }
412
495
  }, TIMEOUTS.RELAY_REQUEST);
413
496
  this.ws.on("open", () => {
414
497
  clearTimeout(connectionTimeout);
415
498
  this.reconnectAttempts = 0;
499
+ didOpen = true;
416
500
  if (this.options.verbose) {
417
501
  console.log("[Relay] Connected");
418
502
  }
419
503
  this.setupPing();
420
504
  this.emit("connected");
421
- resolve();
505
+ finish();
422
506
  });
423
507
  this.ws.on("error", (err) => {
424
508
  clearTimeout(connectionTimeout);
@@ -426,11 +510,25 @@ var RelayTunnel = class extends EventEmitter {
426
510
  console.error("[Relay] WebSocket error:", err.message);
427
511
  }
428
512
  this.emit("error", err);
429
- reject(err);
513
+ if (!didOpen) {
514
+ finish(err);
515
+ }
516
+ });
517
+ this.ws.on("unexpected-response", (_request, response) => {
518
+ clearTimeout(connectionTimeout);
519
+ const error = new Error(
520
+ response.statusCode === 409 ? "Tunnel ID already in use" : `Unexpected response: ${response.statusCode || "unknown"}`
521
+ );
522
+ if (response.statusCode === 409) {
523
+ error.code = "TUNNEL_ID_IN_USE";
524
+ }
525
+ this.isClosing = true;
526
+ this.ws?.terminate();
527
+ finish(error);
430
528
  });
431
529
  this.ws.on("close", () => {
432
530
  this.clearPing();
433
- if (!this.isClosing) {
531
+ if (!this.isClosing && didOpen) {
434
532
  this.emit("disconnected");
435
533
  this.attemptReconnect();
436
534
  }
@@ -510,13 +608,14 @@ var RelayTunnel = class extends EventEmitter {
510
608
  if (this.options.verbose || err.code === "ECONNREFUSED") {
511
609
  console.error(`[Relay] Proxy error: ${err.message}`);
512
610
  }
611
+ const friendlyMessage = err.message === "fetch failed" || err.code === "ECONNREFUSED" ? "Tunnel is offline. Restart the CLI to reconnect." : `Error: ${err.message}`;
513
612
  const errorResponse = {
514
613
  type: "http_response",
515
614
  id: request.id,
516
615
  status: 502,
517
616
  statusText: "Bad Gateway",
518
617
  headers: {},
519
- body: `Error: ${err.message}`
618
+ body: friendlyMessage
520
619
  };
521
620
  this.recordMetrics(duration, 502, requestBytes, 0);
522
621
  this.ws?.send(JSON.stringify(errorResponse));
@@ -681,10 +780,11 @@ var RelayTunnel = class extends EventEmitter {
681
780
  import { PostHog } from "posthog-node";
682
781
  import crypto from "crypto";
683
782
  import fs from "fs";
684
- import os from "os";
783
+ import os2 from "os";
685
784
  import path2 from "path";
686
785
  var POSTHOG_API_KEY = "phc_oB9sTloW28uJQ7HGYDRZRQ23JaQApZ57LrrPFVd796S";
687
786
  var POSTHOG_HOST = "https://us.i.posthog.com";
787
+ var TELEMETRY_DEBUG = process.env["UNTUNNELED_TELEMETRY_DEBUG"] === "1" || process.env["UNTUNNELED_TELEMETRY_DEBUG"] === "true";
688
788
  var Analytics = class {
689
789
  posthog = null;
690
790
  enabled;
@@ -698,6 +798,17 @@ var Analytics = class {
698
798
  flushAt: 10,
699
799
  flushInterval: 1e4
700
800
  });
801
+ if (TELEMETRY_DEBUG) {
802
+ console.log("[Telemetry] Enabled", {
803
+ host: POSTHOG_HOST,
804
+ keyPrefix: POSTHOG_API_KEY.slice(0, 6)
805
+ });
806
+ }
807
+ } else if (TELEMETRY_DEBUG) {
808
+ console.log("[Telemetry] Disabled", {
809
+ enabled: this.enabled,
810
+ hasKey: POSTHOG_API_KEY !== "phc_placeholder"
811
+ });
701
812
  }
702
813
  }
703
814
  async track(event, properties) {
@@ -708,16 +819,23 @@ var Analytics = class {
708
819
  event,
709
820
  properties: {
710
821
  ...properties,
711
- version: "0.3.5",
822
+ version: "0.3.10",
712
823
  cli: true
713
824
  }
714
825
  });
826
+ if (TELEMETRY_DEBUG) {
827
+ console.log("[Telemetry] Captured", { event });
828
+ }
715
829
  } catch (_) {
716
830
  }
717
831
  }
718
832
  async shutdown() {
719
833
  if (!this.posthog) return;
720
834
  try {
835
+ if (TELEMETRY_DEBUG) {
836
+ console.log("[Telemetry] Flushing");
837
+ }
838
+ await this.posthog.flush();
721
839
  await this.posthog.shutdown();
722
840
  } catch (_) {
723
841
  }
@@ -732,7 +850,7 @@ var Analytics = class {
732
850
  return crypto.createHash("sha256").update(rotationKey).digest("hex").slice(0, 32);
733
851
  }
734
852
  getAnonymousId() {
735
- const baseDir = process.env["XDG_CONFIG_HOME"] || process.env["APPDATA"] || path2.join(os.homedir(), ".config");
853
+ const baseDir = process.env["XDG_CONFIG_HOME"] || process.env["APPDATA"] || path2.join(os2.homedir(), ".config");
736
854
  const configDir = path2.join(baseDir, "untunneled.dev");
737
855
  const idPath = path2.join(configDir, "anon_id");
738
856
  try {
@@ -761,9 +879,7 @@ async function startCommand(port, options) {
761
879
  console.error(error.message);
762
880
  process.exit(1);
763
881
  }
764
- const projectName = getProjectName();
765
- const tunnelId = generateTunnelId(projectName);
766
- const tunnelUrl = getTunnelUrl(tunnelId);
882
+ const tunnelIdCandidates = getTunnelIdCandidates({ name: options.name });
767
883
  const analytics = new Analytics({
768
884
  enabled: options.telemetry !== false
769
885
  });
@@ -772,52 +888,80 @@ async function startCommand(port, options) {
772
888
  node_version: process.version,
773
889
  platform: process.platform
774
890
  });
775
- const relayTunnel = new RelayTunnel({
776
- localPort,
777
- tunnelId,
778
- tunnelUrl,
779
- password: options.password,
780
- verbose: options.verbose
781
- });
782
- try {
783
- await relayTunnel.connect();
784
- if (options.verbose) {
785
- console.log(`Tunnel URL: ${tunnelUrl}`);
786
- console.log(`Forwarding to localhost:${localPort}`);
891
+ let relayTunnel = null;
892
+ let tunnelId = "";
893
+ let tunnelUrl = "";
894
+ for (const candidate of tunnelIdCandidates) {
895
+ tunnelId = candidate;
896
+ tunnelUrl = getTunnelUrl(tunnelId);
897
+ relayTunnel = new RelayTunnel({
898
+ localPort,
899
+ tunnelId,
900
+ tunnelUrl,
901
+ password: options.password,
902
+ verbose: options.verbose
903
+ });
904
+ try {
905
+ await relayTunnel.connect();
906
+ break;
907
+ } catch (error) {
908
+ const err = error;
909
+ if (err.code === "TUNNEL_ID_IN_USE") {
910
+ await relayTunnel.disconnect();
911
+ relayTunnel = null;
912
+ continue;
913
+ }
914
+ console.error("Failed to connect to relay server:", err.message);
915
+ await analytics.shutdown();
916
+ process.exit(1);
787
917
  }
788
- } catch (error) {
789
- console.error("Failed to connect to relay server:", error.message);
918
+ }
919
+ if (!relayTunnel) {
920
+ console.error("All candidate tunnel URLs are currently in use. Try again or pass --name.");
790
921
  await analytics.shutdown();
791
922
  process.exit(1);
792
923
  }
924
+ const activeTunnel = relayTunnel;
925
+ if (options.verbose) {
926
+ console.log(`Tunnel URL: ${tunnelUrl}`);
927
+ console.log(`Forwarding to localhost:${localPort}`);
928
+ }
793
929
  const { waitUntilExit } = render(
794
930
  React5.createElement(TunnelUI, {
795
931
  tunnelUrl,
796
932
  localPort,
797
933
  showQR: options.qr,
798
- relayTunnel
934
+ relayTunnel: activeTunnel
799
935
  })
800
936
  );
801
937
  const cleanup = async () => {
802
- const duration = relayTunnel.getDuration();
803
- const requests = relayTunnel.getRequestCount();
938
+ const duration = activeTunnel.getDuration();
939
+ const requests = activeTunnel.getRequestCount();
804
940
  await analytics.track("tunnel_end", {
805
941
  duration_seconds: duration,
806
942
  requests
807
943
  });
808
- const summary = relayTunnel.getMetricsSummary();
944
+ const summary = activeTunnel.getMetricsSummary();
809
945
  await analytics.track("session_summary", summary);
810
- await relayTunnel.disconnect();
946
+ await activeTunnel.disconnect();
811
947
  await analytics.shutdown();
812
948
  };
813
- process.on("SIGINT", async () => {
814
- await cleanup();
815
- process.exit(0);
816
- });
817
- process.on("SIGTERM", async () => {
818
- await cleanup();
819
- process.exit(0);
820
- });
949
+ let isShuttingDown = false;
950
+ const handleShutdown = async () => {
951
+ if (isShuttingDown) return;
952
+ isShuttingDown = true;
953
+ const timeout = setTimeout(() => {
954
+ process.exit(0);
955
+ }, 2e3);
956
+ try {
957
+ await cleanup();
958
+ } finally {
959
+ clearTimeout(timeout);
960
+ process.exit(0);
961
+ }
962
+ };
963
+ process.once("SIGINT", handleShutdown);
964
+ process.once("SIGTERM", handleShutdown);
821
965
  await waitUntilExit();
822
966
  }
823
967
 
@@ -838,8 +982,8 @@ async function stopCommand(tunnelId) {
838
982
 
839
983
  // src/index.ts
840
984
  var program = new Command();
841
- program.name("untunneled").description("Fast, free localhost tunneling").version("0.3.5");
842
- program.argument("<port>", "Local port to tunnel").option("--qr", "Open the QR code modal on start").option("--password <password>", "Password protect the tunnel").option("--verbose", "Show detailed logs").option("--no-telemetry", "Disable anonymous usage tracking").action(startCommand);
985
+ program.name("untunneled").description("Fast, free localhost tunneling").version("0.3.10");
986
+ program.argument("<port>", "Local port to tunnel").option("--qr", "Open the QR code modal on start").option("--name <name>", "Set a custom tunnel subdomain").option("--password <password>", "Password protect the tunnel").option("--verbose", "Show detailed logs").option("--no-telemetry", "Disable anonymous usage tracking").action(startCommand);
843
987
  program.command("list").description("List active tunnels").action(listCommand);
844
988
  program.command("stop").argument("<tunnel-id>", "Tunnel ID to stop").description("Stop a running tunnel").action(stopCommand);
845
989
  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\n .name('untunneled')\n .description('Fast, free localhost tunneling')\n .version(process.env.UNTUNNELED_VERSION || '0.3.5');\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('--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 { generateTunnelId, getProjectName, getTunnelUrl, validatePort } 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 });\n\n await analytics.track('tunnel_start', {\n has_auth: !!options.password,\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 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 const summary = relayTunnel.getMetricsSummary();\n await analytics.track('session_summary', summary);\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.4</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 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 bytesIn: number = 0;\n private bytesOut: number = 0;\n private statusCounts = { ok: 0, clientError: 0, serverError: 0, other: 0 };\n private latencySamples: number[] = [];\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 },\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 const requestBytes = request.body ? Buffer.byteLength(request.body, 'utf8') : 0;\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 const responseBytes = Buffer.byteLength(body, 'utf8');\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.recordMetrics(duration, response.status, requestBytes, responseBytes);\n\n this.emit('request', log);\n this.requestCount++;\n } catch (error) {\n const err = error as any;\n const duration = Date.now() - startTime;\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.recordMetrics(duration, 502, requestBytes, 0);\n\n this.ws?.send(JSON.stringify(errorResponse));\n }\n }\n\n private recordMetrics(duration: number, status: number, bytesIn: number, bytesOut: number): void {\n this.bytesIn += bytesIn;\n this.bytesOut += bytesOut;\n\n if (status >= 200 && status < 300) this.statusCounts.ok += 1;\n else if (status >= 400 && status < 500) this.statusCounts.clientError += 1;\n else if (status >= 500 && status < 600) this.statusCounts.serverError += 1;\n else this.statusCounts.other += 1;\n\n if (this.latencySamples.length < 500) {\n this.latencySamples.push(duration);\n } else {\n const total = this.requestCount + 1;\n const index = Math.floor(Math.random() * total);\n if (index < this.latencySamples.length) {\n this.latencySamples[index] = duration;\n }\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 protocols = protocolHeader\n ? protocolHeader\n .split(',')\n .map((value) => value.trim())\n .filter(Boolean)\n : undefined;\n\n const localWs = protocols?.length\n ? new WebSocket(localUrl, protocols, { 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 getMetricsSummary(): {\n total_requests: number;\n total_bytes_in: number;\n total_bytes_out: number;\n latency_avg_ms: number;\n latency_p50_ms: number;\n latency_p90_ms: number;\n latency_p99_ms: number;\n status_2xx: number;\n status_4xx: number;\n status_5xx: number;\n status_other: number;\n } {\n const samples = [...this.latencySamples].sort((a, b) => a - b);\n const avg = samples.length\n ? Math.round(samples.reduce((sum, value) => sum + value, 0) / samples.length)\n : 0;\n\n const percentile = (p: number) => {\n if (samples.length === 0) return 0;\n const index = Math.min(samples.length - 1, Math.floor(p * (samples.length - 1)));\n return samples[index] ?? 0;\n };\n\n return {\n total_requests: this.requestCount,\n total_bytes_in: this.bytesIn,\n total_bytes_out: this.bytesOut,\n latency_avg_ms: avg,\n latency_p50_ms: percentile(0.5),\n latency_p90_ms: percentile(0.9),\n latency_p99_ms: percentile(0.99),\n status_2xx: this.statusCounts.ok,\n status_4xx: this.statusCounts.clientError,\n status_5xx: this.statusCounts.serverError,\n status_other: this.statusCounts.other,\n };\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 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';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nconst POSTHOG_API_KEY = process.env.POSTHOG_API_KEY || 'phc_placeholder';\nconst POSTHOG_HOST = process.env.POSTHOG_HOST || 'https://app.posthog.com';\n\ninterface AnalyticsOptions {\n enabled: boolean;\n}\n\ntype EventName = 'tunnel_start' | 'tunnel_end' | 'session_summary';\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 session_summary: {\n total_requests: number;\n total_bytes_in: number;\n total_bytes_out: number;\n latency_avg_ms: number;\n latency_p50_ms: number;\n latency_p90_ms: number;\n latency_p99_ms: number;\n status_2xx: number;\n status_4xx: number;\n status_5xx: number;\n status_other: number;\n };\n}\n\nexport class Analytics {\n private posthog: PostHog | null = null;\n private enabled: boolean;\n private distinctId: string;\n\n constructor(options: AnalyticsOptions) {\n this.enabled = options.enabled;\n this.distinctId = this.getRotatingDistinctId();\n\n if (this.enabled && POSTHOG_API_KEY && 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.distinctId,\n event,\n properties: {\n ...properties,\n version: process.env.UNTUNNELED_VERSION || '0.3.5',\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 private getRotatingDistinctId(): string {\n const anonId = this.getAnonymousId();\n const now = new Date();\n const rotationKey = `${anonId}:${now.getUTCFullYear()}-${now.getUTCMonth() + 1}`;\n return crypto.createHash('sha256').update(rotationKey).digest('hex').slice(0, 32);\n }\n\n private getAnonymousId(): string {\n const baseDir =\n process.env['XDG_CONFIG_HOME'] ||\n process.env['APPDATA'] ||\n path.join(os.homedir(), '.config');\n const configDir = path.join(baseDir, 'untunneled.dev');\n const idPath = path.join(configDir, 'anon_id');\n\n try {\n if (!fs.existsSync(configDir)) {\n fs.mkdirSync(configDir, { recursive: true });\n }\n\n if (fs.existsSync(idPath)) {\n const existing = fs.readFileSync(idPath, 'utf8').trim();\n if (existing) return existing;\n }\n\n const newId = crypto.randomUUID();\n fs.writeFileSync(idPath, newId, 'utf8');\n return newId;\n } catch {\n return crypto.randomUUID();\n }\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","path","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\n .name('untunneled')\n .description('Fast, free localhost tunneling')\n .version(process.env.UNTUNNELED_VERSION || '0.3.10');\n\nprogram\n .argument('<port>', 'Local port to tunnel')\n .option('--qr', 'Open the QR code modal on start')\n .option('--name <name>', 'Set a custom tunnel subdomain')\n .option('--password <password>', 'Password protect the tunnel')\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 { getTunnelIdCandidates, getTunnelUrl, validatePort } 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 tunnelIdCandidates = getTunnelIdCandidates({ name: options.name });\n\n const analytics = new Analytics({\n enabled: options.telemetry !== false,\n });\n\n await analytics.track('tunnel_start', {\n has_auth: !!options.password,\n node_version: process.version,\n platform: process.platform,\n });\n\n let relayTunnel: RelayTunnel | null = null;\n let tunnelId = '';\n let tunnelUrl = '';\n\n for (const candidate of tunnelIdCandidates) {\n tunnelId = candidate;\n tunnelUrl = getTunnelUrl(tunnelId);\n relayTunnel = new RelayTunnel({\n localPort,\n tunnelId,\n tunnelUrl,\n password: options.password,\n verbose: options.verbose,\n });\n\n try {\n await relayTunnel.connect();\n break;\n } catch (error) {\n const err = error as NodeJS.ErrnoException;\n if (err.code === 'TUNNEL_ID_IN_USE') {\n await relayTunnel.disconnect();\n relayTunnel = null;\n continue;\n }\n\n console.error('Failed to connect to relay server:', err.message);\n await analytics.shutdown();\n process.exit(1);\n }\n }\n\n if (!relayTunnel) {\n console.error('All candidate tunnel URLs are currently in use. Try again or pass --name.');\n await analytics.shutdown();\n process.exit(1);\n }\n\n const activeTunnel = relayTunnel;\n\n if (options.verbose) {\n console.log(`Tunnel URL: ${tunnelUrl}`);\n console.log(`Forwarding to localhost:${localPort}`);\n }\n\n const { waitUntilExit } = render(\n React.createElement(TunnelUI, {\n tunnelUrl,\n localPort,\n showQR: options.qr,\n relayTunnel: activeTunnel,\n })\n );\n\n const cleanup = async () => {\n const duration = activeTunnel.getDuration();\n const requests = activeTunnel.getRequestCount();\n\n await analytics.track('tunnel_end', {\n duration_seconds: duration,\n requests: requests,\n });\n\n const summary = activeTunnel.getMetricsSummary();\n await analytics.track('session_summary', summary);\n\n await activeTunnel.disconnect();\n await analytics.shutdown();\n };\n\n let isShuttingDown = false;\n const handleShutdown = async () => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n\n const timeout = setTimeout(() => {\n process.exit(0);\n }, 2000);\n\n try {\n await cleanup();\n } finally {\n clearTimeout(timeout);\n process.exit(0);\n }\n };\n\n process.once('SIGINT', handleShutdown);\n process.once('SIGTERM', handleShutdown);\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.6</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 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 bytesIn: number = 0;\n private bytesOut: number = 0;\n private statusCounts = { ok: 0, clientError: 0, serverError: 0, other: 0 };\n private latencySamples: number[] = [];\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 let didOpen = false;\n let settled = false;\n const finish = (error?: Error) => {\n if (settled) return;\n settled = true;\n if (error) {\n reject(error);\n } else {\n resolve();\n }\n };\n\n try {\n this.ws = new WebSocket(wsUrl, {\n headers: {\n 'X-Tunnel-Auth': this.options.password || '',\n },\n });\n\n const connectionTimeout = setTimeout(() => {\n if (this.ws && this.ws.readyState !== WebSocket.OPEN) {\n this.isClosing = true;\n this.ws.terminate();\n finish(new Error('Connection timeout'));\n }\n }, TIMEOUTS.RELAY_REQUEST);\n\n this.ws.on('open', () => {\n clearTimeout(connectionTimeout);\n this.reconnectAttempts = 0;\n didOpen = true;\n\n if (this.options.verbose) {\n console.log('[Relay] Connected');\n }\n\n this.setupPing();\n this.emit('connected');\n finish();\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 if (!didOpen) {\n finish(err);\n }\n });\n\n this.ws.on('unexpected-response', (_request, response) => {\n clearTimeout(connectionTimeout);\n\n const error = new Error(\n response.statusCode === 409\n ? 'Tunnel ID already in use'\n : `Unexpected response: ${response.statusCode || 'unknown'}`\n ) as NodeJS.ErrnoException;\n\n if (response.statusCode === 409) {\n error.code = 'TUNNEL_ID_IN_USE';\n }\n\n this.isClosing = true;\n this.ws?.terminate();\n finish(error);\n });\n\n this.ws.on('close', () => {\n this.clearPing();\n\n if (!this.isClosing && didOpen) {\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 const requestBytes = request.body ? Buffer.byteLength(request.body, 'utf8') : 0;\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 const responseBytes = Buffer.byteLength(body, 'utf8');\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.recordMetrics(duration, response.status, requestBytes, responseBytes);\n\n this.emit('request', log);\n this.requestCount++;\n } catch (error) {\n const err = error as NodeJS.ErrnoException;\n const duration = Date.now() - startTime;\n\n if (this.options.verbose || err.code === 'ECONNREFUSED') {\n console.error(`[Relay] Proxy error: ${err.message}`);\n }\n\n const friendlyMessage =\n err.message === 'fetch failed' || err.code === 'ECONNREFUSED'\n ? 'Tunnel is offline. Restart the CLI to reconnect.'\n : `Error: ${err.message}`;\n\n const errorResponse: RelayResponse = {\n type: 'http_response',\n id: request.id,\n status: 502,\n statusText: 'Bad Gateway',\n headers: {},\n body: friendlyMessage,\n };\n\n this.recordMetrics(duration, 502, requestBytes, 0);\n\n this.ws?.send(JSON.stringify(errorResponse));\n }\n }\n\n private recordMetrics(duration: number, status: number, bytesIn: number, bytesOut: number): void {\n this.bytesIn += bytesIn;\n this.bytesOut += bytesOut;\n\n if (status >= 200 && status < 300) this.statusCounts.ok += 1;\n else if (status >= 400 && status < 500) this.statusCounts.clientError += 1;\n else if (status >= 500 && status < 600) this.statusCounts.serverError += 1;\n else this.statusCounts.other += 1;\n\n if (this.latencySamples.length < 500) {\n this.latencySamples.push(duration);\n } else {\n const total = this.requestCount + 1;\n const index = Math.floor(Math.random() * total);\n if (index < this.latencySamples.length) {\n this.latencySamples[index] = duration;\n }\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 protocols = protocolHeader\n ? protocolHeader\n .split(',')\n .map((value) => value.trim())\n .filter(Boolean)\n : undefined;\n\n const localWs = protocols?.length\n ? new WebSocket(localUrl, protocols, { 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 getMetricsSummary(): {\n total_requests: number;\n total_bytes_in: number;\n total_bytes_out: number;\n latency_avg_ms: number;\n latency_p50_ms: number;\n latency_p90_ms: number;\n latency_p99_ms: number;\n status_2xx: number;\n status_4xx: number;\n status_5xx: number;\n status_other: number;\n } {\n const samples = [...this.latencySamples].sort((a, b) => a - b);\n const avg = samples.length\n ? Math.round(samples.reduce((sum, value) => sum + value, 0) / samples.length)\n : 0;\n\n const percentile = (p: number) => {\n if (samples.length === 0) return 0;\n const index = Math.min(samples.length - 1, Math.floor(p * (samples.length - 1)));\n return samples[index] ?? 0;\n };\n\n return {\n total_requests: this.requestCount,\n total_bytes_in: this.bytesIn,\n total_bytes_out: this.bytesOut,\n latency_avg_ms: avg,\n latency_p50_ms: percentile(0.5),\n latency_p90_ms: percentile(0.9),\n latency_p99_ms: percentile(0.99),\n status_2xx: this.statusCounts.ok,\n status_4xx: this.statusCounts.clientError,\n status_5xx: this.statusCounts.serverError,\n status_other: this.statusCounts.other,\n };\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 { execSync } from 'node:child_process';\nimport os from 'node:os';\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\nconst slugifyName = (value: string, maxLength = 20): string => {\n return value\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, maxLength);\n};\n\nconst getGitRepoName = (): string | null => {\n try {\n const root = execSync('git rev-parse --show-toplevel', {\n cwd: process.cwd(),\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString()\n .trim();\n return root ? path.basename(root) : null;\n } catch {\n return null;\n }\n};\n\nconst getGitUserName = (): string | null => {\n try {\n const name = execSync('git config user.name', {\n cwd: process.cwd(),\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString()\n .trim();\n if (name) return name;\n\n const email = execSync('git config user.email', {\n cwd: process.cwd(),\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString()\n .trim();\n if (!email) return null;\n const [user] = email.split('@');\n return user || null;\n } catch {\n return null;\n }\n};\n\nconst getLocalUsername = (): string | null => {\n try {\n return os.userInfo().username || null;\n } catch {\n return null;\n }\n};\n\nconst addCandidate = (candidates: string[], value?: string | null): void => {\n if (!value) return;\n const slug = slugifyName(value);\n if (!slug || candidates.includes(slug)) return;\n candidates.push(slug);\n};\n\nexport function getProjectName(): string {\n const cwd = process.cwd();\n const folderName = path.basename(cwd);\n\n return slugifyName(folderName) || 'tunnel';\n}\n\nexport function getTunnelIdCandidates(options?: { name?: string }): string[] {\n const candidates: string[] = [];\n\n if (options?.name) {\n addCandidate(candidates, options.name);\n const base = candidates[0];\n if (base) {\n candidates.push(`${base}-${nanoid(6).toLowerCase()}`);\n return candidates;\n }\n }\n\n const gitRepoName = getGitRepoName();\n const directoryName = getProjectName();\n const gitUserName = getGitUserName();\n const localUserName = getLocalUsername();\n\n addCandidate(candidates, gitRepoName);\n addCandidate(candidates, directoryName);\n\n if (gitUserName && gitRepoName) {\n addCandidate(candidates, `${gitUserName}-${gitRepoName}`);\n }\n\n if (gitUserName && directoryName) {\n addCandidate(candidates, `${gitUserName}-${directoryName}`);\n }\n\n if (localUserName && directoryName) {\n addCandidate(candidates, `${localUserName}-${directoryName}`);\n }\n\n const fallbackBase = directoryName || gitRepoName || 'tunnel';\n candidates.push(`${fallbackBase}-${nanoid(6).toLowerCase()}`);\n return candidates;\n}\n\nexport function generateTunnelId(projectName?: string): string {\n const name = projectName ? slugifyName(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 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';\nimport crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nconst POSTHOG_API_KEY = process.env.POSTHOG_API_KEY || 'phc_placeholder';\nconst POSTHOG_HOST = process.env.POSTHOG_HOST || 'https://app.posthog.com';\nconst TELEMETRY_DEBUG =\n process.env['UNTUNNELED_TELEMETRY_DEBUG'] === '1' ||\n process.env['UNTUNNELED_TELEMETRY_DEBUG'] === 'true';\n\ninterface AnalyticsOptions {\n enabled: boolean;\n}\n\ntype EventName = 'tunnel_start' | 'tunnel_end' | 'session_summary';\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 session_summary: {\n total_requests: number;\n total_bytes_in: number;\n total_bytes_out: number;\n latency_avg_ms: number;\n latency_p50_ms: number;\n latency_p90_ms: number;\n latency_p99_ms: number;\n status_2xx: number;\n status_4xx: number;\n status_5xx: number;\n status_other: number;\n };\n}\n\nexport class Analytics {\n private posthog: PostHog | null = null;\n private enabled: boolean;\n private distinctId: string;\n\n constructor(options: AnalyticsOptions) {\n this.enabled = options.enabled;\n this.distinctId = this.getRotatingDistinctId();\n\n if (this.enabled && POSTHOG_API_KEY && 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 if (TELEMETRY_DEBUG) {\n console.log('[Telemetry] Enabled', {\n host: POSTHOG_HOST,\n keyPrefix: POSTHOG_API_KEY.slice(0, 6),\n });\n }\n } else if (TELEMETRY_DEBUG) {\n console.log('[Telemetry] Disabled', {\n enabled: this.enabled,\n hasKey: POSTHOG_API_KEY !== 'phc_placeholder',\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.distinctId,\n event,\n properties: {\n ...properties,\n version: process.env.UNTUNNELED_VERSION || '0.3.10',\n cli: true,\n },\n });\n if (TELEMETRY_DEBUG) {\n console.log('[Telemetry] Captured', { event });\n }\n } catch (_) {}\n }\n\n async shutdown(): Promise<void> {\n if (!this.posthog) return;\n\n try {\n if (TELEMETRY_DEBUG) {\n console.log('[Telemetry] Flushing');\n }\n await this.posthog.flush();\n await this.posthog.shutdown();\n } catch (_) {}\n }\n\n isEnabled(): boolean {\n return this.enabled && this.posthog !== null;\n }\n\n private getRotatingDistinctId(): string {\n const anonId = this.getAnonymousId();\n const now = new Date();\n const rotationKey = `${anonId}:${now.getUTCFullYear()}-${now.getUTCMonth() + 1}`;\n return crypto.createHash('sha256').update(rotationKey).digest('hex').slice(0, 32);\n }\n\n private getAnonymousId(): string {\n const baseDir =\n process.env['XDG_CONFIG_HOME'] ||\n process.env['APPDATA'] ||\n path.join(os.homedir(), '.config');\n const configDir = path.join(baseDir, 'untunneled.dev');\n const idPath = path.join(configDir, 'anon_id');\n\n try {\n if (!fs.existsSync(configDir)) {\n fs.mkdirSync(configDir, { recursive: true });\n }\n\n if (fs.existsSync(idPath)) {\n const existing = fs.readFileSync(idPath, 'utf8').trim();\n if (existing) return existing;\n }\n\n const newId = crypto.randomUUID();\n fs.writeFileSync(idPath, newId, 'utf8');\n return newId;\n } catch {\n return crypto.randomUUID();\n }\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,QAAQ,MAAM,CAAC;AAE9D,WAAS,CAAC,OAAO,QAAQ;AACvB,QAAI,UAAU,OAAO,UAAU,OAAO,UAAU,OAAO,UAAU,KAAK;AACpE,qBAAe,CAAC,SAAS,CAAC,IAAI;AAAA,IAChC;AACA,QAAI,IAAI,QAAQ;AACd,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,IAGA,gBAAAJ,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;;;AInOA,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;;;ACD7B,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,OAAO,QAAQ;AACf,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;AAEA,IAAM,cAAc,CAAC,OAAe,YAAY,OAAe;AAC7D,SAAO,MACJ,YAAY,EACZ,QAAQ,eAAe,GAAG,EAC1B,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EAAE,EACpB,MAAM,GAAG,SAAS;AACvB;AAEA,IAAM,iBAAiB,MAAqB;AAC1C,MAAI;AACF,UAAM,OAAO,SAAS,iCAAiC;AAAA,MACrD,KAAK,QAAQ,IAAI;AAAA,MACjB,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,IACpC,CAAC,EACE,SAAS,EACT,KAAK;AACR,WAAO,OAAO,KAAK,SAAS,IAAI,IAAI;AAAA,EACtC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,iBAAiB,MAAqB;AAC1C,MAAI;AACF,UAAM,OAAO,SAAS,wBAAwB;AAAA,MAC5C,KAAK,QAAQ,IAAI;AAAA,MACjB,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,IACpC,CAAC,EACE,SAAS,EACT,KAAK;AACR,QAAI,KAAM,QAAO;AAEjB,UAAM,QAAQ,SAAS,yBAAyB;AAAA,MAC9C,KAAK,QAAQ,IAAI;AAAA,MACjB,OAAO,CAAC,UAAU,QAAQ,QAAQ;AAAA,IACpC,CAAC,EACE,SAAS,EACT,KAAK;AACR,QAAI,CAAC,MAAO,QAAO;AACnB,UAAM,CAAC,IAAI,IAAI,MAAM,MAAM,GAAG;AAC9B,WAAO,QAAQ;AAAA,EACjB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,mBAAmB,MAAqB;AAC5C,MAAI;AACF,WAAO,GAAG,SAAS,EAAE,YAAY;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,IAAM,eAAe,CAAC,YAAsB,UAAgC;AAC1E,MAAI,CAAC,MAAO;AACZ,QAAM,OAAO,YAAY,KAAK;AAC9B,MAAI,CAAC,QAAQ,WAAW,SAAS,IAAI,EAAG;AACxC,aAAW,KAAK,IAAI;AACtB;AAEO,SAAS,iBAAyB;AACvC,QAAM,MAAM,QAAQ,IAAI;AACxB,QAAM,aAAa,KAAK,SAAS,GAAG;AAEpC,SAAO,YAAY,UAAU,KAAK;AACpC;AAEO,SAAS,sBAAsB,SAAuC;AAC3E,QAAM,aAAuB,CAAC;AAE9B,MAAI,SAAS,MAAM;AACjB,iBAAa,YAAY,QAAQ,IAAI;AACrC,UAAM,OAAO,WAAW,CAAC;AACzB,QAAI,MAAM;AACR,iBAAW,KAAK,GAAG,IAAI,IAAI,OAAO,CAAC,EAAE,YAAY,CAAC,EAAE;AACpD,aAAO;AAAA,IACT;AAAA,EACF;AAEA,QAAM,cAAc,eAAe;AACnC,QAAM,gBAAgB,eAAe;AACrC,QAAM,cAAc,eAAe;AACnC,QAAM,gBAAgB,iBAAiB;AAEvC,eAAa,YAAY,WAAW;AACpC,eAAa,YAAY,aAAa;AAEtC,MAAI,eAAe,aAAa;AAC9B,iBAAa,YAAY,GAAG,WAAW,IAAI,WAAW,EAAE;AAAA,EAC1D;AAEA,MAAI,eAAe,eAAe;AAChC,iBAAa,YAAY,GAAG,WAAW,IAAI,aAAa,EAAE;AAAA,EAC5D;AAEA,MAAI,iBAAiB,eAAe;AAClC,iBAAa,YAAY,GAAG,aAAa,IAAI,aAAa,EAAE;AAAA,EAC9D;AAEA,QAAM,eAAe,iBAAiB,eAAe;AACrD,aAAW,KAAK,GAAG,YAAY,IAAI,OAAO,CAAC,EAAE,YAAY,CAAC,EAAE;AAC5D,SAAO;AACT;AAQO,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,IAAM,WAAW;AAAA,EACtB,eAAe;AAAA,EACf,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,eAAe;AACjB;;;AD9HO,IAAM,cAAN,cAA0B,aAAa;AAAA,EACpC,KAAuB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAoB;AAAA,EACpB,eAAuB;AAAA,EACvB,UAAkB;AAAA,EAClB,WAAmB;AAAA,EACnB,eAAe,EAAE,IAAI,GAAG,aAAa,GAAG,aAAa,GAAG,OAAO,EAAE;AAAA,EACjE,iBAA2B,CAAC;AAAA,EAC5B,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,UAAU;AACd,UAAI,UAAU;AACd,YAAM,SAAS,CAAC,UAAkB;AAChC,YAAI,QAAS;AACb,kBAAU;AACV,YAAI,OAAO;AACT,iBAAO,KAAK;AAAA,QACd,OAAO;AACL,kBAAQ;AAAA,QACV;AAAA,MACF;AAEA,UAAI;AACF,aAAK,KAAK,IAAI,UAAU,OAAO;AAAA,UAC7B,SAAS;AAAA,YACP,iBAAiB,KAAK,QAAQ,YAAY;AAAA,UAC5C;AAAA,QACF,CAAC;AAED,cAAM,oBAAoB,WAAW,MAAM;AACzC,cAAI,KAAK,MAAM,KAAK,GAAG,eAAe,UAAU,MAAM;AACpD,iBAAK,YAAY;AACjB,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;AACzB,oBAAU;AAEV,cAAI,KAAK,QAAQ,SAAS;AACxB,oBAAQ,IAAI,mBAAmB;AAAA,UACjC;AAEA,eAAK,UAAU;AACf,eAAK,KAAK,WAAW;AACrB,iBAAO;AAAA,QACT,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,cAAI,CAAC,SAAS;AACZ,mBAAO,GAAG;AAAA,UACZ;AAAA,QACF,CAAC;AAED,aAAK,GAAG,GAAG,uBAAuB,CAAC,UAAU,aAAa;AACxD,uBAAa,iBAAiB;AAE9B,gBAAM,QAAQ,IAAI;AAAA,YAChB,SAAS,eAAe,MACpB,6BACA,wBAAwB,SAAS,cAAc,SAAS;AAAA,UAC9D;AAEA,cAAI,SAAS,eAAe,KAAK;AAC/B,kBAAM,OAAO;AAAA,UACf;AAEA,eAAK,YAAY;AACjB,eAAK,IAAI,UAAU;AACnB,iBAAO,KAAK;AAAA,QACd,CAAC;AAED,aAAK,GAAG,GAAG,SAAS,MAAM;AACxB,eAAK,UAAU;AAEf,cAAI,CAAC,KAAK,aAAa,SAAS;AAC9B,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;AAC3B,UAAM,eAAe,QAAQ,OAAO,OAAO,WAAW,QAAQ,MAAM,MAAM,IAAI;AAE9E,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;AAC9B,YAAM,gBAAgB,OAAO,WAAW,MAAM,MAAM;AAEpD,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,cAAc,UAAU,SAAS,QAAQ,cAAc,aAAa;AAEzE,WAAK,KAAK,WAAW,GAAG;AACxB,WAAK;AAAA,IACP,SAAS,OAAO;AACd,YAAM,MAAM;AACZ,YAAM,WAAW,KAAK,IAAI,IAAI;AAE9B,UAAI,KAAK,QAAQ,WAAW,IAAI,SAAS,gBAAgB;AACvD,gBAAQ,MAAM,wBAAwB,IAAI,OAAO,EAAE;AAAA,MACrD;AAEA,YAAM,kBACJ,IAAI,YAAY,kBAAkB,IAAI,SAAS,iBAC3C,qDACA,UAAU,IAAI,OAAO;AAE3B,YAAM,gBAA+B;AAAA,QACnC,MAAM;AAAA,QACN,IAAI,QAAQ;AAAA,QACZ,QAAQ;AAAA,QACR,YAAY;AAAA,QACZ,SAAS,CAAC;AAAA,QACV,MAAM;AAAA,MACR;AAEA,WAAK,cAAc,UAAU,KAAK,cAAc,CAAC;AAEjD,WAAK,IAAI,KAAK,KAAK,UAAU,aAAa,CAAC;AAAA,IAC7C;AAAA,EACF;AAAA,EAEQ,cAAc,UAAkB,QAAgB,SAAiB,UAAwB;AAC/F,SAAK,WAAW;AAChB,SAAK,YAAY;AAEjB,QAAI,UAAU,OAAO,SAAS,IAAK,MAAK,aAAa,MAAM;AAAA,aAClD,UAAU,OAAO,SAAS,IAAK,MAAK,aAAa,eAAe;AAAA,aAChE,UAAU,OAAO,SAAS,IAAK,MAAK,aAAa,eAAe;AAAA,QACpE,MAAK,aAAa,SAAS;AAEhC,QAAI,KAAK,eAAe,SAAS,KAAK;AACpC,WAAK,eAAe,KAAK,QAAQ;AAAA,IACnC,OAAO;AACL,YAAM,QAAQ,KAAK,eAAe;AAClC,YAAM,QAAQ,KAAK,MAAM,KAAK,OAAO,IAAI,KAAK;AAC9C,UAAI,QAAQ,KAAK,eAAe,QAAQ;AACtC,aAAK,eAAe,KAAK,IAAI;AAAA,MAC/B;AAAA,IACF;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,cAAc,oBAAoB,KAAK,SAAS;AACtD,oBAAgB,QAAQ,IAAI;AAC5B,QAAI,gBAAgB,SAAS,GAAG;AAC9B,sBAAgB,SAAS,IAAI,GAAG,WAAW,GAAG,MAAM,GAAG;AAAA,IACzD;AAEA,UAAM,YAAY,iBACd,eACG,MAAM,GAAG,EACT,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAC3B,OAAO,OAAO,IACjB;AAEJ,UAAM,UAAU,WAAW,SACvB,IAAI,UAAU,UAAU,WAAW,EAAE,SAAS,gBAAgB,CAAC,IAC/D,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,oBAYE;AACA,UAAM,UAAU,CAAC,GAAG,KAAK,cAAc,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC7D,UAAM,MAAM,QAAQ,SAChB,KAAK,MAAM,QAAQ,OAAO,CAAC,KAAK,UAAU,MAAM,OAAO,CAAC,IAAI,QAAQ,MAAM,IAC1E;AAEJ,UAAM,aAAa,CAAC,MAAc;AAChC,UAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,YAAM,QAAQ,KAAK,IAAI,QAAQ,SAAS,GAAG,KAAK,MAAM,KAAK,QAAQ,SAAS,EAAE,CAAC;AAC/E,aAAO,QAAQ,KAAK,KAAK;AAAA,IAC3B;AAEA,WAAO;AAAA,MACL,gBAAgB,KAAK;AAAA,MACrB,gBAAgB,KAAK;AAAA,MACrB,iBAAiB,KAAK;AAAA,MACtB,gBAAgB;AAAA,MAChB,gBAAgB,WAAW,GAAG;AAAA,MAC9B,gBAAgB,WAAW,GAAG;AAAA,MAC9B,gBAAgB,WAAW,IAAI;AAAA,MAC/B,YAAY,KAAK,aAAa;AAAA,MAC9B,YAAY,KAAK,aAAa;AAAA,MAC9B,YAAY,KAAK,aAAa;AAAA,MAC9B,cAAc,KAAK,aAAa;AAAA,IAClC;AAAA,EACF;AAAA,EAEA,cAAuB;AACrB,WAAO,KAAK,OAAO,QAAQ,KAAK,GAAG,eAAe,UAAU;AAAA,EAC9D;AAAA,EAEA,eAAuB;AACrB,WAAO,KAAK;AAAA,EACd;AACF;;;AEzcA,SAAS,eAAe;AACxB,OAAO,YAAY;AACnB,OAAO,QAAQ;AACf,OAAOC,SAAQ;AACf,OAAOC,WAAU;AAEjB,IAAM,kBAAkB;AACxB,IAAM,eAAe;AACrB,IAAM,kBACJ,QAAQ,IAAI,4BAA4B,MAAM,OAC9C,QAAQ,IAAI,4BAA4B,MAAM;AAiCzC,IAAM,YAAN,MAAgB;AAAA,EACb,UAA0B;AAAA,EAC1B;AAAA,EACA;AAAA,EAER,YAAY,SAA2B;AACrC,SAAK,UAAU,QAAQ;AACvB,SAAK,aAAa,KAAK,sBAAsB;AAE7C,QAAI,KAAK,WAAW,mBAAmB,oBAAoB,mBAAmB;AAC5E,WAAK,UAAU,IAAI,QAAQ,iBAAiB;AAAA,QAC1C,MAAM;AAAA,QACN,SAAS;AAAA,QACT,eAAe;AAAA,MACjB,CAAC;AAED,UAAI,iBAAiB;AACnB,gBAAQ,IAAI,uBAAuB;AAAA,UACjC,MAAM;AAAA,UACN,WAAW,gBAAgB,MAAM,GAAG,CAAC;AAAA,QACvC,CAAC;AAAA,MACH;AAAA,IACF,WAAW,iBAAiB;AAC1B,cAAQ,IAAI,wBAAwB;AAAA,QAClC,SAAS,KAAK;AAAA,QACd,QAAQ,oBAAoB;AAAA,MAC9B,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;AACD,UAAI,iBAAiB;AACnB,gBAAQ,IAAI,wBAAwB,EAAE,MAAM,CAAC;AAAA,MAC/C;AAAA,IACF,SAAS,GAAG;AAAA,IAAC;AAAA,EACf;AAAA,EAEA,MAAM,WAA0B;AAC9B,QAAI,CAAC,KAAK,QAAS;AAEnB,QAAI;AACF,UAAI,iBAAiB;AACnB,gBAAQ,IAAI,sBAAsB;AAAA,MACpC;AACA,YAAM,KAAK,QAAQ,MAAM;AACzB,YAAM,KAAK,QAAQ,SAAS;AAAA,IAC9B,SAAS,GAAG;AAAA,IAAC;AAAA,EACf;AAAA,EAEA,YAAqB;AACnB,WAAO,KAAK,WAAW,KAAK,YAAY;AAAA,EAC1C;AAAA,EAEQ,wBAAgC;AACtC,UAAM,SAAS,KAAK,eAAe;AACnC,UAAM,MAAM,oBAAI,KAAK;AACrB,UAAM,cAAc,GAAG,MAAM,IAAI,IAAI,eAAe,CAAC,IAAI,IAAI,YAAY,IAAI,CAAC;AAC9E,WAAO,OAAO,WAAW,QAAQ,EAAE,OAAO,WAAW,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAAA,EAClF;AAAA,EAEQ,iBAAyB;AAC/B,UAAM,UACJ,QAAQ,IAAI,iBAAiB,KAC7B,QAAQ,IAAI,SAAS,KACrBA,MAAK,KAAKD,IAAG,QAAQ,GAAG,SAAS;AACnC,UAAM,YAAYC,MAAK,KAAK,SAAS,gBAAgB;AACrD,UAAM,SAASA,MAAK,KAAK,WAAW,SAAS;AAE7C,QAAI;AACF,UAAI,CAAC,GAAG,WAAW,SAAS,GAAG;AAC7B,WAAG,UAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAAA,MAC7C;AAEA,UAAI,GAAG,WAAW,MAAM,GAAG;AACzB,cAAM,WAAW,GAAG,aAAa,QAAQ,MAAM,EAAE,KAAK;AACtD,YAAI,SAAU,QAAO;AAAA,MACvB;AAEA,YAAM,QAAQ,OAAO,WAAW;AAChC,SAAG,cAAc,QAAQ,OAAO,MAAM;AACtC,aAAO;AAAA,IACT,QAAQ;AACN,aAAO,OAAO,WAAW;AAAA,IAC3B;AAAA,EACF;AACF;;;APpIA,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,qBAAqB,sBAAsB,EAAE,MAAM,QAAQ,KAAK,CAAC;AAEvE,QAAM,YAAY,IAAI,UAAU;AAAA,IAC9B,SAAS,QAAQ,cAAc;AAAA,EACjC,CAAC;AAED,QAAM,UAAU,MAAM,gBAAgB;AAAA,IACpC,UAAU,CAAC,CAAC,QAAQ;AAAA,IACpB,cAAc,QAAQ;AAAA,IACtB,UAAU,QAAQ;AAAA,EACpB,CAAC;AAED,MAAI,cAAkC;AACtC,MAAI,WAAW;AACf,MAAI,YAAY;AAEhB,aAAW,aAAa,oBAAoB;AAC1C,eAAW;AACX,gBAAY,aAAa,QAAQ;AACjC,kBAAc,IAAI,YAAY;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,QAAQ;AAAA,MAClB,SAAS,QAAQ;AAAA,IACnB,CAAC;AAED,QAAI;AACF,YAAM,YAAY,QAAQ;AAC1B;AAAA,IACF,SAAS,OAAO;AACd,YAAM,MAAM;AACZ,UAAI,IAAI,SAAS,oBAAoB;AACnC,cAAM,YAAY,WAAW;AAC7B,sBAAc;AACd;AAAA,MACF;AAEA,cAAQ,MAAM,sCAAsC,IAAI,OAAO;AAC/D,YAAM,UAAU,SAAS;AACzB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,MAAI,CAAC,aAAa;AAChB,YAAQ,MAAM,2EAA2E;AACzF,UAAM,UAAU,SAAS;AACzB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,eAAe;AAErB,MAAI,QAAQ,SAAS;AACnB,YAAQ,IAAI,eAAe,SAAS,EAAE;AACtC,YAAQ,IAAI,2BAA2B,SAAS,EAAE;AAAA,EACpD;AAEA,QAAM,EAAE,cAAc,IAAI;AAAA,IACxBC,OAAM,cAAc,UAAU;AAAA,MAC5B;AAAA,MACA;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAEA,QAAM,UAAU,YAAY;AAC1B,UAAM,WAAW,aAAa,YAAY;AAC1C,UAAM,WAAW,aAAa,gBAAgB;AAE9C,UAAM,UAAU,MAAM,cAAc;AAAA,MAClC,kBAAkB;AAAA,MAClB;AAAA,IACF,CAAC;AAED,UAAM,UAAU,aAAa,kBAAkB;AAC/C,UAAM,UAAU,MAAM,mBAAmB,OAAO;AAEhD,UAAM,aAAa,WAAW;AAC9B,UAAM,UAAU,SAAS;AAAA,EAC3B;AAEA,MAAI,iBAAiB;AACrB,QAAM,iBAAiB,YAAY;AACjC,QAAI,eAAgB;AACpB,qBAAiB;AAEjB,UAAM,UAAU,WAAW,MAAM;AAC/B,cAAQ,KAAK,CAAC;AAAA,IAChB,GAAG,GAAI;AAEP,QAAI;AACF,YAAM,QAAQ;AAAA,IAChB,UAAE;AACA,mBAAa,OAAO;AACpB,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,UAAQ,KAAK,UAAU,cAAc;AACrC,UAAQ,KAAK,WAAW,cAAc;AAEtC,QAAM,cAAc;AACtB;;;AQzHA,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,QACG,KAAK,YAAY,EACjB,YAAY,gCAAgC,EAC5C,QAAQ,QAA0C;AAErD,QACG,SAAS,UAAU,sBAAsB,EACzC,OAAO,QAAQ,iCAAiC,EAChD,OAAO,iBAAiB,+BAA+B,EACvD,OAAO,yBAAyB,6BAA6B,EAC7D,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","os","path","React"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "untunneled.dev",
3
- "version": "0.3.5",
3
+ "version": "0.3.10",
4
4
  "description": "Fast, free localhost tunneling for developers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",