fix: countdown hydration mismatch; rate-limit sse-error session refreshes
- suppressHydrationWarning on countdown time text (server second differs from client second; first tick corrects) - sse reconnects attempt a session refresh at most once per minute so a flaky stream cannot hammer the refresh endpoint
This commit is contained in:
@@ -53,12 +53,14 @@ export function Countdown({ date, label, color }: CountdownProps) {
|
|||||||
return (
|
return (
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
{label && <Text size='sm' fw={500}>{label}:</Text>}
|
{label && <Text size='sm' fw={500}>{label}:</Text>}
|
||||||
|
{/* Server and client render different seconds; the first client tick corrects it. */}
|
||||||
<Text
|
<Text
|
||||||
size='sm'
|
size='sm'
|
||||||
fw={600}
|
fw={600}
|
||||||
c={isFinalStretch ? 'red' : color}
|
c={isFinalStretch ? 'red' : color}
|
||||||
ff="monospace"
|
ff="monospace"
|
||||||
className={classes.time}
|
className={classes.time}
|
||||||
|
suppressHydrationWarning
|
||||||
>
|
>
|
||||||
{prefix}
|
{prefix}
|
||||||
<span
|
<span
|
||||||
@@ -66,6 +68,7 @@ export function Countdown({ date, label, color }: CountdownProps) {
|
|||||||
className={`${classes.seconds} ${
|
className={`${classes.seconds} ${
|
||||||
isFinalStretch ? classes.finalTick : classes.tick
|
isFinalStretch ? classes.finalTick : classes.tick
|
||||||
}`}
|
}`}
|
||||||
|
suppressHydrationWarning
|
||||||
>
|
>
|
||||||
{pad(timeLeft.seconds)}
|
{pad(timeLeft.seconds)}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ const INVALIDATE_JITTER_MS = 1500;
|
|||||||
const invalidateTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
const invalidateTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
||||||
|
|
||||||
const WATCHDOG_MS = 45_000;
|
const WATCHDOG_MS = 45_000;
|
||||||
|
const REFRESH_ATTEMPT_COOLDOWN_MS = 60_000;
|
||||||
|
let lastRefreshAttemptAt = 0;
|
||||||
|
|
||||||
function debouncedInvalidate(
|
function debouncedInvalidate(
|
||||||
queryClient: ReturnType<typeof useQueryClient>,
|
queryClient: ReturnType<typeof useQueryClient>,
|
||||||
@@ -163,13 +165,18 @@ export function useServerEvents() {
|
|||||||
// an expired session rather than a network blip. Try to refresh; if the
|
// an expired session rather than a network blip. Try to refresh; if the
|
||||||
// session is genuinely gone, stop retrying so we don't hammer the
|
// session is genuinely gone, stop retrying so we don't hammer the
|
||||||
// endpoint forever — a real re-login (or a wake event) will reconnect.
|
// endpoint forever — a real re-login (or a wake event) will reconnect.
|
||||||
|
// Cooldown: a flaky stream (proxy idle timeouts) must not turn every
|
||||||
|
// reconnect into a refresh call.
|
||||||
let sessionValid = true;
|
let sessionValid = true;
|
||||||
|
if (Date.now() - lastRefreshAttemptAt > REFRESH_ATTEMPT_COOLDOWN_MS) {
|
||||||
|
lastRefreshAttemptAt = Date.now();
|
||||||
try {
|
try {
|
||||||
const { attemptRefreshingSession } = await import('supertokens-web-js/recipe/session');
|
const { attemptRefreshingSession } = await import('supertokens-web-js/recipe/session');
|
||||||
sessionValid = await attemptRefreshingSession();
|
sessionValid = await attemptRefreshingSession();
|
||||||
} catch {
|
} catch {
|
||||||
// Refresh call itself failed (likely offline) — treat as transient.
|
// Refresh call itself failed (likely offline) — treat as transient.
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (disposed) return;
|
if (disposed) return;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user