mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 18:52:19 +00:00
fix: prevent unwanted login redirects during WebSocket reconnection (#6410)
## 🐛 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 <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { computed, ref, watch } from 'vue'
|
|
||||||
import { createSharedComposable } from '@vueuse/core'
|
import { createSharedComposable } from '@vueuse/core'
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
import { useCurrentUser } from '@/composables/auth/useCurrentUser'
|
||||||
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
import { useFirebaseAuthActions } from '@/composables/auth/useFirebaseAuthActions'
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User is fully onboarded
|
// User is fully onboarded
|
||||||
window.location.href = '/'
|
await router.replace('/')
|
||||||
}),
|
}),
|
||||||
null,
|
null,
|
||||||
{ resetOnExecute: false }
|
{ resetOnExecute: false }
|
||||||
|
|||||||
@@ -183,12 +183,11 @@ router.beforeEach(async (to, _from, next) => {
|
|||||||
// User is logged in - check if they need onboarding
|
// User is logged in - check if they need onboarding
|
||||||
// For root path, check actual user status to handle waitlisted users
|
// For root path, check actual user status to handle waitlisted users
|
||||||
if (!isElectron() && isLoggedIn && to.path === '/') {
|
if (!isElectron() && isLoggedIn && to.path === '/') {
|
||||||
|
// Import auth functions dynamically to avoid circular dependency
|
||||||
|
const { getUserCloudStatus, getSurveyCompletedStatus } = await import(
|
||||||
|
'@/api/auth'
|
||||||
|
)
|
||||||
try {
|
try {
|
||||||
// Import auth functions dynamically to avoid circular dependency
|
|
||||||
const { getUserCloudStatus, getSurveyCompletedStatus } = await import(
|
|
||||||
'@/api/auth'
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check user's actual status
|
// Check user's actual status
|
||||||
const userStatus = await getUserCloudStatus()
|
const userStatus = await getUserCloudStatus()
|
||||||
const surveyCompleted = await getSurveyCompletedStatus()
|
const surveyCompleted = await getSurveyCompletedStatus()
|
||||||
|
|||||||
@@ -433,8 +433,12 @@ export class ComfyApi extends EventTarget {
|
|||||||
await this.#waitForAuthInitialization()
|
await this.#waitForAuthInitialization()
|
||||||
|
|
||||||
// Add Firebase JWT token if user is logged in
|
// 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 {
|
try {
|
||||||
const authHeader = await useFirebaseAuthStore().getAuthHeader()
|
const authHeader =
|
||||||
|
await useFirebaseAuthStore().getAuthHeader(isReconnecting)
|
||||||
if (authHeader) {
|
if (authHeader) {
|
||||||
if (Array.isArray(options.headers)) {
|
if (Array.isArray(options.headers)) {
|
||||||
for (const [key, value] of Object.entries(authHeader)) {
|
for (const [key, value] of Object.entries(authHeader)) {
|
||||||
@@ -542,9 +546,10 @@ export class ComfyApi extends EventTarget {
|
|||||||
let existingSession = window.name
|
let existingSession = window.name
|
||||||
|
|
||||||
// Get auth token if available
|
// Get auth token if available
|
||||||
|
// Force refresh on reconnect to avoid stale tokens
|
||||||
let authToken: string | undefined
|
let authToken: string | undefined
|
||||||
try {
|
try {
|
||||||
authToken = await useFirebaseAuthStore().getIdToken()
|
authToken = await useFirebaseAuthStore().getIdToken(isReconnect)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Continue without auth token if there's an error
|
// Continue without auth token if there's an error
|
||||||
console.warn('Could not get auth token for WebSocket connection:', error)
|
console.warn('Could not get auth token for WebSocket connection:', error)
|
||||||
|
|||||||
@@ -106,10 +106,12 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const getIdToken = async (): Promise<string | undefined> => {
|
const getIdToken = async (
|
||||||
|
forceRefresh = false
|
||||||
|
): Promise<string | undefined> => {
|
||||||
if (!currentUser.value) return
|
if (!currentUser.value) return
|
||||||
try {
|
try {
|
||||||
return await currentUser.value.getIdToken()
|
return await currentUser.value.getIdToken(forceRefresh)
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (
|
if (
|
||||||
error instanceof FirebaseError &&
|
error instanceof FirebaseError &&
|
||||||
@@ -140,9 +142,11 @@ export const useFirebaseAuthStore = defineStore('firebaseAuth', () => {
|
|||||||
* - An ApiKeyAuthHeader with X-API-KEY if API key exists
|
* - An ApiKeyAuthHeader with X-API-KEY if API key exists
|
||||||
* - null if neither authentication method is available
|
* - null if neither authentication method is available
|
||||||
*/
|
*/
|
||||||
const getAuthHeader = async (): Promise<AuthHeader | null> => {
|
const getAuthHeader = async (
|
||||||
|
forceRefresh = false
|
||||||
|
): Promise<AuthHeader | null> => {
|
||||||
// If available, set header with JWT used to identify the user to Firebase service
|
// 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) {
|
if (token) {
|
||||||
return {
|
return {
|
||||||
Authorization: `Bearer ${token}`
|
Authorization: `Bearer ${token}`
|
||||||
|
|||||||
Reference in New Issue
Block a user