-
Notifications
You must be signed in to change notification settings - Fork 452
Description
Frontend Version
1.24.1
Expected Behavior
ComfyUI should function offline without Firebase network requests when not using Firebase features.
Actual Behavior
Firebase Auth makes network requests that fail when offline or when Firebase is blocked (if there is a token stored from a previous or expired session):
-
On app initialization:
onAuthStateChanged(firebaseAuthStore.ts:82) attempts to validate stored tokens fromfirebase:authUser:[PROJECT_ID]:[DEFAULT] -
When queuing prompts:
getIdToken()is called (app.ts:1242-1243) which attempts to refresh tokens older than 1 hour
Results in toast error messages and degraded offline experience.
Steps to Reproduce
- Sign in to ComfyUI with Firebase Auth
- Close browser
- Disconnect from internet (or be behind firewall that blocks Firebase)
- Wait 1 hour so access token expires (or change timestamp in localstorage)
- Open ComfyUI
- Queue a graph
- Observe network errors in console
Debug Logs
auth/network-request-failed: A network error has occurred.
Failed to refresh token: FirebaseError: Firebase: Error (auth/network-request-failed)
Browser Logs
FirebaseError: Firebase: Error (auth/network-request-failed).
at createErrorInternal (index-6bd8d405.js:474:41)
at _fail (index-6bd8d405.js:445:11)
at _performFetchWithErrorHandling (index-6bd8d405.js:998:11)
Setting JSON
Standard settings with Firebase Auth enabled
What browsers do you use to access the UI?
- Google Chrome
- Mozilla Firefox
- Brave
Other Information
Firebase Auth behavior:
- ID tokens expire after 1 hour
browserLocalPersistencestores tokens in localStorage/IndexedDB- Automatic token refresh attempts on expiry
- Exponential backoff for failed requests
- localStorage not cleared on network failures
Proposed Solutions
1. Catch Network Errors Gracefully
const getIdToken = async (): Promise<string | null> => {
if (currentUser.value) {
try {
return await currentUser.value.getIdToken()
} catch (error) {
if (error.code === 'auth/network-request-failed') {
return null; // Works offline or behind firewalls
}
throw error
}
}
return null
}2. Lazy Token Validation
onAuthStateChanged(auth, (user) => {
currentUser.value = user
isInitialized.value = true
// Skip immediate token validation
})
// app.ts - only try token if needed
const comfyOrgAuthToken = await useFirebaseAuthStore().getIdToken()
.catch(() => undefined); // Fail silently3. Circuit Breaker Pattern
After multiple failures, stop attempting requests for a cooldown period:
class AuthCircuitBreaker {
private failures = 0;
private lastFailure = 0;
private readonly threshold = 3;
private readonly timeout = 60000; // 1 minute
async getToken(): Promise<string | null> {
// If circuit is "open" (too many recent failures), return null immediately
if (this.failures >= this.threshold &&
Date.now() - this.lastFailure < this.timeout) {
return null;
}
try {
const token = await getIdToken();
this.failures = 0; // Reset on success
return token;
} catch (error) {
this.failures++;
this.lastFailure = Date.now();
return null;
}
}
}4. Service Worker Intercept
Intercept and immediately fail Firebase requests to prevent hanging:
// In service worker
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// Firebase domains that might be blocked
const firebaseDomains = [
'googleapis.com',
'firebaseapp.com',
'firebaseio.com'
];
if (firebaseDomains.some(domain => url.hostname.includes(domain))) {
// Try fetch with short timeout
event.respondWith(
Promise.race([
fetch(event.request),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), 3000)
)
]).catch(() =>
new Response(null, { status: 503, statusText: 'Service Unavailable' })
)
);
}
});5. Offline-First Auth Wrapper
Wrapper store that tracks Firebase availability and caches state:
export const useOfflineAuthStore = () => {
const authStore = useFirebaseAuthStore();
const firebaseAvailable = ref(true);
const lastValidToken = ref<string | null>(null);
const checkFirebaseAvailability = async () => {
try {
// Quick health check to Firebase
await fetch('https://www.googleapis.com/identitytoolkit/v3/relyingparty', {
method: 'HEAD',
mode: 'no-cors',
signal: AbortSignal.timeout(2000)
});
firebaseAvailable.value = true;
} catch {
firebaseAvailable.value = false;
}
};
return {
...authStore,
getIdToken: async () => {
if (!firebaseAvailable.value) {
return lastValidToken.value; // Use cached token
}
try {
const token = await authStore.getIdToken();
lastValidToken.value = token; // Cache for offline use
return token;
} catch (error) {
firebaseAvailable.value = false;
return lastValidToken.value;
}
}
};
}┆Issue is synchronized with this Notion page by Unito