diff --git a/src/Web/StellaOps.Web/src/app/core/auth/auth-session.store.ts b/src/Web/StellaOps.Web/src/app/core/auth/auth-session.store.ts index c35181ba7..239b1f4ec 100644 --- a/src/Web/StellaOps.Web/src/app/core/auth/auth-session.store.ts +++ b/src/Web/StellaOps.Web/src/app/core/auth/auth-session.store.ts @@ -144,12 +144,12 @@ export class AuthSessionStore { private readPersistedMetadata( restoredSession: AuthSession | null ): PersistedSessionMetadata | null { - if (typeof sessionStorage === 'undefined') { + if (typeof localStorage === 'undefined') { return null; } try { - const raw = sessionStorage.getItem(SESSION_STORAGE_KEY); + const raw = localStorage.getItem(SESSION_STORAGE_KEY); if (!raw) { if (!restoredSession) { return null; @@ -165,7 +165,7 @@ export class AuthSessionStore { typeof parsed.issuedAtEpochMs !== 'number' || typeof parsed.dpopKeyThumbprint !== 'string' ) { - sessionStorage.removeItem(SESSION_STORAGE_KEY); + localStorage.removeItem(SESSION_STORAGE_KEY); return restoredSession ? this.toMetadata(restoredSession) : null; } const tenantId = @@ -180,37 +180,48 @@ export class AuthSessionStore { tenantId, }; } catch { - sessionStorage.removeItem(SESSION_STORAGE_KEY); + localStorage.removeItem(SESSION_STORAGE_KEY); return restoredSession ? this.toMetadata(restoredSession) : null; } } private readPersistedSession(): AuthSession | null { - if (typeof sessionStorage === 'undefined') { + // Try sessionStorage first (same-tab, survives F5), then fall back + // to localStorage (cross-tab, survives new-tab / window.open). + const fromSession = this.readSessionFrom('session'); + if (fromSession) { + return fromSession; + } + return this.readSessionFrom('local'); + } + + private readSessionFrom(storage: 'session' | 'local'): AuthSession | null { + const store = storage === 'session' ? sessionStorage : localStorage; + if (typeof store === 'undefined') { return null; } try { - const raw = sessionStorage.getItem(FULL_SESSION_STORAGE_KEY); + const raw = store.getItem(FULL_SESSION_STORAGE_KEY); if (!raw) { return null; } const parsed = JSON.parse(raw) as AuthSession; if (!this.isValidSession(parsed)) { - sessionStorage.removeItem(FULL_SESSION_STORAGE_KEY); + store.removeItem(FULL_SESSION_STORAGE_KEY); return null; } if (parsed.tokens.expiresAtEpochMs <= Date.now()) { - sessionStorage.removeItem(FULL_SESSION_STORAGE_KEY); + store.removeItem(FULL_SESSION_STORAGE_KEY); this.restoredSessionExpired = true; return null; } return parsed; } catch { - sessionStorage.removeItem(FULL_SESSION_STORAGE_KEY); + store.removeItem(FULL_SESSION_STORAGE_KEY); return null; } } @@ -262,31 +273,28 @@ export class AuthSessionStore { } private persistMetadata(metadata: PersistedSessionMetadata): void { - if (typeof sessionStorage === 'undefined') { + if (typeof localStorage === 'undefined') { return; } - sessionStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(metadata)); + localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(metadata)); } private persistSession(session: AuthSession): void { - if (typeof sessionStorage === 'undefined') { - return; - } - sessionStorage.setItem(FULL_SESSION_STORAGE_KEY, JSON.stringify(session)); + const json = JSON.stringify(session); + try { sessionStorage.setItem(FULL_SESSION_STORAGE_KEY, json); } catch { /* SSR / quota */ } + try { localStorage.setItem(FULL_SESSION_STORAGE_KEY, json); } catch { /* SSR / quota */ } } private clearPersistedMetadata(): void { - if (typeof sessionStorage === 'undefined') { + if (typeof localStorage === 'undefined') { return; } - sessionStorage.removeItem(SESSION_STORAGE_KEY); + localStorage.removeItem(SESSION_STORAGE_KEY); } private clearPersistedSession(): void { - if (typeof sessionStorage === 'undefined') { - return; - } - sessionStorage.removeItem(FULL_SESSION_STORAGE_KEY); + try { sessionStorage.removeItem(FULL_SESSION_STORAGE_KEY); } catch { /* SSR */ } + try { localStorage.removeItem(FULL_SESSION_STORAGE_KEY); } catch { /* SSR */ } } getActiveTenantId(): string | null {