diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 1cc3f49..d15faa1 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -66,6 +66,7 @@ const siteGateEnabled = siteGateSetting == null const staticDataBase = (import.meta.env.VITE_STATIC_DATA_BASE || '/data').replace(/\/+$/, '') const staticDataEnabled = String(import.meta.env.VITE_STATIC_DATA || 'false').toLowerCase() === 'true' const missingStaticDataPaths = new Set() +const turnstileRequiredEvent = 'tssbot:turnstile-required' const BLOCKED_PLAYER_UIDS = new Set(['165569402', '86157459', '33536334', '41808996', '3651161']) const BLOCKED_TEAM_NAMES = new Set(['TPC']) @@ -135,6 +136,15 @@ function dataSource(apiPath, staticPath = null) { return { apiPath, staticPath } } +function turnstileSessionRequiredMessage(error) { + return error === 'Turnstile session required' || error === 'Site session required' +} + +function promptTurnstileSession(detail) { + if (typeof window === 'undefined' || !turnstileSiteKey) return + window.dispatchEvent(new CustomEvent(turnstileRequiredEvent, { detail })) +} + const publicDataSources = { teams: dataSource(apiEndpoints.teams, staticDataPath('leaderboard-teams.json')), players: dataSource(apiEndpoints.players, staticDataPath('leaderboard-players.json')), @@ -161,6 +171,10 @@ async function fetchJson(path, signal) { const error = new Error(body?.error || `Request failed with ${response.status}`) error.status = response.status error.path = path + error.detail = body?.detail + if (response.status === 403 && turnstileSessionRequiredMessage(body?.error)) { + promptTurnstileSession({ path, error: body?.error, detail: body?.detail }) + } throw error } @@ -1307,10 +1321,10 @@ function SiteGate({ onVerified }) { >
- This quick check protects the site from automated abuse. It usually clears itself. + Complete this Turnstile check to continue using protected site data.