136 lines
3.8 KiB
Dart
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,
|
|
);
|
|
}
|
|
}
|