cyberhybridhub/lib/services/auth_service_firebase.dart
2026-05-20 10:22:58 -05:00

136 lines
3.8 KiB
Dart

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<AppUser?> get authStateChanges =>
_firebaseAuth.authStateChanges().map(_mapUser);
AppUser? get currentUser => _mapUser(_firebaseAuth.currentUser);
Future<void> 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<void> 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<void> _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<void> signOut() async {
if (_googleSignInReady) {
await _googleSignIn.signOut();
}
await _firebaseAuth.signOut();
}
Future<String?> 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,
);
}
}