spb/earth-now Public License
earth-now.co — real-time planetary dashboard: live world metrics modeled, not streamed.
TypeScript 93%
Shell 2.3%
SQL 1.4%
JavaScript 1.3%
Dockerfile 1.2%
CSS 0.8%
1/**2 * earth-now.co3 * Author: Simon-Pierre Boucher4 * Contact: contact@spboucher.ai5 * File: infra/proxy.mjs6 * Purpose: Zero-dependency reverse proxy (nginx.conf equivalent) — routes /api,/v1,/sse,/badge to the API, the rest to web; SSE-safe (no buffering)7 */89import http from "node:http";1011const PORT = Number(process.env.PROXY_PORT ?? 8080);12const API = { host: "127.0.0.1", port: Number(process.env.API_PORT ?? 4000) };13const WEB = { host: "127.0.0.1", port: Number(process.env.WEB_PORT ?? 4100) };1415const API_PREFIXES = ["/api", "/v1", "/sse", "/badge"];1617function targetFor(url) {18 return API_PREFIXES.some((p) => url === p || url.startsWith(`${p}/`) || url.startsWith(`${p}?`))19 ? API20 : WEB;21}2223const server = http.createServer((req, res) => {24 const target = targetFor(req.url ?? "/");25 const upstream = http.request(26 {27 host: target.host,28 port: target.port,29 path: req.url,30 method: req.method,31 headers: { ...req.headers, host: req.headers.host ?? "www.earth-now.co" },32 },33 (up) => {34 res.writeHead(up.statusCode ?? 502, up.headers);35 // pipe() flushes chunks as they arrive — SSE streams pass through unbuffered.36 up.pipe(res);37 },38 );39 upstream.on("error", () => {40 if (!res.headersSent) res.writeHead(502, { "content-type": "application/json" });41 res.end(JSON.stringify({ error: "upstream unavailable" }));42 });43 req.pipe(upstream);44 // If the client disconnects (closed SSE tab), tear down the upstream leg too.45 res.on("close", () => upstream.destroy());46});4748// Long-lived SSE connections: disable the default 5-minute socket timeout.49server.requestTimeout = 0;50server.headersTimeout = 60_000;5152server.listen(PORT, "127.0.0.1", () => {53 console.log(`earth-now proxy on 127.0.0.1:${PORT} → api :${API.port}, web :${WEB.port}`);54});55