import { useEffect, useState } from 'react'; import { Method } from 'matrix-js-sdk'; import { useMatrixClient } from './useMatrixClient'; const PROFILE_FIELD = 'io.lotus.avatar_decoration'; // Module-level cache — survives re-renders, lives for the app session. // userId → slug | null (null = fetched, no decoration set) const cache = new Map(); // Callbacks waiting for a userId's result const pending = new Map void>>(); function fetchDecoration( authedRequest: (method: Method, path: string) => Promise>, userId: string, ): Promise { if (cache.has(userId)) return Promise.resolve(cache.get(userId) ?? null); // De-duplicate in-flight requests for the same userId if (pending.has(userId)) { return new Promise((resolve) => { pending.get(userId)!.push(resolve); }); } const waiters: Array<(val: string | null) => void> = []; pending.set(userId, waiters); return authedRequest(Method.Get, `/profile/${encodeURIComponent(userId)}/${PROFILE_FIELD}`) .then((res) => { const val = (res[PROFILE_FIELD] as string | undefined) ?? null; cache.set(userId, val); return val; }) .catch(() => { cache.set(userId, null); return null; }) .finally(() => { const v = cache.get(userId) ?? null; pending.delete(userId); waiters.forEach((cb) => cb(v)); }); } export function invalidateDecorationCache(userId: string): void { cache.delete(userId); } export function useAvatarDecoration(userId: string): string | null { const mx = useMatrixClient(); const [slug, setSlug] = useState(() => cache.get(userId) ?? null); useEffect(() => { let cancelled = false; fetchDecoration( (method, path) => mx.http.authedRequest>(method, path), userId, ).then((val) => { if (!cancelled) setSlug(val); }); return () => { cancelled = true; }; }, [mx, userId]); return slug; }