diff --git a/core/lib/utils/sentry/sentry_config.dart b/core/lib/utils/sentry/sentry_config.dart index 810e413e0..e2690b5f0 100644 --- a/core/lib/utils/sentry/sentry_config.dart +++ b/core/lib/utils/sentry/sentry_config.dart @@ -14,10 +14,12 @@ class SentryConfig { // Current app release version final String release; - // Performance monitoring: Set tracesSampleRate to 1.0 to capture 100% of transactions for tracing + // Performance monitoring: Percentage of transactions captured for tracing. + // Keep low in production (e.g. 0.1 = 10%) to avoid quota exhaustion and latency overhead. final double tracesSampleRate; - // Optional profiling + // Optional profiling: percentage of sampled transactions that are also profiled. + // Keep low in production (e.g. 0.1 = 10%) — profiling adds significant CPU overhead. final double profilesSampleRate; // Enable logs to be sent to Sentry. To use Sentry.logger.fmt @@ -50,8 +52,8 @@ class SentryConfig { required this.dsn, required this.environment, required this.release, - this.tracesSampleRate = 1.0, - this.profilesSampleRate = 1.0, + this.tracesSampleRate = 0.1, + this.profilesSampleRate = 0.1, this.sessionSampleRate = 1.0, this.onErrorSampleRate = 1.0, this.enableLogs = true, @@ -71,11 +73,10 @@ class SentryConfig { final sentryDSN = dotenv.get('SENTRY_DSN', fallback: ''); final sentryEnvironment = dotenv.get('SENTRY_ENVIRONMENT', fallback: ''); - if (!isAvailable || - sentryDSN.trim().isEmpty || - sentryEnvironment.trim().isEmpty) { - return null; - } + final isConfigValid = isAvailable + && sentryDSN.trim().isNotEmpty + && sentryEnvironment.trim().isNotEmpty; + if (!isConfigValid) return null; final appVersion = await ApplicationManager().getAppVersion(); diff --git a/lib/features/caching/caching_manager.dart b/lib/features/caching/caching_manager.dart index d790577a4..e6a4f8367 100644 --- a/lib/features/caching/caching_manager.dart +++ b/lib/features/caching/caching_manager.dart @@ -3,6 +3,7 @@ import 'package:core/utils/file_utils.dart'; import 'package:core/utils/platform_info.dart'; import 'package:tmail_ui_user/features/caching/clients/hive_cache_version_client.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; +import 'package:tmail_ui_user/features/caching/manager/sentry_configuration_cache_manager.dart'; import 'package:tmail_ui_user/features/caching/manager/session_cache_manger.dart'; import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; import 'package:tmail_ui_user/features/cleanup/data/local/recent_login_url_cache_manager.dart'; @@ -42,6 +43,7 @@ class CachingManager { final OidcConfigurationCacheManager _oidcConfigurationCacheManager; final EncryptionKeyCacheManager _encryptionKeyCacheManager; final AuthenticationInfoCacheManager _authenticationInfoCacheManager; + final SentryConfigurationCacheManager? _sentryConfigurationCacheManager; CachingManager( this._mailboxCacheManager, @@ -62,8 +64,9 @@ class CachingManager { this._tokenOidcCacheManager, this._oidcConfigurationCacheManager, this._encryptionKeyCacheManager, - this._authenticationInfoCacheManager, - ); + this._authenticationInfoCacheManager, { + SentryConfigurationCacheManager? sentryConfigurationCacheManager, + }) : _sentryConfigurationCacheManager = sentryConfigurationCacheManager; Future clearAll() async { try { @@ -104,6 +107,8 @@ class CachingManager { _oidcConfigurationCacheManager.clear(), _tokenOidcCacheManager.clear(), _authenticationInfoCacheManager.clear(), + if (PlatformInfo.isMobile && _sentryConfigurationCacheManager != null) + _sentryConfigurationCacheManager!.clearSentryConfiguration(), ], eagerError: true); } catch (e) { logWarning('CachingManager::clearAccountDataCached: Cannot clear account data cache: $e'); diff --git a/lib/features/caching/clients/sentry_configuration_cache_client.dart b/lib/features/caching/clients/sentry_configuration_cache_client.dart new file mode 100644 index 000000000..498e04cad --- /dev/null +++ b/lib/features/caching/clients/sentry_configuration_cache_client.dart @@ -0,0 +1,12 @@ +import 'package:tmail_ui_user/features/caching/config/hive_cache_client.dart'; +import 'package:tmail_ui_user/features/caching/entries/sentry_configuration_cache.dart'; +import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; + +class SentryConfigurationCacheClient + extends HiveCacheClient { + @override + String get tableName => CachingConstants.sentryConfigurationCacheBoxName; + + @override + bool get encryption => true; +} diff --git a/lib/features/caching/clients/sentry_user_cache_client.dart b/lib/features/caching/clients/sentry_user_cache_client.dart new file mode 100644 index 000000000..05a9cbd36 --- /dev/null +++ b/lib/features/caching/clients/sentry_user_cache_client.dart @@ -0,0 +1,12 @@ +import 'package:tmail_ui_user/features/caching/config/hive_cache_client.dart'; +import 'package:tmail_ui_user/features/caching/entries/sentry_user_cache.dart'; +import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; + +class SentryUserCacheClient + extends HiveCacheClient { + @override + String get tableName => CachingConstants.sentryUserCacheBoxName; + + @override + bool get encryption => true; +} diff --git a/lib/features/caching/config/hive_cache_config.dart b/lib/features/caching/config/hive_cache_config.dart index 2826b0af8..726079e0d 100644 --- a/lib/features/caching/config/hive_cache_config.dart +++ b/lib/features/caching/config/hive_cache_config.dart @@ -22,6 +22,8 @@ import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_st import 'package:tmail_ui_user/features/caching/caching_manager.dart'; import 'package:tmail_ui_user/features/caching/config/cache_version.dart'; import 'package:tmail_ui_user/features/caching/config/fcm_isolate_name_server.dart'; +import 'package:tmail_ui_user/features/caching/entries/sentry_configuration_cache.dart'; +import 'package:tmail_ui_user/features/caching/entries/sentry_user_cache.dart'; import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; import 'package:tmail_ui_user/features/home/data/model/session_hive_obj.dart'; import 'package:tmail_ui_user/features/login/data/local/encryption_key_cache_manager.dart'; @@ -239,6 +241,16 @@ class HiveCacheConfig { CachingConstants.OIDC_CONFIGURATION_CACHE_ID, isolated: isolated, ); + registerCacheAdapter( + SentryConfigurationCacheAdapter(), + CachingConstants.SENTRY_CONFIGURATION_CACHE_ID, + isolated: isolated, + ); + registerCacheAdapter( + SentryUserCacheAdapter(), + CachingConstants.SENTRY_USER_CACHE_ID, + isolated: isolated, + ); } void registerCacheAdapter( diff --git a/lib/features/caching/entries/sentry_configuration_cache.dart b/lib/features/caching/entries/sentry_configuration_cache.dart new file mode 100644 index 000000000..d90ae50ac --- /dev/null +++ b/lib/features/caching/entries/sentry_configuration_cache.dart @@ -0,0 +1,80 @@ +import 'package:equatable/equatable.dart'; +import 'package:hive_ce/hive.dart'; +import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; + +part 'sentry_configuration_cache.g.dart'; + +@HiveType(typeId: CachingConstants.SENTRY_CONFIGURATION_CACHE_ID) +class SentryConfigurationCache extends HiveObject with EquatableMixin { + @HiveField(0) + final String dsn; + + @HiveField(1) + final String environment; + + @HiveField(2) + final String release; + + @HiveField(3) + final double tracesSampleRate; + + @HiveField(4) + final double profilesSampleRate; + + @HiveField(5) + final bool enableLogs; + + @HiveField(6) + final bool isDebug; + + @HiveField(7) + final bool attachScreenshot; + + @HiveField(8) + final bool isAvailable; + + @HiveField(9) + final double sessionSampleRate; + + @HiveField(10) + final double onErrorSampleRate; + + @HiveField(11) + final bool enableFramesTracking; + + @HiveField(12) + final String? dist; + + SentryConfigurationCache({ + required this.dsn, + required this.environment, + required this.release, + required this.tracesSampleRate, + required this.profilesSampleRate, + required this.enableLogs, + required this.isDebug, + required this.attachScreenshot, + required this.isAvailable, + required this.sessionSampleRate, + required this.onErrorSampleRate, + required this.enableFramesTracking, + required this.dist, + }); + + @override + List get props => [ + dsn, + environment, + release, + tracesSampleRate, + profilesSampleRate, + enableLogs, + isDebug, + attachScreenshot, + isAvailable, + sessionSampleRate, + onErrorSampleRate, + enableFramesTracking, + dist, + ]; +} diff --git a/lib/features/caching/entries/sentry_user_cache.dart b/lib/features/caching/entries/sentry_user_cache.dart new file mode 100644 index 000000000..6f167cf2a --- /dev/null +++ b/lib/features/caching/entries/sentry_user_cache.dart @@ -0,0 +1,35 @@ +import 'package:equatable/equatable.dart'; +import 'package:hive_ce/hive.dart'; +import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; + +part 'sentry_user_cache.g.dart'; + +@HiveType(typeId: CachingConstants.SENTRY_USER_CACHE_ID) +class SentryUserCache extends HiveObject with EquatableMixin { + @HiveField(0) + final String id; + + @HiveField(1) + final String name; + + @HiveField(2) + final String username; + + @HiveField(3) + final String email; + + SentryUserCache({ + required this.id, + required this.name, + required this.username, + required this.email, + }); + + @override + List get props => [ + id, + name, + username, + email, + ]; +} diff --git a/lib/features/caching/exceptions/local_storage_exception.dart b/lib/features/caching/exceptions/local_storage_exception.dart index c78f21dde..21e105af9 100644 --- a/lib/features/caching/exceptions/local_storage_exception.dart +++ b/lib/features/caching/exceptions/local_storage_exception.dart @@ -6,3 +6,17 @@ class NotFoundDataWithThisKeyException extends AppBaseException { @override String get exceptionName => 'NotFoundDataWithThisKeyException'; } + +class NotFoundSentryConfigurationException extends AppBaseException { + const NotFoundSentryConfigurationException([super.message]); + + @override + String get exceptionName => 'NotFoundSentryConfigurationException'; +} + +class NotFoundSentryUserException extends AppBaseException { + const NotFoundSentryUserException([super.message]); + + @override + String get exceptionName => 'NotFoundSentryUserException'; +} \ No newline at end of file diff --git a/lib/features/caching/extensions/sentry_cache_extensions.dart b/lib/features/caching/extensions/sentry_cache_extensions.dart new file mode 100644 index 000000000..46cfe1f84 --- /dev/null +++ b/lib/features/caching/extensions/sentry_cache_extensions.dart @@ -0,0 +1,66 @@ +import 'package:core/utils/sentry/sentry_config.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:tmail_ui_user/features/caching/entries/sentry_configuration_cache.dart'; +import 'package:tmail_ui_user/features/caching/entries/sentry_user_cache.dart'; + +extension SentryConfigExtension on SentryConfig { + SentryConfigurationCache toSentryConfigurationCache() { + return SentryConfigurationCache( + dsn: dsn, + environment: environment, + release: release, + isAvailable: isAvailable, + tracesSampleRate: tracesSampleRate, + profilesSampleRate: profilesSampleRate, + sessionSampleRate: sessionSampleRate, + onErrorSampleRate: onErrorSampleRate, + enableLogs: enableLogs, + enableFramesTracking: enableFramesTracking, + isDebug: isDebug, + attachScreenshot: attachScreenshot, + dist: dist, + ); + } +} + +extension SentryConfigurationCacheExtension on SentryConfigurationCache { + SentryConfig toSentryConfig() { + return SentryConfig( + dsn: dsn, + environment: environment, + release: release, + isAvailable: isAvailable, + tracesSampleRate: tracesSampleRate, + profilesSampleRate: profilesSampleRate, + sessionSampleRate: sessionSampleRate, + onErrorSampleRate: onErrorSampleRate, + enableLogs: enableLogs, + enableFramesTracking: enableFramesTracking, + isDebug: isDebug, + attachScreenshot: attachScreenshot, + dist: dist, + ); + } +} + +extension SentryUserExtension on SentryUser { + SentryUserCache toSentryUserCache() { + return SentryUserCache( + id: id ?? '', + name: name ?? '', + username: username ?? '', + email: email ?? '', + ); + } +} + +extension SentryUserCacheExtension on SentryUserCache { + SentryUser toSentryUser() { + return SentryUser( + id: id.isEmpty ? null : id, + name: name.isEmpty ? null : name, + username: username.isEmpty ? null : username, + email: email.isEmpty ? null : email, + ); + } +} diff --git a/lib/features/caching/manager/sentry_configuration_cache_manager.dart b/lib/features/caching/manager/sentry_configuration_cache_manager.dart new file mode 100644 index 000000000..00ad9eddd --- /dev/null +++ b/lib/features/caching/manager/sentry_configuration_cache_manager.dart @@ -0,0 +1,68 @@ +import 'package:core/utils/app_logger.dart'; +import 'package:tmail_ui_user/features/caching/clients/sentry_configuration_cache_client.dart'; +import 'package:tmail_ui_user/features/caching/clients/sentry_user_cache_client.dart'; +import 'package:tmail_ui_user/features/caching/entries/sentry_configuration_cache.dart'; +import 'package:tmail_ui_user/features/caching/entries/sentry_user_cache.dart'; +import 'package:tmail_ui_user/features/caching/exceptions/local_storage_exception.dart'; +import 'package:tmail_ui_user/features/caching/utils/caching_constants.dart'; + +class SentryConfigurationCacheManager { + final SentryConfigurationCacheClient _configurationCacheClient; + final SentryUserCacheClient _userCacheClient; + final String _configurationCacheKey = + CachingConstants.sentryConfigurationCacheKeyName; + final String _userCacheKey = CachingConstants.sentryUserCacheKeyName; + + SentryConfigurationCacheManager( + this._configurationCacheClient, + this._userCacheClient, + ); + + Future getSentryConfiguration() async { + final cache = await _configurationCacheClient.getItem(_configurationCacheKey); + if (cache == null) throw const NotFoundSentryConfigurationException(); + return cache; + } + + Future saveSentryConfiguration( + SentryConfigurationCache sentryConfigurationCache, + ) async { + await _configurationCacheClient.insertItem( + _configurationCacheKey, + sentryConfigurationCache, + ); + } + + Future getSentryUser() async { + final cache = await _userCacheClient.getItem(_userCacheKey); + if (cache == null) throw const NotFoundSentryUserException(); + return cache; + } + + Future saveSentryUser( + SentryUserCache sentryUserCache, + ) async { + await _userCacheClient.insertItem(_userCacheKey, sentryUserCache); + } + + Future clearSentryConfiguration() async { + try { + await _configurationCacheClient.clearAllData(); + } catch (e, st) { + logError( + 'SentryConfigurationCacheManager::clearSentryConfiguration: Failed to clear config cache', + exception: e, + stackTrace: st, + ); + } + try { + await _userCacheClient.clearAllData(); + } catch (e, st) { + logError( + 'SentryConfigurationCacheManager::clearSentryConfiguration: Failed to clear user cache', + exception: e, + stackTrace: st, + ); + } + } +} diff --git a/lib/features/caching/utils/caching_constants.dart b/lib/features/caching/utils/caching_constants.dart index 4db4af99f..b8d70118f 100644 --- a/lib/features/caching/utils/caching_constants.dart +++ b/lib/features/caching/utils/caching_constants.dart @@ -20,6 +20,8 @@ class CachingConstants { static const int SENDING_EMAIL_HIVE_CACHE_ID = 18; static const int SESSION_HIVE_CACHE_ID = 19; static const int OIDC_CONFIGURATION_CACHE_ID = 20; + static const int SENTRY_CONFIGURATION_CACHE_ID = 21; + static const int SENTRY_USER_CACHE_ID = 22; static const String fcmCacheBoxName = 'fcm_cache_box'; static const String newEmailCacheBoxName = 'new_email_cache_box'; @@ -28,8 +30,12 @@ class CachingConstants { static const String sessionCacheBoxName = 'session_cache_box'; static const String firebaseRegistrationCacheBoxName = 'firebase_registration_cache_box'; static const String oidcConfigurationCacheBoxName = 'oidc_configuration_cache_box'; + static const String sentryConfigurationCacheBoxName = 'sentry_configuration_cache_box'; + static const String sentryUserCacheBoxName = 'sentry_user_cache_box'; static const String oidcConfigurationCacheKeyName = 'oidc_configuration_cache_key'; + static const String sentryConfigurationCacheKeyName = 'sentry_configuration_cache_key'; + static const String sentryUserCacheKeyName = 'sentry_user_cache_key'; static const String newEmailsContentFolderName = 'new_emails'; static const String openedEmailContentFolderName = 'opened_email'; diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 3eaef79e8..2c3eb1240 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -154,7 +154,9 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_text_formatting_menu_state_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/web_auth_redirect_processor_extension.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mixin/sentry_ecosystem_mixin.dart'; +import 'package:tmail_ui_user/features/caching/manager/sentry_configuration_cache_manager.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/sentry_config_linagora_ecosystem.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/sentry_ecosystem.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/download/download_task_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/draggable_app_state.dart'; @@ -242,8 +244,9 @@ class MailboxDashBoardController extends ReloadableController AiScribeMixin, SearchLabelFilterModalMixin, AddLabelToEmailMixin, - HandleTeamMailboxMixin, - SentryEcosystemMixin { + HandleTeamMailboxMixin { + + SentryEcosystem? _sentryEcosystem; final RemoveEmailDraftsInteractor _removeEmailDraftsInteractor = Get.find(); final EmailReceiveManager _emailReceiveManager = Get.find(); @@ -403,6 +406,7 @@ class MailboxDashBoardController extends ReloadableController @override void onInit() { if (PlatformInfo.isMobile) { + _sentryEcosystem = SentryEcosystem(getBinding()); _registerReceivingFileSharingStream(); _registerDeepLinks(); } @@ -415,6 +419,11 @@ class MailboxDashBoardController extends ReloadableController super.onInit(); } + void initSentryUser(SentryUser? user) => _sentryEcosystem?.initUser(user); + + Future setUpSentry(SentryConfigLinagoraEcosystem ecosystemConfig) async => + _sentryEcosystem?.setUp(ecosystemConfig); + @override void onReady() { if (PlatformInfo.isWeb) { @@ -3477,6 +3486,7 @@ class MailboxDashBoardController extends ReloadableController paywallController?.onClose(); paywallController = null; cachedLinagoraEcosystem = null; + _sentryEcosystem = null; _disposeWorkerObxVariables(); super.onClose(); } diff --git a/lib/features/mailbox_dashboard/presentation/extensions/ai_scribe/setup_scribe_prompt_url_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/ai_scribe/setup_scribe_prompt_url_extension.dart index 2eff73e6d..9509a8768 100644 --- a/lib/features/mailbox_dashboard/presentation/extensions/ai_scribe/setup_scribe_prompt_url_extension.dart +++ b/lib/features/mailbox_dashboard/presentation/extensions/ai_scribe/setup_scribe_prompt_url_extension.dart @@ -1,4 +1,5 @@ import 'package:core/utils/app_logger.dart'; +import 'package:core/utils/platform_info.dart'; import 'package:scribe/scribe/ai/data/service/prompt_service.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_linagora_ecosystem_state.dart'; @@ -51,6 +52,7 @@ extension SetupScribePromptUrlExtension on MailboxDashBoardController { } void _setUpSentry(LinagoraEcosystem ecosystem) { + if (PlatformInfo.isWeb) return; final sentryConfigEcosystem = ecosystem.sentryConfigEcosystem; if (sentryConfigEcosystem != null) { setUpSentry(sentryConfigEcosystem); diff --git a/lib/features/mailbox_dashboard/presentation/mixin/sentry_ecosystem_mixin.dart b/lib/features/mailbox_dashboard/presentation/mixin/sentry_ecosystem_mixin.dart deleted file mode 100644 index 154f178cb..000000000 --- a/lib/features/mailbox_dashboard/presentation/mixin/sentry_ecosystem_mixin.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:core/presentation/extensions/string_extension.dart'; -import 'package:core/utils/app_logger.dart'; -import 'package:core/utils/sentry/sentry_manager.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/sentry_config_linagora_ecosystem.dart'; - -mixin SentryEcosystemMixin { - SentryUser? _sentryUser; - - void initSentryUser(SentryUser? newSentryUser) { - _sentryUser = newSentryUser; - } - - Future setUpSentry( - SentryConfigLinagoraEcosystem ecosystemConfig, - ) async { - final dsn = ecosystemConfig.dsn?.trimmed; - final env = ecosystemConfig.environment?.trimmed; - final isValid = ecosystemConfig.enabled == true - && (dsn?.isNotEmpty ?? false) - && (env?.isNotEmpty ?? false); - - if (!isValid) { - logWarning( - 'SentryEcosystemMixin::setUpSentry: config invalid ' - '(enabled=${ecosystemConfig.enabled}, dsn=${dsn?.isNotEmpty}, env=${env?.isNotEmpty})', - ); - return; - } - - final sentryConfig = await ecosystemConfig.toSentryConfig(); - - await SentryManager.instance.initializeWithSentryConfig(sentryConfig); - - _setupSentryUser(); - } - - void _setupSentryUser() { - if (_sentryUser == null) return; - - SentryManager.instance.setUser(_sentryUser!); - } -} diff --git a/lib/features/mailbox_dashboard/presentation/sentry_ecosystem.dart b/lib/features/mailbox_dashboard/presentation/sentry_ecosystem.dart new file mode 100644 index 000000000..4c39faf0b --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/sentry_ecosystem.dart @@ -0,0 +1,67 @@ +import 'package:core/presentation/extensions/string_extension.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:core/utils/sentry/sentry_config.dart'; +import 'package:core/utils/sentry/sentry_manager.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; +import 'package:tmail_ui_user/features/caching/extensions/sentry_cache_extensions.dart'; +import 'package:tmail_ui_user/features/caching/manager/sentry_configuration_cache_manager.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/sentry_config_linagora_ecosystem.dart'; + +class SentryEcosystem { + final SentryConfigurationCacheManager? _cacheManager; + + SentryUser? _sentryUser; + + SentryEcosystem(this._cacheManager); + + void initUser(SentryUser? user) { + _sentryUser = user; + } + + Future setUp(SentryConfigLinagoraEcosystem ecosystemConfig) async { + final dsn = ecosystemConfig.dsn?.trimmed; + final env = ecosystemConfig.environment?.trimmed; + final isValid = ecosystemConfig.enabled == true + && (dsn?.isNotEmpty ?? false) + && (env?.isNotEmpty ?? false); + + if (!isValid) { + logWarning( + 'SentryEcosystem::setUp: config invalid ' + '(enabled=${ecosystemConfig.enabled}, dsn=${dsn?.isNotEmpty}, env=${env?.isNotEmpty})', + ); + return; + } + + final sentryConfig = await ecosystemConfig.toSentryConfig(); + + await SentryManager.instance.initializeWithSentryConfig(sentryConfig); + + _applyUser(); + + await _cacheData(sentryConfig, _sentryUser); + } + + void _applyUser() { + if (_sentryUser == null) return; + SentryManager.instance.setUser(_sentryUser!); + } + + Future _cacheData(SentryConfig sentryConfig, SentryUser? sentryUser) async { + if (_cacheManager == null) return; + try { + await _cacheManager!.saveSentryConfiguration(sentryConfig.toSentryConfigurationCache()); + if (sentryUser != null) { + await _cacheManager!.saveSentryUser(sentryUser.toSentryUserCache()); + } + } catch (e, st) { + logError( + 'SentryEcosystem::_cacheData: Cannot cache Sentry data', + exception: e, + stackTrace: st, + ); + // Clear both caches to avoid stale/inconsistent state (e.g. new config + old user PII) + await _cacheManager!.clearSentryConfiguration().catchError((_) {}); + } + } +} diff --git a/lib/features/push_notification/presentation/controller/fcm_message_controller.dart b/lib/features/push_notification/presentation/controller/fcm_message_controller.dart index 154651eca..dd64d421d 100644 --- a/lib/features/push_notification/presentation/controller/fcm_message_controller.dart +++ b/lib/features/push_notification/presentation/controller/fcm_message_controller.dart @@ -6,6 +6,7 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; +import 'package:core/utils/sentry/sentry_manager.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; @@ -13,6 +14,9 @@ import 'package:jmap_dart_client/jmap/push/state_change.dart'; import 'package:model/model.dart'; import 'package:rxdart/rxdart.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; +import 'package:tmail_ui_user/features/caching/entries/sentry_configuration_cache.dart'; +import 'package:tmail_ui_user/features/caching/extensions/sentry_cache_extensions.dart'; +import 'package:tmail_ui_user/features/caching/manager/sentry_configuration_cache_manager.dart'; import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart'; import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart'; import 'package:tmail_ui_user/features/home/domain/usecases/get_session_interactor.dart'; @@ -48,10 +52,18 @@ class FcmMessageController extends PushBaseController { @override void initialize({AccountId? accountId, Session? session}) { - super.initialize(accountId: accountId, session: session); + try { + super.initialize(accountId: accountId, session: session); - _listenTokenStream(); - _listenBackgroundMessageStream(); + _listenTokenStream(); + _listenBackgroundMessageStream(); + } catch (e, st) { + logError( + 'FcmMessageController::initialize: throw exception', + exception: e, + stackTrace: st, + ); + } } void _listenBackgroundMessageStream() { @@ -59,36 +71,104 @@ class FcmMessageController extends PushBaseController { .debounceTime(const Duration( milliseconds: FcmUtils.durationBackgroundMessageComing, )) - .listen(_handleBackgroundMessageAction); + .listen( + _handleBackgroundMessageAction, + onError: (e, st) { + logError( + 'FcmMessageController::_listenBackgroundMessageStream', + exception: e, + stackTrace: st, + ); + }, + ); } void _listenTokenStream() { FcmService.instance.fcmTokenStreamController ?.stream .debounceTime(const Duration(milliseconds: FcmUtils.durationRefreshToken)) - .listen(FcmTokenController.instance.onFcmTokenChanged); + .listen( + FcmTokenController.instance.onFcmTokenChanged, + onError: (e, st) { + logError( + 'FcmMessageController::_listenTokenStream', + exception: e, + stackTrace: st, + ); + }, + ); } void _handleBackgroundMessageAction(Map payloadData) async { - log('FcmMessageController::_handleBackgroundMessageAction():payloadData: $payloadData'); + logTrace('FcmMessageController::_handleBackgroundMessageAction():payloadData keys: ${payloadData.keys.toList()}'); final stateChange = FcmUtils.instance.convertFirebaseDataMessageToStateChange(payloadData); - await _initialAppConfig(); + if (stateChange == null) { + logTrace('FcmMessageController::_handleBackgroundMessageAction(): stateChange is null'); + return; + } else { + logTrace('FcmMessageController::_handleBackgroundMessageAction(): stateChange: ${stateChange.toString()}'); + } _getAuthenticatedAccount(stateChange: stateChange); } - Future _initialAppConfig() async { - await Future.wait([ - MainBindings().dependencies(), - HiveCacheConfig.instance.setUp() - ]); + Future initialAppConfig() async { + try { + await MainBindings().dependencies(); + await HiveCacheConfig.instance.setUp(); - await Future.sync(() { HomeBindings().dependencies(); MailboxDashBoardBindings().dependencies(); FcmInteractorBindings().dependencies(); - }); - _getInteractorBindings(); + _getInteractorBindings(); + } catch (e, st) { + logError( + 'FcmMessageController::initialAppConfig: throw exception', + exception: e, + stackTrace: st, + ); + rethrow; + } + } + + Future setUpSentryConfiguration() async { + try { + final cacheManager = getBinding(); + if (cacheManager == null) { + logWarning('FcmMessageController::setUpSentryConfiguration: SentryConfigurationCacheManager is null'); + return; + } + + final SentryConfigurationCache configCache; + try { + configCache = await cacheManager.getSentryConfiguration(); + } catch (e) { + logWarning('FcmMessageController::setUpSentryConfiguration: SentryConfiguration not cached: $e'); + return; + } + + final sentryConfig = configCache.toSentryConfig(); + if (!sentryConfig.isAvailable) { + logWarning('FcmMessageController::setUpSentryConfiguration: SentryConfiguration is not available'); + return; + } + + await SentryManager.instance.initializeWithSentryConfig(sentryConfig); + + try { + final userCache = await cacheManager.getSentryUser(); + SentryManager.instance.setUser(userCache.toSentryUser()); + } catch (e) { + logTrace('FcmMessageController::setUpSentryConfiguration: Sentry user not cached: $e'); + // Acceptable — Sentry initialized without user context + } + } catch (e, st) { + logError( + 'FcmMessageController::setUpSentryConfiguration: throw exception', + exception: e, + stackTrace: st, + ); + } } void _getInteractorBindings() { @@ -100,9 +180,13 @@ class FcmMessageController extends PushBaseController { FcmTokenController.instance.initialBindingInteractor(); } - void _getAuthenticatedAccount({StateChange? stateChange}) { + void _getAuthenticatedAccount({required StateChange stateChange}) { if (_getAuthenticatedAccountInteractor != null) { consumeState(_getAuthenticatedAccountInteractor!.execute(stateChange: stateChange)); + } else { + logTrace( + 'GetAuthenticatedAccountInteractor is null', + ); } } @@ -110,25 +194,18 @@ class FcmMessageController extends PushBaseController { _dynamicUrlInterceptors?.setJmapUrl(storedTokenOidcSuccess.baseUrl.toString()); _authorizationInterceptors?.setTokenAndAuthorityOidc( newToken: storedTokenOidcSuccess.tokenOidc, - newConfig: storedTokenOidcSuccess.oidcConfiguration + newConfig: storedTokenOidcSuccess.oidcConfiguration, ); - if (PlatformInfo.isAndroid) { _dynamicUrlInterceptors?.changeBaseUrl(storedTokenOidcSuccess.baseUrl.toString()); _getSessionAction(stateChange: storedTokenOidcSuccess.stateChange); } else { - _dynamicUrlInterceptors?.changeBaseUrl(storedTokenOidcSuccess.personalAccount.apiUrl); - - final accountId = storedTokenOidcSuccess.personalAccount.accountId; - final username = storedTokenOidcSuccess.personalAccount.userName; - final stateChange = storedTokenOidcSuccess.stateChange; - - if (accountId != null && username != null && stateChange != null) { - _pushActionFromRemoteMessageBackground( - accountId: accountId, - userName: username, - stateChange: stateChange); - } + _dispatchPushOnNonAndroid( + apiUrl: storedTokenOidcSuccess.personalAccount.apiUrl, + accountId: storedTokenOidcSuccess.personalAccount.accountId, + username: storedTokenOidcSuccess.personalAccount.userName, + stateChange: storedTokenOidcSuccess.stateChange, + ); } } @@ -142,24 +219,39 @@ class FcmMessageController extends PushBaseController { _dynamicUrlInterceptors?.changeBaseUrl(credentialViewState.baseUrl.toString()); _getSessionAction(stateChange: credentialViewState.stateChange); } else { - _dynamicUrlInterceptors?.changeBaseUrl(credentialViewState.personalAccount.apiUrl); - - final accountId = credentialViewState.personalAccount.accountId; - final username = credentialViewState.personalAccount.userName; - final stateChange = credentialViewState.stateChange; - - if (accountId != null && username != null && stateChange != null) { - _pushActionFromRemoteMessageBackground( - accountId: accountId, - userName: username, - stateChange: stateChange); - } + _dispatchPushOnNonAndroid( + apiUrl: credentialViewState.personalAccount.apiUrl, + accountId: credentialViewState.personalAccount.accountId, + username: credentialViewState.personalAccount.userName, + stateChange: credentialViewState.stateChange, + ); } } + void _dispatchPushOnNonAndroid({ + required String? apiUrl, + required AccountId? accountId, + required UserName? username, + required StateChange? stateChange, + }) { + _dynamicUrlInterceptors?.changeBaseUrl(apiUrl); + final canPush = accountId != null && username != null && stateChange != null; + if (!canPush) { + logTrace('FcmMessageController::_dispatchPushOnNonAndroid: accountId or username or stateChange is null'); + return; + } + _pushActionFromRemoteMessageBackground( + accountId: accountId, + userName: username, + stateChange: stateChange, + ); + } + void _getSessionAction({StateChange? stateChange}) { if (_getSessionInteractor != null) { consumeState(_getSessionInteractor!.execute(stateChange: stateChange)); + } else { + logTrace('FcmMessageController::_getSessionAction: _getSessionInteractor is null'); } } @@ -176,9 +268,17 @@ class FcmMessageController extends PushBaseController { userName: success.session.username, stateChange: stateChange, session: success.session); + } else { + logTrace( + 'FcmMessageController::_handleGetSessionSuccess: Api url or state change is null', + ); } - } catch (e) { - logWarning('FcmMessageController::_handleGetSessionSuccess: Exception $e'); + } catch (e, st) { + logError( + 'FcmMessageController::_handleGetSessionSuccess:', + exception: e, + stackTrace: st, + ); } } @@ -189,7 +289,7 @@ class FcmMessageController extends PushBaseController { Session? session }) { final mapTypeState = stateChange.getMapTypeState(accountId); - + logTrace('FcmMessageController::_pushActionFromRemoteMessageBackground: Mapping type state to action ${mapTypeState.toString()}'); mappingTypeStateToAction( mapTypeState, accountId, @@ -204,6 +304,11 @@ class FcmMessageController extends PushBaseController { @override void handleFailureViewState(Failure failure) { log('FcmMessageController::_handleFailureViewState(): $failure'); + if (failure is GetStoredTokenOidcFailure) { + logTrace('FcmMessageController::GetStoredTokenOidcFailure: Get stored token oidc is failed'); + } else if (failure is GetSessionFailure) { + logTrace('FcmMessageController::GetSessionFailure: Get session is failed'); + } } @override diff --git a/lib/features/push_notification/presentation/listener/email_change_listener.dart b/lib/features/push_notification/presentation/listener/email_change_listener.dart index 6bb676dee..7188f9e36 100644 --- a/lib/features/push_notification/presentation/listener/email_change_listener.dart +++ b/lib/features/push_notification/presentation/listener/email_change_listener.dart @@ -91,19 +91,9 @@ class EmailChangeListener extends ChangeListener { log('EmailChangeListener::dispatchActions():actions: $actions'); for (var action in actions) { if (action is SynchronizeEmailOnForegroundAction) { - if (PlatformInfo.isAndroid) { - _handleRemoveNotificationWhenEmailMarkAsRead(action.newState, action.accountId, action.session); - } - _synchronizeEmailOnForegroundAction(action.newState); - if (PlatformInfo.isMobile) { - _getNewReceiveEmailFromNotificationAction(action.session, action.accountId, action.newState); - } + _onSynchronizeEmailOnForeground(action); } else if (action is PushNotificationAction) { - _pushNotificationAction(action.newState, action.accountId, action.userName, action.session); - - if (PlatformInfo.isAndroid) { - _getNewReceiveEmailFromNotificationAction(action.session, action.accountId, action.newState); - } + _onPushNotification(action); } else if (action is StoreEmailStateToRefreshAction) { if (PlatformInfo.isAndroid) { _handleRemoveNotificationWhenEmailMarkAsRead(action.newState, action.accountId, action.session); @@ -112,6 +102,23 @@ class EmailChangeListener extends ChangeListener { } } + void _onSynchronizeEmailOnForeground(SynchronizeEmailOnForegroundAction action) { + if (PlatformInfo.isAndroid) { + _handleRemoveNotificationWhenEmailMarkAsRead(action.newState, action.accountId, action.session); + } + _synchronizeEmailOnForegroundAction(action.newState); + if (PlatformInfo.isMobile) { + _getNewReceiveEmailFromNotificationAction(action.session, action.accountId, action.newState); + } + } + + void _onPushNotification(PushNotificationAction action) { + _pushNotificationAction(action.newState, action.accountId, action.userName, action.session); + if (PlatformInfo.isAndroid) { + _getNewReceiveEmailFromNotificationAction(action.session, action.accountId, action.newState); + } + } + void _synchronizeEmailOnForegroundAction(jmap.State newState) { log('EmailChangeListener::_synchronizeEmailAction():newState: $newState'); _dashBoardController?.dispatchEmailUIAction(RefreshChangeEmailAction( @@ -142,27 +149,29 @@ class EmailChangeListener extends ChangeListener { } void _getStoredEmailState() { - if (_getStoredEmailStateInteractor != null && _session != null && _accountId != null) { - consumeState(_getStoredEmailStateInteractor!.execute(_session!, _accountId!)); - } + final canExecute = _getStoredEmailStateInteractor != null + && _session != null + && _accountId != null; + if (!canExecute) return; + consumeState(_getStoredEmailStateInteractor!.execute(_session!, _accountId!)); } void _getEmailChangesAction(jmap.State state) { - if (_getEmailChangesToPushNotificationInteractor != null && - _accountId != null && - _session != null && - _userName != null) { - consumeState(_getEmailChangesToPushNotificationInteractor!.execute( + final canExecute = _getEmailChangesToPushNotificationInteractor != null + && _accountId != null + && _session != null + && _userName != null; + if (!canExecute) return; + consumeState(_getEmailChangesToPushNotificationInteractor!.execute( + _session!, + _accountId!, + _userName!, + state, + propertiesCreated: EmailUtils.getPropertiesForEmailGetMethod( _session!, _accountId!, - _userName!, - state, - propertiesCreated: EmailUtils.getPropertiesForEmailGetMethod( - _session!, - _accountId!, - ), - )); - } + ), + )); } void _storeEmailDeliveryStateAction(AccountId accountId, UserName userName, jmap.State state) { @@ -205,19 +214,12 @@ class EmailChangeListener extends ChangeListener { @override void handleSuccessViewState(Success success) { log('EmailChangeListener::_handleSuccessViewState(): $success'); - if (success is GetStoredEmailDeliveryStateSuccess && _newStateEmailDelivery != success.state) { - _getEmailChangesAction(success.state); + if (success is GetStoredEmailDeliveryStateSuccess) { + _handleGetStoredEmailDeliveryStateSuccess(success); } else if (success is GetStoredEmailStateSuccess) { _getEmailChangesAction(success.state); - } else if (success is GetEmailChangesToPushNotificationSuccess && _newStateEmailDelivery != null) { - _storeEmailDeliveryStateAction(success.accountId, success.userName, _newStateEmailDelivery!); - - if (PlatformInfo.isAndroid) { - _handleListEmailToPushNotification( - userName: success.userName, - emailList: success.emailList - ); - } + } else if (success is GetEmailChangesToPushNotificationSuccess) { + _handleGetEmailChangesToPushNotificationSuccess(success); } else if (success is GetMailboxesNotPutNotificationsSuccess) { final listEmails = _emailsAvailablePushNotification.toEmailsAvailablePushNotification( mailboxIdsNotPutNotifications: success.mailboxes.mailboxIds); @@ -240,18 +242,44 @@ class EmailChangeListener extends ChangeListener { } } + void _handleGetStoredEmailDeliveryStateSuccess(GetStoredEmailDeliveryStateSuccess success) { + if (_newStateEmailDelivery != success.state) { + _getEmailChangesAction(success.state); + } + } + + void _handleGetEmailChangesToPushNotificationSuccess(GetEmailChangesToPushNotificationSuccess success) { + if (_newStateEmailDelivery == null) return; + + _storeEmailDeliveryStateAction( + success.accountId, + success.userName, + _newStateEmailDelivery!, + ); + + if (PlatformInfo.isAndroid) { + _handleListEmailToPushNotification( + userName: success.userName, + emailList: success.emailList, + ); + } + } + void _handleListEmailToPushNotification({ required UserName userName, required List emailList }) { _emailsAvailablePushNotification = emailList; - if (_getMailboxesNotPutNotificationsInteractor != null && _accountId != null && _session != null) { + final canFilterByMailbox = _getMailboxesNotPutNotificationsInteractor != null + && _accountId != null + && _session != null; + if (canFilterByMailbox) { consumeState(_getMailboxesNotPutNotificationsInteractor!.execute(_session!, _accountId!)); } else { final listEmails = _emailsAvailablePushNotification.toEmailsAvailablePushNotification(); _handleLocalPushNotification( userName: userName, - emailList: listEmails + emailList: listEmails, ); } } @@ -319,21 +347,21 @@ class EmailChangeListener extends ChangeListener { void _getListDetailedEmailByIdAction(Session? session, AccountId accountId, List emailIds) { log('EmailChangeListener::_getListDetailedEmailByIdAction():emailIds: $emailIds'); - if (_getListDetailedEmailByIdInteractor != null && - _dynamicUrlInterceptors != null && - session != null) { - try { - final baseDownloadUrl = session.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors!.jmapUrl); - consumeState(_getListDetailedEmailByIdInteractor!.execute( - session, - accountId, - emailIds, - baseDownloadUrl - )); - } catch (e) { - logWarning('EmailChangeListener::_getListDetailedEmailByIdAction(): $e'); - consumeState(Stream.value(Left(GetDetailedEmailByIdFailure(e)))); - } + final canExecute = _getListDetailedEmailByIdInteractor != null + && _dynamicUrlInterceptors != null + && session != null; + if (!canExecute) return; + try { + final baseDownloadUrl = session.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors!.jmapUrl); + consumeState(_getListDetailedEmailByIdInteractor!.execute( + session, + accountId, + emailIds, + baseDownloadUrl, + )); + } catch (e) { + logWarning('EmailChangeListener::_getListDetailedEmailByIdAction(): $e'); + consumeState(Stream.value(Left(GetDetailedEmailByIdFailure(e)))); } } diff --git a/lib/features/push_notification/presentation/services/fcm_receiver.dart b/lib/features/push_notification/presentation/services/fcm_receiver.dart index 109217259..e9987fc57 100644 --- a/lib/features/push_notification/presentation/services/fcm_receiver.dart +++ b/lib/features/push_notification/presentation/services/fcm_receiver.dart @@ -3,11 +3,36 @@ import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/controller/fcm_message_controller.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_service.dart'; +Future? _backgroundInitFuture; + +Future _ensureBackgroundInitialized() { + return _backgroundInitFuture ??= (() async { + FcmService.instance.initialStreamController(); + FcmMessageController.instance.initialize(); + await FcmMessageController.instance + .initialAppConfig() + .timeout(const Duration(seconds: 10)); + await FcmMessageController.instance + .setUpSentryConfiguration() + .timeout(const Duration(seconds: 10)); + }()).catchError((Object error, StackTrace stackTrace) { + _backgroundInitFuture = null; + throw error; + }); +} + @pragma('vm:entry-point') Future handleFirebaseBackgroundMessage(RemoteMessage message) async { - FcmService.instance.initialStreamController(); - FcmMessageController.instance.initialize(); - FcmService.instance.handleFirebaseBackgroundMessage(message); + try { + await _ensureBackgroundInitialized(); + FcmService.instance.handleFirebaseBackgroundMessage(message); + } catch (e, st) { + logError( + 'FcmReceiver::handleFirebaseBackgroundMessage: throw exception', + exception: e, + stackTrace: st, + ); + } } class FcmReceiver { @@ -34,21 +59,34 @@ class FcmReceiver { final token = await FirebaseMessaging.instance.getToken(); log('FcmReceiver::_getInitialToken:token: $token'); return token; - } catch (e) { - logWarning('FcmReceiver::_getInitialToken: TYPE = ${e.runtimeType} | Exception = $e'); + } catch (e, st) { + logError( + 'FcmReceiver::_getInitialToken:', + exception: e, + stackTrace: st, + ); return null; } } Future _onHandleFcmToken() async { - final token = await _getInitialToken(); - FcmService.instance.handleToken(token); + var currentToken = await _getInitialToken(); + FcmService.instance.handleToken(currentToken); - FirebaseMessaging.instance.onTokenRefresh.listen((newToken) { - log('FcmReceiver::_onHandleFcmToken:onTokenRefresh: $newToken'); - if (newToken != token) { - FcmService.instance.handleToken(newToken); + FirebaseMessaging.instance.onTokenRefresh.listen( + (newToken) { + if (newToken != currentToken) { + currentToken = newToken; + FcmService.instance.handleToken(newToken); + } + }, + onError: (e, st) { + logError( + 'FcmReceiver::_onHandleFcmToken:onTokenRefresh:', + exception: e, + stackTrace: st, + ); } - }); + ); } } \ No newline at end of file diff --git a/lib/features/push_notification/presentation/services/fcm_service.dart b/lib/features/push_notification/presentation/services/fcm_service.dart index 2906bf981..a9ebbcabf 100644 --- a/lib/features/push_notification/presentation/services/fcm_service.dart +++ b/lib/features/push_notification/presentation/services/fcm_service.dart @@ -33,11 +33,24 @@ class FcmService { fcmTokenStreamController = StreamController.broadcast(); } - void closeStream() { - backgroundMessageStreamController?.close(); - fcmTokenStreamController?.close(); - - backgroundMessageStreamController = null; - fcmTokenStreamController = null; + Future closeStream() async { + try { + await backgroundMessageStreamController?.close(); + } catch (e) { + logWarning( + 'FcmService::closeStream: backgroundMessageStreamController throw exception: $e', + ); + } finally { + backgroundMessageStreamController = null; + } + try { + await fcmTokenStreamController?.close(); + } catch (e) { + logWarning( + 'FcmService::closeStream: fcmTokenStreamController throw exception: $e', + ); + } finally { + fcmTokenStreamController = null; + } } } \ No newline at end of file diff --git a/lib/main/bindings/local/local_bindings.dart b/lib/main/bindings/local/local_bindings.dart index 52127a69a..2a4a52377 100644 --- a/lib/main/bindings/local/local_bindings.dart +++ b/lib/main/bindings/local/local_bindings.dart @@ -1,5 +1,6 @@ import 'package:core/utils/file_utils.dart'; +import 'package:core/utils/platform_info.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -19,9 +20,12 @@ import 'package:tmail_ui_user/features/caching/clients/recent_login_url_cache_cl import 'package:tmail_ui_user/features/caching/clients/recent_login_username_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/recent_search_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/sending_email_hive_cache_client.dart'; +import 'package:tmail_ui_user/features/caching/clients/sentry_configuration_cache_client.dart'; +import 'package:tmail_ui_user/features/caching/clients/sentry_user_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/session_hive_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/state_cache_client.dart'; import 'package:tmail_ui_user/features/caching/clients/token_oidc_cache_client.dart'; +import 'package:tmail_ui_user/features/caching/manager/sentry_configuration_cache_manager.dart'; import 'package:tmail_ui_user/features/caching/manager/session_cache_manger.dart'; import 'package:tmail_ui_user/features/cleanup/data/local/recent_login_url_cache_manager.dart'; import 'package:tmail_ui_user/features/cleanup/data/local/recent_login_username_cache_manager.dart'; @@ -57,14 +61,27 @@ class LocalBindings extends Bindings { } void _bindingCaching() { + _bindingMailDataCache(); + _bindingAccountCache(); + _bindingRecentDataCache(); + _bindingFcmCache(); + _bindingOfflineCache(); + if (PlatformInfo.isMobile) _bindingSentryCache(); + _bindingCachingManager(); + } + + void _bindingMailDataCache() { Get.put(MailboxCacheClient()); + Get.put(MailboxCacheManager(Get.find())); Get.put(StateCacheClient()); Get.put(StateCacheManager(Get.find())); - Get.put(MailboxCacheManager(Get.find())); Get.put(EmailCacheClient()); Get.put(EmailCacheManager(Get.find())); - Get.put(RecentSearchCacheClient()); - Get.put(RecentSearchCacheManager(Get.find())); + Get.put(HiveCacheVersionClient(Get.find(), Get.find())); + Get.put(LocalSortOrderManager(Get.find())); + } + + void _bindingAccountCache() { Get.put(TokenOidcCacheClient()); Get.put(TokenOidcCacheManager(Get.find())); Get.put(AccountCacheClient()); @@ -77,14 +94,24 @@ class LocalBindings extends Bindings { Get.put(OidcConfigurationCacheManager(Get.find(), Get.find())); Get.put(LanguageCacheManager(Get.find())); Get.put(PreferencesSettingManager(Get.find())); + } + + void _bindingRecentDataCache() { + Get.put(RecentSearchCacheClient()); + Get.put(RecentSearchCacheManager(Get.find())); Get.put(RecentLoginUrlCacheClient()); - Get.put(RecentLoginUrlCacheManager((Get.find()))); + Get.put(RecentLoginUrlCacheManager(Get.find())); Get.put(RecentLoginUsernameCacheClient()); Get.put(RecentLoginUsernameCacheManager(Get.find())); + } + + void _bindingFcmCache() { Get.put(FirebaseRegistrationCacheClient()); Get.put(FcmCacheClient()); - Get.put(FCMCacheManager(Get.find(),Get.find())); - Get.put(HiveCacheVersionClient(Get.find(), Get.find())); + Get.put(FCMCacheManager(Get.find(), Get.find())); + } + + void _bindingOfflineCache() { Get.put(NewEmailHiveCacheClient()); Get.put(NewEmailCacheManager(Get.find(), Get.find())); Get.put(OpenedEmailHiveCacheClient()); @@ -93,7 +120,18 @@ class LocalBindings extends Bindings { Get.put(SendingEmailCacheManager(Get.find())); Get.put(SessionHiveCacheClient()); Get.put(SessionCacheManager(Get.find())); - Get.put(LocalSortOrderManager(Get.find())); + } + + void _bindingSentryCache() { + Get.put(SentryConfigurationCacheClient()); + Get.put(SentryUserCacheClient()); + Get.put(SentryConfigurationCacheManager( + Get.find(), + Get.find(), + )); + } + + void _bindingCachingManager() { Get.put(CachingManager( Get.find(), Get.find(), @@ -114,6 +152,9 @@ class LocalBindings extends Bindings { Get.find(), Get.find(), Get.find(), + sentryConfigurationCacheManager: PlatformInfo.isMobile + ? Get.find() + : null, )); }