88 lines
2.2 KiB
TypeScript
88 lines
2.2 KiB
TypeScript
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 interface CustomEmojiAsset {
|
|
mime: string;
|
|
url: string;
|
|
}
|
|
|
|
const MAX_CACHED = 500;
|
|
|
|
const ready = new Map<string, CustomEmojiAsset>();
|
|
const missing = new Set<string>();
|
|
const inflight = new Map<string, Promise<CustomEmojiAsset | null>>();
|
|
|
|
function remember(key: string, asset: CustomEmojiAsset) {
|
|
ready.set(key, asset);
|
|
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.url);
|
|
}
|
|
}
|
|
}
|
|
|
|
function authHeaders(): Record<string, string> {
|
|
return auth.token ? { Authorization: `Bearer ${auth.token}` } : {};
|
|
}
|
|
|
|
function delay(ms: number): Promise<void> {
|
|
return new Promise((resolve) => {
|
|
setTimeout(resolve, ms);
|
|
});
|
|
}
|
|
|
|
async function fetchEmoji(
|
|
account: number,
|
|
id: string,
|
|
key: string,
|
|
retry: boolean
|
|
): Promise<CustomEmojiAsset | null> {
|
|
const url = `${BASE}/custom-emoji/${id}?account_id=${account}`;
|
|
const response = await fetch(url, { headers: authHeaders() });
|
|
if (response.ok) {
|
|
const blob = await response.blob();
|
|
const asset = { mime: blob.type, url: URL.createObjectURL(blob) };
|
|
remember(key, asset);
|
|
return asset;
|
|
}
|
|
if (response.status === 409 && retry) {
|
|
await delay(RETRY_DELAY);
|
|
return fetchEmoji(account, id, key, false);
|
|
}
|
|
missing.add(key);
|
|
return null;
|
|
}
|
|
|
|
export function loadCustomEmoji(id: string): Promise<CustomEmojiAsset | null> {
|
|
const account = accounts.selectedId;
|
|
if (account === null) {
|
|
return Promise.resolve(null);
|
|
}
|
|
const key = `${account}:${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 = fetchEmoji(account, id, key, true).finally(() => {
|
|
inflight.delete(key);
|
|
});
|
|
inflight.set(key, promise);
|
|
return promise;
|
|
}
|