Frontend Labs
⋯

server.mjs

// Browser Lab server — zero dependencies (§79).
//
//   npm run cert   one-time: a self-signed cert for localhost (or use mkcert)
//   npm start      http://localhost:8088   HTTP/1.1, plain text
//                  https://localhost:8443  HTTP/2 over TLS 1.3 (when certs/ exists)
//
// Open both and run the Network station's "fire 18 requests" experiment in
// each: HTTP/1.1 shows a 6-connection staircase, HTTP/2 one multiplexed burst.

import http from "node:http";
import http2 from "node:http2";
import { readFile } from "node:fs/promises";
import { existsSync, readFileSync } from "node:fs";
import { extname, join, normalize, sep } from "node:path";
import { fileURLToPath } from "node:url";

const HTTP_PORT = Number(process.env.HTTP_PORT ?? 8088);
const HTTPS_PORT = Number(process.env.HTTPS_PORT ?? 8443);
const HOST = process.env.HOST ?? "localhost";
const ROOT = fileURLToPath(new URL(".", import.meta.url));
const PUBLIC_DIR = join(ROOT, "public") + sep;
const CERT = join(ROOT, "certs", "cert.pem");
const KEY = join(ROOT, "certs", "key.pem");

const MIME = {
  ".html": "text/html; charset=utf-8",
  ".js": "text/javascript; charset=utf-8",
  ".css": "text/css; charset=utf-8",
  ".svg": "image/svg+xml",
  ".json": "application/json",
};

// Cross-origin isolation (COOP + COEP) unlocks SharedArrayBuffer and
// performance.measureUserAgentSpecificMemory() for the Threads and Debugging
// stations (§39, §83). The CSP is strict: no inline script, no eval — the
// Debugging station shows what a violation looks like.
const HEADERS = {
  "Content-Security-Policy":
    "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: blob:; " +
    "worker-src 'self' blob:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'",
  "Cross-Origin-Opener-Policy": "same-origin",
  "Cross-Origin-Embedder-Policy": "require-corp",
  "Cross-Origin-Resource-Policy": "same-origin",
  "X-Content-Type-Options": "nosniff",
  "Referrer-Policy": "no-referrer",
};

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

function send(res, status, body, headers = {}) {
  res.writeHead(status, { ...HEADERS, ...headers });
  res.end(body);
}

const json = (res, status, data, headers = {}) =>
  send(res, status, JSON.stringify(data), { "Content-Type": "application/json", "Cache-Control": "no-store", ...headers });

// What the browser actually negotiated and sent, from the server's side.
function connectionInfo(req) {
  const socket = req.stream?.session?.socket ?? req.socket;
  return {
    httpVersion: req.httpVersion,
    alpn: socket.alpnProtocol || null,
    tls: socket.getProtocol?.() ?? null,
    cipher: socket.getCipher?.()?.name ?? null,
    remotePort: socket.remotePort,
    headers: req.headers,
  };
}

const routes = {
  // A deliberately slow endpoint for the waterfall experiment.
  "/api/delay": async (req, res, url) => {
    const ms = Math.min(Number(url.searchParams.get("ms")) || 300, 5000);
    const start = performance.now();
    await sleep(ms);
    // Server-Timing shows up in DevTools and in PerformanceResourceTiming.serverTiming.
    json(res, 200, { ms, id: url.searchParams.get("id") }, {
      "Server-Timing": `wait;dur=${(performance.now() - start).toFixed(1)};desc="simulated work"`,
    });
  },
  "/api/whoami": (req, res) => json(res, 200, connectionInfo(req)),
  // A server-sent event stream: one tick every 500ms, ten ticks.
  "/api/ticks": async (req, res) => {
    res.writeHead(200, { ...HEADERS, "Content-Type": "text/event-stream", "Cache-Control": "no-store" });
    for (let i = 1; i <= 10 && !res.destroyed; i++) {
      res.write(`id: ${i}\ndata: ${JSON.stringify({ tick: i, at: Date.now() })}\n\n`);
      await sleep(500);
    }
    res.end();
  },
};

async function handler(req, res) {
  const url = new URL(req.url, "http://x");
  try {
    if (routes[url.pathname]) return await routes[url.pathname](req, res, url);
    const rel = url.pathname === "/" ? "index.html" : decodeURIComponent(url.pathname.slice(1));
    const file = normalize(join(PUBLIC_DIR, rel));
    if (!file.startsWith(PUBLIC_DIR)) return json(res, 404, { error: "Not found" });
    const body = await readFile(file);
    send(res, 200, body, { "Content-Type": MIME[extname(file)] ?? "application/octet-stream", "Cache-Control": "no-cache" });
  } catch (err) {
    if (err.code === "ENOENT" || err.code === "EISDIR") return json(res, 404, { error: "Not found" });
    console.error(err);
    if (!res.headersSent) json(res, 500, { error: "Something went wrong" });
  }
}

http.createServer(handler).listen(HTTP_PORT, HOST, () =>
  console.log(`Browser Lab  HTTP/1.1  http://${HOST}:${HTTP_PORT}`),
);

if (existsSync(CERT) && existsSync(KEY)) {
  // allowHTTP1: browsers that don't offer "h2" in ALPN still get a page.
  http2
    .createSecureServer({ cert: readFileSync(CERT), key: readFileSync(KEY), allowHTTP1: true }, handler)
    .listen(HTTPS_PORT, HOST, () => console.log(`Browser Lab  HTTP/2    https://${HOST}:${HTTPS_PORT}`));
} else {
  console.log("No certs/ found: run `npm run cert` to also serve HTTP/2 over TLS on :" + HTTPS_PORT);
}

Generated from SOURCE.md · 8 Oct 2026 16:36