-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.mjs
More file actions
52 lines (48 loc) · 3.49 KB
/
Copy pathserver.mjs
File metadata and controls
52 lines (48 loc) · 3.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import { createServer } from "node:http";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { extname, join, normalize } from "node:path";
import { createApi } from "./shared-api.mjs";
import { radarResponse } from "./radar/api.mjs";
import { updatesResponse } from "./updates/api.mjs";
import { supabaseUpdatesResponse } from "./updates/remote-api.mjs";
const PORT = Number.parseInt(process.env.PORT ?? "3000", 10);
const feedbackFile = join(process.cwd(), "data", "feedback-aggregates.json");
const mimeTypes = { ".css": "text/css; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".html": "text/html; charset=utf-8" };
const feedbackStore = {
async load() { try { return JSON.parse(await readFile(feedbackFile, "utf8")); } catch { return {}; } },
async save(value) { await mkdir(join(process.cwd(), "data"), { recursive: true }); await writeFile(feedbackFile, JSON.stringify(value, null, 2)); },
};
const api = createApi({ origin: process.env.TECHNOCORE_ORIGIN, cacheTtlMs: Number.parseInt(process.env.CACHE_TTL_MS ?? "180000", 10), operatorDid: process.env.TECHNOSCOPE_OPERATOR_DID ?? null, feedbackStore });
async function serveStatic(pathname, response) {
const relative = pathname === "/" ? "index.html" : pathname.slice(1);
const safePath = normalize(relative).replace(/^\.{2}(?:[/\\]|$)/, "");
try {
const content = await readFile(join(process.cwd(), "public", safePath));
response.writeHead(200, { "content-type": mimeTypes[extname(safePath)] ?? "application/octet-stream", "cache-control": "public, max-age=300", "x-content-type-options": "nosniff", "content-security-policy": "default-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; object-src 'none'" });
response.end(content);
} catch { response.writeHead(404, { "content-type": "text/plain; charset=utf-8" }); response.end("Not found"); }
}
createServer(async (request, response) => {
const url = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
if (!url.pathname.startsWith("/api/")) return serveStatic(url.pathname, response);
if (request.method === "GET" && url.pathname === "/api/flop-radar") {
const webResponse = await radarResponse();
response.writeHead(webResponse.status, Object.fromEntries(webResponse.headers));
response.end(Buffer.from(await webResponse.arrayBuffer()));
return;
}
if (request.method === "GET" && (url.pathname === "/api/flop-updates" || /^\/api\/flop-updates\/topics\/[a-z0-9-]+$/.test(url.pathname))) {
let webResponse;
try { webResponse = await supabaseUpdatesResponse(process.env, url.pathname); } catch (error) { console.warn("FLOP Updates Supabase read failed; serving fallback.", error.message); }
webResponse ??= updatesResponse(url.pathname);
response.writeHead(webResponse.status, Object.fromEntries(webResponse.headers));
response.end(Buffer.from(await webResponse.arrayBuffer()));
return;
}
let body = undefined;
if (request.method !== "GET" && request.method !== "HEAD") { const chunks = []; for await (const chunk of request) chunks.push(chunk); body = Buffer.concat(chunks); }
const webRequest = new Request(url, { method: request.method, headers: request.headers, body, duplex: body ? "half" : undefined });
const webResponse = await api.handle(webRequest);
response.writeHead(webResponse.status, Object.fromEntries(webResponse.headers));
response.end(Buffer.from(await webResponse.arrayBuffer()));
}).listen(PORT, () => console.log(`Technocore Matome listening on http://localhost:${PORT}`));