TF-4269 Integrate Sentry into iOS NSE and automate dSYM upload in CI
This commit is contained in:
@@ -101,10 +101,11 @@ jobs:
|
|||||||
# 1. Install Sentry CLI
|
# 1. Install Sentry CLI
|
||||||
curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION="2.42.2" bash
|
curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION="2.42.2" bash
|
||||||
|
|
||||||
# 2. Validate tag version matches pubspec.yaml version
|
# 2. Validate tag version matches pubspec.yaml version (base version only, ignoring -rc* suffix)
|
||||||
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
||||||
TAG_VERSION="${TAG_VERSION%%-rc*}"
|
TAG_VERSION="${TAG_VERSION%%-rc*}"
|
||||||
PUBSPEC_VERSION=$(awk '/^version:/{split($2,a,"+"); print a[1]; exit}' pubspec.yaml)
|
PUBSPEC_VERSION=$(awk '/^version:/{split($2,a,"+"); print a[1]; exit}' pubspec.yaml)
|
||||||
|
PUBSPEC_VERSION="${PUBSPEC_VERSION%%-rc*}"
|
||||||
if [ "$PUBSPEC_VERSION" != "$TAG_VERSION" ]; then
|
if [ "$PUBSPEC_VERSION" != "$TAG_VERSION" ]; then
|
||||||
echo "::error::Tag version ($TAG_VERSION) does not match pubspec version ($PUBSPEC_VERSION)."
|
echo "::error::Tag version ($TAG_VERSION) does not match pubspec version ($PUBSPEC_VERSION)."
|
||||||
exit 1
|
exit 1
|
||||||
@@ -117,7 +118,6 @@ jobs:
|
|||||||
|
|
||||||
# 4. Upload ProGuard Mapping
|
# 4. Upload ProGuard Mapping
|
||||||
MAPPING_FILE="build/app/outputs/mapping/release/mapping.txt"
|
MAPPING_FILE="build/app/outputs/mapping/release/mapping.txt"
|
||||||
|
|
||||||
if [ -f "$MAPPING_FILE" ]; then
|
if [ -f "$MAPPING_FILE" ]; then
|
||||||
echo "Found mapping.txt, uploading ProGuard mapping..."
|
echo "Found mapping.txt, uploading ProGuard mapping..."
|
||||||
sentry-cli upload-proguard "$MAPPING_FILE"
|
sentry-cli upload-proguard "$MAPPING_FILE"
|
||||||
@@ -128,7 +128,6 @@ jobs:
|
|||||||
|
|
||||||
# 5. Upload Dart Debug Symbols (Native Dart Stacktrace)
|
# 5. Upload Dart Debug Symbols (Native Dart Stacktrace)
|
||||||
SYMBOLS_DIR="build/app/outputs/symbols"
|
SYMBOLS_DIR="build/app/outputs/symbols"
|
||||||
|
|
||||||
if [ -d "$SYMBOLS_DIR" ]; then
|
if [ -d "$SYMBOLS_DIR" ]; then
|
||||||
echo "Uploading Dart debug symbols from $SYMBOLS_DIR..."
|
echo "Uploading Dart debug symbols from $SYMBOLS_DIR..."
|
||||||
sentry-cli debug-files upload "$SYMBOLS_DIR"
|
sentry-cli debug-files upload "$SYMBOLS_DIR"
|
||||||
@@ -139,3 +138,45 @@ jobs:
|
|||||||
|
|
||||||
# 6. Finalize
|
# 6. Finalize
|
||||||
sentry-cli releases finalize "$SENTRY_RELEASE"
|
sentry-cli releases finalize "$SENTRY_RELEASE"
|
||||||
|
|
||||||
|
# --- UPLOAD DSYM TO SENTRY FOR IOS ---
|
||||||
|
# sentry-cli debug-files upload works independently of the release lifecycle,
|
||||||
|
# so no releases new/finalize needed here — avoids race condition with Android leg.
|
||||||
|
- name: Upload iOS dSYMs to Sentry
|
||||||
|
if: matrix.os == 'ios'
|
||||||
|
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/ | SENTRY_CLI_VERSION="2.42.2" bash
|
||||||
|
|
||||||
|
# 2. Validate tag version matches pubspec.yaml version (base version only, ignoring -rc* suffix)
|
||||||
|
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
||||||
|
TAG_VERSION="${TAG_VERSION%%-rc*}"
|
||||||
|
PUBSPEC_VERSION=$(awk '/^version:/{split($2,a,"+"); print a[1]; exit}' pubspec.yaml)
|
||||||
|
PUBSPEC_VERSION="${PUBSPEC_VERSION%%-rc*}"
|
||||||
|
if [ "$PUBSPEC_VERSION" != "$TAG_VERSION" ]; then
|
||||||
|
echo "::error::Tag version ($TAG_VERSION) does not match pubspec version ($PUBSPEC_VERSION)."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Read the xcarchive path written by Fastlane after build_app.
|
||||||
|
# Fastlane writes lane_context[XCODEBUILD_ARCHIVE] to ios/gym_archive_path.txt.
|
||||||
|
XCARCHIVE=$(cat ios/gym_archive_path.txt 2>/dev/null)
|
||||||
|
if [ -z "$XCARCHIVE" ] || [ ! -d "$XCARCHIVE" ]; then
|
||||||
|
echo "::error::xcarchive not found. Check that Fastlane wrote gym_archive_path.txt correctly."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Found xcarchive: $XCARCHIVE"
|
||||||
|
|
||||||
|
# 4. Upload dSYMs (Native code: Swift, Objective-C, C++)
|
||||||
|
DSYM_DIR="$XCARCHIVE/dSYMs"
|
||||||
|
if [ -d "$DSYM_DIR" ]; then
|
||||||
|
echo "Uploading dSYMs from $DSYM_DIR..."
|
||||||
|
sentry-cli debug-files upload --include-sources "$DSYM_DIR"
|
||||||
|
else
|
||||||
|
echo "::error::dSYMs directory not found inside xcarchive at $DSYM_DIR."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|||||||
@@ -95,4 +95,24 @@ class SentryConfig {
|
|||||||
dist: sentryDist.isNotEmpty ? sentryDist : null,
|
dist: sentryDist.isNotEmpty ? sentryDist : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const String sentryConfigKeyChain = 'sentry_config_data';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'dsn': dsn,
|
||||||
|
'environment': environment,
|
||||||
|
'release': release,
|
||||||
|
if (dist != null) 'dist': dist,
|
||||||
|
'tracesSampleRate': tracesSampleRate,
|
||||||
|
'profilesSampleRate': profilesSampleRate,
|
||||||
|
'sessionSampleRate': sessionSampleRate,
|
||||||
|
'onErrorSampleRate': onErrorSampleRate,
|
||||||
|
'enableLogs': enableLogs,
|
||||||
|
'isDebug': isDebug,
|
||||||
|
'attachScreenshot': attachScreenshot,
|
||||||
|
'isAvailable': isAvailable,
|
||||||
|
'enableFramesTracking': enableFramesTracking,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ ServiceDefinitions.json
|
|||||||
Runner/GeneratedPluginRegistrant.*
|
Runner/GeneratedPluginRegistrant.*
|
||||||
Flutter/ephemeral/
|
Flutter/ephemeral/
|
||||||
|
|
||||||
|
# Build artifacts generated by Fastlane
|
||||||
|
gym_archive_path.txt
|
||||||
|
|
||||||
# Exceptions to above rules.
|
# Exceptions to above rules.
|
||||||
!default.mode1v3
|
!default.mode1v3
|
||||||
!default.mode2v3
|
!default.mode2v3
|
||||||
|
|||||||
+12
@@ -38,6 +38,18 @@ target 'Runner' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
target 'TwakeMailNSE' do
|
||||||
|
use_frameworks!
|
||||||
|
# Must use HybridSDK subspec at the same version as sentry_flutter requires.
|
||||||
|
# sentry_flutter 9.8.0 depends on Sentry/HybridSDK (= 8.56.2).
|
||||||
|
# Using plain `pod 'Sentry'` (Core) causes a version conflict and missing PrivateSentrySDKOnly errors.
|
||||||
|
#
|
||||||
|
# UPGRADE NOTE: When bumping sentry_flutter, update this version to match the new
|
||||||
|
# Sentry/HybridSDK constraint declared in Podfile.lock under DEPENDENCIES.
|
||||||
|
# Verify with: grep 'Sentry/HybridSDK' ios/Podfile.lock
|
||||||
|
pod 'Sentry/HybridSDK', '8.56.2'
|
||||||
|
end
|
||||||
|
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
installer.pods_project.targets.each do |target|
|
installer.pods_project.targets.each do |target|
|
||||||
flutter_additional_ios_build_settings(target)
|
flutter_additional_ios_build_settings(target)
|
||||||
|
|||||||
+3
-2
@@ -262,6 +262,7 @@ DEPENDENCIES:
|
|||||||
- pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
|
- pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
|
||||||
- printing (from `.symlinks/plugins/printing/ios`)
|
- printing (from `.symlinks/plugins/printing/ios`)
|
||||||
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||||
|
- Sentry/HybridSDK (= 8.56.2)
|
||||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
@@ -418,7 +419,7 @@ SPEC CHECKSUMS:
|
|||||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||||
package_info_plus: 580e9a5f1b6ca5594e7c9ed5f92d1dfb2a66b5e1
|
package_info_plus: 580e9a5f1b6ca5594e7c9ed5f92d1dfb2a66b5e1
|
||||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||||
patrol: 5df5d241d7f95f0df12a6906bbf45acb43a1e537
|
patrol: cea8074f183a2a4232d0ebd10569ae05149ada42
|
||||||
pdfrx: 310e84d01e06fd2af26e16507a0e48c27e99195c
|
pdfrx: 310e84d01e06fd2af26e16507a0e48c27e99195c
|
||||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||||
photo_manager: d2fbcc0f2d82458700ee6256a15018210a81d413
|
photo_manager: d2fbcc0f2d82458700ee6256a15018210a81d413
|
||||||
@@ -438,6 +439,6 @@ SPEC CHECKSUMS:
|
|||||||
UniversalDetector2: 7c9ffd935cf050eeb19edf7e90f6febe3743a1af
|
UniversalDetector2: 7c9ffd935cf050eeb19edf7e90f6febe3743a1af
|
||||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||||
|
|
||||||
PODFILE CHECKSUM: 40b12ce0bc437886ee4f4050970375d7d253708d
|
PODFILE CHECKSUM: 01821a4f5c4164186c3bcdf0b69845d9f6e8604e
|
||||||
|
|
||||||
COCOAPODS: 1.16.2
|
COCOAPODS: 1.16.2
|
||||||
|
|||||||
@@ -11,9 +11,12 @@
|
|||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
756BBC4C51FCB44047AB39E4 /* Pods_TeamMailShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 143BD7B2CF181BA69CDE351A /* Pods_TeamMailShareExtension.framework */; };
|
756BBC4C51FCB44047AB39E4 /* Pods_TeamMailShareExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 143BD7B2CF181BA69CDE351A /* Pods_TeamMailShareExtension.framework */; };
|
||||||
|
84EC933C2F236E8300A0EDC7 /* SentryConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EC933B2F236E7800A0EDC7 /* SentryConfig.swift */; };
|
||||||
|
84EC933E2F23705E00A0EDC7 /* SentryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84EC933D2F23705D00A0EDC7 /* SentryManager.swift */; };
|
||||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
B561AD69C2BE6DF40BF29406 /* Pods_TwakeMailNSE.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EA243B93F65E4E3CD7DC0348 /* Pods_TwakeMailNSE.framework */; };
|
||||||
CDFECA8C54311B749F044831 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5810EDDA99BEFEACD742F507 /* Pods_Runner.framework */; };
|
CDFECA8C54311B749F044831 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5810EDDA99BEFEACD742F507 /* Pods_Runner.framework */; };
|
||||||
F522E87F2C0EE23400DDA35B /* AuthenticationSSOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E87E2C0EE23400DDA35B /* AuthenticationSSOTests.swift */; };
|
F522E87F2C0EE23400DDA35B /* AuthenticationSSOTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E87E2C0EE23400DDA35B /* AuthenticationSSOTests.swift */; };
|
||||||
F522E8812C0EE3F200DDA35B /* AuthenticationSSO.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D8832B3877050009BB8A /* AuthenticationSSO.swift */; };
|
F522E8812C0EE3F200DDA35B /* AuthenticationSSO.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D8832B3877050009BB8A /* AuthenticationSSO.swift */; };
|
||||||
@@ -126,10 +129,14 @@
|
|||||||
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
4D124E74293A67D900BA5186 /* RunnerProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerProfile.entitlements; sourceTree = "<group>"; };
|
4D124E74293A67D900BA5186 /* RunnerProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerProfile.entitlements; sourceTree = "<group>"; };
|
||||||
5810EDDA99BEFEACD742F507 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
5810EDDA99BEFEACD742F507 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
5E35AF242935B79C2DCACB65 /* Pods-TwakeMailNSE.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TwakeMailNSE.debug.xcconfig"; path = "Target Support Files/Pods-TwakeMailNSE/Pods-TwakeMailNSE.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
631346BB444C71671599207F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
631346BB444C71671599207F /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
6B7C6E49525344DE515F7373 /* Pods-TwakeMailNSE.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TwakeMailNSE.release.xcconfig"; path = "Target Support Files/Pods-TwakeMailNSE/Pods-TwakeMailNSE.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
84EC933B2F236E7800A0EDC7 /* SentryConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryConfig.swift; sourceTree = "<group>"; };
|
||||||
|
84EC933D2F23705D00A0EDC7 /* SentryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryManager.swift; sourceTree = "<group>"; };
|
||||||
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -137,8 +144,10 @@
|
|||||||
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
AF31502A83CA492E27C36A7B /* Pods-TwakeMailNSE.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TwakeMailNSE.profile.xcconfig"; path = "Target Support Files/Pods-TwakeMailNSE/Pods-TwakeMailNSE.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
B2EAFF659572E6B9F5AFAAF8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
B2EAFF659572E6B9F5AFAAF8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
BC08294F5AE09BF8CC592AD1 /* Pods-TeamMailShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TeamMailShareExtension.release.xcconfig"; path = "Target Support Files/Pods-TeamMailShareExtension/Pods-TeamMailShareExtension.release.xcconfig"; sourceTree = "<group>"; };
|
BC08294F5AE09BF8CC592AD1 /* Pods-TeamMailShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TeamMailShareExtension.release.xcconfig"; path = "Target Support Files/Pods-TeamMailShareExtension/Pods-TeamMailShareExtension.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
EA243B93F65E4E3CD7DC0348 /* Pods_TwakeMailNSE.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_TwakeMailNSE.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
F522E87E2C0EE23400DDA35B /* AuthenticationSSOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationSSOTests.swift; sourceTree = "<group>"; };
|
F522E87E2C0EE23400DDA35B /* AuthenticationSSOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationSSOTests.swift; sourceTree = "<group>"; };
|
||||||
F522E8852C0EE8B600DDA35B /* CoreUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUtils.swift; sourceTree = "<group>"; };
|
F522E8852C0EE8B600DDA35B /* CoreUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUtils.swift; sourceTree = "<group>"; };
|
||||||
F52F992D27FD6EB900346091 /* TeamMailShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TeamMailShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
F52F992D27FD6EB900346091 /* TeamMailShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TeamMailShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -218,6 +227,7 @@
|
|||||||
files = (
|
files = (
|
||||||
F5630C812B2CEBC0003CC0FD /* KeychainAccess in Frameworks */,
|
F5630C812B2CEBC0003CC0FD /* KeychainAccess in Frameworks */,
|
||||||
F53D1E632B2DE80200051FD0 /* Alamofire in Frameworks */,
|
F53D1E632B2DE80200051FD0 /* Alamofire in Frameworks */,
|
||||||
|
B561AD69C2BE6DF40BF29406 /* Pods_TwakeMailNSE.framework in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -229,6 +239,7 @@
|
|||||||
children = (
|
children = (
|
||||||
5810EDDA99BEFEACD742F507 /* Pods_Runner.framework */,
|
5810EDDA99BEFEACD742F507 /* Pods_Runner.framework */,
|
||||||
143BD7B2CF181BA69CDE351A /* Pods_TeamMailShareExtension.framework */,
|
143BD7B2CF181BA69CDE351A /* Pods_TeamMailShareExtension.framework */,
|
||||||
|
EA243B93F65E4E3CD7DC0348 /* Pods_TwakeMailNSE.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -393,6 +404,7 @@
|
|||||||
F5630C792B2CE133003CC0FD /* Model */ = {
|
F5630C792B2CE133003CC0FD /* Model */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
84EC933B2F236E7800A0EDC7 /* SentryConfig.swift */,
|
||||||
F5630C7A2B2CE148003CC0FD /* KeychainSharingSession.swift */,
|
F5630C7A2B2CE148003CC0FD /* KeychainSharingSession.swift */,
|
||||||
F5630C842B2CF50C003CC0FD /* TypeName.swift */,
|
F5630C842B2CF50C003CC0FD /* TypeName.swift */,
|
||||||
);
|
);
|
||||||
@@ -402,6 +414,7 @@
|
|||||||
F5630C7C2B2CE33A003CC0FD /* Utils */ = {
|
F5630C7C2B2CE33A003CC0FD /* Utils */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
84EC933D2F23705D00A0EDC7 /* SentryManager.swift */,
|
||||||
F5630C7D2B2CE359003CC0FD /* InfoPlistReader.swift */,
|
F5630C7D2B2CE359003CC0FD /* InfoPlistReader.swift */,
|
||||||
F5BBBF542B2EEF3D007930E1 /* BundleExtension.swift */,
|
F5BBBF542B2EEF3D007930E1 /* BundleExtension.swift */,
|
||||||
F5630C862B2D0387003CC0FD /* PayloadParser.swift */,
|
F5630C862B2D0387003CC0FD /* PayloadParser.swift */,
|
||||||
@@ -491,6 +504,9 @@
|
|||||||
07753111B751BFDB3187FE6F /* Pods-TeamMailShareExtension.debug.xcconfig */,
|
07753111B751BFDB3187FE6F /* Pods-TeamMailShareExtension.debug.xcconfig */,
|
||||||
BC08294F5AE09BF8CC592AD1 /* Pods-TeamMailShareExtension.release.xcconfig */,
|
BC08294F5AE09BF8CC592AD1 /* Pods-TeamMailShareExtension.release.xcconfig */,
|
||||||
1FB76FA91BBCB2BF7B08705B /* Pods-TeamMailShareExtension.profile.xcconfig */,
|
1FB76FA91BBCB2BF7B08705B /* Pods-TeamMailShareExtension.profile.xcconfig */,
|
||||||
|
5E35AF242935B79C2DCACB65 /* Pods-TwakeMailNSE.debug.xcconfig */,
|
||||||
|
6B7C6E49525344DE515F7373 /* Pods-TwakeMailNSE.release.xcconfig */,
|
||||||
|
AF31502A83CA492E27C36A7B /* Pods-TwakeMailNSE.profile.xcconfig */,
|
||||||
);
|
);
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -564,6 +580,7 @@
|
|||||||
isa = PBXNativeTarget;
|
isa = PBXNativeTarget;
|
||||||
buildConfigurationList = F5D4EA0B2B2ABF090090DDFC /* Build configuration list for PBXNativeTarget "TwakeMailNSE" */;
|
buildConfigurationList = F5D4EA0B2B2ABF090090DDFC /* Build configuration list for PBXNativeTarget "TwakeMailNSE" */;
|
||||||
buildPhases = (
|
buildPhases = (
|
||||||
|
0F3B486617C7C494D3DB7CF9 /* [CP] Check Pods Manifest.lock */,
|
||||||
F5D4E9FC2B2ABF090090DDFC /* Sources */,
|
F5D4E9FC2B2ABF090090DDFC /* Sources */,
|
||||||
F5D4E9FD2B2ABF090090DDFC /* Frameworks */,
|
F5D4E9FD2B2ABF090090DDFC /* Frameworks */,
|
||||||
F5D4E9FE2B2ABF090090DDFC /* Resources */,
|
F5D4E9FE2B2ABF090090DDFC /* Resources */,
|
||||||
@@ -674,6 +691,28 @@
|
|||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
0F3B486617C7C494D3DB7CF9 /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-TwakeMailNSE-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
2A73AAD0923EF3B729E8973A /* [CP] Embed Pods Frameworks */ = {
|
2A73AAD0923EF3B729E8973A /* [CP] Embed Pods Frameworks */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
@@ -827,6 +866,7 @@
|
|||||||
files = (
|
files = (
|
||||||
F5BBBF512B2EEC37007930E1 /* NetworkExceptions.swift in Sources */,
|
F5BBBF512B2EEC37007930E1 /* NetworkExceptions.swift in Sources */,
|
||||||
F53D1E7F2B2E3C2600051FD0 /* JmapConstants.swift in Sources */,
|
F53D1E7F2B2E3C2600051FD0 /* JmapConstants.swift in Sources */,
|
||||||
|
84EC933C2F236E8300A0EDC7 /* SentryConfig.swift in Sources */,
|
||||||
F5E7D8822B3876F60009BB8A /* AuthenticationCredential.swift in Sources */,
|
F5E7D8822B3876F60009BB8A /* AuthenticationCredential.swift in Sources */,
|
||||||
F5D4EA032B2ABF090090DDFC /* NotificationService.swift in Sources */,
|
F5D4EA032B2ABF090090DDFC /* NotificationService.swift in Sources */,
|
||||||
F522E8872C0EE8B600DDA35B /* CoreUtils.swift in Sources */,
|
F522E8872C0EE8B600DDA35B /* CoreUtils.swift in Sources */,
|
||||||
@@ -838,6 +878,7 @@
|
|||||||
F53D1E662B2DE8B800051FD0 /* AlamofireService.swift in Sources */,
|
F53D1E662B2DE8B800051FD0 /* AlamofireService.swift in Sources */,
|
||||||
F5BBBF532B2EECAA007930E1 /* JmapExceptions.swift in Sources */,
|
F5BBBF532B2EECAA007930E1 /* JmapExceptions.swift in Sources */,
|
||||||
F53D1E882B2E4B3B00051FD0 /* AuthenticationType.swift in Sources */,
|
F53D1E882B2E4B3B00051FD0 /* AuthenticationType.swift in Sources */,
|
||||||
|
84EC933E2F23705E00A0EDC7 /* SentryManager.swift in Sources */,
|
||||||
F53D1E6C2B2E208C00051FD0 /* Email.swift in Sources */,
|
F53D1E6C2B2E208C00051FD0 /* Email.swift in Sources */,
|
||||||
F5630C872B2D0387003CC0FD /* PayloadParser.swift in Sources */,
|
F5630C872B2D0387003CC0FD /* PayloadParser.swift in Sources */,
|
||||||
F5E7D8862B3877390009BB8A /* TokenResponse.swift in Sources */,
|
F5E7D8862B3877390009BB8A /* TokenResponse.swift in Sources */,
|
||||||
@@ -1419,6 +1460,7 @@
|
|||||||
};
|
};
|
||||||
F5D4EA082B2ABF090090DDFC /* Debug */ = {
|
F5D4EA082B2ABF090090DDFC /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 5E35AF242935B79C2DCACB65 /* Pods-TwakeMailNSE.debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
APP_GROUP_ID = group.com.linagora.teammail;
|
APP_GROUP_ID = group.com.linagora.teammail;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
@@ -1466,6 +1508,7 @@
|
|||||||
};
|
};
|
||||||
F5D4EA092B2ABF090090DDFC /* Release */ = {
|
F5D4EA092B2ABF090090DDFC /* Release */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 6B7C6E49525344DE515F7373 /* Pods-TwakeMailNSE.release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
APP_GROUP_ID = group.com.linagora.teammail;
|
APP_GROUP_ID = group.com.linagora.teammail;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
@@ -1510,6 +1553,7 @@
|
|||||||
};
|
};
|
||||||
F5D4EA0A2B2ABF090090DDFC /* Profile */ = {
|
F5D4EA0A2B2ABF090090DDFC /* Profile */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = AF31502A83CA492E27C36A7B /* Pods-TwakeMailNSE.profile.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
APP_GROUP_ID = group.com.linagora.teammail;
|
APP_GROUP_ID = group.com.linagora.teammail;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
|||||||
@@ -58,3 +58,21 @@ class KeychainController: KeychainControllerDelegate {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension KeychainController {
|
||||||
|
/// The key used in Dart to store the Sentry configuration JSON
|
||||||
|
private var sentryConfigKey: String { "sentry_config_data" }
|
||||||
|
|
||||||
|
/// Retrieves and decodes the SentryConfig from Keychain
|
||||||
|
func retrieveSentryConfig() -> SentryConfig? {
|
||||||
|
do {
|
||||||
|
guard let configData = try keychain.getData(sentryConfigKey) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return try JSONDecoder().decode(SentryConfig.self, from: configData)
|
||||||
|
} catch {
|
||||||
|
TwakeLogger.shared.log(message: "SentryConfig could not be decoded from Keychain")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
import Sentry
|
||||||
|
|
||||||
struct KeychainSharingSession: Codable {
|
struct KeychainSharingSession: Codable {
|
||||||
let accountId: String
|
let accountId: String
|
||||||
@@ -61,4 +62,12 @@ extension KeychainSharingSession {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sentryUser: User {
|
||||||
|
let user = User()
|
||||||
|
user.userId = self.accountId
|
||||||
|
user.email = self.userName
|
||||||
|
user.username = self.userName
|
||||||
|
return user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct SentryConfig: Codable {
|
||||||
|
/// DSN (Data Source Name) endpoint for the Sentry project
|
||||||
|
let dsn: String
|
||||||
|
|
||||||
|
/// Running environment (production/staging/dev)
|
||||||
|
let environment: String
|
||||||
|
|
||||||
|
/// Current app release version
|
||||||
|
let release: String
|
||||||
|
|
||||||
|
/// Distribution (e.g. Git SHA). Must match --dist used when uploading symbols to Sentry.
|
||||||
|
/// Optional: only set when the main app passes it via --dart-define=SENTRY_DIST.
|
||||||
|
let dist: String?
|
||||||
|
|
||||||
|
/// Performance monitoring: Set to 1.0 to capture 100% of transactions for tracing.
|
||||||
|
/// High values in NSE might impact extension memory limit (24MB).
|
||||||
|
let tracesSampleRate: Double
|
||||||
|
|
||||||
|
/// Optional profiling sample rate.
|
||||||
|
let profilesSampleRate: Double
|
||||||
|
|
||||||
|
/// Release Health: The sampling rate for sessions (0.0 to 1.0).
|
||||||
|
let sessionSampleRate: Double
|
||||||
|
|
||||||
|
/// Error tracking: The sampling rate for errors (0.0 to 1.0).
|
||||||
|
/// If set to 0.1, only 10% of errors are sent.
|
||||||
|
let onErrorSampleRate: Double
|
||||||
|
|
||||||
|
/// Enable logs to be sent to Sentry (or internal console logging).
|
||||||
|
let enableLogs: Bool
|
||||||
|
|
||||||
|
/// Debug logs during development.
|
||||||
|
let isDebug: Bool
|
||||||
|
|
||||||
|
/// Automatically attaches a screenshot when capturing an error.
|
||||||
|
/// Ignored in NSE as there is no UI to screenshot.
|
||||||
|
let attachScreenshot: Bool
|
||||||
|
|
||||||
|
/// Master switch to check if Sentry integration is allowed/available.
|
||||||
|
let isAvailable: Bool
|
||||||
|
|
||||||
|
/// Performance: Tracks UI rendering performance.
|
||||||
|
/// Ignored in NSE as there is no UI rendering.
|
||||||
|
let enableFramesTracking: Bool
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import UserNotifications
|
import UserNotifications
|
||||||
|
import Sentry
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
class NotificationService: UNNotificationServiceExtension {
|
class NotificationService: UNNotificationServiceExtension {
|
||||||
@@ -13,6 +14,10 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier)
|
accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier)
|
||||||
|
|
||||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||||
|
|
||||||
|
SentryManager.shared.configure(with: keychainController)
|
||||||
|
SentryManager.shared.clearUser()
|
||||||
|
|
||||||
handler = contentHandler
|
handler = contentHandler
|
||||||
modifiedContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
modifiedContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
||||||
|
|
||||||
@@ -22,10 +27,12 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
if isAppActive == true {
|
if isAppActive == true {
|
||||||
self.modifiedContent?.userInfo = request.content.userInfo.merging(["data": request.content.userInfo], uniquingKeysWith: {(_, new) in new})
|
self.modifiedContent?.userInfo = request.content.userInfo.merging(["data": request.content.userInfo], uniquingKeysWith: {(_, new) in new})
|
||||||
contentHandler(self.modifiedContent ?? request.content)
|
contentHandler(self.modifiedContent ?? request.content)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let payloadData = request.content.userInfo as? [String: Any],
|
guard let payloadData = request.content.userInfo as? [String: Any],
|
||||||
!keychainController.retrieveSharingSessions().isEmpty else {
|
!keychainController.retrieveSharingSessions().isEmpty else {
|
||||||
|
SentryManager.shared.capture(message: "NSE: Payload invalid or No Session found in Keychain")
|
||||||
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
|
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
|
||||||
return self.notify()
|
return self.notify()
|
||||||
}
|
}
|
||||||
@@ -42,6 +49,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
override func serviceExtensionTimeWillExpire() {
|
override func serviceExtensionTimeWillExpire() {
|
||||||
// Called just before the extension will be terminated by the system.
|
// Called just before the extension will be terminated by the system.
|
||||||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
||||||
|
SentryManager.shared.capture(message: "NSE: Service Extension Time Expired (Timeout)", flushTimeout: 0.3)
|
||||||
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
|
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
|
||||||
self.notify()
|
self.notify()
|
||||||
}
|
}
|
||||||
@@ -55,14 +63,23 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
let mapStateChanges: [String: [TypeName: String]] = PayloadParser.shared.parsingPayloadNotification(payloadData: payloadData)
|
let mapStateChanges: [String: [TypeName: String]] = PayloadParser.shared.parsingPayloadNotification(payloadData: payloadData)
|
||||||
|
|
||||||
if (mapStateChanges.isEmpty) {
|
if (mapStateChanges.isEmpty) {
|
||||||
|
SentryManager.shared.capture(message: "NSE: Payload parsing returned empty state changes")
|
||||||
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
|
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
|
||||||
return self.notify()
|
return self.notify()
|
||||||
} else {
|
} else {
|
||||||
guard let currentAccountId = mapStateChanges.keys.first,
|
guard let currentAccountId = mapStateChanges.keys.first,
|
||||||
let keychainSharingSession = keychainController.retrieveSharingSessionFromKeychain(accountId: currentAccountId),
|
let keychainSharingSession = keychainController.retrieveSharingSessionFromKeychain(accountId: currentAccountId),
|
||||||
keychainSharingSession.tokenOIDC != nil || keychainSharingSession.basicAuth != nil,
|
keychainSharingSession.tokenOIDC != nil || keychainSharingSession.basicAuth != nil else {
|
||||||
let listStateOfAccount = mapStateChanges[currentAccountId],
|
SentryManager.shared.capture(message: "NSE: Session missing or invalid credential for account: \(mapStateChanges.keys.first ?? "unknown")")
|
||||||
|
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
|
||||||
|
return self.notify()
|
||||||
|
}
|
||||||
|
|
||||||
|
SentryManager.shared.setSentryUser(keychainSharingSession.sentryUser)
|
||||||
|
|
||||||
|
guard let listStateOfAccount = mapStateChanges[currentAccountId],
|
||||||
let newEmailDeliveryState = listStateOfAccount[TypeName.emailDelivery] else {
|
let newEmailDeliveryState = listStateOfAccount[TypeName.emailDelivery] else {
|
||||||
|
SentryManager.shared.capture(message: "NSE: Missing emailDelivery state in payload")
|
||||||
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
|
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
|
||||||
return self.notify()
|
return self.notify()
|
||||||
}
|
}
|
||||||
@@ -106,7 +123,8 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
TwakeLogger.shared.log(message: "JmapClient.shared.getNewEmails: \(error)")
|
TwakeLogger.shared.log(message: "Error processing emails: \(error)")
|
||||||
|
SentryManager.shared.capture(error: error)
|
||||||
self.showDefaultNotification(message: NSLocalizedString(self.newEmailDefaultMessageKey, comment: "Localizable"))
|
self.showDefaultNotification(message: NSLocalizedString(self.newEmailDefaultMessageKey, comment: "Localizable"))
|
||||||
return self.notify()
|
return self.notify()
|
||||||
}
|
}
|
||||||
@@ -183,7 +201,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
content.userInfo = userInfo
|
content.userInfo = userInfo
|
||||||
|
|
||||||
// Create a notification trigger
|
// Create a notification trigger
|
||||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false)
|
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false)
|
||||||
// Create a notification request
|
// Create a notification request
|
||||||
let request = UNNotificationRequest(identifier: notificationId, content: content, trigger: trigger)
|
let request = UNNotificationRequest(identifier: notificationId, content: content, trigger: trigger)
|
||||||
|
|
||||||
@@ -191,6 +209,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
UNUserNotificationCenter.current().add(request) { error in
|
UNUserNotificationCenter.current().add(request) { error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
TwakeLogger.shared.log(message: "Error scheduling notification: \(error.localizedDescription)")
|
TwakeLogger.shared.log(message: "Error scheduling notification: \(error.localizedDescription)")
|
||||||
|
SentryManager.shared.capture(error: error)
|
||||||
} else {
|
} else {
|
||||||
TwakeLogger.shared.log(message: "Notification scheduled successfully")
|
TwakeLogger.shared.log(message: "Notification scheduled successfully")
|
||||||
}
|
}
|
||||||
@@ -212,6 +231,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func cleanUp() {
|
private func cleanUp() {
|
||||||
|
SentryManager.shared.clearUser()
|
||||||
handler = nil
|
handler = nil
|
||||||
modifiedContent = nil
|
modifiedContent = nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import Foundation
|
||||||
|
import Sentry
|
||||||
|
|
||||||
|
class SentryManager {
|
||||||
|
|
||||||
|
/// Singleton instance for easy access
|
||||||
|
static let shared = SentryManager()
|
||||||
|
|
||||||
|
/// Internal flag to prevent multiple initializations
|
||||||
|
private var isInitialized: Bool = false
|
||||||
|
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
/// Configures Sentry using the config stored in Keychain.
|
||||||
|
func configure(with keychainController: KeychainController) {
|
||||||
|
// Prevent re-initialization
|
||||||
|
if isInitialized { return }
|
||||||
|
|
||||||
|
// Retrieve config and validate 'isAvailable' and DSN presence
|
||||||
|
guard let config = keychainController.retrieveSentryConfig(),
|
||||||
|
config.isAvailable,
|
||||||
|
!config.dsn.isEmpty else {
|
||||||
|
TwakeLogger.shared.log(message: "Sentry is disabled or config is missing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start Sentry SDK with options mapped from the config
|
||||||
|
SentrySDK.start { options in
|
||||||
|
options.dsn = config.dsn
|
||||||
|
options.environment = config.environment
|
||||||
|
options.releaseName = config.release
|
||||||
|
options.dist = config.dist
|
||||||
|
options.debug = config.isDebug
|
||||||
|
// Map enableLogs to diagnostic level if needed
|
||||||
|
options.diagnosticLevel = config.isDebug ? .debug : .none
|
||||||
|
// Maps 'onErrorSampleRate' (Dart) to 'sampleRate' (iOS).
|
||||||
|
// tracesSampleRate, profilesSampleRate, sessionSampleRate are intentionally not applied:
|
||||||
|
// NSE has no UI and its lifecycle is too short for performance/session tracking.
|
||||||
|
options.sampleRate = NSNumber(value: config.onErrorSampleRate)
|
||||||
|
// Disable App Hang tracking: NSE execution is short, this causes false positives.
|
||||||
|
options.enableAppHangTracking = false
|
||||||
|
// Disable Watchdog tracking: Prevent OOM reports specific to extensions.
|
||||||
|
options.enableWatchdogTerminationTracking = false
|
||||||
|
// Disable UI/Interaction tracing: NSE has no UI.
|
||||||
|
options.enableUserInteractionTracing = false
|
||||||
|
options.enableAutoPerformanceTracing = false
|
||||||
|
options.enablePreWarmedAppStartTracing = false
|
||||||
|
}
|
||||||
|
|
||||||
|
isInitialized = true
|
||||||
|
TwakeLogger.shared.log(message: "Sentry has been successfully initialized.")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safely captures an error if Sentry is initialized.
|
||||||
|
/// - Parameter flushTimeout: If provided, blocks until events are sent or the timeout elapses.
|
||||||
|
/// Use in critical paths (e.g. serviceExtensionTimeWillExpire) where the process may be
|
||||||
|
/// suspended before Sentry flushes its queue.
|
||||||
|
func capture(error: Error, flushTimeout: TimeInterval? = nil) {
|
||||||
|
guard isInitialized else { return }
|
||||||
|
SentrySDK.capture(error: error)
|
||||||
|
if let flushTimeout {
|
||||||
|
SentrySDK.flush(timeout: flushTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safely captures a message if Sentry is initialized.
|
||||||
|
/// - Parameter flushTimeout: If provided, blocks until events are sent or the timeout elapses.
|
||||||
|
/// Use in critical paths (e.g. serviceExtensionTimeWillExpire) where the process may be
|
||||||
|
/// suspended before Sentry flushes its queue.
|
||||||
|
func capture(message: String, flushTimeout: TimeInterval? = nil) {
|
||||||
|
guard isInitialized else { return }
|
||||||
|
SentrySDK.capture(message: message)
|
||||||
|
if let flushTimeout {
|
||||||
|
SentrySDK.flush(timeout: flushTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set user context cho Sentry
|
||||||
|
func setSentryUser(_ user: User) {
|
||||||
|
guard isInitialized else { return }
|
||||||
|
SentrySDK.setUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear user
|
||||||
|
func clearUser() {
|
||||||
|
guard isInitialized else { return }
|
||||||
|
SentrySDK.setUser(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,6 +56,10 @@ platform :ios do
|
|||||||
workspace: "Runner.xcworkspace",
|
workspace: "Runner.xcworkspace",
|
||||||
export_method: "app-store"
|
export_method: "app-store"
|
||||||
)
|
)
|
||||||
|
# Write the xcarchive path to a file so CI can locate dSYMs for Sentry upload.
|
||||||
|
# lane_context[XCODEBUILD_ARCHIVE] is set by build_app and contains the exact path.
|
||||||
|
archive_path = Actions.lane_context[SharedValues::XCODEBUILD_ARCHIVE]
|
||||||
|
File.write("gym_archive_path.txt", archive_path.to_s) if archive_path
|
||||||
upload_to_testflight(
|
upload_to_testflight(
|
||||||
skip_waiting_for_build_processing: true,
|
skip_waiting_for_build_processing: true,
|
||||||
ipa: "Runner.ipa"
|
ipa: "Runner.ipa"
|
||||||
|
|||||||
+5
-1
@@ -234,6 +234,7 @@ import 'package:tmail_ui_user/main/universal_import/html_stub.dart' as html;
|
|||||||
import 'package:tmail_ui_user/main/utils/app_config.dart';
|
import 'package:tmail_ui_user/main/utils/app_config.dart';
|
||||||
import 'package:tmail_ui_user/main/utils/email_receive_manager.dart';
|
import 'package:tmail_ui_user/main/utils/email_receive_manager.dart';
|
||||||
import 'package:tmail_ui_user/main/utils/ios_notification_manager.dart';
|
import 'package:tmail_ui_user/main/utils/ios_notification_manager.dart';
|
||||||
|
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
|
||||||
import 'package:tmail_ui_user/main/utils/toast_manager.dart';
|
import 'package:tmail_ui_user/main/utils/toast_manager.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
@@ -406,7 +407,10 @@ class MailboxDashBoardController extends ReloadableController
|
|||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
if (PlatformInfo.isMobile) {
|
if (PlatformInfo.isMobile) {
|
||||||
_sentryEcosystem = SentryEcosystem(getBinding<SentryConfigurationCacheManager>());
|
_sentryEcosystem = SentryEcosystem(
|
||||||
|
getBinding<SentryConfigurationCacheManager>(),
|
||||||
|
getBinding<IOSSharingManager>(),
|
||||||
|
);
|
||||||
_registerReceivingFileSharingStream();
|
_registerReceivingFileSharingStream();
|
||||||
_registerDeepLinks();
|
_registerDeepLinks();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import 'package:core/presentation/extensions/string_extension.dart';
|
import 'package:core/presentation/extensions/string_extension.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/sentry/sentry_config.dart';
|
import 'package:core/utils/sentry/sentry_config.dart';
|
||||||
import 'package:core/utils/sentry/sentry_manager.dart';
|
import 'package:core/utils/sentry/sentry_manager.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.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/extensions/sentry_cache_extensions.dart';
|
||||||
import 'package:tmail_ui_user/features/caching/manager/sentry_configuration_cache_manager.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/domain/linagora_ecosystem/sentry_config_linagora_ecosystem.dart';
|
||||||
|
import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
|
||||||
|
|
||||||
class SentryEcosystem {
|
class SentryEcosystem {
|
||||||
final SentryConfigurationCacheManager? _cacheManager;
|
final SentryConfigurationCacheManager? _cacheManager;
|
||||||
|
final IOSSharingManager? _iosSharingManager;
|
||||||
|
|
||||||
SentryUser? _sentryUser;
|
SentryUser? _sentryUser;
|
||||||
|
|
||||||
SentryEcosystem(this._cacheManager);
|
SentryEcosystem(this._cacheManager, this._iosSharingManager);
|
||||||
|
|
||||||
void initUser(SentryUser? user) {
|
void initUser(SentryUser? user) {
|
||||||
_sentryUser = user;
|
_sentryUser = user;
|
||||||
@@ -40,6 +43,10 @@ class SentryEcosystem {
|
|||||||
_applyUser();
|
_applyUser();
|
||||||
|
|
||||||
await _cacheData(sentryConfig, _sentryUser);
|
await _cacheData(sentryConfig, _sentryUser);
|
||||||
|
|
||||||
|
if (PlatformInfo.isIOS) {
|
||||||
|
await _saveSentryConfigToKeychain(sentryConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _applyUser() {
|
void _applyUser() {
|
||||||
@@ -64,4 +71,8 @@ class SentryEcosystem {
|
|||||||
await _cacheManager!.clearSentryConfiguration().catchError((_) {});
|
await _cacheManager!.clearSentryConfiguration().catchError((_) {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _saveSentryConfigToKeychain(SentryConfig sentryConfig) async {
|
||||||
|
await _iosSharingManager?.saveSentryConfigToKeychain(sentryConfig);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:core/utils/sentry/sentry_config.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||||
import 'package:model/extensions/account_id_extensions.dart';
|
import 'package:model/extensions/account_id_extensions.dart';
|
||||||
@@ -11,11 +12,16 @@ class KeychainSharingManager {
|
|||||||
|
|
||||||
KeychainSharingManager(this._secureStorage);
|
KeychainSharingManager(this._secureStorage);
|
||||||
|
|
||||||
Future<void> save(KeychainSharingSession keychainSharingSession) => _secureStorage.write(
|
Future<void> saveSharingSession(KeychainSharingSession keychainSharingSession) => _secureStorage.write(
|
||||||
key: keychainSharingSession.accountId.asString,
|
key: keychainSharingSession.accountId.asString,
|
||||||
value: jsonEncode(keychainSharingSession.toJson()),
|
value: jsonEncode(keychainSharingSession.toJson()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<void> saveSentryConfig(SentryConfig sentryConfig) => _secureStorage.write(
|
||||||
|
key: SentryConfig.sentryConfigKeyChain,
|
||||||
|
value: jsonEncode(sentryConfig.toJson()),
|
||||||
|
);
|
||||||
|
|
||||||
Future<bool> isSessionExist(AccountId accountId) =>
|
Future<bool> isSessionExist(AccountId accountId) =>
|
||||||
_secureStorage.containsKey(key: accountId.asString);
|
_secureStorage.containsKey(key: accountId.asString);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.dart';
|
||||||
|
import 'package:core/utils/sentry/sentry_config.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/user_name.dart';
|
import 'package:jmap_dart_client/jmap/core/user_name.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||||
@@ -103,7 +104,7 @@ class IOSSharingManager {
|
|||||||
isTWP: tokenRecords?.isTWP ?? false,
|
isTWP: tokenRecords?.isTWP ?? false,
|
||||||
);
|
);
|
||||||
|
|
||||||
await _keychainSharingManager.save(keychainSharingSession);
|
await _keychainSharingManager.saveSharingSession(keychainSharingSession);
|
||||||
|
|
||||||
log('IOSSharingManager::_saveKeyChainSharingSession: COMPLETED');
|
log('IOSSharingManager::_saveKeyChainSharingSession: COMPLETED');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -194,7 +195,7 @@ class IOSSharingManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final newKeychain = keychainSharingStored.updating(emailState: newEmailState);
|
final newKeychain = keychainSharingStored.updating(emailState: newEmailState);
|
||||||
await _keychainSharingManager.save(newKeychain);
|
await _keychainSharingManager.saveSharingSession(newKeychain);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isExistMailboxIdsBlockNotificationInKeyChain(AccountId accountId) async {
|
Future<bool> isExistMailboxIdsBlockNotificationInKeyChain(AccountId accountId) async {
|
||||||
@@ -217,7 +218,7 @@ class IOSSharingManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final newKeychain = keychainSharingStored.updating(mailboxIdsBlockNotification: mailboxIds);
|
final newKeychain = keychainSharingStored.updating(mailboxIdsBlockNotification: mailboxIds);
|
||||||
await _keychainSharingManager.save(newKeychain);
|
await _keychainSharingManager.saveSharingSession(newKeychain);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logWarning('IOSSharingManager::updateMailboxIdsBlockNotificationInKeyChain: Exception = $e');
|
logWarning('IOSSharingManager::updateMailboxIdsBlockNotificationInKeyChain: Exception = $e');
|
||||||
}
|
}
|
||||||
@@ -240,4 +241,14 @@ class IOSSharingManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> saveSentryConfigToKeychain(SentryConfig sentryConfig) async {
|
||||||
|
log('IOSSharingManager::saveSentryConfigToKeychain: START');
|
||||||
|
try {
|
||||||
|
await _keychainSharingManager.saveSentryConfig(sentryConfig);
|
||||||
|
log('IOSSharingManager::saveSentryConfigToKeychain: COMPLETED');
|
||||||
|
} catch (e) {
|
||||||
|
logWarning('IOSSharingManager::saveSentryConfigToKeychain: Exception: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:core/utils/sentry/sentry_config.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||||
@@ -13,6 +14,59 @@ void main() {
|
|||||||
late KeychainSharingManager keychainSharingManager;
|
late KeychainSharingManager keychainSharingManager;
|
||||||
late FlutterSecureStorage flutterSecureStorage;
|
late FlutterSecureStorage flutterSecureStorage;
|
||||||
|
|
||||||
|
group('KeychainSharingManager:saveSentryConfig', () {
|
||||||
|
setUp(() {
|
||||||
|
flutterSecureStorage = const FlutterSecureStorage();
|
||||||
|
keychainSharingManager = KeychainSharingManager(flutterSecureStorage);
|
||||||
|
FlutterSecureStorage.setMockInitialValues({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('WHEN saveSentryConfig called \n'
|
||||||
|
'THEN stores JSON under sentryConfigKeyChain key', () async {
|
||||||
|
final config = SentryConfig(
|
||||||
|
dsn: 'https://test@sentry.io/123',
|
||||||
|
environment: 'production',
|
||||||
|
release: '1.0.0',
|
||||||
|
);
|
||||||
|
|
||||||
|
await keychainSharingManager.saveSentryConfig(config);
|
||||||
|
|
||||||
|
final stored = await flutterSecureStorage.read(
|
||||||
|
key: SentryConfig.sentryConfigKeyChain,
|
||||||
|
);
|
||||||
|
expect(stored, isNotNull);
|
||||||
|
final decoded = jsonDecode(stored!) as Map<String, dynamic>;
|
||||||
|
expect(decoded['dsn'], equals('https://test@sentry.io/123'));
|
||||||
|
expect(decoded['environment'], equals('production'));
|
||||||
|
expect(decoded['release'], equals('1.0.0'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('WHEN SentryConfig has no dist \n'
|
||||||
|
'THEN toJson does not include dist key', () {
|
||||||
|
final config = SentryConfig(
|
||||||
|
dsn: 'https://test@sentry.io/123',
|
||||||
|
environment: 'staging',
|
||||||
|
release: '1.0.0',
|
||||||
|
);
|
||||||
|
|
||||||
|
final json = config.toJson();
|
||||||
|
expect(json.containsKey('dist'), isFalse);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('WHEN SentryConfig has dist \n'
|
||||||
|
'THEN toJson includes dist key', () {
|
||||||
|
final config = SentryConfig(
|
||||||
|
dsn: 'https://test@sentry.io/123',
|
||||||
|
environment: 'staging',
|
||||||
|
release: '1.0.0',
|
||||||
|
dist: 'abc123',
|
||||||
|
);
|
||||||
|
|
||||||
|
final json = config.toJson();
|
||||||
|
expect(json['dist'], equals('abc123'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
group('KeychainSharingManager:save for the same accountId', () {
|
group('KeychainSharingManager:save for the same accountId', () {
|
||||||
setUp(() {
|
setUp(() {
|
||||||
flutterSecureStorage = const FlutterSecureStorage();
|
flutterSecureStorage = const FlutterSecureStorage();
|
||||||
@@ -36,7 +90,7 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// act
|
// act
|
||||||
await keychainSharingManager.save(keychainSharingSession);
|
await keychainSharingManager.saveSharingSession(keychainSharingSession);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
final keychainSession = await keychainSharingManager.getSharingSession(AccountId(Id(
|
final keychainSession = await keychainSharingManager.getSharingSession(AccountId(Id(
|
||||||
@@ -79,7 +133,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// act
|
// act
|
||||||
await keychainSharingManager.save(keychainSharingSession2);
|
await keychainSharingManager.saveSharingSession(keychainSharingSession2);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
final keychainSession = await keychainSharingManager.getSharingSession(AccountId(Id(
|
final keychainSession = await keychainSharingManager.getSharingSession(AccountId(Id(
|
||||||
|
|||||||
Reference in New Issue
Block a user