SPB Git

spb/zyquo-cloud-web Public MIT

Zyquo Cloud Web — every cloud model, one beautiful chat, entirely in your browser.

TypeScript 81.9% CSS 8.9% JavaScript 7.5% Shell 1.1% HTML 0.6%
3.1 KB · 93 lines javascript
Raw Blame History
1/*2 *  server.mjs3 *  Zyquo Cloud Web4 *5 *  Author: Simon-Pierre Boucher6 *  Mail: contact@spboucher.ai7 *8 *  Production static server (zero dependencies): serves the dist/ bundle with9 *  security headers (CSP mirroring index.html, HSTS, nosniff, referrer10 *  policy), immutable caching for hashed assets, no-cache for the HTML shell,11 *  SPA fallback, and a /healthz endpoint. Run: node server.mjs [port] [dir]12 */1314import { createServer } from 'node:http'15import { createReadStream, existsSync, statSync } from 'node:fs'16import { extname, join, normalize, resolve } from 'node:path'1718const PORT = Number(process.argv[2] ?? process.env.PORT ?? 8080)19const ROOT = resolve(process.argv[3] ?? join(process.cwd(), 'dist'))2021const CSP = [22  "default-src 'self'",23  "script-src 'self'",24  "style-src 'self' 'unsafe-inline'",25  "img-src 'self' data: blob:",26  "font-src 'self' data:",27  "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:*",28  "worker-src 'self'",29  "object-src 'none'",30  "base-uri 'self'",31  "form-action 'none'",32  "frame-ancestors 'none'",33].join('; ')3435const MIME = {36  '.html': 'text/html; charset=utf-8',37  '.js': 'text/javascript; charset=utf-8',38  '.css': 'text/css; charset=utf-8',39  '.json': 'application/json',40  '.svg': 'image/svg+xml',41  '.png': 'image/png',42  '.ico': 'image/x-icon',43  '.woff2': 'font/woff2',44  '.woff': 'font/woff',45  '.webmanifest': 'application/manifest+json',46  '.txt': 'text/plain; charset=utf-8',47  '.map': 'application/json',48}4950const server = createServer((req, res) => {51  const url = new URL(req.url ?? '/', 'http://localhost')52  let pathname = decodeURIComponent(url.pathname)5354  if (pathname === '/healthz') {55    res.writeHead(200, { 'Content-Type': 'text/plain' })56    res.end('ok')57    return58  }5960  // Resolve inside ROOT only (no traversal).61  let filePath = normalize(join(ROOT, pathname))62  if (!filePath.startsWith(ROOT)) {63    res.writeHead(403).end()64    return65  }66  if (!existsSync(filePath) || statSync(filePath).isDirectory()) {67    filePath = join(ROOT, 'index.html') // SPA fallback (also serves /)68  }6970  const ext = extname(filePath)71  const hashed = /\/assets\//.test(filePath) || /-\w{8,}\./.test(filePath)72  const headers = {73    'Content-Type': MIME[ext] ?? 'application/octet-stream',74    'Content-Security-Policy': CSP,75    'Strict-Transport-Security': 'max-age=63072000; includeSubDomains; preload',76    'X-Content-Type-Options': 'nosniff',77    'Referrer-Policy': 'no-referrer',78    'Permissions-Policy': 'camera=(), geolocation=(), payment=()',79    'Cache-Control':80      ext === '.html'81        ? 'no-cache'82        : hashed83          ? 'public, max-age=31536000, immutable'84          : 'public, max-age=3600',85  }86  res.writeHead(200, headers)87  createReadStream(filePath).pipe(res)88})8990server.listen(PORT, '0.0.0.0', () => {91  console.log(`Zyquo Cloud Web serving ${ROOT} on :${PORT}`)92})93