import type { CertificateIssueJob, CertificateIssueRequest } from "@mmapp/contracts/certificates" import { getCertificateIssueJob } from "@/shared/api/certificates" export function waitForCertificatePoll(ms: number, signal: AbortSignal): Promise { return new Promise((resolve, reject) => { if (signal.aborted) { reject(signal.reason); return } const abort = () => { clearTimeout(timer); signal.removeEventListener("abort", abort); reject(signal.reason) } const timer = setTimeout(() => { signal.removeEventListener("abort", abort); resolve() }, ms) signal.addEventListener("abort", abort, { once: true }) }) } export async function pollCertificateIssue(baseUrl: string, jobId: string, target: CertificateIssueRequest, options: { signal: AbortSignal onJob?: (job: CertificateIssueJob) => void readJob?: typeof getCertificateIssueJob wait?: typeof waitForCertificatePoll attempts?: number intervalMs?: number }) { const signal = AbortSignal.any([options.signal, AbortSignal.timeout(300_000)]) const readJob = options.readJob ?? getCertificateIssueJob const wait = options.wait ?? waitForCertificatePoll const attempts = options.attempts ?? 120 for (let index = 0; index < attempts; index++) { signal.throwIfAborted() const job = await readJob(baseUrl, jobId, { signal }) signal.throwIfAborted() if (job.id !== jobId || job.serverId !== String(target.serverId) || job.certName !== target.certName || JSON.stringify(job.domainNames) !== JSON.stringify(target.domainNames)) throw new Error("Ответ задачи не соответствует выбранному серверу, сертификату или доменам") if (job.status === "done" && job.step !== "done") throw new Error("Завершение задачи не подтверждено её шагом") options.onJob?.(job) if (job.status === "done" || job.status === "failed") return job if (index + 1 < attempts) await wait(options.intervalMs ?? 2000, signal) } throw new Error("Время наблюдения истекло. Задача может продолжаться на сервере; повторите проверку этой задачи.") }