import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:fcs/domain/constants.dart'; import 'package:fcs/domain/entities/auth_result.dart' as fcs; import 'package:fcs/domain/entities/auth_status.dart'; import 'package:fcs/domain/entities/setting.dart'; import 'package:fcs/domain/entities/user.dart'; import 'package:fcs/domain/exceiptions/signin_exception.dart'; import 'package:fcs/helpers/api_helper.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:logging/logging.dart'; class AuthFb { final log = Logger('AuthFb'); static final AuthFb instance = AuthFb._(); AuthFb._(); StreamController controller; static final FirebaseAuth _fb = FirebaseAuth.instance; static String _verificationId; Future sendSmsCodeToPhoneNumber(String phoneNumber) { Completer completer = Completer(); bool codeSentCompleted = false; final PhoneVerificationCompleted verificationCompleted = (AuthCredential credential) async { AuthResult _authResult; try { _authResult = await _fb.signInWithCredential(credential); print("PhoneVerificationCompleted :$_authResult"); if (_authResult == null) { throw SigninException("Sigin error!"); } } catch (e) { print("Exception:$e"); // throw e; completer.completeError(SigninException(e.toString())); return; } fcs.AuthResult auth = fcs.AuthResult(authStatus: AuthStatus.AUTH_VERIFIED); completer.complete(auth); print( 'Inside _sendCodeToPhoneNumber: signInWithPhoneNumber auto succeeded: ${_authResult.user}'); }; final PhoneVerificationFailed verificationFailed = (AuthException authException) async { print( 'Phone number verification failed. Code: ${authException.code}. Message: ${authException.message}'); completer.completeError(SigninException( "Phone number verification failed:${authException.message}")); }; final PhoneCodeSent codeSent = (String verificationId, [int forceResendingToken]) async { _verificationId = verificationId; print("codeSent " + phoneNumber); codeSentCompleted = true; completer.complete(fcs.AuthResult(authStatus: AuthStatus.SMS_SENT)); }; final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout = (String verificationId) { print("codeAutoRetrievalTimeout $verificationId "); _verificationId = verificationId; if (codeSentCompleted) { completer.complete(fcs.AuthResult(authStatus: AuthStatus.SMS_SENT)); } else { completer.completeError(SigninException("SMS code failed")); } }; _fb.verifyPhoneNumber( phoneNumber: phoneNumber, timeout: const Duration(seconds: 0), verificationCompleted: verificationCompleted, verificationFailed: verificationFailed, codeSent: codeSent, codeAutoRetrievalTimeout: codeAutoRetrievalTimeout); return completer.future; } Future signInWithPhoneNumber(String smsCode) async { User user; try { final AuthCredential credential = PhoneAuthProvider.getCredential( verificationId: _verificationId, smsCode: smsCode, ); AuthResult _authResult = await _fb.signInWithCredential(credential); if (_authResult == null) { throw SigninException("Sigin error!"); } await _addUserToStream(refreshIdToken: true); } on Exception catch (e) { return Future.error(SigninException(e.toString())); } if (user == null) Future.error(SigninException("No current user!")); return Future.value(fcs.AuthResult(authStatus: AuthStatus.AUTH_VERIFIED)); } Future signout() async { if (userListener != null) await userListener.cancel(); return _fb.signOut(); } Future _addUserToStream({bool refreshIdToken = false}) async { FirebaseUser firebaseUser = await _fb.currentUser(); if (firebaseUser == null) return null; IdTokenResult idToken = await firebaseUser.getIdToken(refresh: refreshIdToken); log.info("Claims:${idToken.claims}"); String cid = idToken.claims["cid"]; User user; if (cid != null && cid != "") { user = await _getUserFromFirestore(cid); } if (user == null) { controller.add(null); return; } // add privileges String privileges = idToken.claims["pr"]; if (privileges != null && privileges != "") { user.privileges = privileges.split(":").toList(); } controller.add(user); } Future _getUserFromFirestore(String userID) async { DocumentSnapshot snap = await Firestore.instance .collection(user_collection) .document(userID) .get(); if (snap.exists) { User user = User.fromMap(snap.data, snap.documentID); return user; } return null; } Future isLogin() async { final FirebaseUser firebaseUser = await _fb.currentUser(); return Future.value(firebaseUser != null); } Future signup(String userName) async { await requestAPI("/signup", "POST", payload: { 'user_name': userName, }, token: await getToken()); await _addUserToStream(refreshIdToken: true); _startUserListener(); return; } Future joinInvite(String userName) async { await requestAPI("/join_invite", "POST", payload: { 'user_name': userName, }, token: await getToken()); // refresh token once signup await _addUserToStream(refreshIdToken: true); _startUserListener(); return; } Future hasInvite() async { var invited = await requestAPI("/check_invitation", "GET", token: await getToken()); return invited["invited"]; } Future updateProfileName(String newUserName) async { return await requestAPI("/profile", "PUT", payload: {"user_name": newUserName}, token: await getToken()); } Future updatePreferredCurrency(String currency) async { return await requestAPI("/currency", "PUT", payload: {"preferred_currency": currency}, token: await getToken()); } Future getToken() async { FirebaseUser firebaseUser = await _fb.currentUser(); IdTokenResult token = await firebaseUser.getIdToken(); return token.token; } Future getSetting() async { var snap = await Firestore.instance .collection(config_collection) .document(setting_doc_id) .get(); if (!snap.exists) { return null; } // _listSetting(); return Setting.fromMap(snap.data); } Stream settings() async* { Stream snapshot = Firestore.instance .collection(config_collection) .document(setting_doc_id) .snapshots(); await for (var snap in snapshot) { Setting setting = Setting.fromMap(snap.data); yield setting; } } Future _getCurrentUserID() async { FirebaseUser firebaseUser = await _fb.currentUser(); if (firebaseUser == null) return null; IdTokenResult idToken = await firebaseUser.getIdToken(); String cid = idToken.claims["cid"]; return cid; } Future _startUserListener() async { if (userListener != null) userListener.cancel(); String _userID = await _getCurrentUserID(); if (_userID == null) { return; } Stream snapshot = Firestore.instance .collection(user_collection) .document(_userID) .snapshots(); userListener = snapshot.listen((snap) async { User user = User.fromMap(snap.data, snap.documentID); FirebaseUser firebaseUser = await _fb.currentUser(); if (firebaseUser == null) { userListener.cancel(); return; } // get privilege from claim IdTokenResult idToken = await firebaseUser.getIdToken(refresh: true); String privileges = idToken.claims["pr"]; if (privileges != null && privileges != "") { user.privileges = privileges.split(":").toList(); } controller.add(user); }); } StreamSubscription userListener; Stream user() { // ignore: close_sinks StreamSubscription authListener; Future _start() async { authListener = _fb.onAuthStateChanged.listen((firebaseUser) async { if (firebaseUser == null) { controller.add(null); } else { _addUserToStream(refreshIdToken: true); _startUserListener(); } }); } void _stop() { if (userListener != null) { userListener.cancel(); } if (authListener != null) { authListener.cancel(); } } controller = StreamController( onListen: _start, onPause: _stop, onResume: _start, onCancel: _stop); return controller.stream; } }