129 lines
3.8 KiB
TypeScript
129 lines
3.8 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { isMediaInputFailure } from "@/lib/ai/runtime/request-failure-classification";
|
|
|
|
export const VIP98K_REFERENCE_STAGING_KEY_PREFIX = "uploads/vip98k-";
|
|
const VIP98K_REFERENCE_CACHE_OBJECT_PREFIX = "uploads/vip98k-cache-v2";
|
|
|
|
function parseUrl(value: string) {
|
|
try {
|
|
return new URL(String(value || "").trim());
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function isVip98kStagedReferenceUrl(value: string) {
|
|
const parsed = parseUrl(value);
|
|
return Boolean(
|
|
parsed?.pathname.includes(`/${VIP98K_REFERENCE_STAGING_KEY_PREFIX}`),
|
|
);
|
|
}
|
|
|
|
export function getVip98kReferenceStagingObjectKey(mediaUrl: string) {
|
|
const parsed = parseUrl(mediaUrl);
|
|
if (parsed) {
|
|
parsed.hash = "";
|
|
parsed.searchParams.delete("sg_media_retry");
|
|
}
|
|
const cacheIdentity = parsed?.toString() || mediaUrl;
|
|
const digest = createHash("sha256").update(cacheIdentity).digest("hex");
|
|
return `${VIP98K_REFERENCE_CACHE_OBJECT_PREFIX}/${digest}.jpg`;
|
|
}
|
|
|
|
export function isUrlWithinPublicBase(mediaUrl: string, publicBaseUrl: string) {
|
|
const media = parseUrl(mediaUrl);
|
|
const base = parseUrl(publicBaseUrl);
|
|
if (!media || !base || media.host.toLowerCase() !== base.host.toLowerCase()) {
|
|
return false;
|
|
}
|
|
|
|
const basePath = base.pathname.replace(/\/+$/, "");
|
|
if (!basePath) return true;
|
|
return media.pathname === basePath || media.pathname.startsWith(`${basePath}/`);
|
|
}
|
|
|
|
export function shouldStageVip98kReferenceUrl(params: {
|
|
mediaUrl: string;
|
|
sourcePublicUrl: string;
|
|
targetPublicUrl?: string;
|
|
}) {
|
|
if (
|
|
params.targetPublicUrl &&
|
|
isUrlWithinPublicBase(params.mediaUrl, params.targetPublicUrl)
|
|
) {
|
|
return false;
|
|
}
|
|
return isUrlWithinPublicBase(params.mediaUrl, params.sourcePublicUrl);
|
|
}
|
|
|
|
export function getStorageObjectKeyFromPublicUrl(
|
|
mediaUrl: string,
|
|
publicBaseUrl: string,
|
|
) {
|
|
if (!isUrlWithinPublicBase(mediaUrl, publicBaseUrl)) return null;
|
|
const media = parseUrl(mediaUrl);
|
|
const base = parseUrl(publicBaseUrl);
|
|
if (!media || !base) return null;
|
|
const basePath = base.pathname.replace(/\/+$/, "");
|
|
const encodedKey = media.pathname.slice(basePath.length).replace(/^\/+/, "");
|
|
if (!encodedKey) return null;
|
|
try {
|
|
return decodeURIComponent(encodedKey);
|
|
} catch {
|
|
return encodedKey;
|
|
}
|
|
}
|
|
|
|
export function getVip98kStagedMediaRetryDelay(params: {
|
|
status: number;
|
|
message: string;
|
|
attempt: number;
|
|
maxAttempts: number;
|
|
stagedReferenceCount: number;
|
|
}) {
|
|
if (
|
|
params.status !== 400 ||
|
|
params.stagedReferenceCount < 1 ||
|
|
params.attempt >= params.maxAttempts ||
|
|
/(?:larger than|too large|exceeds?|maximum).*(?:MiB|MB|bytes|size)/i.test(params.message) ||
|
|
!isMediaInputFailure(params.message)
|
|
) {
|
|
return null;
|
|
}
|
|
return params.attempt * 20_000;
|
|
}
|
|
|
|
export function getVip98kSubmissionRetryDelay(params: {
|
|
status: number;
|
|
message: string;
|
|
attempt: number;
|
|
maxAttempts: number;
|
|
retryAfterMs?: number | null;
|
|
}) {
|
|
const recoverableGatewayFailure = params.status === 502 || params.status === 504;
|
|
const recoverableSubmission =
|
|
/pending\s+safe\s+reconciliation/i.test(params.message) ||
|
|
/request\s+outcome\s+(?:is\s+)?unknown/i.test(params.message) ||
|
|
/final\s+outcome\s+could\s+not\s+be\s+confirmed/i.test(params.message);
|
|
if (
|
|
params.attempt >= params.maxAttempts ||
|
|
(!recoverableGatewayFailure &&
|
|
(params.status !== 503 || !recoverableSubmission))
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const providerDelay = Number(params.retryAfterMs);
|
|
if (Number.isFinite(providerDelay) && providerDelay > 0) {
|
|
return Math.min(Math.max(providerDelay, 1_000), 60_000);
|
|
}
|
|
return Math.min(params.attempt * 15_000, 30_000);
|
|
}
|
|
|
|
export function addVip98kReferenceRetryToken(mediaUrl: string, token: string) {
|
|
const parsed = parseUrl(mediaUrl);
|
|
if (!parsed) return mediaUrl;
|
|
parsed.searchParams.set("sg_media_retry", token);
|
|
return parsed.toString();
|
|
}
|