import * as fs from "node:fs" import * as http from "node:http" import * as path from "node:path" import ScriptUtils from "./ScriptUtils" import * as meta from "../package.json" import { ServerResponse } from "http" const PORT = 1235 const CORS = "http://localhost:1234,https://mapcomplete.org,https://dev.mapcomplete.org" const MIME_TYPES = { default: "application/octet-stream", html: "text/html; charset=UTF-8", js: "application/javascript", css: "text/css", png: "image/png", jpg: "image/jpg", gif: "image/gif", ico: "image/x-icon", svg: "image/svg+xml", json: "application/json", } const STATIC_PATH = path.join(process.cwd(), "./assets") async function prepareFile(url: string): Promise { const paths = [STATIC_PATH, url] if (url.endsWith("/")) paths.push("index.html") const filePath = path.join(...paths) if (fs.existsSync(filePath)) { return fs.readFileSync(filePath, "utf8") } while (url.startsWith("/")) { url = url.slice(1) } const sliced = url.split("/").slice(1) if (!sliced) { return } const backupFile = path.join(STATIC_PATH, ...sliced) console.log("Using backup path", backupFile) if (fs.existsSync(backupFile)) { return fs.readFileSync(backupFile, "utf8") } return null } async function handleDelete(req: http.IncomingMessage, res: ServerResponse) { let body = "" req.on("data", (chunk) => { body = body + chunk }) const paths = req.url.split("/") console.log("Got a valid delete to:", paths.join("/")) for (let i = 1; i < paths.length; i++) { const p = paths.slice(0, i) const dir = STATIC_PATH + p.join("/") if (!fs.existsSync(dir)) { res.writeHead(304, { "Content-Type": MIME_TYPES.html }) res.write("No parent directory, nothing deleted", "utf8") res.end() return } } const path = STATIC_PATH + paths.join("/") if (!fs.existsSync(path)) { res.writeHead(304, { "Content-Type": MIME_TYPES.html }) res.write("File not found", "utf8") res.end() return } fs.renameSync(path, path + ".bak") res.writeHead(200, { "Content-Type": MIME_TYPES.html }) res.write("File moved to backup", "utf8") res.end() } async function handlePost(req: http.IncomingMessage, res: ServerResponse) { let body = "" req.on("data", (chunk) => { body = body + chunk }) await new Promise((resolve) => req.on("end", resolve)) console.log(new Date().toISOString()) let parsed: any try { parsed = JSON.parse(body) } catch (e) { console.error("ERROR: probably did not receive the full JSON", e) res.writeHead(400, { "Content-Type": MIME_TYPES.html }) res.write("Invalid JSON:" + e + "", "utf8") res.end() return } const paths = req.url.split("/") console.log("Got a valid update to:", paths.join("/")) for (let i = 1; i < paths.length; i++) { const p = paths.slice(0, i) const dir = STATIC_PATH + p.join("/") if (!fs.existsSync(dir)) { console.log("Creating new directory", dir) fs.mkdirSync(dir) } } const path = STATIC_PATH + paths.join("/") fs.writeFileSync(path, JSON.stringify(parsed, null, " ")) res.writeHead(200, { "Content-Type": MIME_TYPES.html }) res.write("OK", "utf8") res.end() return } http.createServer(async (req: http.IncomingMessage, res) => { try { console.log( req.method + " " + req.url, "from:", req.headers.origin, new Date().toISOString() ) res.setHeader( "Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept" ) res.setHeader("Access-Control-Allow-Origin", req.headers.origin ?? "*") if (req.method === "OPTIONS") { res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, UPDATE") res.writeHead(204, { "Content-Type": MIME_TYPES.html }) res.end() return } if (req.method === "POST" || req.method === "UPDATE") { await handlePost(req, res) return } if (req.method === "DELETE") { console.log("Got a DELETE", new Date()) await handleDelete(req, res) return } const url = new URL(`http://127.0.0.1/` + req.url) console.log("URL pathname is") if (url.pathname.endsWith("overview")) { console.log("Giving overview") const userId = url.searchParams.get("userId") const allFiles = ScriptUtils.readDirRecSync(STATIC_PATH) .filter( (p) => p.endsWith(".json") && !p.endsWith("license_info.json") && (p.startsWith("layers") || p.startsWith("themes") || userId !== undefined || p.startsWith(userId)) ) .map((p) => p.substring(STATIC_PATH.length + 1)) res.writeHead(200, { "Content-Type": MIME_TYPES.json }) res.write(JSON.stringify({ allFiles })) res.end() return } const file = await prepareFile(req.url) if (file === null) { res.writeHead(404, { "Content-Type": MIME_TYPES.html }) res.write("

Not found...

") res.end() return } const statusCode = 200 const mimeType = MIME_TYPES.json || MIME_TYPES.default res.writeHead(statusCode, { "Content-Type": mimeType }) res.write(file) res.end() } catch (e) { console.error(e) } }).listen(PORT) console.log( `Server started at http://127.0.0.1:${PORT}/, the time is ${new Date().toISOString()}, version from package.json is ${ meta.version }` )