import { env } from "cloudflare:workers";
import { getRequestHeader } from "@tanstack/react-start/server";
import { HttpError } from "./errors";
import type { Viewer } from "~/lib/types";
const HTTP_TOO_MANY = 429;
type LimiterName = "AUTH_LIMITER" | "WRITE_LIMITER" | "HEAVY_LIMITER" | "GIT_LIMITER";
/** The exact client address from Cloudflare; use for logs and Turnstile, not for rate-limit keys. */
export function rawRequestIp(request?: Request): string {
const header = request ? request.headers.get("cf-connecting-ip") : getRequestHeader("cf-connecting-ip");
return header ?? "unknown";
}
/**
* An IPv6 client typically holds a whole /64, so keying on the full address lets it rotate past every
* per-IP limit. IPv6 collapses to its /64 prefix; IPv4 stays exact.
*/
const IPV6_GROUPS = 8;
const IPV6_PREFIX_GROUPS = 4;
export function ipBucket(ip: string): string {
if (!ip.includes(":") || ip.includes(".")) return ip;
const halves = ip.toLowerCase().split("::");
if (halves.length > 2) return ip;
const [head, tail = ""] = halves;
const left = head ? head.split(":") : [];
const right = tail ? tail.split(":") : [];
if ([...left, ...right].some((group) => !/^[0-9a-f]{1,4}$/.test(group))) return ip;
if (!ip.includes("::") && left.length !== IPV6_GROUPS) return ip;
const fill = Math.max(0, IPV6_GROUPS - left.length - right.length);
const groups = [...left, ...Array<string>(fill).fill("0"), ...right];
if (groups.length !== IPV6_GROUPS) return ip;
return `${groups
.slice(0, IPV6_PREFIX_GROUPS)
.map((group) => group.replace(/^0+(?=.)/, ""))
.join(":")}::/64`;
}