46 lines
2.5 KiB
TypeScript
46 lines
2.5 KiB
TypeScript
import "server-only";
|
|
import { getDefaultStorageConfig } from "@/lib/db/queries";
|
|
import { findImageArchiveDownloadTask } from "./image-archive-db";
|
|
import { inferOriginalImageUrl } from "@/lib/image-url";
|
|
import { getImageDownloadExtension } from "@/lib/image-delivery";
|
|
import { sanitizeCanvasDownloadFileName } from "@/lib/canvas/photoshop-safe-download";
|
|
|
|
/** Resolve the archived original without waiting for a separate PNG conversion. */
|
|
export async function resolveOriginalCdnDownload(source: string, name: string, signal: AbortSignal) {
|
|
const config = await getDefaultStorageConfig();
|
|
if (config?.type !== "qiniu-kodo" || !config.isEnabled || !config.publicUrl) return null;
|
|
const base = new URL(config.publicUrl);
|
|
const origin = process.env.CANVAS_QINIU_ORIGIN_URL;
|
|
let target = new URL(source);
|
|
if (target.origin !== base.origin && (!origin || target.origin !== new URL(origin).origin)) {
|
|
const task = await findImageArchiveDownloadTask([...new Set([source, inferOriginalImageUrl(source)])]);
|
|
target = new URL(task?.targetUrl || source);
|
|
}
|
|
if (origin && target.origin === new URL(origin).origin && !target.searchParams.has("token")) {
|
|
target.host = base.host;
|
|
target.protocol = base.protocol;
|
|
}
|
|
if (base.protocol !== "https:" || target.origin !== base.origin ||
|
|
!target.pathname.startsWith(base.pathname.replace(/\/$/, "") + "/")) return null;
|
|
// A Kodo download token signs the complete URL; adding attname would invalidate it.
|
|
if (target.searchParams.has("token")) return null;
|
|
|
|
// Only metadata crosses the application server. The browser receives the file
|
|
// from the CDN, including when cross-origin fetch is disabled by storage CORS.
|
|
const response = await fetch(target, {
|
|
method: "HEAD", cache: "no-store",
|
|
signal: AbortSignal.any([signal, AbortSignal.timeout(10_000)]),
|
|
});
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
const contentType = response.headers.get("content-type") || "";
|
|
if (!contentType.toLowerCase().startsWith("image/")) throw new Error("Source is not an image");
|
|
const extension = getImageDownloadExtension(contentType);
|
|
if (!extension) return null;
|
|
const basename = sanitizeCanvasDownloadFileName(name || target.pathname.split("/").pop() || "image", "image")
|
|
.replace(/\.(?:png|jpe?g|webp|avif|gif|svg|bmp|tiff?)$/i, "");
|
|
const filename = `${basename}.${extension}`;
|
|
target.searchParams.set("attname", filename);
|
|
return { delivery: "cdn" as const, downloadUrl: target.toString(), filename,
|
|
bytes: Number(response.headers.get("content-length")) || 0 };
|
|
}
|