mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-25 08:49:36 +00:00
Fix session cookie creation race: skip initial token refresh, wrap extension auth hooks (#6563)
Fixes a race causing “No auth header available for session creation” during sign‑in, by skipping the initial token refresh event, and wrapping extension auth hooks with async error handling. Sentry: https://comfy-org.sentry.io/issues/6990347926/?alert_rule_id=1614600&project=4509681221369857 Context - Error surfaced as an unhandled rejection when session creation was triggered without a valid auth header. - Triggers: both onAuthUserResolved and onAuthTokenRefreshed fired during initial login. - Pre‑fix, onIdTokenChanged treated the very first token emission as a “refresh” as well, so two concurrent createSession() calls ran back‑to‑back. - One of those calls could land before a Firebase ID token existed, so getAuthHeader() returned null → createSession threw “No auth header available for session creation”. Exact pre‑fix failure path - src/extensions/core/cloudSessionCookie.ts - onAuthUserResolved → useSessionCookie().createSession() - onAuthTokenRefreshed → useSessionCookie().createSession() - src/stores/firebaseAuthStore.ts - onIdTokenChanged increments tokenRefreshTrigger even for the initial token (treated as a refresh) - getAuthHeader() → getIdToken() may be undefined briefly during initialization - src/platform/auth/session/useSessionCookie.ts - createSession(): calls authStore.getAuthHeader(); if falsy, throws Error('No auth header available for session creation') What this PR changes 1) Skip initial token “refresh” - Track lastTokenUserId and ignore the first onIdTokenChanged for a user; only subsequent token changes count as refresh events. - File: src/stores/firebaseAuthStore.ts 2) Wrap extension auth hooks with async error handling - Use wrapWithErrorHandlingAsync for onAuthUserResolved/onAuthTokenRefreshed/onAuthUserLogout callbacks to avoid unhandled rejections. - File: src/services/extensionService.ts Result - Eliminates the timing window where createSession() runs before getIdToken() returns a token. - Ensures any remaining errors are caught and reported instead of surfacing as unhandled promise rejections. Notes - Lint and typecheck run clean (pnpm lint:fix && pnpm typecheck). ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6563-Fix-session-cookie-creation-race-dedupe-calls-skip-initial-token-refresh-wrap-extensio-2a16d73d365081ef8c22c5ac8cb948aa) by [Unito](https://www.unito.io)
This commit is contained in:
@@ -11,12 +11,17 @@ import { useMenuItemStore } from '@/stores/menuItemStore'
|
||||
import { useWidgetStore } from '@/stores/widgetStore'
|
||||
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
|
||||
import type { ComfyExtension } from '@/types/comfy'
|
||||
import type { AuthUserInfo } from '@/types/authTypes'
|
||||
|
||||
export const useExtensionService = () => {
|
||||
const extensionStore = useExtensionStore()
|
||||
const settingStore = useSettingStore()
|
||||
const keybindingStore = useKeybindingStore()
|
||||
const { wrapWithErrorHandling } = useErrorHandling()
|
||||
const {
|
||||
wrapWithErrorHandling,
|
||||
wrapWithErrorHandlingAsync,
|
||||
toastErrorHandler
|
||||
} = useErrorHandling()
|
||||
|
||||
/**
|
||||
* Loads all extensions from the API into the window in parallel
|
||||
@@ -77,22 +82,55 @@ export const useExtensionService = () => {
|
||||
|
||||
if (extension.onAuthUserResolved) {
|
||||
const { onUserResolved } = useCurrentUser()
|
||||
const handleUserResolved = wrapWithErrorHandlingAsync(
|
||||
(user: AuthUserInfo) => extension.onAuthUserResolved?.(user, app),
|
||||
(error) => {
|
||||
console.error('[Extension Auth Hook Error]', {
|
||||
extension: extension.name,
|
||||
hook: 'onAuthUserResolved',
|
||||
error
|
||||
})
|
||||
toastErrorHandler(error)
|
||||
}
|
||||
)
|
||||
onUserResolved((user) => {
|
||||
void extension.onAuthUserResolved?.(user, app)
|
||||
void handleUserResolved(user)
|
||||
})
|
||||
}
|
||||
|
||||
if (extension.onAuthTokenRefreshed) {
|
||||
const { onTokenRefreshed } = useCurrentUser()
|
||||
const handleTokenRefreshed = wrapWithErrorHandlingAsync(
|
||||
() => extension.onAuthTokenRefreshed?.(),
|
||||
(error) => {
|
||||
console.error('[Extension Auth Hook Error]', {
|
||||
extension: extension.name,
|
||||
hook: 'onAuthTokenRefreshed',
|
||||
error
|
||||
})
|
||||
toastErrorHandler(error)
|
||||
}
|
||||
)
|
||||
onTokenRefreshed(() => {
|
||||
void extension.onAuthTokenRefreshed?.()
|
||||
void handleTokenRefreshed()
|
||||
})
|
||||
}
|
||||
|
||||
if (extension.onAuthUserLogout) {
|
||||
const { onUserLogout } = useCurrentUser()
|
||||
const handleUserLogout = wrapWithErrorHandlingAsync(
|
||||
() => extension.onAuthUserLogout?.(),
|
||||
(error) => {
|
||||
console.error('[Extension Auth Hook Error]', {
|
||||
extension: extension.name,
|
||||
hook: 'onAuthUserLogout',
|
||||
error
|
||||
})
|
||||
toastErrorHandler(error)
|
||||
}
|
||||
)
|
||||
onUserLogout(() => {
|
||||
void extension.onAuthUserLogout?.()
|
||||
void handleUserLogout()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user