Files
IdP/apps/frontend/lib/resilient-media-fetch.ts
2026-06-30 23:43:49 +03:00

77 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const GATEWAY_RETRY_STATUS = new Set([502, 503, 504]);
const MEDIA_FETCH_MAX_ATTEMPTS = 3;
const MEDIA_SLOW_LOAD_WARN_MS = 8_000;
const MEDIA_STREAM_PATH = '/media/stream/';
const SAME_ORIGIN_API_PREFIX = '/idp-api';
function sleep(ms: number): Promise<void> {
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 function resolveBrowserMediaUrl(accessUrl: string): string {
if (typeof window === 'undefined') return accessUrl;
try {
const url = new URL(accessUrl, window.location.origin);
const streamIndex = url.pathname.indexOf(MEDIA_STREAM_PATH);
if (streamIndex === -1) {
return accessUrl;
}
const streamPath = url.pathname.slice(streamIndex);
return `${window.location.origin}${SAME_ORIGIN_API_PREFIX}${streamPath}${url.search}`;
} catch {
return accessUrl;
}
}
export async function fetchMediaBlobWithRetry(
accessUrl: string,
token: string,
options?: { mimeType?: string; label?: string }
): Promise<Blob> {
const startedAt = Date.now();
const label = options?.label ?? 'изображение';
const mediaUrl = resolveBrowserMediaUrl(accessUrl);
let lastError: unknown;
for (let attempt = 0; attempt < MEDIA_FETCH_MAX_ATTEMPTS; attempt += 1) {
try {
const response = await fetch(mediaUrl, {
headers: { Authorization: `Bearer ${token}` },
cache: 'no-store'
});
if (GATEWAY_RETRY_STATUS.has(response.status) && attempt + 1 < MEDIA_FETCH_MAX_ATTEMPTS) {
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('Не удалось загрузить файл');
}