import { fetchPopiserverGateway as fetch } from "@/app/api/_popiserverFetch"; import { NextRequest } from "next/server"; import { ApiError } from "./_errors"; import { getPopiartApiBaseUrl } from "./_popiartBaseUrl"; const POPI_MEDIA_UPLOAD_PATH = "/api_client/media/upload"; type PopiserverResponse = { status?: string; message?: string; data?: T; }; type PopiMedia = { path?: string; url?: string; originPath?: string; originUrl?: string; }; function getPopiBaseUrl(request: NextRequest): string { void request; return getPopiartApiBaseUrl(); } function parseDataUrl(value: string): { mimeType: string; data: Buffer } | null { const match = value.match(/^data:([^;,]+)?(?:;[^,]*)?;base64,(.*)$/); if (!match) return null; return { mimeType: match[1] || "application/octet-stream", data: Buffer.from(match[2], "base64"), }; } function extensionForMimeType(mimeType: string): string { const normalized = mimeType.toLowerCase(); if (normalized === "image/jpeg") return "jpg"; if (normalized === "image/png") return "png"; if (normalized === "image/webp") return "webp"; if (normalized === "image/gif") return "gif"; if (normalized === "video/mp4") return "mp4"; if (normalized === "video/webm") return "webm"; if (normalized === "video/quicktime") return "mov"; if (normalized === "audio/mpeg" || normalized === "audio/mp3") return "mp3"; if (normalized === "audio/wav" || normalized === "audio/x-wav") return "wav"; if (normalized === "audio/ogg") return "ogg"; if (normalized === "audio/flac") return "flac"; if (normalized === "audio/aac") return "aac"; if (normalized === "model/gltf-binary") return "glb"; return normalized.split("/")[1]?.replace(/[^a-z0-9]/g, "") || "bin"; } function mediaResourceUrl(media: PopiMedia): string | null { return media.url || media.originUrl || media.path || media.originPath || null; } export function isDataUrl(value: string | null | undefined): value is string { return typeof value === "string" && value.startsWith("data:"); } export async function uploadDataUrlToPopiMedia( request: NextRequest, token: string, dataUrl: string, filePrefix: string ): Promise { const parsed = parseDataUrl(dataUrl); if (!parsed) { throw ApiError.badRequest("Invalid media data URL."); } const extension = extensionForMimeType(parsed.mimeType); const arrayBuffer = parsed.data.buffer.slice( parsed.data.byteOffset, parsed.data.byteOffset + parsed.data.byteLength ) as ArrayBuffer; const blob = new Blob([arrayBuffer], { type: parsed.mimeType }); const formData = new FormData(); formData.append("file", blob, `${filePrefix}-${Date.now()}.${extension}`); const upstream = await fetch(getPopiBaseUrl(request) + POPI_MEDIA_UPLOAD_PATH, { method: "POST", headers: { Accept: "application/json", Authorization: `Bearer ${token}`, token, }, body: formData, cache: "no-store", }); const payload = (await upstream.json().catch(() => null)) as PopiserverResponse | null; if (!upstream.ok) { throw ApiError.provider(payload?.message || "Popiserver media upload failed.", upstream.status); } if (!payload || payload.status !== "0000" || !payload.data) { throw ApiError.provider(payload?.message || "Popiserver media upload returned an error.", 502); } const url = mediaResourceUrl(payload.data); if (!url) { throw ApiError.provider("Popiserver media upload response is missing resource URL.", 502); } return url; }