Skip to content

TF-4136 Add sentry for web#4137

Merged
hoangdat merged 12 commits intomasterfrom
improvement/tf-4136-add-sentry
Jan 15, 2026
Merged

TF-4136 Add sentry for web#4137
hoangdat merged 12 commits intomasterfrom
improvement/tf-4136-add-sentry

Conversation

@dab246
Copy link
Member

@dab246 dab246 commented Oct 31, 2025

Issue

Fixes #4136

Dependency

This PR depends on:

Description

This pull request integrates Sentry error reporting to capture, track, and analyze runtime issues across the Flutter app.
It includes detailed information for each reported event such as stack trace, breadcrumbs, contexts, and custom logs — helping developers debug production errors more efficiently.

Resolved

1. Issues Captured

Sentry successfully records exceptions triggered within the application:

Captured issues

2. Issue Details

Detailed event information is available, including device data, environment, and app version:

Issue detail

3. Stack Trace

Captured stack trace for debugging and pinpointing the root cause:

Stack trace

4. Breadcrumbs

Breadcrumbs show user actions and app events leading up to the error:

Breadcrumbs

5. Contexts: Reported Exception

Captured context includes runtime environment, user, and device information:

Contexts

6. Logs

Sentry integrates with Sentry.logger and captureMessage to record structured logs:

Logs

Summary by CodeRabbit

  • Chores

    • Bumped dio dependency and centralized app startup under a monitored wrapper with improved error capture and network wiring.
  • New Features

    • Added Sentry integration: initialization, reporting, user context, and optional network tracing.
    • Added environment-driven loader to control Sentry activation.
  • Documentation

    • Added Sentry configuration guide with env var instructions.
  • Tests

    • Updated tests to stop asserting content-length headers and to align with updated HTTP exception types.

✏️ Tip: You can customize this high-level summary in your review settings.

@dab246 dab246 force-pushed the improvement/tf-4136-add-sentry branch from 189ed2d to d7bcfe3 Compare October 31, 2025 09:15
@github-actions
Copy link

This PR has been deployed to https://linagora.github.io/tmail-flutter/4137.

@chibenwa
Copy link
Member

chibenwa commented Nov 3, 2025

Please document the expected linagora ecosystem values expected by the front.

Cc @Arsnael

@Arsnael
Copy link
Member

Arsnael commented Nov 4, 2025

Please document the expected linagora ecosystem values expected by the front.

cf linagora/tmail-backend#1988 , thanks

@quantranhong1999
Copy link
Member

Please document the expected linagora ecosystem values expected by the front.

It seems to be more TMail web configs instead cf configurations/sentry.env: https://github.com/linagora/tmail-flutter/pull/4137/files#diff-fe03c77c1eb4f4abaf1899888d097cff0409156c7a7b37cfb0458cc3c18fcf7f

Likely in Helm, we need to configure it as we did for the FCM env file.

@dab246 dab246 force-pushed the improvement/tf-4136-add-sentry branch from e41d493 to bdeb1a7 Compare January 12, 2026 03:04
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In @core/lib/utils/sentry/sentry_initializer.dart:
- Around line 54-59: The code assumes req.headers is non-null when building
sanitizedHeaders, but SentryRequest.headers is nullable; guard against null by
using a default empty map or checking for null before calling Map.from. Update
the sanitizedHeaders construction (use req.headers ?? {} or an early
null-coalescing) so Map<String, String>.from receives a non-null Map, then apply
the removeWhere filter using _blockedHeaderPatterns as before to produce the
sanitizedHeaders.

In @core/lib/utils/sentry/sentry_manager.dart:
- Around line 22-26: The initialize() method currently returns early when
_isSentryAvailable is true, which prevents the provided appRunner from
executing; modify initialize() so that appRunner() is always invoked regardless
of _isSentryAvailable (call await appRunner() before returning when Sentry is
already initialized), preserving existing try/catch and any Sentry setup path
for the first-time initialization; ensure you reference the initialize()
function, the _isSentryAvailable flag, and the appRunner parameter so the app
still starts on repeated calls.

In @lib/main/bindings/network/network_bindings.dart:
- Around line 105-112: The current duplicate check uses fragile string matching
on interceptor runtimeType; replace it with a proper type check against the
actual Sentry interceptor (e.g., detect SentryDioInterceptor via
interceptors.any((i) => i is SentryDioInterceptor) or
interceptors.whereType<SentryDioInterceptor>().isNotEmpty) to reliably guard
adding the interceptor in the block that calls dio.addSentry(); alternatively,
add a registration flag in SentryManager (e.g., a bool like isSentryRegistered)
and consult/set that flag when calling addSentry() to prevent duplicate
registration.

In @lib/main/bindings/network/network_isolate_binding.dart:
- Around line 65-66: The code calls Get.find<DynamicUrlInterceptors>() inside
NetworkIsolateBindings without registering it in the isolate scope, which can
break if main-scope bindings initialize in a different order; fix by registering
the interceptor in NetworkIsolateBindings (call
Get.put(DynamicUrlInterceptors(), tag: BindingTag.isolateTag)) or explicitly
resolve the isolate-tagged instance (Get.find<DynamicUrlInterceptors>(tag:
BindingTag.isolateTag)) before adding it to dio.interceptors so
DynamicUrlInterceptors is reliably available in the isolate binding.

In @lib/main/main_entry.dart:
- Around line 22-28: The code creates a throwaway Executor with
Executor().warmUp(...) while MainBindings().dependencies() registers an Executor
via Get.put(Executor()), so warm up the actual bound instance instead of a new
one: retrieve the registered Executor (the instance created by MainBindings,
e.g., via Get.find<Executor>() after binding or by binding the Executor before
calling Future.wait) and call warmUp(log: BuildUtils.isDebugMode) on that
instance, or move the binding of Executor() out of MainBindings and into the
setup sequence before warming so the warmed executor is the same one that gets
registered.
🧹 Nitpick comments (12)
core/lib/utils/config/env_loader.dart (1)

21-34: Consider adding a clarifying comment for the callback pattern.

The onCallBack is used to reload the base config when FCM config fails, which restores dotenv.env to a valid state. This fallback behavior is clever but not immediately obvious.

📝 Suggested documentation
   static Future<void> loadFcmConfigFileToEnv({
     Map<String, String>? currentMapEnvData,
     Future<void> Function()? onCallBack,
   }) async {
     try {
       await dotenv.load(
         fileName: appFCMConfigurationPath,
         mergeWith: currentMapEnvData ?? {},
       );
     } catch (e) {
       logWarning('EnvLoader::loadFcmConfigFileToEnv: Exception = $e');
+      // FCM config is optional; reload base config to restore dotenv.env state
       await onCallBack?.call();
     }
   }
core/lib/utils/application_manager.dart (1)

27-33: Consider resetting _isMobileUserAgentInitialized in clearCache().

The clearCache() method resets all cached values but doesn't reset _isMobileUserAgentInitialized. This could cause inconsistent state in tests where _cachedMobileUserAgent is cleared but _isMobileUserAgentInitialized remains true.

♻️ Suggested fix
   @visibleForTesting
   void clearCache() {
     _packageInfoCache = null;
     _versionCache = null;
     _cachedWebUserAgent = null;
     _cachedMobileUserAgent = null;
+    _isMobileUserAgentInitialized = false;
   }
