import type * as interfaces from '../../../dist_ts_interfaces/index.js'; export interface IEmailDomainCreationProgress { percentage: number; indeterminate: boolean; showPercentage: boolean; statusText: string; terminalLines: string[]; } export interface IEmailDomainCreationProgressInput { domain?: interfaces.data.IEmailDomain; operation?: interfaces.requests.IEmailDomainActionResult | null; requestCompleted: boolean; } const settledIntentStatuses = new Set([ 'satisfied', 'valid', 'conflict', 'failed', ]); const formatOperationError = ( errorArg: interfaces.data.IEmailDomainOperationError | interfaces.requests.IEmailDomainActionError, ): string => { const location = errorArg.recordKey ? `${errorArg.recordKey}: ` : ''; return `${location}${errorArg.message} [${errorArg.code}]`; }; const appendDnsStatusLines = ( linesArg: string[], dnsStatusArg: interfaces.data.IEmailDomainDnsStatus, ): void => { for (const [label, status] of [ ['MX', dnsStatusArg.mx], ['SPF', dnsStatusArg.spf], ['DKIM', dnsStatusArg.dkim], ['DMARC', dnsStatusArg.dmarc], ] as const) { if (status !== 'unchecked') { linesArg.push(`${label}: ${status}`); } } }; export const getEmailDomainCreationProgress = ( inputArg: IEmailDomainCreationProgressInput, ): IEmailDomainCreationProgress => { const { domain, operation, requestCompleted } = inputArg; if (!domain) { if (!requestCompleted) { return { percentage: 0, indeterminate: true, showPercentage: false, statusText: 'Generating DKIM material and creating the domain record...', terminalLines: ['Submitting email domain configuration'], }; } const lines = ['Email domain creation request completed without a persisted domain']; for (const error of operation?.errors || []) { lines.push(formatOperationError(error)); } if (operation?.message && !lines.includes(operation.message)) { lines.push(operation.message); } return { percentage: 100, indeterminate: false, showPercentage: false, statusText: operation?.message || 'Email domain creation failed.', terminalLines: lines, }; } const reconciliation = domain.reconciliation; const lines = [`Domain record persisted: ${domain.domain}`]; if (!reconciliation || reconciliation.intents.length === 0) { if (!requestCompleted) { lines.push('Preparing the managed DNS plan'); return { percentage: 0, indeterminate: true, showPercentage: false, statusText: 'Preparing the managed DNS plan...', terminalLines: lines, }; } lines.push('No DNS reconciliation plan is available'); const errors = reconciliation?.errors.length ? reconciliation.errors : operation?.errors || []; for (const error of errors) { lines.push(formatOperationError(error)); } const retryAt = reconciliation?.retryAt || operation?.retryAt; if (retryAt) { lines.push(`Retry scheduled for ${retryAt}`); } const statusText = reconciliation?.lifecycleStatus === 'failed' ? operation?.message || `DNS setup failed for ${domain.domain}.` : retryAt ? 'DNS reconciliation is pending and will retry automatically.' : operation?.message || 'The domain was created without a DNS reconciliation plan.'; return { percentage: 100, indeterminate: false, showPercentage: false, statusText, terminalLines: lines, }; } const settledCount = reconciliation.intents.filter((intent) => settledIntentStatuses.has(intent.status) ).length; lines.push(`DNS plan prepared: ${reconciliation.intents.length} managed records`); lines.push( `DNS reconciliation: ${settledCount} settled, ${reconciliation.intents.length - settledCount} pending`, ); appendDnsStatusLines(lines, domain.dnsStatus); if (reconciliation.lifecycleStatus === 'active') { lines.push('Email domain is active'); return { percentage: 100, indeterminate: false, showPercentage: true, statusText: `${domain.domain} is active.`, terminalLines: lines, }; } if (!requestCompleted) { return { percentage: 0, indeterminate: true, showPercentage: false, statusText: 'Reconciling and validating managed DNS records...', terminalLines: lines, }; } const errors = reconciliation.errors.length > 0 ? reconciliation.errors : operation?.errors || []; for (const error of errors) { lines.push(formatOperationError(error)); } const retryAt = reconciliation.retryAt || operation?.retryAt; if (retryAt) { lines.push(`Retry scheduled for ${retryAt}`); } if (reconciliation.lifecycleStatus === 'failed') { return { percentage: 100, indeterminate: false, showPercentage: false, statusText: operation?.message || `DNS setup failed for ${domain.domain}.`, terminalLines: lines, }; } return { percentage: 100, indeterminate: false, showPercentage: false, statusText: retryAt ? `DNS reconciliation is pending and will retry automatically.` : `DNS reconciliation is pending for ${domain.domain}.`, terminalLines: lines, }; }; export const findNewEmailDomain = ( domainsArg: interfaces.data.IEmailDomain[], expectedDomainArg: string, existingIdsArg: ReadonlySet, ): interfaces.data.IEmailDomain | undefined => { const expectedDomain = expectedDomainArg.trim().toLowerCase(); const candidates = domainsArg.filter( (domain) => domain.domain.toLowerCase() === expectedDomain && !existingIdsArg.has(domain.id), ); if (candidates.length > 1) { throw new Error(`Multiple new email domains appeared for ${expectedDomain}`); } return candidates[0]; }; const waitForPollInterval = async (delayMsArg: number, signalArg: AbortSignal): Promise => { if (signalArg.aborted) return; await new Promise((resolve) => { const finish = () => { signalArg.removeEventListener('abort', handleAbort); resolve(); }; const handleAbort = () => { clearTimeout(timeoutId); finish(); }; const timeoutId = setTimeout(finish, delayMsArg); signalArg.addEventListener('abort', handleAbort, { once: true }); }); }; export interface IObserveNewEmailDomainOptions { expectedDomain: string; existingIds: ReadonlySet; signal: AbortSignal; listDomains: () => Promise; getDomain: (idArg: string) => Promise; onDomain: (domainArg: interfaces.data.IEmailDomain) => void; subscribeToDomains?: ( listenerArg: (domainsArg: interfaces.data.IEmailDomain[]) => void, ) => () => void; pollIntervalMs?: number; timeoutMs?: number; } export const observeNewEmailDomain = async ( optionsArg: IObserveNewEmailDomainOptions, ): Promise => { const startedAt = Date.now(); const pollIntervalMs = optionsArg.pollIntervalMs ?? ( optionsArg.subscribeToDomains ? 15_000 : 600 ); const timeoutMs = optionsArg.timeoutMs ?? 120_000; let observedId: string | undefined; let latestDomain: interfaces.data.IEmailDomain | undefined; let pushedDomains: interfaces.data.IEmailDomain[] | undefined; let wakeObserver: (() => void) | undefined; const unsubscribe = optionsArg.subscribeToDomains?.((domainsArg) => { pushedDomains = domainsArg; wakeObserver?.(); }); try { while (!optionsArg.signal.aborted && Date.now() - startedAt < timeoutMs) { try { let domain: interfaces.data.IEmailDomain | null | undefined; if (!observedId) { const domainSnapshot = pushedDomains || await optionsArg.listDomains(); pushedDomains = undefined; const foundDomain = findNewEmailDomain( domainSnapshot, optionsArg.expectedDomain, optionsArg.existingIds, ); observedId = foundDomain?.id; domain = foundDomain; } else if (pushedDomains) { domain = pushedDomains.find((candidate) => candidate.id === observedId); pushedDomains = undefined; } else { domain = await optionsArg.getDomain(observedId); } if (optionsArg.signal.aborted) break; if (domain) { if (domain !== latestDomain) { latestDomain = domain; optionsArg.onDomain(domain); } const lifecycleStatus = domain.reconciliation?.lifecycleStatus; if (lifecycleStatus === 'active' || lifecycleStatus === 'failed') { return domain; } } } catch (error: unknown) { if (error instanceof Error && error.message.startsWith('Multiple new email domains')) { throw error; } // The realtime subscription or bounded fallback read will retry. } const updatePromise = new Promise((resolve) => { wakeObserver = resolve; }); if (pushedDomains) { wakeObserver = undefined; continue; } await Promise.race([ updatePromise, waitForPollInterval(pollIntervalMs, optionsArg.signal), ]); wakeObserver = undefined; } } finally { unsubscribe?.(); wakeObserver?.(); } return latestDomain; }; export interface IAbortableDeadlineOptions { signal?: AbortSignal; timeoutMs: number; timeoutMessage: string; } export const withAbortableDeadline = async ( promiseArg: Promise, optionsArg: IAbortableDeadlineOptions, ): Promise => { return new Promise((resolve, reject) => { let settled = false; const finish = (callbackArg: () => void) => { if (settled) return; settled = true; clearTimeout(timeoutId); optionsArg.signal?.removeEventListener('abort', handleAbort); callbackArg(); }; const handleAbort = () => { finish(() => reject(new Error('Email domain creation progress was interrupted'))); }; const timeoutId = setTimeout(() => { finish(() => reject(new Error(optionsArg.timeoutMessage))); }, optionsArg.timeoutMs); if (optionsArg.signal?.aborted) { handleAbort(); return; } optionsArg.signal?.addEventListener('abort', handleAbort, { once: true }); promiseArg.then( (value) => finish(() => resolve(value)), (error) => finish(() => reject(error)), ); }); };