fix(clerk-js): Fix broken transitive state for org switch and signout#7823
fix(clerk-js): Fix broken transitive state for org switch and signout#7823
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 549a027 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 |
|
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:
📝 WalkthroughWalkthroughThe PR introduces an optional 🚥 Pre-merge checks | ✅ 4 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. No actionable comments were generated in the recent review. 🎉 Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Comment |
@clerk/agent-toolkit
@clerk/astro
@clerk/backend
@clerk/chrome-extension
@clerk/clerk-js
@clerk/dev-cli
@clerk/expo
@clerk/expo-passkeys
@clerk/express
@clerk/fastify
@clerk/hono
@clerk/localizations
@clerk/nextjs
@clerk/nuxt
@clerk/react
@clerk/react-router
@clerk/shared
@clerk/tanstack-react-start
@clerk/testing
@clerk/ui
@clerk/upgrade
@clerk/vue
commit: |
…-transitive-state-when-switching-orgs
| /* | ||
| These tests verify that useAuth emits the correct transitive state sequence when switching | ||
| auth context (org or user) with navigation. The expected pattern is: | ||
| Path A - Value A, Path A - undefined, Path B - undefined, Path B - Value B | ||
| */ |
brkalow
left a comment
There was a problem hiding this comment.
Looks good! This is a pragmatic way to go about it. Great to have tests for this going forward.
| } | ||
|
|
||
| protected static _updateClient<J>(responseJSON: FapiResponseJSON<J> | null): void { | ||
| protected static _getClientResourceFromPayload<J>( |
There was a problem hiding this comment.
nit: Consider moving this to Client, or making it a standalone helper.
| __internal_touch = async (): Promise<ClientResource | undefined> => { | ||
| const json = await BaseResource._fetch<SessionJSON>( | ||
| { | ||
| method: 'POST', | ||
| path: this.path('touch'), | ||
| body: { active_organization_id: this.lastActiveOrganizationId } as any, | ||
| }, | ||
| { skipUpdateClient: true }, | ||
| ); | ||
|
|
||
| // Update session in-place from response (same as _baseMutate) | ||
| this.fromJSON((json?.response || json) as SessionJSON); | ||
|
|
||
| return BaseResource._getClientResourceFromPayload(json); | ||
| }; |
There was a problem hiding this comment.
nit: Consider consolidating the fetch across touch() and __internal_touch
| // reload session from updated client | ||
| newSession = this.#getSessionFromClient(newSession?.id); | ||
| let updatedClient: ClientResource | undefined; | ||
| if (shouldNavigate && newSession && '__internal_touch' in newSession) { |
There was a problem hiding this comment.
[nit] Curious why the duck-type check and not just a newSession typeof Session here
There was a problem hiding this comment.
Oh right, I forgot to go back on this. Tests were failing because they mocked Session without the __internal_touch function and I just ran with it while testing things out. I don't like tests affecting real code though so meant to go back and implement that mock instead but never did. Will fix!
jacekradko
left a comment
There was a problem hiding this comment.
Nice work. I don't think there is anything blocking here, just some nits
Description
This PR fixes a bug that was introduced when we migrated to
useSyncExternalStore. The bug is that when switching orgs and navigating to a new page, the current page re-rendered with the new organization before the navigation, which can cause problems.Alternative solution: #7815
Compared to that solution, this touches more code, but is scoped to only block
updateClientfrom emitting for the specificsetActivecall that triggered it, it does not block allupdateClientcalls to keep the splash radius as small as possible.We want to refactor this more heavily to have better guarantees, but now is not the time.
I went with a version where a
__internal_touch()does not callupdateClientand instead returns aclientresource, expecting the caller to then callupdateClientwith the result.updateClientnow takes an option to skip emitting.If we want to refactor further in the future, this approach let's us move the
updateClientcall around, and also do things with the intermediaryclientif necessary.Another version would be to thread a
skipUiEmitoption all the way through the base resource calls, but in total that's 6 stacks deep and couples UI concerns to resources in a way I did not like.UPDATE
I found the same bug applied for signing out. I added two tests for this, single session and multi session.
The issue for signOut was that the session removal ran before setTransitiveState, which like the above bug triggered an
updateClientwhich emitted.Since this flow was simpler I opted for a simpler fix, to move the
setTransitiveStateto before the session removal. WhenupdateClientis called, theif (this.session)guard detects there is no current session and skips theupdateAccessorscall. It still emits, but with the transitive state. Later whenupdateAccessorsis called during thesignOut, this converts theundefinedtonulland we are now in a correctly signed out case.I have verified these fixes works in the dashboard.
Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change
Summary by CodeRabbit
New Features
Bug Fixes
Tests
Chores