/** * Cloudflare Worker: reverse proxy that serves the CCR Platform Hugging Face * Space at the lab's own domain (e.g. ccr.culturemoralitylab.org or * www.psychologicaltextanalysis.org), so the address bar keeps the custom * domain throughout. The Space host never appears to the visitor. * * Why a Worker: Hugging Face Spaces do not support custom domains on ANY tier * (free or paid), so the domain has to be served in front of the Space. The * Workers free tier (100k requests/day) covers lab traffic comfortably. * * SETUP * 1. Set UPSTREAM_HOST below to the Space's direct host. Find it on the Space * page: Settings > "Embed this Space" > Direct URL. It has the form * https://-.hf.space (lowercased, non-alphanumerics become * hyphens), e.g. culture-and-morality-lab-ccr-platform.hf.space. * 2. Cloudflare dashboard > Workers & Pages > Create Worker > paste this file * > Deploy. * 3. That Worker > Settings > Domains & Routes > Add Custom Domain > enter the * subdomain (ccr.culturemoralitylab.org). Cloudflare provisions TLS * automatically. The domain must already be in this Cloudflare account. * * PAIRS WITH (already on the migration runbook, deploy/PRODUCTION_RUNBOOK.md): * - Space secret CCR_APP_URL = https:// * (the app builds the Google sign-in return URL from this). * - Supabase Auth > URL Configuration: add * https:///api/auth/google/callback to the redirect list. * * NOTES * - redirect: "manual" is required. The app returns 3xx responses (the Google * sign-in hand-off, and the post-login redirect back to "/"). Manual mode * passes those to the browser instead of the Worker following them * server-side, so sign-in works and app redirects stay on the custom domain. * - The app's own redirects are relative ("/"), so they resolve against the * custom domain automatically. The block below rewrites only the rare * absolute redirect that points back at the Space host, as a safety net. * - Host-only cookies (no Domain attribute) scope to the custom domain through * the proxy with no Set-Cookie rewriting needed. * - No websockets in the app; uploads are capped at 50 MB, under the Worker * request-body limit. * * AFTER DEPLOY, verify once: load the site on the custom domain, sign in with * Google (the address bar should return to the custom domain, not hf.space), * upload a small corpus, run it. If Google sign-in lands on an error, re-check * CCR_APP_URL and the Supabase redirect URL above. */ const UPSTREAM_HOST = "culture-and-morality-lab-ccr-platform.hf.space"; export default { async fetch(request) { const incoming = new URL(request.url); // Same path and query, sent to the Space host over HTTPS. const target = new URL(request.url); target.hostname = UPSTREAM_HOST; target.protocol = "https:"; target.port = ""; // Clone onto the target URL (preserves method, headers, and body). The // Host header is derived from the target URL, so the Space routes correctly. const proxied = new Request(target, request); const response = await fetch(proxied, { redirect: "manual" }); // Safety net: rewrite an absolute redirect back at the Space host onto the // custom domain. App redirects are relative, so this rarely fires. const location = response.headers.get("location"); if (location && location.startsWith(`https://${UPSTREAM_HOST}`)) { const headers = new Headers(response.headers); headers.set( "location", incoming.origin + location.slice(`https://${UPSTREAM_HOST}`.length), ); return new Response(response.body, { status: response.status, statusText: response.statusText, headers, }); } return response; }, };