import { initializeApp } from 'firebase/app'; import { getAuth, signInWithPopup, GoogleAuthProvider, onAuthStateChanged, User, signOut } from 'firebase/auth'; export type { User }; import { getFirestore, doc, getDocFromServer, collection, addDoc, setDoc, getDocs, onSnapshot } from 'firebase/firestore'; import firebaseConfig from '../firebase-applet-config.json'; // Initialize Firebase const app = initializeApp(firebaseConfig); // Initialize Firestore with explicit databaseId from config export const db = getFirestore(app, firebaseConfig.firestoreDatabaseId); // Initialize Auth export const auth = getAuth(app); // Google Workspace Scopes export const WORKSPACE_SCOPES = [ 'https://www.googleapis.com/auth/drive.file', 'https://www.googleapis.com/auth/drive', 'https://www.googleapis.com/auth/drive.readonly', 'https://www.googleapis.com/auth/spreadsheets', 'https://www.googleapis.com/auth/spreadsheets.readonly', 'https://mail.google.com/', 'https://www.googleapis.com/auth/gmail.compose', 'https://www.googleapis.com/auth/gmail.modify', 'https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.send', 'https://www.googleapis.com/auth/calendar', 'https://www.googleapis.com/auth/calendar.events', 'https://www.googleapis.com/auth/calendar.readonly', 'https://www.googleapis.com/auth/documents', 'https://www.googleapis.com/auth/documents.readonly', 'https://www.googleapis.com/auth/chat.messages', 'https://www.googleapis.com/auth/chat.messages.create', 'https://www.googleapis.com/auth/chat.spaces', 'https://www.googleapis.com/auth/chat.spaces.readonly', 'https://www.googleapis.com/auth/meetings.space.created', 'https://www.googleapis.com/auth/meetings.space.readonly' ]; const provider = new GoogleAuthProvider(); WORKSPACE_SCOPES.forEach(scope => provider.addScope(scope)); // In-memory token management (strictly not stored in localStorage) let isSigningIn = false; let cachedAccessToken: string | null = null; // Auth listener export const initAuth = ( onAuthSuccess?: (user: User, token: string) => void, onAuthFailure?: () => void ) => { return onAuthStateChanged(auth, async (user: User | null) => { if (user) { if (cachedAccessToken) { if (onAuthSuccess) onAuthSuccess(user, cachedAccessToken); } else if (!isSigningIn) { cachedAccessToken = null; if (onAuthFailure) onAuthFailure(); } } else { cachedAccessToken = null; if (onAuthFailure) onAuthFailure(); } }); }; // Sign in with Google Popup export const googleSignIn = async (): Promise<{ user: User; accessToken: string } | null> => { try { isSigningIn = true; const result = await signInWithPopup(auth, provider); const credential = GoogleAuthProvider.credentialFromResult(result); if (!credential?.accessToken) { throw new Error('Failed to obtain Google Workspace access token.'); } cachedAccessToken = credential.accessToken; return { user: result.user, accessToken: cachedAccessToken }; } catch (error: any) { console.error('Google Sign-in Error:', error); throw error; } finally { isSigningIn = false; } }; export const getAccessToken = (): string | null => { return cachedAccessToken; }; export const logout = async () => { await signOut(auth); cachedAccessToken = null; }; // Error handling standard required by Firebase skill export enum OperationType { CREATE = 'create', UPDATE = 'update', DELETE = 'delete', LIST = 'list', GET = 'get', WRITE = 'write', } export interface FirestoreErrorInfo { error: string; operationType: OperationType; path: string | null; authInfo: { userId?: string | null; email?: string | null; emailVerified?: boolean | null; isAnonymous?: boolean | null; tenantId?: string | null; providerInfo?: { providerId?: string | null; email?: string | null; }[]; }; } export function handleFirestoreError(error: unknown, operationType: OperationType, path: string | null): never { const errInfo: FirestoreErrorInfo = { error: error instanceof Error ? error.message : String(error), authInfo: { userId: auth.currentUser?.uid, email: auth.currentUser?.email, emailVerified: auth.currentUser?.emailVerified, isAnonymous: auth.currentUser?.isAnonymous, tenantId: auth.currentUser?.tenantId, providerInfo: auth.currentUser?.providerData?.map(provider => ({ providerId: provider.providerId, email: provider.email, })) || [] }, operationType, path }; console.error('Firestore Error: ', JSON.stringify(errInfo)); throw new Error(JSON.stringify(errInfo)); } // Test connection on app load export async function testFirestoreConnection() { try { await getDocFromServer(doc(db, 'test', 'connection')); } catch (error: any) { if (error instanceof Error && error.message.includes('the client is offline')) { console.warn("Firestore client appears offline. Please check network connection."); } } } testFirestoreConnection();