diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 405ec6c17..f59eb1a97 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -178,8 +178,10 @@ jobs: # 4. Upload dSYMs (Native code: Swift, Objective-C, C++) DSYM_DIR="$XCARCHIVE/dSYMs" if [ -d "$DSYM_DIR" ]; then + echo "dSYM contents:" + ls -la "$DSYM_DIR" echo "Uploading dSYMs from $DSYM_DIR..." - sentry-cli debug-files upload --include-sources "$DSYM_DIR" + sentry-cli debug-files upload --include-sources --log-level info "$DSYM_DIR" else echo "::error::dSYMs directory not found inside xcarchive at $DSYM_DIR." exit 1 diff --git a/ios/TwakeMailNSE/Keychain/KeychainController.swift b/ios/TwakeMailNSE/Keychain/KeychainController.swift index a5324cb19..f21e6de43 100644 --- a/ios/TwakeMailNSE/Keychain/KeychainController.swift +++ b/ios/TwakeMailNSE/Keychain/KeychainController.swift @@ -71,7 +71,7 @@ extension KeychainController { } return try JSONDecoder().decode(SentryConfig.self, from: configData) } catch { - TwakeLogger.shared.log(message: "SentryConfig could not be decoded from Keychain") + TwakeLogger.shared.log(message: "SentryConfig could not be decoded from Keychain: \(error)") return nil } } diff --git a/ios/TwakeMailNSE/NotificationService.swift b/ios/TwakeMailNSE/NotificationService.swift index ff69185cf..23028b5ef 100644 --- a/ios/TwakeMailNSE/NotificationService.swift +++ b/ios/TwakeMailNSE/NotificationService.swift @@ -32,7 +32,7 @@ class NotificationService: UNNotificationServiceExtension { guard let payloadData = request.content.userInfo as? [String: Any], !keychainController.retrieveSharingSessions().isEmpty else { - SentryManager.shared.capture(message: "NSE: Payload invalid or No Session found in Keychain") + SentryManager.shared.addBreadcrumb(message: "NSE: Payload invalid or No Session found in Keychain", level: .warning) self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable")) return self.notify() } @@ -63,29 +63,34 @@ class NotificationService: UNNotificationServiceExtension { let mapStateChanges: [String: [TypeName: String]] = PayloadParser.shared.parsingPayloadNotification(payloadData: payloadData) if (mapStateChanges.isEmpty) { - SentryManager.shared.capture(message: "NSE: Payload parsing returned empty state changes") + SentryManager.shared.addBreadcrumb(message: "NSE: Payload parsing returned empty state changes", level: .warning) self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable")) return self.notify() } else { guard let currentAccountId = mapStateChanges.keys.first, let keychainSharingSession = keychainController.retrieveSharingSessionFromKeychain(accountId: currentAccountId), keychainSharingSession.tokenOIDC != nil || keychainSharingSession.basicAuth != nil else { - SentryManager.shared.capture(message: "NSE: Session missing or invalid credential for account: \(mapStateChanges.keys.first ?? "unknown")") + SentryManager.shared.addBreadcrumb(message: "NSE: Session missing or invalid credential for account: \(mapStateChanges.keys.first ?? "unknown")", level: .warning) 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 { - SentryManager.shared.capture(message: "NSE: Missing emailDelivery state in payload") + SentryManager.shared.addBreadcrumb(message: "NSE: Missing emailDelivery state in payload", level: .warning) self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable")) return self.notify() } - - guard let oldEmailDeliveryState = keychainSharingSession.emailDeliveryState ?? keychainSharingSession.emailState, - newEmailDeliveryState != oldEmailDeliveryState else { + + guard let oldEmailDeliveryState = keychainSharingSession.emailDeliveryState ?? keychainSharingSession.emailState else { + SentryManager.shared.addBreadcrumb(message: "NSE: No stored email state for account: \(currentAccountId)", level: .warning) + self.showDefaultNotification(message: NSLocalizedString(self.newEmailDefaultMessageKey, comment: "Localizable")) + return self.notify() + } + + guard newEmailDeliveryState != oldEmailDeliveryState else { self.showDefaultNotification(message: NSLocalizedString(self.newEmailDefaultMessageKey, comment: "Localizable")) return self.notify() } @@ -101,33 +106,28 @@ class NotificationService: UNNotificationServiceExtension { oidcScopes: keychainSharingSession.oidcScopes, isTWP: keychainSharingSession.isTWP, onComplete: { (emails, errors) in - do { - if emails.isEmpty { - self.showDefaultNotification(message: NSLocalizedString(self.newEmailDefaultMessageKey, comment: "Localizable")) - return self.notify() - } else { - self.keychainController.updateEmailDeliveryStateToKeychain( - accountId: keychainSharingSession.accountId, - newEmailDeliveryState: newEmailDeliveryState - ) + errors.forEach { SentryManager.shared.capture(error: $0) } - let mailboxIdsBlockNotification = keychainSharingSession.mailboxIdsBlockNotification ?? [] - - if (mailboxIdsBlockNotification.isEmpty) { - return self.showListNotification(emails: emails) - } else { - let emailFiltered = self.filterEmailsToPushNotification( - emails: emails, - mailboxIdsBlockNotification: mailboxIdsBlockNotification) - return self.showListNotification(emails: emailFiltered) - } - } - } catch { - TwakeLogger.shared.log(message: "Error processing emails: \(error)") - SentryManager.shared.capture(error: error) + if emails.isEmpty { self.showDefaultNotification(message: NSLocalizedString(self.newEmailDefaultMessageKey, comment: "Localizable")) return self.notify() } + + self.keychainController.updateEmailDeliveryStateToKeychain( + accountId: keychainSharingSession.accountId, + newEmailDeliveryState: newEmailDeliveryState + ) + + let mailboxIdsBlockNotification = keychainSharingSession.mailboxIdsBlockNotification ?? [] + + if mailboxIdsBlockNotification.isEmpty { + return self.showListNotification(emails: emails) + } else { + let emailFiltered = self.filterEmailsToPushNotification( + emails: emails, + mailboxIdsBlockNotification: mailboxIdsBlockNotification) + return self.showListNotification(emails: emailFiltered) + } } ) } diff --git a/ios/TwakeMailNSE/Utils/SentryManager.swift b/ios/TwakeMailNSE/Utils/SentryManager.swift index 35d44cf39..0cf55cefa 100644 --- a/ios/TwakeMailNSE/Utils/SentryManager.swift +++ b/ios/TwakeMailNSE/Utils/SentryManager.swift @@ -75,7 +75,20 @@ class SentryManager { } } - /// Set user context cho Sentry + /// Adds a breadcrumb to the current Sentry scope. + /// Breadcrumbs are accumulated in-memory and automatically attached to the next + /// captured error event. Use this for diagnostic checkpoints that provide context + /// for real errors — they are NOT sent as standalone events. + func addBreadcrumb(message: String, category: String = "nse", level: SentryLevel = .info) { + guard isInitialized else { return } + let crumb = Breadcrumb() + crumb.message = message + crumb.category = category + crumb.level = level + SentrySDK.addBreadcrumb(crumb) + } + + /// Set user context for Sentry func setSentryUser(_ user: User) { guard isInitialized else { return } SentrySDK.setUser(user)