Skip to content

Commit bd901bb

Browse files
Fix Firebase Database initialization errors with centralized error handling.
1 parent 5ff00f3 commit bd901bb

File tree

10 files changed

+119
-65
lines changed

10 files changed

+119
-65
lines changed

src/api/firebase.ts

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
1-
import {
2-
update as dbUpdate,
3-
endAt,
4-
get,
5-
getDatabase,
6-
limitToLast,
7-
orderByChild,
8-
query,
9-
ref,
10-
startAt,
11-
} from 'firebase/database';
12-
import {app} from '../base';
1+
import {update as dbUpdate, endAt, get, limitToLast, orderByChild, query, ref, startAt} from 'firebase/database';
2+
import {getFirebaseDatabase} from '../utils/firebaseDatabase';
133
import {
144
type EnrichedBlueprintSummary,
155
type RawBlueprint,
@@ -161,7 +151,7 @@ export const fetchBlueprint = async (
161151
}
162152

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

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

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

193183
export const fetchTags = async (): Promise<Record<string, string[]>> => {
194184
try {
195-
const tagsRef = ref(getDatabase(app), '/tags/');
185+
const tagsRef = ref(getFirebaseDatabase(), '/tags/');
196186
const snapshot = await get(tagsRef);
197187

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

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

233223
export const fetchModerator = async (userId: string): Promise<boolean> => {
234224
try {
235-
const moderatorRef = ref(getDatabase(app), `/moderators/${userId}`);
225+
const moderatorRef = ref(getFirebaseDatabase(), `/moderators/${userId}`);
236226
const snapshot = await get(moderatorRef);
237227

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

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

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

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

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

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

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

310300
export const fetchAllUsers = async (): Promise<UserData[]> => {
311301
try {
312-
const usersRef = ref(getDatabase(app), '/users/');
302+
const usersRef = ref(getFirebaseDatabase(), '/users/');
313303
const snapshot = await get(usersRef);
314304

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

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

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

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

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

358-
await dbUpdate(ref(getDatabase(app)), updates);
348+
await dbUpdate(ref(getFirebaseDatabase()), updates);
359349
}
360350

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

371361
// 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.
372362
export const reconcileUserFavorites = async (userId: string): Promise<UserReconcileResult> => {
373-
const userFavoritesRef = ref(getDatabase(app), `/users/${userId}/favorites`);
363+
const userFavoritesRef = ref(getFirebaseDatabase(), `/users/${userId}/favorites`);
374364
const userFavoritesSnapshot = await get(userFavoritesRef);
375365
const userFavorites = userFavoritesSnapshot.exists() ? userFavoritesSnapshot.val() : {};
376366

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

389-
const blueprintFavoritesRef = ref(getDatabase(app), `/blueprints/${blueprintId}/favorites/${userId}`);
379+
const blueprintFavoritesRef = ref(getFirebaseDatabase(), `/blueprints/${blueprintId}/favorites/${userId}`);
390380
const blueprintFavoriteSnapshot = await get(blueprintFavoritesRef);
391381

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

408398
if (Object.keys(updates).length > 0) {
409-
await dbUpdate(ref(getDatabase(app)), updates);
399+
await dbUpdate(ref(getFirebaseDatabase()), updates);
410400
}
411401

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

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

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

433-
await dbUpdate(ref(getDatabase(app)), updates);
423+
await dbUpdate(ref(getFirebaseDatabase()), updates);
434424

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

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

446436
if (!snapshot.exists()) {
@@ -469,14 +459,14 @@ export const fetchPaginatedSummaries = async (
469459
try {
470460
if (lastKey && lastValue) {
471461
summariesQuery = query(
472-
ref(getDatabase(app), '/blueprintSummaries/'),
462+
ref(getFirebaseDatabase(), '/blueprintSummaries/'),
473463
orderByChild(orderByField),
474464
endAt(lastValue, lastKey),
475465
limitToLast(pageSize + 1),
476466
);
477467
} else {
478468
summariesQuery = query(
479-
ref(getDatabase(app), '/blueprintSummaries/'),
469+
ref(getFirebaseDatabase(), '/blueprintSummaries/'),
480470
orderByChild(orderByField),
481471
limitToLast(pageSize + 1),
482472
);
@@ -544,7 +534,7 @@ export const fetchSummariesNewerThan = async (
544534
): Promise<RawBlueprintSummary[]> => {
545535
try {
546536
const summariesQuery = query(
547-
ref(getDatabase(app), '/blueprintSummaries/'),
537+
ref(getFirebaseDatabase(), '/blueprintSummaries/'),
548538
orderByChild('lastUpdatedDate'),
549539
startAt(highWatermark + 1),
550540
limitToLast(pageSize),

src/components/Account.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
33
import {useMutation, useQueryClient} from '@tanstack/react-query';
44
import {useNavigate} from '@tanstack/react-router';
55
import {getAuth, updateProfile} from 'firebase/auth';
6-
import {getDatabase, ref, set} from 'firebase/database';
6+
import {ref, set} from 'firebase/database';
77
import type React from 'react';
88
import {useEffect, useState} from 'react';
99
import Button from 'react-bootstrap/Button';
@@ -15,6 +15,7 @@ import Row from 'react-bootstrap/Row';
1515
import {useAuthState} from 'react-firebase-hooks/auth';
1616

1717
import {app} from '../base';
18+
import {getFirebaseDatabase} from '../utils/firebaseDatabase';
1819
import PageHeader from './PageHeader';
1920

2021
const Account = () => {
@@ -29,7 +30,7 @@ const Account = () => {
2930
displayName: newDisplayName,
3031
});
3132

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

3536
return newDisplayName;

src/components/Root.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import {Outlet} from '@tanstack/react-router';
22
import {getAuth, User} from 'firebase/auth';
3-
import {getDatabase, ref, runTransaction} from 'firebase/database';
3+
import {ref, runTransaction} from 'firebase/database';
44
import type React from 'react';
55
import {useEffect} from 'react';
66
import {useAuthState} from 'react-firebase-hooks/auth';
77

88
import {app} from '../base';
9+
import {getFirebaseDatabase} from '../utils/firebaseDatabase';
910
import ErrorBoundary from './ErrorBoundary';
1011
import Header from './Header';
1112

@@ -43,8 +44,16 @@ const Root: React.FC = () => {
4344
};
4445
};
4546

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

src/hooks/useBlueprintFavorite.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import {
55
useQuery,
66
useQueryClient,
77
} from '@tanstack/react-query';
8-
import {update as dbUpdate, get, getDatabase, ref} from 'firebase/database';
8+
import {update as dbUpdate, get, ref} from 'firebase/database';
99
import React from 'react';
10-
import {app} from '../base';
10+
import {getFirebaseDatabase} from '../utils/firebaseDatabase';
1111

1212
interface ReconcileResult {
1313
userId: string;
@@ -41,7 +41,7 @@ const reconcileFavorites = async (
4141
}
4242

4343
if (Object.keys(updates).length > 0) {
44-
await dbUpdate(ref(getDatabase(app)), updates);
44+
await dbUpdate(ref(getFirebaseDatabase()), updates);
4545
}
4646
};
4747

@@ -53,7 +53,7 @@ export const useIsUserFavorite = (
5353
queryKey: ['users', 'userId', userId, 'favorites', 'blueprintId', blueprintId],
5454
queryFn: async () => {
5555
if (!userId || !blueprintId) return false;
56-
const snapshot = await get(ref(getDatabase(app), `/users/${userId}/favorites/${blueprintId}`));
56+
const snapshot = await get(ref(getFirebaseDatabase(), `/users/${userId}/favorites/${blueprintId}`));
5757
return snapshot.exists() && snapshot.val() === true;
5858
},
5959
enabled: !!userId && !!blueprintId,
@@ -68,7 +68,7 @@ export const useIsBlueprintFavorite = (
6868
queryKey: ['blueprints', 'blueprintId', blueprintId, 'favorites', 'userId', userId],
6969
queryFn: async () => {
7070
if (!userId || !blueprintId) return false;
71-
const snapshot = await get(ref(getDatabase(app), `/blueprints/${blueprintId}/favorites/${userId}`));
71+
const snapshot = await get(ref(getFirebaseDatabase(), `/blueprints/${blueprintId}/favorites/${userId}`));
7272
return snapshot.exists() && snapshot.val() === true;
7373
},
7474
enabled: !!blueprintId && !!userId,

src/hooks/useCreateBlueprint.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import {useMutation, useQueryClient} from '@tanstack/react-query';
22
import {useNavigate} from '@tanstack/react-router';
33
import type {User} from 'firebase/auth';
4-
import {update as dbUpdate, getDatabase, push, ref, serverTimestamp} from 'firebase/database';
4+
import {update as dbUpdate, push, ref, serverTimestamp} from 'firebase/database';
55
import flatMap from 'lodash/flatMap';
6-
import {app} from '../base';
6+
import {getFirebaseDatabase} from '../utils/firebaseDatabase';
77
import {
88
validateRawBlueprintSummary,
99
validateRawPaginatedBlueprintSummaries,
@@ -86,7 +86,7 @@ export const useCreateBlueprint = () => {
8686
lastUpdatedDate: serverTimestamp(),
8787
};
8888

89-
const blueprintsRef = ref(getDatabase(app), '/blueprints');
89+
const blueprintsRef = ref(getFirebaseDatabase(), '/blueprints');
9090
const newBlueprintRef = push(blueprintsRef, blueprintData);
9191
const newBlueprintKey = newBlueprintRef.key;
9292

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

107-
await dbUpdate(ref(getDatabase(app)), updates);
107+
await dbUpdate(ref(getFirebaseDatabase()), updates);
108108

109109
return {
110110
blueprintId: newBlueprintKey,

src/hooks/useToggleFavoriteMutation.test.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
22
import {renderHook, waitFor} from '@testing-library/react';
3-
import {update as dbUpdate, getDatabase, ref} from 'firebase/database';
3+
import {update as dbUpdate, ref} from 'firebase/database';
4+
import {getFirebaseDatabase} from '../utils/firebaseDatabase';
45
import type React from 'react';
56
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
67
import {useToggleFavoriteMutation} from './useToggleFavoriteMutation';
78

89
// Mock Firebase
910
vi.mock('firebase/database', () => ({
10-
getDatabase: vi.fn(),
1111
ref: vi.fn(),
1212
update: vi.fn(),
1313
}));
1414

15+
vi.mock('../utils/firebaseDatabase', () => ({
16+
getFirebaseDatabase: vi.fn(),
17+
}));
18+
1519
vi.mock('../base', () => ({
1620
app: {},
1721
}));
@@ -36,7 +40,7 @@ describe('useToggleFavoriteMutation', () => {
3640

3741
mockDatabase = {};
3842
mockRef = {};
39-
vi.mocked(getDatabase).mockReturnValue(mockDatabase);
43+
vi.mocked(getFirebaseDatabase).mockReturnValue(mockDatabase);
4044
vi.mocked(ref).mockReturnValue(mockRef);
4145
vi.mocked(dbUpdate).mockResolvedValue(undefined);
4246
});
@@ -456,7 +460,7 @@ describe('useToggleFavoriteMutation', () => {
456460

457461
// Should only call dbUpdate, no other Firebase operations
458462
expect(vi.mocked(dbUpdate)).toHaveBeenCalledTimes(1);
459-
expect(vi.mocked(getDatabase)).toHaveBeenCalledTimes(1);
463+
expect(vi.mocked(getFirebaseDatabase)).toHaveBeenCalledTimes(1);
460464
expect(vi.mocked(ref)).toHaveBeenCalledTimes(1);
461465
});
462466
});

0 commit comments

Comments
 (0)