// ── The feed: what happened on one server ───────────────────────────────── // // Rows come from `/public/rust/servers/:id/events`, which serves a default-deny // ALLOWLIST (`server/catalogue.js`). Everything carrying an IP address, a // player's report about another player, or the grid square somebody's base is in // is stored and never answered here — so this component cannot leak one by // forgetting to filter, which is the point of the boundary living on the server. // // It polls (org lead, phase 4): every twenty seconds while the tab is visible, // paused when it is not. `usePolled` keeps the rows on screen across a refresh — // see the comment at the top of that file for why core's `useAsync` cannot do // this job. import { EmptyState, ErrorState, Loading } from '../core.js' import { describe, FILTERS, kindsFor } from '../lib/feed.js' import { ago, clock } from '../lib/format.js' import usePolled from '../hooks/usePolled.js' import api from '../api.js' const TONE = { kill: 'var(--accent-bright)', death: 'var(--muted)', join: 'var(--mode-live, #5fb98a)', leave: 'var(--dim)', chat: 'var(--text)', server: 'var(--mode-maint, #e6c26a)', other: 'var(--muted)', } export default function Feed({ serverId, wipeId, filter, onFilter }) { const kinds = kindsFor(filter) const { data, error, loading, at } = usePolled( () => api.servers.events(serverId, { kinds, wipe: wipeId, limit: 100 }), // The key is the QUESTION. Changing server, wipe or filter blanks the rows, // because what is on screen is an answer to a different one; a poll tick // does not, because it is the same question asked again. { key: `${serverId}|${wipeId || ''}|${filter}`, intervalMs: 20_000 }, ) const events = data ? data.events : [] return (