import http from "node:http";
const API_HOST = "eu.i.posthog.com"; // Change to "us.i.posthog.com" for the US region
const ASSET_HOST = "eu-assets.i.posthog.com"; // Change to "us-assets.i.posthog.com" for the US region
// Convert Node.js headers to Fetch API headers
const toHeaders = (headers: Record<string, string[] | undefined>): Headers =>
Object.entries(headers).reduce((acc, [name, values]) => {
values?.forEach((value) => acc.append(name, value));
return acc;
}, new Headers());
// Convert Fetch API headers to Node.js headers
const fromHeaders = (headers: Headers): Record<string, string[] | undefined> =>
[...headers].reduce<Record<string, string[] | undefined>>(
(acc, [name, value]) => {
if (acc[name]) {
acc[name] = [...acc[name], value];
} else {
acc[name] = [value];
}
return acc;
},
{},
);
/**
* Proxies an incoming HTTP request to the appropriate PostHog endpoint.
*/
export default function proxy({ prefix }: { prefix: string }) {
return (
request: http.IncomingMessage,
response: http.ServerResponse,
next: (err?: Error) => void,
): void => {
if (!request.url?.startsWith(prefix)) {
next();
return;
}
const pathname = (request.url ?? "").slice(prefix.length);
const posthogHost = pathname.startsWith("/static/") ? ASSET_HOST : API_HOST;
// Rewrite headers
const headers = toHeaders(request.headersDistinct);
headers.set("host", posthogHost);
if (request.headers.host) {
headers.set("X-Forwarded-Host", request.headers.host);
}
if (request.socket.remoteAddress) {
headers.set("X-Real-IP", request.socket.remoteAddress);
headers.set("X-Forwarded-For", request.socket.remoteAddress);
}
// Remove sensitive or hop-by-hop headers
headers.delete("cookie");
headers.delete("connection"); // https://datatracker.ietf.org/doc/html/rfc7230#section-6.1
fetch(new URL(pathname, `https://${posthogHost}`), {
method: request.method ?? "",
headers,
...(!["HEAD", "GET"].includes(request.method ?? "")
? {
body: request,
duplex: "half",
}
: {}),
})
.then(async (res: Response) => {
const headers = new Headers(res.headers);
const body = await res.text();
// See https://github.com/nodejs/undici/issues/2514
if (headers.has("content-encoding")) {
headers.delete("content-encoding");
headers.delete("content-length");
}
response.writeHead(res.status, fromHeaders(headers));
response.end(body);
})
.catch((e: unknown) => {
next(new Error("Bad gateway", { cause: e }));
});
};
}