Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UI for blocked state #5239

Merged
merged 1 commit into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
//

import Foundation
import PacketTunnelCore

final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotificationProvider {
private var isWaitingForConnectivity = false
private var noNetwork = false
private var packetTunnelError: String?
private var packetTunnelError: BlockedStateReason?
private var tunnelManagerError: Error?
private var tunnelObserver: TunnelBlockObserver?

Expand Down Expand Up @@ -57,7 +58,7 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific
private func handleTunnelStatus(_ tunnelStatus: TunnelStatus) {
let invalidateForTunnelError: Bool
if case let .error(blockStateReason) = tunnelStatus.state {
invalidateForTunnelError = updateLastTunnelError(blockStateReason.rawValue)
invalidateForTunnelError = updateLastTunnelError(blockStateReason)
} else {
invalidateForTunnelError = updateLastTunnelError(nil)
}
Expand All @@ -71,7 +72,7 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific
}
}

private func updateLastTunnelError(_ lastTunnelError: String?) -> Bool {
private func updateLastTunnelError(_ lastTunnelError: BlockedStateReason?) -> Bool {
if packetTunnelError != lastTunnelError {
packetTunnelError = lastTunnelError

Expand Down Expand Up @@ -120,22 +121,21 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific
return false
}

private func notificationDescription(for packetTunnelError: String) -> InAppNotificationDescriptor {
private func notificationDescription(for packetTunnelError: BlockedStateReason) -> InAppNotificationDescriptor {
InAppNotificationDescriptor(
identifier: identifier,
style: .error,
title: NSLocalizedString(
"TUNNEL_LEAKING_INAPP_NOTIFICATION_TITLE",
value: "NETWORK TRAFFIC MIGHT BE LEAKING",
"TUNNEL_BLOCKED_INAPP_NOTIFICATION_TITLE",
value: "BLOCKING INTERNET",
comment: ""
),
body: .init(string: String(
format: NSLocalizedString(
"PACKET_TUNNEL_ERROR_INAPP_NOTIFICATION_BODY",
value: "Could not configure VPN: %@",
"TUNNEL_BLOCKED_INAPP_NOTIFICATION_BODY",
value: localizedReasonForBlockedStateError(packetTunnelError),
comment: ""
),
packetTunnelError
)
))
)
}
Expand Down Expand Up @@ -220,4 +220,28 @@ final class TunnelStatusNotificationProvider: NotificationProvider, InAppNotific
)
)
}

