import { z } from "zod" import { getClaims, getToken } from "@/lib/auth" import { ApiClientError, requestBlob } from "./http-client" const receiptSchema = z.object({ artifactId: z.string().uuid(), humanDownloadOnly: z.literal(true), downloadPath: z.string().optional(), }) /** Only structured receipts qualify; supplied paths never become navigation targets. */ export function humanArtifactReceipt(value: unknown) { const parsed = receiptSchema.safeParse(value) if (!parsed.success) return null const path = `/api/mcp/artifacts/${parsed.data.artifactId}` if (parsed.data.downloadPath !== undefined && parsed.data.downloadPath !== path) return null return { artifactId: parsed.data.artifactId, path } } export async function downloadHumanArtifact(base: string, receipt: unknown, signal?: AbortSignal) { const artifact = humanArtifactReceipt(receipt) if (!artifact) throw new Error("Некорректная квитанция файла") const claims = getClaims() if (!getToken() || !claims?.sub || !claims.apps?.includes("mm")) { throw new ApiClientError("Войдите в портал для скачивания файла", 401) } const response = await requestBlob(base, artifact.path, { signal, cache: "no-store", redirect: "error" }) const blob = await response.blob() signal?.throwIfAborted() const disposition = response.headers.get("Content-Disposition") ?? "" const suggested = /filename="([a-zA-Z0-9._-]{1,200})"/.exec(disposition)?.[1] const filename = suggested && suggested !== "." && suggested !== ".." ? suggested : `mcp-${artifact.artifactId}.bin` const url = URL.createObjectURL(blob) const link = document.createElement("a") try { link.href = url link.download = filename document.body.appendChild(link) link.click() } finally { link.remove() // Let the browser begin consuming the download before releasing the in-memory file. setTimeout(() => URL.revokeObjectURL(url), 1000) } } export function humanArtifactError(error: unknown): string { if (error instanceof ApiClientError) { if (error.status === 401) return "Войдите в портал для скачивания файла." if (error.status === 403) return "Доступ к файлу отозван или файл принадлежит другому пользователю." if (error.status === 404 || error.status === 410) return "Файл отсутствует или срок хранения истёк. Подготовьте новую выгрузку." if (error.status === 409) return "Файл изменился или больше не соответствует квитанции. Подготовьте новую выгрузку." } // Do not render response bodies, filenames, credentials, or binary content as errors. return "Не удалось скачать файл. Проверьте соединение и повторите попытку." }