import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:google_sign_in/google_sign_in.dart'; import '../config/auth_config.dart'; import '../models/app_user.dart'; class AuthServiceFirebase { AuthServiceFirebase._(); static final AuthServiceFirebase instance = AuthServiceFirebase._(); final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; final GoogleSignIn _googleSignIn = GoogleSignIn.instance; bool _googleSignInReady = false; Stream get authStateChanges => _firebaseAuth.authStateChanges().map(_mapUser); AppUser? get currentUser => _mapUser(_firebaseAuth.currentUser); Future initialize() async { if (kIsWeb) { if (googleWebOAuthClientId != null) { await _googleSignIn.initialize(clientId: googleWebOAuthClientId); _googleSignInReady = true; } return; } if (googleWebOAuthClientId != null) { await _googleSignIn.initialize( serverClientId: googleWebOAuthClientId, ); } else { await _googleSignIn.initialize(); } _googleSignInReady = true; } Future signInWithGoogle() async { if (kIsWeb) { await _signInWithGoogleWeb(); return; } _ensureGoogleSignInReady(); final GoogleSignInAccount account = await _googleSignIn.authenticate(); final String? idToken = account.authentication.idToken; if (idToken == null) { throw StateError('Google Sign-In did not return an ID token.'); } final credential = GoogleAuthProvider.credential(idToken: idToken); await _firebaseAuth.signInWithCredential(credential); } /// Web: popup avoids Firebase redirect sessionStorage (broken under ETP). /// Falls back to Google Identity Services + [signInWithCredential] when set up. Future _signInWithGoogleWeb() async { try { await _firebaseAuth.signInWithPopup(GoogleAuthProvider()); return; } on FirebaseAuthException catch (e) { if (_isUserCancelledAuth(e)) { rethrow; } } if (!_googleSignInReady) { throw StateError( 'Google sign-in failed in this browser (often Firefox with strict ' 'privacy). Set googleWebOAuthClientId in lib/config/auth_config.dart ' '(Firebase Console → Authentication → Google → Web SDK configuration) ' 'and add ${Uri.base.origin} to that client\'s authorized JavaScript ' 'origins in Google Cloud Console.', ); } final GoogleSignInAccount account = await _googleSignIn.authenticate(); final String? idToken = account.authentication.idToken; if (idToken == null) { throw StateError('Google Sign-In did not return an ID token.'); } await _firebaseAuth.signInWithCredential( GoogleAuthProvider.credential(idToken: idToken), ); } Future signOut() async { if (_googleSignInReady) { await _googleSignIn.signOut(); } await _firebaseAuth.signOut(); } Future getIdToken({bool forceRefresh = false}) async { final User? user = _firebaseAuth.currentUser; if (user == null) { return null; } return user.getIdToken(forceRefresh); } void _ensureGoogleSignInReady() { if (_googleSignInReady) { return; } throw StateError( 'Google Sign-In is not configured. Set googleWebOAuthClientId in ' 'lib/config/auth_config.dart for Android, or run a full restart after ' 'changing auth settings.', ); } bool _isUserCancelledAuth(FirebaseAuthException e) { return e.code == 'popup-closed-by-user' || e.code == 'cancelled-popup-request'; } AppUser? _mapUser(User? user) { if (user == null) { return null; } return AppUser( uid: user.uid, email: user.email, displayName: user.displayName, photoUrl: user.photoURL, ); } }