fix(auth/session): dedupe session creation, skip initial token refresh, and wrap extension auth hooks\n\nPrevents concurrent createSession calls and avoids treating the initial onIdTokenChanged as a refresh. This eliminates a race where useSessionCookie.createSession() could run before a Firebase ID token existed, causing:\n\n Error: No auth header available for session creation\n\nAlso wraps extension auth hooks with async error handling to avoid unhandled rejections.\n\nRefs: Sentry issue 6990347926

This commit is contained in:
Benjamin Lu
2025-11-03 16:34:58 -08:00
parent dffb07c745
commit 3719a46b41
4 changed files with 200 additions and 31 deletions

View File

@@ -2,6 +2,15 @@ import { api } from '@/scripts/api'
import { isCloud } from '@/platform/distribution/types'
import { useFirebaseAuthStore } from '@/stores/firebaseAuthStore'
/**
* Tracks the in-flight createSession request to dedupe concurrent calls.
*/
let createSessionInFlight: Promise<void> | null = null
/**
* Tracks the in-flight deleteSession request to dedupe concurrent calls.
*/
let deleteSessionInFlight: Promise<void> | null = null
/**
* Session cookie management for cloud authentication.
* Creates and deletes session cookies on the ComfyUI server.
@@ -14,27 +23,40 @@ export const useSessionCookie = () => {
const createSession = async (): Promise<void> => {
if (!isCloud) return
const authStore = useFirebaseAuthStore()
const authHeader = await authStore.getAuthHeader()
if (!authHeader) {
throw new Error('No auth header available for session creation')
if (createSessionInFlight) {
await createSessionInFlight
return
}
const response = await fetch(api.apiURL('/auth/session'), {
method: 'POST',
credentials: 'include',
headers: {
...authHeader,
'Content-Type': 'application/json'
}
})
createSessionInFlight = (async () => {
const authStore = useFirebaseAuthStore()
const authHeader = await authStore.getAuthHeader()
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
throw new Error(
`Failed to create session: ${errorData.message || response.statusText}`
)
if (!authHeader) {
throw new Error('No auth header available for session creation')
}
const response = await fetch(api.apiURL('/auth/session'), {
method: 'POST',
credentials: 'include',
headers: {
...authHeader,
'Content-Type': 'application/json'
}
})
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
throw new Error(
`Failed to create session: ${errorData.message || response.statusText}`
)
}
})()
try {
await createSessionInFlight
} finally {
createSessionInFlight = null
}
}
@@ -45,16 +67,29 @@ export const useSessionCookie = () => {
const deleteSession = async (): Promise<void> => {
if (!isCloud) return
const response = await fetch(api.apiURL('/auth/session'), {
method: 'DELETE',
credentials: 'include'
})
if (deleteSessionInFlight) {
await deleteSessionInFlight
return
}
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
throw new Error(
`Failed to delete session: ${errorData.message || response.statusText}`
)
deleteSessionInFlight = (async () => {
const response = await fetch(api.apiURL('/auth/session'), {
method: 'DELETE',
credentials: 'include'
})
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
throw new Error(
`Failed to delete session: ${errorData.message || response.statusText}`
)
}
})()
try {
await deleteSessionInFlight
} finally {
deleteSessionInFlight = null
}
}