diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2020-01-08 15:15:03 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2020-01-08 15:15:03 +0100 |
| commit | 69a36607f22a7a7cb87d4b4d0319b9e8bb118416 (patch) | |
| tree | 7f809baa81b130f456d3c48e28bc65e2638bfd01 | |
| parent | 233690c4e1bd67cc971ff36516f96bcfa29056cc (diff) | |
| parent | 281287c86ee32a8c43cc6fcfde1c565a8ac77527 (diff) | |
| download | mullvadvpn-69a36607f22a7a7cb87d4b4d0319b9e8bb118416.tar.xz mullvadvpn-69a36607f22a7a7cb87d4b4d0319b9e8bb118416.zip | |
Merge branch 'localize-errors'
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNTests.xcscheme (renamed from ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/RelaySelectorTests.xcscheme) | 31 | ||||
| -rw-r--r-- | ios/MullvadVPN/Account.swift | 42 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountExpiry.swift | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/AccountViewController.swift | 12 | ||||
| -rw-r--r-- | ios/MullvadVPN/Base.lproj/Main.storyboard | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/ConnectViewController.swift | 13 | ||||
| -rw-r--r-- | ios/MullvadVPN/LoginViewController.swift | 20 | ||||
| -rw-r--r-- | ios/MullvadVPN/MullvadAPI.swift | 29 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsAccountCell.swift | 13 | ||||
| -rw-r--r-- | ios/MullvadVPN/TunnelManager.swift | 60 | ||||
| -rw-r--r-- | ios/MullvadVPN/UIAlertController+Error.swift | 52 |
13 files changed, 253 insertions, 33 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index afc0800ad1..ea382d9c6d 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -59,6 +59,7 @@ 5894FC492296A8090017471D /* CustomButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5894FC482296A8090017471D /* CustomButton.swift */; }; 589AB4F7227B64450039131E /* BasicTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 589AB4F6227B64450039131E /* BasicTableViewCell.swift */; }; 58A8BE81239FBE62006B74AC /* IPEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58561C98239A5D1500BD6B5E /* IPEndpoint.swift */; }; + 58A8BE8323A0F362006B74AC /* UIAlertController+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A8BE8223A0F362006B74AC /* UIAlertController+Error.swift */; }; 58ADDB3C227B1BD200FAFEA7 /* JsonRpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ADDB3B227B1BD200FAFEA7 /* JsonRpc.swift */; }; 58ADDB3E227B1CD900FAFEA7 /* MullvadAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58ADDB3D227B1CD900FAFEA7 /* MullvadAPI.swift */; }; 58AEEF652344A36000C9BBD5 /* KeychainError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58AEEF642344A36000C9BBD5 /* KeychainError.swift */; }; @@ -182,6 +183,7 @@ 5894E725236B2801008A2793 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 5894FC482296A8090017471D /* CustomButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomButton.swift; sourceTree = "<group>"; }; 589AB4F6227B64450039131E /* BasicTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicTableViewCell.swift; sourceTree = "<group>"; }; + 58A8BE8223A0F362006B74AC /* UIAlertController+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Error.swift"; sourceTree = "<group>"; }; 58ADDB3B227B1BD200FAFEA7 /* JsonRpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonRpc.swift; sourceTree = "<group>"; }; 58ADDB3D227B1CD900FAFEA7 /* MullvadAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadAPI.swift; sourceTree = "<group>"; }; 58AEEF642344A36000C9BBD5 /* KeychainError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainError.swift; sourceTree = "<group>"; }; @@ -348,6 +350,7 @@ 58AEEF6A2344A46200C9BBD5 /* TunnelConfigurationManager.swift */, 5845F837236C466400B2D93C /* TunnelControlViewController.swift */, 5835B7CB233B76CB0096D79F /* TunnelManager.swift */, + 58A8BE8223A0F362006B74AC /* UIAlertController+Error.swift */, 587CBFE222807F530028DED3 /* UIColor+Helpers.swift */, 58CCA0152242560B004F3011 /* UIColor+Palette.swift */, 581CBCE52296B97300727D7F /* ViewControllerIdentifier.swift */, @@ -643,6 +646,7 @@ 5894FC492296A8090017471D /* CustomButton.swift in Sources */, 58CCA01E2242787B004F3011 /* AccountTextField.swift in Sources */, 587AD7CA2342283900E93A53 /* Account.swift in Sources */, + 58A8BE8323A0F362006B74AC /* UIAlertController+Error.swift in Sources */, 587425C12299833500CA2045 /* RootContainerViewController.swift in Sources */, 588AE72F2362001F009F9F2E /* MutuallyExclusive.swift in Sources */, 5888AD89227B18C40051EB06 /* RelayList.swift in Sources */, diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme index d894a24a28..405be68500 100644 --- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme +++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPN.xcscheme @@ -32,7 +32,7 @@ skipped = "NO"> <BuildableReference BuildableIdentifier = "primary" - BlueprintIdentifier = "584B26DF237433ED0073B10E" + BlueprintIdentifier = "58B0A29F238EE67E00BC001D" BuildableName = "MullvadVPNTests.xctest" BlueprintName = "MullvadVPNTests" ReferencedContainer = "container:MullvadVPN.xcodeproj"> diff --git a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/RelaySelectorTests.xcscheme b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNTests.xcscheme index 0f05e5b7f1..0c2fdada19 100644 --- a/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/RelaySelectorTests.xcscheme +++ b/ios/MullvadVPN.xcodeproj/xcshareddata/xcschemes/MullvadVPNTests.xcscheme @@ -5,6 +5,22 @@ <BuildAction parallelizeBuildables = "YES" buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "58B0A29F238EE67E00BC001D" + BuildableName = "MullvadVPNTests.xctest" + BlueprintName = "MullvadVPNTests" + ReferencedContainer = "container:MullvadVPN.xcodeproj"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> </BuildAction> <TestAction buildConfiguration = "Debug" @@ -16,9 +32,9 @@ skipped = "NO"> <BuildableReference BuildableIdentifier = "primary" - BlueprintIdentifier = "584B26F0237434D00073B10E" - BuildableName = "RelaySelectorTests.xctest" - BlueprintName = "RelaySelectorTests" + BlueprintIdentifier = "58B0A29F238EE67E00BC001D" + BuildableName = "MullvadVPNTests.xctest" + BlueprintName = "MullvadVPNTests" ReferencedContainer = "container:MullvadVPN.xcodeproj"> </BuildableReference> </TestableReference> @@ -41,6 +57,15 @@ savedToolIdentifier = "" useCustomWorkingDirectory = "NO" debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "58B0A29F238EE67E00BC001D" + BuildableName = "MullvadVPNTests.xctest" + BlueprintName = "MullvadVPNTests" + ReferencedContainer = "container:MullvadVPN.xcodeproj"> + </BuildableReference> + </MacroExpansion> </ProfileAction> <AnalyzeAction buildConfiguration = "Debug"> diff --git a/ios/MullvadVPN/Account.swift b/ios/MullvadVPN/Account.swift index 0d98134769..7feb876837 100644 --- a/ios/MullvadVPN/Account.swift +++ b/ios/MullvadVPN/Account.swift @@ -8,6 +8,7 @@ import Combine import Foundation +import NetworkExtension import os /// A enum describing the errors emitted by `Account` @@ -25,6 +26,47 @@ enum AccountLoginError: Error { case tunnelConfiguration(TunnelManagerError) } +extension AccountError: LocalizedError { + var errorDescription: String? { + switch self { + case .login: + return NSLocalizedString("Log in error", comment: "") + + case .logout: + return NSLocalizedString("Log out error", comment: "") + } + } + + var failureReason: String? { + switch self { + case .login(.invalidAccount): + return NSLocalizedString("Invalid account", comment: "") + + case .login(.tunnelConfiguration(.setAccount(let setAccountError))): + switch setAccountError { + case .pushWireguardKey(.transport(.network)): + return NSLocalizedString("Network error", comment: "") + + case .pushWireguardKey(.server(let serverError)): + return serverError.errorDescription ?? serverError.message + + case .setup(.saveTunnel(let systemError as NEVPNError)) + where systemError.code == .configurationReadWriteFailed: + return NSLocalizedString("Permission denied to add a VPN profile", comment: "") + + default: + return NSLocalizedString("Internal error", comment: "") + } + + case .logout: + return NSLocalizedString("Internal error", comment: "") + + default: + return nil + } + } +} + /// A enum holding the `UserDefaults` string keys private enum UserDefaultsKeys: String { case accountToken = "accountToken" diff --git a/ios/MullvadVPN/AccountExpiry.swift b/ios/MullvadVPN/AccountExpiry.swift index 493de6dc5b..aa4f4c5c06 100644 --- a/ios/MullvadVPN/AccountExpiry.swift +++ b/ios/MullvadVPN/AccountExpiry.swift @@ -28,8 +28,8 @@ class AccountExpiry { return date < Date() } - var formattedRemainingTime: String { - return relativeFormatter.string(from: Date(), to: date)! + var formattedRemainingTime: String? { + return relativeFormatter.string(from: Date(), to: date) } var formattedDate: String { diff --git a/ios/MullvadVPN/AccountViewController.swift b/ios/MullvadVPN/AccountViewController.swift index c51a8e6977..51d8013737 100644 --- a/ios/MullvadVPN/AccountViewController.swift +++ b/ios/MullvadVPN/AccountViewController.swift @@ -31,8 +31,14 @@ class AccountViewController: UIViewController { @IBAction func doLogout() { logoutSubscriber = Account.shared.logout() .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { (_) in - self.performSegue(withIdentifier: SegueIdentifier.Account.logout.rawValue, sender: self) + .sink(receiveCompletion: { (completion) in + switch completion { + case .failure(let error): + self.presentError(error, preferredStyle: .alert) + + case .finished: + self.performSegue(withIdentifier: SegueIdentifier.Account.logout.rawValue, sender: self) + } }) } @@ -45,7 +51,7 @@ class AccountViewController: UIViewController { let accountExpiry = AccountExpiry(date: expiryDate) if accountExpiry.isExpired { - expiryLabel.text = NSLocalizedString("OUT OF TIME", tableName: "Settings", comment: "") + expiryLabel.text = NSLocalizedString("OUT OF TIME", comment: "") expiryLabel.textColor = .dangerColor } else { expiryLabel.text = accountExpiry.formattedDate diff --git a/ios/MullvadVPN/Base.lproj/Main.storyboard b/ios/MullvadVPN/Base.lproj/Main.storyboard index 82f6917ce1..b1c99c9877 100644 --- a/ios/MullvadVPN/Base.lproj/Main.storyboard +++ b/ios/MullvadVPN/Base.lproj/Main.storyboard @@ -123,8 +123,8 @@ <color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> </label> - <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enter your account number" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XSV-Lk-dj4"> - <rect key="frame" x="24" y="47" width="327" height="21"/> + <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enter your account number" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="XSV-Lk-dj4"> + <rect key="frame" x="24" y="47" width="327" height="20.5"/> <fontDescription key="fontDescription" type="system" pointSize="17"/> <color key="textColor" white="1" alpha="0.60359589041095896" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> <nil key="highlightedColor"/> diff --git a/ios/MullvadVPN/ConnectViewController.swift b/ios/MullvadVPN/ConnectViewController.swift index 8e9c4f09b7..7d3361a512 100644 --- a/ios/MullvadVPN/ConnectViewController.swift +++ b/ios/MullvadVPN/ConnectViewController.swift @@ -96,7 +96,10 @@ class ConnectViewController: UIViewController, RootContainment, TunnelControlVie .receive(on: DispatchQueue.main) .sink(receiveCompletion: { (completion) in if case .failure(let error) = completion { - os_log(.error, "Failed to start the tunnel: %{public}s", error.localizedDescription) + os_log(.error, "Failed to start the tunnel: %{public}s", + error.localizedDescription) + + self.presentError(error, preferredStyle: .alert) } }) } @@ -104,7 +107,7 @@ class ConnectViewController: UIViewController, RootContainment, TunnelControlVie private func disconnectTunnel() { startStopTunnelSubscriber = TunnelManager.shared.stopTunnel() .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { (_) in + .sink(receiveCompletion: { (completion) in // no-op }) } @@ -151,13 +154,13 @@ private extension TunnelState { func textForSecureLabel() -> String { switch self { case .connecting, .reconnecting: - return NSLocalizedString("Creating secure connection", tableName: "Connect", comment: "") + return NSLocalizedString("Creating secure connection", comment: "") case .connected: - return NSLocalizedString("Secure connection", tableName: "Connect", comment: "") + return NSLocalizedString("Secure connection", comment: "") case .disconnecting, .disconnected: - return NSLocalizedString("Unsecured connection", tableName: "Connect", comment: "") + return NSLocalizedString("Unsecured connection", comment: "") } } diff --git a/ios/MullvadVPN/LoginViewController.swift b/ios/MullvadVPN/LoginViewController.swift index 5f2733f6a4..e44ac1e454 100644 --- a/ios/MullvadVPN/LoginViewController.swift +++ b/ios/MullvadVPN/LoginViewController.swift @@ -259,36 +259,32 @@ private extension LoginState { var localizedTitle: String { switch self { case .default: - return NSLocalizedString("Login", tableName: "Login", comment: "") + return NSLocalizedString("Login", comment: "") case .authenticating: - return NSLocalizedString("Logging in...", tableName: "Login", comment: "") + return NSLocalizedString("Logging in...", comment: "") case .failure: - return NSLocalizedString("Login failed", tableName: "Login", comment: "") + return NSLocalizedString("Login failed", comment: "") case .success: - return NSLocalizedString("Logged in", tableName: "Login", comment: "") + return NSLocalizedString("Logged in", comment: "") } } var localizedMessage: String { switch self { case .default: - return NSLocalizedString("Enter your account number", tableName: "Login", comment: "") + return NSLocalizedString("Enter your account number", comment: "") case .authenticating: - return NSLocalizedString("Checking account number", tableName: "Login", comment: "") + return NSLocalizedString("Checking account number", comment: "") case .failure(let error): - if case .login(.invalidAccount) = error { - return NSLocalizedString("Invalid account number", tableName: "Login", comment: "") - } else { - return NSLocalizedString("Internal error", tableName: "Login", comment: "") - } + return error.failureReason ?? "" case .success: - return NSLocalizedString("Correct account number", tableName: "Login", comment: "") + return NSLocalizedString("Correct account number", comment: "") } } } diff --git a/ios/MullvadVPN/MullvadAPI.swift b/ios/MullvadVPN/MullvadAPI.swift index be87728b7f..055b55e078 100644 --- a/ios/MullvadVPN/MullvadAPI.swift +++ b/ios/MullvadVPN/MullvadAPI.swift @@ -204,3 +204,32 @@ class MullvadAPI { return decoder } } + + +extension JsonRpcResponseError: LocalizedError + where + ResponseCode == MullvadAPI.ResponseCode +{ + var errorDescription: String? { + switch code { + case .accountDoesNotExist: + return NSLocalizedString("Invalid account", comment: "") + + case .tooManyWireguardKeys: + return NSLocalizedString("Too many public WireGuard keys", comment: "") + + case .other: + return nil + } + } + + var recoverySuggestion: String? { + switch code { + case .tooManyWireguardKeys: + return NSLocalizedString("Remove unused WireGuard keys", comment: "") + + default: + return nil + } + } +} diff --git a/ios/MullvadVPN/SettingsAccountCell.swift b/ios/MullvadVPN/SettingsAccountCell.swift index cc959c7c46..ce5705448e 100644 --- a/ios/MullvadVPN/SettingsAccountCell.swift +++ b/ios/MullvadVPN/SettingsAccountCell.swift @@ -24,14 +24,17 @@ class SettingsAccountCell: SettingsCell { let accountExpiry = AccountExpiry(date: accountExpiryDate) if accountExpiry.isExpired { - expiryLabel.text = NSLocalizedString("OUT OF TIME", tableName: "Settings", comment: "") + expiryLabel.text = NSLocalizedString("OUT OF TIME", comment: "") expiryLabel.textColor = .dangerColor } else { - let remainingTime = accountExpiry.formattedRemainingTime - let localizedString = NSLocalizedString("%@ left", tableName: "Settings", comment: "") - let formattedString = String(format: localizedString, remainingTime) + if let remainingTime = accountExpiry.formattedRemainingTime { + let localizedString = NSLocalizedString("%@ left", comment: "") + let formattedString = String(format: localizedString, remainingTime) - expiryLabel.text = formattedString.uppercased() + expiryLabel.text = formattedString.uppercased() + } else { + expiryLabel.text = "" + } expiryLabel.textColor = .white } } else { diff --git a/ios/MullvadVPN/TunnelManager.swift b/ios/MullvadVPN/TunnelManager.swift index f1b0ab8c59..3019fd2343 100644 --- a/ios/MullvadVPN/TunnelManager.swift +++ b/ios/MullvadVPN/TunnelManager.swift @@ -41,6 +41,66 @@ enum TunnelManagerError: Error { case regenerateWireguardPrivateKey(RegenerateWireguardPrivateKeyError) } +extension TunnelManagerError: LocalizedError { + + var errorDescription: String? { + switch self { + case .regenerateWireguardPrivateKey: + return NSLocalizedString("Cannot regenerate the private key", comment: "") + + case .setAccount: + return NSLocalizedString("Cannot set up the tunnel", comment: "") + + case .getWireguardPublicKey: + return NSLocalizedString("Cannot retrieve the public key", comment: "") + + case .startTunnel: + return NSLocalizedString("Cannot start the tunnel", comment: "") + + default: + return nil + } + } + + var failureReason: String? { + switch self { + + case .setAccount(.pushWireguardKey(let pushError)), + .regenerateWireguardPrivateKey(.replaceWireguardKey(let pushError)): + switch pushError { + case .transport(.network(let urlError)): + return urlError.localizedDescription + + case .server(let serverError): + return serverError.errorDescription + + default: + return NSLocalizedString("Internal error", comment: "") + } + + default: + return nil + } + } + + var recoverySuggestion: String? { + switch self { + case .regenerateWireguardPrivateKey(.replaceWireguardKey(let pushError)): + switch pushError { + case .server(let serverError) where serverError.code == .tooManyWireguardKeys: + return NSLocalizedString("Remove unused WireGuard keys and try again.", comment: "") + + default: + return nil + } + + default: + return nil + } + } + +} + enum TunnelIpcRequestError: Error { /// IPC is not set yet case missingIpc diff --git a/ios/MullvadVPN/UIAlertController+Error.swift b/ios/MullvadVPN/UIAlertController+Error.swift new file mode 100644 index 0000000000..b76958b0ef --- /dev/null +++ b/ios/MullvadVPN/UIAlertController+Error.swift @@ -0,0 +1,52 @@ +// +// UIAlertController+Error.swift +// MullvadVPN +// +// Created by pronebird on 11/12/2019. +// Copyright © 2019 Amagicom AB. All rights reserved. +// + +import Foundation +import UIKit + +/// An extension for presenting `LocalizedError` subclasses in `UIAlertController` +extension UIAlertController { + + convenience init<Error>(_ error: Error, preferredStyle: UIAlertController.Style) + where Error: LocalizedError + { + let title = error.errorDescription + let message = [error.failureReason, error.recoverySuggestion] + .compactMap { $0 } + .joined(separator: "\n\n") + + self.init(title: title, message: message, preferredStyle: preferredStyle) + } + +} + +extension UIViewController { + + /// Present an instance of `LocalizedError` using `UIAlertController` + /// Note: this method adds a default "OK" action when `configurationBlock` is not given + func presentError<Error>( + _ error: Error, + preferredStyle: UIAlertController.Style, + configurationBlock: ((UIAlertController) -> Void)? = nil, + completionBlock: (() -> Void)? = nil) + where Error: LocalizedError + { + let alertController = UIAlertController(error, preferredStyle: preferredStyle) + + if let configurationBlock = configurationBlock { + configurationBlock(alertController) + } else { + alertController.addAction( + UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .cancel) + ) + } + + self.present(alertController, animated: true, completion: completionBlock) + } + +} |
