diff --git a/packages/wyatt_authentication_bloc/lib/src/data/repositories/authentication_repository_impl.dart b/packages/wyatt_authentication_bloc/lib/src/data/repositories/authentication_repository_impl.dart index 287b4171..67648935 100644 --- a/packages/wyatt_authentication_bloc/lib/src/data/repositories/authentication_repository_impl.dart +++ b/packages/wyatt_authentication_bloc/lib/src/data/repositories/authentication_repository_impl.dart @@ -16,7 +16,6 @@ import 'dart:async'; -import 'package:rxdart/rxdart.dart'; import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_authentication_bloc/src/core/constants/form_field.dart'; import 'package:wyatt_authentication_bloc/src/core/constants/form_name.dart'; @@ -25,37 +24,25 @@ import 'package:wyatt_authentication_bloc/src/domain/data_sources/local/authenti import 'package:wyatt_authentication_bloc/src/domain/data_sources/remote/authentication_remote_data_source.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/account_wrapper.dart'; +import 'package:wyatt_authentication_bloc/src/domain/entities/auth_change_event.dart'; import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; import 'package:wyatt_type_utils/wyatt_type_utils.dart'; -typedef OnSignUpSuccess = FutureOrResult Function( - AuthenticationRepository repo, - Account? account, - WyattForm form, -); - -typedef OnAuthChange = FutureOrResult Function( - AuthenticationRepository repo, - Account? account, -); - class AuthenticationRepositoryImpl extends AuthenticationRepository { AuthenticationRepositoryImpl({ - required AuthenticationCacheDataSource authenticationCacheDataSource, - required AuthenticationRemoteDataSource authenticationRemoteDataSource, + required this.authenticationCacheDataSource, + required this.authenticationRemoteDataSource, FormRepository? formRepository, // ignore: strict_raw_type List? extraSignUpInputs, FormInputValidator? customEmailValidator, FormInputValidator? customPasswordValidator, - OnSignUpSuccess? onSignUpSuccess, - OnAuthChange? onAuthChange, - }) : _authenticationLocalDataSource = authenticationCacheDataSource, - _authenticationRemoteDataSource = authenticationRemoteDataSource, - _onSignUpSuccess = onSignUpSuccess, - _onAccountChanges = onAuthChange { + AuthChangeListener? onAuthChange, + AccountStreamTransformer? accountStreamTransformer, + }) : _authChangeListener = onAuthChange, + _accountStreamTransformer = accountStreamTransformer { _formRepository = formRepository ?? FormRepositoryImpl(); if (formRepository != null) { return; @@ -103,23 +90,34 @@ class AuthenticationRepositoryImpl name: AuthFormName.passwordResetForm, ), ); + _accountStreamTransformer ??= (input) => input + .where((event) => event is! SignUpAuthChangeEvent) + .map>>((event) async { + if (listener == null) { + return Ok(AccountWrapperModel(event.account, null)); + } + // Handle sign in, sign out and refresh + final dataResult = await listener!.call(this, event); + return dataResult.map((data) { + authenticationCacheDataSource.storeData(data); + return AccountWrapperModel(event.account, data); + }); + }); } - final AuthenticationCacheDataSource _authenticationLocalDataSource; - final AuthenticationRemoteDataSource _authenticationRemoteDataSource; + final AuthenticationCacheDataSource authenticationCacheDataSource; + final AuthenticationRemoteDataSource authenticationRemoteDataSource; late FormRepository _formRepository; - final OnSignUpSuccess? _onSignUpSuccess; - - final OnAuthChange? _onAccountChanges; - final StreamController>> _signUpStream = - StreamController(); - - bool _pause = false; + AuthChangeListener? _authChangeListener; + AccountStreamTransformer? _accountStreamTransformer; @override FormRepository get formRepository => _formRepository; + @override + AuthChangeListener? get listener => _authChangeListener; + @override FutureOrResult signInWithEmailAndPassword({ required String email, @@ -128,11 +126,11 @@ class AuthenticationRepositoryImpl Result.tryCatchAsync( () async { final account = - await _authenticationRemoteDataSource.signInWithEmailAndPassword( + await authenticationRemoteDataSource.signInWithEmailAndPassword( email: email, password: password, ); - await _authenticationLocalDataSource.storeAccount(account); + await authenticationCacheDataSource.storeAccount(account); return account; }, (error) => error, @@ -142,8 +140,8 @@ class AuthenticationRepositoryImpl FutureOrResult signOut() => Result.tryCatchAsync( () async { - await _authenticationRemoteDataSource.signOut(); - await _authenticationLocalDataSource.destroy(); + await authenticationRemoteDataSource.signOut(); + await authenticationCacheDataSource.destroy(); }, (error) => error, ); @@ -155,54 +153,34 @@ class AuthenticationRepositoryImpl }) => Result.tryCatchAsync( () async { - _pause = true; - final account = await _authenticationRemoteDataSource.signUp( + final account = await authenticationRemoteDataSource.signUp( email: email, password: password, ); - await _authenticationLocalDataSource.storeAccount(account); - if (_onSignUpSuccess.isNotNull) { - final dataResult = await _onSignUpSuccess!.call( - this, - account, - _formRepository.accessForm(AuthFormName.signUpForm).clone(), - ); - await dataResult.foldAsync( - (data) async { - await _authenticationLocalDataSource.storeData(data); - _signUpStream - .add(Future.value(Ok(AccountWrapperModel(account, data)))); - }, - (error) async => error, - ); - } - _pause = false; + await authenticationCacheDataSource.storeAccount(account); return account; }, - (error) { - _pause = false; - return error; - }, + (error) => error, ); @override FutureOrResult destroyCache() => Result.tryCatchAsync( - _authenticationLocalDataSource.destroy, + authenticationCacheDataSource.destroy, (error) => error, ); @override FutureOrResult> getCache() => Result.tryCatchAsync, AppException, AppException>( - _authenticationLocalDataSource.load, + authenticationCacheDataSource.load, (error) => error, ); @override FutureOrResult getAccount() => Result.tryCatchAsync( - _authenticationLocalDataSource.loadAccount, + authenticationCacheDataSource.loadAccount, (error) => error, ); @@ -212,7 +190,7 @@ class AuthenticationRepositoryImpl ) => Result.tryCatchAsync( () async { - await _authenticationLocalDataSource.storeAccount(account); + await authenticationCacheDataSource.storeAccount(account); }, (error) => error, ); @@ -220,7 +198,7 @@ class AuthenticationRepositoryImpl @override FutureOrResult getData() => Result.tryCatchAsync( - _authenticationLocalDataSource.loadData, + authenticationCacheDataSource.loadData, (error) => error, ); @@ -230,7 +208,7 @@ class AuthenticationRepositoryImpl ) => Result.tryCatchAsync( () async { - await _authenticationLocalDataSource.storeData(data); + await authenticationCacheDataSource.storeData(data); }, (error) => error, ); @@ -238,26 +216,13 @@ class AuthenticationRepositoryImpl @override FutureOrResult getIdentityToken() => Result.tryCatchAsync( - _authenticationRemoteDataSource.getIdentityToken, + authenticationRemoteDataSource.getIdentityToken, (error) => error, ); @override - Stream>> streamAccount() => MergeStream([ - _signUpStream.stream.asBroadcastStream(), - _authenticationRemoteDataSource.streamAccount().map((account) async { - if (_onAccountChanges.isNotNull && !_pause) { - final dataResult = await _onAccountChanges!.call(this, account); - return dataResult.map((data) { - _authenticationLocalDataSource.storeData(data); - return AccountWrapperModel(account, data); - }); - } - return Ok, AppException>( - AccountWrapperModel(account, null), - ); - }) - ]); + Stream>> streamAccount() => + _accountStreamTransformer!.call(changes()); @override FutureOrResult confirmPasswordReset({ @@ -266,7 +231,7 @@ class AuthenticationRepositoryImpl }) => Result.tryCatchAsync( () async { - await _authenticationRemoteDataSource.confirmPasswordReset( + await authenticationRemoteDataSource.confirmPasswordReset( code: code, newPassword: newPassword, ); @@ -278,7 +243,7 @@ class AuthenticationRepositoryImpl FutureOrResult sendEmailVerification() => Result.tryCatchAsync( () async { - await _authenticationRemoteDataSource.sendEmailVerification(); + await authenticationRemoteDataSource.sendEmailVerification(); }, (error) => error, ); @@ -287,7 +252,7 @@ class AuthenticationRepositoryImpl FutureOrResult sendPasswordResetEmail({required String email}) => Result.tryCatchAsync( () async { - await _authenticationRemoteDataSource.sendPasswordResetEmail( + await authenticationRemoteDataSource.sendPasswordResetEmail( email: email, ); }, @@ -299,7 +264,7 @@ class AuthenticationRepositoryImpl Result.tryCatchAsync( () async { final account = - await _authenticationRemoteDataSource.signInAnonymously(); + await authenticationRemoteDataSource.signInAnonymously(); return account; }, (error) => error, @@ -310,7 +275,7 @@ class AuthenticationRepositoryImpl Result.tryCatchAsync( () async { final account = - await _authenticationRemoteDataSource.signInWithGoogle(); + await authenticationRemoteDataSource.signInWithGoogle(); return account; }, (error) => error, @@ -320,7 +285,7 @@ class AuthenticationRepositoryImpl FutureOrResult verifyPasswordResetCode({required String code}) => Result.tryCatchAsync( () async { - final response = await _authenticationRemoteDataSource + final response = await authenticationRemoteDataSource .verifyPasswordResetCode(code: code); return response; }, @@ -331,7 +296,7 @@ class AuthenticationRepositoryImpl FutureOrResult refresh() => Result.tryCatchAsync( () async { - await _authenticationRemoteDataSource.refresh(); + await authenticationRemoteDataSource.refresh(); }, (error) => error, ); @@ -340,7 +305,7 @@ class AuthenticationRepositoryImpl FutureOrResult reauthenticateWithCredential() => Result.tryCatchAsync( () async { - final account = await _authenticationRemoteDataSource + final account = await authenticationRemoteDataSource .reauthenticateWithCredential(); return account; }, @@ -352,7 +317,7 @@ class AuthenticationRepositoryImpl Result.tryCatchAsync( () async { final account = - await _authenticationRemoteDataSource.updateEmail(email: email); + await authenticationRemoteDataSource.updateEmail(email: email); return account; }, (error) => error, @@ -362,11 +327,52 @@ class AuthenticationRepositoryImpl FutureOrResult updatePassword({required String password}) => Result.tryCatchAsync( () async { - final account = await _authenticationRemoteDataSource.updatePassword( + final account = await authenticationRemoteDataSource.updatePassword( password: password, ); return account; }, (error) => error, ); + + Account? _lastChange; + + @override + Stream changes() => authenticationRemoteDataSource + .streamAccount() + .map((account) { + if (_lastChange != null && account == null) { + _lastChange = null; + return SignOutAuthChangeEvent(); + } + if (_lastChange == null && account != null) { + _lastChange = account; + if (account.isNewUser ?? false) { + return SignUpAuthChangeEvent(account); + } + return SignInAuthChangeEvent(account); + } + if (_lastChange != null && account != null) { + _lastChange = account; + return RefreshAuthChangeEvent(account); + } + if (_lastChange == null && account == null) { + _lastChange = account; + return StillUnauthenticatedAuthChangeEvent(); + } + _lastChange = account; + return RefreshAuthChangeEvent(account); + }); + + @override + FutureOrResult addAuthChangeListener(AuthChangeListener listener) { + _authChangeListener = listener; + return const Ok(null); + } + + @override + FutureOrResult removeAuthChangeListener() { + _authChangeListener = null; + return const Ok(null); + } } diff --git a/packages/wyatt_authentication_bloc/lib/src/domain/entities/auth_change_event.dart b/packages/wyatt_authentication_bloc/lib/src/domain/entities/auth_change_event.dart new file mode 100644 index 00000000..71a311a3 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/domain/entities/auth_change_event.dart @@ -0,0 +1,43 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart'; + +abstract class AuthChangeEvent { + AuthChangeEvent(this.account); + + final Account? account; +} + +class SignInAuthChangeEvent extends AuthChangeEvent { + SignInAuthChangeEvent(super.account); +} + +class SignUpAuthChangeEvent extends AuthChangeEvent { + SignUpAuthChangeEvent(super.account); +} + +class RefreshAuthChangeEvent extends AuthChangeEvent { + RefreshAuthChangeEvent(super.account); +} + +class StillUnauthenticatedAuthChangeEvent extends AuthChangeEvent { + StillUnauthenticatedAuthChangeEvent() : super(null); +} + +class SignOutAuthChangeEvent extends AuthChangeEvent { + SignOutAuthChangeEvent() : super(null); +} diff --git a/packages/wyatt_authentication_bloc/lib/src/domain/entities/entities.dart b/packages/wyatt_authentication_bloc/lib/src/domain/entities/entities.dart index e9a858fe..9c83225f 100644 --- a/packages/wyatt_authentication_bloc/lib/src/domain/entities/entities.dart +++ b/packages/wyatt_authentication_bloc/lib/src/domain/entities/entities.dart @@ -16,3 +16,4 @@ export 'account.dart'; export 'account_wrapper.dart'; +export 'auth_change_event.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/domain/repositories/authentication_repository.dart b/packages/wyatt_authentication_bloc/lib/src/domain/repositories/authentication_repository.dart index 027c7525..3e958f78 100644 --- a/packages/wyatt_authentication_bloc/lib/src/domain/repositories/authentication_repository.dart +++ b/packages/wyatt_authentication_bloc/lib/src/domain/repositories/authentication_repository.dart @@ -17,10 +17,28 @@ import 'package:wyatt_architecture/wyatt_architecture.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart'; import 'package:wyatt_authentication_bloc/src/domain/entities/account_wrapper.dart'; +import 'package:wyatt_authentication_bloc/src/domain/entities/auth_change_event.dart'; import 'package:wyatt_form_bloc/wyatt_form_bloc.dart'; +typedef SignUpCallback = FutureOrResult Function( + AuthenticationRepository repo, + Account? account, + WyattForm form, +); + +typedef AuthChangeListener = FutureOrResult Function( + AuthenticationRepository repo, + AuthChangeEvent? authEvent, +); + +typedef AccountStreamTransformer + = Stream>> Function( + Stream input, +); + abstract class AuthenticationRepository extends BaseRepository { FormRepository get formRepository; + AuthChangeListener? get listener; /// {@template signup} /// Creates a new user with the provided [email] and [password]. @@ -141,6 +159,8 @@ abstract class AuthenticationRepository extends BaseRepository { /// {@endtemplate} Stream>> streamAccount(); + Stream changes(); + FutureOrResult getIdentityToken(); FutureOrResult getAccount(); @@ -151,4 +171,7 @@ abstract class AuthenticationRepository extends BaseRepository { FutureOrResult> getCache(); FutureOrResult destroyCache(); + + FutureOrResult addAuthChangeListener(AuthChangeListener listener); + FutureOrResult removeAuthChangeListener(); }