import "server-only"; const DEFAULT_TIMEOUT_MS = 30_000; const DEFAULT_RETRY_COUNT = 3; const RETRY_DELAY_MS = 1_000; function sleep(ms: number) { return new Promise((resolve) => setTimeout(resolve, ms)); } export function describeRemoteAssetLocation(url: string) { const normalizedUrl = String(url || "").trim(); if (!normalizedUrl) { return "未知地址"; } try { const parsed = new URL(normalizedUrl); return parsed.host || parsed.hostname || normalizedUrl; } catch { return normalizedUrl.length > 80 ? `${normalizedUrl.slice(0, 77)}...` : normalizedUrl; } } function isRetryableStatus(status: number) { return status === 408 || status === 425 || status === 429 || status >= 500; } function isRetryableFetchError(error: unknown) { if (!(error instanceof Error)) { return true; } const message = `${error.name} ${error.message}`.toLowerCase(); return ( error.name === "AbortError" || message.includes("fetch failed") || message.includes("timeout") || message.includes("timed out") || message.includes("econnreset") || message.includes("socket") || message.includes("network") || message.includes("terminated") || message.includes("und_err") ); } export async function fetchRemoteAssetBuffer( url: string, options?: { label?: string; timeoutMs?: number; retries?: number; } ) { const normalizedUrl = String(url || "").trim(); const label = String(options?.label || "远程资源").trim() || "远程资源"; if (!normalizedUrl) { throw new Error(`${label}地址为空`); } const attempts = Math.max(1, Math.round(Number(options?.retries || DEFAULT_RETRY_COUNT))); const timeoutMs = Math.max(5_000, Math.round(Number(options?.timeoutMs || DEFAULT_TIMEOUT_MS))); const location = describeRemoteAssetLocation(normalizedUrl); for (let attempt = 1; attempt <= attempts; attempt += 1) { try { const response = await fetch(normalizedUrl, { cache: "no-store", signal: AbortSignal.timeout(timeoutMs), }); if (!response.ok) { const statusError = new Error(`${label}下载失败(${response.status})`); if (attempt < attempts && isRetryableStatus(response.status)) { console.warn("[PSD Remote Asset] transient status, retrying download:", { label, location, status: response.status, attempt, attempts, }); await sleep(RETRY_DELAY_MS * attempt); continue; } throw statusError; } return Buffer.from(await response.arrayBuffer()); } catch (error) { if (attempt < attempts && isRetryableFetchError(error)) { console.warn("[PSD Remote Asset] transient fetch error, retrying download:", { label, location, attempt, attempts, error, }); await sleep(RETRY_DELAY_MS * attempt); continue; } console.error("[PSD Remote Asset] failed to download asset:", { label, location, attemptsTried: attempt, error, }); throw new Error(`${label}下载失败,请稍后重试或检查资源是否可访问(${location})`); } } throw new Error(`${label}下载失败,请稍后重试或检查资源是否可访问(${location})`); }