UNPKG

fvttt

Version:

FVTTT is a CLI tool for tunneling Foundry VTT application through Cloudflare.

96 lines (78 loc) 2.58 kB
#!/usr/bin/env bun import * as p from "@clack/prompts"; import { Tunnel } from "cloudflared"; import { installCloudflared, TUNNEL_TIMEOUT } from "./utils"; const APP_VERSION = "5.5.0"; const DEFAULT_PORT = "30000"; async function main(): Promise<void> { console.log(); p.intro(`[ FVTTT ${APP_VERSION} ]`); await installCloudflared(); const port = await p.text({ message: "FoundryVTT Port", placeholder: DEFAULT_PORT, initialValue: DEFAULT_PORT, validate: (value) => { const portNum = parseInt(value || DEFAULT_PORT, 10); if (isNaN(portNum) || portNum < 1 || portNum > 65535) { return "Please enter a valid port number (1-65535)"; } }, }); if (p.isCancel(port)) { p.cancel("Operation cancelled"); return process.exit(0); } const s = p.spinner(); s.start("Waiting for tunnel URL..."); const host = `http://localhost:${port || DEFAULT_PORT}`; const tunnel = Tunnel.quick(host); let exitCode = 0; let timeoutId: ReturnType<typeof setTimeout> | undefined; const stopTunnel = () => { if (timeoutId) { clearTimeout(timeoutId); timeoutId = undefined; } tunnel.stop(); }; tunnel.on("exit", (code) => { p.log.info(`Cloudflare tunnel exited with code ${code ?? 0}`); process.exit(exitCode || code || 0); }); const shutdown = (message?: string) => { if (message) p.cancel(message); stopTunnel(); }; process.once("SIGINT", () => shutdown("Tunnel stopped")); process.once("SIGTERM", () => shutdown()); try { const url = await new Promise<string>((resolve, reject) => { timeoutId = setTimeout(() => { reject(new Error("Timeout waiting for tunnel URL")); }, TUNNEL_TIMEOUT); tunnel.once("url", (url) => { if (timeoutId) clearTimeout(timeoutId); resolve(url); }); tunnel.once("error", (error) => { if (timeoutId) clearTimeout(timeoutId); reject(error); }); }); s.stop(`Tunnel URL: ${url}`); p.outro( "Share the URL above with your players. Press Ctrl+C to stop the tunnel." ); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); s.stop(`Failed to establish tunnel: ${errorMessage}`); exitCode = 1; stopTunnel(); } } main().catch((error) => { const errorMessage = error instanceof Error ? error.message : String(error); p.log.error(`Error: ${errorMessage}`); process.exit(1); });