File size: 3,878 Bytes
4d1197e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3fde616
4d1197e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
 * 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://<owner>-<space>.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://<your-custom-domain>
 *     (the app builds the Google sign-in return URL from this).
 *   - Supabase Auth > URL Configuration: add
 *     https://<your-custom-domain>/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;
  },
};