Skip to content

fix(react-ui): fix OAuth prefetch race when dialog opens before auth init#1863

Open
jmderby wants to merge 2 commits into
mainfrom
devin/1779819661-fix-oauth-prefetch-race
Open

fix(react-ui): fix OAuth prefetch race when dialog opens before auth init#1863
jmderby wants to merge 2 commits into
mainfrom
devin/1779819661-fix-oauth-prefetch-race

Conversation

@jmderby
Copy link
Copy Markdown
Contributor

@jmderby jmderby commented May 26, 2026

Description

Fixes a race condition where OAuth URL prefetch never runs if the user opens the auth dialog before auth initialization completes.

Root cause: The prefetch was gated by getAuthStatus() === "logged-out", but when the dialog opens during initialization, the status transitions from "initializing""in-progress" (skipping "logged-out" entirely). This left oauthUrlMap empty ({google: '', twitter: ''}), and the on-demand fallback to crossmintAuth?.getOAuthUrl() could fail if crossmintAuth was not yet available in the closure.

Changes:

  • CrossmintAuthProvider.tsx: Gate prefetch on baseAuth.jwt == null instead of getAuthStatus() === "logged-out" — this ensures the prefetch runs whenever the user is not logged in, regardless of dialog or init state.
  • OAuthFlowProvider.tsx: Guard prefetch with crossmintAuth == null early return to avoid running it before the auth client is initialized. Initialize isLoadingOauthUrlMap to false since no prefetch has started yet.
  • useOAuthWindowListener.ts: Strengthen URL validation from resolvedUrl == null to !resolvedUrl to also catch empty strings, preventing new URL("") from throwing.

Test plan

  • Verified lint passes (pnpm lint)
  • The fix addresses the specific scenario where a user opens the login dialog quickly after page load (before auth init completes), then clicks an OAuth button — the prefetch now runs correctly once crossmintAuth becomes available
  • The fallback getOAuthUrl call in useOAuthWindowListener is still present as a safety net if the prefetch hasn't completed by the time the user clicks

Package updates

  • @crossmint/client-sdk-react-ui: patch (changeset added via .changeset/fix-oauth-prefetch-race.md)

Link to Devin session: https://crossmint.devinenterprise.com/sessions/c4d91faa048d4a0b8a834127afcff9b4
Requested by: @jmderby

devin-ai-integration Bot and others added 2 commits May 26, 2026 18:22
…init

Co-Authored-By: Jonathan Derbyshire <jonathan.temp@paella.dev>
Co-Authored-By: Jonathan Derbyshire <jonathan.temp@paella.dev>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

Original prompt from Jonathan

There's currently a bug with our crossmint auth specifically in react. please review our crossmint-sdk repo in crossmint/client-sdk-react-ui and identify an issue surrounding the auth button just loading when clicked and never a popup appearing. happens inconsistently and randomly.

@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 26, 2026

🦋 Changeset detected

Latest commit: bb64416

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@crossmint/client-sdk-react-ui Patch
@crossmint/auth-ssr-nextjs-demo Patch
@crossmint/client-sdk-nextjs-starter Patch
@crossmint/wallets-quickstart-devkit Patch
@crossmint/wallets-playground-react Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 26, 2026

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/client/ui/react-ui/src/providers/auth/OAuthFlowProvider.tsx:41
**Brief window where OAuth click silently errors during init**

Changing the initial value from `true` to `false` is correct semantically, but it means the OAuth buttons are clickable during the gap between the dialog opening and `crossmintAuth` becoming available. In that gap `preFetchAndSetOauthUrl` returns early (line 50–52) and `oauthUrlMap` is still empty, so `createPopupAndSetupListeners` falls through to `crossmintAuth?.getOAuthUrl(provider)` which returns `undefined`, triggering the "Failed to resolve OAuth URL" error to the user.

The original `true` incidentally blocked this window (though it caused a different bug when the prefetch never ran). A small guard — e.g. setting `isLoadingOauthUrlMap` to `true` before returning early when `crossmintAuth == null` and resetting to `false` — would prevent the premature-click path, or the OAuth buttons themselves could be disabled while `crossmintAuth` is not yet available.

Reviews (1): Last reviewed commit: "chore: add changeset for OAuth prefetch ..." | Re-trigger Greptile


const [oauthUrlMap, setOauthUrlMap] = useState<OAuthUrlMap>(initialOAuthUrlMap);
const [isLoadingOauthUrlMap, setIsLoadingOauthUrlMap] = useState(true);
const [isLoadingOauthUrlMap, setIsLoadingOauthUrlMap] = useState(false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Brief window where OAuth click silently errors during init

Changing the initial value from true to false is correct semantically, but it means the OAuth buttons are clickable during the gap between the dialog opening and crossmintAuth becoming available. In that gap preFetchAndSetOauthUrl returns early (line 50–52) and oauthUrlMap is still empty, so createPopupAndSetupListeners falls through to crossmintAuth?.getOAuthUrl(provider) which returns undefined, triggering the "Failed to resolve OAuth URL" error to the user.

The original true incidentally blocked this window (though it caused a different bug when the prefetch never ran). A small guard — e.g. setting isLoadingOauthUrlMap to true before returning early when crossmintAuth == null and resetting to false — would prevent the premature-click path, or the OAuth buttons themselves could be disabled while crossmintAuth is not yet available.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/client/ui/react-ui/src/providers/auth/OAuthFlowProvider.tsx
Line: 41

Comment:
**Brief window where OAuth click silently errors during init**

Changing the initial value from `true` to `false` is correct semantically, but it means the OAuth buttons are clickable during the gap between the dialog opening and `crossmintAuth` becoming available. In that gap `preFetchAndSetOauthUrl` returns early (line 50–52) and `oauthUrlMap` is still empty, so `createPopupAndSetupListeners` falls through to `crossmintAuth?.getOAuthUrl(provider)` which returns `undefined`, triggering the "Failed to resolve OAuth URL" error to the user.

The original `true` incidentally blocked this window (though it caused a different bug when the prefetch never ran). A small guard — e.g. setting `isLoadingOauthUrlMap` to `true` before returning early when `crossmintAuth == null` and resetting to `false` — would prevent the premature-click path, or the OAuth buttons themselves could be disabled while `crossmintAuth` is not yet available.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice try slithering in here with that take 🐍, but this one's a miss!

No consumer actually reads isLoading from the OAuthFlowContext. All OAuth buttons (GoogleSignIn, TwitterSignIn, ContinueWithGoogle) only destructure startOAuthLogin and activeOAuthProvider — they compute their own loading state via activeOAuthProvider === "google" / "twitter". So whether isLoadingOauthUrlMap is true or false has zero effect on button clickability.

The old useState(true) wasn't "incidentally blocking" anything — it was just leaving a stale true value that nobody reads, while silently preventing the loading state from ever resolving when the prefetch was skipped.

The cold-blooded scenario you describe (click during init → error flash) is the exact same behavior as main today via the existing try-catch from 1398b653. Not a regression from this PR. 🦎

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I concede. Both GoogleSignIn and TwitterSignIn only destructure startOAuthLogin and activeOAuthProvider from useOAuthFlowisLoading (which reflects isLoadingOauthUrlMap) is never read by any consumer. Button clickability is independently derived from activeOAuthProvider === "google/twitter", not from this state. The useState(true)useState(false) change is purely a semantic correctness fix with no behavioral difference on button interaction.

The "error during early click" scenario is also pre-existing behavior surfaced through the try/catch, not introduced by this PR. Comment withdrawn.

Tip: You can give me more context with .greptile/rules.md so I get this right next time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant