// 처리 큐 overview store — GET /api/queue/overview 를 60초 주기로 폴링. // system.ts 의 dashboardSummary 와 같은 구독 기반 패턴 (첫 subscribe 시 시작). // // 의도적으로 api() 헬퍼를 쓰지 않는다 — 폴링 경로의 401 이 refresh 실패 → // window.location='/login' 강제 logout 부수효과를 일으키면 안 됨 (eid 리뷰 // finding 재발 방지). 백엔드 미배포(404)/401/네트워크 실패 전부 silent 하게 // null 로 수렴하고, 소비자(스트립/보드/드로어)는 null 이면 스스로 숨는다. import { writable } from 'svelte/store'; import { browser } from '$app/environment'; import { getAccessToken } from '$lib/api'; import type { QueueOverview } from '$lib/types/queue'; const POLL_INTERVAL_MS = 60_000; let pollHandle: ReturnType | null = null; let subscriberCount = 0; let inFlight: Promise | null = null; const internal = writable(null, (_set) => { subscriberCount += 1; if (subscriberCount === 1 && browser) { void refreshQueueOverview(); pollHandle = setInterval(() => void refreshQueueOverview(), POLL_INTERVAL_MS); } return () => { subscriberCount -= 1; if (subscriberCount === 0 && pollHandle) { clearInterval(pollHandle); pollHandle = null; } }; }); export const queueOverview = { subscribe: internal.subscribe }; /** 경량 fetch — 실패는 전부 null (silent 비차단, 강제 logout 경로 없음) */ async function fetchOverview(): Promise { try { const headers: Record = {}; const token = getAccessToken(); if (token) headers['Authorization'] = `Bearer ${token}`; const res = await fetch('/api/queue/overview', { headers, credentials: 'include' }); if (!res.ok) return null; return (await res.json()) as QueueOverview; } catch { return null; } } /** 수동/추가 폴링용 — 홈은 자체 30s interval 로 이 함수를 호출 (동시 fetch 합치기) */ export async function refreshQueueOverview(): Promise { if (!browser) return; if (inFlight) return inFlight; inFlight = (async () => { try { internal.set(await fetchOverview()); } finally { inFlight = null; } })(); return inFlight; }