import localforage from "localforage"; import { cloudAuth } from "@/seqta/utils/CloudAuth"; const ACCOUNTS_BASE = "https://accounts.betterseqta.org"; const store = localforage.createInstance({ name: "cloud-pfp-store", storeName: "cloudPfp", }); function hashKey(userId: string) { return `hash:${userId}`; } function blobKey(userId: string) { return `blob:${userId}`; } export function isAccountsHostedPfpUrl(url: string): boolean { if (!url.includes("/api/user/pfp/")) return false; if (url.includes("/hist/")) return false; return /\/api\/user\/pfp\/[^/?#]+/.test(url.split("?")[0]!); } export function pfpUrlWithHash(url: string, hash: string | null | undefined): string { if (!url || !hash || !isAccountsHostedPfpUrl(url)) return url; const base = url.split("?")[0]!; return `${base}?v=${hash}`; } export function defaultAccountsPfpUrl(userId: string): string { return `${ACCOUNTS_BASE}/api/user/pfp/${userId}`; } async function fetchServerHash(userId: string): Promise { const res = await fetch(`${ACCOUNTS_BASE}/api/user/pfp/${userId}/meta`); if (!res.ok) return null; const data = (await res.json()) as { pfpHash?: string | null }; return data.pfpHash ?? null; } async function clearLocal(userId: string): Promise { await store.removeItem(hashKey(userId)); await store.removeItem(blobKey(userId)); } export async function clearCloudPfpCache(userId?: string): Promise { const id = userId ?? cloudAuth.state.user?.id; if (!id) return; await clearLocal(id); } export type ResolveCloudPfpResult = { src: string; fromCache: boolean; }; /** * Returns an object URL or direct URL for the cloud profile picture. * Order: session hash match → local blob; else meta → download → store blob then hash. */ export async function resolveCloudPfp( userId: string, pfpUrl: string, ): Promise { if (!isAccountsHostedPfpUrl(pfpUrl)) { return { src: pfpUrl, fromCache: false }; } const sessionHash = cloudAuth.state.user?.pfpHash ?? null; const localHash = await store.getItem(hashKey(userId)); const localBlob = await store.getItem(blobKey(userId)); let serverHash = sessionHash; const localMatches = !!serverHash && serverHash === localHash && localBlob instanceof Blob; if (localMatches) { return { src: URL.createObjectURL(localBlob), fromCache: true }; } if (!serverHash || serverHash !== localHash) { serverHash = await fetchServerHash(userId); } if (!serverHash) { await clearLocal(userId); return null; } if (serverHash === localHash && localBlob instanceof Blob) { return { src: URL.createObjectURL(localBlob), fromCache: true }; } await clearLocal(userId); const imageUrl = pfpUrlWithHash(pfpUrl, serverHash); const headers: HeadersInit = {}; if (localHash) { headers["If-None-Match"] = `"${localHash}"`; } const token = await cloudAuth.getStoredToken(); if (token && isAccountsHostedPfpUrl(pfpUrl)) { headers.Authorization = `Bearer ${token}`; } const res = await fetch(imageUrl, { headers }); if (res.status === 304 && localBlob instanceof Blob) { await store.setItem(hashKey(userId), serverHash); return { src: URL.createObjectURL(localBlob), fromCache: true }; } if (!res.ok) return null; const blob = await res.blob(); await store.setItem(blobKey(userId), blob); await store.setItem(hashKey(userId), serverHash); return { src: URL.createObjectURL(blob), fromCache: false }; }