import { env } from "cloudflare:workers";
import { assertPublicHttpsUrl } from "../git/relay";
import { HttpError, badRequest } from "../errors";
const OUTBOUND_TIMEOUT_MS = 10_000;
const MAX_REQUEST_BYTES = 256 * 1024;
const MAX_RESPONSE_BYTES = 64 * 1024;
const HTTP_OK_MIN = 200;
const HTTP_OK_MAX = 300;
const HTTP_REDIRECT_MIN = 300;
const HTTP_REDIRECT_MAX = 400;
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
/** `permanent` failures (bad config, rejected URL, most 4xx) are not worth retrying. */
export type OutboundResult = {
status: number | null;
body: string;
error: string | null;
durationMs: number;
permanent?: boolean;
};
/**
* Every integration URL must be public https (no IP literals, internal suffixes or our own zone). Local
* development can opt into loopback sinks with INTEGRATIONS_ALLOW_LOOPBACK, which production never sets.
*/
export function assertOutboundUrl(raw: string, allowLoopback = env.INTEGRATIONS_ALLOW_LOOPBACK === "true"): URL {
if (allowLoopback) {
try {
const url = new URL(raw);
if ((url.protocol === "http:" || url.protocol === "https:") && LOOPBACK_HOSTS.has(url.hostname)) return url;
} catch {
throw badRequest("Invalid URL");
}
}
return assertPublicHttpsUrl(raw);
}
async function readCapped(response: Response, maxBytes: number): Promise<string> {
if (!response.body) return "";