UNPKG

@mvp-factory/holy-auth-firebase

Version:

Firebase Authentication module with Google Sign-In support

270 lines (269 loc) 9.59 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FirebaseAuthManager = void 0; const app_1 = require("firebase/app"); const auth_1 = require("firebase/auth"); const FirebaseAuth_1 = require("../types/FirebaseAuth"); class FirebaseAuthManager { constructor(options = {}) { this.app = null; this.auth = null; this.config = null; this.eventListeners = new Map(); this.authState = { isAuthenticated: false, isLoading: true, user: null, token: null, error: null }; this.unsubscribeAuthStateChanged = null; this.options = { persistence: 'local', autoSync: true, syncEndpoint: '/api/auth/firebase/sync', ...options }; } static getInstance(options) { if (!FirebaseAuthManager.instance) { FirebaseAuthManager.instance = new FirebaseAuthManager(options); } return FirebaseAuthManager.instance; } async initialize(config) { if (this.app) { console.warn('Firebase Auth already initialized'); return; } try { this.config = config; this.app = (0, app_1.initializeApp)(config); this.auth = (0, auth_1.getAuth)(this.app); await this.setPersistenceMode(); this.setupAuthStateListener(); console.log('✅ Firebase Auth initialized successfully'); } catch (error) { console.error('❌ Firebase Auth initialization failed:', error); throw this.createAuthError(error); } } async setPersistenceMode() { if (!this.auth) return; const persistenceMap = { local: auth_1.browserLocalPersistence, session: auth_1.browserSessionPersistence, none: auth_1.inMemoryPersistence }; const persistence = persistenceMap[this.options.persistence || 'local']; await (0, auth_1.setPersistence)(this.auth, persistence); } setupAuthStateListener() { if (!this.auth) return; this.unsubscribeAuthStateChanged = (0, auth_1.onAuthStateChanged)(this.auth, async (firebaseUser) => { if (firebaseUser) { const user = await this.mapFirebaseUser(firebaseUser); const token = await firebaseUser.getIdToken(); this.updateAuthState({ isAuthenticated: true, isLoading: false, user, token, error: null }); if (this.options.autoSync) { await this.syncWithBackend(user, token); } this.emit(FirebaseAuth_1.AuthEvent.AUTH_STATE_CHANGED, { user, token }); } else { this.updateAuthState({ isAuthenticated: false, isLoading: false, user: null, token: null, error: null }); this.emit(FirebaseAuth_1.AuthEvent.AUTH_STATE_CHANGED, { user: null, token: null }); } }); } async signInWithGoogle(customParameters) { if (!this.auth) { throw new Error('Firebase Auth not initialized'); } try { const provider = new auth_1.GoogleAuthProvider(); if (customParameters || this.options.customParameters) { const params = { ...this.options.customParameters, ...customParameters }; Object.entries(params).forEach(([key, value]) => { provider.setCustomParameters({ [key]: value }); }); } if (this.options.scopes) { this.options.scopes.forEach(scope => { provider.addScope(scope); }); } const result = await (0, auth_1.signInWithPopup)(this.auth, provider); const user = await this.mapFirebaseUser(result.user); const token = await result.user.getIdToken(); const signInResult = { user, token, credential: result.credential, additionalUserInfo: result.additionalUserInfo }; this.emit(FirebaseAuth_1.AuthEvent.SIGN_IN, signInResult); return signInResult; } catch (error) { const authError = this.createAuthError(error); this.updateAuthState({ ...this.authState, error: authError }); throw authError; } } async signOut() { if (!this.auth) { throw new Error('Firebase Auth not initialized'); } try { await (0, auth_1.signOut)(this.auth); this.clearLocalStorage(); this.emit(FirebaseAuth_1.AuthEvent.SIGN_OUT, null); } catch (error) { throw this.createAuthError(error); } } getCurrentUser() { return this.authState.user; } getAuthState() { return { ...this.authState }; } async getIdToken(forceRefresh = false) { if (!this.auth?.currentUser) { return null; } try { const token = await this.auth.currentUser.getIdToken(forceRefresh); if (forceRefresh) { this.updateAuthState({ ...this.authState, token }); this.emit(FirebaseAuth_1.AuthEvent.TOKEN_REFRESH, { token }); } return token; } catch (error) { throw this.createAuthError(error); } } isAuthenticated() { return this.authState.isAuthenticated; } async syncWithBackend(user, token) { if (!this.options.syncEndpoint) return; try { const syncData = { uid: user.uid, email: user.email, displayName: user.displayName, photoURL: user.photoURL, emailVerified: user.emailVerified, phoneNumber: user.phoneNumber, providerId: user.providerId }; const response = await fetch(this.options.syncEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify(syncData), credentials: 'include' }); if (!response.ok) { throw new Error(`Sync failed with status: ${response.status}`); } const result = await response.json(); if (!result.success) { throw new Error(result.error || 'Sync failed'); } console.log('✅ Backend session synchronized successfully'); } catch (error) { console.error('❌ Backend sync failed:', error); } } async mapFirebaseUser(firebaseUser) { return { uid: firebaseUser.uid, email: firebaseUser.email, displayName: firebaseUser.displayName, photoURL: firebaseUser.photoURL, emailVerified: firebaseUser.emailVerified, phoneNumber: firebaseUser.phoneNumber, isAnonymous: firebaseUser.isAnonymous, providerId: firebaseUser.providerId, metadata: { creationTime: firebaseUser.metadata.creationTime, lastSignInTime: firebaseUser.metadata.lastSignInTime } }; } updateAuthState(newState) { this.authState = newState; if (this.options.persistence === 'local' && newState.user) { localStorage.setItem('firebase_id_token', newState.token || ''); localStorage.setItem('user_email', newState.user.email || ''); localStorage.setItem('user_uid', newState.user.uid); } } clearLocalStorage() { localStorage.removeItem('firebase_id_token'); localStorage.removeItem('user_email'); localStorage.removeItem('user_uid'); } createAuthError(error) { const authError = new Error(error.message || 'Authentication error'); authError.code = error.code || 'unknown'; authError.customData = error.customData; return authError; } on(event, listener) { if (!this.eventListeners.has(event)) { this.eventListeners.set(event, new Set()); } this.eventListeners.get(event).add(listener); return () => { this.eventListeners.get(event)?.delete(listener); }; } off(event, listener) { this.eventListeners.get(event)?.delete(listener); } emit(event, data) { this.eventListeners.get(event)?.forEach(listener => { try { listener(event, data); } catch (error) { console.error(`Error in auth event listener for ${event}:`, error); } }); } destroy() { if (this.unsubscribeAuthStateChanged) { this.unsubscribeAuthStateChanged(); } this.eventListeners.clear(); this.auth = null; this.app = null; FirebaseAuthManager.instance = null; } } exports.FirebaseAuthManager = FirebaseAuthManager;