import { useCallback, useEffect, useState } from 'react'; import { useMatrixClient } from './useMatrixClient'; import { useMediaAuthentication } from './useMediaAuthentication'; import { mxcUrlToHttp } from '../utils/matrix'; import { getCurrentAccessToken } from '../utils/auth'; import { deleteBannerUrl, loadBannerUrl, registerBannerCacheClear, saveBannerUrl, } from '../utils/profileFields'; /** * Hook to manage the user's profile banner (MSC4427 via MSC4133). */ export function useUserBanner(): [ string | undefined, (banner: string | undefined) => Promise, boolean ] { const mx = useMatrixClient(); const [banner, setBanner] = useState(); const [loading, setLoading] = useState(true); useEffect(() => { const userId = mx.getUserId(); if (!userId) { setLoading(false); return undefined; } let cancelled = false; const load = async () => { setLoading(true); try { const bannerMxc = await loadBannerUrl(mx, userId); if (!cancelled) setBanner(bannerMxc); } catch { if (!cancelled) setBanner(undefined); } if (!cancelled) setLoading(false); }; load(); return () => { cancelled = true; }; }, [mx]); const updateBanner = useCallback( async (newBanner: string | undefined) => { if (!newBanner) { await deleteBannerUrl(mx); setBanner(undefined); return; } await saveBannerUrl(mx, newBanner); setBanner(newBanner); }, [mx] ); return [banner, updateBanner, loading]; } const userBannerCache = new Map(); const CACHE_TTL_MS = 5 * 60 * 1000; registerBannerCacheClear((userId: string) => { userBannerCache.delete(userId); }); async function fetchBannerBlobUrl( mx: ReturnType, bannerMxc: string, useAuthentication: boolean ): Promise { const bannerHttpUrl = mxcUrlToHttp(mx, bannerMxc, useAuthentication); if (!bannerHttpUrl) return undefined; const accessToken = getCurrentAccessToken(); const headers: HeadersInit = {}; if (useAuthentication && accessToken) { headers.Authorization = `Bearer ${accessToken}`; } const response = await fetch(bannerHttpUrl, { headers }); if (!response.ok) return undefined; const blob = await response.blob(); return URL.createObjectURL(blob); } /** * Hook to get another user's profile banner as a blob URL. */ export function useOtherUserBanner(userId: string): string | undefined { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const [bannerBlobUrl, setBannerBlobUrl] = useState(); useEffect(() => { if (!userId) { setBannerBlobUrl(undefined); return undefined; } let blobUrl: string | undefined; let cancelled = false; const load = async () => { const cached = userBannerCache.get(userId); let bannerMxc: string | undefined; if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) { bannerMxc = cached.bannerMxc; } else { bannerMxc = await loadBannerUrl(mx, userId); userBannerCache.set(userId, { bannerMxc, timestamp: Date.now() }); } if (!bannerMxc) { if (!cancelled) setBannerBlobUrl(undefined); return; } blobUrl = await fetchBannerBlobUrl(mx, bannerMxc, useAuthentication); if (!cancelled) setBannerBlobUrl(blobUrl); }; load(); return () => { cancelled = true; if (blobUrl) URL.revokeObjectURL(blobUrl); }; }, [mx, useAuthentication, userId]); return bannerBlobUrl; }