untunneled.dev 0.1.4-alpha5 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -8,8 +8,8 @@ import { render } from "ink";
8
8
  import React5 from "react";
9
9
 
10
10
  // src/ui/TunnelUI.tsx
11
- import { useState as useState2, useEffect as useEffect2 } from "react";
12
- import { Box as Box4, Text as Text4, Newline, useApp } from "ink";
11
+ import { useState as useState3, useEffect as useEffect3 } from "react";
12
+ import { Box as Box4, Text as Text4, useApp } from "ink";
13
13
 
14
14
  // src/ui/QRCode.tsx
15
15
  import { useState, useEffect } from "react";
@@ -39,17 +39,18 @@ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
39
39
  var getStatusColor = (status) => {
40
40
  if (status >= 500) return "red";
41
41
  if (status >= 400) return "yellow";
42
- if (status >= 300) return "blue";
43
- return "green";
42
+ if (status >= 300) return "cyan";
43
+ if (status >= 200) return "green";
44
+ return "gray";
44
45
  };
45
46
  var getMethodColor = (method) => {
46
- switch (method) {
47
+ switch (method.toUpperCase()) {
47
48
  case "GET":
48
- return "green";
49
+ return "cyan";
49
50
  case "POST":
50
- return "yellow";
51
+ return "green";
51
52
  case "PUT":
52
- return "blue";
53
+ return "yellow";
53
54
  case "DELETE":
54
55
  return "red";
55
56
  case "PATCH":
@@ -60,201 +61,224 @@ var getMethodColor = (method) => {
60
61
  };
61
62
  var RequestLog = ({ requests }) => {
62
63
  if (requests.length === 0) {
63
- return /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " Waiting for requests..." });
64
- }
65
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: requests.map((req) => /* @__PURE__ */ jsxs2(Box2, { gap: 1, children: [
66
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: req.timestamp }),
67
- /* @__PURE__ */ jsx2(Box2, { width: 7, children: /* @__PURE__ */ jsx2(Text2, { color: getMethodColor(req.method), children: req.method.padEnd(6) }) }),
68
- /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, children: /* @__PURE__ */ jsx2(Text2, { children: req.path.length > 40 ? req.path.substring(0, 37) + "..." : req.path }) }),
69
- /* @__PURE__ */ jsx2(Box2, { width: 5, children: /* @__PURE__ */ jsx2(Text2, { color: getStatusColor(req.status), children: req.status }) }),
70
- /* @__PURE__ */ jsx2(Box2, { width: 8, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
71
- req.duration,
72
- "ms"
73
- ] }) }),
74
- /* @__PURE__ */ jsx2(Box2, { width: 7, children: /* @__PURE__ */ jsxs2(Text2, { color: req.type === "p2p" ? "green" : "yellow", children: [
75
- "[",
76
- req.type.toUpperCase(),
77
- "]"
78
- ] }) })
79
- ] }, req.id)) });
64
+ return /* @__PURE__ */ jsxs2(Box2, { paddingY: 1, flexDirection: "column", alignItems: "center", children: [
65
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510" }),
66
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2502 \u2502" }),
67
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
68
+ "\u2502 ",
69
+ /* @__PURE__ */ jsx2(Text2, { color: "cyan", children: "\u23F3" }),
70
+ " Waiting for requests... \u2502"
71
+ ] }),
72
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2502 \u2502" }),
73
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2502 Open the URL in your browser \u2502" }),
74
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2502 \u2502" }),
75
+ /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518" })
76
+ ] });
77
+ }
78
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: requests.map((req) => {
79
+ const time = new Date(req.timestamp).toLocaleTimeString([], {
80
+ hour: "2-digit",
81
+ minute: "2-digit",
82
+ second: "2-digit",
83
+ hour12: false
84
+ });
85
+ return /* @__PURE__ */ jsxs2(Box2, { gap: 1, children: [
86
+ /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
87
+ "[",
88
+ time,
89
+ "]"
90
+ ] }),
91
+ /* @__PURE__ */ jsx2(Box2, { width: 8, children: /* @__PURE__ */ jsx2(Text2, { color: getMethodColor(req.method), bold: true, children: req.method.padEnd(7) }) }),
92
+ /* @__PURE__ */ jsx2(Box2, { flexGrow: 1, children: /* @__PURE__ */ jsx2(Text2, { children: req.path.length > 40 ? req.path.substring(0, 37) + "..." : req.path }) }),
93
+ /* @__PURE__ */ jsx2(Box2, { width: 6, children: /* @__PURE__ */ jsx2(Text2, { color: getStatusColor(req.status), bold: true, children: req.status }) }),
94
+ /* @__PURE__ */ jsx2(Box2, { width: 10, justifyContent: "flex-end", children: /* @__PURE__ */ jsxs2(Text2, { color: req.duration > 500 ? "yellow" : "gray", children: [
95
+ req.duration.toString().padStart(4),
96
+ "ms"
97
+ ] }) })
98
+ ] }, req.id);
99
+ }) });
80
100
  };
81
101
 
82
102
  // src/ui/ConnectionStatus.tsx
83
- import "react";
103
+ import { useState as useState2, useEffect as useEffect2 } from "react";
84
104
  import { Box as Box3, Text as Text3 } from "ink";
85
105
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
86
- var getStatusIndicator = (status) => {
106
+ var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
107
+ var getStatusIndicator = (status, frame) => {
87
108
  switch (status) {
88
109
  case "connected":
89
- return { color: "green", symbol: "\u25CF" };
110
+ return { color: "green", symbol: "\u2714", label: "CONNECTED" };
90
111
  case "connecting":
91
- return { color: "yellow", symbol: "\u25D0" };
112
+ return { color: "yellow", symbol: frame, label: "CONNECTING" };
92
113
  case "disconnected":
93
- return { color: "red", symbol: "\u25CB" };
114
+ return { color: "gray", symbol: "\u25CB", label: "DISCONNECTED" };
94
115
  case "error":
95
- return { color: "red", symbol: "\u2717" };
116
+ return { color: "red", symbol: "\u2718", label: "ERROR" };
117
+ default:
118
+ return { color: "white", symbol: "?", label: String(status).toUpperCase() };
96
119
  }
97
120
  };
98
- var getModeLabel = (mode, p2pConnected) => {
99
- if (mode === "relay") return "HTTP Relay";
100
- if (mode === "p2p-only") return p2pConnected ? "P2P Direct" : "P2P (connecting...)";
101
- return p2pConnected ? "P2P + Relay Fallback" : "Relay (P2P pending...)";
102
- };
103
- var ConnectionStatus = ({
104
- status,
105
- mode,
106
- p2pConnected,
107
- relayConnected
108
- }) => {
109
- const indicator = getStatusIndicator(status);
110
- const modeLabel = getModeLabel(mode, p2pConnected);
111
- return /* @__PURE__ */ jsxs3(Box3, { gap: 2, children: [
112
- /* @__PURE__ */ jsxs3(Box3, { children: [
113
- /* @__PURE__ */ jsx3(Text3, { color: indicator.color, children: indicator.symbol }),
114
- /* @__PURE__ */ jsx3(Text3, { children: " " }),
115
- /* @__PURE__ */ jsx3(Text3, { color: indicator.color, children: status.toUpperCase() })
116
- ] }),
117
- /* @__PURE__ */ jsxs3(Box3, { children: [
118
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Mode: " }),
119
- /* @__PURE__ */ jsx3(Text3, { color: p2pConnected ? "green" : "yellow", children: modeLabel })
120
- ] }),
121
- mode !== "relay" && /* @__PURE__ */ jsxs3(Box3, { children: [
122
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "P2P: " }),
123
- /* @__PURE__ */ jsx3(Text3, { color: p2pConnected ? "green" : "yellow", children: p2pConnected ? "Active" : "Pending" })
124
- ] }),
125
- /* @__PURE__ */ jsxs3(Box3, { children: [
126
- /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Relay: " }),
127
- /* @__PURE__ */ jsx3(Text3, { color: relayConnected ? "green" : "red", children: relayConnected ? "Connected" : "Disconnected" })
128
- ] })
129
- ] });
121
+ var ConnectionStatus = ({ status }) => {
122
+ const [frameIndex, setFrameIndex] = useState2(0);
123
+ useEffect2(() => {
124
+ if (status !== "connecting") return;
125
+ const timer = setInterval(() => {
126
+ setFrameIndex((prev) => (prev + 1) % SPINNER_FRAMES.length);
127
+ }, 80);
128
+ return () => clearInterval(timer);
129
+ }, [status]);
130
+ const frame = SPINNER_FRAMES[frameIndex] ?? " ";
131
+ const indicator = getStatusIndicator(status, frame);
132
+ return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: indicator.color, bold: true, children: [
133
+ indicator.symbol,
134
+ " ",
135
+ indicator.label
136
+ ] }) });
130
137
  };
131
138
 
132
139
  // src/ui/TunnelUI.tsx
133
- import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
140
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
134
141
  var MAX_LOGS = 10;
