import { accounts } from "$lib/stores/accounts.svelte"; import { auth } from "$lib/stores/auth.svelte"; const BASE = import.meta.env.VITE_API_BASE ?? "/api"; const RETRY_DELAY = 2500; export type AvatarKind = "peer" | "chat"; const MAX_CACHED = 240; const ready = new Map(); const missing = new Set(); const inflight = new Map>(); function cacheKey(account: number, kind: AvatarKind, id: number): string { return `${account}:${kind}:${id}`; } function remember(key: string, url: string) { ready.set(key, url); while (ready.size > MAX_CACHED) { const oldest = ready.keys().next(); if (oldest.done) { return; } const stale = ready.get(oldest.value); ready.delete(oldest.value); if (stale) { URL.revokeObjectURL(stale); } } } function authHeaders(): Record { return auth.token ? { Authorization: `Bearer ${auth.token}` } : {}; } function delay(ms: number): Promise { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function fetchAvatar( account: number, kind: AvatarKind, id: number, key: string, retry: boolean ): Promise { const url = `${BASE}/avatars/${kind}/${id}?account_id=${account}`; const response = await fetch(url, { headers: authHeaders() }); if (response.ok) { const objectUrl = URL.createObjectURL(await response.blob()); remember(key, objectUrl); return objectUrl; } if (response.status === 409 && retry) { await delay(RETRY_DELAY); return fetchAvatar(account, kind, id, key, false); } missing.add(key); return null; } export function loadAvatar( kind: AvatarKind, id: number ): Promise { const account = accounts.selectedId; if (account === null) { return Promise.resolve(null); } const key = cacheKey(account, kind, id); const cached = ready.get(key); if (cached) { return Promise.resolve(cached); } if (missing.has(key)) { return Promise.resolve(null); } const existing = inflight.get(key); if (existing) { return existing; } const promise = fetchAvatar(account, kind, id, key, true).finally(() => { inflight.delete(key); }); inflight.set(key, promise); return promise; } async function fetchVariant( account: number, kind: AvatarKind, id: number, uniqueId: string, key: string, retry: boolean ): Promise { const url = `${BASE}/avatars/${kind}/${id}?account_id=${account}&unique_id=${encodeURIComponent(uniqueId)}`; const response = await fetch(url, { headers: authHeaders() }); if (response.ok) { const objectUrl = URL.createObjectURL(await response.blob()); remember(key, objectUrl); return objectUrl; } if (response.status === 409 && retry) { await delay(RETRY_DELAY); return fetchVariant(account, kind, id, uniqueId, key, false); } missing.add(key); return null; } export function loadAvatarVariant( kind: AvatarKind, id: number, uniqueId: string ): Promise { const account = accounts.selectedId; if (account === null) { return Promise.resolve(null); } const key = `${cacheKey(account, kind, id)}:${uniqueId}`; const cached = ready.get(key); if (cached) { return Promise.resolve(cached); } if (missing.has(key)) { return Promise.resolve(null); } const existing = inflight.get(key); if (existing) { return existing; } const promise = fetchVariant(account, kind, id, uniqueId, key, true).finally( () => { inflight.delete(key); } ); inflight.set(key, promise); return promise; }