1// Monotonic ordering for game `progress` tokens, used to decide whether a 2// freshly-served bootstrap payload is AHEAD of the state the capture 3// controller already holds in memory. 4// 5// Why this exists: Turbo keeps the JS context alive across visits, so 6// `window.gameData` survives navigating away from the capture page and back. 7// The bootstrap used to return early whenever `window.gameData` was defined, 8// which meant the fresh server payload was ignored on re-entry and the page 9// could render (and then persist) a period that had already ended. See 10// docs â Trello Y8W2foZE. 11// 12// The comparison has to be DIRECTIONAL. Adopting the server payload whenever 13// it merely differs would be its own bug: if a coach taps "End Quarter" and 14// navigates away before the debounced persist lands, the server is briefly 15// BEHIND the client, and blindly adopting it would revert the quarter end â 16// exactly the symptom we're fixing. So we only ever move forward. 17// 18// Ranking is structural rather than a per-sport sequence table. A single game 19// only ever uses one regulation family (q⦠for AFL / American football / 20// netball, h⦠for football / rugby league / flag football), so those share a 21// base and never compete with each other. Extra periods always follow 22// regulation, whichever form they take (etâ¦, gpâ¦, ot, gg). 23 24const PREGAME = new Set(['pregame', 'pre-game']); 25 26// Base offsets keep the families ordered: regulation < extra < shootout < done. 27const REGULATION_BASE = 10; 28const EXTRA_BASE = 100; 29const SHOOTOUT_RANK = 200; 30const COMPLETED_RANK = 300; 31 32const REGULATION_PREFIXES = ['q', 'h']; 33// `ot` (gridiron overtime) and `gg` (netball golden goal) carry no number. 34const EXTRA_PREFIXES = ['et', 'gp', 'ot', 'gg']; 35 36// Two ranks per period so the "-ended" break always sorts immediately after 37// the live period it closes, and before the next period starts. 38function periodRank(base, periodNumber, ended) { 39 return base + periodNumber * 2 + (ended ? 1 : 0); 40} 41 42// Returns a comparable integer, or null for anything we don't recognise. 43// Null is the fail-safe: an unknown token must never be treated as "ahead". 44export function progressRank(progress) { 45 if (typeof progress !== 'string') return null; 46 47 const token = progress.trim().toLowerCase(); 48 if (token === '') return null; 49 50 if (PREGAME.has(token)) return 0; 51 if (token === 'completed') return COMPLETED_RANK; 52 if (token === 'penalty_shootout') return SHOOTOUT_RANK; 53 54 const match = token.match(/^([a-z]+)(\d*)(-ended)?$/); 55 if (!match) return null; 56 57 const [, prefix, digits, endedSuffix] = match; 58 const ended = Boolean(endedSuffix); 59 60 if (REGULATION_PREFIXES.includes(prefix)) { 61 // Regulation periods are always numbered ("q2", "h1") â a bare "q" is junk. 62 if (digits === '') return null; 63 return periodRank(REGULATION_BASE, Number(digits), ended); 64 } 65 66 if (EXTRA_PREFIXES.includes(prefix)) { 67 // "ot" / "gg" are single unnumbered periods; treat them as period 1 so they 68 // sort alongside "et1" / "gp1". 69 const periodNumber = digits === '' ? 1 : Number(digits); 70 return periodRank(EXTRA_BASE, periodNumber, ended); 71 } 72 73 return null; 74} 75 76// True only when the server payload represents a strictly later point in the 77// game than what the client holds. Unknown tokens on either side return false 78// so we leave the in-memory state alone rather than guess. 79export function serverStateIsAhead(clientProgress, serverProgress) { 80 const clientRank = progressRank(clientProgress); 81 const serverRank = progressRank(serverProgress); 82 83 if (clientRank === null || serverRank === null) return false; 84 85 return serverRank > clientRank; 86}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.