Files
electric-cherry/src/lib/ui/stores/servers.ts
T

75 lines
2.5 KiB
TypeScript

import { writable, readonly } from 'svelte/store';
import { browser } from '$app/environment';
import type { RoundTelemetry } from '$lib/telemetry.js';
export const telemetryByServer = writable<Map<string, RoundTelemetry>>(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<Map<string, ServerStatus>>(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<typeof setInterval> | 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<string, RoundTelemetry>() : 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<string, ServerStatus>();
for (const e of data.entries) m.set(e.server, e);
return m;
});
});
if (tickInterval === null) {
tickInterval = setInterval(() => _uiNow.set(Date.now()), 1000);
}
}