fix(auth): close two regressions from the resume refactor
Critical: play-page bootstrap infinite loop when AUTH_ENABLED and no resume snapshot. The refactor changed the gate from `if (AUTH_ENABLED && hasSnapshot)` to `if (AUTH_ENABLED)`, so any snapshot-less /play entry (the common case — normal card/preset/custom start) entered the async branch, got null from consumeResumeSnapshot, bumped retryBootstrap, and re-ran the effect forever. Restored the peek-before-await: only enter the async resume branch when a snapshot actually exists; otherwise fall straight through to normal bootstrap. Verified via control-flow simulation across all three paths (no snapshot / snapshot + signed in / snapshot + not signed in). Major: homepage auto-started a game after a bare OAuth login. Routing persistPendingStart through AuthModal.onBeforeOAuth fired it for every OAuth redirect, including bare logins via UserChip / StyleModal onRequireAuth (where pendingAction is null and the user only wanted to sign in). Guarded the snapshot on `pendingAction === "start"` so only the mid-start flow persists; bare logins no longer resurrect the form and auto-start on return.
This commit is contained in:
+10
-4
@@ -1992,10 +1992,16 @@ export default function HomePage() {
|
||||
start();
|
||||
}
|
||||
}}
|
||||
// Snapshot the form the instant the OAuth redirect begins — the
|
||||
// round-trip unmounts the page and discards in-memory state. Fires
|
||||
// only for Google/GitHub (signInWithOAuth), not OTP.
|
||||
onBeforeOAuth={persistPendingStart}
|
||||
//
|
||||
// Only snapshot when the user is mid-start: the OAuth redirect also
|
||||
// fires for bare logins (UserChip / StyleModal onRequireAuth), where
|
||||
// the user just wants to sign in — not kick off a game. Guarding on
|
||||
// pendingAction keeps bare logins from auto-starting a session on
|
||||
// return. (start() sets pendingAction="start" right before opening
|
||||
// this modal.)
|
||||
onBeforeOAuth={() => {
|
||||
if (pendingAction === "start") persistPendingStart();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
+13
-5
@@ -1478,16 +1478,24 @@ function PlayInner() {
|
||||
// re-bootstrapping from `?card=…` (which would restart the story). OTP
|
||||
// login never writes a snapshot — its onSuccess retry keeps state
|
||||
// in-memory.
|
||||
if (AUTH_ENABLED) {
|
||||
// Let the async resume run; on failure (no snapshot / not signed in /
|
||||
// corrupt) it relinquishes the slot so the normal bootstrap below
|
||||
// re-runs. Either way return here — the sync body must not run while the
|
||||
// OAuth return is being reconciled.
|
||||
//
|
||||
// Peek before awaiting: when there's no snapshot (the common case —
|
||||
// normal card/preset/custom entry), fall straight through to the
|
||||
// bootstrap below. Only when a snapshot exists do we enter the async
|
||||
// gate, which itself removes the entry. This keeps the no-snapshot path
|
||||
// off the retryBootstrap re-trigger loop entirely.
|
||||
if (
|
||||
AUTH_ENABLED &&
|
||||
sessionStorage.getItem(PLAY_RESUME_KEY) !== null
|
||||
) {
|
||||
void (async () => {
|
||||
const snap = await consumeResumeSnapshot<PlayResumeSnapshot>(
|
||||
PLAY_RESUME_KEY,
|
||||
);
|
||||
if (!snap) {
|
||||
// Snapshot existed but user isn't signed in / payload corrupt →
|
||||
// consumeResumeSnapshot already removed it. Relinquish the slot so
|
||||
// the normal bootstrap below re-runs on the next effect cycle.
|
||||
startedRef.current = false;
|
||||
setRetryBootstrap((n) => n + 1);
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user