import { env } from "../env.js"; const CF_API = "https://api.cloudflare.com/client/v4"; export interface CloudflareCustomHostname { id: string; hostname: string; status: string; ownership_verification?: { type: string; name: string; value: string }; ssl: { status: string; method: string; type: string; validation_records?: { txt_name: string; txt_value: string }[]; }; } async function cfFetch(path: string, options: RequestInit = {}): Promise { if (!env.CLOUDFLARE_API_TOKEN || !env.CLOUDFLARE_ZONE_ID) { throw new Error("Cloudflare not configured (CLOUDFLARE_API_TOKEN / CLOUDFLARE_ZONE_ID missing)"); } const res = await fetch(`${CF_API}${path}`, { ...options, headers: { Authorization: `Bearer ${env.CLOUDFLARE_API_TOKEN}`, "Content-Type": "application/json", ...(options.headers ?? {}), }, }); const json = (await res.json()) as { success: boolean; result: T; errors: { message: string }[] }; if (!json.success) { throw new Error(json.errors?.[0]?.message ?? "Cloudflare API error"); } return json.result; } // One-time DCV (domain ownership) TXT + a per-hostname SSL cert via // Cloudflare for SaaS. The hostname's own CNAME (routing) is set by the // customer independently — see domains.ts's cnameTarget instruction. export async function createCustomHostname(hostname: string): Promise { return cfFetch(`/zones/${env.CLOUDFLARE_ZONE_ID}/custom_hostnames`, { method: "POST", body: JSON.stringify({ hostname, ssl: { method: "txt", type: "dv" } }), }); } export async function getCustomHostnameByHostname( hostname: string, ): Promise { const results = await cfFetch( `/zones/${env.CLOUDFLARE_ZONE_ID}/custom_hostnames?hostname=${encodeURIComponent(hostname)}`, ); return results?.[0] ?? null; } export async function deleteCustomHostname(id: string): Promise { await cfFetch(`/zones/${env.CLOUDFLARE_ZONE_ID}/custom_hostnames/${id}`, { method: "DELETE" }); }