import { INGEST_PREFIX } from "~/lib/analytics";
import { analyticsConfig } from "./analytics";
const MAX_BODY_BYTES = 5 * 1024 * 1024;
const FORWARDED_REQUEST_HEADERS = ["content-type", "content-encoding", "accept", "accept-encoding", "user-agent"];
const DROPPED_RESPONSE_HEADERS = ["set-cookie", "access-control-allow-origin", "access-control-allow-credentials"];
/** PostHog serves the browser library's lazily loaded extensions from a separate assets host. */
export function ingestUpstream(host: string, path: string): string {
const assets = /^\/(static|array)\//.test(path);
const match = /^https:\/\/(us|eu)\.i\.posthog\.com$/.exec(host);
if (assets && match) return `https://${match[1]}-assets.i.posthog.com`;
return host;
}
/**
* Same-origin reverse proxy for PostHog (/ingest/*), so the page never talks to a third party directly. Only
* the headers PostHog needs are forwarded: no cookies, credentials or client IP, and upstream cookies are
* dropped. Answers 404 when analytics is off.
*/
export async function handleIngestProxy(request: Request, url: URL): Promise<Response | null> {
if (url.pathname !== INGEST_PREFIX && !url.pathname.startsWith(`${INGEST_PREFIX}/`)) return null;
const config = analyticsConfig();
if (!config) return new Response("Not found\n", { status: 404 });
if (request.method !== "GET" && request.method !== "POST" && request.method !== "HEAD") {
return new Response("Method not allowed\n", { status: 405 });
}
const length = Number(request.headers.get("content-length") ?? 0);
if (length > MAX_BODY_BYTES) return new Response("Payload too large\n", { status: 413 });
const path = url.pathname.slice(INGEST_PREFIX.length) || "/";
const target = new URL(`${path}${url.search}`, ingestUpstream(config.host, path));
const headers = new Headers();
for (const name of FORWARDED_REQUEST_HEADERS) {
const value = request.headers.get(name);
if (value) headers.set(name, value);
}
const hasBody = request.method === "POST";
const upstream = await fetch(target, {
method: request.method,
headers,