TF-4268 Cache Sentry config and user for FCM background re-initialization

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2026-04-09 16:22:37 +07:00
committed by Dat H. Pham
parent cb5d43630a
commit eeaab49e93
20 changed files with 755 additions and 183 deletions
+10 -9
View File
@@ -14,10 +14,12 @@ class SentryConfig {
// Current app release version // Current app release version
final String release; 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; 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; final double profilesSampleRate;
// Enable logs to be sent to Sentry. To use Sentry.logger.fmt // Enable logs to be sent to Sentry. To use Sentry.logger.fmt
@@ -50,8 +52,8 @@ class SentryConfig {
required this.dsn, required this.dsn,
required this.environment, required this.environment,
required this.release, required this.release,
this.tracesSampleRate = 1.0, this.tracesSampleRate = 0.1,
this.profilesSampleRate = 1.0, this.profilesSampleRate = 0.1,
this.sessionSampleRate = 1.0, this.sessionSampleRate = 1.0,
this.onErrorSampleRate = 1.0, this.onErrorSampleRate = 1.0,
this.enableLogs = true, this.enableLogs = true,
@@ -71,11 +73,10 @@ class SentryConfig {
final sentryDSN = dotenv.get('SENTRY_DSN', fallback: ''); final sentryDSN = dotenv.get('SENTRY_DSN', fallback: '');
final sentryEnvironment = dotenv.get('SENTRY_ENVIRONMENT', fallback: ''); final sentryEnvironment = dotenv.get('SENTRY_ENVIRONMENT', fallback: '');
if (!isAvailable || final isConfigValid = isAvailable
sentryDSN.trim().isEmpty || && sentryDSN.trim().isNotEmpty
sentryEnvironment.trim().isEmpty) { && sentryEnvironment.trim().isNotEmpty;
return null; if (!isConfigValid) return null;
}
final appVersion = await ApplicationManager().getAppVersion(); final appVersion = await ApplicationManager().getAppVersion();
+7 -2
View File
@@ -3,6 +3,7 @@ import 'package:core/utils/file_utils.dart';
import 'package:core/utils/platform_info.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/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/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/manager/session_cache_manger.dart';
import 'package:tmail_ui_user/features/caching/utils/caching_constants.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'; 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 OidcConfigurationCacheManager _oidcConfigurationCacheManager;
final EncryptionKeyCacheManager _encryptionKeyCacheManager; final EncryptionKeyCacheManager _encryptionKeyCacheManager;
final AuthenticationInfoCacheManager _authenticationInfoCacheManager; final AuthenticationInfoCacheManager _authenticationInfoCacheManager;
final SentryConfigurationCacheManager? _sentryConfigurationCacheManager;
CachingManager( CachingManager(
this._mailboxCacheManager, this._mailboxCacheManager,
@@ -62,8 +64,9 @@ class CachingManager {
this._tokenOidcCacheManager, this._tokenOidcCacheManager,
this._oidcConfigurationCacheManager, this._oidcConfigurationCacheManager,
this._encryptionKeyCacheManager, this._encryptionKeyCacheManager,
this._authenticationInfoCacheManager, this._authenticationInfoCacheManager, {
); SentryConfigurationCacheManager? sentryConfigurationCacheManager,
}) : _sentryConfigurationCacheManager = sentryConfigurationCacheManager;
Future<void> clearAll() async { Future<void> clearAll() async {
try { try {
@@ -104,6 +107,8 @@ class CachingManager {
_oidcConfigurationCacheManager.clear(), _oidcConfigurationCacheManager.clear(),
_tokenOidcCacheManager.clear(), _tokenOidcCacheManager.clear(),
_authenticationInfoCacheManager.clear(), _authenticationInfoCacheManager.clear(),
if (PlatformInfo.isMobile && _sentryConfigurationCacheManager != null)
_sentryConfigurationCacheManager!.clearSentryConfiguration(),
], eagerError: true); ], eagerError: true);
} catch (e) { } catch (e) {
logWarning('CachingManager::clearAccountDataCached: Cannot clear account data cache: $e'); logWarning('CachingManager::clearAccountDataCached: Cannot clear account data cache: $e');
@@ -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<SentryConfigurationCache> {
@override
String get tableName => CachingConstants.sentryConfigurationCacheBoxName;
@override
bool get encryption => true;
}
@@ -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<SentryUserCache> {
@override
String get tableName => CachingConstants.sentryUserCacheBoxName;
@override
bool get encryption => true;
}
@@ -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/caching_manager.dart';
import 'package:tmail_ui_user/features/caching/config/cache_version.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/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/caching/utils/caching_constants.dart';
import 'package:tmail_ui_user/features/home/data/model/session_hive_obj.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'; 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, CachingConstants.OIDC_CONFIGURATION_CACHE_ID,
isolated: isolated, isolated: isolated,
); );
registerCacheAdapter<SentryConfigurationCache>(
SentryConfigurationCacheAdapter(),
CachingConstants.SENTRY_CONFIGURATION_CACHE_ID,
isolated: isolated,
);
registerCacheAdapter<SentryUserCache>(
SentryUserCacheAdapter(),
CachingConstants.SENTRY_USER_CACHE_ID,
isolated: isolated,
);
} }
void registerCacheAdapter<T>( void registerCacheAdapter<T>(
@@ -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<Object?> get props => [
dsn,
environment,
release,
tracesSampleRate,
profilesSampleRate,
enableLogs,
isDebug,
attachScreenshot,
isAvailable,
sessionSampleRate,
onErrorSampleRate,
enableFramesTracking,
dist,
];
}
@@ -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<Object?> get props => [
id,
name,
username,
email,
];
}
@@ -6,3 +6,17 @@ class NotFoundDataWithThisKeyException extends AppBaseException {
@override @override
String get exceptionName => 'NotFoundDataWithThisKeyException'; 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';
}
@@ -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,
);
}
}
@@ -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<SentryConfigurationCache> getSentryConfiguration() async {
final cache = await _configurationCacheClient.getItem(_configurationCacheKey);
if (cache == null) throw const NotFoundSentryConfigurationException();
return cache;
}
Future<void> saveSentryConfiguration(
SentryConfigurationCache sentryConfigurationCache,
) async {
await _configurationCacheClient.insertItem(
_configurationCacheKey,
sentryConfigurationCache,
);
}
Future<SentryUserCache> getSentryUser() async {
final cache = await _userCacheClient.getItem(_userCacheKey);
if (cache == null) throw const NotFoundSentryUserException();
return cache;
}
Future<void> saveSentryUser(
SentryUserCache sentryUserCache,
) async {
await _userCacheClient.insertItem(_userCacheKey, sentryUserCache);
}
Future<void> 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,
);
}
}
}
@@ -20,6 +20,8 @@ class CachingConstants {
static const int SENDING_EMAIL_HIVE_CACHE_ID = 18; static const int SENDING_EMAIL_HIVE_CACHE_ID = 18;
static const int SESSION_HIVE_CACHE_ID = 19; static const int SESSION_HIVE_CACHE_ID = 19;
static const int OIDC_CONFIGURATION_CACHE_ID = 20; 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 fcmCacheBoxName = 'fcm_cache_box';
static const String newEmailCacheBoxName = 'new_email_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 sessionCacheBoxName = 'session_cache_box';
static const String firebaseRegistrationCacheBoxName = 'firebase_registration_cache_box'; static const String firebaseRegistrationCacheBoxName = 'firebase_registration_cache_box';
static const String oidcConfigurationCacheBoxName = 'oidc_configuration_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 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 newEmailsContentFolderName = 'new_emails';
static const String openedEmailContentFolderName = 'opened_email'; static const String openedEmailContentFolderName = 'opened_email';
@@ -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_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/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/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/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/download/download_task_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/draggable_app_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, AiScribeMixin,
SearchLabelFilterModalMixin, SearchLabelFilterModalMixin,
AddLabelToEmailMixin, AddLabelToEmailMixin,
HandleTeamMailboxMixin, HandleTeamMailboxMixin {
SentryEcosystemMixin {
SentryEcosystem? _sentryEcosystem;
final RemoveEmailDraftsInteractor _removeEmailDraftsInteractor = Get.find<RemoveEmailDraftsInteractor>(); final RemoveEmailDraftsInteractor _removeEmailDraftsInteractor = Get.find<RemoveEmailDraftsInteractor>();
final EmailReceiveManager _emailReceiveManager = Get.find<EmailReceiveManager>(); final EmailReceiveManager _emailReceiveManager = Get.find<EmailReceiveManager>();
@@ -403,6 +406,7 @@ class MailboxDashBoardController extends ReloadableController
@override @override
void onInit() { void onInit() {
if (PlatformInfo.isMobile) { if (PlatformInfo.isMobile) {
_sentryEcosystem = SentryEcosystem(getBinding<SentryConfigurationCacheManager>());
_registerReceivingFileSharingStream(); _registerReceivingFileSharingStream();
_registerDeepLinks(); _registerDeepLinks();
} }
@@ -415,6 +419,11 @@ class MailboxDashBoardController extends ReloadableController
super.onInit(); super.onInit();
} }
void initSentryUser(SentryUser? user) => _sentryEcosystem?.initUser(user);
Future<void> setUpSentry(SentryConfigLinagoraEcosystem ecosystemConfig) async =>
_sentryEcosystem?.setUp(ecosystemConfig);
@override @override
void onReady() { void onReady() {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
@@ -3477,6 +3486,7 @@ class MailboxDashBoardController extends ReloadableController
paywallController?.onClose(); paywallController?.onClose();
paywallController = null; paywallController = null;
cachedLinagoraEcosystem = null; cachedLinagoraEcosystem = null;
_sentryEcosystem = null;
_disposeWorkerObxVariables(); _disposeWorkerObxVariables();
super.onClose(); super.onClose();
} }
@@ -1,4 +1,5 @@
import 'package:core/utils/app_logger.dart'; 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: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/linagora_ecosystem/linagora_ecosystem.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_linagora_ecosystem_state.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) { void _setUpSentry(LinagoraEcosystem ecosystem) {
if (PlatformInfo.isWeb) return;
final sentryConfigEcosystem = ecosystem.sentryConfigEcosystem; final sentryConfigEcosystem = ecosystem.sentryConfigEcosystem;
if (sentryConfigEcosystem != null) { if (sentryConfigEcosystem != null) {
setUpSentry(sentryConfigEcosystem); setUpSentry(sentryConfigEcosystem);
@@ -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<void> 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!);
}
}
@@ -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<void> 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<void> _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((_) {});
}
}
}
@@ -6,6 +6,7 @@ import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/state/success.dart';
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.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/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/core/user_name.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:model/model.dart';
import 'package:rxdart/rxdart.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/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/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/state/get_session_state.dart';
import 'package:tmail_ui_user/features/home/domain/usecases/get_session_interactor.dart'; import 'package:tmail_ui_user/features/home/domain/usecases/get_session_interactor.dart';
@@ -48,10 +52,18 @@ class FcmMessageController extends PushBaseController {
@override @override
void initialize({AccountId? accountId, Session? session}) { void initialize({AccountId? accountId, Session? session}) {
super.initialize(accountId: accountId, session: session); try {
super.initialize(accountId: accountId, session: session);
_listenTokenStream(); _listenTokenStream();
_listenBackgroundMessageStream(); _listenBackgroundMessageStream();
} catch (e, st) {
logError(
'FcmMessageController::initialize: throw exception',
exception: e,
stackTrace: st,
);
}
} }
void _listenBackgroundMessageStream() { void _listenBackgroundMessageStream() {
@@ -59,36 +71,104 @@ class FcmMessageController extends PushBaseController {
.debounceTime(const Duration( .debounceTime(const Duration(
milliseconds: FcmUtils.durationBackgroundMessageComing, milliseconds: FcmUtils.durationBackgroundMessageComing,
)) ))
.listen(_handleBackgroundMessageAction); .listen(
_handleBackgroundMessageAction,
onError: (e, st) {
logError(
'FcmMessageController::_listenBackgroundMessageStream',
exception: e,
stackTrace: st,
);
},
);
} }
void _listenTokenStream() { void _listenTokenStream() {
FcmService.instance.fcmTokenStreamController FcmService.instance.fcmTokenStreamController
?.stream ?.stream
.debounceTime(const Duration(milliseconds: FcmUtils.durationRefreshToken)) .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<String, dynamic> payloadData) async { void _handleBackgroundMessageAction(Map<String, dynamic> payloadData) async {
log('FcmMessageController::_handleBackgroundMessageAction():payloadData: $payloadData'); logTrace('FcmMessageController::_handleBackgroundMessageAction():payloadData keys: ${payloadData.keys.toList()}');
final stateChange = FcmUtils.instance.convertFirebaseDataMessageToStateChange(payloadData); 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); _getAuthenticatedAccount(stateChange: stateChange);
} }
Future<void> _initialAppConfig() async { Future<void> initialAppConfig() async {
await Future.wait([ try {
MainBindings().dependencies(), await MainBindings().dependencies();
HiveCacheConfig.instance.setUp() await HiveCacheConfig.instance.setUp();
]);
await Future.sync(() {
HomeBindings().dependencies(); HomeBindings().dependencies();
MailboxDashBoardBindings().dependencies(); MailboxDashBoardBindings().dependencies();
FcmInteractorBindings().dependencies(); FcmInteractorBindings().dependencies();
});
_getInteractorBindings(); _getInteractorBindings();
} catch (e, st) {
logError(
'FcmMessageController::initialAppConfig: throw exception',
exception: e,
stackTrace: st,
);
rethrow;
}
}
Future<void> setUpSentryConfiguration() async {
try {
final cacheManager = getBinding<SentryConfigurationCacheManager>();
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() { void _getInteractorBindings() {
@@ -100,9 +180,13 @@ class FcmMessageController extends PushBaseController {
FcmTokenController.instance.initialBindingInteractor(); FcmTokenController.instance.initialBindingInteractor();
} }
void _getAuthenticatedAccount({StateChange? stateChange}) { void _getAuthenticatedAccount({required StateChange stateChange}) {
if (_getAuthenticatedAccountInteractor != null) { if (_getAuthenticatedAccountInteractor != null) {
consumeState(_getAuthenticatedAccountInteractor!.execute(stateChange: stateChange)); consumeState(_getAuthenticatedAccountInteractor!.execute(stateChange: stateChange));
} else {
logTrace(
'GetAuthenticatedAccountInteractor is null',
);
} }
} }
@@ -110,25 +194,18 @@ class FcmMessageController extends PushBaseController {
_dynamicUrlInterceptors?.setJmapUrl(storedTokenOidcSuccess.baseUrl.toString()); _dynamicUrlInterceptors?.setJmapUrl(storedTokenOidcSuccess.baseUrl.toString());
_authorizationInterceptors?.setTokenAndAuthorityOidc( _authorizationInterceptors?.setTokenAndAuthorityOidc(
newToken: storedTokenOidcSuccess.tokenOidc, newToken: storedTokenOidcSuccess.tokenOidc,
newConfig: storedTokenOidcSuccess.oidcConfiguration newConfig: storedTokenOidcSuccess.oidcConfiguration,
); );
if (PlatformInfo.isAndroid) { if (PlatformInfo.isAndroid) {
_dynamicUrlInterceptors?.changeBaseUrl(storedTokenOidcSuccess.baseUrl.toString()); _dynamicUrlInterceptors?.changeBaseUrl(storedTokenOidcSuccess.baseUrl.toString());
_getSessionAction(stateChange: storedTokenOidcSuccess.stateChange); _getSessionAction(stateChange: storedTokenOidcSuccess.stateChange);
} else { } else {
_dynamicUrlInterceptors?.changeBaseUrl(storedTokenOidcSuccess.personalAccount.apiUrl); _dispatchPushOnNonAndroid(
apiUrl: storedTokenOidcSuccess.personalAccount.apiUrl,
final accountId = storedTokenOidcSuccess.personalAccount.accountId; accountId: storedTokenOidcSuccess.personalAccount.accountId,
final username = storedTokenOidcSuccess.personalAccount.userName; username: storedTokenOidcSuccess.personalAccount.userName,
final stateChange = storedTokenOidcSuccess.stateChange; stateChange: storedTokenOidcSuccess.stateChange,
);
if (accountId != null && username != null && stateChange != null) {
_pushActionFromRemoteMessageBackground(
accountId: accountId,
userName: username,
stateChange: stateChange);
}
} }
} }
@@ -142,24 +219,39 @@ class FcmMessageController extends PushBaseController {
_dynamicUrlInterceptors?.changeBaseUrl(credentialViewState.baseUrl.toString()); _dynamicUrlInterceptors?.changeBaseUrl(credentialViewState.baseUrl.toString());
_getSessionAction(stateChange: credentialViewState.stateChange); _getSessionAction(stateChange: credentialViewState.stateChange);
} else { } else {
_dynamicUrlInterceptors?.changeBaseUrl(credentialViewState.personalAccount.apiUrl); _dispatchPushOnNonAndroid(
apiUrl: credentialViewState.personalAccount.apiUrl,
final accountId = credentialViewState.personalAccount.accountId; accountId: credentialViewState.personalAccount.accountId,
final username = credentialViewState.personalAccount.userName; username: credentialViewState.personalAccount.userName,
final stateChange = credentialViewState.stateChange; stateChange: credentialViewState.stateChange,
);
if (accountId != null && username != null && stateChange != null) {
_pushActionFromRemoteMessageBackground(
accountId: accountId,
userName: username,
stateChange: 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}) { void _getSessionAction({StateChange? stateChange}) {
if (_getSessionInteractor != null) { if (_getSessionInteractor != null) {
consumeState(_getSessionInteractor!.execute(stateChange: stateChange)); consumeState(_getSessionInteractor!.execute(stateChange: stateChange));
} else {
logTrace('FcmMessageController::_getSessionAction: _getSessionInteractor is null');
} }
} }
@@ -176,9 +268,17 @@ class FcmMessageController extends PushBaseController {
userName: success.session.username, userName: success.session.username,
stateChange: stateChange, stateChange: stateChange,
session: success.session); session: success.session);
} else {
logTrace(
'FcmMessageController::_handleGetSessionSuccess: Api url or state change is null',
);
} }
} catch (e) { } catch (e, st) {
logWarning('FcmMessageController::_handleGetSessionSuccess: Exception $e'); logError(
'FcmMessageController::_handleGetSessionSuccess:',
exception: e,
stackTrace: st,
);
} }
} }
@@ -189,7 +289,7 @@ class FcmMessageController extends PushBaseController {
Session? session Session? session
}) { }) {
final mapTypeState = stateChange.getMapTypeState(accountId); final mapTypeState = stateChange.getMapTypeState(accountId);
logTrace('FcmMessageController::_pushActionFromRemoteMessageBackground: Mapping type state to action ${mapTypeState.toString()}');
mappingTypeStateToAction( mappingTypeStateToAction(
mapTypeState, mapTypeState,
accountId, accountId,
@@ -204,6 +304,11 @@ class FcmMessageController extends PushBaseController {
@override @override
void handleFailureViewState(Failure failure) { void handleFailureViewState(Failure failure) {
log('FcmMessageController::_handleFailureViewState(): $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 @override
@@ -91,19 +91,9 @@ class EmailChangeListener extends ChangeListener {
log('EmailChangeListener::dispatchActions():actions: $actions'); log('EmailChangeListener::dispatchActions():actions: $actions');
for (var action in actions) { for (var action in actions) {
if (action is SynchronizeEmailOnForegroundAction) { if (action is SynchronizeEmailOnForegroundAction) {
if (PlatformInfo.isAndroid) { _onSynchronizeEmailOnForeground(action);
_handleRemoveNotificationWhenEmailMarkAsRead(action.newState, action.accountId, action.session);
}
_synchronizeEmailOnForegroundAction(action.newState);
if (PlatformInfo.isMobile) {
_getNewReceiveEmailFromNotificationAction(action.session, action.accountId, action.newState);
}
} else if (action is PushNotificationAction) { } else if (action is PushNotificationAction) {
_pushNotificationAction(action.newState, action.accountId, action.userName, action.session); _onPushNotification(action);
if (PlatformInfo.isAndroid) {
_getNewReceiveEmailFromNotificationAction(action.session, action.accountId, action.newState);
}
} else if (action is StoreEmailStateToRefreshAction) { } else if (action is StoreEmailStateToRefreshAction) {
if (PlatformInfo.isAndroid) { if (PlatformInfo.isAndroid) {
_handleRemoveNotificationWhenEmailMarkAsRead(action.newState, action.accountId, action.session); _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) { void _synchronizeEmailOnForegroundAction(jmap.State newState) {
log('EmailChangeListener::_synchronizeEmailAction():newState: $newState'); log('EmailChangeListener::_synchronizeEmailAction():newState: $newState');
_dashBoardController?.dispatchEmailUIAction(RefreshChangeEmailAction( _dashBoardController?.dispatchEmailUIAction(RefreshChangeEmailAction(
@@ -142,27 +149,29 @@ class EmailChangeListener extends ChangeListener {
} }
void _getStoredEmailState() { void _getStoredEmailState() {
if (_getStoredEmailStateInteractor != null && _session != null && _accountId != null) { final canExecute = _getStoredEmailStateInteractor != null
consumeState(_getStoredEmailStateInteractor!.execute(_session!, _accountId!)); && _session != null
} && _accountId != null;
if (!canExecute) return;
consumeState(_getStoredEmailStateInteractor!.execute(_session!, _accountId!));
} }
void _getEmailChangesAction(jmap.State state) { void _getEmailChangesAction(jmap.State state) {
if (_getEmailChangesToPushNotificationInteractor != null && final canExecute = _getEmailChangesToPushNotificationInteractor != null
_accountId != null && && _accountId != null
_session != null && && _session != null
_userName != null) { && _userName != null;
consumeState(_getEmailChangesToPushNotificationInteractor!.execute( if (!canExecute) return;
consumeState(_getEmailChangesToPushNotificationInteractor!.execute(
_session!,
_accountId!,
_userName!,
state,
propertiesCreated: EmailUtils.getPropertiesForEmailGetMethod(
_session!, _session!,
_accountId!, _accountId!,
_userName!, ),
state, ));
propertiesCreated: EmailUtils.getPropertiesForEmailGetMethod(
_session!,
_accountId!,
),
));
}
} }
void _storeEmailDeliveryStateAction(AccountId accountId, UserName userName, jmap.State state) { void _storeEmailDeliveryStateAction(AccountId accountId, UserName userName, jmap.State state) {
@@ -205,19 +214,12 @@ class EmailChangeListener extends ChangeListener {
@override @override
void handleSuccessViewState(Success success) { void handleSuccessViewState(Success success) {
log('EmailChangeListener::_handleSuccessViewState(): $success'); log('EmailChangeListener::_handleSuccessViewState(): $success');
if (success is GetStoredEmailDeliveryStateSuccess && _newStateEmailDelivery != success.state) { if (success is GetStoredEmailDeliveryStateSuccess) {
_getEmailChangesAction(success.state); _handleGetStoredEmailDeliveryStateSuccess(success);
} else if (success is GetStoredEmailStateSuccess) { } else if (success is GetStoredEmailStateSuccess) {
_getEmailChangesAction(success.state); _getEmailChangesAction(success.state);
} else if (success is GetEmailChangesToPushNotificationSuccess && _newStateEmailDelivery != null) { } else if (success is GetEmailChangesToPushNotificationSuccess) {
_storeEmailDeliveryStateAction(success.accountId, success.userName, _newStateEmailDelivery!); _handleGetEmailChangesToPushNotificationSuccess(success);
if (PlatformInfo.isAndroid) {
_handleListEmailToPushNotification(
userName: success.userName,
emailList: success.emailList
);
}
} else if (success is GetMailboxesNotPutNotificationsSuccess) { } else if (success is GetMailboxesNotPutNotificationsSuccess) {
final listEmails = _emailsAvailablePushNotification.toEmailsAvailablePushNotification( final listEmails = _emailsAvailablePushNotification.toEmailsAvailablePushNotification(
mailboxIdsNotPutNotifications: success.mailboxes.mailboxIds); 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({ void _handleListEmailToPushNotification({
required UserName userName, required UserName userName,
required List<PresentationEmail> emailList required List<PresentationEmail> emailList
}) { }) {
_emailsAvailablePushNotification = 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!)); consumeState(_getMailboxesNotPutNotificationsInteractor!.execute(_session!, _accountId!));
} else { } else {
final listEmails = _emailsAvailablePushNotification.toEmailsAvailablePushNotification(); final listEmails = _emailsAvailablePushNotification.toEmailsAvailablePushNotification();
_handleLocalPushNotification( _handleLocalPushNotification(
userName: userName, userName: userName,
emailList: listEmails emailList: listEmails,
); );
} }
} }
@@ -319,21 +347,21 @@ class EmailChangeListener extends ChangeListener {
void _getListDetailedEmailByIdAction(Session? session, AccountId accountId, List<EmailId> emailIds) { void _getListDetailedEmailByIdAction(Session? session, AccountId accountId, List<EmailId> emailIds) {
log('EmailChangeListener::_getListDetailedEmailByIdAction():emailIds: $emailIds'); log('EmailChangeListener::_getListDetailedEmailByIdAction():emailIds: $emailIds');
if (_getListDetailedEmailByIdInteractor != null && final canExecute = _getListDetailedEmailByIdInteractor != null
_dynamicUrlInterceptors != null && && _dynamicUrlInterceptors != null
session != null) { && session != null;
try { if (!canExecute) return;
final baseDownloadUrl = session.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors!.jmapUrl); try {
consumeState(_getListDetailedEmailByIdInteractor!.execute( final baseDownloadUrl = session.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors!.jmapUrl);
session, consumeState(_getListDetailedEmailByIdInteractor!.execute(
accountId, session,
emailIds, accountId,
baseDownloadUrl emailIds,
)); baseDownloadUrl,
} catch (e) { ));
logWarning('EmailChangeListener::_getListDetailedEmailByIdAction(): $e'); } catch (e) {
consumeState(Stream.value(Left(GetDetailedEmailByIdFailure(e)))); logWarning('EmailChangeListener::_getListDetailedEmailByIdAction(): $e');
} consumeState(Stream.value(Left(GetDetailedEmailByIdFailure(e))));
} }
} }
@@ -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/controller/fcm_message_controller.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_service.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/services/fcm_service.dart';
Future<void>? _backgroundInitFuture;
Future<void> _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') @pragma('vm:entry-point')
Future<void> handleFirebaseBackgroundMessage(RemoteMessage message) async { Future<void> handleFirebaseBackgroundMessage(RemoteMessage message) async {
FcmService.instance.initialStreamController(); try {
FcmMessageController.instance.initialize(); await _ensureBackgroundInitialized();
FcmService.instance.handleFirebaseBackgroundMessage(message); FcmService.instance.handleFirebaseBackgroundMessage(message);
} catch (e, st) {
logError(
'FcmReceiver::handleFirebaseBackgroundMessage: throw exception',
exception: e,
stackTrace: st,
);
}
} }
class FcmReceiver { class FcmReceiver {
@@ -34,21 +59,34 @@ class FcmReceiver {
final token = await FirebaseMessaging.instance.getToken(); final token = await FirebaseMessaging.instance.getToken();
log('FcmReceiver::_getInitialToken:token: $token'); log('FcmReceiver::_getInitialToken:token: $token');
return token; return token;
} catch (e) { } catch (e, st) {
logWarning('FcmReceiver::_getInitialToken: TYPE = ${e.runtimeType} | Exception = $e'); logError(
'FcmReceiver::_getInitialToken:',
exception: e,
stackTrace: st,
);
return null; return null;
} }
} }
Future _onHandleFcmToken() async { Future _onHandleFcmToken() async {
final token = await _getInitialToken(); var currentToken = await _getInitialToken();
FcmService.instance.handleToken(token); FcmService.instance.handleToken(currentToken);
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) { FirebaseMessaging.instance.onTokenRefresh.listen(
log('FcmReceiver::_onHandleFcmToken:onTokenRefresh: $newToken'); (newToken) {
if (newToken != token) { if (newToken != currentToken) {
FcmService.instance.handleToken(newToken); currentToken = newToken;
FcmService.instance.handleToken(newToken);
}
},
onError: (e, st) {
logError(
'FcmReceiver::_onHandleFcmToken:onTokenRefresh:',
exception: e,
stackTrace: st,
);
} }
}); );
} }
} }
@@ -33,11 +33,24 @@ class FcmService {
fcmTokenStreamController = StreamController<String?>.broadcast(); fcmTokenStreamController = StreamController<String?>.broadcast();
} }
void closeStream() { Future<void> closeStream() async {
backgroundMessageStreamController?.close(); try {
fcmTokenStreamController?.close(); await backgroundMessageStreamController?.close();
} catch (e) {
backgroundMessageStreamController = null; logWarning(
fcmTokenStreamController = null; '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;
}
} }
} }
+48 -7
View File
@@ -1,5 +1,6 @@
import 'package:core/utils/file_utils.dart'; 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:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.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_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/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/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/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/state_cache_client.dart';
import 'package:tmail_ui_user/features/caching/clients/token_oidc_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/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_url_cache_manager.dart';
import 'package:tmail_ui_user/features/cleanup/data/local/recent_login_username_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() { void _bindingCaching() {
_bindingMailDataCache();
_bindingAccountCache();
_bindingRecentDataCache();
_bindingFcmCache();
_bindingOfflineCache();
if (PlatformInfo.isMobile) _bindingSentryCache();
_bindingCachingManager();
}
void _bindingMailDataCache() {
Get.put(MailboxCacheClient()); Get.put(MailboxCacheClient());
Get.put(MailboxCacheManager(Get.find<MailboxCacheClient>()));
Get.put(StateCacheClient()); Get.put(StateCacheClient());
Get.put(StateCacheManager(Get.find<StateCacheClient>())); Get.put(StateCacheManager(Get.find<StateCacheClient>()));
Get.put(MailboxCacheManager(Get.find<MailboxCacheClient>()));
Get.put(EmailCacheClient()); Get.put(EmailCacheClient());
Get.put(EmailCacheManager(Get.find<EmailCacheClient>())); Get.put(EmailCacheManager(Get.find<EmailCacheClient>()));
Get.put(RecentSearchCacheClient()); Get.put(HiveCacheVersionClient(Get.find<SharedPreferences>(), Get.find<CacheExceptionThrower>()));
Get.put(RecentSearchCacheManager(Get.find<RecentSearchCacheClient>())); Get.put(LocalSortOrderManager(Get.find<SharedPreferences>()));
}
void _bindingAccountCache() {
Get.put(TokenOidcCacheClient()); Get.put(TokenOidcCacheClient());
Get.put(TokenOidcCacheManager(Get.find<TokenOidcCacheClient>())); Get.put(TokenOidcCacheManager(Get.find<TokenOidcCacheClient>()));
Get.put(AccountCacheClient()); Get.put(AccountCacheClient());
@@ -77,14 +94,24 @@ class LocalBindings extends Bindings {
Get.put(OidcConfigurationCacheManager(Get.find<SharedPreferences>(), Get.find<OidcConfigurationCacheClient>())); Get.put(OidcConfigurationCacheManager(Get.find<SharedPreferences>(), Get.find<OidcConfigurationCacheClient>()));
Get.put(LanguageCacheManager(Get.find<SharedPreferences>())); Get.put(LanguageCacheManager(Get.find<SharedPreferences>()));
Get.put(PreferencesSettingManager(Get.find<SharedPreferences>())); Get.put(PreferencesSettingManager(Get.find<SharedPreferences>()));
}
void _bindingRecentDataCache() {
Get.put(RecentSearchCacheClient());
Get.put(RecentSearchCacheManager(Get.find<RecentSearchCacheClient>()));
Get.put(RecentLoginUrlCacheClient()); Get.put(RecentLoginUrlCacheClient());
Get.put(RecentLoginUrlCacheManager((Get.find<RecentLoginUrlCacheClient>()))); Get.put(RecentLoginUrlCacheManager(Get.find<RecentLoginUrlCacheClient>()));
Get.put(RecentLoginUsernameCacheClient()); Get.put(RecentLoginUsernameCacheClient());
Get.put(RecentLoginUsernameCacheManager(Get.find<RecentLoginUsernameCacheClient>())); Get.put(RecentLoginUsernameCacheManager(Get.find<RecentLoginUsernameCacheClient>()));
}
void _bindingFcmCache() {
Get.put(FirebaseRegistrationCacheClient()); Get.put(FirebaseRegistrationCacheClient());
Get.put(FcmCacheClient()); Get.put(FcmCacheClient());
Get.put(FCMCacheManager(Get.find<FcmCacheClient>(),Get.find<FirebaseRegistrationCacheClient>())); Get.put(FCMCacheManager(Get.find<FcmCacheClient>(), Get.find<FirebaseRegistrationCacheClient>()));
Get.put(HiveCacheVersionClient(Get.find<SharedPreferences>(), Get.find<CacheExceptionThrower>())); }
void _bindingOfflineCache() {
Get.put(NewEmailHiveCacheClient()); Get.put(NewEmailHiveCacheClient());
Get.put(NewEmailCacheManager(Get.find<NewEmailHiveCacheClient>(), Get.find<FileUtils>())); Get.put(NewEmailCacheManager(Get.find<NewEmailHiveCacheClient>(), Get.find<FileUtils>()));
Get.put(OpenedEmailHiveCacheClient()); Get.put(OpenedEmailHiveCacheClient());
@@ -93,7 +120,18 @@ class LocalBindings extends Bindings {
Get.put(SendingEmailCacheManager(Get.find<SendingEmailHiveCacheClient>())); Get.put(SendingEmailCacheManager(Get.find<SendingEmailHiveCacheClient>()));
Get.put(SessionHiveCacheClient()); Get.put(SessionHiveCacheClient());
Get.put(SessionCacheManager(Get.find<SessionHiveCacheClient>())); Get.put(SessionCacheManager(Get.find<SessionHiveCacheClient>()));
Get.put(LocalSortOrderManager(Get.find<SharedPreferences>())); }
void _bindingSentryCache() {
Get.put(SentryConfigurationCacheClient());
Get.put(SentryUserCacheClient());
Get.put(SentryConfigurationCacheManager(
Get.find<SentryConfigurationCacheClient>(),
Get.find<SentryUserCacheClient>(),
));
}
void _bindingCachingManager() {
Get.put(CachingManager( Get.put(CachingManager(
Get.find<MailboxCacheManager>(), Get.find<MailboxCacheManager>(),
Get.find<StateCacheManager>(), Get.find<StateCacheManager>(),
@@ -114,6 +152,9 @@ class LocalBindings extends Bindings {
Get.find<OidcConfigurationCacheManager>(), Get.find<OidcConfigurationCacheManager>(),
Get.find<EncryptionKeyCacheManager>(), Get.find<EncryptionKeyCacheManager>(),
Get.find<AuthenticationInfoCacheManager>(), Get.find<AuthenticationInfoCacheManager>(),
sentryConfigurationCacheManager: PlatformInfo.isMobile
? Get.find<SentryConfigurationCacheManager>()
: null,
)); ));
} }