From d5e093150f34dc437caafc6e29ef40c79763bc99 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 29 Nov 2022 16:24:04 +0700 Subject: [PATCH] TF-439 Implement handle register new token when expired --- .../set/firebase_subscription_set_method.dart | 1 + ...firebase_subscription_set_method_test.dart | 3 +- .../datasource_impl/fcm_datasource_impl.dart | 10 +++ .../hive_fcm_datasource_impl.dart | 6 ++ .../data/network/fcm_api.dart | 25 +++++++ .../data/repository/fcm_repository_impl.dart | 6 ++ .../state/register_new_token_state.dart | 25 +++++++ .../register_new_token_interactor.dart | 24 +++++++ .../bindings/fcm_interactor_bindings.dart | 2 + .../controller/fcm_token_handler.dart | 71 +++++++++++++++++-- 10 files changed, 166 insertions(+), 7 deletions(-) create mode 100644 lib/features/push_notification/domain/state/register_new_token_state.dart create mode 100644 lib/features/push_notification/domain/usecases/register_new_token_interactor.dart diff --git a/fcm/lib/method/set/firebase_subscription_set_method.dart b/fcm/lib/method/set/firebase_subscription_set_method.dart index 9144bb628..4ac4a687a 100644 --- a/fcm/lib/method/set/firebase_subscription_set_method.dart +++ b/fcm/lib/method/set/firebase_subscription_set_method.dart @@ -16,6 +16,7 @@ class FirebaseSubscriptionSetMethod extends SetMethodNoNeedAccountId get requiredCapabilities => { CapabilityIdentifier.jmapCore, + CapabilityIdentifier.jmapMail, FirebaseCapability.fcmIdentifier }; diff --git a/fcm/test/method/firebase_subscription_set_method_test.dart b/fcm/test/method/firebase_subscription_set_method_test.dart index f6db27c7a..bb50212b5 100644 --- a/fcm/test/method/firebase_subscription_set_method_test.dart +++ b/fcm/test/method/firebase_subscription_set_method_test.dart @@ -48,6 +48,7 @@ void main() { data: { "using": [ "urn:ietf:params:jmap:core", + "urn:ietf:params:jmap:mail", "com:linagora:params:jmap:firebase:push" ], "methodCalls": [ @@ -69,7 +70,7 @@ void main() { headers: { "accept": "application/json;jmapVersion=rfc-8621", "content-type": "application/json; charset=utf-8", - "content-length": 225 + "content-length": 253 } ); diff --git a/lib/features/push_notification/data/datasource_impl/fcm_datasource_impl.dart b/lib/features/push_notification/data/datasource_impl/fcm_datasource_impl.dart index 1ee990892..d1d0ffec0 100644 --- a/lib/features/push_notification/data/datasource_impl/fcm_datasource_impl.dart +++ b/lib/features/push_notification/data/datasource_impl/fcm_datasource_impl.dart @@ -4,6 +4,7 @@ import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:model/fcm/fcm_token_dto.dart'; import 'package:tmail_ui_user/features/push_notification/data/datasource/fcm_datasource.dart'; import 'package:tmail_ui_user/features/push_notification/data/network/fcm_api.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/model/register_new_token_request.dart'; import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; class FcmDatasourceImpl extends FCMDatasource { @@ -56,4 +57,13 @@ class FcmDatasourceImpl extends FCMDatasource { Future storeStateToRefresh(TypeName typeName, jmap.State newState) { throw UnimplementedError(); } + + @override + Future registerNewToken(RegisterNewTokenRequest newTokenRequest) { + return Future.sync(() async { + return await _fcmApi.registerNewToken(newTokenRequest); + }).catchError((error) { + _exceptionThrower.throwException(error); + }); + } } \ No newline at end of file diff --git a/lib/features/push_notification/data/datasource_impl/hive_fcm_datasource_impl.dart b/lib/features/push_notification/data/datasource_impl/hive_fcm_datasource_impl.dart index 25f1c8bd6..5339fa5ee 100644 --- a/lib/features/push_notification/data/datasource_impl/hive_fcm_datasource_impl.dart +++ b/lib/features/push_notification/data/datasource_impl/hive_fcm_datasource_impl.dart @@ -4,6 +4,7 @@ import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:model/fcm/fcm_token_dto.dart'; import 'package:tmail_ui_user/features/push_notification/data/datasource/fcm_datasource.dart'; import 'package:tmail_ui_user/features/push_notification/data/local/fcm_cache_manager.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/model/register_new_token_request.dart'; import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; class HiveFCMDatasourceImpl extends FCMDatasource { @@ -80,4 +81,9 @@ class HiveFCMDatasourceImpl extends FCMDatasource { Future getFirebaseSubscriptionByDeviceId(String deviceId) { throw UnimplementedError(); } + + @override + Future registerNewToken(RegisterNewTokenRequest registerNewTokenRequest) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/push_notification/data/network/fcm_api.dart b/lib/features/push_notification/data/network/fcm_api.dart index 4e046760c..1d8d3fc01 100644 --- a/lib/features/push_notification/data/network/fcm_api.dart +++ b/lib/features/push_notification/data/network/fcm_api.dart @@ -1,10 +1,13 @@ +import 'package:fcm/method/set/firebase_subscription_set_method.dart'; +import 'package:fcm/method/set/firebase_subscription_set_response.dart'; import 'package:fcm/method/get/firebase_subscription_get_method.dart'; import 'package:fcm/method/get/firebase_subscription_get_response.dart'; import 'package:fcm/model/firebase_subscription.dart'; import 'package:jmap_dart_client/http/http_client.dart'; import 'package:jmap_dart_client/jmap/jmap_request.dart'; import 'package:tmail_ui_user/features/push_notification/domain/exceptions/fcm_exception.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/model/register_new_token_request.dart'; class FcmApi { @@ -37,4 +40,26 @@ class FcmApi { throw NotFoundFirebaseSubscriptionException(); } } + + Future registerNewToken(RegisterNewTokenRequest newTokenRequest) async { + final processingInvocation = ProcessingInvocation(); + final requestBuilder = JmapRequestBuilder(_httpClient, processingInvocation); + + final firebaseSubscriptionSetMethod = FirebaseSubscriptionSetMethod() + ..addCreate( + newTokenRequest.createRequestId, + newTokenRequest.firebaseSubscription + ); + final firebaseSubscriptionSetInvocation = requestBuilder.invocation(firebaseSubscriptionSetMethod); + final response = await (requestBuilder + ..usings(firebaseSubscriptionSetMethod.requiredCapabilities)) + .build() + .execute(); + + final firebaseSubscriptionSetResponse = response.parse( + firebaseSubscriptionSetInvocation.methodCallId, + FirebaseSubscriptionSetResponse.deserialize); + + return firebaseSubscriptionSetResponse!.created![newTokenRequest.createRequestId]!; + } } \ No newline at end of file diff --git a/lib/features/push_notification/data/repository/fcm_repository_impl.dart b/lib/features/push_notification/data/repository/fcm_repository_impl.dart index c2862998d..2b568dd0d 100644 --- a/lib/features/push_notification/data/repository/fcm_repository_impl.dart +++ b/lib/features/push_notification/data/repository/fcm_repository_impl.dart @@ -5,6 +5,7 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:model/fcm/fcm_token_dto.dart'; import 'package:tmail_ui_user/features/push_notification/data/datasource/fcm_datasource.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/model/register_new_token_request.dart'; import 'package:tmail_ui_user/features/push_notification/domain/repository/fcm_repository.dart'; import 'package:tmail_ui_user/features/thread/data/datasource/thread_datasource.dart'; import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; @@ -98,4 +99,9 @@ class FCMRepositoryImpl extends FCMRepository { Future getFirebaseSubscriptionByDeviceId(String deviceId) { return _fcmDatasource[DataSourceType.network]!.getFirebaseSubscriptionByDeviceId(deviceId); } + + @override + Future registerNewToken(RegisterNewTokenRequest newTokenRequest) { + return _fcmDatasource[DataSourceType.network]!.registerNewToken(newTokenRequest); + } } \ No newline at end of file diff --git a/lib/features/push_notification/domain/state/register_new_token_state.dart b/lib/features/push_notification/domain/state/register_new_token_state.dart new file mode 100644 index 000000000..97f819be3 --- /dev/null +++ b/lib/features/push_notification/domain/state/register_new_token_state.dart @@ -0,0 +1,25 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:fcm/model/firebase_subscription.dart'; + +class RegisterNewTokenLoading extends UIState {} + +class RegisterNewTokenSuccess extends UIState { + + final FirebaseSubscription firebaseSubscription; + + RegisterNewTokenSuccess(this.firebaseSubscription); + + @override + List get props => [firebaseSubscription]; +} + +class RegisterNewTokenFailure extends FeatureFailure { + final dynamic exception; + + RegisterNewTokenFailure(this.exception); + + @override + List get props => [exception]; +} \ No newline at end of file diff --git a/lib/features/push_notification/domain/usecases/register_new_token_interactor.dart b/lib/features/push_notification/domain/usecases/register_new_token_interactor.dart new file mode 100644 index 000000000..88b714533 --- /dev/null +++ b/lib/features/push_notification/domain/usecases/register_new_token_interactor.dart @@ -0,0 +1,24 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/model/register_new_token_request.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/repository/fcm_repository.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/state/register_new_token_state.dart'; + +class RegisterNewTokenInteractor { + + final FCMRepository _fcmRepository; + + RegisterNewTokenInteractor(this._fcmRepository); + + Stream> execute(RegisterNewTokenRequest newTokenRequest) async* { + try { + yield Right(RegisterNewTokenLoading()); + final firebaseSubscription = await _fcmRepository.registerNewToken(newTokenRequest); + yield Right(RegisterNewTokenSuccess(firebaseSubscription)); + } catch (e) { + yield Left(RegisterNewTokenFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart b/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart index 5d3b4f73a..2278223f5 100644 --- a/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart +++ b/lib/features/push_notification/presentation/bindings/fcm_interactor_bindings.dart @@ -15,6 +15,7 @@ import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_ema import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_fcm_token_cache_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_firebase_subscription_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_stored_email_delivery_state_interactor.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/usecases/register_new_token_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/save_fcm_token_cache_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/store_device_id_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/store_email_delivery_state_interactor.dart'; @@ -64,6 +65,7 @@ class FcmInteractorBindings extends InteractorsBindings { Get.lazyPut(() => DeleteEmailStateToRefreshInteractor(Get.find())); Get.lazyPut(() => StoreDeviceIdInteractor(Get.find())); Get.lazyPut(() => GetFirebaseSubscriptionInteractor(Get.find())); + Get.lazyPut(() => RegisterNewTokenInteractor(Get.find())); } @override diff --git a/lib/features/push_notification/presentation/controller/fcm_token_handler.dart b/lib/features/push_notification/presentation/controller/fcm_token_handler.dart index 8fea04735..c948a33fd 100644 --- a/lib/features/push_notification/presentation/controller/fcm_token_handler.dart +++ b/lib/features/push_notification/presentation/controller/fcm_token_handler.dart @@ -1,14 +1,24 @@ +import 'package:core/domain/extensions/datetime_extension.dart'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; import 'package:dartz/dartz.dart'; +import 'package:fcm/model/device_client_id.dart'; +import 'package:fcm/model/firebase_expired_time.dart'; +import 'package:fcm/model/firebase_subscription.dart'; import 'package:fcm/model/firebase_token.dart'; +import 'package:fcm/model/type_name.dart'; +import 'package:jmap_dart_client/jmap/core/id.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/model/register_new_token_request.dart'; import 'package:tmail_ui_user/features/push_notification/domain/state/get_firebase_subscription_state.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/get_firebase_subscription_interactor.dart'; +import 'package:tmail_ui_user/features/push_notification/domain/usecases/register_new_token_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/domain/usecases/store_device_id_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/utils/fcm_utils.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; +import 'package:uuid/uuid.dart'; class FcmTokenHandler { @@ -20,14 +30,16 @@ class FcmTokenHandler { StoreDeviceIdInteractor? _storeDeviceIdInteractor; GetFirebaseSubscriptionInteractor? _getFirebaseSubscriptionInteractor; + RegisterNewTokenInteractor? _registerNewTokenInteractor; FirebaseToken? _fcmToken; - String? _deviceId; + DeviceClientId? _deviceClientId; void initialize() { try { _storeDeviceIdInteractor = getBinding(); _getFirebaseSubscriptionInteractor = getBinding(); + _registerNewTokenInteractor = getBinding(); } catch (e) { logError('FcmTokenHandler::initialize(): ${e.toString()}'); } @@ -37,10 +49,11 @@ class FcmTokenHandler { log('FcmTokenHandler::handle():token: $token'); if (token != null) { _fcmToken = FirebaseToken(token); - _deviceId = FcmUtils.instance.hashTokenToDeviceId(token); + final deviceId = FcmUtils.instance.hashTokenToDeviceId(token); + _deviceClientId = DeviceClientId(deviceId); log('FcmTokenHandler::handle(): fcmToken: $_fcmToken'); - log('FcmTokenHandler::handle(): deviceId: $_deviceId'); - _getFcmTokenFromBackend(_deviceId!); + log('FcmTokenHandler::handle(): deviceId: $deviceId'); + _getFcmTokenFromBackend(deviceId); } } @@ -80,8 +93,54 @@ class FcmTokenHandler { void _handleSuccessViewState(Success success) { log('FcmTokenHandler::_handleSuccessViewState(): $success'); if (success is GetFirebaseSubscriptionSuccess) { - final token = success.firebaseSubscription.token; - log('FcmTokenHandler::_handleSuccessViewState():token: $token'); + _fcmToken = success.firebaseSubscription.token; + _deviceClientId = success.firebaseSubscription.deviceClientId; + final expireTime = success.firebaseSubscription.expires; + log('FcmTokenHandler::_handleSuccessViewState():_fcmToken: $_fcmToken'); + if (_isTokenExpired(expireTime)) { + _handleWhenTokenExpired(_fcmToken!, _deviceClientId!); + } + } + } + + bool _isTokenExpired(FirebaseExpiredTime? expireTime) { + log('FcmTokenHandler::_isTokenExpired():expireTime: $expireTime'); + if (expireTime != null) { + final expireTimeLocal = expireTime.value.value.toLocal(); + final currentTime = DateTime.now(); + + log('FcmTokenHandler::_isTokenExpired():expireTimeLocal: $expireTimeLocal'); + log('FcmTokenHandler::_isTokenExpired():currentTime: $currentTime'); + + return currentTime.isBefore(expireTimeLocal) && + expireTimeLocal.daysBetween(currentTime) == 3; + } else { + return true; + } + } + + void _handleWhenTokenExpired(FirebaseToken fcmToken, DeviceClientId deviceClientId) { + final generateCreationId = Id(const Uuid().v4()); + final newExpireTime = DateTime.now().add(const Duration(days: 7)); + + log('FcmTokenHandler::_handleSuccessViewState():newExpireTime: $newExpireTime'); + final firebaseSubscription = FirebaseSubscription( + token: fcmToken, + expires: FirebaseExpiredTime(newExpireTime.toUTCDate()!), + deviceClientId: deviceClientId, + types: [TypeName.emailType, TypeName.mailboxType, TypeName.emailDelivery] + ); + + log('FcmTokenHandler::_handleSuccessViewState():firebaseSubscription: $firebaseSubscription'); + _registerNewTokenAction(RegisterNewTokenRequest( + generateCreationId, + firebaseSubscription + )); + } + + void _registerNewTokenAction(RegisterNewTokenRequest newTokenRequest) { + if (_registerNewTokenInteractor != null) { + _consumeState(_registerNewTokenInteractor!.execute(newTokenRequest)); } } } \ No newline at end of file