mirror of
https://github.com/AmanTahiliani/box-box.git
synced 2026-08-07 11:54:59 -04:00
fix(#98): rearm capped race hub refresh
This commit is contained in:
@@ -145,11 +145,13 @@ export function formatSessionScheduleTime(value: string): string {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const MAX_BROWSER_TIMEOUT = 2_147_483_647
|
||||||
|
|
||||||
export function refreshDeadlineDelay(refreshAt: string | undefined, now = Date.now()): number | null {
|
export function refreshDeadlineDelay(refreshAt: string | undefined, now = Date.now()): number | null {
|
||||||
if (!refreshAt) return null
|
if (!refreshAt) return null
|
||||||
const deadline = Date.parse(refreshAt)
|
const deadline = Date.parse(refreshAt)
|
||||||
if (Number.isNaN(deadline)) return null
|
if (Number.isNaN(deadline)) return null
|
||||||
return Math.min(Math.max(0, deadline - now), 2_147_483_647)
|
return Math.min(Math.max(0, deadline - now), MAX_BROWSER_TIMEOUT)
|
||||||
}
|
}
|
||||||
|
|
||||||
export type FocusMeetingKind = 'current' | 'next' | 'recent' | 'fallback'
|
export type FocusMeetingKind = 'current' | 'next' | 'recent' | 'fallback'
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { SourceBadge } from '../components/SourceBadge'
|
|||||||
import { countryAccent, countryDecal, formatGpDateRange } from '../lib/gpIdentity'
|
import { countryAccent, countryDecal, formatGpDateRange } from '../lib/gpIdentity'
|
||||||
import { formatCoverageHint, sessionTypeAbbrev } from '../lib/coverage'
|
import { formatCoverageHint, sessionTypeAbbrev } from '../lib/coverage'
|
||||||
import {
|
import {
|
||||||
|
MAX_BROWSER_TIMEOUT,
|
||||||
formatCountdown,
|
formatCountdown,
|
||||||
formatSessionScheduleTime,
|
formatSessionScheduleTime,
|
||||||
refreshDeadlineDelay,
|
refreshDeadlineDelay,
|
||||||
@@ -33,6 +34,7 @@ export function RaceHubPage({ sessionKey }: Props) {
|
|||||||
const [activeTab, setActiveTab] = useState<Tab>('overview')
|
const [activeTab, setActiveTab] = useState<Tab>('overview')
|
||||||
const [switcherOpen, setSwitcherOpen] = useState(false)
|
const [switcherOpen, setSwitcherOpen] = useState(false)
|
||||||
const [now, setNow] = useState(() => Date.now())
|
const [now, setNow] = useState(() => Date.now())
|
||||||
|
const [refreshGeneration, setRefreshGeneration] = useState(0)
|
||||||
|
|
||||||
// The server owns bare Race Hub selection so every open tab crosses the
|
// The server owns bare Race Hub selection so every open tab crosses the
|
||||||
// one-hour handoff at the same instant.
|
// one-hour handoff at the same instant.
|
||||||
@@ -57,9 +59,14 @@ export function RaceHubPage({ sessionKey }: Props) {
|
|||||||
if (sessionKey !== 0) return
|
if (sessionKey !== 0) return
|
||||||
const delay = refreshDeadlineDelay(context?.race_hub_refresh_at)
|
const delay = refreshDeadlineDelay(context?.race_hub_refresh_at)
|
||||||
if (delay == null) return
|
if (delay == null) return
|
||||||
const timer = window.setTimeout(() => { void refetchContext() }, delay)
|
const rearmAfterRefetch = delay === MAX_BROWSER_TIMEOUT
|
||||||
|
const timer = window.setTimeout(() => {
|
||||||
|
void refetchContext().finally(() => {
|
||||||
|
if (rearmAfterRefetch) setRefreshGeneration((generation) => generation + 1)
|
||||||
|
})
|
||||||
|
}, delay)
|
||||||
return () => window.clearTimeout(timer)
|
return () => window.clearTimeout(timer)
|
||||||
}, [sessionKey, context?.race_hub_refresh_at, refetchContext])
|
}, [sessionKey, context?.race_hub_refresh_at, refetchContext, refreshGeneration])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!preSession) return
|
if (!preSession) return
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
createRoute,
|
createRoute,
|
||||||
} from '@tanstack/react-router'
|
} from '@tanstack/react-router'
|
||||||
import { RaceHubPage } from '../pages/RaceHubPage'
|
import { RaceHubPage } from '../pages/RaceHubPage'
|
||||||
|
import { MAX_BROWSER_TIMEOUT } from '../lib/schedule'
|
||||||
import type { ContextAvailability, DatasetInfo, Meeting, RaceHub, Session, Weekend, WeekendContext } from '../types'
|
import type { ContextAvailability, DatasetInfo, Meeting, RaceHub, Session, Weekend, WeekendContext } from '../types'
|
||||||
|
|
||||||
vi.mock('../api', () => ({
|
vi.mock('../api', () => ({
|
||||||
@@ -375,6 +376,22 @@ describe('RaceHubPage', () => {
|
|||||||
expect(mockFetchRaceHub).not.toHaveBeenCalledWith(9473)
|
expect(mockFetchRaceHub).not.toHaveBeenCalledWith(9473)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('re-arms a bare route refresh after a capped browser timer', async () => {
|
||||||
|
vi.useFakeTimers({ shouldAdvanceTime: true })
|
||||||
|
mockFetchWeekendContext.mockResolvedValue({
|
||||||
|
...analysisContext,
|
||||||
|
race_hub_refresh_at: new Date(Date.now() + MAX_BROWSER_TIMEOUT + 1_000).toISOString(),
|
||||||
|
})
|
||||||
|
|
||||||
|
renderRaceHub(0)
|
||||||
|
await screen.findByTestId('race-hub')
|
||||||
|
await act(async () => { await vi.advanceTimersByTimeAsync(MAX_BROWSER_TIMEOUT) })
|
||||||
|
|
||||||
|
await waitFor(() => expect(mockFetchWeekendContext).toHaveBeenCalledTimes(2))
|
||||||
|
await act(async () => { await vi.advanceTimersByTimeAsync(1_000) })
|
||||||
|
await waitFor(() => expect(mockFetchWeekendContext).toHaveBeenCalledTimes(3))
|
||||||
|
})
|
||||||
|
|
||||||
it('keeps an explicit session URL stable across the canonical refresh boundary', async () => {
|
it('keeps an explicit session URL stable across the canonical refresh boundary', async () => {
|
||||||
vi.useFakeTimers()
|
vi.useFakeTimers()
|
||||||
renderRaceHub(9472)
|
renderRaceHub(9472)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
focusMeetingKind,
|
focusMeetingKind,
|
||||||
focusMeetingLabel,
|
focusMeetingLabel,
|
||||||
formatCountdown,
|
formatCountdown,
|
||||||
|
MAX_BROWSER_TIMEOUT,
|
||||||
nextUpcomingMeeting,
|
nextUpcomingMeeting,
|
||||||
pickFocusMeeting,
|
pickFocusMeeting,
|
||||||
refreshDeadlineDelay,
|
refreshDeadlineDelay,
|
||||||
@@ -84,4 +85,10 @@ describe('schedule helpers', () => {
|
|||||||
expect(refreshDeadlineDelay('2025-05-25T13:00:00Z', Date.parse('2025-05-25T12:59:30Z'))).toBe(30_000)
|
expect(refreshDeadlineDelay('2025-05-25T13:00:00Z', Date.parse('2025-05-25T12:59:30Z'))).toBe(30_000)
|
||||||
expect(refreshDeadlineDelay(undefined)).toBeNull()
|
expect(refreshDeadlineDelay(undefined)).toBeNull()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('caps a refresh deadline beyond the browser timer maximum', () => {
|
||||||
|
const now = Date.parse('2025-05-25T12:00:00Z')
|
||||||
|
const deadline = new Date(now + MAX_BROWSER_TIMEOUT + 1_000).toISOString()
|
||||||
|
expect(refreshDeadlineDelay(deadline, now)).toBe(MAX_BROWSER_TIMEOUT)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user