mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-02 04:02:20 +00:00
## Summary Rename `useFirebaseAuthStore` → `useAuthStore` and `FirebaseAuthStoreError` → `AuthStoreError`. Introduce shared mock factory (`authStoreMock.ts`) to replace 16 independent bespoke mocks. ## Changes - **What**: Mechanical rename of store, composable, class, and store ID (`firebaseAuth` → `auth`). Created `src/stores/__tests__/authStoreMock.ts` — a shared mock factory with reactive controls, used by all consuming test files. Migrated all 16 test files from ad-hoc mocks to the shared factory. - **Files**: 62 files changed (rename propagation + new test infra) ## Review Focus - Mock factory API design in `authStoreMock.ts` — covers all store properties with reactive `controls` for per-test customization - Self-test in `authStoreMock.test.ts` validates computed reactivity Fixes #8219 ## Stack This is PR 1/5 in a stacked refactoring series: 1. **→ This PR**: Rename + shared test fixtures 2. #10484: Extract auth-routing from workspaceApi 3. #10485: Auth token priority tests 4. #10486: Decompose MembersPanelContent 5. #10487: Consolidate SubscriptionTier type --------- Co-authored-by: Alexander Brown <drjkl@comfy.org>
136 lines
4.8 KiB
Vue
136 lines
4.8 KiB
Vue
<template>
|
|
<slot v-if="isReady" />
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
/**
|
|
* WorkspaceAuthGate - Conditional auth checkpoint for workspace mode.
|
|
*
|
|
* This gate ensures proper initialization order for workspace-scoped auth:
|
|
* 1. Wait for Firebase auth to resolve
|
|
* 2. Check if teamWorkspacesEnabled feature flag is on
|
|
* 3. If YES: Initialize workspace token and store before rendering
|
|
* 4. If NO: Render immediately using existing Firebase auth
|
|
*
|
|
* This prevents race conditions where API calls use Firebase tokens
|
|
* instead of workspace tokens when the workspace feature is enabled.
|
|
*
|
|
* The splash loader in index.html (z-9999) covers the screen during this
|
|
* phase, so no separate loading indicator is needed here.
|
|
*/
|
|
import { promiseTimeout, until } from '@vueuse/core'
|
|
import { storeToRefs } from 'pinia'
|
|
import { onMounted, ref } from 'vue'
|
|
|
|
import { useFeatureFlags } from '@/composables/useFeatureFlags'
|
|
import { isCloud } from '@/platform/distribution/types'
|
|
import { useSubscriptionDialog } from '@/platform/cloud/subscription/composables/useSubscriptionDialog'
|
|
import { refreshRemoteConfig } from '@/platform/remoteConfig/refreshRemoteConfig'
|
|
import { useTeamWorkspaceStore } from '@/platform/workspace/stores/teamWorkspaceStore'
|
|
import { useAuthStore } from '@/stores/authStore'
|
|
|
|
const FIREBASE_INIT_TIMEOUT_MS = 16_000
|
|
const CONFIG_REFRESH_TIMEOUT_MS = 10_000
|
|
|
|
const isReady = ref(!isCloud)
|
|
const subscriptionDialog = useSubscriptionDialog()
|
|
|
|
async function initialize(): Promise<void> {
|
|
if (!isCloud) return
|
|
|
|
const authStore = useAuthStore()
|
|
const { isInitialized, currentUser } = storeToRefs(authStore)
|
|
|
|
try {
|
|
// Step 1: Wait for Firebase auth to resolve
|
|
// This is shared with router guard - both wait for the same thing,
|
|
// but this gate blocks rendering while router guard blocks navigation
|
|
if (!isInitialized.value) {
|
|
await until(isInitialized).toBe(true, {
|
|
timeout: FIREBASE_INIT_TIMEOUT_MS
|
|
})
|
|
}
|
|
|
|
// Step 2: If not authenticated, nothing more to do
|
|
// Unauthenticated users don't have workspace context
|
|
if (!currentUser.value) {
|
|
isReady.value = true
|
|
return
|
|
}
|
|
|
|
// Step 3: Refresh feature flags with auth context
|
|
// This ensures teamWorkspacesEnabled reflects the authenticated user's state
|
|
// Timeout prevents hanging if server is slow/unresponsive
|
|
try {
|
|
await Promise.race([
|
|
refreshRemoteConfig({ useAuth: true }),
|
|
promiseTimeout(CONFIG_REFRESH_TIMEOUT_MS).then(() => {
|
|
throw new Error('Config refresh timeout')
|
|
})
|
|
])
|
|
} catch (error) {
|
|
console.warn(
|
|
'[WorkspaceAuthGate] Failed to refresh remote config:',
|
|
error
|
|
)
|
|
// Continue - feature flags will use defaults (teamWorkspacesEnabled=false)
|
|
// App will render with Firebase auth fallback
|
|
}
|
|
|
|
// Step 4: THE CHECKPOINT - Are we in workspace mode?
|
|
const { flags } = useFeatureFlags()
|
|
if (!flags.teamWorkspacesEnabled) {
|
|
// Not in workspace mode - use existing Firebase auth flow
|
|
// No additional initialization needed
|
|
isReady.value = true
|
|
return
|
|
}
|
|
|
|
// Step 5: WORKSPACE MODE - Full initialization
|
|
await initializeWorkspaceMode()
|
|
|
|
// Step 6: Resume any pending pricing flow from team workspace creation
|
|
// Only safe after workspace store initialized successfully — the pricing
|
|
// dialog reads workspace state to decide which variant to show.
|
|
const workspaceStore = useTeamWorkspaceStore()
|
|
if (workspaceStore.initState === 'ready') {
|
|
subscriptionDialog.resumePendingPricingFlow()
|
|
}
|
|
} catch (error) {
|
|
console.error('[WorkspaceAuthGate] Initialization failed:', error)
|
|
} finally {
|
|
// Always render (graceful degradation)
|
|
// If workspace init failed, API calls fall back to Firebase token
|
|
isReady.value = true
|
|
}
|
|
}
|
|
|
|
async function initializeWorkspaceMode(): Promise<void> {
|
|
// Initialize the full workspace store which handles:
|
|
// - Restoring workspace token from session (fast path for refresh)
|
|
// - Fetching workspace list
|
|
// - Switching to last used workspace if needed
|
|
// - Setting active workspace
|
|
try {
|
|
const workspaceStore = useTeamWorkspaceStore()
|
|
if (workspaceStore.initState === 'uninitialized') {
|
|
await workspaceStore.initialize()
|
|
}
|
|
} catch (error) {
|
|
// Log but don't block - workspace UI features may not work but app will render
|
|
// API calls will fall back to Firebase token
|
|
console.warn(
|
|
'[WorkspaceAuthGate] Failed to initialize workspace store:',
|
|
error
|
|
)
|
|
}
|
|
}
|
|
|
|
// Initialize on mount. This gate should be placed on the authenticated layout
|
|
// (LayoutDefault) so it mounts fresh after login and unmounts on logout.
|
|
// The router guard ensures only authenticated users reach this layout.
|
|
onMounted(() => {
|
|
void initialize()
|
|
})
|
|
</script>
|