From b575a8d7a206c975c766b4f7aa7cd60b68c4b051 Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Fri, 31 Oct 2025 11:16:22 +0900 Subject: [PATCH] fix: prevent unwanted login redirects during WebSocket reconnection (#6410) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## πŸ› Problem Users were experiencing the following issues during WebSocket reconnection: 1. Automatic redirect to login page after "Reconnecting" toast message appears 2. Automatic re-login after a few seconds, returning to the main interface 3. This cycle repeats, severely degrading user experience ## πŸ” Root Cause Analysis ### 1. Router Guard Catching Too Many Errors ```typescript // Problematic code try { const { getUserCloudStatus, getSurveyCompletedStatus } = await import('@/api/auth') const userStatus = await getUserCloudStatus() // ... } catch (error) { // All types of errors are caught here return next({ name: 'cloud-user-check' }) } ``` With dynamic import inside the try block, the following were all being caught: - Errors during `@/api/auth` module loading - Runtime errors from the API singleton - Actual API call errors Everything was caught and redirected to `cloud-user-check`. ### 2. Full Page Reload in UserCheckView ```typescript // Problematic code window.location.href = '/' // Full page reload! ``` This caused: - Loss of SPA benefits - Firebase Auth re-initialization β†’ temporarily null user - Router guard re-execution β†’ potential for another redirect ## βœ… Solution ### 1. router.ts: Move dynamic import outside try block ```typescript // After fix const { getUserCloudStatus, getSurveyCompletedStatus } = await import('@/api/auth') try { // Only API calls inside try const userStatus = await getUserCloudStatus() // ... } catch (error) { // Now only catches pure API call errors return next({ name: 'cloud-user-check' }) } ``` ### 2. UserCheckView.vue: Use SPA routing ```typescript // After fix await router.replace('/') // Use Vue Router instead of window.location.href ``` πŸ€– Generated with [Claude Code](https://claude.ai/code) ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6410-fix-prevent-unwanted-login-redirects-during-WebSocket-reconnection-29c6d73d3650818a8a1acbdcebd2f703) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Co-authored-by: GitHub Action --- .../subscription/composables/useSubscription.ts | 2 +- src/platform/onboarding/cloud/UserCheckView.vue | 2 +- src/router.ts | 9 ++++----- src/scripts/api.ts | 9 +++++++-- src/stores/firebaseAuthStore.ts | 12 ++++++++---- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/platform/cloud/subscription/composables/useSubscription.ts b/src/platform/cloud/subscription/composables/useSubscription.ts index 92588907f..b06278aa2 100644 --- a/src/platform/cloud/subscription/composables/useSubscription.ts +++ b/src/platform/cloud/subscription/composables/useSubscription.ts @@ -1,5 +1,5 @@ -import { computed, ref, watch } from 'vue' import { createSharedComposable } from '@vueuse/core' +import { computed, ref, watch } from 'vue' import { useCurrentUser } from '@/composables/auth/useCurrentUser' import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions' diff --git a/src/platform/onboarding/cloud/UserCheckView.vue b/src/platform/onboarding/cloud/UserCheckView.vue index 6cc1d7832..ed14d2d27 100644 --- a/src/platform/onboarding/cloud/UserCheckView.vue +++ b/src/platform/onboarding/cloud/UserCheckView.vue @@ -74,7 +74,7 @@ const { } // User is fully onboarded - window.location.href = '/' + await router.replace('/') }), null, { resetOnExecute: false } diff --git a/src/router.ts b/src/router.ts index 41c63a7fd..2c28d1fda 100644 --- a/src/router.ts +++ b/src/router.ts @@ -183,12 +183,11 @@ router.beforeEach(async (to, _from, next) => { // User is logged in - check if they need onboarding // For root path, check actual user status to handle waitlisted users if (!isElectron() && isLoggedIn && to.path === '/') { + // Import auth functions dynamically to avoid circular dependency + const { getUserCloudStatus, getSurveyCompletedStatus } = await import( + '@/api/auth' + ) try { - // Import auth functions dynamically to avoid circular dependency - const { getUserCloudStatus, getSurveyCompletedStatus } = await import( - '@/api/auth' - ) - // Check user's actual status const userStatus = await getUserCloudStatus() const surveyCompleted = await getSurveyCompletedStatus() diff --git a/src/scripts/api.ts b/src/scripts/api.ts index 68acb0e30..628649aa7 100644 --- a/src/scripts/api.ts +++ b/src/scripts/api.ts @@ -433,8 +433,12 @@ export class ComfyApi extends EventTarget { await this.#waitForAuthInitialization() // Add Firebase JWT token if user is logged in + // Force refresh token on reconnection to avoid 401 errors + const isReconnecting = + options.headers && 'X-Reconnecting' in options.headers try { - const authHeader = await useFirebaseAuthStore().getAuthHeader() + const authHeader = + await useFirebaseAuthStore().getAuthHeader(isReconnecting) if (authHeader) { if (Array.isArray(options.headers)) { for (const [key, value] of Object.entries(authHeader)) { @@ -542,9 +546,10 @@ export class ComfyApi extends EventTarget { let existingSession = window.name // Get auth token if available + // Force refresh on reconnect to avoid stale tokens let authToken: string | undefined try { - authToken = await useFirebaseAuthStore().getIdToken() + authToken = await useFirebaseAuthStore().getIdToken(isReconnect) } catch (error) { // Continue without auth token if there's an error console.warn('Could not get auth token for WebSocket connection:', error) diff --git a/src/stores/firebaseAuthStore.ts b/src/stores/firebaseAuthStore.ts index 599ef80cb..4cdf1eff4 100644 --- a/src/stores/firebaseAuthStore.ts +++ b/src/stores/firebaseAuthStore.ts @@ -106,10 +106,12 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { } }) - const getIdToken = async (): Promise => { + const getIdToken = async ( + forceRefresh = false + ): Promise => { if (!currentUser.value) return try { - return await currentUser.value.getIdToken() + return await currentUser.value.getIdToken(forceRefresh) } catch (error: unknown) { if ( error instanceof FirebaseError && @@ -140,9 +142,11 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => { * - An ApiKeyAuthHeader with X-API-KEY if API key exists * - null if neither authentication method is available */ - const getAuthHeader = async (): Promise => { + const getAuthHeader = async ( + forceRefresh = false + ): Promise => { // If available, set header with JWT used to identify the user to Firebase service - const token = await getIdToken() + const token = await getIdToken(forceRefresh) if (token) { return { Authorization: `Bearer ${token}`