import type { CloudflareOpContext } from "@distilled.cloud/cloudflare"; import { listRecords } from "@distilled.cloud/cloudflare/dns"; import { BadRequest, Conflict, Forbidden, NotFound, Unauthorized, } from "@distilled.cloud/cloudflare/Errors"; import { deleteDomain, listDomains } from "@distilled.cloud/cloudflare/workers"; import { getZone, listZones } from "@distilled.cloud/cloudflare/zones"; import * as Effect from "effect/Effect"; import { cloudflare } from "./cloudflare-sdk.ts"; import { DomainAccountDenied, DomainAttachmentOutcomeUnknown, DomainInvalidHostname, DomainReauthorizationRequired, DomainResourceConflict, DomainTemporarilyUnavailable, } from "./domain-errors.ts"; import { domainIdentifier, type DomainRecord } from "./domain-metadata.ts"; import type { Installation } from "./installation-metadata.ts"; import { bodyJson, withResponse } from "../server/http.ts"; const identifier = (value: unknown): value is string => typeof value === "string" && /^[a-f0-9]{32}$/.test(value); const object = (value: unknown): value is Record => value !== null && typeof value === "object" && !Array.isArray(value); // Fixed provider endpoints; each Workflow attempt supplies fresh authorization. export class DomainAPI { constructor( private readonly token: string, private readonly record: Installation, private readonly network: typeof fetch = fetch, ) {} private sdk(operation: Effect.Effect) { return cloudflare( operation, this.token, this.network, 15_000, 128 * 1024, ).pipe(Effect.mapError(providerFailure)); } zones() { return Effect.gen({ self: this }, function* () { const zones: { id: string; name: string }[] = []; for (let page = 1; page <= 20; page++) { const envelope = yield* this.sdk( listZones({ account: { id: this.record.accountId }, status: "active", perPage: 50, page, }), ); if (!envelope || !Array.isArray(envelope.result)) return yield* new DomainTemporarilyUnavailable(); const pages = envelope.resultInfo?.totalPages; if ( envelope.resultInfo != null && (typeof pages !== "number" || !Number.isInteger(pages) || pages < 0) ) return yield* new DomainTemporarilyUnavailable(); for (const zone of envelope.result) { if ( !object(zone) || !identifier(zone.id) || typeof zone.name !== "string" || !object(zone.account) || zone.account.id !== this.record.accountId || zone.status !== "active" ) return yield* new DomainAccountDenied(); zones.push({ id: zone.id, name: zone.name }); } if ( envelope.resultInfo?.totalPages == null || envelope.resultInfo.totalPages <= page ) return zones; } return yield* new DomainTemporarilyUnavailable(); }); } zone(zoneId: string, hostname: string) { return Effect.gen({ self: this }, function* () { const zone = yield* this.sdk(getZone({ zoneId })); if ( !object(zone) || zone.id !== zoneId || !object(zone.account) || zone.account.id !== this.record.accountId || zone.status !== "active" ) return yield* new DomainAccountDenied(); if ( typeof zone.name !== "string" || !(hostname === zone.name || hostname.endsWith(`.${zone.name}`)) ) return yield* new DomainInvalidHostname(); }); } find(domain: DomainRecord) { return Effect.gen({ self: this }, function* () { const envelope = yield* this.sdk( listDomains({ accountId: this.record.accountId, hostname: domain.hostname, }), ); if ( !envelope || !Array.isArray(envelope.result) || envelope.result.length > 1 || (envelope.resultInfo?.totalPages ?? 1) > 1 ) return yield* new DomainResourceConflict(); if (!envelope.result.length) return null; const value: unknown = envelope.result[0]; if ( !object(value) || typeof value.id !== "string" || !domainIdentifier.safeParse(value.id).success || value.hostname !== domain.hostname || value.zoneId !== domain.zoneId || value.service !== this.record.resources.workerName || (value.environment && value.environment !== "production") ) return yield* new DomainResourceConflict(); return value.id; }); } preflight(domain: DomainRecord) { return Effect.gen({ self: this }, function* () { yield* this.zone(domain.zoneId, domain.hostname); if (yield* this.find(domain)) return yield* new DomainResourceConflict(); const envelope = yield* this.sdk( listRecords({ zoneId: domain.zoneId, name: { exact: domain.hostname }, perPage: 1, }), ); if ( !envelope || !Array.isArray(envelope.result) || envelope.result.length ) return yield* new DomainResourceConflict(); }); } attach(domain: DomainRecord) { return Effect.gen({ self: this }, function* () { if (!domain.writeIntent || domain.domainId) return yield* new DomainResourceConflict(); // Preserve unrelated domains and never opt into DNS/Worker takeover. // Distilled does not expose this no-takeover attachment operation. Keep // its native contract until the SDK supports the three override guards. yield* withResponse( this.network, `https://api.cloudflare.com/client/v4/accounts/${this.record.accountId}/workers/scripts/${this.record.resources.workerName}/domains/records`, { method: "PUT", headers: { Authorization: `Bearer ${this.token}`, "Content-Type": "application/json", }, body: JSON.stringify({ override_scope: false, override_existing_origin: false, override_existing_dns_record: false, origins: [{ hostname: domain.hostname, zone_id: domain.zoneId }], }), }, 15_000, new DomainTemporarilyUnavailable(), (response) => Effect.gen(function* () { if (response.status === 401) return yield* new DomainReauthorizationRequired(); if (response.status === 403) return yield* new DomainAccountDenied(); if ([400, 409].includes(response.status)) return yield* new DomainResourceConflict(); if (!response.ok) return yield* new DomainTemporarilyUnavailable(); const envelope = yield* bodyJson( response, 128 * 1024, new DomainTemporarilyUnavailable(), ); if ( !object(envelope) || envelope.success !== true || !("result" in envelope) ) return yield* new DomainTemporarilyUnavailable(); }), ); }); } remove(domain: DomainRecord) { return Effect.gen({ self: this }, function* () { // Abandoning a failed preflight must not detach an existing hostname. if (!domain.writeIntent) return; if (!domain.domainId) return yield* new DomainAttachmentOutcomeUnknown(); const found = yield* this.find(domain); if (!found) return; if (found !== domain.domainId) return yield* new DomainResourceConflict(); yield* this.sdk( deleteDomain({ accountId: this.record.accountId, domainId: found, }).pipe( Effect.catchIf( (error) => error instanceof NotFound, () => Effect.void, ), ), ); if (yield* this.find(domain)) return yield* new DomainTemporarilyUnavailable(); }); } } function providerFailure(error: unknown) { if (error instanceof Unauthorized) return new DomainReauthorizationRequired(); if (error instanceof Forbidden) return new DomainAccountDenied(); if (error instanceof BadRequest || error instanceof Conflict) return new DomainResourceConflict(); return new DomainTemporarilyUnavailable(); }