@@ -86,8 +86,8 @@ export class AuthService extends TypedEventEmitter<AuthServiceEvents> {
8686 private session : InMemorySession | null = null ;
8787 private initializePromise : Promise < void > | null = null ;
8888 private refreshPromise : Promise < InMemorySession > | null = null ;
89- // Serializes session-state commits so overlapping selections can't interleave
90- // across async encryption (see commitSessionState).
89+ // Serializes session-state commits so overlapping selections can't
90+ // interleave across async encryption (see commitSessionState).
9191 private commitChain : Promise < void > = Promise . resolve ( ) ;
9292 constructor (
9393 @inject ( AUTH_PREFERENCE_STORE )
@@ -337,15 +337,10 @@ export class AuthService extends TypedEventEmitter<AuthServiceEvents> {
337337 currentProjectId : number | null ;
338338 } ,
339339 ) : Promise < void > {
340- // Serialize commits onto a chain. Encryption is async, so two overlapping
341- // selections would otherwise interleave across the await — an earlier one
342- // completing last would clobber the newer stored session and published
343- // state (and persistSession's own read of the prior selection would race).
344- // Chaining runs each commit's persist-then-publish to completion before the
345- // next starts, so the latest selection wins consistently across the
346- // in-memory session, storage, and subscribers. The stored chain swallows
347- // rejections so one failed commit doesn't wedge later ones; the returned
348- // promise still rejects for the caller.
340+ // Serialize commits onto a chain so overlapping selections can't
341+ // interleave across async encryption and clobber a newer one. The chain
342+ // swallows rejections so one failure doesn't wedge later commits; the
343+ // returned promise still rejects for the caller.
349344 const run = this . commitChain . then ( ( ) =>
350345 this . applyCommittedSession ( prevSession , next ) ,
351346 ) ;
@@ -368,12 +363,10 @@ export class AuthService extends TypedEventEmitter<AuthServiceEvents> {
368363 orgProjectsIncomplete : false ,
369364 } ;
370365
371- // Persist the durable session first — it's the only step here that can
372- // fail (encryption is async and may reject). Mutating this.session, the
373- // project preference, or the published state before it would strand the
374- // service on a project change that the stored session and UI never
375- // committed. Commit those only after the persist resolves, so a rejection
376- // leaves every layer on the prior session.
366+ // Persist the durable session first — the only step that can fail (async
367+ // encryption may reject). Mutate this.session, the preference, and
368+ // published state only after it resolves, so a rejection leaves every
369+ // layer on the prior session.
377370 await this . persistSession ( {
378371 refreshToken : nextSession . refreshToken ,
379372 cloudRegion : nextSession . cloudRegion ,
@@ -533,13 +526,10 @@ export class AuthService extends TypedEventEmitter<AuthServiceEvents> {
533526 return this . refreshPromise ;
534527 }
535528
536- // Assign refreshPromise synchronously — with no await between the guard
537- // above and this assignment — so concurrent callers dedupe onto one
538- // refresh. Resolving the stored session (which now awaits decryption)
539- // must therefore happen INSIDE refreshAndSync, not before it; otherwise
540- // two callers both pass the null guard, both decrypt, and both fire a
541- // refresh, burning the rotating refresh token twice and logging the user
542- // out when the second request fails.
529+ // Assign refreshPromise synchronously — no await before this — so
530+ // concurrent callers dedupe onto one refresh. Resolving the stored session
531+ // (now async) must happen INSIDE refreshAndSync, else two callers both
532+ // refresh and burn the rotating token twice.
543533 const refreshAndSync = async ( ) : Promise < InMemorySession > => {
544534 const sessionInput = await this . getSessionInputForRefresh ( ) ;
545535 let session : InMemorySession ;
@@ -1118,9 +1108,9 @@ export class AuthService extends TypedEventEmitter<AuthServiceEvents> {
11181108 if ( ! stored ) return ;
11191109 if ( stored . scopeVersion < OAUTH_SCOPE_VERSION ) return ;
11201110
1121- // Claim the recovery slot synchronously so concurrent triggers (network
1122- // regained + tab resume) don't both kick a token refresh; decryptability
1123- // is now async (Web Crypto), so it's validated inside recoverSession.
1111+ // Claim the recovery slot synchronously so concurrent triggers don't both
1112+ // kick a token refresh; decryptability is now async (Web Crypto), so it's
1113+ // validated inside recoverSession.
11241114 this . recoveryPromise = this . recoverSession ( )
11251115 . catch ( ( error ) => {
11261116 this . logger . warn ( "Session recovery failed" , { error } ) ;
0 commit comments