fix(react): wrap fetchAccessToken in new Promise to fix useConvexAuth on Hermes V1#368
Conversation
|
@ramonclaudio is attempting to deploy a commit to the Convex Team on Vercel. A member of the Team first needs to authorize it. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: get-convex/coderabbit/.coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughfetchAccessToken was refactored to return an explicit new Promise wrapper. It still short-circuits to cachedToken when present (unless forceRefreshToken), reuses pendingTokenRef for concurrent callers, calls authClient.convex.token({ fetchOptions: { throw: false } }) for new requests, updates/clears cachedToken on success/failure, and always clears pendingTokenRef in finally; resolution/rejection from in-flight requests is routed through the wrapper’s resolve/reject. Sequence Diagram(s)sequenceDiagram
participant Consumer
participant fetchAccessToken
participant Cache as cachedToken
participant Pending as pendingTokenRef
participant API as authClient.convex.token()
Consumer->>fetchAccessToken: request token
fetchAccessToken->>Cache: check cachedToken
alt cachedToken present
Cache-->>fetchAccessToken: return cached token
fetchAccessToken-->>Consumer: resolve(token)
else no cached token
fetchAccessAccessToken->>Pending: check pendingTokenRef
alt pending in flight
Pending-->>fetchAccessToken: existing promise
fetchAccessToken-->>Consumer: attach then(resolve,reject)
else fetch new
fetchAccessToken->>API: token({ throw: false })
API-->>fetchAccessToken: token or error
fetchAccessToken->>Cache: update or clear cachedToken
fetchAccessToken-->>Consumer: resolve/reject via outer callbacks
fetchAccessToken->>Pending: clear in finally
end
end
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed: one or more packages not found in the registry. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
b1550be to
53c46f0
Compare
|
Might this be fixed upstream in the Expo 56 preview? |
@dowski the expo team did patch it upstream, but only at the babel preset level (expo/expo#45601). That covers SDK 56 users on Filed facebook/react-native#56816 to port the same babel transforms into Leaving it up to @erquhart to decide what to do here! |
|
Thanks Ray - holdilng this until expo 56 is closer to stable, would hope this would be addressed upstream. |
… on Hermes V1 The /convex/token response triggers a session rotation (via Better Auth's Set-Cookie processing) plus a setCachedToken call inside the bridge's .then. The next render rebuilds fetchAccessToken's useCallback (keyed on [sessionId]) and fires ConvexAuthStateFirstEffect's client.setAuth a second time. On Hermes V1 native async (Expo SDK 56 canary 2026-05-05+ since expo/expo#45345 dropped @babel/plugin-transform-async-to-generator), that second setAuth lands inside the first setConfig's await window in authentication_manager.ts. fetchTokenAndGuardAgainstRace bumps configVersion on entry and the original await sees the stale value, returning isFromOutdatedConfig: true. setConfig bails without resumeSocket() and the chain repeats. Drop the async keyword and wrap the body in new Promise(executor) directly. The constructor's resolve(thenable) schedules a NewPromiseResolveThenableJob microtask, the same hop regenerator's _asyncToGenerator provides. With the hop in place the second setAuth lands after the first setConfig finishes rather than during its await window.
53c46f0 to
428390d
Compare
Ran into an issue where
useConvexAuth().isAuthenticatednever flips totrueafter sign-in on Expo SDK 56 canary (2026-05-05+) with React19.2.3and React Native0.85.3. Better Auth session lands,/convex/tokenreturns a JWT, but the bridge stays paused. Same on sign-up and sign-out.Tracked it to a Hermes V1 native runtime bug with async arrow functions that have non-simple parameters (
facebook/hermes#1761). The Hermes fix landed on mainline last September but isn't backported to RN's bundled Hermes. The bug only fires when the build pipeline preserves async (sends it to Hermes natively instead of transforming to generators), so it only hits some setups today:babel-preset-expo@<56.0.6(thehermes-v1config preserves async)0.85.xwithunstable_preserveAsync: trueopt-inWrapping the function body in an explicit
new Promise(executor)sidesteps the buggy shape regardless of what the build pipeline does.The Expo team patched it at the babel preset level (
expo/expo#45601), which papers it over for Expo SDK 56 preview/stable users onbabel-preset-expo@56.0.6+. I filedfacebook/react-native#56816to do the same for@react-native/babel-presetso bare RN withpreserveAsyncgets the same coverage, but that's on Meta's timeline. Until both framework fixes ship and propagate, this PR is the defensive workaround.Repro:
ramonclaudio/convex-better-auth-368-repro.Test Plan
npm run buildandnpm run typecheckcleannpm run testunchanged (no tests undersrc/react/)2026-05-06-03817f5via the linked repro: vanilla0.12.2stalls atuseConvexAuth.isAuthenticated: false, useSession.hasSession: trueacross sign in, sign up, and sign out. This branch's tarball flips it totrueon every transition.