From 22bff51147a3baf0681f8272b34b1eb0e6931f38 Mon Sep 17 00:00:00 2001 From: Heidi Date: Sat, 20 Jun 2026 00:43:44 +0100 Subject: [PATCH] ai generated solutions to our ai generated problems --- README.md | 14 +-- example.env | 2 +- frontend/index.html | 1 - frontend/public/data/README.md | 7 +- frontend/src/App.jsx | 2 +- frontend/src/main.jsx | 51 +++++++++- server.cjs | 176 +++++++++++++++++++++++++++++---- 7 files changed, 218 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index a444fdc..2afa7f2 100644 --- a/README.md +++ b/README.md @@ -86,12 +86,12 @@ Vehicle icon PNGs are served statically at `/vehicle-icons` from `VEHICLE_ICONS_ The proxy blocks cross-origin/API-navigation requests, strips CORS headers from the upstream response, rate limits callers, and caches successful GET responses. -Public TSS reads are written to a bounded JSON snapshot cache and served at both -their `/api/tss/*` route and matching `/data/*` path. The frontend uses `/data/*` -by default for public pages. Fresh snapshots return without touching the backend; -stale snapshots are served immediately while the server refreshes them in the -background. Missing `/data/*` snapshots are filled from the matching upstream API -with a short timeout, then written atomically for future requests. All responses +Public TSS reads are written to a bounded JSON snapshot cache and served through +their normal `/api/tss/*` route. Fresh snapshots return without touching the +backend; stale snapshots are served immediately while the server refreshes them +in the background. Matching `/data/*` paths are also available for diagnostics or +static-first experiments, but the frontend uses `/api/tss/*` by default so the +site stays dynamic. All responses ship `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy`, `Permissions-Policy`, `Cross-Origin-Opener-Policy`, `Cross-Origin-Resource-Policy`, HSTS (over HTTPS), and HTML responses include a Content Security Policy that @@ -119,7 +119,7 @@ PUBLIC_DATA_CACHE_FRESH_MS=300000 PUBLIC_DATA_CACHE_STALE_MS=86400000 PUBLIC_DATA_PREWARM_INTERVAL_MS=300000 PUBLIC_DATA_COLD_TIMEOUT_MS=8000 -VITE_STATIC_DATA=true +VITE_STATIC_DATA=false VITE_SITE_GATE=false API_RATE_LIMIT_WINDOW_MS=60000 API_RATE_LIMIT_MAX=120 diff --git a/example.env b/example.env index e7b0dad..6a9e844 100644 --- a/example.env +++ b/example.env @@ -63,4 +63,4 @@ DISCORD_INCLUDE_PATCH=true VITE_TURNSTILE_SITE_KEY= TURNSTILE_SECRET_KEY= VITE_SITE_GATE=false -VITE_STATIC_DATA=true +VITE_STATIC_DATA=false diff --git a/frontend/index.html b/frontend/index.html index 0a9860d..eefd45b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -38,7 +38,6 @@ } catch {} window.__TSS_BOOT_PREFERENCES__ = { analyticsPreferences, theme } - window.__TSS_BOOT_DATA__ = __TSS_BOOT_DATA__ document.documentElement.dataset.theme = theme document.documentElement.style.colorScheme = theme document.querySelector('meta[name="theme-color"]')?.setAttribute( diff --git a/frontend/public/data/README.md b/frontend/public/data/README.md index a750291..237255a 100644 --- a/frontend/public/data/README.md +++ b/frontend/public/data/README.md @@ -1,8 +1,9 @@ # Static Public Data -The frontend tries these JSON snapshots before falling back to the live API by -default. The web server serves `/data/*` from the public data cache and fills -missing snapshots from the matching `/api/tss/*` route with a short timeout. +The web server serves `/data/*` from the same public data cache used by +`/api/tss/*`, and fills missing snapshots from the matching API route with a +short timeout. The frontend uses `/api/tss/*` by default; set +`VITE_STATIC_DATA=true` only for static-first experiments. - `/data/leaderboard-teams.json` - `/data/leaderboard-players.json` diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index aabfb49..8801076 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -52,7 +52,7 @@ const siteVersion = '1.0.1' const turnstileSiteKey = import.meta.env.VITE_TURNSTILE_SITE_KEY || '' const siteGateEnabled = String(import.meta.env.VITE_SITE_GATE || 'false').toLowerCase() === 'true' const staticDataBase = (import.meta.env.VITE_STATIC_DATA_BASE || '/data').replace(/\/+$/, '') -const staticDataEnabled = String(import.meta.env.VITE_STATIC_DATA || 'true').toLowerCase() !== 'false' +const staticDataEnabled = String(import.meta.env.VITE_STATIC_DATA || 'false').toLowerCase() === 'true' const missingStaticDataPaths = new Set() const defaultAnalyticsPreferences = { diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 5671800..f4199eb 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -2,4 +2,53 @@ import { createRoot } from 'react-dom/client' import './styles.css' import App from './App.jsx' -createRoot(document.getElementById('root')).render() +const root = document.getElementById('root') + +async function fetchBootJson(path) { + const response = await fetch(path, { headers: { Accept: 'application/json' } }) + if (!response.ok) throw new Error(`Boot request failed with ${response.status}`) + return response.json() +} + +async function preloadBootData() { + if (!root?.dataset.tssFallback) return + + const pathname = window.location.pathname + const timeout = new Promise((_, reject) => { + window.setTimeout(() => reject(new Error('Boot preload timed out')), 1500) + }) + + const load = async () => { + if (pathname === '/') { + const [homeTeams, live] = await Promise.all([ + fetchBootJson('/data/home-teams.json'), + fetchBootJson('/data/recent-games.json'), + ]) + return { homeTeams, live } + } + + if (pathname === '/teams') { + return { leaderboard: await fetchBootJson('/data/leaderboard-teams.json') } + } + + if (pathname === '/players') { + return { playerLeaderboard: await fetchBootJson('/data/leaderboard-players.json') } + } + + if (pathname === '/battle-logs' || pathname === '/live') { + const [live, leaderboard] = await Promise.all([ + fetchBootJson('/data/recent-games.json'), + fetchBootJson('/data/leaderboard-teams.json'), + ]) + return { live, leaderboard } + } + + return null + } + + window.__TSS_BOOT_DATA__ = await Promise.race([load(), timeout]).catch(() => null) +} + +preloadBootData().finally(() => { + createRoot(root).render() +}) diff --git a/server.cjs b/server.cjs index 46a7205..133f308 100644 --- a/server.cjs +++ b/server.cjs @@ -2294,28 +2294,156 @@ function readPublicDataSnapshot(relativePath) { } } -function routeBootData(pathname) { - const boot = {} +function fallbackNumber(value) { + return new Intl.NumberFormat('en-GB').format(Number(value || 0)) +} - if (pathname === '/') { - const homeTeams = readPublicDataSnapshot('home-teams.json') - const live = readPublicDataSnapshot('recent-games.json') - if (homeTeams) boot.homeTeams = homeTeams - if (live) boot.live = live - } else if (pathname === '/teams') { - const leaderboard = readPublicDataSnapshot('leaderboard-teams.json') - if (leaderboard) boot.leaderboard = leaderboard - } else if (pathname === '/players') { - const playerLeaderboard = readPublicDataSnapshot('leaderboard-players.json') - if (playerLeaderboard) boot.playerLeaderboard = playerLeaderboard - } else if (pathname === '/battle-logs' || pathname === '/live') { - const live = readPublicDataSnapshot('recent-games.json') - const leaderboard = readPublicDataSnapshot('leaderboard-teams.json') - if (live) boot.live = live - if (leaderboard) boot.leaderboard = leaderboard - } +function fallbackDate(timestamp) { + if (!timestamp) return 'Unknown time' + return new Intl.DateTimeFormat('en-GB', { dateStyle: 'medium', timeStyle: 'short' }).format(new Date(Number(timestamp) * 1000)) +} - return Object.keys(boot).length ? boot : null +function fallbackShell(title, meta, body) { + return ` +
+
+
+

${escapeHtml(title)}

+

${escapeHtml(meta)}

+
+ ${body} +
+
+ ` +} + +function playersFallbackHtml() { + const players = readPublicDataSnapshot('leaderboard-players.json')?.players || [] + if (!players.length) return '' + + const rows = players.slice(0, 100).map((player, index) => ` + +

#${index + 1}

+
+

${escapeHtml(player.nick || player.uid)}

+

${escapeHtml(player.uid)} · last seen ${escapeHtml(fallbackDate(player.last_seen))}

+
+

${fallbackNumber(player.score)}

+

${fallbackNumber(player.total_battles)}

+

${fallbackNumber(player.total_kills)}

+

${fallbackNumber(player.assists)}

+

${Number(player.win_rate || 0).toFixed(1)}%

+

${Number(player.kdr || 0).toFixed(2)}

+

${fallbackNumber(player.teams_seen)}

+
+ `).join('') + + return fallbackShell( + 'Player Leaderboard', + `${players.length} players returned`, + `
+
+
+
+

Rank

Player

Score

Battles

Kills

Assists

WR

KDR

Teams

+
+ ${rows} +
+
+
`, + ) +} + +function teamsFallbackHtml() { + const teams = readPublicDataSnapshot('leaderboard-teams.json')?.teams || [] + if (!teams.length) return '' + + const rows = teams.slice(0, 100).map((team, index) => { + const name = team.name || '' + return ` + + #${index + 1} + ${escapeHtml(name)} + ${fallbackNumber(team.player_count)} players + ${fallbackNumber(team.total_battles)} battles + ${Number(team.win_rate || 0).toFixed(1)}% WR + ${fallbackNumber(team.points?.total_points || team.total_kills)} + + ` + }).join('') + + return fallbackShell( + 'Team Leaderboard', + `${teams.length} teams returned`, + `
${rows}
`, + ) +} + +function battleLogsFallbackHtml() { + const matches = readPublicDataSnapshot('recent-games.json')?.matches || [] + if (!matches.length) return '' + + const rows = matches.slice(0, 50).map((match) => { + const winner = match.winning_team || match.team_name || '' + const loser = match.losing_team || '' + return ` + +
+

${escapeHtml(match.map_name || 'Unknown map')}

+

${escapeHtml(fallbackDate(match.timestamp))} · ${escapeHtml(match.session_id)}

+
+
+ ${escapeHtml(winner)} + vs + ${escapeHtml(loser)} +
+

${fallbackNumber(match.player_count)}v${fallbackNumber(match.player_count)}

+
+ ` + }).join('') + + return fallbackShell( + 'Battle Logs', + `${matches.length} battles returned`, + `
${rows}
`, + ) +} + +function homeFallbackHtml() { + const teams = readPublicDataSnapshot('home-teams.json')?.teams || [] + const matches = readPublicDataSnapshot('recent-games.json')?.matches || [] + + return ` +
+
+
+

BorisBot got nothin on THIS

+

Toothless' TSS Bot

+

Powered by Spectra. TSS analytics.

+ +
+
+

Cached now

+
+

${fallbackNumber(teams.length)} featured teams · ${fallbackNumber(matches.length)} recent games

+ ${teams.slice(0, 4).map((team) => `${escapeHtml(team.name || '')}`).join('')} +
+
+
+
+ ` +} + +function routeFallbackHtml(pathname) { + if (pathname === '/players') return playersFallbackHtml() + if (pathname === '/teams') return teamsFallbackHtml() + if (pathname === '/battle-logs' || pathname === '/live') return battleLogsFallbackHtml() + if (pathname === '/') return homeFallbackHtml() + return '' } function htmlWithSeo(req, data) { @@ -2330,7 +2458,14 @@ function htmlWithSeo(req, data) { const seo = routeSeo(pathname) const canonicalUrl = `${origin}${seo.path}` + const fallback = routeFallbackHtml(pathname) + + const rootHtml = fallback + ? `
${fallback}
` + : '
' + return data.toString('utf8') + .replace('
', rootHtml) .replace(/\s+integrity=(["'])sha(?:256|384|512)-[^"']+\1/g, '') .replaceAll('__PUBLIC_ORIGIN__', origin) .replaceAll('__SEO_TITLE__', escapeHtml(seo.title)) @@ -2338,7 +2473,6 @@ function htmlWithSeo(req, data) { .replaceAll('__SEO_ROBOTS__', escapeHtml(seo.robots)) .replaceAll('__SEO_CANONICAL__', escapeHtml(canonicalUrl)) .replaceAll('__SEO_JSON_LD__', routeStructuredData(origin, seo, canonicalUrl).replace(/