// Standalone preview server for the connector-namespaces connector catalog. // // Renders every canvas state with no Copilot app, no ARM, and no real OAuth by // importing the *pure* HTML builders from renderer.mjs and stubbing every // /api/* endpoint the inline client script calls. Point any browser (or the // agent-browser driver in shots.mjs) at it to see exactly what ships. // // Run: node extensions/connector-namespaces/preview/server.mjs // Then open http://127.0.0.1:7331/ (catalog), /setup, /error. // // This process is NOT the JSON-RPC extension provider, so console.log here is // fine and intentional — it is how you watch which stubbed endpoints get hit. import { createServer } from "node:http"; import { renderCatalogHtml, renderSetupHtml, renderErrorHtml, } from "../renderer.mjs"; import * as fixtures from "./fixtures.mjs"; const HOST = "127.0.0.1"; const PORT = 7331; const INSTANCE = "preview"; // Whatever /api/state should report next. The catalog route updates this from // its query flags so a page load can force the banner / "added" tile on, and a // real Connect click flips pendingRestart on via showRestartBanner(). let activeState = fixtures.stateEmpty; function selectState(query) { const restart = query.get("restart") === "1"; const installed = query.get("installed") === "1"; if (restart && installed) return fixtures.stateInstalledRestart; if (installed) return { state: fixtures.stateInstalledRestart.state, pendingRestart: false }; if (restart) return { state: {}, pendingRestart: true }; return fixtures.stateEmpty; } function sendHtml(res, body) { res.setHeader("Content-Type", "text/html; charset=utf-8"); res.end(body); } function sendJson(res, obj, status = 200) { res.statusCode = status; res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(obj)); } async function readBody(req) { const chunks = []; for await (const chunk of req) chunks.push(chunk); if (!chunks.length) return {}; try { return JSON.parse(Buffer.concat(chunks).toString("utf8")); } catch { return {}; } } const server = createServer(async (req, res) => { const url = new URL(req.url, `http://${HOST}:${PORT}`); const path = url.pathname; const q = url.searchParams; // Strip CR/LF/tab so a crafted request line can't forge extra log entries. console.log(`${req.method} ${req.url}`.replace(/[\r\n\t]/g, " ")); // --- Page routes --------------------------------------------------------- if (req.method === "GET" && (path === "/" || path === "/catalog")) { activeState = selectState(q); return sendHtml( res, renderCatalogHtml(INSTANCE, fixtures.catalog, { filter: q.get("filter") || "", category: q.get("category") || "all", source: q.get("source") || "", config: fixtures.config, }), ); } if (req.method === "GET" && path === "/setup") { return sendHtml(res, renderSetupHtml(fixtures.subscriptions)); } if (req.method === "GET" && path === "/error") { return sendHtml(res, renderErrorHtml(q.get("message") || "Something went wrong loading connectors.")); } if (req.method === "GET" && path === "/fake-consent") { return sendHtml(res, "