const http = require("node:http"); const next = require("next"); const { createAccessGate } = require("./access-gate"); const { createGatewayProxy } = require("./gateway-proxy"); const { assertPublicHostAllowed, resolveHosts } = require("./network-policy"); const { loadUpstreamGatewaySettings } = require("./studio-settings"); const resolvePort = () => { const raw = process.env.PORT?.trim() || "3000"; const port = Number(raw); if (!Number.isFinite(port) || port <= 0) return 3000; return port; }; const resolvePathname = (url) => { const raw = typeof url === "string" ? url : ""; const idx = raw.indexOf("?"); return (idx === -1 ? raw : raw.slice(0, idx)) || "/"; }; async function main() { const dev = process.argv.includes("--dev"); const hostnames = Array.from(new Set(resolveHosts(process.env))); const hostname = hostnames[0] ?? "127.0.0.1"; const port = resolvePort(); for (const host of hostnames) { assertPublicHostAllowed({ host, studioAccessToken: process.env.STUDIO_ACCESS_TOKEN, }); } const app = next({ dev, hostname, port, ...(dev ? { webpack: true } : null), }); const handle = app.getRequestHandler(); const accessGate = createAccessGate({ token: process.env.STUDIO_ACCESS_TOKEN, }); const proxy = createGatewayProxy({ loadUpstreamSettings: async () => { const settings = loadUpstreamGatewaySettings(process.env); return { url: settings.url, token: settings.token }; }, allowWs: (req) => { if (resolvePathname(req.url) !== "/api/gateway/ws") return false; return true; }, verifyClient: (info) => accessGate.allowUpgrade(info.req), }); await app.prepare(); const handleUpgrade = app.getUpgradeHandler(); const handleServerUpgrade = (req, socket, head) => { if (resolvePathname(req.url) === "/api/gateway/ws") { proxy.handleUpgrade(req, socket, head); return; } handleUpgrade(req, socket, head); }; const createServer = () => http.createServer((req, res) => { if (accessGate.handleHttp(req, res)) return; handle(req, res); }); const servers = hostnames.map(() => createServer()); const attachUpgradeHandlers = (server) => { server.on("upgrade", handleServerUpgrade); server.on("newListener", (eventName, listener) => { if (eventName !== "upgrade") return; if (listener === handleServerUpgrade) return; process.nextTick(() => { server.removeListener("upgrade", listener); }); }); }; for (const server of servers) { attachUpgradeHandlers(server); } const listenOnHost = (server, host) => new Promise((resolve, reject) => { const onError = (err) => { server.off("error", onError); reject(err); }; server.once("error", onError); server.listen(port, host, () => { server.off("error", onError); resolve(); }); }); const closeServer = (server) => new Promise((resolve) => { if (!server.listening) return resolve(); server.close(() => resolve()); }); try { await Promise.all(servers.map((server, index) => listenOnHost(server, hostnames[index]))); } catch (err) { await Promise.all(servers.map((server) => closeServer(server))); throw err; } const hostForBrowser = hostnames.some((value) => value === "127.0.0.1" || value === "::1") ? "localhost" : hostname === "0.0.0.0" || hostname === "::" ? "localhost" : hostname; const browserUrl = `http://${hostForBrowser}:${port}`; console.info(`Open in browser: ${browserUrl}`); } main().catch((err) => { console.error(err); process.exitCode = 1; });