summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/MullvadREST/RESTError.swift16
-rw-r--r--ios/MullvadTypes/DisplayError.swift21
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj30
-rw-r--r--ios/MullvadVPN/AccountInteractor.swift2
-rw-r--r--ios/MullvadVPN/AccountViewController.swift4
-rw-r--r--ios/MullvadVPN/DisplayChainedError.swift199
-rw-r--r--ios/MullvadVPN/LoginViewController.swift3
-rw-r--r--ios/MullvadVPN/OutOfTimeInteractor.swift2
-rw-r--r--ios/MullvadVPN/OutOfTimeViewController.swift4
-rw-r--r--ios/MullvadVPN/ProblemReportSubmissionOverlayView.swift2
-rw-r--r--ios/MullvadVPN/RESTError+Display.swift66
-rw-r--r--ios/MullvadVPN/SKError+Localized.swift53
-rw-r--r--ios/MullvadVPN/StorePaymentManager/StorePaymentManager.swift2
-rw-r--r--ios/MullvadVPN/StorePaymentManager/StorePaymentManagerError.swift6
-rw-r--r--ios/MullvadVPN/StorePaymentManagerError+Display.swift86
-rw-r--r--ios/MullvadVPN/TunnelManager/SendTunnelProviderMessageOperation.swift2
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 {