tunnelhook 0.1.0 → 0.1.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/README.md +1 -1
- package/package.json +10 -1
- package/src/index.tsx +86 -17
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tunnelhook",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "CLI tool for receiving and forwarding webhooks via TunnelHook",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "src/index.tsx",
|
|
@@ -24,6 +24,15 @@
|
|
|
24
24
|
"forward"
|
|
25
25
|
],
|
|
26
26
|
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/plutolaboratories/tunnelhook.git",
|
|
30
|
+
"directory": "apps/tui"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"provenance": true,
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
27
36
|
"engines": {
|
|
28
37
|
"bun": ">=1.0.0"
|
|
29
38
|
},
|
package/src/index.tsx
CHANGED
|
@@ -19,8 +19,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
|
|
21
21
|
const SERVER_URL =
|
|
22
|
-
process.env.TUNNELHOOK_SERVER_URL ??
|
|
23
|
-
"https://tunnelhook-server-shkumbinhasani.shkumbinhasani20001439.workers.dev";
|
|
22
|
+
process.env.TUNNELHOOK_SERVER_URL ?? "https://api.tunnelhook.com";
|
|
24
23
|
const WS_URL = SERVER_URL.replace(/^http/, "ws");
|
|
25
24
|
|
|
26
25
|
// ---------------------------------------------------------------------------
|
|
@@ -166,10 +165,13 @@ function loadSession(): string | null {
|
|
|
166
165
|
function saveSession(cookies: string): void {
|
|
167
166
|
try {
|
|
168
167
|
if (!existsSync(CONFIG_DIR)) {
|
|
169
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
168
|
+
mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
170
169
|
}
|
|
171
170
|
const data: SessionData = { cookies, serverUrl: SERVER_URL };
|
|
172
|
-
writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2),
|
|
171
|
+
writeFileSync(SESSION_FILE, JSON.stringify(data, null, 2), {
|
|
172
|
+
encoding: "utf-8",
|
|
173
|
+
mode: 0o600,
|
|
174
|
+
});
|
|
173
175
|
} catch {
|
|
174
176
|
// Non-critical — session just won't persist
|
|
175
177
|
}
|
|
@@ -382,9 +384,39 @@ async function handleLoginCommand(): Promise<never> {
|
|
|
382
384
|
console.log(`Server: ${SERVER_URL}\n`);
|
|
383
385
|
|
|
384
386
|
const email = await ask("Email: ");
|
|
385
|
-
const password = await ask("Password: ");
|
|
386
387
|
rl.close();
|
|
387
388
|
|
|
389
|
+
// Read password with masked input (echo asterisks instead of plaintext)
|
|
390
|
+
const password = await new Promise<string>((resolve) => {
|
|
391
|
+
process.stdout.write("Password: ");
|
|
392
|
+
let pwd = "";
|
|
393
|
+
process.stdin.setRawMode(true);
|
|
394
|
+
process.stdin.resume();
|
|
395
|
+
process.stdin.setEncoding("utf8");
|
|
396
|
+
const onData = (ch: string) => {
|
|
397
|
+
if (ch === "\r" || ch === "\n") {
|
|
398
|
+
process.stdin.setRawMode(false);
|
|
399
|
+
process.stdin.pause();
|
|
400
|
+
process.stdin.removeListener("data", onData);
|
|
401
|
+
process.stdout.write("\n");
|
|
402
|
+
resolve(pwd);
|
|
403
|
+
} else if (ch === "\u007F" || ch === "\b") {
|
|
404
|
+
if (pwd.length > 0) {
|
|
405
|
+
pwd = pwd.slice(0, -1);
|
|
406
|
+
process.stdout.write("\b \b");
|
|
407
|
+
}
|
|
408
|
+
} else if (ch === "\u0003") {
|
|
409
|
+
// Ctrl+C
|
|
410
|
+
process.stdout.write("\n");
|
|
411
|
+
process.exit(0);
|
|
412
|
+
} else {
|
|
413
|
+
pwd += ch;
|
|
414
|
+
process.stdout.write("*");
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
process.stdin.on("data", onData);
|
|
418
|
+
});
|
|
419
|
+
|
|
388
420
|
console.log("\nSigning in...");
|
|
389
421
|
const result = await signIn(email, password);
|
|
390
422
|
|
|
@@ -673,12 +705,30 @@ function LoginScreen({ onLogin }: { onLogin: () => void }) {
|
|
|
673
705
|
setFocusField((prev: "email" | "password") =>
|
|
674
706
|
prev === "email" ? "password" : "email"
|
|
675
707
|
);
|
|
708
|
+
return;
|
|
676
709
|
}
|
|
677
710
|
if ((key.name === "enter" || key.name === "return") && !loading) {
|
|
678
711
|
handleSubmit();
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Manually handle password input since we display masked characters
|
|
716
|
+
if (focusField === "password") {
|
|
717
|
+
if (key.name === "backspace") {
|
|
718
|
+
setPassword((prev) => prev.slice(0, -1));
|
|
719
|
+
} else if (
|
|
720
|
+
key.sequence &&
|
|
721
|
+
key.sequence.length === 1 &&
|
|
722
|
+
!key.ctrl &&
|
|
723
|
+
!key.meta
|
|
724
|
+
) {
|
|
725
|
+
setPassword((prev) => prev + key.sequence);
|
|
726
|
+
}
|
|
679
727
|
}
|
|
680
728
|
});
|
|
681
729
|
|
|
730
|
+
const maskedPassword = "*".repeat(password.length);
|
|
731
|
+
|
|
682
732
|
return (
|
|
683
733
|
<box
|
|
684
734
|
alignItems="center"
|
|
@@ -715,16 +765,15 @@ function LoginScreen({ onLogin }: { onLogin: () => void }) {
|
|
|
715
765
|
/>
|
|
716
766
|
|
|
717
767
|
<text fg={COLORS.text}>Password</text>
|
|
718
|
-
<
|
|
719
|
-
backgroundColor={COLORS.bg}
|
|
720
|
-
|
|
721
|
-
focusedBackgroundColor="#1c2128"
|
|
722
|
-
onChange={setPassword}
|
|
723
|
-
placeholder="password"
|
|
724
|
-
textColor={COLORS.text}
|
|
725
|
-
value={password}
|
|
768
|
+
<box
|
|
769
|
+
backgroundColor={focusField === "password" ? "#1c2128" : COLORS.bg}
|
|
770
|
+
height={1}
|
|
726
771
|
width={40}
|
|
727
|
-
|
|
772
|
+
>
|
|
773
|
+
<text fg={password.length > 0 ? COLORS.text : COLORS.textDim}>
|
|
774
|
+
{password.length > 0 ? maskedPassword : "password"}
|
|
775
|
+
</text>
|
|
776
|
+
</box>
|
|
728
777
|
|
|
729
778
|
{error ? <text fg={COLORS.red}>{error}</text> : null}
|
|
730
779
|
{loading ? (
|
|
@@ -1138,6 +1187,10 @@ function MonitorScreen({
|
|
|
1138
1187
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
1139
1188
|
const [connected, setConnected] = useState(false);
|
|
1140
1189
|
const [wsStatus, setWsStatus] = useState<string>("connecting...");
|
|
1190
|
+
const [reconnectAttempt, setReconnectAttempt] = useState(0);
|
|
1191
|
+
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
|
|
1192
|
+
null
|
|
1193
|
+
);
|
|
1141
1194
|
const wsRef = useRef<WebSocket | null>(null);
|
|
1142
1195
|
|
|
1143
1196
|
// Connect to WebSocket as a machine
|
|
@@ -1159,6 +1212,7 @@ function MonitorScreen({
|
|
|
1159
1212
|
ws.addEventListener("open", () => {
|
|
1160
1213
|
setConnected(true);
|
|
1161
1214
|
setWsStatus("connected");
|
|
1215
|
+
setReconnectAttempt(0);
|
|
1162
1216
|
});
|
|
1163
1217
|
|
|
1164
1218
|
ws.addEventListener("message", (msgEvent) => {
|
|
@@ -1274,9 +1328,21 @@ function MonitorScreen({
|
|
|
1274
1328
|
}
|
|
1275
1329
|
});
|
|
1276
1330
|
|
|
1277
|
-
ws.addEventListener("close", () => {
|
|
1331
|
+
ws.addEventListener("close", (event) => {
|
|
1278
1332
|
setConnected(false);
|
|
1279
1333
|
setWsStatus("disconnected");
|
|
1334
|
+
|
|
1335
|
+
// Don't reconnect if intentionally closed (code 1000) or component unmounting
|
|
1336
|
+
if (event.code === 1000) {
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
const delay = Math.min(1000 * 2 ** reconnectAttempt, 30_000);
|
|
1341
|
+
setWsStatus(`reconnecting in ${Math.round(delay / 1000)}s...`);
|
|
1342
|
+
|
|
1343
|
+
reconnectTimeoutRef.current = setTimeout(() => {
|
|
1344
|
+
setReconnectAttempt((prev) => prev + 1);
|
|
1345
|
+
}, delay);
|
|
1280
1346
|
});
|
|
1281
1347
|
|
|
1282
1348
|
ws.addEventListener("error", () => {
|
|
@@ -1284,9 +1350,12 @@ function MonitorScreen({
|
|
|
1284
1350
|
});
|
|
1285
1351
|
|
|
1286
1352
|
return () => {
|
|
1287
|
-
|
|
1353
|
+
if (reconnectTimeoutRef.current) {
|
|
1354
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
1355
|
+
}
|
|
1356
|
+
ws.close(1000); // Intentional close — code 1000 prevents reconnect
|
|
1288
1357
|
};
|
|
1289
|
-
}, [ep.slug, mach.id, mach.name, mach.forwardUrl]);
|
|
1358
|
+
}, [ep.slug, mach.id, mach.name, mach.forwardUrl, reconnectAttempt]);
|
|
1290
1359
|
|
|
1291
1360
|
useKeyboard((key) => {
|
|
1292
1361
|
if (key.name === "q" || (key.ctrl && key.name === "c")) {
|