TM-SGNL-iOS/SignalServiceKit/Messages/MessageSender+Errors.swift
TeleMessage developers dde0620daf initial commit
2025-05-03 12:28:28 -07:00

489 lines
16 KiB
Swift

//
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
public import LibSignalClient
public enum MessageSenderError: Error, IsRetryableProvider, UserErrorDescriptionProvider {
case prekeyRateLimit
case missingDevice
case blockedContactRecipient
case threadMissing
public var localizedDescription: String {
switch self {
case .blockedContactRecipient:
return OWSLocalizedString(
"ERROR_DESCRIPTION_MESSAGE_SEND_FAILED_DUE_TO_BLOCK_LIST",
comment: "Error message indicating that message send failed due to block list"
)
case .prekeyRateLimit, .missingDevice, .threadMissing:
return OWSLocalizedString(
"MESSAGE_STATUS_SEND_FAILED",
comment: "Label indicating that a message failed to send."
)
}
}
// MARK: - IsRetryableProvider
public var isRetryableProvider: Bool {
switch self {
case .prekeyRateLimit:
// TODO: Retry with backoff.
// TODO: Can we honor a retry delay hint from the response?
return true
case .missingDevice:
return true
case .blockedContactRecipient:
return false
case .threadMissing:
return false
}
}
}
// MARK: -
extension NSError {
@objc
public var shouldBeIgnoredForNonContactThreads: Bool {
(self as Error).shouldBeIgnoredForNonContactThreads
}
}
// MARK: -
extension Error {
public var shouldBeIgnoredForNonContactThreads: Bool {
self is MessageSenderNoSuchSignalRecipientError
}
}
// MARK: -
extension NSError {
@objc
public var isFatalError: Bool { isFatalErrorImpl }
fileprivate var isFatalErrorImpl: Bool {
let error: Error = self as Error
switch error {
case is MessageSenderNoSessionForTransientMessageError:
return true
case is UntrustedIdentityError:
return true
case is SignalServiceRateLimitedError:
// Avoid exacerbating the rate limiting.
return true
case is MessageDeletedBeforeSentError:
return true
default:
// Default to NOT fatal.
return false
}
}
}
// MARK: -
extension Error {
public var isFatalError: Bool { (self as NSError).isFatalErrorImpl }
}
// MARK: -
@objc
public class MessageSenderNoSuchSignalRecipientError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
public static let errorDomain = OWSError.errorDomain
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.noSuchSignalRecipient.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"ERROR_DESCRIPTION_UNREGISTERED_RECIPIENT",
comment: "Error message when attempting to send message"
)
}
@objc
public class func isNoSuchSignalRecipientError(_ error: Error?) -> Bool {
error is MessageSenderNoSuchSignalRecipientError
}
// MARK: - IsRetryableProvider
// No need to retry if the recipient is not registered.
public var isRetryableProvider: Bool { false }
}
// MARK: -
@objc
class MessageSenderErrorNoValidRecipients: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
@objc
public static var asNSError: NSError {
MessageSenderErrorNoValidRecipients() as Error as NSError
}
// NSError bridging: the domain of the error.
public static let errorDomain = OWSError.errorDomain
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.messageSendNoValidRecipients.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"ERROR_DESCRIPTION_NO_VALID_RECIPIENTS",
comment: "Error indicating that an outgoing message had no valid recipients."
)
}
public var isRetryableProvider: Bool { false }
}
// MARK: -
class MessageSenderNoSessionForTransientMessageError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
public static let errorDomain = OWSError.errorDomain
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.noSessionForTransientMessage.rawValue }
public var isRetryableProvider: Bool { false }
public var localizedDescription: String {
// These messages are never presented to the user, since these errors only
// occur to transient messages. We only specify an error to avoid an assert.
return OWSError.genericErrorDescription()
}
}
// MARK: -
public class UntrustedIdentityError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
public let serviceId: ServiceId
init(serviceId: ServiceId) {
self.serviceId = serviceId
}
// NSError bridging: the domain of the error.
public static let errorDomain = OWSError.errorDomain
public static var errorCode: Int { OWSErrorCode.untrustedIdentity.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
let format = OWSLocalizedString(
"FAILED_SENDING_BECAUSE_UNTRUSTED_IDENTITY_KEY",
comment: "action sheet header when re-sending message which failed because of untrusted identity keys"
)
return String(format: format, SSKEnvironment.shared.databaseStorageRef.read { tx in
return SSKEnvironment.shared.contactManagerRef.displayName(for: SignalServiceAddress(serviceId), tx: tx).resolvedValue()
})
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { Self.errorCode }
/// Key will continue to be unaccepted, so no need to retry. It'll only
/// cause us to hit the Pre-Key request rate limit.
public var isRetryableProvider: Bool { false }
}
public class InvalidKeySignatureError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
public let serviceId: ServiceId
public let isTerminalFailure: Bool
init(serviceId: ServiceId, isTerminalFailure: Bool) {
self.serviceId = serviceId
self.isTerminalFailure = isTerminalFailure
}
// NSError bridging: the domain of the error.
public static let errorDomain = OWSError.errorDomain
public static var errorCode: Int { OWSErrorCode.invalidKeySignature.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
let format = OWSLocalizedString(
"FAILED_SENDING_BECAUSE_INVALID_KEY_SIGNATURE",
comment: "action sheet header when re-sending message which failed because of an invalid key signature"
)
return String(format: format, SSKEnvironment.shared.databaseStorageRef.read { tx in
return SSKEnvironment.shared.contactManagerRef.displayName(for: SignalServiceAddress(serviceId), tx: tx).resolvedValue()
})
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { Self.errorCode }
/// Key will continue to be invalidly signed, so no need to retry. It'll only
/// cause us to hit the Pre-Key request rate limit.
public var isRetryableProvider: Bool {
!isTerminalFailure
}
}
// MARK: -
class SignalServiceRateLimitedError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
@objc
public static let errorDomain = OWSError.errorDomain
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"FAILED_SENDING_BECAUSE_RATE_LIMIT",
comment: "action sheet header when re-sending message which failed because of too many attempts"
)
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.signalServiceRateLimited.rawValue }
// We're already rate-limited. No need to exacerbate the problem.
public var isRetryableProvider: Bool { false }
}
// MARK: -
@objc
public class SpamChallengeRequiredError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
@objc
public static let errorDomain = OWSError.errorDomain
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"ERROR_DESCRIPTION_SUSPECTED_SPAM",
comment: "Description for errors returned from the server due to suspected spam."
)
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.serverRejectedSuspectedSpam.rawValue }
public var isRetryableProvider: Bool { false }
}
// MARK: -
@objc
class SpamChallengeResolvedError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
public static let errorDomain = OWSError.errorDomain
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"ERROR_DESCRIPTION_SUSPECTED_SPAM",
comment: "Description for errors returned from the server due to suspected spam."
)
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.serverRejectedSuspectedSpam.rawValue }
public var isRetryableProvider: Bool { true }
}
// MARK: -
@objc
class OWSRetryableMessageSenderError: NSObject, Error, IsRetryableProvider {
@objc
public static var asNSError: NSError {
OWSRetryableMessageSenderError() as Error as NSError
}
// MARK: - IsRetryableProvider
public var isRetryableProvider: Bool { true }
}
// MARK: -
// NOTE: We typically prefer to use a more specific error.
@objc
class OWSUnretryableMessageSenderError: NSObject, Error, IsRetryableProvider {
@objc
public static var asNSError: NSError {
OWSUnretryableMessageSenderError() as Error as NSError
}
// MARK: - IsRetryableProvider
public var isRetryableProvider: Bool { false }
}
// MARK: -
@objc
public class AppExpiredError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
@objc
public static var asNSError: NSError {
AppExpiredError() as Error as NSError
}
// NSError bridging: the domain of the error.
@objc
public static let errorDomain = OWSError.errorDomain
// NSError bridging: the error code within the given domain.
@objc
public static var errorCode: Int { OWSErrorCode.appExpired.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString("ERROR_SENDING_EXPIRED",
comment: "Error indicating a send failure due to an expired application.")
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { Self.errorCode }
public var isRetryableProvider: Bool { false }
}
// MARK: -
@objc
public class AppDeregisteredError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
@objc
public static var asNSError: NSError {
AppDeregisteredError() as Error as NSError
}
// NSError bridging: the domain of the error.
@objc
public static let errorDomain = OWSError.errorDomain
// NSError bridging: the error code within the given domain.
@objc
public static var errorCode: Int { OWSErrorCode.appDeregistered.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isPrimaryDevice ?? true
? OWSLocalizedString("ERROR_SENDING_DEREGISTERED",
comment: "Error indicating a send failure due to a deregistered application.")
: OWSLocalizedString("ERROR_SENDING_DELINKED",
comment: "Error indicating a send failure due to a delinked application.")
}
// NSError bridging: the error code within the given domain.
public var errorCode: Int { Self.errorCode }
public var isRetryableProvider: Bool { false }
}
// MARK: -
@objc
class MessageDeletedBeforeSentError: NSObject, CustomNSError, IsRetryableProvider {
@objc
public static var asNSError: NSError {
MessageDeletedBeforeSentError() as Error as NSError
}
// NSError bridging: the domain of the error.
public static let errorDomain = OWSError.errorDomain
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.messageDeletedBeforeSent.rawValue }
public var isRetryableProvider: Bool { false }
}
// MARK: -
class MessageSendUnauthorizedError: NSObject, CustomNSError, IsRetryableProvider, UserErrorDescriptionProvider {
// NSError bridging: the domain of the error.
public static let errorDomain = OWSError.errorDomain
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.messageSendUnauthorized.rawValue }
// NSError bridging: the error code within the given domain.
public var errorUserInfo: [String: Any] {
[NSLocalizedDescriptionKey: self.localizedDescription]
}
public var localizedDescription: String {
OWSLocalizedString(
"ERROR_DESCRIPTION_SENDING_UNAUTHORIZED",
comment: "Error message when attempting to send message"
)
}
// No need to retry if we've been de-authed.
public var isRetryableProvider: Bool { false }
}
// MARK: -
class MessageSendEncryptionError: NSObject, CustomNSError, IsRetryableProvider {
public let serviceId: ServiceId
public let deviceId: UInt32
init(serviceId: ServiceId, deviceId: UInt32) {
self.serviceId = serviceId
self.deviceId = deviceId
}
// NSError bridging: the domain of the error.
public static let errorDomain = OWSError.errorDomain
// NSError bridging: the error code within the given domain.
public var errorCode: Int { OWSErrorCode.messageSendEncryptionFailure.rawValue }
public var isRetryableProvider: Bool { true }
}