import { browser } from "$app/env"; import { SvelteMap } from "svelte/reactivity"; import { avatarWidths } from "#lib/avatarWidths.ts"; const VERSIONS_KEY = "tangled.avatarVersions"; const VERSION_TTL_MS = 12 * 60 * 60 * 1000; const versions = new SvelteMap(); type StoredVersions = Record; const readStored = (): StoredVersions => { try { const parsed: unknown = JSON.parse(localStorage.getItem(VERSIONS_KEY) ?? "{}"); return parsed && typeof parsed === "object" ? (parsed as StoredVersions) : {}; } catch { return {}; } }; const writeStored = (stored: StoredVersions): boolean => { try { localStorage.setItem(VERSIONS_KEY, JSON.stringify(stored)); return true; } catch { return false; } }; export const restoreAvatarVersions = (): void => { if (!browser) return; const now = Date.now(); for (const [did, entry] of Object.entries(readStored())) { if (typeof entry?.version === "string" && now - entry.at < VERSION_TTL_MS) { versions.set(did, entry.version); } } }; export const bumpAvatar = (did: string, version: string = String(Date.now())): void => { versions.set(did, version); if (!browser) return; const now = Date.now(); const kept = Object.fromEntries( Object.entries(readStored()).filter(([, entry]) => now - entry.at < VERSION_TTL_MS) ); writeStored({ ...kept, [did]: { version, at: now } }); }; export const avatarWidthFor = (px: number): number => avatarWidths.find((width) => width >= px * 2) ?? avatarWidths[avatarWidths.length - 1]; // the service wants signed urls and the secret is server side, so this points // at the route that signs export const avatarUrl = ( did: string, width?: number, format?: "webp" | "jpeg" | "png", cid?: string ): string => { // cids all start the same way. the end tells them apart const version = versions.get(did) ?? cid?.slice(-12); const query = new URLSearchParams({ ...(width ? { size: String(width) } : {}), ...(format ? { format } : {}), ...(version ? { v: version } : {}) }).toString(); return `/avatar/${encodeURIComponent(did)}${query ? `?${query}` : ""}`; };