-
Notifications
You must be signed in to change notification settings - Fork 410
fix(clerk-js): Ensure sessionId is set after sign in #7117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
f0d9553
7964a90
6397235
5112830
74575b8
d29ec53
a71bb0f
fc11bb5
8b3df51
89fc47e
d7b3918
51b2f6a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1412,7 +1412,7 @@ export class Clerk implements ClerkInterface { | |
| return; | ||
| } | ||
|
|
||
| if (newSession?.status !== 'pending') { | ||
| if (newSession?.status !== 'pending' && (this.session?.id !== newSession?.id || shouldSwitchOrganization)) { | ||
| this.#setTransitiveState(); | ||
| } | ||
|
|
||
|
|
@@ -2351,7 +2351,9 @@ export class Clerk implements ClerkInterface { | |
| } | ||
|
|
||
| updateClient = (newClient: ClientResource): void => { | ||
| if (!this.client) { | ||
| const isFirstClientSet = !this.client; | ||
|
|
||
| if (isFirstClientSet) { | ||
| // This is the first time client is being | ||
| // set, so we also need to set session | ||
| const session = this.#options.selectInitialSession | ||
|
|
@@ -2381,6 +2383,11 @@ export class Clerk implements ClerkInterface { | |
|
|
||
| // A client response contains its associated sessions, along with a fresh token, so we dispatch a token update event. | ||
| eventBus.emit(events.TokenUpdate, { token: this.session?.lastActiveToken }); | ||
| } else if (!isFirstClientSet && newClient.sessions?.length > 0) { | ||
| const session = this.#options.selectInitialSession | ||
| ? this.#options.selectInitialSession(newClient) | ||
| : this.#defaultSession(newClient); | ||
| this.#setAccessors(session); | ||
|
Comment on lines
2424
to
2431
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we know why this is new case is necessary? The client is being updated, which has a new session to be set, but the session accessor wasn't yet set elsewhere?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it is. This handles the case where updateClient() is executed during touch(). In that call we get the piggybacked client data. The key here is that this is an else branch of So without this else, we do nothing here and just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh! Had to dig through the code and think for a bit to get it, but:
Very nice find. Adding a comment might be helpful here.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be covered by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's what this PR adds. It might be easier to grok looking at it like this: updateClient = (newClient: ClientResource): void => {
const isFirstClientSet = !this.client;
if (isFirstClientSet) {
// If first time setting client, also set session
// Note: This does _not_ trigger during transitive state as we still have a client
}
this.client = newClient;
if (this.session) {
// Previously we only set a new session if we already had an active session,
// which we don't during transitive state
} else if (!isFirstClientSet && newClient.sessions?.length > 0) {
// Now, if client is set, but not session, and there is a new session to set, set it
}
this.#emit();
};
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Ephem that's a pretty good summary. This is my mental model of this mechanism: updateClient = (newClient: ClientResource): void => {
const isFirstClientSet = !this.client;
if (isFirstClientSet) {
// First time setting client (during initialization)
// Also set the initial session from the new client
}
this.client = newClient;
if (this.session) {
// We have an active session - refresh it with updated data from newClient
// This is the normal path during regular operation
} else if (!isFirstClientSet && newClient.sessions?.length > 0) {
// Race condition recovery path:
// - Client was already set (so NOT first initialization)
// - Session is currently falsy (e.g., cleared by setTransitiveState during setActive)
// - BUT newClient has session data (e.g., from touch() response that piggybacked)
// → Restore the session immediately to prevent null flash
}
this.#emit();
};There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking about this some more, I realize I still don't understand how the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I found the reason here: #7157 (comment) (Just crossposting here to keep a good history if someone stumbles across this in the future) |
||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| this.#emit(); | ||
|
|
@@ -2591,6 +2598,12 @@ export class Clerk implements ClerkInterface { | |
| }); | ||
|
|
||
| const initClient = async () => { | ||
| const jwtInCookie = this.#authService?.getSessionCookie(); | ||
| if (jwtInCookie) { | ||
| const preliminaryClient = createClientFromJwt(jwtInCookie); | ||
| this.updateClient(preliminaryClient); | ||
| } | ||
jacekradko marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| return Client.getOrCreateInstance() | ||
| .fetch() | ||
| .then(res => this.updateClient(res)) | ||
jacekradko marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what problem is this addressing? The transitive state sets
undefined, notnull. If we're not entirely sure, I would recommend that we remove this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is unclear to me as well. Could this cause a chain of events that result to
nulleventually ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check prevents calling #setTransitiveState() unnecessarily. It ensures we only clear the transitive state (which sets the session to undefined) when the session is changing or the org is changing.
Without it you get unnecessary session → undefined → session when nothing changed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this what triggered during the sign-in, causing session to be
undefinedwhen theupdateClientcall ran, causing it to not update the session?So this PR kind of fixes the sign-in bug from two separate angles?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ephem Exactly! Without this, every navigation with setActive() would clear the session unnecessarily, opening the window for a race condition where updateClient() would run before setActive() can set and emit the session