feat(mcp): подготовить сервер и доступ к приёмке
This commit is contained in:
commit
bf3d50e1ff
3835 files changed
+537122
No files matched your search
@@ -0,0 +1,56 @@
|
||||
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 "Не удалось скачать файл. Проверьте соединение и повторите попытку."
|
||||
}
|
||||
Reference in new issue
Block a user