summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/AccountDataThrottling.swift80
-rw-r--r--ios/MullvadVPN/CustomNavigationController.swift6
-rw-r--r--ios/MullvadVPN/SceneDelegate.swift135
-rw-r--r--ios/MullvadVPN/SettingsNavigationController.swift33
5 files changed, 202 insertions, 56 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 84e1b45cf0..2ad363563b 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -163,6 +163,7 @@
58781CCE22AE8918009B9D8E /* RelayConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CC822AE7CA8009B9D8E /* RelayConstraints.swift */; };
58781CD522AFBA39009B9D8E /* RelaySelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58781CD422AFBA39009B9D8E /* RelaySelector.swift */; };
5878BA1426DD0B01004147D7 /* OSLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5823FA4F26CA690600283BF8 /* OSLogHandler.swift */; };
+ 587988C728A2A01F00E3DF54 /* AccountDataThrottling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587988C628A2A01F00E3DF54 /* AccountDataThrottling.swift */; };
587A01FC23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */; };
587AD7C623421D7000E93A53 /* TunnelSettingsV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */; };
587AD7C723421D8600E93A53 /* TunnelSettingsV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */; };
@@ -465,6 +466,7 @@
5875960926F371FC00BF6711 /* Tunnel+Messaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tunnel+Messaging.swift"; sourceTree = "<group>"; };
58781CC822AE7CA8009B9D8E /* RelayConstraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConstraints.swift; sourceTree = "<group>"; };
58781CD422AFBA39009B9D8E /* RelaySelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySelector.swift; sourceTree = "<group>"; };
+ 587988C628A2A01F00E3DF54 /* AccountDataThrottling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDataThrottling.swift; sourceTree = "<group>"; };
587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelProviderHost.swift; sourceTree = "<group>"; };
587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV1.swift; sourceTree = "<group>"; };
587B7535266528A200DEF7E9 /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
@@ -847,6 +849,7 @@
isa = PBXGroup;
children = (
5896CEF126972DEB00B0FAE8 /* AccountContentView.swift */,
+ 587988C628A2A01F00E3DF54 /* AccountDataThrottling.swift */,
58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */,
58CCA01D2242787B004F3011 /* AccountTextField.swift */,
582AE30F2440A6CA00E6733A /* AccountTokenInput.swift */,
@@ -1305,6 +1308,7 @@
5891BF5125E66B1E006D6FB0 /* UIBarButtonItem+KeyboardNavigation.swift in Sources */,
587B75412668FD7800DEF7E9 /* AccountExpiryNotificationProvider.swift in Sources */,
585DA89926B0329200B8C587 /* PacketTunnelStatus.swift in Sources */,
+ 587988C728A2A01F00E3DF54 /* AccountDataThrottling.swift in Sources */,
5896CEF226972DEB00B0FAE8 /* AccountContentView.swift in Sources */,
5840250122B1124600E4CFEC /* IPAddress+Codable.swift in Sources */,
5842102E282D3FC200F24E46 /* ResultBlockOperation.swift in Sources */,
diff --git a/ios/MullvadVPN/AccountDataThrottling.swift b/ios/MullvadVPN/AccountDataThrottling.swift
new file mode 100644
index 0000000000..31f0b1f849
--- /dev/null
+++ b/ios/MullvadVPN/AccountDataThrottling.swift
@@ -0,0 +1,80 @@
+//
+// AccountDataThrottling.swift
+// MullvadVPN
+//
+// Created by pronebird on 09/08/2022.
+// Copyright © 2022 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+
+/// Struct used for throttling UI calls to update account data via tunnel manager.
+struct AccountDataThrottling {
+ /// Default cooldown interval between requests.
+ private static let defaultWaitInterval: TimeInterval = 60
+
+ /// Cooldown interval used when account has already expired.
+ private static let waitIntervalForExpiredAccount: TimeInterval = 10
+
+ /// Interval in days when account is considered to be close to expiry.
+ private static let closeToExpiryDays = 4
+
+ enum Condition {
+ /// Always update account data.
+ case always
+
+ /// Only update account data when account is close to expiry or already expired.
+ case whenCloseToExpiryAndBeyond
+ }
+
+ let tunnelManager: TunnelManager
+ private(set) var lastUpdate: Date?
+
+ init(tunnelManager: TunnelManager = .shared) {
+ self.tunnelManager = tunnelManager
+ }
+
+ mutating func requestUpdate(condition: Condition) {
+ guard let accountData = tunnelManager.deviceState.accountData else {
+ return
+ }
+
+ let now = Date()
+
+ switch condition {
+ case .always:
+ break
+
+ case .whenCloseToExpiryAndBeyond:
+ guard let closeToExpiry = Calendar.current.date(
+ byAdding: .day,
+ value: Self.closeToExpiryDays * -1,
+ to: accountData.expiry
+ ) else { return }
+
+ if closeToExpiry > now {
+ return
+ }
+ }
+
+ let waitInterval = accountData.expiry > now
+ ? Self.defaultWaitInterval
+ : Self.waitIntervalForExpiredAccount
+
+ let nextUpdateAfter = lastUpdate?.addingTimeInterval(waitInterval)
+ let comparisonResult = nextUpdateAfter?.compare(now) ?? .orderedAscending
+
+ switch comparisonResult {
+ case .orderedAscending, .orderedSame:
+ lastUpdate = Date()
+ tunnelManager.updateAccountData()
+
+ case .orderedDescending:
+ break
+ }
+ }
+
+ mutating func reset() {
+ lastUpdate = nil
+ }
+}
diff --git a/ios/MullvadVPN/CustomNavigationController.swift b/ios/MullvadVPN/CustomNavigationController.swift
index a19136b0c8..098ba7042f 100644
--- a/ios/MullvadVPN/CustomNavigationController.swift
+++ b/ios/MullvadVPN/CustomNavigationController.swift
@@ -56,12 +56,18 @@ class CustomNavigationController: UINavigationController, UINavigationBarDelegat
// Only call super implementation when we want to pop the controller
if shouldPop {
+ willPop(navigationItem: item)
+
// Call super implementation
return customNavigationController_navigationBar(navigationBar, shouldPop: item)
} else {
return shouldPop
}
}
+
+ func willPop(navigationItem: UINavigationItem) {
+ // Override in subclasses
+ }
}
private class CustomPopGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate {
diff --git a/ios/MullvadVPN/SceneDelegate.swift b/ios/MullvadVPN/SceneDelegate.swift
index 003672284d..d07b019e34 100644
--- a/ios/MullvadVPN/SceneDelegate.swift
+++ b/ios/MullvadVPN/SceneDelegate.swift
@@ -27,6 +27,7 @@ class SceneDelegate: UIResponder {
private var connectController: ConnectViewController?
private weak var settingsNavController: SettingsNavigationController?
private var lastLoginAction: LoginAction?
+ private var accountDataThrottling = AccountDataThrottling()
override init() {
super.init()
@@ -73,6 +74,8 @@ class SceneDelegate: UIResponder {
RelayCache.Tracker.shared.addObserver(self)
NotificationManager.shared.delegate = self
+
+ accountDataThrottling.requestUpdate(condition: .always)
}
private func setShowsPrivacyOverlay(_ showOverlay: Bool) {
@@ -115,6 +118,14 @@ class SceneDelegate: UIResponder {
@objc private func sceneDidBecomeActive() {
TunnelManager.shared.refreshTunnelStatus()
+ if isSceneConfigured {
+ accountDataThrottling.requestUpdate(
+ condition: settingsNavController == nil
+ ? .whenCloseToExpiryAndBeyond
+ : .always
+ )
+ }
+
RelayCache.Tracker.shared.startPeriodicUpdates()
TunnelManager.shared.startPeriodicPrivateKeyRotation()
AddressCache.Tracker.shared.startPeriodicUpdates()
@@ -184,14 +195,13 @@ extension SceneDelegate: RootContainerViewControllerDelegate {
) {
// Check if settings controller is already presented.
if let settingsNavController = settingsNavController {
- if let route = route {
- settingsNavController.navigate(to: route, animated: animated)
- } else {
- settingsNavController.popToRootViewController(animated: animated)
- }
+ settingsNavController.navigate(to: route ?? .root, animated: animated)
} else {
let navController = makeSettingsNavigationController(route: route)
+ // Refresh account data each time user opens settings
+ accountDataThrottling.requestUpdate(condition: .always)
+
// On iPad the login controller can be presented modally above the root container.
// in that case we have to use the presented controller to present the next modal.
if let presentedController = controller.presentedViewController {
@@ -488,6 +498,55 @@ extension SceneDelegate {
fatalError()
}
}
+
+ private func showRevokedDeviceView() {
+ switch UIDevice.current.userInterfaceIdiom {
+ case .phone:
+ guard let loginController = rootContainer.viewControllers.first as? LoginViewController
+ else {
+ return
+ }
+
+ loginController.reset()
+
+ let viewControllers = [
+ loginController,
+ makeRevokedDeviceController(),
+ ]
+
+ rootContainer.setViewControllers(viewControllers, animated: true)
+
+ case .pad:
+ guard let loginController = modalRootContainer.viewControllers
+ .first as? LoginViewController
+ else {
+ return
+ }
+
+ loginController.reset()
+
+ let viewControllers = [
+ loginController,
+ makeRevokedDeviceController(),
+ ]
+
+ let didDismissSettings = {
+ self.showSplitViewMaster(false, animated: true)
+ self.presentModalRootContainerIfNeeded(animated: true)
+ }
+
+ modalRootContainer.setViewControllers(viewControllers, animated: isModalRootPresented)
+
+ if let settingsNavController = settingsNavController {
+ settingsNavController.dismiss(animated: true, completion: didDismissSettings)
+ } else {
+ didDismissSettings()
+ }
+
+ default:
+ fatalError()
+ }
+ }
}
// MARK: - LoginViewControllerDelegate
@@ -608,6 +667,19 @@ extension SceneDelegate: DeviceManagementViewControllerDelegate {
extension SceneDelegate: SettingsNavigationControllerDelegate {
func settingsNavigationController(
_ controller: SettingsNavigationController,
+ willNavigateTo route: SettingsNavigationRoute
+ ) {
+ switch route {
+ case .root, .account:
+ accountDataThrottling.requestUpdate(condition: .always)
+
+ default:
+ break
+ }
+ }
+
+ func settingsNavigationController(
+ _ controller: SettingsNavigationController,
didFinishWithReason reason: SettingsDismissReason
) {
if case .userLoggedOut = reason {
@@ -771,53 +843,16 @@ extension SceneDelegate: TunnelObserver {
}
func tunnelManager(_ manager: TunnelManager, didUpdateDeviceState deviceState: DeviceState) {
- guard deviceState == .revoked else { return }
-
- switch UIDevice.current.userInterfaceIdiom {
- case .phone:
- guard let loginController = rootContainer.viewControllers.first as? LoginViewController
- else {
- return
- }
-
- loginController.reset()
-
- let viewControllers = [
- loginController,
- makeRevokedDeviceController(),
- ]
-
- rootContainer.setViewControllers(viewControllers, animated: true)
-
- case .pad:
- guard let loginController = modalRootContainer.viewControllers
- .first as? LoginViewController
- else {
- return
- }
-
- loginController.reset()
-
- let viewControllers = [
- loginController,
- makeRevokedDeviceController(),
- ]
-
- let didDismissSettings = {
- self.showSplitViewMaster(false, animated: true)
- self.presentModalRootContainerIfNeeded(animated: true)
- }
+ switch deviceState {
+ case .loggedIn:
+ break
- modalRootContainer.setViewControllers(viewControllers, animated: isModalRootPresented)
+ case .loggedOut:
+ accountDataThrottling.reset()
- if let settingsNavController = settingsNavController {
- settingsNavController.dismiss(animated: true, completion: didDismissSettings)
- } else {
- didDismissSettings()
- }
-
- default:
- fatalError()
+ case .revoked:
+ accountDataThrottling.reset()
+ showRevokedDeviceView()
}
}
diff --git a/ios/MullvadVPN/SettingsNavigationController.swift b/ios/MullvadVPN/SettingsNavigationController.swift
index cc59a05c4e..88bb80b519 100644
--- a/ios/MullvadVPN/SettingsNavigationController.swift
+++ b/ios/MullvadVPN/SettingsNavigationController.swift
@@ -10,6 +10,7 @@ import Foundation
import UIKit
enum SettingsNavigationRoute {
+ case root
case account
case preferences
case problemReport
@@ -23,6 +24,11 @@ enum SettingsDismissReason {
protocol SettingsNavigationControllerDelegate: AnyObject {
func settingsNavigationController(
_ controller: SettingsNavigationController,
+ willNavigateTo route: SettingsNavigationRoute
+ )
+
+ func settingsNavigationController(
+ _ controller: SettingsNavigationController,
didFinishWithReason reason: SettingsDismissReason
)
}
@@ -43,10 +49,7 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont
init() {
super.init(navigationBarClass: CustomNavigationBar.self, toolbarClass: nil)
- let settingsController = SettingsViewController()
- settingsController.delegate = self
-
- pushViewController(settingsController, animated: false)
+ setViewControllers([makeViewController(for: .root)], animated: false)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
@@ -63,9 +66,14 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont
super.viewDidLoad()
navigationBar.prefersLargeTitles = true
+ }
+
+ override func willPop(navigationItem: UINavigationItem) {
+ let index = viewControllers.firstIndex { $0.navigationItem == navigationItem }
- // Update account expiry
- TunnelManager.shared.updateAccountData()
+ if viewControllers.count > 1, index == 1 {
+ settingsDelegate?.settingsNavigationController(self, willNavigateTo: .root)
+ }
}
// MARK: - SettingsViewControllerDelegate
@@ -83,7 +91,15 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont
// MARK: - Navigation
func navigate(to route: SettingsNavigationRoute, animated: Bool) {
+ guard route != .root else {
+ popToRootViewController(animated: animated)
+ return
+ }
+
+ settingsDelegate?.settingsNavigationController(self, willNavigateTo: route)
+
let nextViewController = makeViewController(for: route)
+
if let rootController = viewControllers.first, viewControllers.count > 1 {
setViewControllers([rootController, nextViewController], animated: animated)
} else {
@@ -93,6 +109,11 @@ class SettingsNavigationController: CustomNavigationController, SettingsViewCont
private func makeViewController(for route: SettingsNavigationRoute) -> UIViewController {
switch route {
+ case .root:
+ let settingsController = SettingsViewController()
+ settingsController.delegate = self
+ return settingsController
+
case .account:
let controller = AccountViewController()
controller.delegate = self