/* * server.mjs * Zyquo Cloud Web * * Author: Simon-Pierre Boucher * Mail: contact@spboucher.ai * * Production static server (zero dependencies): serves the dist/ bundle with * security headers (CSP mirroring index.html, HSTS, nosniff, referrer * policy), immutable caching for hashed assets, no-cache for the HTML shell, * SPA fallback, and a /healthz endpoint. Run: node server.mjs [port] [dir] */ import { createServer } from 'node:http' import { createReadStream, existsSync, statSync } from 'node:fs' import { extname, join, normalize, resolve } from 'node:path' const PORT = Number(process.argv[2] ?? process.env.PORT ?? 8080) const ROOT = resolve(process.argv[3] ?? join(process.cwd(), 'dist')) const CSP = [ "default-src 'self'", "script-src 'self'", "style-src 'self' 'unsafe-inline'", "img-src 'self' data: blob:", "font-src 'self' data:", "connect-src 'self' https://api.openai.com https://api.anthropic.com https://api.x.ai https://api.mistral.ai https://generativelanguage.googleapis.com https://dashscope-intl.aliyuncs.com https://api.deepseek.com https://api.moonshot.ai https://api.perplexity.ai https://api.together.xyz https://api.deepinfra.com https://api.cerebras.ai http://localhost:* http://127.0.0.1:*", "worker-src 'self'", "object-src 'none'", "base-uri 'self'", "form-action 'none'", "frame-ancestors 'none'", ].join('; ') const MIME = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.ico': 'image/x-icon', '.woff2': 'font/woff2', '.woff': 'font/woff', '.webmanifest': 'application/manifest+json', '.txt': 'text/plain; charset=utf-8', '.map': 'application/json', } const server = createServer((req, res) => { const url = new URL(req.url ?? '/', 'http://localhost') let pathname = decodeURIComponent(url.pathname) if (pathname === '/healthz') { res.writeHead(200, { 'Content-Type': 'text/plain' }) res.end('ok') return } // Resolve inside ROOT only (no traversal). let filePath = normalize(join(ROOT, pathname)) if (!filePath.startsWith(ROOT)) { res.writeHead(403).end() return } if (!existsSync(filePath) || statSync(filePath).isDirectory()) { filePath = join(ROOT, 'index.html') // SPA fallback (also serves /) } const ext = extname(filePath) const hashed = /\/assets\//.test(filePath) || /-\w{8,}\./.test(filePath) const headers = { 'Content-Type': MIME[ext] ?? 'application/octet-stream', 'Content-Security-Policy': CSP, 'Strict-Transport-Security': 'max-age=63072000; includeSubDomains; preload', 'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'no-referrer', 'Permissions-Policy': 'camera=(), geolocation=(), payment=()', 'Cache-Control': ext === '.html' ? 'no-cache' : hashed ? 'public, max-age=31536000, immutable' : 'public, max-age=3600', } res.writeHead(200, headers) createReadStream(filePath).pipe(res) }) server.listen(PORT, '0.0.0.0', () => { console.log(`Zyquo Cloud Web serving ${ROOT} on :${PORT}`) })