import { getJob, getReadInfo, refreshReadInfo } from "#lib/api/endpoints.js"; import type { ReadInfo } from "#lib/api/types.js"; import { accounts } from "#lib/stores/accounts.svelte.js"; const POLL_INTERVAL = 1000; const POLL_MAX = 20; export interface ReadEntry { fetching: boolean; info: ReadInfo; } function delay(ms: number): Promise { return new Promise((resolve) => { setTimeout(resolve, ms); }); } async function waitForJob(jobId: number) { for (let attempt = 0; attempt < POLL_MAX; attempt += 1) { // biome-ignore lint/performance/noAwaitInLoops: polling waits between tries on purpose await delay(POLL_INTERVAL); const job = await getJob(jobId); if (job.status !== "pending" && job.status !== "running") { return; } } } function createReads() { let entries = $state>({}); const inflight = new Set(); function key(chatId: number, messageId: number): string { return `${accounts.selectedId}:${chatId}:${messageId}`; } async function load(entryKey: string, chatId: number, messageId: number) { const info = await getReadInfo(chatId, messageId); entries[entryKey] = { fetching: info.can_fetch, info }; if (!info.can_fetch) { return; } try { const { job_id } = await refreshReadInfo(chatId, messageId); if (job_id !== null) { await waitForJob(job_id); } entries[entryKey] = { fetching: false, info: await getReadInfo(chatId, messageId), }; } finally { entries[entryKey].fetching = false; } } return { get(chatId: number, messageId: number): ReadEntry | undefined { return entries[key(chatId, messageId)]; }, open(chatId: number, messageId: number) { const entryKey = key(chatId, messageId); if (accounts.selectedId === null || inflight.has(entryKey)) { return; } inflight.add(entryKey); load(entryKey, chatId, messageId) .catch(() => undefined) .finally(() => { inflight.delete(entryKey); }); }, }; } export const reads = createReads();