docs/configuration/sentry_configuration.md (1)

1-39: Consider expanding documentation for completeness.

The configuration guide covers the essentials well. A few suggestions to improve it:

  1. Mobile platform note: The PR objectives mention that mobile Sentry configuration is hardcoded. Consider adding a note clarifying this is for web/Flutter web deployments, or documenting where mobile config resides.

  2. Privacy considerations: Based on team discussions, Sentry captures user PII (account ID, display name, email) for error tracking. Consider adding a note about this and referencing the privacy policy documentation.

  3. Example environment values: Providing example values for SENTRY_ENVIRONMENT (e.g., dev, staging, production) in the code block would help first-time users.

core/lib/utils/sentry/sentry_initializer.dart (2)

7-17: Good security practice: sanitizing sensitive headers.

The blocked header patterns cover common sensitive headers. Consider adding x-api-key (hyphenated variant) and session for broader coverage.

💡 Suggested additions for broader coverage
 static const _blockedHeaderPatterns = [
   'authorization',
   'cookie',
   'set-cookie',
   'x-auth',
   'x-token',
   'api-key',
   'apikey',
+  'x-api-key',
   'secret',
   'bearer',
+  'session',
+  'password',
 ];

70-85: Manual event reconstruction may lose fields added in future Sentry versions.

The comment notes copyWith is deprecated. However, manually reconstructing SentryEvent means any new fields added to SentryEvent in future Sentry SDK versions will be silently dropped. Consider periodically reviewing the Sentry SDK changelog or adding a comment to track this.

lib/main/bindings/network/network_isolate_binding.dart (1)

54-77: Duplicated Sentry registration logic across network bindings.

This interceptor setup mirrors network_bindings.dart exactly, including the fragile runtimeType.toString().contains('Sentry') check. Consider extracting a shared helper (e.g., SentryDioHelper.addIfAvailable(dio)) to avoid duplication and centralize the duplicate-guard logic.

♻️ Example shared helper
// In core or a shared utility file
class SentryDioHelper {
  static void addIfAvailable(Dio dio) {
    if (!SentryManager.instance.isSentryAvailable) return;
    
    final alreadyHasSentry = dio.interceptors
        .any((i) => i.runtimeType.toString().contains('Sentry'));
    if (!alreadyHasSentry) {
      dio.addSentry();
    }
  }
}

Then both bindings can call SentryDioHelper.addIfAvailable(dio);.

lib/main/main_entry.dart (1)

22-28: No error handling for parallel initialization failures.

If any task in Future.wait throws, the entire initialization fails without recovery or clear diagnostics. Consider wrapping with error handling or using Future.wait(..., eagerError: false) to let other tasks complete, then aggregate errors.

core/lib/utils/sentry/sentry_config.dart (2)

57-63: Using exceptions for expected control flow is non-idiomatic.

When SENTRY_ENABLED=false, throwing an exception forces the caller to use try/catch for normal control flow. Consider returning a nullable SentryConfig? or a result type, or providing a separate isEnabled() check.

