import { writable, readonly } from 'svelte/store'; import { browser } from '$app/environment'; import type { RoundTelemetry } from '$lib/telemetry.js'; export const telemetryByServer = writable>(new Map()); export interface RosterPlayer { num: number | null; score: number; ping: number; guid: string | null; name: string; } export type Health = 'online' | 'stale' | 'offline' | 'starting'; export type RconFailure = 'no-config' | 'no-port' | 'send-error' | 'timeout' | 'bad-reply' | 'no-players'; export interface RconEndpoint { host: string; port: number; } export interface ServerStatus { server: string; players: number | null; map: string | null; gametype: string | null; roster: RosterPlayer[] | null; health: Health; lastSeenAt: string | null; lastError: string | null; endpoint: RconEndpoint | null; reason: RconFailure | null; replySnippet: string | null; activeSince: string | null; inactiveSince: string | null; gameStartedAt: string | null; gameEndedAt: string | null; } export const statusByServer = writable>(new Map()); // Shared 1s ticker for elapsed-time displays. One interval feeds every card // on the page instead of N per-card timers. Timer starts on the first // startServerStream() call in the browser. const _uiNow = writable(Date.now()); export const uiNow = readonly(_uiNow); let tickInterval: ReturnType | null = null; let source: EventSource | null = null; export function startServerStream(): void { if (!browser || source) return; source = new EventSource('/api/events/servers'); source.addEventListener('server-telemetry', (evt) => { const data = JSON.parse((evt as MessageEvent).data) as { entries: RoundTelemetry[]; snapshot?: boolean }; telemetryByServer.update((previous) => { const next = data.snapshot ? new Map() : new Map(previous); for (const entry of data.entries) next.set(entry.server, entry); return next; }); }); source.addEventListener('server-status', (evt) => { const data = JSON.parse((evt as MessageEvent).data) as { entries: ServerStatus[] }; statusByServer.update(() => { const m = new Map(); for (const e of data.entries) m.set(e.server, e); return m; }); }); if (tickInterval === null) { tickInterval = setInterval(() => _uiNow.set(Date.now()), 1000); } }