import { AlpineApp } from "@dx/alpine-server"; import { Context, Router } from "@oak/oak"; let config = { api_url: Deno.env.get("API_URL") || "http://localhost:8080", port: parseInt(Deno.env.get("PORT") || "8090"), }; try { const configFile = await Deno.readTextFile("./config.json"); const fileConfig = JSON.parse(configFile); config = { ...config, ...fileConfig }; // deno-lint-ignore no-explicit-any } catch (e: any) { console.warn("Could not read config.json, using defaults", e.message); } const app = new AlpineApp({ app: { dev: true, staticFilesPath: "./public", }, oak: { listenOptions: { port: config.port }, }, }); const healthRouter = new Router(); healthRouter.get("/health", (ctx: Context) => { ctx.response.body = { status: "ok" }; ctx.response.status = 200; }); app.append(healthRouter); app.use(async (ctx: Context, next: () => Promise) => { await next(); const contentType = ctx.response.headers.get("content-type") || ""; if (contentType.includes("text/html")) { let csp = ctx.response.headers.get("Content-Security-Policy"); if (!csp) { // securityHeaders might not have set it yet if it runs after us in the call stack // but in run(), securityHeaders is app.use()ed BEFORE user middlewares. // So when we are here (after await next()), securityHeaders has already finished its await next() // and set the headers. csp = [ "default-src 'self'", "base-uri 'self'", "object-src 'none'", "frame-ancestors 'none'", "script-src 'self' 'unsafe-eval'", "style-src 'self'", "img-src 'self' data:", "font-src 'self'", "connect-src 'self'", "media-src 'self'", ].join("; "); } const updatedCsp = csp.replace( "connect-src 'self'", `connect-src 'self' ${config.api_url} ws:`, ); ctx.response.headers.set("Content-Security-Policy", updatedCsp); } }); console.log(`URL: http://localhost:${config.port}`); console.log(`API: ${config.api_url}`); // Create config.js file in public directory await Deno.writeTextFile( "./public/config.js", `window.APP_CONFIG = ${JSON.stringify(config)};`, ); await app.run();