Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b4f590c
fix(sentry): sync dist parameter to resolve minified stack traces
dab246 Feb 24, 2026
48a6f83
refactor(sentry): standardize exceptions to prevent Sentry minification
dab246 Feb 24, 2026
063f925
fix(sentry): resolve minified exception types on web release
dab246 Mar 3, 2026
42068df
TF-4268 refactor: extract shared app runner logic and split web/mobil…
dab246 Jan 22, 2026
7615763
TF-4268 Add sentry configuration to linagora ecosystem
dab246 Jan 22, 2026
a6d1b12
TF-4268 Implement get linagora ecosystem interactor
dab246 Jan 22, 2026
c877eb8
TF-4268 Init Sentry from ecosystem
dab246 Jan 22, 2026
a81c334
TF-4268 refactor: initialize `SentryWidgetsFlutterBinding` to fix fra…
dab246 Jan 22, 2026
f5c979d
TF-4268 Avoid loading environment files multiple times when enabling …
dab246 Jan 23, 2026
661446b
TF-4268 Replace `runZonedGuarded` with `PlatformDispatcher.onError` f…
dab246 Jan 23, 2026
49a0ce8
TF-4268 Update SentryConfig: add sampling and frames tracking
dab246 Jan 23, 2026
3c677ac
TF-4268 Optimize SentryManager for non-blocking execution
dab246 Jan 23, 2026
66b8d74
TF-4268 Add a log to monitor push notifications on Android.
dab246 Jan 23, 2026
555b8f3
TF-4268 Fix guard against invalid paywall property types
dab246 Jan 23, 2026
58e066b
TF-4268 Reset _baseUrl on each load to prevent stale URL caching acro…
dab246 Jan 23, 2026
66a54c7
TF-4268 Add more log to sentry to cover all case possible
dab246 Feb 2, 2026
84fd928
TF-4268 Save Sentry configuration to cache to re-init in onBackground…
dab246 Feb 3, 2026
4f57671
TF-4268 Save Sentry user to cache to re-init in onBackground of FCM
dab246 Feb 3, 2026
d7cbe54
TF-4268 Clear cached Sentry config and user when disabled to prevent …
dab246 Feb 3, 2026
94a3d75
TF-4268 Clean up partially initialized controllers on exception.
dab246 Feb 3, 2026
129f941
TF-4268 Parse `enabled` string
hoangdat Feb 4, 2026
8a30d00
TF-4268 Remove some unnecessary log and add more logError
dab246 Feb 4, 2026
97efbd3
TF-4268 Test asserts only the type, not the expected fallback values.
dab246 Feb 11, 2026
573ff8e
TF-4268 Upload `ProGuard Mapping (mapping.txt)`` to de-obfuscate code…
dab246 Feb 11, 2026
b3dae8c
TF-4268 Upload `Dart Debug Symbols` to de-obfuscate code `Dart`
dab246 Feb 11, 2026
964fab2
TF-4268 Add ADR 0071 documenting the Sentry Android integration strategy
dab246 Feb 11, 2026
9b9672c
Support new version of sentry-cli
hoangdat Feb 24, 2026
bd6963e
support sentry-cli 3.x
hoangdat Feb 24, 2026
3ccb27b
TF-4268 Fix missing ProGuard Mapping and Debug Symbols file on Sentry…
dab246 Feb 25, 2026
f7a5615
TF-4268 fix: invalid value 'proguard' for '--type <TYPE>'
dab246 Feb 25, 2026
9c463ba
TF-4268 Add `const` for exceptions class
dab246 Mar 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,47 @@ jobs:
APPLE_KEY_CONTENT: ${{ secrets.APPLE_KEY_CONTENT }}
run: ../scripts/build-release.sh
working-directory: ${{ matrix.os }}

# --- UPLOAD SENTRY MAPPING AND SYMBOLS FOR ANDROID ---
- name: Upload Android ProGuard Mapping & Dart Debug Symbols to Sentry
if: matrix.os == 'android'
working-directory: .
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
run: |
# 1. Install Sentry CLI
curl -sL https://sentry.io/get-cli/ | bash

# 2. Get Version from pubspec.yaml
SENTRY_RELEASE=$(grep '^version:' pubspec.yaml | awk '{print $2}')
echo "Processing Sentry Release: $SENTRY_RELEASE"

# 3. Create Release
sentry-cli releases new "$SENTRY_RELEASE"

# 4. Upload ProGuard Mapping
MAPPING_FILE="build/app/outputs/mapping/release/mapping.txt"

if [ -f "$MAPPING_FILE" ]; then
echo "Found mapping.txt, uploading ProGuard mapping..."
sentry-cli upload-proguard "$MAPPING_FILE"
else
echo "::error::Could not find mapping.txt at $MAPPING_FILE."
exit 1
fi

# 5. Upload Dart Debug Symbols (Native Dart Stacktrace)
SYMBOLS_DIR="build/app/outputs/symbols"

if [ -d "$SYMBOLS_DIR" ]; then
echo "Uploading Dart debug symbols from $SYMBOLS_DIR..."
sentry-cli debug-files upload "$SYMBOLS_DIR"
else
echo "::error::Symbols directory not found at $SYMBOLS_DIR. Check your Fastfile build arguments."
exit 1
fi

# 6. Finalize
sentry-cli releases finalize "$SENTRY_RELEASE"
7 changes: 5 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ ENV GITHUB_SHA=$GITHUB_SHA \
SENTRY_RELEASE=$SENTRY_RELEASE

RUN ./scripts/prebuild.sh && \
flutter build web --release --source-maps --dart-define=SENTRY_RELEASE=$SENTRY_RELEASE && \
if [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_PROJECT" ] && [ -n "$SENTRY_RELEASE" ]; then \
if [ -z "$GITHUB_SHA" ]; then echo "GITHUB_SHA is required for SENTRY_DIST"; exit 1; fi && \
flutter build web --release --source-maps \
--dart-define=SENTRY_RELEASE=$SENTRY_RELEASE \
--dart-define=SENTRY_DIST=$GITHUB_SHA && \
if [ -n "$SENTRY_AUTH_TOKEN" ] && [ -n "$SENTRY_ORG" ] && [ -n "$SENTRY_PROJECT" ] && [ -n "$SENTRY_RELEASE" ] && [ -n "$GITHUB_SHA" ]; then \
echo "Sentry configuration detected, uploading sourcemaps..." && \
curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION=2.20.7 bash && \
sentry-cli releases new "$SENTRY_RELEASE" && \
Expand Down
4 changes: 3 additions & 1 deletion android/fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ platform :android do
json_key_data: ENV["PLAY_STORE_CONFIG_JSON"]
)[0].to_i
build_name = last_git_tag.gsub("v", "")
sh "flutter build appbundle --verbose --release --dart-define=SERVER_URL=$SERVER_URL --build-number=#{latest_build_number+1} --build-name=#{build_name}"
# Add: --obfuscate --split-debug-info=../build/app/outputs/symbols
# Note: The path starts with ../ because Fastlane runs in the android/ directory.
sh "cd .. && flutter build appbundle --verbose --release --obfuscate --split-debug-info=build/app/outputs/symbols --dart-define=SERVER_URL=$SERVER_URL --build-number=#{latest_build_number+1} --build-name=#{build_name}"
upload_to_play_store(
json_key_data: ENV["PLAY_STORE_CONFIG_JSON"],
track: track,
Expand Down
1 change: 1 addition & 0 deletions core/lib/core.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export 'domain/exceptions/download_file_exception.dart';
export 'data/extensions/options_extensions.dart';
export 'domain/exceptions/web_session_exception.dart';
export 'domain/exceptions/platform_exception.dart';
export 'domain/exceptions/app_base_exception.dart';

// Utils
export 'presentation/utils/theme_utils.dart';
Expand Down
13 changes: 4 additions & 9 deletions core/lib/domain/exceptions/address_exception.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import 'package:equatable/equatable.dart';
import 'package:core/domain/exceptions/app_base_exception.dart';

class AddressException with EquatableMixin implements Exception {
final String message;

AddressException(this.message);

@override
String toString() => message;
class AddressException extends AppBaseException {
const AddressException(super.message);

@override
List<Object> get props => [message];
String get exceptionName => 'AddressException';
}
20 changes: 20 additions & 0 deletions core/lib/domain/exceptions/app_base_exception.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:equatable/equatable.dart';

abstract class AppBaseException with EquatableMixin implements Exception {
final String? message;

const AppBaseException([this.message]);

@override
String toString() {
if (message?.isNotEmpty == true) {
return '$exceptionName: $message';
}
return exceptionName;
}

String get exceptionName;

@override
List<Object?> get props => [message];
}
30 changes: 13 additions & 17 deletions core/lib/domain/exceptions/download_file_exception.dart
Original file line number Diff line number Diff line change
@@ -1,31 +1,27 @@
import 'package:equatable/equatable.dart';
import 'package:core/domain/exceptions/app_base_exception.dart';

abstract class DownloadFileException with EquatableMixin implements Exception {
final String message;

DownloadFileException(this.message);

@override
String toString() => message;

@override
List<Object> get props => [message];
abstract class DownloadFileException extends AppBaseException {
const DownloadFileException(super.message);
}

class CommonDownloadFileException extends DownloadFileException {
CommonDownloadFileException(message) : super(message);
const CommonDownloadFileException(super.message);

@override
List<Object> get props => [message];
String get exceptionName => 'CommonDownloadFileException';
}

class CancelDownloadFileException extends DownloadFileException {
CancelDownloadFileException(cancelMessage) : super(cancelMessage);
const CancelDownloadFileException(super.message);

@override
List<Object> get props => [message];
String get exceptionName => 'CancelDownloadFileException';
}

class DeviceNotSupportedException extends DownloadFileException {
DeviceNotSupportedException() : super('This device is not supported, please try on Android or iOS');
}
const DeviceNotSupportedException()
: super('This device is not supported, please try on Android or iOS');

@override
String get exceptionName => 'DeviceNotSupportedException';
}
22 changes: 10 additions & 12 deletions core/lib/domain/exceptions/file_exception.dart
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import 'package:equatable/equatable.dart';
import 'package:core/domain/exceptions/app_base_exception.dart';

abstract class FileException with EquatableMixin implements Exception {
final String message;

FileException(this.message);

@override
String toString() => message;

@override
List<Object> get props => [message];
abstract class FileException extends AppBaseException {
const FileException(super.message);
}

class NotFoundFileInFolderException extends FileException {
NotFoundFileInFolderException() : super('No files found in the folder');

@override
String get exceptionName => 'NotFoundFileInFolderException';
}

class UserCancelShareFileException extends FileException {
UserCancelShareFileException() : super('User cancel share file');
}

@override
String get exceptionName => 'UserCancelShareFileException';
}
17 changes: 9 additions & 8 deletions core/lib/domain/exceptions/platform_exception.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import 'package:equatable/equatable.dart';
import 'package:core/domain/exceptions/app_base_exception.dart';

class PlatformException with EquatableMixin implements Exception {
final String message;

PlatformException(this.message);
class PlatformException extends AppBaseException {
const PlatformException(super.message);

@override
List<Object> get props => [message];
String get exceptionName => 'PlatformException';
}

class NoSupportPlatformException extends PlatformException {
NoSupportPlatformException() : super('This platform is not supported');
}
const NoSupportPlatformException() : super('This platform is not supported');

@override
String get exceptionName => 'NoSupportPlatformException';
}
18 changes: 13 additions & 5 deletions core/lib/domain/exceptions/string_exception.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
class UnsupportedCharsetException implements Exception {
const UnsupportedCharsetException();
import 'package:core/domain/exceptions/app_base_exception.dart';

class UnsupportedCharsetException extends AppBaseException {
const UnsupportedCharsetException([super.message]);

@override
String get exceptionName => 'UnsupportedCharsetException';
}

class NullCharsetException implements Exception {
const NullCharsetException();
}
class NullCharsetException extends AppBaseException {
const NullCharsetException([super.message]);

@override
String get exceptionName => 'NullCharsetException';
}
40 changes: 28 additions & 12 deletions core/lib/domain/exceptions/web_session_exception.dart
Original file line number Diff line number Diff line change
@@ -1,28 +1,44 @@
import 'package:core/domain/exceptions/app_base_exception.dart';
import 'package:equatable/equatable.dart';

class NotFoundInWebSessionException with EquatableMixin implements Exception {
final String? errorMessage;
class NotFoundInWebSessionException extends AppBaseException
with EquatableMixin {
const NotFoundInWebSessionException({String? errorMessage})
: super(errorMessage);

NotFoundInWebSessionException({this.errorMessage});
@override
String get exceptionName => 'NotFoundInWebSessionException';

@override
List<Object> get props => [];
List<Object?> get props => [message, exceptionName];
}

class NotMatchInWebSessionException with EquatableMixin implements Exception {
const NotMatchInWebSessionException();
class NotMatchInWebSessionException extends AppBaseException
with EquatableMixin {
const NotMatchInWebSessionException() : super('Session data does not match');

@override
String get exceptionName => 'NotMatchInWebSessionException';

@override
List<Object> get props => [];
List<Object?> get props => [message, exceptionName];
}

class SaveToWebSessionFailException with EquatableMixin implements Exception {
final String? errorMessage;
class SaveToWebSessionFailException extends AppBaseException
with EquatableMixin {
const SaveToWebSessionFailException({String? errorMessage})
: super(errorMessage);

SaveToWebSessionFailException({this.errorMessage});
@override
String get exceptionName => 'SaveToWebSessionFailException';

@override
List<Object> get props => [];
List<Object?> get props => [message, exceptionName];
}

class CannotOpenNewWindowException implements Exception {}
class CannotOpenNewWindowException extends AppBaseException {
const CannotOpenNewWindowException([super.message]);

@override
String get exceptionName => 'CannotOpenNewWindowException';
}
4 changes: 3 additions & 1 deletion core/lib/presentation/state/failure.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ abstract class Failure with EquatableMixin {

abstract class FeatureFailure extends Failure {
final dynamic exception;
final StackTrace? stackTrace;
final Stream<Either<Failure, Success>>? onRetry;

FeatureFailure({this.exception, this.onRetry});
FeatureFailure({this.exception, this.stackTrace, this.onRetry});

// Including stackTrace in props may break Equatable equality semantics.
@override
List<Object?> get props => [exception, onRetry];
}
18 changes: 6 additions & 12 deletions core/lib/utils/app_logger.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import 'dart:async';

import 'package:core/utils/build_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:core/utils/sentry/sentry_manager.dart';
Expand Down Expand Up @@ -82,17 +80,13 @@ void _internalLog(

if (shouldSentry) {
if (level == Level.trace) {
unawaited(
SentryManager.instance.captureMessage(rawMessage, extras: extras),
);
SentryManager.instance.captureMessage(rawMessage, extras: extras);
} else {
unawaited(
SentryManager.instance.captureException(
exception ?? rawMessage,
stackTrace: stackTrace,
message: rawMessage,
extras: extras,
),
SentryManager.instance.captureException(
exception ?? rawMessage,
stackTrace: stackTrace,
message: rawMessage,
extras: extras,
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion core/lib/utils/file_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class FileUtils {

return fileDirectory;
} else {
throw DeviceNotSupportedException();
throw const DeviceNotSupportedException();
}
}

Expand Down
2 changes: 1 addition & 1 deletion core/lib/utils/logger/log_tracking.dart
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class LogTracking {

return fileDirectory;
} else {
throw DeviceNotSupportedException();
throw const DeviceNotSupportedException();
}
}

Expand Down
Loading
Loading