'use client'; import { useCallback, useEffect, useRef, useState } from 'react'; import type { ChatMessage } from '@/lib/api'; import { fetchChatMediaUrl } from '@/lib/api'; import { createBlobObjectUrl, fetchAuthenticatedMediaBlob, revokeBlobObjectUrl } from '@/lib/authenticated-media'; import { parseChatMediaMetadata } from '@/lib/chat-media-album'; export type ChatRoomMediaEntry = { blobUrl?: string; fileName?: string; mimeType?: string; status?: 'loading' | 'ready' | 'error'; errorMessage?: string; }; export function useChatRoomMedia(token: string | null, roomId: string | null, messages: ChatMessage[]) { const [mediaUrls, setMediaUrls] = useState>({}); const mediaUrlsRef = useRef>({}); const inFlightRef = useRef(new Set()); useEffect(() => { mediaUrlsRef.current = mediaUrls; }, [mediaUrls]); const loadMedia = useCallback( async (message: ChatMessage, force = false) => { if (!token || !roomId || !message.storageKey || message.isDeleted || message.isEncrypted) return; const storageKey = message.storageKey; const existing = mediaUrlsRef.current[storageKey]; if (!force && existing?.status === 'ready' && existing.blobUrl) { return; } if (inFlightRef.current.has(storageKey) && !force) { return; } inFlightRef.current.add(storageKey); setMediaUrls((current) => ({ ...current, [storageKey]: { ...current[storageKey], status: 'loading', errorMessage: undefined } })); const meta = parseChatMediaMetadata(message.metadataJson); const fileName = meta.fileName || message.content || undefined; try { const access = await fetchChatMediaUrl(roomId, storageKey, token, fileName); const blob = await fetchAuthenticatedMediaBlob(access.accessUrl, token, message.mimeType); const blobUrl = createBlobObjectUrl(blob); setMediaUrls((current) => { revokeBlobObjectUrl(current[storageKey]?.blobUrl); return { ...current, [storageKey]: { blobUrl, fileName, mimeType: message.mimeType, status: 'ready' } }; }); } catch (error) { setMediaUrls((current) => ({ ...current, [storageKey]: { ...current[storageKey], status: 'error', errorMessage: error instanceof Error ? error.message : 'Не удалось загрузить файл' } })); } finally { inFlightRef.current.delete(storageKey); } }, [roomId, token] ); useEffect(() => { if (!token || !roomId) return; messages.forEach((message) => { if (message.storageKey && message.type === 'IMAGE') { void loadMedia(message); } }); }, [loadMedia, messages, roomId, token]); useEffect(() => { return () => { Object.values(mediaUrlsRef.current).forEach((entry) => revokeBlobObjectUrl(entry.blobUrl)); }; }, []); return { mediaUrls, loadMedia }; }