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 +184 -476
- package/dist/index.js.map +1 -1
- package/package.json +2 -6
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
|
|
12
|
-
import { Box as Box4, Text as Text4,
|
|
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 "
|
|
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 "
|
|
49
|
+
return "cyan";
|
|
49
50
|
case "POST":
|
|
50
|
-
return "
|
|
51
|
+
return "green";
|
|
51
52
|
case "PUT":
|
|
52
|
-
return "
|
|
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__ */
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
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: "\
|
|
110
|
+
return { color: "green", symbol: "\u2714", label: "CONNECTED" };
|
|
90
111
|
case "connecting":
|
|
91
|
-
return { color: "yellow", symbol: "
|
|
112
|
+
return { color: "yellow", symbol: frame, label: "CONNECTING" };
|
|
92
113
|
case "disconnected":
|
|
93
|
-
return { color: "
|
|
114
|
+
return { color: "gray", symbol: "\u25CB", label: "DISCONNECTED" };
|
|
94
115
|
case "error":
|
|
95
|
-
return { color: "red", symbol: "\
|
|
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
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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 {
|
|
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
|
-
|
|
140
|
-
relayTunnel,
|
|
141
|
-
p2pTunnel,
|
|
142
|
-
p2pError
|
|
157
|
+
relayTunnel
|
|
143
158
|
}) => {
|
|
144
159
|
useApp();
|
|
145
|
-
const [requests, setRequests] =
|
|
146
|
-
const [stats, setStats] =
|
|
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] =
|
|
153
|
-
const [
|
|
154
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
181
|
-
p2pTunnel.off("request", handleRequest);
|
|
182
|
-
p2pTunnel.off("connected", handleP2PConnected);
|
|
183
|
-
p2pTunnel.off("disconnected", handleP2PDisconnected);
|
|
184
|
-
}
|
|
187
|
+
clearInterval(uptimeTimer);
|
|
185
188
|
};
|
|
186
|
-
}, [relayTunnel,
|
|
187
|
-
const connectionStatus = relayConnected
|
|
188
|
-
const
|
|
189
|
-
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column",
|
|
190
|
-
/* @__PURE__ */ jsxs4(Box4, {
|
|
191
|
-
/* @__PURE__ */
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
200
|
+
/* @__PURE__ */ jsxs4(
|
|
216
201
|
Box4,
|
|
217
202
|
{
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
203
|
+
borderStyle: "round",
|
|
204
|
+
borderColor: accentColor,
|
|
205
|
+
paddingX: 2,
|
|
206
|
+
paddingY: 1,
|
|
222
207
|
flexDirection: "column",
|
|
223
208
|
children: [
|
|
224
|
-
/* @__PURE__ */
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
238
|
-
/* @__PURE__ */
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
/* @__PURE__ */
|
|
243
|
-
|
|
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, {
|
|
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__ */
|
|
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
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
899
|
-
const p2pRequests = p2pTunnel?.getRequestCount() ?? 0;
|
|
610
|
+
const requests = relayTunnel.getRequestCount();
|
|
900
611
|
await analytics.track("tunnel_end", {
|
|
901
612
|
duration_seconds: duration,
|
|
902
|
-
|
|
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("
|
|
939
|
-
program.argument("<port>", "Local port to tunnel").option("
|
|
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.
|
|
4
|
-
"description": "
|
|
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",
|