private func localizedReasonForBlockedStateError(_ error: BlockedStateReason) -> String {
let errorString: String

switch error {
case .outdatedSchema:
errorString = "Unable to start tunnel connection after update. Please send a problem report."
case .noRelaysSatisfyingConstraints:
errorString = "No servers match your settings, try changing server or other settings."
case .invalidAccount:
errorString = "You are logged in with an invalid account number. Please log out and try another one."
case .deviceRevoked, .deviceLoggedOut:
errorString = "Unable to authenticate account. Please log out and log back in."
default:
errorString = "Unable to start tunnel connection. Please send a problem report."
}

return NSLocalizedString(
"BLOCKED_STATE_ERROR_TITLE",
tableName: "Main",
value: errorString,
comment: ""
)
}
}
35 changes: 30 additions & 5 deletions ios/MullvadVPN/TunnelManager/TunnelManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,12 @@ final class TunnelManager: StorePaymentObserver {
finish(result.error)
}
} catch {
if let error = error as? NoRelaysSatisfyingConstraintsError {
_ = self.setTunnelStatus { tunnelStatus in
tunnelStatus.state = .error(.noRelaysSatisfyingConstraints)
}
}

finish(error)

return AnyCancellable()
Expand Down Expand Up @@ -703,22 +709,30 @@ final class TunnelManager: StorePaymentObserver {
lastPacketTunnelKeyRotation = newPacketTunnelKeyRotation
refreshDeviceState()
}

// TODO: handle blocked state (error state). See how handleRestError() manages invalid account or revoked device.

switch newTunnelStatus.state {
case .connecting, .reconnecting:
// Start polling tunnel status to keep the relay information up to date
// while the tunnel process is trying to connect.
startPollingTunnelStatus(interval: establishingTunnelStatusPollInterval)

case .connected, .waitingForConnectivity(.noConnection), .error:
case .connected, .waitingForConnectivity(.noConnection):
// Start polling tunnel status to keep connectivity status up to date.
startPollingTunnelStatus(interval: establishedTunnelStatusPollInterval)

case .pendingReconnect, .disconnecting, .disconnected, .waitingForConnectivity(.noNetwork):
// Stop polling tunnel status once connection moved to final state.
cancelPollingTunnelStatus()

case let .error(blockedStateReason):
switch blockedStateReason {
case .deviceRevoked, .invalidAccount:
handleBlockedState(reason: blockedStateReason)
default:
break
}

// Stop polling tunnel status once blocked state has been determined.
cancelPollingTunnelStatus()
}

DispatchQueue.main.async {
Expand Down Expand Up @@ -1084,13 +1098,24 @@ final class TunnelManager: StorePaymentObserver {
guard let restError = error as? REST.Error else { return }

if restError.compareErrorCode(.deviceNotFound) {
setDeviceState(.revoked, persist: true)
handleBlockedState(reason: .deviceRevoked)
} else if restError.compareErrorCode(.invalidAccount) {
handleBlockedState(reason: .invalidAccount)
}
}

private func handleBlockedState(reason: BlockedStateReason) {
switch reason {
case .deviceRevoked:
setDeviceState(.revoked, persist: true)
case .invalidAccount:
unsetTunnelConfiguration {
self.setDeviceState(.revoked, persist: true)
self.operationQueue.cancelAllOperations()
self.wipeAllUserData()
}
default:
break
}
}

Expand Down
19 changes: 4 additions & 15 deletions ios/MullvadVPN/View controllers/Tunnel/TunnelControlView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ private extension TunnelState {
comment: ""
)

case .waitingForConnectivity(.noConnection):
case .waitingForConnectivity(.noConnection), .error:
return NSLocalizedString(
"TUNNEL_STATE_WAITING_FOR_CONNECTIVITY",
tableName: "Main",
Expand All @@ -511,10 +511,6 @@ private extension TunnelState {
value: "No network",
comment: ""
)

case let .error(blockedStateReason):
// TODO: Fix me
return ""
}
}

Expand All @@ -535,17 +531,14 @@ private extension TunnelState {
value: "Select location",
comment: ""
)
case .connecting, .connected, .reconnecting, .waitingForConnectivity:

case .connecting, .connected, .reconnecting, .waitingForConnectivity, .error:
return NSLocalizedString(
"SWITCH_LOCATION_BUTTON_TITLE",
tableName: "Main",
value: "Switch location",
comment: ""
)

case let .error(blockedStateReason):
// TODO: Fix me
return ""
}
}

Expand Down Expand Up @@ -591,7 +584,7 @@ private extension TunnelState {
tunnelInfo.location.country
)

case .waitingForConnectivity(.noConnection):
case .waitingForConnectivity(.noConnection), .error:
return NSLocalizedString(
"TUNNEL_STATE_WAITING_FOR_CONNECTIVITY_ACCESSIBILITY_LABEL",
tableName: "Main",
Expand Down Expand Up @@ -622,10 +615,6 @@ private extension TunnelState {
value: "Reconnecting",
comment: ""
)

case let .error(blockedStateReason):
// TODO: Fix me
return ""
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import WireGuardKit
/**
Struct responsible for mapping errors that may occur in the packet tunnel to the `BlockedStateReason`.
*/
struct BlockedStateErrorMapper: BlockedStateErrorMapperProtocol {
func mapError(_ error: Error) -> BlockedStateReason {
public struct BlockedStateErrorMapper: BlockedStateErrorMapperProtocol {
public func mapError(_ error: Error) -> BlockedStateReason {
switch error {
case let error as ReadDeviceDataError:
// Such error is thrown by implementations of `SettingsReaderProtocol`.
Expand Down
Loading