142
+ var LOGO = `
143
+ \u2566 \u2566\u2554\u2557\u2554\u2554\u2566\u2557\u2566 \u2566\u2554\u2557\u2554\u2554\u2557\u2554\u2554\u2550\u2557\u2566 \u2554\u2550\u2557\u2554\u2566\u2557 \u2554\u2566\u2557\u2554\u2550\u2557\u2566 \u2566
144
+ \u2551 \u2551\u2551\u2551\u2551 \u2551 \u2551 \u2551\u2551\u2551\u2551\u2551\u2551\u2551\u2551\u2563 \u2551 \u2551\u2563 \u2551\u2551 \u2551\u2551\u2551\u2563 \u255A\u2557\u2554\u255D
145
+ \u255A\u2550\u255D\u255D\u255A\u255D \u2569 \u255A\u2550\u255D\u255D\u255A\u255D\u255D\u255A\u255D\u255A\u2550\u255D\u2569\u2550\u255D\u255A\u2550\u255D\u2550\u2569\u255Do\u2550\u2569\u255D\u255A\u2550\u255D \u255A\u255D
146
+ `;
147
+ var formatUptime = (ms) => {
148
+ const seconds = Math.floor(ms / 1e3);
149
+ const minutes = Math.floor(seconds / 60);
150
+ const hours = Math.floor(minutes / 60);
151
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
152
+ };
135
153
  var TunnelUI = ({
136
154
  tunnelUrl,
137
155
  localPort,
138
156
  showQR,
139
- mode,
140
- relayTunnel,
141
- p2pTunnel,
142
- p2pError
157
+ relayTunnel
143
158
  }) => {
144
159
  useApp();
145
- const [requests, setRequests] = useState2([]);
146
- const [stats, setStats] = useState2({
160
+ const [requests, setRequests] = useState3([]);
161
+ const [stats, setStats] = useState3({
147
162
  total: 0,
148
- p2p: 0,
149
- relay: 0,
150
163
  startTime: Date.now()
151
164
  });
152
- const [relayConnected, setRelayConnected] = useState2(relayTunnel.isConnected());
153
- const [p2pConnected, setP2pConnected] = useState2(p2pTunnel?.isConnected() ?? false);
154
- useEffect2(() => {
165
+ const [relayConnected, setRelayConnected] = useState3(relayTunnel.isConnected());
166
+ const [uptime, setUptime] = useState3(0);
167
+ useEffect3(() => {
155
168
  const handleRequest = (req) => {
156
169
  setRequests((prev) => [...prev.slice(-(MAX_LOGS - 1)), req]);
157
170
  setStats((prev) => ({
158
171
  ...prev,
159
- total: prev.total + 1,
160
- p2p: prev.p2p + (req.type === "p2p" ? 1 : 0),
161
- relay: prev.relay + (req.type === "relay" ? 1 : 0)
172
+ total: prev.total + 1
162
173
  }));
163
174
  };
164
175
  const handleRelayConnected = () => setRelayConnected(true);
165
176
  const handleRelayDisconnected = () => setRelayConnected(false);
166
- const handleP2PConnected = () => setP2pConnected(true);
167
- const handleP2PDisconnected = () => setP2pConnected(false);
168
177
  relayTunnel.on("request", handleRequest);
169
178
  relayTunnel.on("connected", handleRelayConnected);
170
179
  relayTunnel.on("disconnected", handleRelayDisconnected);
171
- if (p2pTunnel) {
172
- p2pTunnel.on("request", handleRequest);
173
- p2pTunnel.on("connected", handleP2PConnected);
174
- p2pTunnel.on("disconnected", handleP2PDisconnected);
175
- }
180
+ const uptimeTimer = setInterval(() => {
181
+ setUptime(Date.now() - stats.startTime);
182
+ }, 1e3);
176
183
  return () => {
177
184
  relayTunnel.off("request", handleRequest);
178
185
  relayTunnel.off("connected", handleRelayConnected);
179
186
  relayTunnel.off("disconnected", handleRelayDisconnected);
180
- if (p2pTunnel) {
181
- p2pTunnel.off("request", handleRequest);
182
- p2pTunnel.off("connected", handleP2PConnected);
183
- p2pTunnel.off("disconnected", handleP2PDisconnected);
184
- }
187
+ clearInterval(uptimeTimer);
185
188
  };
186
- }, [relayTunnel, p2pTunnel]);
187
- const connectionStatus = relayConnected || p2pConnected ? "connected" : "connecting";
188
- const borderColor = p2pConnected ? "green" : relayConnected ? "yellow" : "gray";
189
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", padding: 1, children: [
190
- /* @__PURE__ */ jsxs4(Box4, { borderStyle: "round", borderColor, padding: 1, flexDirection: "column", children: [
191
- /* @__PURE__ */ jsxs4(Text4, { bold: true, color: borderColor, children: [
192
- p2pConnected ? "\u{1F512}" : "\u26A1",
193
- " untunneled.dev"
194
- ] }),
195
- /* @__PURE__ */ jsx4(Newline, {}),
196
- /* @__PURE__ */ jsx4(
197
- ConnectionStatus,
198
- {
199
- status: connectionStatus,
200
- mode,
201
- p2pConnected,
202
- relayConnected
203
- }
204
- ),
205
- /* @__PURE__ */ jsx4(Newline, {}),
206
- /* @__PURE__ */ jsxs4(Box4, { children: [
207
- /* @__PURE__ */ jsx4(Text4, { children: "URL: " }),
208
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: tunnelUrl })
209
- ] }),
210
- /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
211
- "\u2192 localhost:",
212
- localPort
213
- ] }) })
189
+ }, [relayTunnel, stats.startTime]);
190
+ const connectionStatus = relayConnected ? "connected" : "connecting";
191
+ const accentColor = relayConnected ? "cyan" : "yellow";
192
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [
193
+ /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginBottom: 1, children: [
194
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: LOGO }),
195
+ /* @__PURE__ */ jsxs4(Box4, { justifyContent: "space-between", paddingX: 1, children: [
196
+ /* @__PURE__ */ jsx4(ConnectionStatus, { status: connectionStatus }),
197
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "v0.2.2" })
198
+ ] })
214
199
  ] }),
215
- p2pError && mode === "p2p-only" && /* @__PURE__ */ jsxs4(
200
+ /* @__PURE__ */ jsxs4(
216
201
  Box4,
217
202
  {
218
- marginTop: 1,
219
- borderStyle: "single",
220
- borderColor: "red",
221
- padding: 1,
203
+ borderStyle: "round",
204
+ borderColor: accentColor,
205
+ paddingX: 2,
206
+ paddingY: 1,
222
207
  flexDirection: "column",
223
208
  children: [
224
- /* @__PURE__ */ jsx4(Text4, { color: "red", bold: true, children: "P2P Connection Failed" }),
225
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: p2pError.message }),
226
- /* @__PURE__ */ jsx4(Newline, {}),
227
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Use --relay-fallback for guaranteed compatibility" })
209
+ /* @__PURE__ */ jsxs4(Box4, { children: [
210
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: "Public URL: " }),
211
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "black", backgroundColor: "cyan", children: [
212
+ " ",
213
+ tunnelUrl,
214
+ " "
215
+ ] })
216
+ ] }),
217
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
218
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Forwarding: " }),
219
+ /* @__PURE__ */ jsxs4(Text4, { color: "yellow", children: [
220
+ "http://localhost:",
221
+ localPort
222
+ ] }),
223
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " \u2192 " }),
224
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: tunnelUrl })
225
+ ] })
228
226
  ]
229
227
  }
230
228
  ),
231
- showQR && /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(QRCode, { url: tunnelUrl }) }),
232
- /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, gap: 2, children: [
233
- /* @__PURE__ */ jsxs4(Text4, { children: [
234
- "Requests: ",
235
- /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: stats.total })
229
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, paddingX: 1, gap: 4, children: [
230
+ /* @__PURE__ */ jsxs4(Box4, { children: [
231
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Requests: " }),
232
+ /* @__PURE__ */ jsx4(Text4, { color: "white", bold: true, children: stats.total })
236
233
  ] }),
237
- mode !== "relay" && stats.total > 0 && /* @__PURE__ */ jsxs4(Fragment, { children: [
238
- /* @__PURE__ */ jsxs4(Text4, { children: [
239
- "P2P: ",
240
- /* @__PURE__ */ jsx4(Text4, { color: "green", children: stats.p2p })
241
- ] }),
242
- /* @__PURE__ */ jsxs4(Text4, { children: [
243
- "Relay: ",
244
- /* @__PURE__ */ jsx4(Text4, { color: "yellow", children: stats.relay })
245
- ] }),
246
- /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
247
- "(",
248
- Math.round(stats.p2p / stats.total * 100),
249
- "% private)"
250
- ] })
234
+ /* @__PURE__ */ jsxs4(Box4, { children: [
235
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Uptime: " }),
236
+ /* @__PURE__ */ jsx4(Text4, { color: "white", bold: true, children: formatUptime(uptime) })
237
+ ] }),
238
+ /* @__PURE__ */ jsxs4(Box4, { children: [
239
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Latency: " }),
240
+ /* @__PURE__ */ jsx4(Text4, { color: "white", bold: true, children: requests.length > 0 ? `${requests[requests.length - 1]?.duration}ms` : "-" })
251
241
  ] })
252
242
  ] }),
243
+ showQR && /* @__PURE__ */ jsx4(
244
+ Box4,
245
+ {
246
+ marginY: 1,
247
+ borderStyle: "single",
248
+ borderColor: "gray",
249
+ paddingX: 1,
250
+ alignSelf: "flex-start",
251
+ children: /* @__PURE__ */ jsx4(QRCode, { url: tunnelUrl })
252
+ }
253
+ ),
253
254
  /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
254
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Recent requests:" }),
255
+ /* @__PURE__ */ jsx4(Box4, { marginBottom: 1, children: /* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "\u2500\u2500\u2500 Recent Requests \u2500\u2500\u2500" }) }),
255
256
  /* @__PURE__ */ jsx4(RequestLog, { requests })
256
257
  ] }),
257
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press Ctrl+C to stop" }) })
258
+ /* @__PURE__ */ jsxs4(
259
+ Box4,
260
+ {
261
+ marginTop: 1,
262
+ borderStyle: "single",
263
+ borderTop: true,
264
+ borderBottom: false,
265
+ borderLeft: false,
266
+ borderRight: false,
267
+ borderColor: "gray",
268
+ paddingTop: 1,
269
+ children: [
270
+ /* @__PURE__ */ jsxs4(Box4, { flexGrow: 1, children: [
271
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press " }),
272
+ /* @__PURE__ */ jsx4(Text4, { color: "yellow", bold: true, children: "Ctrl+C" }),
273
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " to stop tunnel" })
274
+ ] }),
275
+ /* @__PURE__ */ jsxs4(Box4, { children: [
276
+ /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Sponsor: " }),
277
+ /* @__PURE__ */ jsx4(Text4, { color: "magenta", bold: true, children: "untunneled.dev/sponsor" })
278
+ ] })
279
+ ]
280
+ }
281
+ )
258
282
  ] });
259
283
  };
260
284
 
@@ -266,18 +290,11 @@ import { EventEmitter } from "events";
266
290
  import { nanoid } from "nanoid";
267
291
  import path from "path";
268
292
  var RELAY_DOMAIN = "relay.untunneled.dev";
269
- var SIGNALING_DOMAIN = "signal.untunneled.dev";
270
293
  var TUNNEL_DOMAIN = "untunneled.dev";
