{"version":3,"file":"static-dev.mjs","names":[],"sources":["../../../src/astro/middleware/static-dev.ts"],"sourcesContent":["/**\n * Local-dev editor session for live-connected static frontends.\n *\n * `bun dev` renders the site on localhost while every `/_emdash/*` request is\n * proxied to the deployed backend. Authentication cannot happen on localhost\n * itself — passkeys are origin-bound to the backend's domain and magic-link\n * emails carry absolute URLs to it — so editors sign in on the backend's own\n * origin and the preview-session handoff transfers the session here: the\n * sign-in pill links to `<backend>/_emdash/preview-session/start?to=<page>`,\n * the worker authenticates (or sends the visitor to the admin login first),\n * mints a single-use ticket, and the finish leg — reached back through the\n * dev proxy — sets this host's own session cookie.\n *\n * With that cookie present, this middleware asks the backend's `auth/me` who\n * the editor is and splices the same visual-editing toolbar the platform\n * splices on preview hosts. Registered only for `staticFrontend` projects\n * under `astro dev`.\n */\nimport { defineMiddleware } from \"astro:middleware\";\n\n// @ts-ignore - virtual module\nimport virtualConfig from \"virtual:emdash/config\";\n\nimport {\n\tcookieHas,\n\tEDIT_MODE_COOKIE,\n\tinjectToolbarHtml,\n\trenderToolbar,\n\tTOOLBAR_MIN_ROLE,\n} from \"../../visual-editing/index.js\";\n\nconst SESSION_COOKIE = \"astro-session\";\nconst VERDICT_TTL_MS = 30_000;\nconst TRAILING_SLASHES = /\\/+$/;\n\nconst verdicts = new Map<string, { editor: boolean; exp: number }>();\n\nfunction cookieValue(header: string | null, name: string): string {\n\tif (!header) return \"\";\n\tfor (const part of header.split(\";\")) {\n\t\tconst eq = part.indexOf(\"=\");\n\t\tif (eq === -1) continue;\n\t\tif (part.slice(0, eq).trim() === name) return part.slice(eq + 1).trim();\n\t}\n\treturn \"\";\n}\n\n/** The live backend's origin — set only in snapshot-live (live-connected) mode. */\nfunction backendOrigin(): string {\n\tconst database = (virtualConfig as { database?: { entrypoint?: string; config?: unknown } })\n\t\t?.database;\n\tif (!database?.entrypoint?.endsWith(\"/snapshot-live\")) return \"\";\n\tconst cfg = (database.config ?? {}) as { url?: string };\n\treturn typeof cfg.url === \"string\" ? cfg.url.replace(TRAILING_SLASHES, \"\") : \"\";\n}\n\nasync function isEditor(backend: string, session: string): Promise<boolean> {\n\tconst cached = verdicts.get(session);\n\tif (cached && cached.exp > Date.now()) return cached.editor;\n\tlet editor = false;\n\ttry {\n\t\tconst me = await fetch(`${backend}/_emdash/api/auth/me`, {\n\t\t\theaders: {\n\t\t\t\tcookie: `${SESSION_COOKIE}=${session}`,\n\t\t\t\taccept: \"application/json\",\n\t\t\t\t\"X-EmDash-Request\": \"1\",\n\t\t\t},\n\t\t});\n\t\tif (me.ok) {\n\t\t\tconst body = (await me.json()) as { data?: { role?: unknown } } | null;\n\t\t\teditor = typeof body?.data?.role === \"number\" && body.data.role >= TOOLBAR_MIN_ROLE;\n\t\t}\n\t} catch {\n\t\t// backend unreachable — treat as signed out\n\t}\n\tverdicts.set(session, { editor, exp: Date.now() + VERDICT_TTL_MS });\n\treturn editor;\n}\n\nfunction signInPill(startUrl: string): string {\n\treturn `\n<!-- EmDash dev sign-in -->\n<div id=\"emdash-dev-signin\" style=\"position:fixed;bottom:16px;inset-inline-end:16px;z-index:999999;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;font-size:13px;line-height:1;-webkit-font-smoothing:antialiased;\">\n  <a href=\"${startUrl}\" style=\"display:flex;align-items:center;gap:8px;padding:8px 14px;background:#1a1a1a;color:#e0e0e0;border-radius:999px;box-shadow:0 4px 24px rgba(0,0,0,0.3),0 0 0 1px rgba(255,255,255,0.08);text-decoration:none;\">\n    <span style=\"width:8px;height:8px;border-radius:50%;background:#6366f1;\"></span>\n    Edit with EmDash\n  </a>\n</div>\n`;\n}\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n\tif (!import.meta.env.DEV) return next();\n\tconst { request, url } = context;\n\tif (request.method !== \"GET\" || url.pathname.startsWith(\"/_emdash\")) return next();\n\t// Astro's dev server hands prerendered-page middleware a Request with no\n\t// accept header, so only an explicitly non-HTML accept opts out here; the\n\t// content-type check below is the real page gate.\n\tconst accept = request.headers.get(\"accept\");\n\tif (accept && !accept.includes(\"text/html\") && !accept.includes(\"*/*\")) return next();\n\tconst backend = backendOrigin();\n\tif (!backend) return next();\n\n\tconst cookie = request.headers.get(\"cookie\");\n\tconst session = cookieValue(cookie, SESSION_COOKIE);\n\n\tconst response = await next();\n\tif (!(response.headers.get(\"content-type\") ?? \"\").includes(\"text/html\")) return response;\n\n\tif (session && (await isEditor(backend, session))) {\n\t\tconst toolbar = renderToolbar({\n\t\t\teditMode: cookieHas(cookie, EDIT_MODE_COOKIE, \"true\"),\n\t\t\tisPreview: true,\n\t\t});\n\t\tconst out = new Response(injectToolbarHtml(await response.text(), toolbar), response);\n\t\tout.headers.set(\"cache-control\", \"private, no-store\");\n\t\tout.headers.delete(\"content-length\");\n\t\treturn out;\n\t}\n\n\tconst startUrl = `${backend}/_emdash/preview-session/start?to=${encodeURIComponent(url.href)}`;\n\tconst out = new Response(injectToolbarHtml(await response.text(), signInPill(startUrl)), response);\n\tout.headers.delete(\"content-length\");\n\treturn out;\n});\n\nexport default onRequest;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAM,iBAAiB;AACvB,MAAM,iBAAiB;AACvB,MAAM,mBAAmB;AAEzB,MAAM,2BAAW,IAAI,KAA+C;AAEpE,SAAS,YAAY,QAAuB,MAAsB;AACjE,KAAI,CAAC,OAAQ,QAAO;AACpB,MAAK,MAAM,QAAQ,OAAO,MAAM,IAAI,EAAE;EACrC,MAAM,KAAK,KAAK,QAAQ,IAAI;AAC5B,MAAI,OAAO,GAAI;AACf,MAAI,KAAK,MAAM,GAAG,GAAG,CAAC,MAAM,KAAK,KAAM,QAAO,KAAK,MAAM,KAAK,EAAE,CAAC,MAAM;;AAExE,QAAO;;;AAIR,SAAS,gBAAwB;CAChC,MAAM,WAAY,eACf;AACH,KAAI,CAAC,UAAU,YAAY,SAAS,iBAAiB,CAAE,QAAO;CAC9D,MAAM,MAAO,SAAS,UAAU,EAAE;AAClC,QAAO,OAAO,IAAI,QAAQ,WAAW,IAAI,IAAI,QAAQ,kBAAkB,GAAG,GAAG;;AAG9E,eAAe,SAAS,SAAiB,SAAmC;CAC3E,MAAM,SAAS,SAAS,IAAI,QAAQ;AACpC,KAAI,UAAU,OAAO,MAAM,KAAK,KAAK,CAAE,QAAO,OAAO;CACrD,IAAI,SAAS;AACb,KAAI;EACH,MAAM,KAAK,MAAM,MAAM,GAAG,QAAQ,uBAAuB,EACxD,SAAS;GACR,QAAQ,GAAG,eAAe,GAAG;GAC7B,QAAQ;GACR,oBAAoB;GACpB,EACD,CAAC;AACF,MAAI,GAAG,IAAI;GACV,MAAM,OAAQ,MAAM,GAAG,MAAM;AAC7B,YAAS,OAAO,MAAM,MAAM,SAAS,YAAY,KAAK,KAAK,QAAQ;;SAE7D;AAGR,UAAS,IAAI,SAAS;EAAE;EAAQ,KAAK,KAAK,KAAK,GAAG;EAAgB,CAAC;AACnE,QAAO;;AAGR,SAAS,WAAW,UAA0B;AAC7C,QAAO;;;aAGK,SAAS;;;;;;;AAQtB,MAAa,YAAY,iBAAiB,OAAO,SAAS,SAAS;AAClE,KAAI,CAAC,OAAO,KAAK,IAAI,IAAK,QAAO,MAAM;CACvC,MAAM,EAAE,SAAS,QAAQ;AACzB,KAAI,QAAQ,WAAW,SAAS,IAAI,SAAS,WAAW,WAAW,CAAE,QAAO,MAAM;CAIlF,MAAM,SAAS,QAAQ,QAAQ,IAAI,SAAS;AAC5C,KAAI,UAAU,CAAC,OAAO,SAAS,YAAY,IAAI,CAAC,OAAO,SAAS,MAAM,CAAE,QAAO,MAAM;CACrF,MAAM,UAAU,eAAe;AAC/B,KAAI,CAAC,QAAS,QAAO,MAAM;CAE3B,MAAM,SAAS,QAAQ,QAAQ,IAAI,SAAS;CAC5C,MAAM,UAAU,YAAY,QAAQ,eAAe;CAEnD,MAAM,WAAW,MAAM,MAAM;AAC7B,KAAI,EAAE,SAAS,QAAQ,IAAI,eAAe,IAAI,IAAI,SAAS,YAAY,CAAE,QAAO;AAEhF,KAAI,WAAY,MAAM,SAAS,SAAS,QAAQ,EAAG;EAClD,MAAM,UAAU,cAAc;GAC7B,UAAU,UAAU,QAAQ,kBAAkB,OAAO;GACrD,WAAW;GACX,CAAC;EACF,MAAM,MAAM,IAAI,SAAS,kBAAkB,MAAM,SAAS,MAAM,EAAE,QAAQ,EAAE,SAAS;AACrF,MAAI,QAAQ,IAAI,iBAAiB,oBAAoB;AACrD,MAAI,QAAQ,OAAO,iBAAiB;AACpC,SAAO;;CAGR,MAAM,WAAW,GAAG,QAAQ,oCAAoC,mBAAmB,IAAI,KAAK;CAC5F,MAAM,MAAM,IAAI,SAAS,kBAAkB,MAAM,SAAS,MAAM,EAAE,WAAW,SAAS,CAAC,EAAE,SAAS;AAClG,KAAI,QAAQ,OAAO,iBAAiB;AACpC,QAAO;EACN"}