diff --git a/MailCore/API/MailApiError.swift b/MailCore/API/MailApiError.swift index 5fbef73b3..51ea06614 100644 --- a/MailCore/API/MailApiError.swift +++ b/MailCore/API/MailApiError.swift @@ -19,13 +19,30 @@ import Foundation import MailResources +/// Static definitions of Mail API error codes +enum MailApiErrorCode { + /// The email is not found + static let mailMessageNotFound = "mail__message_not_found" + + /// Invalid credentials + static let invalidCredentials = "invalid_credentials" + + /// The server does not know about the identity used in the request + static let identityNotFound = "identity__not_found" +} + public class MailApiError: MailError { - public static let apiMessageNotFound = MailApiError(code: "mail__message_not_found", + /// The email is not found + public static let apiMessageNotFound = MailApiError(code: MailApiErrorCode.mailMessageNotFound, localizedDescription: MailResourcesStrings.Localizable .errorMessageNotFound, shouldDisplay: true) - public static let apiInvalidCredential = MailApiError(code: "invalid_credentials") + /// Invalid credentials + public static let apiInvalidCredential = MailApiError(code: MailApiErrorCode.invalidCredentials) + + /// The server does not know bout the identity used in the request + public static let apiIdentityNotFound = MailApiError(code: MailApiErrorCode.identityNotFound, shouldDisplay: false) static let allErrors: [MailApiError] = [ // General @@ -112,7 +129,10 @@ public class MailApiError: MailError { MailApiError(code: "attachment__store_to_drive_fail"), // Message - MailApiError(code: "message__uid_is_not_valid") + MailApiError(code: "message__uid_is_not_valid"), + + // Signatures / Identity + apiIdentityNotFound ] static func mailApiErrorFromCode(_ code: String) -> MailApiError? { diff --git a/MailCore/Cache/DraftManager.swift b/MailCore/Cache/DraftManager.swift index 17e2c1fdf..43e2d2436 100644 --- a/MailCore/Cache/DraftManager.swift +++ b/MailCore/Cache/DraftManager.swift @@ -78,7 +78,7 @@ public final class DraftManager { } /// Save a draft server side - private func saveDraftRemotely(draft: Draft, mailboxManager: MailboxManager) async { + private func saveDraftRemotely(draft: Draft, mailboxManager: MailboxManager, retry: Bool = true) async { guard draft.identityId != nil else { SentrySDK.capture(message: "We are trying to send a draft without an identityId, this will fail.") return @@ -92,13 +92,47 @@ public final class DraftManager { do { try await mailboxManager.save(draft: draft) } catch { - guard error.shouldDisplay else { return } - alertDisplayable.show(message: error.localizedDescription) + // Retry with default signature on missing identity + if retry, + let mailError = error as? MailApiError, + mailError == MailApiError.apiIdentityNotFound { + guard let updatedDraft = await setDefaultSignature(draft: draft, mailboxManager: mailboxManager) else { + return + } + await saveDraftRemotely(draft: updatedDraft, mailboxManager: mailboxManager, retry: false) + } + // show error if needed + else { + guard error.shouldDisplay else { return } + alertDisplayable.show(message: error.localizedDescription) + } } await draftQueue.endBackgroundTask(uuid: draft.localUUID) } - public func send(draft: Draft, mailboxManager: MailboxManager) async -> Date? { + private func setDefaultSignature(draft: Draft, mailboxManager: MailboxManager) async -> Draft? { + try? await mailboxManager.refreshAllSignatures() + let storedSignatures = mailboxManager.getStoredSignatures() + guard let defaultSignature = storedSignatures.defaultSignature else { + return nil + } + + var updatedDraft: Draft? + let realm = mailboxManager.getRealm() + try? realm.write { + guard let liveDraft = realm.object(ofType: Draft.self, forPrimaryKey: draft.localUUID) else { + return + } + liveDraft.identityId = "\(defaultSignature.id)" + + realm.add(liveDraft, update: .modified) + + updatedDraft = liveDraft.detached() + } + return updatedDraft + } + + public func send(draft: Draft, mailboxManager: MailboxManager, retry: Bool = true) async -> Date? { alertDisplayable.show(message: MailResourcesStrings.Localizable.snackbarEmailSending) var sendDate: Date? @@ -110,6 +144,16 @@ public final class DraftManager { alertDisplayable.show(message: MailResourcesStrings.Localizable.snackbarEmailSent) sendDate = cancelableResponse.scheduledDate } catch { + // Retry with default signature on missing identity + if retry, + let mailError = error as? MailApiError, + mailError == MailApiError.apiIdentityNotFound { + guard let updatedDraft = await setDefaultSignature(draft: draft, mailboxManager: mailboxManager) else { + return nil + } + return await send(draft: updatedDraft, mailboxManager: mailboxManager, retry: false) + } + alertDisplayable.show(message: error.localizedDescription) } await draftQueue.endBackgroundTask(uuid: draft.localUUID) diff --git a/MailCore/Cache/MailboxManager/MailboxManager+Draft.swift b/MailCore/Cache/MailboxManager/MailboxManager+Draft.swift index 11c15393e..90202abfa 100644 --- a/MailCore/Cache/MailboxManager/MailboxManager+Draft.swift +++ b/MailCore/Cache/MailboxManager/MailboxManager+Draft.swift @@ -53,6 +53,11 @@ public extension MailboxManager { try await deleteLocally(draft: draft) throw error } catch let error as MailApiError { + // Do not delete draft on invalid identity + guard error != MailApiError.apiIdentityNotFound else { + throw error + } + // The api returned an error try await deleteLocally(draft: draft) throw error @@ -72,6 +77,11 @@ public extension MailboxManager { } } } catch let error as MailApiError { + // Do not delete draft on invalid identity + guard error != MailApiError.apiIdentityNotFound else { + throw error + } + // The api returned an error for now we can do nothing about it so we delete the draft try await deleteLocally(draft: draft) throw error