271
- var getRelayUrl = (tunnelId, mode) => {
294
+ var getRelayUrl = (tunnelId) => {
272
295
  const host = process.env["UNTUNNELED_RELAY_HOST"] || RELAY_DOMAIN;
273
296
  const protocol = host.includes("localhost") ? "ws" : "wss";
274
- const modeParam = mode ? `?mode=${mode}` : "";
275
- return `${protocol}://${host}/${tunnelId}${modeParam}`;
276
- };
277
- var getSignalingUrl = () => {
278
- const host = process.env["UNTUNNELED_SIGNALING_HOST"] || SIGNALING_DOMAIN;
279
- const protocol = host.includes("localhost") ? "ws" : "wss";
280
- return `${protocol}://${host}`;
297
+ return `${protocol}://${host}/${tunnelId}`;
281
298
  };
282
299
  var getTunnelUrl = (tunnelId) => {
283
300
  const domain = process.env["UNTUNNELED_TUNNEL_DOMAIN"] || TUNNEL_DOMAIN;
@@ -304,14 +321,7 @@ function parseAllowList(allow) {
304
321
  if (!allow) return void 0;
305
322
  return allow.split(",").map((email) => email.trim().toLowerCase()).filter((email) => email.length > 0);
306
323
  }
307
- var ICE_SERVERS = [
308
- { urls: "stun:stun.l.google.com:19302" },
309
- { urls: "stun:stun1.l.google.com:19302" },
310
- { urls: "stun:stun2.l.google.com:19302" },
311
- { urls: "stun:global.stun.twilio.com:3478" }
312
- ];
313
324
  var TIMEOUTS = {
314
- P2P_CONNECTION: 3e4,
315
325
  RELAY_REQUEST: 3e4,
316
326
  WEBSOCKET_PING: 3e4,
317
327
  RECONNECT_BASE: 1e3,
@@ -340,7 +350,7 @@ var RelayTunnel = class extends EventEmitter {
340
350
  async connect() {
341
351
  this.startTime = Date.now();
342
352
  this.isClosing = false;
343
- const wsUrl = getRelayUrl(this.tunnelId, this.options.mode);
353
+ const wsUrl = getRelayUrl(this.tunnelId);
344
354
  if (this.options.verbose) {
345
355
  console.log(`[Relay] Connecting to ${wsUrl}`);
346
356
  }
@@ -349,8 +359,7 @@ var RelayTunnel = class extends EventEmitter {
349
359
  this.ws = new WebSocket(wsUrl, {
350
360
  headers: {
351
361
  "X-Tunnel-Auth": this.options.password || "",
352
- "X-Tunnel-Allow": this.options.allowList?.join(",") || "",
353
- "X-Tunnel-Mode": this.options.mode || "relay"
362
+ "X-Tunnel-Allow": this.options.allowList?.join(",") || ""
354
363
  }
355
364
  });
356
365
  const connectionTimeout = setTimeout(() => {
@@ -358,7 +367,7 @@ var RelayTunnel = class extends EventEmitter {
358
367
  this.ws.terminate();
359
368
  reject(new Error("Connection timeout"));
360
369
  }
361
- }, TIMEOUTS.P2P_CONNECTION);
370
+ }, TIMEOUTS.RELAY_REQUEST);
362
371
  this.ws.on("open", () => {
363
372
  clearTimeout(connectionTimeout);
364
373
  this.reconnectAttempts = 0;
@@ -429,8 +438,7 @@ var RelayTunnel = class extends EventEmitter {
429
438
  method: request.method,
430
439
  path: request.path,
431
440
  status: response.status,
432
- duration,
433
- type: "relay"
441
+ duration
434
442
  };
435
443
  this.emit("request", log);
436
444
  this.requestCount++;
@@ -503,271 +511,6 @@ var RelayTunnel = class extends EventEmitter {
503
511
  }
504
512
  };
505
513
 
506
- // src/tunnel/p2p.ts
507
- import wrtc from "@roamhq/wrtc";
508
- import { WebSocket as WebSocket2 } from "ws";
509
- import { EventEmitter as EventEmitter2 } from "events";
510
- var { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate } = wrtc;
511
- var P2PTunnel = class extends EventEmitter2 {
512
- pc = null;
513
- dataChannel = null;
514
- ws = null;
515
- localPort;
516
- tunnelId;
517
- options;
518
- connected = false;
519
- requestCount = 0;
520
- signalingConnected = false;
521
- pingInterval = null;
522
- constructor(options) {
523
- super();
524
- this.options = options;
525
- this.localPort = options.localPort;
526
- this.tunnelId = options.tunnelId;
527
- }
528
- async connect() {
529
- if (this.options.verbose) {
530
- console.log("[P2P] Attempting connection...");
531
- }
532
- return new Promise((resolve, reject) => {
533
- const signalingUrl = getSignalingUrl();
534
- this.ws = new WebSocket2(signalingUrl);
535
- const connectionTimeout = setTimeout(() => {
536
- if (!this.connected) {
537
- reject(new Error("P2P connection timeout"));
538
- }
539
- }, TIMEOUTS.P2P_CONNECTION);
540
- this.ws.on("open", () => {
541
- this.signalingConnected = true;
542
- if (this.options.verbose) {
543
- console.log("[P2P] Signaling connected");
544
- }
545
- this.setupPing();
546
- const registerMsg = {
547
- type: "register",
548
- tunnelId: this.tunnelId,
549
- role: "cli"
550
- };
551
- this.ws.send(JSON.stringify(registerMsg));
552
- this.initPeerConnection();
553
- });
554
- this.ws.on("message", async (data) => {
555
- const msg = JSON.parse(data.toString());
556
- if (this.options.verbose) {
557
- console.log("[P2P] Received:", msg.type, msg.from || "");
558
- }
559
- if (msg.type === "signal" && msg.from === "browser") {
560
- await this.handleBrowserSignal(msg, connectionTimeout, resolve);
561
- }
562
- });
563
- this.ws.on("error", (err) => {
564
- clearTimeout(connectionTimeout);
565
- this.signalingConnected = false;
566
- reject(err);
567
- });
568
- this.ws.on("close", () => {
569
- this.signalingConnected = false;
570
- this.clearPing();
571
- if (this.options.verbose) {
572
- console.log("[P2P] Signaling closed");
573
- }
574
- });
575
- });
576
- }
577
- initPeerConnection() {
578
- this.pc = new RTCPeerConnection({
579
- iceServers: ICE_SERVERS
580
- });
581
- this.pc.onicecandidate = (event) => {
582
- if (event.candidate && this.ws && this.signalingConnected) {
583
- if (this.options.verbose) {
584
- console.log("[P2P] Sending ICE candidate");
585
- }
586
- const signalMsg = {
587
- type: "signal",
588
- from: "cli",
589
- to: "browser",
590
- tunnelId: this.tunnelId,
591
- signal: { candidate: event.candidate }
592
- };
593
- this.ws.send(JSON.stringify(signalMsg));
594
- }
595
- };
596
- this.pc.oniceconnectionstatechange = () => {
597
- if (this.options.verbose) {
598
- console.log("[P2P] ICE state:", this.pc?.iceConnectionState);
599
- }
600
- };
601
- this.pc.ondatachannel = (event) => {
602
- if (this.options.verbose) {
603
- console.log("[P2P] Received data channel");
604
- }
605
- this.dataChannel = event.channel;
606
- this.setupDataChannel();
607
- };
608
- }
609
- async handleBrowserSignal(msg, connectionTimeout, resolve) {
610
- if (!this.pc) {
611
- this.initPeerConnection();
612
- }
613
- const signal = msg.signal;
614
- if (signal.type === "offer" && signal.sdp) {
615
- if (this.options.verbose) {
616
- console.log("[P2P] Received offer, creating answer");
617
- }
618
- try {
619
- await this.pc.setRemoteDescription(
620
- new RTCSessionDescription({ type: "offer", sdp: signal.sdp })
621
- );
622
- const answer = await this.pc.createAnswer();
623
- await this.pc.setLocalDescription(answer);
624
- const signalMsg = {
625
- type: "signal",
626
- from: "cli",
627
- to: "browser",
628
- tunnelId: this.tunnelId,
629
- signal: { type: "answer", sdp: this.pc.localDescription?.sdp }
630
- };
631
- this.ws.send(JSON.stringify(signalMsg));
632
- if (this.options.verbose) {
633
- console.log("[P2P] Sent answer");
634
- }
635
- this.setupDataChannelResolver(connectionTimeout, resolve);
636
- } catch (err) {
637
- console.error("[P2P] Error handling offer:", err);
638
- }
639
- } else if (signal.candidate) {
640
- if (this.options.verbose) {
641
- console.log("[P2P] Adding ICE candidate");
642
- }
643
- try {
644
- await this.pc.addIceCandidate(new RTCIceCandidate(signal.candidate));
645
- } catch (err) {
646
- console.error("[P2P] Error adding ICE candidate:", err);
647
- }
648
- }
649
- }
650
- setupDataChannelResolver(connectionTimeout, resolve) {
651
- const checkConnection = () => {
652
- if (this.connected) {
653
- clearTimeout(connectionTimeout);
654
- resolve();
655
- }
656
- };
657
- this.once("connected", checkConnection);
658
- }
659
- setupDataChannel() {
660
- if (!this.dataChannel) return;
661
- this.dataChannel.onopen = () => {
662
- this.connected = true;
663
- this.emit("connected");
664
- if (this.options.verbose) {
665
- console.log("[P2P] Data channel open");
666
- }
667
- };
668
- this.dataChannel.onmessage = async (event) => {
669
- await this.handleP2PRequest(event.data);
670
- };
671
- this.dataChannel.onerror = (event) => {
672
- console.error("[P2P] Data channel error:", event);
673
- };
674
- this.dataChannel.onclose = () => {
675
- this.connected = false;
676
- this.emit("disconnected");
677
- if (this.options.verbose) {
678
- console.log("[P2P] Data channel closed");
679
- }
680
- };
681
- }
682
- setupPing() {
683
- this.pingInterval = setInterval(() => {
684
- if (this.ws && this.ws.readyState === WebSocket2.OPEN) {
685
- this.ws.ping();
686
- }
687
- }, TIMEOUTS.WEBSOCKET_PING);
688
- }
689
- clearPing() {
690
- if (this.pingInterval) {
691
- clearInterval(this.pingInterval);
692
- this.pingInterval = null;
693
- }
694
- }
695
- async handleP2PRequest(data) {
696
- const startTime = Date.now();
697
- let request;
698
- try {
699
- request = JSON.parse(data);
700
- } catch {
701
- return;
702
- }
703
- try {
704
- const url = `http://localhost:${this.localPort}${request.path}`;
705
- const fetchOptions = {
706
- method: request.method,
707
- headers: request.headers
708
- };
709
- if (request.body && !["GET", "HEAD"].includes(request.method)) {
710
- fetchOptions.body = request.body;
711
- }
712
- const response = await fetch(url, fetchOptions);
713
- const body = await response.text();
714
- const duration = Date.now() - startTime;
715
- const p2pResponse = {
716
- id: request.id,
717
- status: response.status,
718
- statusText: response.statusText,
719
- headers: Object.fromEntries(response.headers.entries()),
720
- body
721
- };
722
- this.dataChannel?.send(JSON.stringify(p2pResponse));
723
- const log = {
724
- id: request.id,
725
- timestamp: (/* @__PURE__ */ new Date()).toISOString().substring(11, 19),
726
- method: request.method,
727
- path: request.path,
728
- status: response.status,
729
- duration,
730
- type: "p2p"
731
- };
732
- this.emit("request", log);
733
- this.requestCount++;
734
- } catch (error) {
735
- const err = error;
736
- const errorResponse = {
737
- id: request.id,
738
- status: 502,
739
- statusText: "Bad Gateway",
740
- headers: {},
741
- body: `Error: ${err.message}`
742
- };
743
- this.dataChannel?.send(JSON.stringify(errorResponse));
744
- }
745
- }
746
- cleanup() {
747
- this.clearPing();
748
- this.dataChannel?.close();
749
- this.pc?.close();
750
- this.ws?.close();
751
- this.dataChannel = null;
752
- this.pc = null;
753
- this.ws = null;
754
- this.connected = false;
755
- this.signalingConnected = false;
756
- }
757
- disconnect() {
758
- this.cleanup();
759
- }
760
- isConnected() {
761
- return this.connected;
762
- }
763
- isSignalingConnected() {
764
- return this.signalingConnected;
765
- }
766
- getRequestCount() {
767
- return this.requestCount;
768
- }
769
- };
770
-
771
514
  // src/analytics.ts
772
515
  import { PostHog } from "posthog-node";
773
516
  var POSTHOG_API_KEY = process.env["POSTHOG_API_KEY"] || "phc_placeholder";
@@ -826,13 +569,11 @@ async function startCommand(port, options) {
826
569
  const projectName = getProjectName();
827
570
  const tunnelId = generateTunnelId(projectName);
828
571
  const tunnelUrl = getTunnelUrl(tunnelId);
829
- const mode = options.relayFallback ? "p2p-with-fallback" : "p2p-only";
830
572
  const analytics = new Analytics({
831
573
  enabled: options.telemetry !== false,
832
574
  tunnelId
833
575
  });
834
576
  await analytics.track("tunnel_start", {
835
- mode,
836
577
  has_auth: !!options.password || !!options.allow,
837
578
  node_version: process.version,
838
579
  platform: process.platform
@@ -843,11 +584,8 @@ async function startCommand(port, options) {
843
584
  tunnelUrl,
844
585
  password: options.password,
845
586
  allowList: parseAllowList(options.allow),
846
- verbose: options.verbose,
847
- mode
587
+ verbose: options.verbose
848
588
  });
849
- let p2pTunnel = null;
850
- let p2pError = null;
851
589
  try {
852
590
  await relayTunnel.connect();
853
591
  if (options.verbose) {
@@ -859,52 +597,22 @@ async function startCommand(port, options) {
859
597
  await analytics.shutdown();
860
598
  process.exit(1);
861
599
  }
862
- p2pTunnel = new P2PTunnel({
863
- localPort,
864
- tunnelId,
865
- verbose: options.verbose
866
- });
867
- try {
868
- await p2pTunnel.connect();
869
- await analytics.track("p2p_success", { mode });
870
- } catch (error) {
871
- p2pError = error;
872
- await analytics.track("p2p_failed", { mode, reason: p2pError.message });
873
- if (!options.relayFallback) {
874
- console.error("");
875
- console.error("P2P connection failed:", p2pError.message);
876
- console.error("");
877
- console.error("Subsequent requests from the browser will fail.");
878
- console.error("");
879
- console.error("Options:");
880
- console.error(" 1. Use --relay-fallback for automatic fallback");
881
- console.error(" 2. Try a different network (mobile hotspot, home WiFi)");
882
- console.error("");
883
- }
884
- }
885
600
  const { waitUntilExit } = render(
886
601
  React5.createElement(TunnelUI, {
887
602
  tunnelUrl,
888
603
  localPort,
889
604
  showQR: options.qr,
890
- mode,
891
- relayTunnel,
892
- p2pTunnel: p2pTunnel?.isConnected() ? p2pTunnel : null,
893
- p2pError
605
+ relayTunnel
894
606
  })
895
607
  );
896
608
  const cleanup = async () => {
897
609
  const duration = relayTunnel.getDuration();
898
- const relayRequests = relayTunnel.getRequestCount();
899
- const p2pRequests = p2pTunnel?.getRequestCount() ?? 0;
610
+ const requests = relayTunnel.getRequestCount();
900
611
  await analytics.track("tunnel_end", {
901
612
  duration_seconds: duration,
902
- requests_relay: relayRequests,
903
- requests_p2p: p2pRequests,
904
- p2p_success: p2pTunnel?.isConnected() ?? false
613
+ requests
905
614
  });
906
615
  await relayTunnel.disconnect();
907
- p2pTunnel?.disconnect();
908
616
  await analytics.shutdown();
909
617
  };
910
618
  process.on("SIGINT", async () => {
@@ -935,8 +643,8 @@ async function stopCommand(tunnelId) {
935
643
 
936
644
  // src/index.ts
937
645
  var program = new Command();
938
- program.name("untunneled").description("Privacy-first localhost tunneling - P2P by default").version("0.1.0");
939
- program.argument("<port>", "Local port to tunnel").option("-r, --relay-fallback", "Enable HTTP relay fallback if P2P fails").option("--qr", "Show QR code for mobile access").option("--password <password>", "Password protect the tunnel").option("--allow <emails>", "Email whitelist (comma-separated)").option("--team <name>", "Team tunnel name").option("--verbose", "Show detailed logs").option("--no-telemetry", "Disable anonymous usage tracking").action(startCommand);
646
+ program.name("untunneled").description("Fast, free localhost tunneling").version("0.2.2");
647
+ program.argument("<port>", "Local port to tunnel").option("--qr", "Show QR code for mobile access").option("--password <password>", "Password protect the tunnel").option("--allow <emails>", "Email whitelist (comma-separated)").option("--team <name>", "Team tunnel name").option("--verbose", "Show detailed logs").option("--no-telemetry", "Disable anonymous usage tracking").action(startCommand);
940
648
  program.command("list").description("List active tunnels").action(listCommand);
941
649
  program.command("stop").argument("<tunnel-id>", "Tunnel ID to stop").description("Stop a running tunnel").action(stopCommand);
942
650
  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/tunnel/p2p.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('Privacy-first localhost tunneling - P2P by default')\n .version('0.1.0');\n\nprogram\n .argument('<port>', 'Local port to tunnel')\n .option('-r, --relay-fallback', 'Enable HTTP relay fallback if P2P fails')\n .option('--qr', 'Show QR code for mobile access')\n .option('--password <password>', 'Password protect the tunnel')\n .option('--allow <emails>', 'Email whitelist (comma-separated)')\n .option('--team <name>', 'Team tunnel name')\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 { P2PTunnel } from '../tunnel/p2p.js';\nimport { Analytics } from '../analytics.js';\nimport {\n generateTunnelId,\n getProjectName,\n getTunnelUrl,\n validatePort,\n parseAllowList,\n} from '../config.js';\nimport type { StartOptions, TunnelMode } 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 const mode: TunnelMode = options.relayFallback ? 'p2p-with-fallback' : 'p2p-only';\n\n const analytics = new Analytics({\n enabled: options.telemetry !== false,\n tunnelId,\n });\n\n await analytics.track('tunnel_start', {\n mode,\n has_auth: !!options.password || !!options.allow,\n node_version: process.version,\n platform: process.platform,\n });\n\n const relayTunnel = new RelayTunnel({\n localPort,\n tunnelId,\n tunnelUrl,\n password: options.password,\n allowList: parseAllowList(options.allow),\n verbose: options.verbose,\n mode,\n });\n\n let p2pTunnel: P2PTunnel | null = null;\n let p2pError: Error | null = null;\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 p2pTunnel = new P2PTunnel({\n localPort,\n tunnelId,\n verbose: options.verbose,\n });\n\n try {\n await p2pTunnel.connect();\n await analytics.track('p2p_success', { mode });\n } catch (error) {\n p2pError = error as Error;\n await analytics.track('p2p_failed', { mode, reason: p2pError.message });\n\n if (!options.relayFallback) {\n console.error('');\n console.error('P2P connection failed:', p2pError.message);\n console.error('');\n console.error('Subsequent requests from the browser will fail.');\n console.error('');\n console.error('Options:');\n console.error(' 1. Use --relay-fallback for automatic fallback');\n console.error(' 2. Try a different network (mobile hotspot, home WiFi)');\n console.error('');\n }\n }\n\n const { waitUntilExit } = render(\n React.createElement(TunnelUI, {\n tunnelUrl,\n localPort,\n showQR: options.qr,\n mode,\n relayTunnel,\n p2pTunnel: p2pTunnel?.isConnected() ? p2pTunnel : null,\n p2pError,\n })\n );\n\n const cleanup = async () => {\n const duration = relayTunnel.getDuration();\n const relayRequests = relayTunnel.getRequestCount();\n const p2pRequests = p2pTunnel?.getRequestCount() ?? 0;\n\n await analytics.track('tunnel_end', {\n duration_seconds: duration,\n requests_relay: relayRequests,\n requests_p2p: p2pRequests,\n p2p_success: p2pTunnel?.isConnected() ?? false,\n });\n\n await relayTunnel.disconnect();\n p2pTunnel?.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, Newline, useApp } 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 { P2PTunnel } from '../tunnel/p2p.js';\nimport type { RequestLog as RequestLogType, TunnelMode, TunnelStats } from '../types.js';\n\ninterface TunnelUIProps {\n tunnelUrl: string;\n localPort: number;\n showQR?: boolean;\n mode: TunnelMode;\n relayTunnel: RelayTunnel;\n p2pTunnel?: P2PTunnel | null;\n p2pError?: Error | null;\n}\n\nconst MAX_LOGS = 10;\n\nexport const TunnelUI: React.FC<TunnelUIProps> = ({\n tunnelUrl,\n localPort,\n showQR,\n mode,\n relayTunnel,\n p2pTunnel,\n p2pError,\n}) => {\n useApp();\n const [requests, setRequests] = useState<RequestLogType[]>([]);\n const [stats, setStats] = useState<TunnelStats>({\n total: 0,\n p2p: 0,\n relay: 0,\n startTime: Date.now(),\n });\n const [relayConnected, setRelayConnected] = useState(relayTunnel.isConnected());\n const [p2pConnected, setP2pConnected] = useState(p2pTunnel?.isConnected() ?? false);\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 p2p: prev.p2p + (req.type === 'p2p' ? 1 : 0),\n relay: prev.relay + (req.type === 'relay' ? 1 : 0),\n }));\n };\n\n const handleRelayConnected = () => setRelayConnected(true);\n const handleRelayDisconnected = () => setRelayConnected(false);\n const handleP2PConnected = () => setP2pConnected(true);\n const handleP2PDisconnected = () => setP2pConnected(false);\n\n relayTunnel.on('request', handleRequest);\n relayTunnel.on('connected', handleRelayConnected);\n relayTunnel.on('disconnected', handleRelayDisconnected);\n\n if (p2pTunnel) {\n p2pTunnel.on('request', handleRequest);\n p2pTunnel.on('connected', handleP2PConnected);\n p2pTunnel.on('disconnected', handleP2PDisconnected);\n }\n\n return () => {\n relayTunnel.off('request', handleRequest);\n relayTunnel.off('connected', handleRelayConnected);\n relayTunnel.off('disconnected', handleRelayDisconnected);\n\n if (p2pTunnel) {\n p2pTunnel.off('request', handleRequest);\n p2pTunnel.off('connected', handleP2PConnected);\n p2pTunnel.off('disconnected', handleP2PDisconnected);\n }\n };\n }, [relayTunnel, p2pTunnel]);\n\n const connectionStatus = relayConnected || p2pConnected ? 'connected' : 'connecting';\n const borderColor = p2pConnected ? 'green' : relayConnected ? 'yellow' : 'gray';\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Box borderStyle=\"round\" borderColor={borderColor} padding={1} flexDirection=\"column\">\n <Text bold color={borderColor}>\n {p2pConnected ? '🔒' : '⚡'} untunneled.dev\n </Text>\n <Newline />\n <ConnectionStatus\n status={connectionStatus}\n mode={mode}\n p2pConnected={p2pConnected}\n relayConnected={relayConnected}\n />\n <Newline />\n <Box>\n <Text>URL: </Text>\n <Text bold color=\"cyan\">\n {tunnelUrl}\n </Text>\n </Box>\n <Box>\n <Text dimColor>→ localhost:{localPort}</Text>\n </Box>\n </Box>\n\n {p2pError && mode === 'p2p-only' && (\n <Box\n marginTop={1}\n borderStyle=\"single\"\n borderColor=\"red\"\n padding={1}\n flexDirection=\"column\"\n >\n <Text color=\"red\" bold>\n P2P Connection Failed\n </Text>\n <Text dimColor>{p2pError.message}</Text>\n <Newline />\n <Text dimColor>Use --relay-fallback for guaranteed compatibility</Text>\n </Box>\n )}\n\n {showQR && (\n <Box marginTop={1}>\n <QRCode url={tunnelUrl} />\n </Box>\n )}\n\n <Box marginTop={1} gap={2}>\n <Text>\n Requests: <Text color=\"cyan\">{stats.total}</Text>\n </Text>\n {mode !== 'relay' && stats.total > 0 && (\n <>\n <Text>\n P2P: <Text color=\"green\">{stats.p2p}</Text>\n </Text>\n <Text>\n Relay: <Text color=\"yellow\">{stats.relay}</Text>\n </Text>\n <Text dimColor>({Math.round((stats.p2p / stats.total) * 100)}% private)</Text>\n </>\n )}\n </Box>\n\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>Recent requests:</Text>\n <RequestLog requests={requests} />\n </Box>\n\n <Box marginTop={1}>\n <Text dimColor>Press Ctrl+C to stop</Text>\n </Box>\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 'blue';\n return 'green';\n};\n\nconst getMethodColor = (method: string): string => {\n switch (method) {\n case 'GET':\n return 'green';\n case 'POST':\n return 'yellow';\n case 'PUT':\n return 'blue';\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 <Text dimColor> Waiting for requests...</Text>;\n }\n\n return (\n <Box flexDirection=\"column\">\n {requests.map((req) => (\n <Box key={req.id} gap={1}>\n <Text dimColor>{req.timestamp}</Text>\n <Box width={7}>\n <Text color={getMethodColor(req.method)}>{req.method.padEnd(6)}</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={5}>\n <Text color={getStatusColor(req.status)}>{req.status}</Text>\n </Box>\n <Box width={8}>\n <Text dimColor>{req.duration}ms</Text>\n </Box>\n <Box width={7}>\n <Text color={req.type === 'p2p' ? 'green' : 'yellow'}>[{req.type.toUpperCase()}]</Text>\n </Box>\n </Box>\n ))}\n </Box>\n );\n};\n","import React from 'react';\nimport { Box, Text } from 'ink';\nimport type { ConnectionStatus as ConnectionStatusType, TunnelMode } from '../types.js';\n\ninterface ConnectionStatusProps {\n status: ConnectionStatusType;\n mode: TunnelMode;\n p2pConnected: boolean;\n relayConnected: boolean;\n}\n\nconst getStatusIndicator = (status: ConnectionStatusType): { color: string; symbol: string } => {\n switch (status) {\n case 'connected':\n return { color: 'green', symbol: '●' };\n case 'connecting':\n return { color: 'yellow', symbol: '◐' };\n case 'disconnected':\n return { color: 'red', symbol: '○' };\n case 'error':\n return { color: 'red', symbol: '✗' };\n }\n};\n\nconst getModeLabel = (mode: TunnelMode, p2pConnected: boolean): string => {\n if (mode === 'relay') return 'HTTP Relay';\n if (mode === 'p2p-only') return p2pConnected ? 'P2P Direct' : 'P2P (connecting...)';\n return p2pConnected ? 'P2P + Relay Fallback' : 'Relay (P2P pending...)';\n};\n\nexport const ConnectionStatus: React.FC<ConnectionStatusProps> = ({\n status,\n mode,\n p2pConnected,\n relayConnected,\n}) => {\n const indicator = getStatusIndicator(status);\n const modeLabel = getModeLabel(mode, p2pConnected);\n\n return (\n <Box gap={2}>\n <Box>\n <Text color={indicator.color}>{indicator.symbol}</Text>\n <Text> </Text>\n <Text color={indicator.color}>{status.toUpperCase()}</Text>\n </Box>\n <Box>\n <Text dimColor>Mode: </Text>\n <Text color={p2pConnected ? 'green' : 'yellow'}>{modeLabel}</Text>\n </Box>\n {mode !== 'relay' && (\n <Box>\n <Text dimColor>P2P: </Text>\n <Text color={p2pConnected ? 'green' : 'yellow'}>\n {p2pConnected ? 'Active' : 'Pending'}\n </Text>\n </Box>\n )}\n <Box>\n <Text dimColor>Relay: </Text>\n <Text color={relayConnected ? 'green' : 'red'}>\n {relayConnected ? 'Connected' : 'Disconnected'}\n </Text>\n </Box>\n </Box>\n );\n};\n","import { WebSocket } from 'ws';\nimport { EventEmitter } from 'node:events';\nimport { getRelayUrl, TIMEOUTS } from '../config.js';\nimport type { RelayRequest, RelayResponse, RequestLog, TunnelMode } from '../types.js';\n\ninterface RelayTunnelOptions {\n localPort: number;\n tunnelId: string;\n tunnelUrl: string;\n password?: string;\n allowList?: string[];\n verbose?: boolean;\n mode?: TunnelMode;\n}\n\nexport class RelayTunnel extends EventEmitter {\n private ws: WebSocket | null = null;\n private localPort: number;\n private tunnelId: string;\n private tunnelUrl: string;\n private options: RelayTunnelOptions;\n private startTime: number = 0;\n private requestCount: number = 0;\n private reconnectAttempts: number = 0;\n private isClosing: boolean = false;\n private pingInterval: NodeJS.Timeout | null = null;\n\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, this.options.mode);\n \n if (this.options.verbose) {\n console.log(`[Relay] Connecting to ${wsUrl}`);\n }\n\n return new Promise((resolve, reject) => {\n try {\n this.ws = new WebSocket(wsUrl, {\n headers: {\n 'X-Tunnel-Auth': this.options.password || '',\n 'X-Tunnel-Allow': this.options.allowList?.join(',') || '',\n 'X-Tunnel-Mode': this.options.mode || 'relay',\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.P2P_CONNECTION);\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.handleRelayRequest(data);\n });\n } catch (error) {\n reject(error);\n }\n });\n }\n\n private async handleRelayRequest(data: Buffer | ArrayBuffer | Buffer[]): Promise<void> {\n const startTime = Date.now();\n let request: RelayRequest;\n\n try {\n request = JSON.parse(String(data)) as RelayRequest;\n } catch {\n if (this.options.verbose) {\n console.error('[Relay] Invalid request data');\n }\n return;\n }\n\n try {\n const url = `http://localhost:${this.localPort}${request.path}`;\n\n const fetchOptions: RequestInit = {\n method: request.method,\n headers: request.headers,\n };\n\n if (request.body && !['GET', 'HEAD'].includes(request.method)) {\n fetchOptions.body = request.body;\n }\n\n const response = await fetch(url, fetchOptions);\n const body = await response.text();\n const duration = Date.now() - startTime;\n\n const relayResponse: RelayResponse = {\n 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: new Date().toISOString().substring(11, 19),\n method: request.method,\n path: request.path,\n status: response.status,\n duration,\n type: 'relay',\n };\n\n this.emit('request', log);\n this.requestCount++;\n } catch (error) {\n const err = error as Error;\n\n if (this.options.verbose) {\n console.error(`[Relay] Proxy error: ${err.message}`);\n }\n\n const errorResponse: RelayResponse = {\n id: request.id,\n status: 502,\n statusText: 'Bad Gateway',\n headers: {},\n body: `Error: ${err.message}`,\n };\n\n this.ws?.send(JSON.stringify(errorResponse));\n }\n }\n\n private 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 if (this.ws) {\n this.ws.close(1000, 'Client closing');\n this.ws = null;\n }\n }\n\n getDuration(): number {\n if (this.startTime === 0) return 0;\n return Math.floor((Date.now() - this.startTime) / 1000);\n }\n\n getRequestCount(): number {\n return this.requestCount;\n }\n\n isConnected(): boolean {\n return this.ws !== null && this.ws.readyState === WebSocket.OPEN;\n }\n\n getTunnelUrl(): string {\n return this.tunnelUrl;\n }\n}\n","import { nanoid } from 'nanoid';\nimport path from 'node:path';\n\nexport const RELAY_DOMAIN = 'relay.untunneled.dev';\nexport const SIGNALING_DOMAIN = 'signal.untunneled.dev';\nexport const TUNNEL_DOMAIN = 'untunneled.dev';\n\nexport const getRelayUrl = (tunnelId: string, mode?: string): string => {\n const host = process.env['UNTUNNELED_RELAY_HOST'] || RELAY_DOMAIN;\n const protocol = host.includes('localhost') ? 'ws' : 'wss';\n const modeParam = mode ? `?mode=${mode}` : '';\n return `${protocol}://${host}/${tunnelId}${modeParam}`;\n};\n\nexport const getSignalingUrl = (): string => {\n const host = process.env['UNTUNNELED_SIGNALING_HOST'] || SIGNALING_DOMAIN;\n const protocol = host.includes('localhost') ? 'ws' : 'wss';\n return `${protocol}://${host}`;\n};\n\nexport const getTunnelUrl = (tunnelId: string): string => {\n const domain = process.env['UNTUNNELED_TUNNEL_DOMAIN'] || TUNNEL_DOMAIN;\n return `https://${tunnelId}.${domain}`;\n};\n\nexport function getProjectName(): string {\n const cwd = process.cwd();\n const folderName = path.basename(cwd);\n\n return (\n folderName\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, 20) || 'tunnel'\n );\n}\n\nexport function generateTunnelId(projectName?: string): string {\n const name = projectName || getProjectName();\n const suffix = nanoid(6).toLowerCase();\n return `${name}-${suffix}`;\n}\n\nexport function validatePort(port: string | number): number {\n const portNum = typeof port === 'string' ? parseInt(port, 10) : port;\n\n if (isNaN(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error(`Invalid port number: ${port}. Must be between 1 and 65535.`);\n }\n\n return portNum;\n}\n\nexport function parseAllowList(allow?: string): string[] | undefined {\n if (!allow) return undefined;\n\n return allow\n .split(',')\n .map((email) => email.trim().toLowerCase())\n .filter((email) => email.length > 0);\n}\n\nexport const ICE_SERVERS = [\n { urls: 'stun:stun.l.google.com:19302' },\n { urls: 'stun:stun1.l.google.com:19302' },\n { urls: 'stun:stun2.l.google.com:19302' },\n { urls: 'stun:global.stun.twilio.com:3478' },\n];\n\nexport const TIMEOUTS = {\n P2P_CONNECTION: 30000,\n RELAY_REQUEST: 30000,\n WEBSOCKET_PING: 30000,\n RECONNECT_BASE: 1000,\n RECONNECT_MAX: 30000,\n};\n","import wrtc from '@roamhq/wrtc';\nimport { WebSocket } from 'ws';\nimport { EventEmitter } from 'node:events';\nimport { getSignalingUrl, ICE_SERVERS, TIMEOUTS } from '../config.js';\nimport type { RelayRequest, RelayResponse, RequestLog, SignalingMessage } from '../types.js';\n\nconst { RTCPeerConnection, RTCSessionDescription, RTCIceCandidate } = wrtc;\n\ntype WrtcPeerConnection = InstanceType<typeof RTCPeerConnection>;\ntype WrtcDataChannel = ReturnType<WrtcPeerConnection['createDataChannel']>;\n\ninterface P2PTunnelOptions {\n localPort: number;\n tunnelId: string;\n verbose?: boolean;\n}\n\nexport class P2PTunnel extends EventEmitter {\n private pc: WrtcPeerConnection | null = null;\n private dataChannel: WrtcDataChannel | null = null;\n private ws: WebSocket | null = null;\n private localPort: number;\n private tunnelId: string;\n private options: P2PTunnelOptions;\n private connected: boolean = false;\n private requestCount: number = 0;\n private signalingConnected: boolean = false;\n private pingInterval: NodeJS.Timeout | null = null;\n\n constructor(options: P2PTunnelOptions) {\n super();\n this.options = options;\n this.localPort = options.localPort;\n this.tunnelId = options.tunnelId;\n }\n\n async connect(): Promise<void> {\n if (this.options.verbose) {\n console.log('[P2P] Attempting connection...');\n }\n\n return new Promise((resolve, reject) => {\n const signalingUrl = getSignalingUrl();\n this.ws = new WebSocket(signalingUrl);\n\n const connectionTimeout = setTimeout(() => {\n if (!this.connected) {\n reject(new Error('P2P connection timeout'));\n }\n }, TIMEOUTS.P2P_CONNECTION);\n\n this.ws.on('open', () => {\n this.signalingConnected = true;\n if (this.options.verbose) {\n console.log('[P2P] Signaling connected');\n }\n\n this.setupPing();\n\n const registerMsg: SignalingMessage = {\n type: 'register',\n tunnelId: this.tunnelId,\n role: 'cli',\n };\n this.ws!.send(JSON.stringify(registerMsg));\n\n this.initPeerConnection();\n });\n\n this.ws.on('message', async (data) => {\n const msg = JSON.parse(data.toString()) as SignalingMessage;\n\n if (this.options.verbose) {\n console.log('[P2P] Received:', msg.type, msg.from || '');\n }\n\n if (msg.type === 'signal' && msg.from === 'browser') {\n await this.handleBrowserSignal(msg, connectionTimeout, resolve);\n }\n });\n\n this.ws.on('error', (err) => {\n clearTimeout(connectionTimeout);\n this.signalingConnected = false;\n reject(err);\n });\n\n this.ws.on('close', () => {\n this.signalingConnected = false;\n this.clearPing();\n if (this.options.verbose) {\n console.log('[P2P] Signaling closed');\n }\n });\n });\n }\n\n private initPeerConnection(): void {\n this.pc = new RTCPeerConnection({\n iceServers: ICE_SERVERS,\n });\n\n this.pc.onicecandidate = (event: {\n candidate: InstanceType<typeof RTCIceCandidate> | null;\n }) => {\n if (event.candidate && this.ws && this.signalingConnected) {\n if (this.options.verbose) {\n console.log('[P2P] Sending ICE candidate');\n }\n const signalMsg: SignalingMessage = {\n type: 'signal',\n from: 'cli',\n to: 'browser',\n tunnelId: this.tunnelId,\n signal: { candidate: event.candidate },\n };\n this.ws.send(JSON.stringify(signalMsg));\n }\n };\n\n this.pc.oniceconnectionstatechange = () => {\n if (this.options.verbose) {\n console.log('[P2P] ICE state:', this.pc?.iceConnectionState);\n }\n };\n\n this.pc.ondatachannel = (event: { channel: WrtcDataChannel }) => {\n if (this.options.verbose) {\n console.log('[P2P] Received data channel');\n }\n this.dataChannel = event.channel;\n this.setupDataChannel();\n };\n }\n\n private async handleBrowserSignal(\n msg: SignalingMessage,\n connectionTimeout: NodeJS.Timeout,\n resolve: () => void\n ): Promise<void> {\n if (!this.pc) {\n this.initPeerConnection();\n }\n\n const signal = msg.signal as {\n type?: string;\n sdp?: string;\n candidate?: Record<string, unknown>;\n };\n\n if (signal.type === 'offer' && signal.sdp) {\n if (this.options.verbose) {\n console.log('[P2P] Received offer, creating answer');\n }\n try {\n await this.pc!.setRemoteDescription(\n new RTCSessionDescription({ type: 'offer', sdp: signal.sdp })\n );\n const answer = await this.pc!.createAnswer();\n await this.pc!.setLocalDescription(answer);\n\n const signalMsg: SignalingMessage = {\n type: 'signal',\n from: 'cli',\n to: 'browser',\n tunnelId: this.tunnelId,\n signal: { type: 'answer', sdp: this.pc!.localDescription?.sdp },\n };\n this.ws!.send(JSON.stringify(signalMsg));\n if (this.options.verbose) {\n console.log('[P2P] Sent answer');\n }\n\n this.setupDataChannelResolver(connectionTimeout, resolve);\n } catch (err) {\n console.error('[P2P] Error handling offer:', err);\n }\n } else if (signal.candidate) {\n if (this.options.verbose) {\n console.log('[P2P] Adding ICE candidate');\n }\n try {\n await this.pc!.addIceCandidate(new RTCIceCandidate(signal.candidate));\n } catch (err) {\n console.error('[P2P] Error adding ICE candidate:', err);\n }\n }\n }\n\n private setupDataChannelResolver(connectionTimeout: NodeJS.Timeout, resolve: () => void): void {\n const checkConnection = () => {\n if (this.connected) {\n clearTimeout(connectionTimeout);\n resolve();\n }\n };\n\n this.once('connected', checkConnection);\n }\n\n private setupDataChannel(): void {\n if (!this.dataChannel) return;\n\n this.dataChannel.onopen = () => {\n this.connected = true;\n this.emit('connected');\n\n if (this.options.verbose) {\n console.log('[P2P] Data channel open');\n }\n };\n\n this.dataChannel.onmessage = async (event: MessageEvent) => {\n await this.handleP2PRequest(event.data);\n };\n\n this.dataChannel.onerror = (event: unknown) => {\n console.error('[P2P] Data channel error:', event);\n };\n\n this.dataChannel.onclose = () => {\n this.connected = false;\n this.emit('disconnected');\n if (this.options.verbose) {\n console.log('[P2P] Data channel closed');\n }\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 handleP2PRequest(data: string): Promise<void> {\n const startTime = Date.now();\n let request: RelayRequest;\n\n try {\n request = JSON.parse(data) as RelayRequest;\n } catch {\n return;\n }\n\n try {\n const url = `http://localhost:${this.localPort}${request.path}`;\n\n const fetchOptions: RequestInit = {\n method: request.method,\n headers: request.headers,\n };\n\n if (request.body && !['GET', 'HEAD'].includes(request.method)) {\n fetchOptions.body = request.body;\n }\n\n const response = await fetch(url, fetchOptions);\n const body = await response.text();\n const duration = Date.now() - startTime;\n\n const p2pResponse: RelayResponse = {\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.dataChannel?.send(JSON.stringify(p2pResponse));\n\n const log: RequestLog = {\n id: request.id,\n timestamp: new Date().toISOString().substring(11, 19),\n method: request.method,\n path: request.path,\n status: response.status,\n duration,\n type: 'p2p',\n };\n\n this.emit('request', log);\n this.requestCount++;\n } catch (error) {\n const err = error as Error;\n\n const errorResponse: RelayResponse = {\n id: request.id,\n status: 502,\n statusText: 'Bad Gateway',\n headers: {},\n body: `Error: ${err.message}`,\n };\n\n this.dataChannel?.send(JSON.stringify(errorResponse));\n }\n }\n\n private cleanup(): void {\n this.clearPing();\n this.dataChannel?.close();\n this.pc?.close();\n this.ws?.close();\n this.dataChannel = null;\n this.pc = null;\n this.ws = null;\n this.connected = false;\n this.signalingConnected = false;\n }\n\n disconnect(): void {\n this.cleanup();\n }\n\n isConnected(): boolean {\n return this.connected;\n }\n\n isSignalingConnected(): boolean {\n return this.signalingConnected;\n }\n\n getRequestCount(): number {\n return this.requestCount;\n }\n}\n","import { PostHog } from 'posthog-node';\n\nconst POSTHOG_API_KEY = process.env['POSTHOG_API_KEY'] || 'phc_placeholder';\nconst POSTHOG_HOST = 'https://app.posthog.com';\n\ninterface AnalyticsOptions {\n enabled: boolean;\n tunnelId: string;\n}\n\ntype EventName = 'tunnel_start' | 'tunnel_end' | 'p2p_success' | 'p2p_failed' | 'request_handled';\n\ninterface EventProperties {\n tunnel_start: {\n mode: string;\n has_auth: boolean;\n node_version: string;\n platform: string;\n };\n tunnel_end: {\n duration_seconds: number;\n requests_relay: number;\n requests_p2p: number;\n p2p_success: boolean;\n };\n p2p_success: {\n mode: string;\n connection_time_ms?: number;\n };\n p2p_failed: {\n mode: string;\n reason: string;\n };\n request_handled: {\n transport: 'p2p' | 'relay';\n method: string;\n status: number;\n duration_ms: number;\n };\n}\n\nexport class Analytics {\n private posthog: PostHog | null = null;\n private tunnelId: string;\n private enabled: boolean;\n\n constructor(options: AnalyticsOptions) {\n this.tunnelId = options.tunnelId;\n this.enabled = options.enabled;\n\n if (this.enabled && POSTHOG_API_KEY !== 'phc_placeholder') {\n this.posthog = new PostHog(POSTHOG_API_KEY, {\n host: POSTHOG_HOST,\n flushAt: 10,\n flushInterval: 10000,\n });\n }\n }\n\n async track<E extends EventName>(event: E, properties?: EventProperties[E]): Promise<void> {\n if (!this.posthog || !this.enabled) return;\n\n try {\n this.posthog.capture({\n distinctId: this.tunnelId,\n event,\n properties: {\n ...properties,\n version: '0.1.0',\n cli: true,\n },\n });\n } catch (_) {}\n }\n\n async shutdown(): Promise<void> {\n if (!this.posthog) return;\n\n try {\n await this.posthog.shutdown();\n } catch (_) {}\n }\n\n isEnabled(): boolean {\n return this.enabled && this.posthog !== null;\n }\n}\n","export async function listCommand(): Promise<void> {\n console.log('Active tunnels:');\n console.log(' (No active tunnels)');\n console.log('');\n console.log('Note: Tunnel list feature coming soon.');\n}\n","export async function stopCommand(tunnelId: string): Promise<void> {\n console.log(`Stopping tunnel: ${tunnelId}`);\n console.log('');\n console.log('Note: Remote stop feature coming soon.');\n}\n"],"mappings":"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","names":["React","useState","useEffect","Box","Text","Box","Text","jsx","jsxs","Box","Text","jsx","jsxs","jsx","jsxs","useState","useEffect","Box","Text","WebSocket","EventEmitter","EventEmitter","WebSocket","React"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/start.ts","../src/ui/TunnelUI.tsx","../src/ui/QRCode.tsx","../src/ui/RequestLog.tsx","../src/ui/ConnectionStatus.tsx","../src/tunnel/relay.ts","../src/config.ts","../src/analytics.ts","../src/commands/list.ts","../src/commands/stop.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { startCommand, listCommand, stopCommand } from './commands/index.js';\n\nconst program = new Command();\n\nprogram.name('untunneled').description('Fast, free localhost tunneling').version('0.2.2');\n\nprogram\n .argument('<port>', 'Local port to tunnel')\n .option('--qr', 'Show QR code for mobile access')\n .option('--password <password>', 'Password protect the tunnel')\n .option('--allow <emails>', 'Email whitelist (comma-separated)')\n .option('--team <name>', 'Team tunnel name')\n .option('--verbose', 'Show detailed logs')\n .option('--no-telemetry', 'Disable anonymous usage tracking')\n .action(startCommand);\n\nprogram.command('list').description('List active tunnels').action(listCommand);\n\nprogram\n .command('stop')\n .argument('<tunnel-id>', 'Tunnel ID to stop')\n .description('Stop a running tunnel')\n .action(stopCommand);\n\nprogram.parse();\n","import { render } from 'ink';\nimport React from 'react';\nimport { TunnelUI } from '../ui/TunnelUI.js';\nimport { RelayTunnel } from '../tunnel/relay.js';\nimport { Analytics } from '../analytics.js';\nimport {\n generateTunnelId,\n getProjectName,\n getTunnelUrl,\n validatePort,\n parseAllowList,\n} from '../config.js';\nimport type { StartOptions } from '../types.js';\n\nexport async function startCommand(port: string, options: StartOptions): Promise<void> {\n let localPort: number;\n\n try {\n localPort = validatePort(port);\n } catch (error) {\n console.error((error as Error).message);\n process.exit(1);\n }\n\n const projectName = getProjectName();\n const tunnelId = generateTunnelId(projectName);\n const tunnelUrl = getTunnelUrl(tunnelId);\n\n const analytics = new Analytics({\n enabled: options.telemetry !== false,\n tunnelId,\n });\n\n await analytics.track('tunnel_start', {\n has_auth: !!options.password || !!options.allow,\n node_version: process.version,\n platform: process.platform,\n });\n\n const relayTunnel = new RelayTunnel({\n localPort,\n tunnelId,\n tunnelUrl,\n password: options.password,\n allowList: parseAllowList(options.allow),\n verbose: options.verbose,\n });\n\n try {\n await relayTunnel.connect();\n\n if (options.verbose) {\n console.log(`Tunnel URL: ${tunnelUrl}`);\n console.log(`Forwarding to localhost:${localPort}`);\n }\n } catch (error) {\n console.error('Failed to connect to relay server:', (error as Error).message);\n await analytics.shutdown();\n process.exit(1);\n }\n\n const { waitUntilExit } = render(\n React.createElement(TunnelUI, {\n tunnelUrl,\n localPort,\n showQR: options.qr,\n relayTunnel,\n })\n );\n\n const cleanup = async () => {\n const duration = relayTunnel.getDuration();\n const requests = relayTunnel.getRequestCount();\n\n await analytics.track('tunnel_end', {\n duration_seconds: duration,\n requests: requests,\n });\n\n await relayTunnel.disconnect();\n await analytics.shutdown();\n };\n\n process.on('SIGINT', async () => {\n await cleanup();\n process.exit(0);\n });\n\n process.on('SIGTERM', async () => {\n await cleanup();\n process.exit(0);\n });\n\n await waitUntilExit();\n}\n","import React, { useState, useEffect } from 'react';\nimport { Box, Text, useApp } 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\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.2.2</Text>\n </Box>\n </Box>\n\n {/* Main Connection Info */}\n <Box\n borderStyle=\"round\"\n borderColor={accentColor}\n paddingX={2}\n paddingY={1}\n flexDirection=\"column\"\n >\n <Box>\n <Text bold>Public URL: </Text>\n <Text bold color=\"black\" backgroundColor=\"cyan\">\n {' '}\n {tunnelUrl}{' '}\n </Text>\n </Box>\n <Box marginTop={1}>\n <Text dimColor>Forwarding: </Text>\n <Text color=\"yellow\">http://localhost:{localPort}</Text>\n <Text dimColor> → </Text>\n <Text color=\"cyan\">{tunnelUrl}</Text>\n </Box>\n </Box>\n\n {/* Stats Bar */}\n <Box marginTop={1} paddingX={1} gap={4}>\n <Box>\n <Text dimColor>Requests: </Text>\n <Text color=\"white\" bold>\n {stats.total}\n </Text>\n </Box>\n <Box>\n <Text dimColor>Uptime: </Text>\n <Text color=\"white\" bold>\n {formatUptime(uptime)}\n </Text>\n </Box>\n <Box>\n <Text dimColor>Latency: </Text>\n <Text color=\"white\" bold>\n {requests.length > 0 ? `${requests[requests.length - 1]?.duration}ms` : '-'}\n </Text>\n </Box>\n </Box>\n\n {/* QR Code */}\n {showQR && (\n <Box\n marginY={1}\n borderStyle=\"single\"\n borderColor=\"gray\"\n paddingX={1}\n alignSelf=\"flex-start\"\n >\n <QRCode url={tunnelUrl} />\n </Box>\n )}\n\n {/* Request Log */}\n <Box marginTop={1} flexDirection=\"column\">\n <Box marginBottom={1}>\n <Text bold color=\"cyan\">\n ─── Recent Requests ───\n </Text>\n </Box>\n <RequestLog requests={requests} />\n </Box>\n\n {/* Footer */}\n <Box\n marginTop={1}\n borderStyle=\"single\"\n borderTop={true}\n borderBottom={false}\n borderLeft={false}\n borderRight={false}\n borderColor=\"gray\"\n paddingTop={1}\n >\n <Box flexGrow={1}>\n <Text dimColor>Press </Text>\n <Text color=\"yellow\" bold>\n Ctrl+C\n </Text>\n <Text dimColor> to stop tunnel</Text>\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 </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 paddingY={1} flexDirection=\"column\" alignItems=\"center\">\n <Text dimColor>┌─────────────────────────────────────┐</Text>\n <Text dimColor>│ │</Text>\n <Text dimColor>\n │ <Text color=\"cyan\">⏳</Text> Waiting for requests... │\n </Text>\n <Text dimColor>│ │</Text>\n <Text dimColor>│ Open the URL in your browser │</Text>\n <Text dimColor>│ │</Text>\n <Text dimColor>└─────────────────────────────────────┘</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 { RelayRequest, RelayResponse, RequestLog } from '../types.js';\n\ninterface RelayTunnelOptions {\n localPort: number;\n tunnelId: string;\n tunnelUrl: string;\n password?: string;\n allowList?: string[];\n verbose?: boolean;\n}\n\nexport class RelayTunnel extends EventEmitter {\n private ws: WebSocket | null = null;\n private localPort: number;\n private tunnelId: string;\n private tunnelUrl: string;\n private options: RelayTunnelOptions;\n private startTime: number = 0;\n private requestCount: number = 0;\n private reconnectAttempts: number = 0;\n private isClosing: boolean = false;\n private pingInterval: NodeJS.Timeout | null = null;\n\n constructor(options: RelayTunnelOptions) {\n super();\n this.options = options;\n this.localPort = options.localPort;\n this.tunnelId = options.tunnelId;\n this.tunnelUrl = options.tunnelUrl;\n }\n\n async connect(): Promise<void> {\n this.startTime = Date.now();\n this.isClosing = false;\n\n const wsUrl = getRelayUrl(this.tunnelId);\n\n if (this.options.verbose) {\n console.log(`[Relay] Connecting to ${wsUrl}`);\n }\n\n return new Promise((resolve, reject) => {\n try {\n this.ws = new WebSocket(wsUrl, {\n headers: {\n 'X-Tunnel-Auth': this.options.password || '',\n 'X-Tunnel-Allow': this.options.allowList?.join(',') || '',\n },\n });\n\n const connectionTimeout = setTimeout(() => {\n if (this.ws && this.ws.readyState !== WebSocket.OPEN) {\n this.ws.terminate();\n reject(new Error('Connection timeout'));\n }\n }, TIMEOUTS.RELAY_REQUEST);\n\n this.ws.on('open', () => {\n clearTimeout(connectionTimeout);\n this.reconnectAttempts = 0;\n\n if (this.options.verbose) {\n console.log('[Relay] Connected');\n }\n\n this.setupPing();\n this.emit('connected');\n resolve();\n });\n\n this.ws.on('error', (err) => {\n clearTimeout(connectionTimeout);\n\n if (this.options.verbose) {\n console.error('[Relay] WebSocket error:', err.message);\n }\n\n this.emit('error', err);\n reject(err);\n });\n\n this.ws.on('close', () => {\n this.clearPing();\n\n if (!this.isClosing) {\n this.emit('disconnected');\n this.attemptReconnect();\n }\n });\n\n this.ws.on('message', async (data) => {\n await this.handleRelayRequest(data);\n });\n } catch (error) {\n reject(error);\n }\n });\n }\n\n private async handleRelayRequest(data: Buffer | ArrayBuffer | Buffer[]): Promise<void> {\n const startTime = Date.now();\n let request: RelayRequest;\n\n try {\n request = JSON.parse(String(data)) as RelayRequest;\n } catch {\n if (this.options.verbose) {\n console.error('[Relay] Invalid request data');\n }\n return;\n }\n\n try {\n const url = `http://localhost:${this.localPort}${request.path}`;\n\n const fetchOptions: RequestInit = {\n method: request.method,\n headers: request.headers,\n };\n\n if (request.body && !['GET', 'HEAD'].includes(request.method)) {\n fetchOptions.body = request.body;\n }\n\n const response = await fetch(url, fetchOptions);\n const body = await response.text();\n const duration = Date.now() - startTime;\n\n const relayResponse: RelayResponse = {\n 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: new Date().toISOString().substring(11, 19),\n method: request.method,\n path: request.path,\n status: response.status,\n duration,\n };\n\n this.emit('request', log);\n this.requestCount++;\n } catch (error) {\n const err = error as Error;\n\n if (this.options.verbose) {\n console.error(`[Relay] Proxy error: ${err.message}`);\n }\n\n const errorResponse: RelayResponse = {\n id: request.id,\n status: 502,\n statusText: 'Bad Gateway',\n headers: {},\n body: `Error: ${err.message}`,\n };\n\n this.ws?.send(JSON.stringify(errorResponse));\n }\n }\n\n private 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 if (this.ws) {\n this.ws.close(1000, 'Client closing');\n this.ws = null;\n }\n }\n\n getDuration(): number {\n if (this.startTime === 0) return 0;\n return Math.floor((Date.now() - this.startTime) / 1000);\n }\n\n getRequestCount(): number {\n return this.requestCount;\n }\n\n isConnected(): boolean {\n return this.ws !== null && this.ws.readyState === WebSocket.OPEN;\n }\n\n getTunnelUrl(): string {\n return this.tunnelUrl;\n }\n}\n","import { nanoid } from 'nanoid';\nimport path from 'node:path';\n\nexport const RELAY_DOMAIN = 'relay.untunneled.dev';\nexport const TUNNEL_DOMAIN = 'untunneled.dev';\n\nexport const getRelayUrl = (tunnelId: string): string => {\n const host = process.env['UNTUNNELED_RELAY_HOST'] || RELAY_DOMAIN;\n const protocol = host.includes('localhost') ? 'ws' : 'wss';\n return `${protocol}://${host}/${tunnelId}`;\n};\n\nexport const getTunnelUrl = (tunnelId: string): string => {\n const domain = process.env['UNTUNNELED_TUNNEL_DOMAIN'] || TUNNEL_DOMAIN;\n return `https://${tunnelId}.${domain}`;\n};\n\nexport function getProjectName(): string {\n const cwd = process.cwd();\n const folderName = path.basename(cwd);\n\n return (\n folderName\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n .slice(0, 20) || 'tunnel'\n );\n}\n\nexport function generateTunnelId(projectName?: string): string {\n const name = projectName || getProjectName();\n const suffix = nanoid(6).toLowerCase();\n return `${name}-${suffix}`;\n}\n\nexport function validatePort(port: string | number): number {\n const portNum = typeof port === 'string' ? parseInt(port, 10) : port;\n\n if (isNaN(portNum) || portNum < 1 || portNum > 65535) {\n throw new Error(`Invalid port number: ${port}. Must be between 1 and 65535.`);\n }\n\n return portNum;\n}\n\nexport function parseAllowList(allow?: string): string[] | undefined {\n if (!allow) return undefined;\n\n return allow\n .split(',')\n .map((email) => email.trim().toLowerCase())\n .filter((email) => email.length > 0);\n}\n\nexport const TIMEOUTS = {\n RELAY_REQUEST: 30000,\n WEBSOCKET_PING: 30000,\n RECONNECT_BASE: 1000,\n RECONNECT_MAX: 30000,\n};\n","import { PostHog } from 'posthog-node';\n\nconst POSTHOG_API_KEY = process.env['POSTHOG_API_KEY'] || 'phc_placeholder';\nconst POSTHOG_HOST = 'https://app.posthog.com';\n\ninterface AnalyticsOptions {\n enabled: boolean;\n tunnelId: string;\n}\n\ntype EventName = 'tunnel_start' | 'tunnel_end' | 'request_handled';\n\ninterface EventProperties {\n tunnel_start: {\n has_auth: boolean;\n node_version: string;\n platform: string;\n };\n tunnel_end: {\n duration_seconds: number;\n requests: number;\n };\n request_handled: {\n method: string;\n status: number;\n duration_ms: number;\n };\n}\n\nexport class Analytics {\n private posthog: PostHog | null = null;\n private tunnelId: string;\n private enabled: boolean;\n\n constructor(options: AnalyticsOptions) {\n this.tunnelId = options.tunnelId;\n this.enabled = options.enabled;\n\n if (this.enabled && POSTHOG_API_KEY !== 'phc_placeholder') {\n this.posthog = new PostHog(POSTHOG_API_KEY, {\n host: POSTHOG_HOST,\n flushAt: 10,\n flushInterval: 10000,\n });\n }\n }\n\n async track<E extends EventName>(event: E, properties?: EventProperties[E]): Promise<void> {\n if (!this.posthog || !this.enabled) return;\n\n try {\n this.posthog.capture({\n distinctId: this.tunnelId,\n event,\n properties: {\n ...properties,\n version: '0.1.0',\n cli: true,\n },\n });\n } catch (_) {}\n }\n\n async shutdown(): Promise<void> {\n if (!this.posthog) return;\n\n try {\n await this.posthog.shutdown();\n } catch (_) {}\n }\n\n isEnabled(): boolean {\n return this.enabled && this.posthog !== null;\n }\n}\n","export async function listCommand(): Promise<void> {\n console.log('Active tunnels:');\n console.log(' (No active tunnels)');\n console.log('');\n console.log('Note: Tunnel list feature coming soon.');\n}\n","export async function stopCommand(tunnelId: string): Promise<void> {\n console.log(`Stopping tunnel: ${tunnelId}`);\n console.log('');\n console.log('Note: Remote stop feature coming soon.');\n}\n"],"mappings":"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","names":["React","useState","useEffect","Box","Text","Box","Text","jsx","jsxs","useState","useEffect","Box","Text","jsx","jsxs","jsx","jsxs","useState","useEffect","Box","Text","React"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "untunneled.dev",
3
- "version": "0.1.4-alpha5",
4
- "description": "Privacy-first localhost tunneling - P2P by default",
3
+ "version": "0.2.2",
4
+ "description": "Fast, free localhost tunneling for developers",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -12,16 +12,12 @@
12
12
  "tunnel",
13
13
  "localhost",
14
14
  "ngrok",
15
- "p2p",
16
15
  "privacy",
17
- "webrtc",
18
- "peer-to-peer",
19
16
  "cli"
20
17
  ],
21
18
  "author": "",
22
19
  "license": "MIT",
23
20
  "dependencies": {
24
- "@roamhq/wrtc": "^0.8.0",
25
21
  "chalk": "^5.3.0",
26
22
  "commander": "^14.0.2",
27
23
  "ink": "^6.6.0",