♻️ Suggested approach
-  static Future<SentryConfig> load() async {
+  static Future<SentryConfig?> load() async {
     await EnvLoader.loadConfigFromEnv();
 
     final sentryAvailable = dotenv.get('SENTRY_ENABLED', fallback: 'false');
 
     final isAvailable = sentryAvailable == 'true';
     final sentryDSN = dotenv.get('SENTRY_DSN', fallback: '');
     final sentryEnvironment = dotenv.get('SENTRY_ENVIRONMENT', fallback: '');
 
-    if (!isAvailable) {
-      throw Exception('Sentry is not available');
-    }
-
-    if (sentryDSN.trim().isEmpty || sentryEnvironment.trim().isEmpty) {
-      throw Exception('Sentry configuration is missing');
+    if (!isAvailable || sentryDSN.trim().isEmpty || sentryEnvironment.trim().isEmpty) {
+      return null;
     }
     // ...
   }

39-40: Consider lower default sample rates for production.

Defaults of 1.0 (100%) for both tracesSampleRate and profilesSampleRate will capture every transaction and profile. This can be expensive in production and may impact quota. Typical production values are 0.1–0.2. Consider making these configurable via environment variables or using lower defaults.

lib/features/composer/data/repository/composer_repository_impl.dart (1)

75-75: Inline ApplicationManager() instantiation reduces testability.

Creating ApplicationManager() inline couples this method to the concrete implementation. Tests must now rely on ApplicationManager.debugDeviceInfoOverride to mock behavior, which is less explicit than constructor injection.

This is acceptable if the team prefers the static override pattern, but be aware it makes test setup less discoverable.

core/lib/utils/sentry/sentry_manager.dart (1)

47-56: Breadcrumb duplicates the captured event.

Adding the exception/message as a breadcrumb within withScope duplicates the event information. Breadcrumbs are typically for contextual events leading up to an error, not the error itself. Consider using breadcrumbs only for additional context in extras or removing this pattern.

Also applies to: 70-78

core/test/utils/application_manager_test.dart (1)

80-106: Consider verifying Android platform call count for consistency with web test.

The web test (lines 69-76) verifies webBrowserInfo is called only once to confirm caching. The Android test doesn't verify that the method channel handler is called only once. For consistency, consider tracking and asserting invocation count.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e41d493 and bdeb1a7.

⛔ Files ignored due to path filters (10)
  • contact/pubspec.lock is excluded by !**/*.lock
  • core/pubspec.lock is excluded by !**/*.lock
  • email_recovery/pubspec.lock is excluded by !**/*.lock
  • fcm/pubspec.lock is excluded by !**/*.lock
  • forward/pubspec.lock is excluded by !**/*.lock
  • model/pubspec.lock is excluded by !**/*.lock
  • pubspec.lock is excluded by !**/*.lock
  • rule_filter/pubspec.lock is excluded by !**/*.lock
  • scribe/pubspec.lock is excluded by !**/*.lock
  • server_settings/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (78)
  • contact/pubspec.yaml
  • contact/test/contact/autocomplete_tmail_contact_method_test.dart
  • core/lib/core.dart
  • core/lib/utils/app_logger.dart
  • core/lib/utils/application_manager.dart
  • core/lib/utils/config/env_loader.dart
  • core/lib/utils/sentry/sentry_config.dart
  • core/lib/utils/sentry/sentry_initializer.dart
  • core/lib/utils/sentry/sentry_manager.dart
  • core/lib/utils/string_convert.dart
  • core/pubspec.yaml
  • core/test/utils/application_manager_test.dart
  • docs/configuration/sentry_configuration.md
  • email_recovery/pubspec.yaml
  • email_recovery/test/method/get_email_recovery_action_method_test.dart
  • email_recovery/test/method/set_email_recovery_action_method_test.dart
  • env.file
  • fcm/pubspec.yaml
  • fcm/test/method/firebase_registration_get_method_test.dart
  • fcm/test/method/firebase_registration_set_method_test.dart
  • forward/pubspec.yaml
  • forward/test/forward/get_forward_method_test.dart
  • forward/test/forward/set_forward_method_test.dart
  • integration_test/base/test_base.dart
  • lib/features/base/base_controller.dart
  • lib/features/base/mixin/ai_scribe_mixin.dart
  • lib/features/base/widget/application_version_widget.dart
  • lib/features/composer/data/repository/composer_repository_impl.dart
  • lib/features/composer/presentation/composer_bindings.dart
  • lib/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart
  • lib/features/composer/presentation/widgets/mobile/mobile_editor_widget.dart
  • lib/features/composer/presentation/widgets/web/web_editor_widget.dart
  • lib/features/home/domain/extensions/session_extensions.dart
  • lib/features/login/data/network/interceptors/authorization_interceptors.dart
  • lib/features/login/data/network/oidc_http_client.dart
  • lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart
  • lib/features/manage_account/presentation/manage_account_dashboard_controller.dart
  • lib/features/public_asset/presentation/public_asset_bindings.dart
  • lib/features/upload/data/network/file_uploader.dart
  • lib/features/upload/domain/model/upload_attachment.dart
  • lib/main.dart
  • lib/main/app_runner.dart
  • lib/main/bindings/core/core_bindings.dart
  • lib/main/bindings/network/network_bindings.dart
  • lib/main/bindings/network/network_isolate_binding.dart
  • lib/main/exceptions/remote_exception_thrower.dart
  • lib/main/main_entry.dart
  • lib/main/utils/app_config.dart
  • lib/main/utils/app_utils.dart
  • lib/main/utils/toast_manager.dart
  • pubspec.yaml
  • rule_filter/pubspec.yaml
  • rule_filter/test/rule_filter/get_rule_filter_method_test.dart
  • rule_filter/test/rule_filter/set_rule_filter_method_test.dart
  • scribe/lib/scribe/ai/data/datasource_impl/ai_datasource_impl.dart
  • scribe/lib/scribe/ai/presentation/model/ai_capability.dart
  • scribe/pubspec.yaml
  • server_settings/pubspec.yaml
  • server_settings/test/server_settings/get/get_server_settings_method_test.dart
  • server_settings/test/server_settings/set/set_server_settings_method_test.dart
  • test/features/base/base_controller_test.dart
  • test/features/composer/data/repository/composer_repository_impl_test.dart
  • test/features/composer/presentation/composer_controller_test.dart
  • test/features/email/presentation/controller/single_email_controller_test.dart
  • test/features/home/presentation/home_controller_test.dart
  • test/features/identity_creator/presentation/identity_creator_controller_test.dart
  • test/features/interceptor/authorization_interceptor_test.dart
  • test/features/login/data/network/oidc_http_client_test.dart
  • test/features/login/presentation/login_controller_test.dart
  • test/features/mailbox_dashboard/presentation/controller/advanced_filter_controller_test.dart
  • test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart
  • test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart
  • test/features/manage_account/presentation/profiles/identities/identities_controller_test.dart
  • test/features/rule_filter_creator/presentation/rule_filter_creator_controller_test.dart
  • test/features/search/verify_before_time_in_search_email_filter_test.dart
  • test/features/server_settings/data/network/server_settings_api_test.dart
  • test/features/thread/data/network/thread_api_test.dart
  • test/features/thread/presentation/controller/thread_controller_test.dart
💤 Files with no reviewable changes (26)
  • fcm/test/method/firebase_registration_set_method_test.dart
  • email_recovery/test/method/set_email_recovery_action_method_test.dart
  • fcm/test/method/firebase_registration_get_method_test.dart
  • test/features/search/verify_before_time_in_search_email_filter_test.dart
  • test/features/thread/presentation/controller/thread_controller_test.dart
  • test/features/manage_account/presentation/profiles/identities/identities_controller_test.dart
  • rule_filter/test/rule_filter/set_rule_filter_method_test.dart
  • test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart
  • test/features/email/presentation/controller/single_email_controller_test.dart
  • contact/test/contact/autocomplete_tmail_contact_method_test.dart
  • lib/main/utils/app_config.dart
  • email_recovery/test/method/get_email_recovery_action_method_test.dart
  • test/features/login/presentation/login_controller_test.dart
  • lib/features/composer/presentation/composer_bindings.dart
  • test/features/home/presentation/home_controller_test.dart
  • test/features/composer/presentation/composer_controller_test.dart
  • test/features/server_settings/data/network/server_settings_api_test.dart
  • forward/test/forward/set_forward_method_test.dart
  • test/features/rule_filter_creator/presentation/rule_filter_creator_controller_test.dart
  • server_settings/test/server_settings/get/get_server_settings_method_test.dart
  • test/features/base/base_controller_test.dart
  • test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart
  • rule_filter/test/rule_filter/get_rule_filter_method_test.dart
  • lib/main/bindings/core/core_bindings.dart
  • server_settings/test/server_settings/set/set_server_settings_method_test.dart
  • forward/test/forward/get_forward_method_test.dart
🚧 Files skipped from review as they are similar to previous changes (17)
  • lib/features/composer/presentation/widgets/web/web_editor_widget.dart
  • forward/pubspec.yaml
  • lib/main/utils/toast_manager.dart
  • lib/main/exceptions/remote_exception_thrower.dart
  • rule_filter/pubspec.yaml
  • server_settings/pubspec.yaml
  • lib/main/utils/app_utils.dart
  • core/lib/core.dart
  • pubspec.yaml
  • lib/features/composer/presentation/extensions/ai_scribe/handle_ai_scribe_in_composer_extension.dart
  • test/features/login/data/network/oidc_http_client_test.dart
  • fcm/pubspec.yaml
  • email_recovery/pubspec.yaml
  • lib/features/composer/presentation/widgets/mobile/mobile_editor_widget.dart
  • lib/features/home/domain/extensions/session_extensions.dart
  • core/pubspec.yaml
  • scribe/lib/scribe/ai/data/datasource_impl/ai_datasource_impl.dart
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4137
File: lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart:32-32
Timestamp: 2025-12-15T06:24:50.369Z
Learning: In lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart and lib/features/manage_account/presentation/manage_account_dashboard_controller.dart, SentryManager.instance.setUser() intentionally sends PII (account ID, display name, username, email) to Sentry for error tracking. The team plans to document this in the app's privacy policy for transparency and compliance.
📚 Learning: 2025-12-12T11:00:57.625Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4205
File: core/lib/utils/app_logger.dart:80-86
Timestamp: 2025-12-12T11:00:57.625Z
Learning: In the tmail-flutter codebase, logging philosophy differs by platform: web applications use the `webConsoleEnabled` parameter in `app_logger.dart` to selectively display logs in production console, while mobile applications intentionally suppress all logs in release builds (enforced by `BuildUtils.isDebugMode` check in `_debugPrint`).

Applied to files:

  • lib/main/main_entry.dart
  • lib/main/app_runner.dart
  • core/lib/utils/application_manager.dart
  • lib/main.dart
📚 Learning: 2025-12-09T09:36:45.349Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4194
File: lib/features/manage_account/presentation/manage_account_dashboard_controller.dart:174-176
Timestamp: 2025-12-09T09:36:45.349Z
Learning: In Dart/Flutter projects using GetX, do not wrap getBinding<T>() calls in try/catch since they return null when not found. Only wrap Get.find<T>() calls in try/catch because they throw if a dependency is unavailable. When a binding is optional, consider checking Get.isRegistered<T>() or handling the null/exception path gracefully instead of blindly catching, and document the expectation for failure modes where a dependency may not be registered.

Applied to files:

  • lib/main/main_entry.dart
  • core/lib/utils/string_convert.dart
  • lib/main/app_runner.dart
  • lib/features/base/mixin/ai_scribe_mixin.dart
  • scribe/lib/scribe/ai/presentation/model/ai_capability.dart
  • lib/features/upload/domain/model/upload_attachment.dart
  • lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart
  • lib/features/base/widget/application_version_widget.dart
  • test/features/interceptor/authorization_interceptor_test.dart
  • lib/features/upload/data/network/file_uploader.dart
  • lib/features/login/data/network/oidc_http_client.dart
  • lib/main/bindings/network/network_isolate_binding.dart
  • test/features/thread/data/network/thread_api_test.dart
  • lib/features/public_asset/presentation/public_asset_bindings.dart
  • integration_test/base/test_base.dart
  • lib/features/login/data/network/interceptors/authorization_interceptors.dart
  • lib/features/manage_account/presentation/manage_account_dashboard_controller.dart
  • test/features/composer/data/repository/composer_repository_impl_test.dart
  • lib/features/composer/data/repository/composer_repository_impl.dart
  • core/lib/utils/app_logger.dart
  • test/features/identity_creator/presentation/identity_creator_controller_test.dart
  • core/lib/utils/application_manager.dart
  • core/lib/utils/sentry/sentry_config.dart
  • lib/main/bindings/network/network_bindings.dart
  • lib/features/base/base_controller.dart
  • core/test/utils/application_manager_test.dart
  • test/features/mailbox_dashboard/presentation/controller/advanced_filter_controller_test.dart
  • core/lib/utils/sentry/sentry_manager.dart
  • core/lib/utils/sentry/sentry_initializer.dart
  • core/lib/utils/config/env_loader.dart
  • lib/main.dart
📚 Learning: 2025-12-15T06:24:50.369Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4137
File: lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart:32-32
Timestamp: 2025-12-15T06:24:50.369Z
Learning: In lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart and lib/features/manage_account/presentation/manage_account_dashboard_controller.dart, SentryManager.instance.setUser() intentionally sends PII (account ID, display name, username, email) to Sentry for error tracking. The team plans to document this in the app's privacy policy for transparency and compliance.

Applied to files:

  • docs/configuration/sentry_configuration.md
  • lib/main/bindings/network/network_isolate_binding.dart
  • lib/features/manage_account/presentation/manage_account_dashboard_controller.dart
  • core/lib/utils/app_logger.dart
  • core/lib/utils/sentry/sentry_config.dart
  • lib/main/bindings/network/network_bindings.dart
  • lib/features/base/base_controller.dart
  • core/lib/utils/sentry/sentry_manager.dart
  • core/lib/utils/sentry/sentry_initializer.dart
📚 Learning: 2025-12-15T06:24:38.823Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4137
File: lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart:32-32
Timestamp: 2025-12-15T06:24:38.823Z
Learning: In lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart and lib/features/manage_account/presentation/manage_account_dashboard_controller.dart, the code calls SentryManager.instance.setUser() with PII fields (account ID, display name, username, email). This is privacy-sensitive. Review and ensure PII handling complies with policy: avoid sending unnecessary PII to Sentry, redact or hash sensitive fields if possible, or document explicit privacy policy and user consent for such data. If PII must be sent, ensure minimal data, secure handling, and add notes to the privacy policy. Consider adding tests or a lint rule to flag setUser calls that include PII.

Applied to files:

  • lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart
📚 Learning: 2025-12-12T04:54:11.121Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4191
File: lib/features/email/presentation/extensions/handle_email_action_extension.dart:37-80
Timestamp: 2025-12-12T04:54:11.121Z
Learning: In lib/features/email/presentation/extensions/handle_email_action_extension.dart, the mailboxDashBoardController.selectedEmail should only be synchronized when isMobileThreadDisabled is true. This is intentional behavior and should not be changed to update selectedEmail in non-mobile or thread-enabled contexts.

Applied to files:

  • lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart
  • test/features/composer/data/repository/composer_repository_impl_test.dart
  • test/features/mailbox_dashboard/presentation/controller/advanced_filter_controller_test.dart
📚 Learning: 2025-12-18T09:19:32.437Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4203
File: scribe/pubspec.yaml:37-37
Timestamp: 2025-12-18T09:19:32.437Z
Learning: In the tmail-flutter repository, json_annotation version 4.8.0 is the standardized version across modules and should not be upgraded without considering the standardization practice.

Applied to files:

  • contact/pubspec.yaml
  • lib/main/bindings/network/network_isolate_binding.dart
  • lib/features/login/data/network/interceptors/authorization_interceptors.dart
  • test/features/composer/data/repository/composer_repository_impl_test.dart
  • core/lib/utils/app_logger.dart
  • lib/main/bindings/network/network_bindings.dart
  • core/lib/utils/config/env_loader.dart
📚 Learning: 2025-12-12T07:56:31.877Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4204
File: lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart:579-582
Timestamp: 2025-12-12T07:56:31.877Z
Learning: In lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart, the addLabelToEmail method intentionally throws UnimplementedError because label synchronization with the email cache is handled separately via extension methods (e.g., _autoSyncLabelToSelectedEmailOnMemory in handle_label_for_email_extension.dart). Implementing it in the cache datasource would cause data conflicts. This differs from other keyword operations like markAsRead, markAsStar, markAsAnswered, and markAsForwarded, which are implemented directly in the cache datasource.

Applied to files:

  • lib/features/public_asset/presentation/public_asset_bindings.dart
📚 Learning: 2025-12-23T05:27:47.287Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4210
File: lib/l10n/intl_messages.arb:5151-5182
Timestamp: 2025-12-23T05:27:47.287Z
Learning: In the tmail-flutter repository, localization (l10n) updates for locale files (intl_fr.arb, intl_vi.arb, intl_ru.arb, etc.) are managed automatically through a third-party website/platform. When new English keys are added to intl_messages.arb, translations to other locales are handled by this automated workflow rather than being manually added in pull requests.

Applied to files:

  • lib/features/login/data/network/interceptors/authorization_interceptors.dart
  • core/lib/utils/app_logger.dart
  • core/lib/utils/config/env_loader.dart
📚 Learning: 2025-12-23T05:27:10.887Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4210
File: model/lib/mailbox/presentation_mailbox.dart:22-34
Timestamp: 2025-12-23T05:27:10.887Z
Learning: In the tmail-flutter codebase, the Action Required folder (actionRequiredFolder) is a virtual folder that is AI-curated and contains emails with the keyword "needs-action". Unlike the Favorites folder, users should NOT be able to drag emails into the Action Required folder. The UI correctly prevents this by not wrapping the Action Required folder item in a DragTarget (see mailbox_item_widget.dart).

Applied to files:

  • lib/features/login/data/network/interceptors/authorization_interceptors.dart
  • core/lib/utils/app_logger.dart
  • test/features/mailbox_dashboard/presentation/controller/advanced_filter_controller_test.dart
📚 Learning: 2025-12-12T11:00:52.914Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4205
File: core/lib/utils/app_logger.dart:80-86
Timestamp: 2025-12-12T11:00:52.914Z
Learning: In core/lib/utils/app_logger.dart, implement platform-specific logging behavior as described: web applications should respect a webConsoleEnabled flag to selectively show logs in production, while mobile platforms must suppress logs in release builds by guarding _debugPrint with BuildUtils.isDebugMode. Ensure the logging calls are conditioned on these checks and verify via builds for web and mobile (debug vs release).

Applied to files:

  • core/lib/utils/app_logger.dart
📚 Learning: 2025-12-12T09:03:05.962Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4204
File: lib/features/email/presentation/model/popup_menu_item_email_action.dart:34-35
Timestamp: 2025-12-12T09:03:05.962Z
Learning: In the tmail-flutter codebase, popup menu items intentionally use a consistent hoverIcon (imagePaths.icThumbsUp) across all popup item types (email actions, mailbox actions, profile settings, etc.), while actionIcon varies per action type. This is a deliberate UI/UX design choice.

Applied to files:

  • core/lib/utils/app_logger.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: analyze-test (email_recovery)
  • GitHub Check: analyze-test (model)
  • GitHub Check: analyze-test (default)
  • GitHub Check: analyze-test (contact)
  • GitHub Check: analyze-test (fcm)
  • GitHub Check: analyze-test (rule_filter)
  • GitHub Check: analyze-test (forward)
  • GitHub Check: analyze-test (core)
  • GitHub Check: analyze-test (server_settings)
  • GitHub Check: Build web version and deploy
🔇 Additional comments (41)
scribe/pubspec.yaml (1)

29-29: LGTM — Consistent with repo-wide Dio upgrade.

The bump from 5.0.0 to 5.2.0 aligns with the coordinated dependency update across other packages in the monorepo (core, fcm/server_settings, etc.) as part of the Sentry integration work. This ensures version consistency and avoids potential conflicts from mixed Dio versions.

lib/features/upload/data/network/file_uploader.dart (1)

150-154: LGTM!

The migration from DioError to DioException is correct for Dio 5.x. The exception handling properly sanitizes request data before rethrowing, which aligns with the PR objective to avoid sending sensitive data to Sentry.

lib/features/upload/domain/model/upload_attachment.dart (1)

65-66: LGTM!

The migration from DioError/DioErrorType.cancel to DioException/DioExceptionType.cancel is correct for Dio 5.x. The cancellation detection logic is preserved.

test/features/identity_creator/presentation/identity_creator_controller_test.dart (1)

7-7: LGTM!

The import change from ApplicationManager to FileUtils aligns with the PR-wide refactoring. The mock is properly declared at line 79 (MockSpec<FileUtils>()) and registered in the DI container at line 162 (Get.put<FileUtils>(MockFileUtils())).

core/lib/utils/string_convert.dart (1)

248-251: LGTM — appropriate log level for a recoverable exception.

The change from logError to logWarning is correct: this exception has a safe fallback (returning trimmed HTML content), so it doesn't warrant error-level severity. This also aligns with the Sentry integration goal of reducing noise from non-critical, recoverable issues and is consistent with similar patterns elsewhere in this file (lines 54, 139, 150).

scribe/lib/scribe/ai/presentation/model/ai_capability.dart (1)

26-37: LGTM! Appropriate log level for non-fatal configuration checks.

Downgrading from logError to logWarning is correct here. An invalid endpoint format or a validation exception is a configuration issue handled gracefully (returns false), not a critical runtime failure. This aligns with the PR's broader effort to harmonize log levels with Sentry integration—reserving error-level logs for issues that require immediate attention.

test/features/thread/data/network/thread_api_test.dart (1)

616-616: LGTM - Appropriate removal of explicit content-length header assertions.

Removing content-length from mock adapter matching is a good cleanup. These headers are auto-calculated by the HTTP client and don't need to be explicitly asserted in tests. This makes the tests less brittle and better aligned with the Dio 5.2.0 migration.

Also applies to: 665-665, 717-717, 755-755

contact/pubspec.yaml (1)

28-28: LGTM — Dio 5.2.0 upgrade is consistent across the monorepo.

The bump to 5.2.0 aligns with the repo-wide standardization. All modules (contact, core, email_recovery, fcm, forward, rule_filter, scribe, and server_settings) are uniformly on 5.2.0, and the codebase migration from DioError to DioException is underway as part of this Dio 5.x upgrade.

integration_test/base/test_base.dart (1)

4-4: LGTM!

The import path and entry point updates correctly align with the new centralized startup orchestration in main_entry.dart. The migration from aliased app.runTmail() to direct runTmail() is clean and maintains the same test setup behavior.

Also applies to: 36-37

lib/features/base/mixin/ai_scribe_mixin.dart (1)

41-42: LGTM — improved stack trace capture.

Capturing the stack trace is a good improvement for debugging injection failures. The downgrade from logError to logWarning is acceptable since AIScribe is an optional feature and injection failures are non-critical to core app functionality.

test/features/mailbox_dashboard/presentation/controller/advanced_filter_controller_test.dart (1)

14-14: LGTM!

The filter_filter.dart import is correctly added to provide FilterField enum values used throughout the onTextChanged::test group. The removal of ApplicationManager mock dependencies aligns with the PR's refactoring to a singleton pattern, simplifying test setup without affecting test coverage.

env.file (1)

13-15: LGTM — Sentry configuration variables added with safe defaults.

SENTRY_ENABLED=false is an appropriate default to prevent accidental data reporting in development. The empty SENTRY_DSN and SENTRY_ENVIRONMENT values correctly require explicit configuration for each deployment environment. Documentation is available in docs/configuration/sentry_configuration.md.

lib/features/login/data/network/oidc_http_client.dart (1)

8-8: LGTM — migration to DioException aligns with Dio 5.x.

The rename from DioError to DioException is correct for Dio 5.2.0. The exception handling logic (checking .error and .response?.statusCode) remains compatible with the new class API.

Also applies to: 48-48

lib/features/base/widget/application_version_widget.dart (1)

29-29: LGTM!

The migration from Get.find<ApplicationManager>() to the singleton ApplicationManager().getAppVersion() is correct and aligns with the broader refactoring in this PR.

lib/features/public_asset/presentation/public_asset_bindings.dart (1)

18-18: LGTM!

The import addition for CreatePublicAssetInteractor is necessary for the usage in bindingsInteractor() and the removal of the ApplicationManager import aligns with the singleton migration.

core/lib/utils/app_logger.dart (2)

64-92: Well-structured Sentry integration.

The conditional logic cleanly separates print vs Sentry paths, and unawaited() correctly avoids blocking the logging flow. The early return when neither path is needed is a good optimization.


134-136: LGTM!

Clean helper function that clearly defines which log levels trigger Sentry reporting.

core/lib/utils/config/env_loader.dart (1)

36-42: LGTM!

Appropriate error handling for optional configuration loading - logs a warning but allows the app to continue.

core/lib/utils/application_manager.dart (2)

92-152: Well-structured user agent management.

The platform-specific dispatching, caching, and initialization checks are well-implemented. The defensive check for _isMobileUserAgentInitialized in _getMobileUserAgent() prevents silent failures.


154-159: LGTM!

Clean composition of the application user agent with proper trimming to handle empty values.

lib/features/manage_account/presentation/manage_account_dashboard_controller.dart (1)

155-165: Sentry user context integration looks good.

The user context is correctly set when a session is available. Based on learnings, the team has intentionally included PII (account ID, display name, username, email) for error tracking and plans to document this in the app's privacy policy.

lib/main.dart (1)

12-19: Clean centralization of startup flow.

The refactoring to use runAppWithMonitoring(runTmail) centralizes initialization, error handling, and Sentry integration. This improves maintainability and aligns with the PR objectives.

lib/features/base/base_controller.dart (1)

554-559: LGTM!

Clearing the Sentry user context at the start of the logout flow is the correct approach. This ensures user identity is disassociated from Sentry before any subsequent cleanup operations, maintaining proper privacy hygiene. Based on learnings, the team has confirmed PII handling will be documented in the privacy policy.

lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart (3)

32-32: LGTM!

Import is correctly added for accessing SentryUser class.


872-879: LGTM!

The Sentry user context is correctly set with account information when the session is established. Based on learnings, this PII handling is intentional and will be documented in the app's privacy policy for transparency and compliance.


3415-3417: Consistent with onInit pattern.

The releaseUserAgent() call mirrors the initUserAgent() in onInit, ensuring proper cleanup of the user agent when the controller is disposed.

test/features/interceptor/authorization_interceptor_test.dart (4)

44-52: LGTM!

Correctly migrated from DioError to DioException and DioExceptionType.badResponse per Dio 5.x API changes.


241-244: LGTM!

Test expectation correctly updated to check for DioException type.


250-272: LGTM!

Both requestOneDioError401 and requestTwoDioError401 fixtures correctly use DioException with DioExceptionType.badResponse.


365-375: LGTM!

The error expectation correctly checks for DioException and validates the nested AccessTokenInvalidException.

lib/main/app_runner.dart (2)

11-49: Well-structured error monitoring setup.

The implementation correctly layers error handling:

  1. Zone-level errors via runZonedGuarded
  2. Flutter framework errors via FlutterError.onError
  3. Platform-level errors via PlatformDispatcher.instance.onError
  4. Sentry integration with proper fallback

One consideration: PlatformDispatcher.instance.onError returns true, indicating the error is "handled." This prevents the error from propagating to other handlers. If you want the default crash reporting behavior to also trigger (e.g., for native crash reports on mobile), consider returning false after logging.


35-41: This concern appears to be based on a misunderstanding of how SentryFlutter.init() handles errors.

The appRunner is passed directly to SentryFlutter.init(), which executes it within Sentry's error capture context. Errors that occur during appRunner execution (including from runTmailPreload() or runApp()) are captured and reported to Sentry before any exception propagates.

The fallBackRunner is only invoked if SentryFlutter.init() itself throws (e.g., misconfigured DSN, network issues during initialization). In that case, Sentry would not be initialized anyway, so local-only logging of initialization errors is expected and unavoidable—this is not an issue with error handling during app execution.

core/lib/utils/sentry/sentry_initializer.dart (2)

61-68: Stripping data and cookies unconditionally may hide non-sensitive context.

Setting data: null and cookies: null is conservative for privacy, but it removes all request body information even when it might be helpful for debugging non-sensitive requests. This is acceptable if the policy is to never send request bodies; just ensure this aligns with the team's intent.


19-44: Review comment is incorrect — Sentry initialization is already guarded.

SentryConfig.load() throws an exception when SENTRY_ENABLED is not 'true' (line 57-58), preventing the init() method from receiving a config object and proceeding with initialization. The scenario described in the review (isAvailable false with DSN set causing events to be captured) cannot occur because the exception short-circuits execution before returning a config. No additional guards are needed.

Likely an incorrect or invalid review comment.

test/features/composer/data/repository/composer_repository_impl_test.dart (1)

62-67: LGTM: Constructor updated to match production code changes.

The test correctly reflects the removal of ApplicationManager from ComposerRepositoryImpl's dependencies, aligning with the broader refactor to instantiate it inline in production code.

lib/main/bindings/network/network_bindings.dart (1)

90-104: Good refactor: local dio variable improves readability.

Extracting the Dio instance into a local variable and passing it to AuthorizationInterceptors is cleaner than repeated Get.find<Dio>() calls.

lib/main/main_entry.dart (2)

14-17: LGTM: Clean separation of preload and app launch.

The split between runTmailPreload() and runTmail() allows integration tests to call only preload when needed, which aligns with the test_base.dart changes mentioned in the summary.


30-31: Sequential awaits after parallel init look intentional.

CozyIntegration.integrateCozy() and HiveCacheConfig.instance.initializeEncryptionKey() are awaited sequentially, suggesting they may depend on the parallel initialization results. This ordering is appropriate if dependencies exist.

lib/features/login/data/network/interceptors/authorization_interceptors.dart (1)

85-85: LGTM! Correct migration to DioException for Dio 5.x.

The DioErrorDioException rename aligns with the Dio 5.2.0 API. The error handling logic (token refresh, retry) remains intact.

Also applies to: 162-162

core/lib/utils/sentry/sentry_manager.dart (1)

82-91: PII logging acknowledged.

setUser logs the user's email, which is intentional per prior discussions. Ensure this is documented in the app's privacy policy for transparency. Based on learnings, this is the expected behavior.

core/test/utils/application_manager_test.dart (1)

20-40: LGTM! Thorough test setup and teardown.

The test setup properly initializes mocks via static override, and teardown cleans up all state including platform overrides, method channel handlers, and FkUserAgent. This prevents test pollution.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/pubspec.yaml (1)

44-108: Consider updating to more recent package versions.

While the Sentry packages are correctly matched to each other (both 9.8.0), several dependencies are already behind their latest available versions. Most notably, dio is pinned to 5.2.0 while version 5.9.0 is available—a gap of 7 minor versions that may contain important performance, security, or compatibility improvements. Similarly, sentry_flutter and sentry_dio are 1 patch behind (9.8.0 vs 9.9.2), and flutter_dotenv is 1 minor behind (5.0.2 vs 6.0.0). These packages should be evaluated for updates to benefit from recent fixes and improvements.

🤖 Fix all issues with AI agents
In @core/lib/utils/sentry/sentry_initializer.dart:
- Around line 27-49: The beforeSend handler (_beforeSendHandler) sanitizes
headers but misses request.queryString and breadcrumb data; update
SentryFlutter.init options to set a beforeBreadcrumb callback (e.g.,
options.beforeBreadcrumb) that filters or redacts URLs and data in Breadcrumbs
created by enableBreadcrumbTrackingForCurrentPlatform(), and extend the request
sanitization inside _beforeSendHandler to explicitly scrub request.queryString
(redact known keys like token/api_key/authorization or drop the entire query
string) before returning the event.
- Around line 75-89: The current code constructs a new SentryEvent losing many
fields; instead mutate the incoming SentryEvent instance by assigning the
sanitized request to event.request and return event (e.g., replace the
SentryEvent(...) construction with event.request = sanitizedRequest; return
event;), removing any use of copyWith/deprecated construction so all existing
fields (transaction, culprit, serverName, debugMeta, sdk, platform, environment,
release, dist, exceptions, threads, measurements, extra, etc.) are preserved.
🧹 Nitpick comments (5)
core/lib/utils/sentry/sentry_manager.dart (1)

45-57: Consider whether the breadcrumb adds value here.

Breadcrumbs typically capture a trail of events before an error occurs. Adding a breadcrumb during captureException for the same exception duplicates information Sentry already records. The extras metadata could alternatively be passed via Sentry.captureException's withScope using scope.setExtra() or scope.setContext() for cleaner separation.

This works as-is, but if the intent is to attach structured metadata, consider using context/extras directly:

withScope: (scope) {
  if (extras != null) {
    scope.setContexts('metadata', extras);
  }
},
core/lib/utils/sentry/sentry_dio_helper.dart (1)

9-13: Fragile runtime type check for detecting Sentry interceptor.

Using runtimeType.toString().contains('FailedRequestInterceptor') is brittle:

  • The class name may change across sentry_dio versions
  • In release/minified builds, type names might be obfuscated (though less common in Dart)

Consider tracking whether Sentry was added via a separate flag, or check if sentry_dio exports the interceptor type for direct type matching.

♻️ Alternative: Track state externally
class SentryDioHelper {
  static final _sentryAddedToDio = Expando<bool>();

  static void addIfAvailable(Dio dio) {
    if (!SentryManager.instance.isSentryAvailable) return;
    
    if (_sentryAddedToDio[dio] == true) return;
    
    dio.addSentry();
    _sentryAddedToDio[dio] = true;
  }
}
core/lib/utils/application_manager.dart (1)

18-19: DeviceInfoPlugin is instantiated on every access.

The _deviceInfo getter creates a new DeviceInfoPlugin() instance each time it's accessed (when no override is set). Since DeviceInfoPlugin is lightweight and stateless, this is functionally correct but slightly wasteful. Consider caching it if accessed frequently.

♻️ Optional: Cache the plugin instance
+ DeviceInfoPlugin? _deviceInfoCache;
+
  DeviceInfoPlugin get _deviceInfo =>
-     debugDeviceInfoOverride ?? DeviceInfoPlugin();
+     debugDeviceInfoOverride ?? (_deviceInfoCache ??= DeviceInfoPlugin());
core/lib/utils/sentry/sentry_initializer.dart (1)

7-20: Header sanitization patterns look comprehensive.

The blocked patterns cover common authentication/session headers. The case-insensitive contains() matching is appropriately broad to catch variations like X-Authorization-Token.

Consider adding token as a standalone pattern to catch generic token headers (e.g., X-Custom-Token, Access-Token).

♻️ Optional: Add generic token pattern
  static const _blockedHeaderPatterns = [
    'authorization',
    'cookie',
    'set-cookie',
    'x-auth',
    'x-token',
    'api-key',
    'x-api-key',
    'apikey',
    'secret',
    'bearer',
    'session',
    'password',
+   'token',
  ];
core/lib/core.dart (1)

146-147: Consider documenting the rationale for re-exporting a third-party package.

Re-exporting package_info_plus directly from the core library is convenient for consumers but has tradeoffs:

  • Pro: Consumers access it without adding their own dependency.
  • Con: Hides the transitive dependency, may cause version conflicts if a consumer also depends on it directly.

If this is intentional for centralizing version management, consider adding a brief comment explaining the pattern for maintainability.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bdeb1a7 and c5157e5.

⛔ Files ignored due to path filters (4)
  • contact/pubspec.lock is excluded by !**/*.lock
  • core/pubspec.lock is excluded by !**/*.lock
  • model/pubspec.lock is excluded by !**/*.lock
  • scribe/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • core/lib/core.dart
  • core/lib/utils/application_manager.dart
  • core/lib/utils/config/env_loader.dart
  • core/lib/utils/sentry/sentry_config.dart
  • core/lib/utils/sentry/sentry_dio_helper.dart
  • core/lib/utils/sentry/sentry_initializer.dart
  • core/lib/utils/sentry/sentry_manager.dart
  • core/pubspec.yaml
  • lib/main/bindings/network/network_bindings.dart
  • lib/main/bindings/network/network_isolate_binding.dart
  • lib/main/main_entry.dart
🚧 Files skipped from review as they are similar to previous changes (5)
  • lib/main/bindings/network/network_isolate_binding.dart
  • core/lib/utils/sentry/sentry_config.dart
  • lib/main/bindings/network/network_bindings.dart
  • lib/main/main_entry.dart
  • core/lib/utils/config/env_loader.dart
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4137
File: lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart:32-32
Timestamp: 2025-12-15T06:24:50.369Z
Learning: In lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart and lib/features/manage_account/presentation/manage_account_dashboard_controller.dart, SentryManager.instance.setUser() intentionally sends PII (account ID, display name, username, email) to Sentry for error tracking. The team plans to document this in the app's privacy policy for transparency and compliance.
📚 Learning: 2025-12-15T06:24:50.369Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4137
File: lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart:32-32
Timestamp: 2025-12-15T06:24:50.369Z
Learning: In lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart and lib/features/manage_account/presentation/manage_account_dashboard_controller.dart, SentryManager.instance.setUser() intentionally sends PII (account ID, display name, username, email) to Sentry for error tracking. The team plans to document this in the app's privacy policy for transparency and compliance.

Applied to files:

  • core/lib/utils/sentry/sentry_initializer.dart
  • core/lib/core.dart
  • core/lib/utils/sentry/sentry_manager.dart
📚 Learning: 2025-12-09T09:36:45.349Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4194
File: lib/features/manage_account/presentation/manage_account_dashboard_controller.dart:174-176
Timestamp: 2025-12-09T09:36:45.349Z
Learning: In Dart/Flutter projects using GetX, do not wrap getBinding<T>() calls in try/catch since they return null when not found. Only wrap Get.find<T>() calls in try/catch because they throw if a dependency is unavailable. When a binding is optional, consider checking Get.isRegistered<T>() or handling the null/exception path gracefully instead of blindly catching, and document the expectation for failure modes where a dependency may not be registered.

Applied to files:

  • core/lib/utils/sentry/sentry_initializer.dart
  • core/lib/utils/sentry/sentry_dio_helper.dart
  • core/lib/core.dart
  • core/lib/utils/application_manager.dart
  • core/lib/utils/sentry/sentry_manager.dart
📚 Learning: 2025-12-12T11:00:57.625Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4205
File: core/lib/utils/app_logger.dart:80-86
Timestamp: 2025-12-12T11:00:57.625Z
Learning: In the tmail-flutter codebase, logging philosophy differs by platform: web applications use the `webConsoleEnabled` parameter in `app_logger.dart` to selectively display logs in production console, while mobile applications intentionally suppress all logs in release builds (enforced by `BuildUtils.isDebugMode` check in `_debugPrint`).

Applied to files:

  • core/lib/utils/application_manager.dart
📚 Learning: 2025-12-18T09:19:32.437Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4203
File: scribe/pubspec.yaml:37-37
Timestamp: 2025-12-18T09:19:32.437Z
Learning: In the tmail-flutter repository, json_annotation version 4.8.0 is the standardized version across modules and should not be upgraded without considering the standardization practice.

Applied to files:

  • core/pubspec.yaml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Build web version and deploy
  • GitHub Check: analyze-test (rule_filter)
  • GitHub Check: analyze-test (core)
  • GitHub Check: analyze-test (email_recovery)
  • GitHub Check: analyze-test (contact)
  • GitHub Check: analyze-test (forward)
  • GitHub Check: analyze-test (server_settings)
  • GitHub Check: analyze-test (fcm)
  • GitHub Check: analyze-test (model)
  • GitHub Check: analyze-test (default)
🔇 Additional comments (6)
core/lib/utils/sentry/sentry_manager.dart (2)

8-34: Solid singleton initialization with graceful fallback.

The guard at line 23-27 correctly skips re-initialization while still running appRunner. The fallback pattern ensures the app runs even if Sentry fails to initialize.


83-107: User management looks correct.

The setUser and clearUser methods properly guard against unavailable Sentry and handle exceptions. Based on learnings, the team has acknowledged that PII (user email) is intentionally sent to Sentry and will be documented in the privacy policy.

core/lib/utils/application_manager.dart (3)

36-63: Caching strategy for package info and version is well-implemented.

The caching pattern correctly handles:

  • First access loads and caches
  • Subsequent accesses return cached value
  • Error handling returns safe defaults

The clearCache() method being @visibleForTesting appropriately limits cache invalidation to test scenarios.


93-108: getUserAgent dispatches correctly with safe fallback.

The try/catch wrapper and platform-based dispatch are correct. Returning an empty string for unhandled platforms (e.g., desktop) is a safe fallback that allows generateApplicationUserAgent() to still produce a valid result.


79-91: User agent lifecycle methods are well-structured.

The initUserAgent() and releaseUserAgent() correctly:

  • Guard against non-mobile platforms
  • Manage the initialization flag
  • Clear the cache on state changes

FkUserAgent.release() is synchronous (returns void), so it's correctly called without await.

core/lib/core.dart (1)

63-65: Sentry utility exports are well-organized and properly located.

The new exports for Sentry integration (sentry_manager.dart, env_loader.dart, sentry_dio_helper.dart) are appropriately placed within the Utils section and follow the existing file organization pattern. All exported files exist at their specified paths.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @core/lib/utils/sentry/sentry_initializer.dart:
- Around line 60-65: The code creates sanitizedHeaders with Map<String,
String>.from(req.headers) but req.headers is SentryRequest.headers which is
nullable and Map.from(null) will throw; update the logic that builds
sanitizedHeaders to handle null safely by using a null-aware fallback (e.g.,
treat req.headers ?? {}), so use req.headers if non-null or an empty map
otherwise before calling Map.from, then apply the existing removeWhere filter
using _blockedHeaderPatterns; ensure references include sanitizedHeaders,
req.headers, SentryRequest.headers, and _blockedHeaderPatterns.
- Around line 76-90: The current reconstruction using a new SentryEvent drops
many fields from the original; instead mutate the incoming event by assigning
the sanitized request to event.request and return the original event (do not
call SentryEvent(...) or rely on the deprecated copyWith); update the code
around SentryEvent construction to set event.request = sanitizedRequest and
return event so fields like message, extra, exceptions, sdk, platform, release,
environment, transaction, etc. are preserved.
🧹 Nitpick comments (1)
core/lib/utils/application_manager.dart (1)

39-49: Consider adding error handling for direct callers.

PackageInfo.fromPlatform() can throw platform exceptions. While getAppVersion() wraps this in try/catch, direct callers of getPackageInfo() would receive uncaught exceptions. Consider adding error handling if this method is called directly elsewhere.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6111487 and 15ab1f4.

📒 Files selected for processing (2)
  • core/lib/utils/application_manager.dart
  • core/lib/utils/sentry/sentry_initializer.dart
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4137
File: lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart:32-32
Timestamp: 2025-12-15T06:24:50.369Z
Learning: In lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart and lib/features/manage_account/presentation/manage_account_dashboard_controller.dart, SentryManager.instance.setUser() intentionally sends PII (account ID, display name, username, email) to Sentry for error tracking. The team plans to document this in the app's privacy policy for transparency and compliance.
📚 Learning: 2025-12-15T06:24:50.369Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4137
File: lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart:32-32
Timestamp: 2025-12-15T06:24:50.369Z
Learning: In lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart and lib/features/manage_account/presentation/manage_account_dashboard_controller.dart, SentryManager.instance.setUser() intentionally sends PII (account ID, display name, username, email) to Sentry for error tracking. The team plans to document this in the app's privacy policy for transparency and compliance.

Applied to files:

  • core/lib/utils/sentry/sentry_initializer.dart
📚 Learning: 2025-12-09T09:36:45.349Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4194
File: lib/features/manage_account/presentation/manage_account_dashboard_controller.dart:174-176
Timestamp: 2025-12-09T09:36:45.349Z
Learning: In Dart/Flutter projects using GetX, do not wrap getBinding<T>() calls in try/catch since they return null when not found. Only wrap Get.find<T>() calls in try/catch because they throw if a dependency is unavailable. When a binding is optional, consider checking Get.isRegistered<T>() or handling the null/exception path gracefully instead of blindly catching, and document the expectation for failure modes where a dependency may not be registered.

Applied to files:

  • core/lib/utils/sentry/sentry_initializer.dart
  • core/lib/utils/application_manager.dart
📚 Learning: 2025-12-12T11:00:57.625Z
Learnt from: dab246
Repo: linagora/tmail-flutter PR: 4205
File: core/lib/utils/app_logger.dart:80-86
Timestamp: 2025-12-12T11:00:57.625Z
Learning: In the tmail-flutter codebase, logging philosophy differs by platform: web applications use the `webConsoleEnabled` parameter in `app_logger.dart` to selectively display logs in production console, while mobile applications intentionally suppress all logs in release builds (enforced by `BuildUtils.isDebugMode` check in `_debugPrint`).

Applied to files:

  • core/lib/utils/application_manager.dart
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: analyze-test (fcm)
  • GitHub Check: analyze-test (server_settings)
  • GitHub Check: analyze-test (model)
  • GitHub Check: analyze-test (email_recovery)
  • GitHub Check: analyze-test (rule_filter)
  • GitHub Check: analyze-test (contact)
  • GitHub Check: analyze-test (core)
  • GitHub Check: analyze-test (default)
  • GitHub Check: analyze-test (forward)
🔇 Additional comments (8)
core/lib/utils/sentry/sentry_initializer.dart (2)

7-21: LGTM! Comprehensive sensitive header filtering.

The blocked patterns cover common authentication and session headers. Using contains() matching ensures variations (e.g., X-Authorization, auth-token) are also filtered.


23-50: LGTM!

The initialization flow is well-structured: loads config, gracefully handles missing config by returning false, and properly configures Sentry options including platform-appropriate breadcrumb tracking.

core/lib/utils/application_manager.dart (6)

9-13: LGTM! Proper singleton implementation.

The private constructor with static instance and factory pattern is the idiomatic Dart approach for singletons.


15-21: LGTM!

The test override pattern with @visibleForTesting and lazy initialization is a clean approach for testability.


23-37: LGTM!

Cache variables are well-organized and clearCache() properly resets all state for test isolation.


68-94: LGTM! Lifecycle methods with proper guards.

The platform guards and error handling are appropriate. Minor note: if FkUserAgent.release() throws, the state flags won't be cleared, but the logged warning provides visibility into this edge case.


96-156: LGTM! Platform-aware user agent retrieval with caching.

Good design with the initialization check on mobile (Lines 135-140) that logs a warning if called before initUserAgent(). The caching logic prevents redundant platform calls.


158-163: LGTM!

The trim() call elegantly handles empty user agent cases, preventing trailing whitespace in the result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Sentry

7 participants