import { ensureApiGatewayReady, invalidateApiGatewayReady } from '@/lib/api'; const GATEWAY_RETRY_STATUS = new Set([502, 503, 504]); const MEDIA_FETCH_MAX_ATTEMPTS = 8; const MEDIA_SLOW_LOAD_WARN_MS = 8_000; function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } function warnSlowMediaLoad(label: string, startedAt: number, error: unknown) { if (Date.now() - startedAt < MEDIA_SLOW_LOAD_WARN_MS) return; const detail = error instanceof Error ? error.message : 'неизвестная ошибка'; console.warn(`[media] ${label}: не удалось загрузить за ${Math.round((Date.now() - startedAt) / 1000)} с (${detail})`); } export async function fetchMediaBlobWithRetry( accessUrl: string, token: string, options?: { mimeType?: string; label?: string } ): Promise { const startedAt = Date.now(); const label = options?.label ?? 'изображение'; let lastError: unknown; for (let attempt = 0; attempt < MEDIA_FETCH_MAX_ATTEMPTS; attempt += 1) { try { await ensureApiGatewayReady(); const response = await fetch(accessUrl, { headers: { Authorization: `Bearer ${token}` }, cache: 'no-store' }); if (GATEWAY_RETRY_STATUS.has(response.status) && attempt + 1 < MEDIA_FETCH_MAX_ATTEMPTS) { invalidateApiGatewayReady(); await sleep(350 * (attempt + 1)); continue; } if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const raw = await response.blob(); const mimeType = options?.mimeType; if (mimeType && (!raw.type || raw.type === 'application/octet-stream')) { return new Blob([await raw.arrayBuffer()], { type: mimeType }); } return raw; } catch (error) { lastError = error; if (attempt + 1 < MEDIA_FETCH_MAX_ATTEMPTS) { await sleep(350 * (attempt + 1)); } } } warnSlowMediaLoad(label, startedAt, lastError); throw lastError instanceof Error ? lastError : new Error('Не удалось загрузить файл'); }