tistru / frontend /src /lib /firebase.ts
dev-scezui's picture
Refactor Dockerfile and frontend to enhance Firebase configuration handling. Update build process to utilize runtime secrets for Firebase variables, ensuring proper integration and dynamic loading in the Next.js application. Modify admin dashboard and other components to accommodate new configuration logic.
05e7451
Raw History Blame Contribute Delete
3.05 kB
import { initializeApp, getApps, type FirebaseApp } from "firebase/app";
import {
getAuth,
GoogleAuthProvider,
signInWithPopup,
type Auth,
type UserCredential,
} from "firebase/auth";
import { API_URL } from "./api";
import { publicEnv } from "./publicEnv";
function firebaseConfig() {
return {
apiKey: publicEnv("NEXT_PUBLIC_FIREBASE_API_KEY"),
authDomain: publicEnv("NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN"),
projectId: publicEnv("NEXT_PUBLIC_FIREBASE_PROJECT_ID"),
appId: publicEnv("NEXT_PUBLIC_FIREBASE_APP_ID"),
};
}
export function isFirebaseConfigured(): boolean {
const cfg = firebaseConfig();
return Boolean(cfg.apiKey && cfg.authDomain && cfg.projectId);
}
let app: FirebaseApp | undefined;
let auth: Auth | undefined;
let googleProvider: GoogleAuthProvider | undefined;
export function getFirebaseAuth(): Auth {
if (!isFirebaseConfigured()) {
throw new Error("Firebase web config is missing");
}
if (!app) {
app = getApps()[0] ?? initializeApp(firebaseConfig());
}
if (!auth) {
auth = getAuth(app);
}
return auth;
}
function getGoogleProvider(): GoogleAuthProvider {
if (!googleProvider) {
googleProvider = new GoogleAuthProvider();
googleProvider.setCustomParameters({ prompt: "select_account" });
}
return googleProvider;
}
const USER_CANCELLED_AUTH_CODES = new Set([
"auth/popup-closed-by-user",
"auth/cancelled-popup-request",
]);
export function getFirebaseAuthCode(err: unknown): string | null {
if (typeof err === "object" && err !== null && "code" in err) {
const code = (err as { code: unknown }).code;
return typeof code === "string" ? code : null;
}
return null;
}
/** True when the user dismissed the sign-in UI — not a real failure. */
export function isAuthCancelled(err: unknown): boolean {
const code = getFirebaseAuthCode(err);
return code !== null && USER_CANCELLED_AUTH_CODES.has(code);
}
/**
* Message safe to show in the UI. Empty string means show nothing
* (user cancelled). Never returns raw Firebase error text.
*/
export function friendlyAuthError(err: unknown, fallback: string): string {
if (isAuthCancelled(err)) return "";
return fallback;
}
/** Fire-and-forget: log auth issues on the API server, never surface to the UI. */
export function reportAuthError(err: unknown, event: string): void {
if (typeof window === "undefined") return;
const code = getFirebaseAuthCode(err);
const detail =
err instanceof Error
? err.message.slice(0, 300)
: typeof err === "string"
? err.slice(0, 300)
: "unknown";
const body = JSON.stringify({
event,
code,
detail,
cancelled: isAuthCancelled(err),
});
void fetch(`${API_URL}/api/client-log`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
keepalive: true,
}).catch(() => undefined);
}
export async function signInWithGoogle(): Promise<UserCredential> {
const authClient = getFirebaseAuth();
return signInWithPopup(authClient, getGoogleProvider());
}