diff options
| -rw-r--r-- | ios/MullvadREST/RESTError.swift | 16 | ||||
| -rw-r--r-- | ios/MullvadTypes/DisplayError.swift | 21 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 30 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountInteractor.swift | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountViewController.swift | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/DisplayChainedError.swift | 199 | ||||
| -rw-r--r-- | ios/MullvadVPN/LoginViewController.swift | 3 | ||||
| -rw-r--r-- | ios/MullvadVPN/OutOfTimeInteractor.swift | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN/OutOfTimeViewController.swift | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/ProblemReportSubmissionOverlayView.swift | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN/RESTError+Display.swift | 66 | ||||
| -rw-r--r-- | ios/MullvadVPN/SKError+Localized.swift | 53 | ||||
| -rw-r--r-- | ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN/StorePaymentManager/StorePaymentManagerError.swift | 6 | ||||
| -rw-r--r-- | ios/MullvadVPN/StorePaymentManagerError+Display.swift | 86 | ||||
| -rw-r--r-- | ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift | 2 |
16 files changed, 265 insertions, 233 deletions
diff --git a/ios/MullvadREST/RESTError.swift b/ios/MullvadREST/RESTError.swift index c802ae6896..5131c40d76 100644 --- a/ios/MullvadREST/RESTError.swift +++ b/ios/MullvadREST/RESTError.swift @@ -29,11 +29,11 @@ extension REST { public var errorDescription: String? { switch self { - case let .createURLRequest(error): - return "Failure to create URL request: \(error.localizedDescription)." + case .createURLRequest: + return "Failure to create URL request." - case let .network(error): - return "Network error: \(error.localizedDescription)." + case .network: + return "Network error." case let .unhandledResponse(statusCode, serverResponse): var str = "Failure to handle server response: HTTP/\(statusCode)." @@ -48,11 +48,11 @@ extension REST { return str - case let .decodeResponse(error): - return "Failure to decode URL response data: \(error.localizedDescription)." + case .decodeResponse: + return "Failure to decode response." - case let .transport(error): - return "Transport error: \(error.localizedDescription)." + case .transport: + return "Transport error." } } diff --git a/ios/MullvadTypes/DisplayError.swift b/ios/MullvadTypes/DisplayError.swift new file mode 100644 index 0000000000..7a9902a871 --- /dev/null +++ b/ios/MullvadTypes/DisplayError.swift @@ -0,0 +1,21 @@ +// +// DisplayError.swift +// MullvadTypes +// +// Created by pronebird on 17/01/2023. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +/// A protocol that adds a formal interface for all errors displayed in user interface. +/// +/// This protocol is meant to be used in place of `LocalizedError` when producing a user friendly +/// error message that requires a deeper look at the underlying cause. +/// +/// Note that `Logger.error(error: Error)` picks up `errorDescription`s when unrolling +/// the underlying error chain, hence it's better to keep error descriptions relatively concise, +/// explaining what happened but without telling why that happened. +public protocol DisplayError { + var displayErrorDescription: String? { get } +} diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 6224d7a8ac..5e532b9083 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -117,6 +117,7 @@ 585CA70F25F8C44600B47C62 /* UIMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585CA70E25F8C44600B47C62 /* UIMetrics.swift */; }; 585E820327F3285E00939F0E /* SendStoreReceiptOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */; }; 58607A4D2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */; }; + 586168692976F6BD00EF8598 /* DisplayError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 586168682976F6BD00EF8598 /* DisplayError.swift */; }; 5862805422428EF100F5A6E1 /* TranslucentButtonBlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */; }; 5867770E29096984006F721F /* OutOfTimeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867770D29096984006F721F /* OutOfTimeInteractor.swift */; }; 58677710290975E9006F721F /* SettingsInteractorFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5867770F290975E8006F721F /* SettingsInteractorFactory.swift */; }; @@ -201,6 +202,8 @@ 589A455F28E094BF00565204 /* OperationConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 580CBFB72848D503007878F0 /* OperationConditionTests.swift */; }; 58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */; }; 58A3BDB028A1821A00C8C2C6 /* WgStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A3BDAF28A1821A00C8C2C6 /* WgStats.swift */; }; + 58A8EE5A2976BFBB009C0F8D /* SKError+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A8EE592976BFBB009C0F8D /* SKError+Localized.swift */; }; + 58A8EE5E2976DB00009C0F8D /* StorePaymentManagerError+Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A8EE5D2976DB00009C0F8D /* StorePaymentManagerError+Display.swift */; }; 58A99ED3240014A0006599E9 /* TermsOfServiceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A99ED2240014A0006599E9 /* TermsOfServiceViewController.swift */; }; 58ACF6492655365700ACE4B7 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF6482655365700ACE4B7 /* PreferencesViewController.swift */; }; 58ACF64B26553C3F00ACE4B7 /* SettingsSwitchCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ACF64A26553C3F00ACE4B7 /* SettingsSwitchCell.swift */; }; @@ -217,7 +220,7 @@ 58B43C1925F77DB60002C8C3 /* TunnelControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B43C1825F77DB60002C8C3 /* TunnelControlView.swift */; }; 58B93A1326C3F13600A55733 /* TunnelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B93A1226C3F13600A55733 /* TunnelState.swift */; }; 58B993B12608A34500BA7811 /* LoginContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B993B02608A34500BA7811 /* LoginContentView.swift */; }; - 58B9EB152489139B00095626 /* DisplayChainedError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B9EB142489139B00095626 /* DisplayChainedError.swift */; }; + 58B9EB152489139B00095626 /* RESTError+Display.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B9EB142489139B00095626 /* RESTError+Display.swift */; }; 58BA693123EADA6A009DC256 /* SimulatorTunnelProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BA693023EADA6A009DC256 /* SimulatorTunnelProvider.swift */; }; 58BFA5C622A7C97F00A6173D /* RelayCacheTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5C522A7C97F00A6173D /* RelayCacheTracker.swift */; }; 58BFA5CC22A7CE1F00A6173D /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; }; @@ -716,6 +719,7 @@ 585DA89826B0329200B8C587 /* PacketTunnelStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelStatus.swift; sourceTree = "<group>"; }; 585E820227F3285E00939F0E /* SendStoreReceiptOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendStoreReceiptOperation.swift; sourceTree = "<group>"; }; 58607A4C2947287800BC467D /* AccountExpiryInAppNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountExpiryInAppNotificationProvider.swift; sourceTree = "<group>"; }; + 586168682976F6BD00EF8598 /* DisplayError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayError.swift; sourceTree = "<group>"; }; 5862805322428EF100F5A6E1 /* TranslucentButtonBlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslucentButtonBlurView.swift; sourceTree = "<group>"; }; 5866F39B2243B82D00168AE5 /* MullvadVPN.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MullvadVPN.entitlements; sourceTree = "<group>"; }; 5867770D29096984006F721F /* OutOfTimeInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutOfTimeInteractor.swift; sourceTree = "<group>"; }; @@ -797,6 +801,8 @@ 58A1AA8623F43901009F7EA6 /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = "<group>"; }; 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionPanelView.swift; sourceTree = "<group>"; }; 58A3BDAF28A1821A00C8C2C6 /* WgStats.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WgStats.swift; sourceTree = "<group>"; }; + 58A8EE592976BFBB009C0F8D /* SKError+Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKError+Localized.swift"; sourceTree = "<group>"; }; + 58A8EE5D2976DB00009C0F8D /* StorePaymentManagerError+Display.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StorePaymentManagerError+Display.swift"; sourceTree = "<group>"; }; 58A94AE326CFD945001CB97C /* TunnelStatusNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelStatusNotificationProvider.swift; sourceTree = "<group>"; }; 58A99ED2240014A0006599E9 /* TermsOfServiceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TermsOfServiceViewController.swift; sourceTree = "<group>"; }; 58ACF6482655365700ACE4B7 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = "<group>"; }; @@ -817,7 +823,7 @@ 58B93A1226C3F13600A55733 /* TunnelState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelState.swift; sourceTree = "<group>"; }; 58B993B02608A34500BA7811 /* LoginContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginContentView.swift; sourceTree = "<group>"; }; 58B9EB122488ED2100095626 /* AlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertPresenter.swift; sourceTree = "<group>"; }; - 58B9EB142489139B00095626 /* DisplayChainedError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayChainedError.swift; sourceTree = "<group>"; }; + 58B9EB142489139B00095626 /* RESTError+Display.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RESTError+Display.swift"; sourceTree = "<group>"; }; 58BA693023EADA6A009DC256 /* SimulatorTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelProvider.swift; sourceTree = "<group>"; }; 58BFA5C522A7C97F00A6173D /* RelayCacheTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayCacheTracker.swift; sourceTree = "<group>"; }; 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationConfiguration.swift; sourceTree = "<group>"; }; @@ -1127,6 +1133,7 @@ 58E511E028DDB7F100B0BCDE /* WrappingError.swift */, 58E511E328DDDE8900B0BCDE /* CustomErrorDescriptionProtocol.swift */, 58E511EA28DDE18400B0BCDE /* Error+Chain.swift */, + 586168682976F6BD00EF8598 /* DisplayError.swift */, 58AEEF642344A36000C9BBD5 /* KeychainError.swift */, 58A1AA8623F43901009F7EA6 /* Location.swift */, 5840250322B11AB700E4CFEC /* MullvadEndpoint.swift */, @@ -1137,6 +1144,7 @@ 5898D2B62902A9EA00EB5EBA /* PacketTunnelRelay.swift */, 58900D0228BBDCC70094E4F0 /* FixedWidthInteger+Arithmetics.swift */, 06410E172934F43B00AFC18C /* PacketTunnelErrorWrapper.swift */, + 58D223D7294C8E5E0029F5F8 /* MullvadTypes.h */, ); path = MullvadTypes; sourceTree = "<group>"; @@ -1336,7 +1344,6 @@ 5898D28A29017BD400EB5EBA /* TunnelProviderMessaging */, 589A455328E094B300565204 /* OperationsTests */, 58CE5E7A224146470008646E /* PacketTunnel */, - 58D223D6294C8E5E0029F5F8 /* MullvadTypes */, 58CE5E61224146200008646E /* Products */, 584F991F2902CBDD001F858D /* Frameworks */, ); @@ -1405,7 +1412,8 @@ 5893716928817A45004EE76C /* DeviceManagementViewController.swift */, 5820EDAA288FF0D2006BF4E4 /* DeviceRowView.swift */, 58907D9424D17B4E00CFC3F5 /* DisconnectSplitButton.swift */, - 58B9EB142489139B00095626 /* DisplayChainedError.swift */, + 58B9EB142489139B00095626 /* RESTError+Display.swift */, + 58A8EE5D2976DB00009C0F8D /* StorePaymentManagerError+Display.swift */, 580F8B8528197958002E0998 /* DNSSettings.swift */, 5892A45D265FABFF00890742 /* EmptyTableViewHeaderFooterView.swift */, 58FEEB45260A028D00A621A8 /* GeoJSON.swift */, @@ -1484,6 +1492,7 @@ 585CA70E25F8C44600B47C62 /* UIMetrics.swift */, 58F7CA872692E34000FC59FD /* WireguardKeysContentView.swift */, 58E11187292FA11F009FCA84 /* SettingsMigrationUIHandler.swift */, + 58A8EE592976BFBB009C0F8D /* SKError+Localized.swift */, ); path = MullvadVPN; sourceTree = "<group>"; @@ -1541,14 +1550,6 @@ path = Operations; sourceTree = "<group>"; }; - 58D223D6294C8E5E0029F5F8 /* MullvadTypes */ = { - isa = PBXGroup; - children = ( - 58D223D7294C8E5E0029F5F8 /* MullvadTypes.h */, - ); - path = MullvadTypes; - sourceTree = "<group>"; - }; 58D223F4294C8FF00029F5F8 /* MullvadLogging */ = { isa = PBXGroup; children = ( @@ -2412,7 +2413,7 @@ 58FD5BF42428C67600112C88 /* InAppPurchaseButton.swift in Sources */, 587D9676288989DB00CD8F1C /* NSLayoutConstraint+Helpers.swift in Sources */, 58293FAE2510CA58005D0BB5 /* ProblemReportViewController.swift in Sources */, - 58B9EB152489139B00095626 /* DisplayChainedError.swift in Sources */, + 58B9EB152489139B00095626 /* RESTError+Display.swift in Sources */, 587B753F2668E5A700DEF7E9 /* NotificationContainerView.swift in Sources */, 58421034282E4B1500F24E46 /* TunnelSettingsV2+REST.swift in Sources */, 58F2E144276A13F300A79513 /* StartTunnelOperation.swift in Sources */, @@ -2436,7 +2437,9 @@ 58C3A4B222456F1B00340BDB /* AccountInputGroupView.swift in Sources */, 58ACF64B26553C3F00ACE4B7 /* SettingsSwitchCell.swift in Sources */, 587EB67027143B6500123C75 /* DataSourceSnapshot.swift in Sources */, + 58A8EE5E2976DB00009C0F8D /* StorePaymentManagerError+Display.swift in Sources */, 580F8B8328197881002E0998 /* TunnelSettingsV2.swift in Sources */, + 58A8EE5A2976BFBB009C0F8D /* SKError+Localized.swift in Sources */, 5803B4B22940A48700C23744 /* TunnelStore.swift in Sources */, 586A950F29012BEE007BAF2B /* AddressCacheTracker.swift in Sources */, 587B753D2666468F00DEF7E9 /* NotificationController.swift in Sources */, @@ -2520,6 +2523,7 @@ 58D2240C294C90210029F5F8 /* WrappingError.swift in Sources */, 58D2240D294C90210029F5F8 /* CustomErrorDescriptionProtocol.swift in Sources */, 58D2240E294C90210029F5F8 /* Error+Chain.swift in Sources */, + 586168692976F6BD00EF8598 /* DisplayError.swift in Sources */, 58D2240F294C90210029F5F8 /* KeychainError.swift in Sources */, 58D22410294C90210029F5F8 /* Location.swift in Sources */, 58D22411294C90210029F5F8 /* MullvadEndpoint.swift in Sources */, diff --git a/ios/MullvadVPN/AccountInteractor.swift b/ios/MullvadVPN/AccountInteractor.swift index 298f73f0b5..8444a426db 100644 --- a/ios/MullvadVPN/AccountInteractor.swift +++ b/ios/MullvadVPN/AccountInteractor.swift @@ -69,7 +69,7 @@ final class AccountInteractor { func requestProducts( with productIdentifiers: Set<StoreSubscription>, - completionHandler: @escaping (OperationCompletion<SKProductsResponse, Swift.Error>) -> Void + completionHandler: @escaping (OperationCompletion<SKProductsResponse, Error>) -> Void ) -> Cancellable { return storePaymentManager.requestProducts( with: productIdentifiers, diff --git a/ios/MullvadVPN/AccountViewController.swift b/ios/MullvadVPN/AccountViewController.swift index 9a583fee16..1d4b82b56e 100644 --- a/ios/MullvadVPN/AccountViewController.swift +++ b/ios/MullvadVPN/AccountViewController.swift @@ -203,7 +203,7 @@ class AccountViewController: UIViewController { value: "Cannot complete the purchase", comment: "" ), - message: error.errorChainDescription, + message: error.displayErrorDescription, preferredStyle: .alert ) @@ -229,7 +229,7 @@ class AccountViewController: UIViewController { value: "Cannot restore purchases", comment: "" ), - message: error.errorChainDescription, + message: error.displayErrorDescription, preferredStyle: .alert ) alertController.addAction( diff --git a/ios/MullvadVPN/DisplayChainedError.swift b/ios/MullvadVPN/DisplayChainedError.swift deleted file mode 100644 index c1ef9b463e..0000000000 --- a/ios/MullvadVPN/DisplayChainedError.swift +++ /dev/null @@ -1,199 +0,0 @@ -// -// DisplayChainedError.swift -// MullvadVPN -// -// Created by pronebird on 04/06/2020. -// Copyright © 2020 Mullvad VPN AB. All rights reserved. -// - -import Foundation -import MullvadREST -import StoreKit - -protocol DisplayChainedError { - var errorChainDescription: String? { get } -} - -extension REST.Error: DisplayChainedError { - var errorChainDescription: String? { - switch self { - case let .network(urlError): - return String( - format: NSLocalizedString( - "NETWORK_ERROR", - tableName: "REST", - value: "Network error: %@", - comment: "" - ), - urlError.localizedDescription - ) - case let .unhandledResponse(statusCode, serverResponse): - return String( - format: NSLocalizedString( - "SERVER_ERROR", - tableName: "REST", - value: "Unexpected server response: %1$@ (HTTP status: %2$d)", - comment: "" - ), - serverResponse?.code.rawValue ?? "(no code)", - statusCode - ) - case .createURLRequest: - return NSLocalizedString( - "SERVER_REQUEST_ENCODING_ERROR", - tableName: "REST", - value: "Failure to create URL request", - comment: "" - ) - case .decodeResponse: - return NSLocalizedString( - "SERVER_SUCCESS_RESPONSE_DECODING_ERROR", - tableName: "REST", - value: "Server response decoding error", - comment: "" - ) - case let .transport(error): - return NSLocalizedString( - "TRANSPORT_ERROR", - tableName: "REST", - value: "Transport error: \(error.localizedDescription)", - comment: "" - ) - } - } -} - -extension SKError: LocalizedError { - public var errorDescription: String? { - switch code { - case .unknown: - return NSLocalizedString( - "UNKNOWN_ERROR", - tableName: "StoreKitErrors", - value: "Unknown error.", - comment: "" - ) - case .clientInvalid: - return NSLocalizedString( - "CLIENT_INVALID", - tableName: "StoreKitErrors", - value: "Client is not allowed to issue the request.", - comment: "" - ) - case .paymentCancelled: - return NSLocalizedString( - "PAYMENT_CANCELLED", - tableName: "StoreKitErrors", - value: "User cancelled the request.", - comment: "" - ) - case .paymentInvalid: - return NSLocalizedString( - "PAYMENT_INVALID", - tableName: "StoreKitErrors", - value: "Invalid purchase identifier.", - comment: "" - ) - case .paymentNotAllowed: - return NSLocalizedString( - "PAYMENT_NOT_ALLOWED", - tableName: "StoreKitErrors", - value: "This device is not allowed to make the payment.", - comment: "" - ) - default: - return localizedDescription - } - } -} - -extension StorePaymentManagerError: DisplayChainedError { - var errorChainDescription: String? { - switch self { - case .noAccountSet: - return NSLocalizedString( - "NO_ACCOUNT_SET_ERROR", - tableName: "StorePaymentManager", - value: "Internal error: account is not set.", - comment: "" - ) - - case let .validateAccount(restError): - let reason = restError.errorChainDescription ?? "" - - if restError.compareErrorCode(.invalidAccount) { - return String( - format: NSLocalizedString( - "INVALID_ACCOUNT_ERROR", - tableName: "StorePaymentManager", - value: "Cannot add credit to invalid account.", - comment: "" - ), reason - ) - } else { - let reason = restError.errorChainDescription ?? "" - - return String( - format: NSLocalizedString( - "VALIDATE_ACCOUNT_ERROR", - tableName: "StorePaymentManager", - value: "Failed to validate account token: %@", - comment: "" - ), reason - ) - } - - case let .readReceipt(readReceiptError): - if readReceiptError is StoreReceiptNotFound { - return NSLocalizedString( - "RECEIPT_NOT_FOUND_ERROR", - tableName: "StorePaymentManager", - value: "AppStore receipt is not found on disk.", - comment: "" - ) - } else if let storeError = readReceiptError as? SKError { - return String( - format: NSLocalizedString( - "REFRESH_RECEIPT_ERROR", - tableName: "StorePaymentManager", - value: "Cannot refresh the AppStore receipt: %@", - comment: "" - ), - storeError.localizedDescription - ) - } else { - return String( - format: NSLocalizedString( - "READ_RECEIPT_ERROR", - tableName: "StorePaymentManager", - value: "Cannot read the AppStore receipt from disk: %@", - comment: "" - ), - readReceiptError.localizedDescription - ) - } - - case let .sendReceipt(restError): - let reason = restError.errorChainDescription ?? "" - let errorFormat = NSLocalizedString( - "SEND_RECEIPT_ERROR", - tableName: "StorePaymentManager", - value: "Failed to send the receipt to server: %@", - comment: "" - ) - let recoverySuggestion = NSLocalizedString( - "SEND_RECEIPT_RECOVERY_SUGGESTION", - tableName: "StorePaymentManager", - value: "Please retry by using the \"Restore purchases\" button.", - comment: "" - ) - var errorString = String(format: errorFormat, reason) - errorString.append("\n\n") - errorString.append(recoverySuggestion) - return errorString - - case let .storePayment(storeError): - return (storeError as? SKError)?.errorDescription ?? storeError.localizedDescription - } - } -} diff --git a/ios/MullvadVPN/LoginViewController.swift b/ios/MullvadVPN/LoginViewController.swift index 1e64fd1663..a0c13358a3 100644 --- a/ios/MullvadVPN/LoginViewController.swift +++ b/ios/MullvadVPN/LoginViewController.swift @@ -7,6 +7,7 @@ // import MullvadLogging +import MullvadTypes import Operations import UIKit @@ -392,7 +393,7 @@ private extension LoginState { } case let .failure(error): - return error.localizedDescription + return (error as? DisplayError)?.displayErrorDescription ?? error.localizedDescription case let .success(method): switch method { diff --git a/ios/MullvadVPN/OutOfTimeInteractor.swift b/ios/MullvadVPN/OutOfTimeInteractor.swift index 068a299c3e..cd4989461a 100644 --- a/ios/MullvadVPN/OutOfTimeInteractor.swift +++ b/ios/MullvadVPN/OutOfTimeInteractor.swift @@ -74,7 +74,7 @@ final class OutOfTimeInteractor { func requestProducts( with productIdentifiers: Set<StoreSubscription>, - completionHandler: @escaping (OperationCompletion<SKProductsResponse, Swift.Error>) -> Void + completionHandler: @escaping (OperationCompletion<SKProductsResponse, Error>) -> Void ) -> Cancellable { return storePaymentManager.requestProducts( with: productIdentifiers, diff --git a/ios/MullvadVPN/OutOfTimeViewController.swift b/ios/MullvadVPN/OutOfTimeViewController.swift index 65ea02069c..ac8c021bd3 100644 --- a/ios/MullvadVPN/OutOfTimeViewController.swift +++ b/ios/MullvadVPN/OutOfTimeViewController.swift @@ -210,7 +210,7 @@ class OutOfTimeViewController: UIViewController, RootContainment { value: "Cannot complete the purchase", comment: "" ), - message: error.errorChainDescription, + message: error.displayErrorDescription, preferredStyle: .alert ) @@ -236,7 +236,7 @@ class OutOfTimeViewController: UIViewController, RootContainment { value: "Cannot restore purchases", comment: "" ), - message: error.errorChainDescription, + message: error.displayErrorDescription, preferredStyle: .alert ) diff --git a/ios/MullvadVPN/ProblemReportSubmissionOverlayView.swift b/ios/MullvadVPN/ProblemReportSubmissionOverlayView.swift index 89919b8dc1..160128dce9 100644 --- a/ios/MullvadVPN/ProblemReportSubmissionOverlayView.swift +++ b/ios/MullvadVPN/ProblemReportSubmissionOverlayView.swift @@ -96,7 +96,7 @@ class ProblemReportSubmissionOverlayView: UIView { return combinedAttributedString case let .failure(error): - return error.errorChainDescription.flatMap { NSAttributedString(string: $0) } + return error.displayErrorDescription.flatMap { NSAttributedString(string: $0) } } } } diff --git a/ios/MullvadVPN/RESTError+Display.swift b/ios/MullvadVPN/RESTError+Display.swift new file mode 100644 index 0000000000..8681dd9aa8 --- /dev/null +++ b/ios/MullvadVPN/RESTError+Display.swift @@ -0,0 +1,66 @@ +// +// RESTError+Display.swift +// MullvadVPN +// +// Created by pronebird on 04/06/2020. +// Copyright © 2020 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadREST +import MullvadTypes + +extension REST.Error: DisplayError { + public var displayErrorDescription: String? { + switch self { + case let .network(urlError): + return String( + format: NSLocalizedString( + "NETWORK_ERROR", + tableName: "REST", + value: "Network error: %@", + comment: "" + ), + urlError.localizedDescription + ) + + case let .unhandledResponse(statusCode, serverResponse): + guard let serverResponse = serverResponse else { + return String(format: NSLocalizedString( + "INVALID_ACCOUNT_ERROR", + tableName: "REST", + value: "Unexpected server response: %@", + comment: "" + ), statusCode) + } + + if serverResponse.code == .invalidAccount { + return NSLocalizedString( + "INVALID_ACCOUNT_ERROR", + tableName: "REST", + value: "Invalid account", + comment: "" + ) + } else { + return String( + format: NSLocalizedString( + "SERVER_ERROR", + tableName: "REST", + value: "Unexpected server response: %1$@ (HTTP status: %2$d)", + comment: "" + ), + serverResponse.code.rawValue, + statusCode + ) + } + + default: + return NSLocalizedString( + "INTERNAL_ERROR", + tableName: "REST", + value: "Internal error.", + comment: "" + ) + } + } +} diff --git a/ios/MullvadVPN/SKError+Localized.swift b/ios/MullvadVPN/SKError+Localized.swift new file mode 100644 index 0000000000..6367877fc4 --- /dev/null +++ b/ios/MullvadVPN/SKError+Localized.swift @@ -0,0 +1,53 @@ +// +// SKError+Localized.swift +// MullvadVPN +// +// Created by pronebird on 17/01/2023. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import StoreKit + +extension SKError: LocalizedError { + public var errorDescription: String? { + switch code { + case .unknown: + return NSLocalizedString( + "UNKNOWN_ERROR", + tableName: "StoreKitErrors", + value: "Unknown error.", + comment: "" + ) + case .clientInvalid: + return NSLocalizedString( + "CLIENT_INVALID", + tableName: "StoreKitErrors", + value: "Client is not allowed to issue the request.", + comment: "" + ) + case .paymentCancelled: + return NSLocalizedString( + "PAYMENT_CANCELLED", + tableName: "StoreKitErrors", + value: "User cancelled the request.", + comment: "" + ) + case .paymentInvalid: + return NSLocalizedString( + "PAYMENT_INVALID", + tableName: "StoreKitErrors", + value: "Invalid purchase identifier.", + comment: "" + ) + case .paymentNotAllowed: + return NSLocalizedString( + "PAYMENT_NOT_ALLOWED", + tableName: "StoreKitErrors", + value: "This device is not allowed to make the payment.", + comment: "" + ) + default: + return localizedDescription + } + } +} diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift index d9521bfda6..5aa5db8316 100644 --- a/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift +++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift @@ -110,7 +110,7 @@ final class StorePaymentManager: NSObject, SKPaymentTransactionObserver { func requestProducts( with productIdentifiers: Set<StoreSubscription>, - completionHandler: @escaping (OperationCompletion<SKProductsResponse, Swift.Error>) -> Void + completionHandler: @escaping (OperationCompletion<SKProductsResponse, Error>) -> Void ) -> Cancellable { let productIdentifiers = productIdentifiers.productIdentifiersSet let operation = ProductsRequestOperation( diff --git a/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerError.swift b/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerError.swift index 83a5508e77..660d181df4 100644 --- a/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerError.swift +++ b/ios/MullvadVPN/StorePaymentManager/StorePaymentManagerError.swift @@ -19,10 +19,10 @@ enum StorePaymentManagerError: LocalizedError, WrappingError { case validateAccount(REST.Error) /// Failure to handle payment transaction. Contains error returned by StoreKit. - case storePayment(Swift.Error) + case storePayment(Error) /// Failure to read the AppStore receipt. - case readReceipt(Swift.Error) + case readReceipt(Error) /// Failure to send the AppStore receipt to backend. case sendReceipt(REST.Error) @@ -42,7 +42,7 @@ enum StorePaymentManagerError: LocalizedError, WrappingError { } } - var underlyingError: Swift.Error? { + var underlyingError: Error? { switch self { case .noAccountSet: return nil diff --git a/ios/MullvadVPN/StorePaymentManagerError+Display.swift b/ios/MullvadVPN/StorePaymentManagerError+Display.swift new file mode 100644 index 0000000000..3015c69081 --- /dev/null +++ b/ios/MullvadVPN/StorePaymentManagerError+Display.swift @@ -0,0 +1,86 @@ +// +// StorePaymentManagerError+Display.swift +// MullvadVPN +// +// Created by pronebird on 17/01/2023. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation +import MullvadTypes +import StoreKit + +extension StorePaymentManagerError: DisplayError { + var displayErrorDescription: String? { + switch self { + case .noAccountSet: + return NSLocalizedString( + "INTERNAL_ERROR", + tableName: "StorePaymentManager", + value: "Internal error.", + comment: "" + ) + + case let .validateAccount(restError): + let reason = restError.displayErrorDescription ?? "" + + return String( + format: NSLocalizedString( + "VALIDATE_ACCOUNT_ERROR", + tableName: "StorePaymentManager", + value: "Failed to validate account number: %@", + comment: "" + ), reason + ) + + case let .readReceipt(readReceiptError): + if readReceiptError is StoreReceiptNotFound { + return NSLocalizedString( + "RECEIPT_NOT_FOUND_ERROR", + tableName: "StorePaymentManager", + value: "AppStore receipt is not found on disk.", + comment: "" + ) + } else if let storeError = readReceiptError as? SKError { + return String( + format: NSLocalizedString( + "REFRESH_RECEIPT_ERROR", + tableName: "StorePaymentManager", + value: "Cannot refresh the AppStore receipt: %@", + comment: "" + ), + storeError.localizedDescription + ) + } else { + return NSLocalizedString( + "READ_RECEIPT_ERROR", + tableName: "StorePaymentManager", + value: "Cannot read the AppStore receipt from disk", + comment: "" + ) + } + + case let .sendReceipt(restError): + let reason = restError.displayErrorDescription ?? "" + let errorFormat = NSLocalizedString( + "SEND_RECEIPT_ERROR", + tableName: "StorePaymentManager", + value: "Failed to send the receipt to server: %@", + comment: "" + ) + let recoverySuggestion = NSLocalizedString( + "SEND_RECEIPT_RECOVERY_SUGGESTION", + tableName: "StorePaymentManager", + value: "Please retry by using the \"Restore purchases\" button.", + comment: "" + ) + var errorString = String(format: errorFormat, reason) + errorString.append("\n\n") + errorString.append(recoverySuggestion) + return errorString + + case let .storePayment(storeError): + return (storeError as? SKError)?.errorDescription ?? storeError.localizedDescription + } + } +} diff --git a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift index 960393f7fd..9fc30565f5 100644 --- a/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift +++ b/ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift @@ -253,7 +253,7 @@ enum SendTunnelProviderMessageError: LocalizedError, WrappingError { case timeout /// System error. - case system(Swift.Error) + case system(Error) var errorDescription: String? { switch self { |
