Skip to content

Fix Firebase Database initialization errors with centralized error handling. #1658

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 24 additions & 34 deletions src/api/firebase.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
import {
update as dbUpdate,
endAt,
get,
getDatabase,
limitToLast,
orderByChild,
query,
ref,
startAt,
} from 'firebase/database';
import {app} from '../base';
import {update as dbUpdate, endAt, get, limitToLast, orderByChild, query, ref, startAt} from 'firebase/database';
import {getFirebaseDatabase} from '../utils/firebaseDatabase';
import {
type EnrichedBlueprintSummary,
type RawBlueprint,
Expand Down Expand Up @@ -161,7 +151,7 @@ export const fetchBlueprint = async (
}

// Fall back to Firebase if CDN failed or data was stale
const blueprintRef = ref(getDatabase(app), `/blueprints/${blueprintId}/`);
const blueprintRef = ref(getFirebaseDatabase(), `/blueprints/${blueprintId}/`);
const snapshot = await get(blueprintRef);

if (!snapshot.exists()) {
Expand All @@ -179,7 +169,7 @@ export const fetchBlueprint = async (

export const fetchBlueprintTags = async (blueprintId: string): Promise<string[]> => {
try {
const tagsRef = ref(getDatabase(app), `/blueprints/${blueprintId}/tags/`);
const tagsRef = ref(getFirebaseDatabase(), `/blueprints/${blueprintId}/tags/`);
const snapshot = await get(tagsRef);
return snapshot.exists() ? snapshot.val() : [];
} catch (error) {
Expand All @@ -192,7 +182,7 @@ export const fetchBlueprintTags = async (blueprintId: string): Promise<string[]>

export const fetchTags = async (): Promise<Record<string, string[]>> => {
try {
const tagsRef = ref(getDatabase(app), '/tags/');
const tagsRef = ref(getFirebaseDatabase(), '/tags/');
const snapshot = await get(tagsRef);

if (!snapshot.exists()) {
Expand Down Expand Up @@ -220,7 +210,7 @@ export const fetchByTagData = async (tagId: string): Promise<Record<string, bool
}

try {
const snapshot = await get(ref(getDatabase(app), `/byTag/${tagId}`));
const snapshot = await get(ref(getFirebaseDatabase(), `/byTag/${tagId}`));
return snapshot.val() || {};
} catch (error) {
if (isNetworkError(error)) {
Expand All @@ -232,7 +222,7 @@ export const fetchByTagData = async (tagId: string): Promise<Record<string, bool

export const fetchModerator = async (userId: string): Promise<boolean> => {
try {
const moderatorRef = ref(getDatabase(app), `/moderators/${userId}`);
const moderatorRef = ref(getFirebaseDatabase(), `/moderators/${userId}`);
const snapshot = await get(moderatorRef);

return Boolean(snapshot.val());
Expand All @@ -246,7 +236,7 @@ export const fetchModerator = async (userId: string): Promise<boolean> => {

export const fetchUserDisplayName = async (userId: string): Promise<string | null> => {
try {
const userRef = ref(getDatabase(app), `/users/${userId}/displayName`);
const userRef = ref(getFirebaseDatabase(), `/users/${userId}/displayName`);
const snapshot = await get(userRef);

return snapshot.val();
Expand All @@ -260,7 +250,7 @@ export const fetchUserDisplayName = async (userId: string): Promise<string | nul

export const fetchUserBlueprints = async (userId: string): Promise<Record<string, boolean>> => {
try {
const snapshot = await get(ref(getDatabase(app), `/users/${userId}/blueprints`));
const snapshot = await get(ref(getFirebaseDatabase(), `/users/${userId}/blueprints`));
return snapshot.val() || {};
} catch (error) {
if (isNetworkError(error)) {
Expand All @@ -272,7 +262,7 @@ export const fetchUserBlueprints = async (userId: string): Promise<Record<string

export const fetchUserFavorites = async (userId: string): Promise<Record<string, boolean>> => {
try {
const snapshot = await get(ref(getDatabase(app), `/users/${userId}/favorites`));
const snapshot = await get(ref(getFirebaseDatabase(), `/users/${userId}/favorites`));
return snapshot.val() || {};
} catch (error) {
if (isNetworkError(error)) {
Expand All @@ -284,7 +274,7 @@ export const fetchUserFavorites = async (userId: string): Promise<Record<string,

export const fetchUser = async (userId: string): Promise<RawUser | null> => {
try {
const userRef = ref(getDatabase(app), `/users/${userId}`);
const userRef = ref(getFirebaseDatabase(), `/users/${userId}`);
const snapshot = await get(userRef);

if (!snapshot.exists()) {
Expand All @@ -309,7 +299,7 @@ export const fetchUser = async (userId: string): Promise<RawUser | null> => {

export const fetchAllUsers = async (): Promise<UserData[]> => {
try {
const usersRef = ref(getDatabase(app), '/users/');
const usersRef = ref(getFirebaseDatabase(), '/users/');
const snapshot = await get(usersRef);

if (!snapshot.exists()) {
Expand Down Expand Up @@ -338,13 +328,13 @@ export const fetchAllUsers = async (): Promise<UserData[]> => {
};

export const reconcileFavoritesCount = async (blueprintId: string): Promise<ReconcileResult> => {
const favoritesRef = ref(getDatabase(app), `/blueprints/${blueprintId}/favorites`);
const favoritesRef = ref(getFirebaseDatabase(), `/blueprints/${blueprintId}/favorites`);
const favoritesSnapshot = await get(favoritesRef);
const favorites = favoritesSnapshot.exists() ? favoritesSnapshot.val() : {};

const actualCount = Object.values(favorites).filter(Boolean).length;

const summaryRef = ref(getDatabase(app), `/blueprintSummaries/${blueprintId}/numberOfFavorites`);
const summaryRef = ref(getFirebaseDatabase(), `/blueprintSummaries/${blueprintId}/numberOfFavorites`);
const summarySnapshot = await get(summaryRef);
const currentSummaryCount = summarySnapshot.exists() ? summarySnapshot.val() : 0;

Expand All @@ -355,7 +345,7 @@ export const reconcileFavoritesCount = async (blueprintId: string): Promise<Reco
[`/blueprintSummaries/${blueprintId}/numberOfFavorites`]: actualCount,
};

await dbUpdate(ref(getDatabase(app)), updates);
await dbUpdate(ref(getFirebaseDatabase()), updates);
}

return {
Expand All @@ -370,7 +360,7 @@ export const reconcileFavoritesCount = async (blueprintId: string): Promise<Reco

// TODO 2025-04-12: Move this out of firebase.js, and refactor it to use react query hooks from the hooks/ dir. The problem with the current implementation is that it performs many queries but doesn't cache anything. /users/${userId}/favorites already has a hook useUserFavorites in useUser. But `/blueprints/${blueprintId}/favorites/${userId}` doesn't have a hook or a mutation yet, so we need to add them.
export const reconcileUserFavorites = async (userId: string): Promise<UserReconcileResult> => {
const userFavoritesRef = ref(getDatabase(app), `/users/${userId}/favorites`);
const userFavoritesRef = ref(getFirebaseDatabase(), `/users/${userId}/favorites`);
const userFavoritesSnapshot = await get(userFavoritesRef);
const userFavorites = userFavoritesSnapshot.exists() ? userFavoritesSnapshot.val() : {};

Expand All @@ -386,7 +376,7 @@ export const reconcileUserFavorites = async (userId: string): Promise<UserReconc
continue;
}

const blueprintFavoritesRef = ref(getDatabase(app), `/blueprints/${blueprintId}/favorites/${userId}`);
const blueprintFavoritesRef = ref(getFirebaseDatabase(), `/blueprints/${blueprintId}/favorites/${userId}`);
const blueprintFavoriteSnapshot = await get(blueprintFavoritesRef);

if (!blueprintFavoriteSnapshot.exists() || !blueprintFavoriteSnapshot.val()) {
Expand All @@ -406,7 +396,7 @@ export const reconcileUserFavorites = async (userId: string): Promise<UserReconc
}

if (Object.keys(updates).length > 0) {
await dbUpdate(ref(getDatabase(app)), updates);
await dbUpdate(ref(getFirebaseDatabase()), updates);
}

return {
Expand All @@ -419,7 +409,7 @@ export const reconcileUserFavorites = async (userId: string): Promise<UserReconc

export const cleanupInvalidUserFavorite = async (userId: string, blueprintId: string): Promise<boolean> => {
try {
const summaryRef = ref(getDatabase(app), `/blueprintSummaries/${blueprintId}`);
const summaryRef = ref(getFirebaseDatabase(), `/blueprintSummaries/${blueprintId}`);
const summarySnapshot = await get(summaryRef);

if (summarySnapshot.exists()) {
Expand All @@ -430,7 +420,7 @@ export const cleanupInvalidUserFavorite = async (userId: string, blueprintId: st
[`/users/${userId}/favorites/${blueprintId}`]: null,
};

await dbUpdate(ref(getDatabase(app)), updates);
await dbUpdate(ref(getFirebaseDatabase()), updates);

return true;
} catch {
Expand All @@ -440,7 +430,7 @@ export const cleanupInvalidUserFavorite = async (userId: string, blueprintId: st

export const fetchBlueprintSummary = async (blueprintId: string): Promise<RawBlueprintSummary | null> => {
try {
const summaryRef = ref(getDatabase(app), `/blueprintSummaries/${blueprintId}`);
const summaryRef = ref(getFirebaseDatabase(), `/blueprintSummaries/${blueprintId}`);
const snapshot = await get(summaryRef);

if (!snapshot.exists()) {
Expand Down Expand Up @@ -469,14 +459,14 @@ export const fetchPaginatedSummaries = async (
try {
if (lastKey && lastValue) {
summariesQuery = query(
ref(getDatabase(app), '/blueprintSummaries/'),
ref(getFirebaseDatabase(), '/blueprintSummaries/'),
orderByChild(orderByField),
endAt(lastValue, lastKey),
limitToLast(pageSize + 1),
);
} else {
summariesQuery = query(
ref(getDatabase(app), '/blueprintSummaries/'),
ref(getFirebaseDatabase(), '/blueprintSummaries/'),
orderByChild(orderByField),
limitToLast(pageSize + 1),
);
Expand Down Expand Up @@ -544,7 +534,7 @@ export const fetchSummariesNewerThan = async (
): Promise<RawBlueprintSummary[]> => {
try {
const summariesQuery = query(
ref(getDatabase(app), '/blueprintSummaries/'),
ref(getFirebaseDatabase(), '/blueprintSummaries/'),
orderByChild('lastUpdatedDate'),
startAt(highWatermark + 1),
limitToLast(pageSize),
Expand Down
5 changes: 3 additions & 2 deletions src/components/Account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {useMutation, useQueryClient} from '@tanstack/react-query';
import {useNavigate} from '@tanstack/react-router';
import {getAuth, updateProfile} from 'firebase/auth';
import {getDatabase, ref, set} from 'firebase/database';
import {ref, set} from 'firebase/database';
import type React from 'react';
import {useEffect, useState} from 'react';
import Button from 'react-bootstrap/Button';
Expand All @@ -15,6 +15,7 @@ import Row from 'react-bootstrap/Row';
import {useAuthState} from 'react-firebase-hooks/auth';

import {app} from '../base';
import {getFirebaseDatabase} from '../utils/firebaseDatabase';
import PageHeader from './PageHeader';

const Account = () => {
Expand All @@ -29,7 +30,7 @@ const Account = () => {
displayName: newDisplayName,
});

const userRef = ref(getDatabase(app), `/users/${user!.uid}/displayName/`);
const userRef = ref(getFirebaseDatabase(), `/users/${user!.uid}/displayName/`);
await set(userRef, newDisplayName);

return newDisplayName;
Expand Down
15 changes: 12 additions & 3 deletions src/components/Root.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {Outlet} from '@tanstack/react-router';
import {getAuth, User} from 'firebase/auth';
import {getDatabase, ref, runTransaction} from 'firebase/database';
import {ref, runTransaction} from 'firebase/database';
import type React from 'react';
import {useEffect} from 'react';
import {useAuthState} from 'react-firebase-hooks/auth';

import {app} from '../base';
import {getFirebaseDatabase} from '../utils/firebaseDatabase';
import ErrorBoundary from './ErrorBoundary';
import Header from './Header';

Expand Down Expand Up @@ -43,8 +44,16 @@ const Root: React.FC = () => {
};
};

const userRef = ref(getDatabase(app), `/users/${uid}/`);
runTransaction(userRef, buildUserInformation);
try {
const userRef = ref(getFirebaseDatabase(), `/users/${uid}/`);
runTransaction(userRef, buildUserInformation).catch((error) => {
// Silently ignore transaction errors - user data will be updated on next sign-in
console.log('User data update skipped:', error?.message || 'Transaction failed');
});
} catch (error) {
// Database initialization failed - likely due to network issues or browser privacy settings
console.log('Database connection skipped:', error instanceof Error ? error.message : 'Unknown error');
}
}
}, [user]);

Expand Down
10 changes: 5 additions & 5 deletions src/hooks/useBlueprintFavorite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import {
useQuery,
useQueryClient,
} from '@tanstack/react-query';
import {update as dbUpdate, get, getDatabase, ref} from 'firebase/database';
import {update as dbUpdate, get, ref} from 'firebase/database';
import React from 'react';
import {app} from '../base';
import {getFirebaseDatabase} from '../utils/firebaseDatabase';

interface ReconcileResult {
userId: string;
Expand Down Expand Up @@ -41,7 +41,7 @@ const reconcileFavorites = async (
}

if (Object.keys(updates).length > 0) {
await dbUpdate(ref(getDatabase(app)), updates);
await dbUpdate(ref(getFirebaseDatabase()), updates);
}
};

Expand All @@ -53,7 +53,7 @@ export const useIsUserFavorite = (
queryKey: ['users', 'userId', userId, 'favorites', 'blueprintId', blueprintId],
queryFn: async () => {
if (!userId || !blueprintId) return false;
const snapshot = await get(ref(getDatabase(app), `/users/${userId}/favorites/${blueprintId}`));
const snapshot = await get(ref(getFirebaseDatabase(), `/users/${userId}/favorites/${blueprintId}`));
return snapshot.exists() && snapshot.val() === true;
},
enabled: !!userId && !!blueprintId,
Expand All @@ -68,7 +68,7 @@ export const useIsBlueprintFavorite = (
queryKey: ['blueprints', 'blueprintId', blueprintId, 'favorites', 'userId', userId],
queryFn: async () => {
if (!userId || !blueprintId) return false;
const snapshot = await get(ref(getDatabase(app), `/blueprints/${blueprintId}/favorites/${userId}`));
const snapshot = await get(ref(getFirebaseDatabase(), `/blueprints/${blueprintId}/favorites/${userId}`));
return snapshot.exists() && snapshot.val() === true;
},
enabled: !!blueprintId && !!userId,
Expand Down
8 changes: 4 additions & 4 deletions src/hooks/useCreateBlueprint.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {useMutation, useQueryClient} from '@tanstack/react-query';
import {useNavigate} from '@tanstack/react-router';
import type {User} from 'firebase/auth';
import {update as dbUpdate, getDatabase, push, ref, serverTimestamp} from 'firebase/database';
import {update as dbUpdate, push, ref, serverTimestamp} from 'firebase/database';
import flatMap from 'lodash/flatMap';
import {app} from '../base';
import {getFirebaseDatabase} from '../utils/firebaseDatabase';
import {
validateRawBlueprintSummary,
validateRawPaginatedBlueprintSummaries,
Expand Down Expand Up @@ -86,7 +86,7 @@ export const useCreateBlueprint = () => {
lastUpdatedDate: serverTimestamp(),
};

const blueprintsRef = ref(getDatabase(app), '/blueprints');
const blueprintsRef = ref(getFirebaseDatabase(), '/blueprints');
const newBlueprintRef = push(blueprintsRef, blueprintData);
const newBlueprintKey = newBlueprintRef.key;

Expand All @@ -104,7 +104,7 @@ export const useCreateBlueprint = () => {
updates[`/byTag/${tag}/${newBlueprintKey}`] = true;
});

await dbUpdate(ref(getDatabase(app)), updates);
await dbUpdate(ref(getFirebaseDatabase()), updates);

return {
blueprintId: newBlueprintKey,
Expand Down
12 changes: 8 additions & 4 deletions src/hooks/useToggleFavoriteMutation.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
import {renderHook, waitFor} from '@testing-library/react';
import {update as dbUpdate, getDatabase, ref} from 'firebase/database';
import {update as dbUpdate, ref} from 'firebase/database';
import {getFirebaseDatabase} from '../utils/firebaseDatabase';
import type React from 'react';
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
import {useToggleFavoriteMutation} from './useToggleFavoriteMutation';

// Mock Firebase
vi.mock('firebase/database', () => ({
getDatabase: vi.fn(),
ref: vi.fn(),
update: vi.fn(),
}));

vi.mock('../utils/firebaseDatabase', () => ({
getFirebaseDatabase: vi.fn(),
}));

vi.mock('../base', () => ({
app: {},
}));
Expand All @@ -36,7 +40,7 @@ describe('useToggleFavoriteMutation', () => {

mockDatabase = {};
mockRef = {};
vi.mocked(getDatabase).mockReturnValue(mockDatabase);
vi.mocked(getFirebaseDatabase).mockReturnValue(mockDatabase);
vi.mocked(ref).mockReturnValue(mockRef);
vi.mocked(dbUpdate).mockResolvedValue(undefined);
});
Expand Down Expand Up @@ -456,7 +460,7 @@ describe('useToggleFavoriteMutation', () => {

// Should only call dbUpdate, no other Firebase operations
expect(vi.mocked(dbUpdate)).toHaveBeenCalledTimes(1);
expect(vi.mocked(getDatabase)).toHaveBeenCalledTimes(1);
expect(vi.mocked(getFirebaseDatabase)).toHaveBeenCalledTimes(1);
expect(vi.mocked(ref)).toHaveBeenCalledTimes(1);
});
});
Expand Down
Loading