fvttt
Version:
FVTTT is a CLI tool for tunneling Foundry VTT application through Cloudflare.
96 lines (78 loc) • 2.58 kB
text/typescript
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);
});