Skip to content
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
@@ -1,13 +1,16 @@


import Foundation

private let log = PIALogger.logger(for: ClientErrorMapper.self)

/// Maps an Network Error with a ClientError
/// The idea is to use this mapper on `PIAWebServices` to map the errors returned from the Swift implementation of the Accounts Lib with the ones that the app expects
struct ClientErrorMapper {
static func map(networkRequestError: NetworkRequestError) -> ClientError {
log.info("Will map ClientError from NetworkRequestError: \(networkRequestError)")

switch networkRequestError {
case .connectionError(let statusCode, let message):
log.info(".connectionError - statusCode: \(statusCode?.description ?? "nil"), message: \(message ?? "nil")")
return getClientError(from: statusCode) ?? .unexpectedReply

case .allConnectionAttemptsFailed(let statusCode):
Expand All @@ -29,6 +32,7 @@ struct ClientErrorMapper {
return .malformedResponseData

case .unknown(message: let message):
log.info(".unknown - message: \(message ?? "nil")")
return .unexpectedReply

case .unableToDecodeAPIToken, .unableToDecodeDataContent:
Expand All @@ -49,17 +53,25 @@ struct ClientErrorMapper {
}

static func getClientError(from statusCode: Int?) -> ClientError? {

guard let statusCode,
let httpStatusCode = HttpResponseStatusCode(rawValue: statusCode) else {
guard
let statusCode,
let httpStatusCode = HttpResponseStatusCode(rawValue: statusCode)
else {
log.error("Unable to map ClientError due to lack of statusCode")
return nil
}

switch httpStatusCode {
case .unauthorized:
log.info("Did map \(ClientError.unauthorized) out of httpStatusCode: \(httpStatusCode)")
return .unauthorized

case .throttled:
log.info("Did map \(ClientError.throttled(retryAfter: 60)) out of httpStatusCode: \(httpStatusCode)")
return .throttled(retryAfter: 60)

default:
log.info("Mapped no ClientError out of httpStatusCode: \(httpStatusCode)")
return nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -601,6 +601,9 @@ open class DefaultAccountProvider: AccountProvider, ConfigurationAccess, Databas
accessedDatabase.plain.orderedTiles = AvailableTiles.defaultTiles()
accessedDatabase.plain.historicalServers = []
accessedDatabase.plain.reset()

apiTokenProvider.clearAPIToken()
vpnTokenProvider.clearVpnToken()
}

#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import Foundation
import NWHttpConnection

private let log = PIALogger.logger(for: RefreshAPITokenUseCase.self)

protocol RefreshAPITokenUseCaseType {
typealias Completion = ((NetworkRequestError?) -> Void)
func callAsFunction(completion: @escaping RefreshAPITokenUseCaseType.Completion)
Expand All @@ -18,30 +20,35 @@ class RefreshAPITokenUseCase: RefreshAPITokenUseCaseType {
}

func callAsFunction(completion: @escaping RefreshAPITokenUseCaseType.Completion) {

let configuration = RefreshApiTokenRequestConfiguration()

networkClient.executeRequest(with: configuration) { [weak self] error, dataResponse in
guard let self else { return }

if let error {
// Clear old token on refresh failure to prevent repeated auth failures
log.info("Clearing API token due to error: \(error)")
self?.apiTokenProvider.clearAPIToken()
completion(error)
} else if let dataResponse {
self.handleDataResponse(dataResponse, completion: completion)
self?.handleDataResponse(dataResponse, completion: completion)
} else {
// Clear old token on refresh failure to prevent repeated auth failures
log.info("Clearing API token due to allConnectionAttemptsFailed (no error and no response)")
self?.apiTokenProvider.clearAPIToken()
completion(NetworkRequestError.allConnectionAttemptsFailed())
}
}

}

}


private extension RefreshAPITokenUseCase {
private func handleDataResponse(_ dataResponse: NetworkRequestResponseType, completion: @escaping RefreshVpnTokenUseCaseType.Completion) {

private func handleDataResponse(_ dataResponse: NetworkRequestResponseType, completion: @escaping RefreshAPITokenUseCaseType.Completion) {
guard let dataResponseContent = dataResponse.data else {
// Clear old token on refresh failure to prevent repeated auth failures
log.info("Clearing API token due to noDataContent in response")
apiTokenProvider.clearAPIToken()
completion(NetworkRequestError.noDataContent)
return
}
Expand All @@ -50,9 +57,11 @@ private extension RefreshAPITokenUseCase {
try apiTokenProvider.saveAPIToken(from: dataResponseContent)
completion(nil)
} catch {
// Clear old token when unable to save new one to prevent repeated auth failures
log.info("Clearing API token due to save failure - error: \(error)")
apiTokenProvider.clearAPIToken()
completion(NetworkRequestError.unableToSaveAPIToken)
}

}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import Foundation

private let log = PIALogger.logger(for: RefreshVpnTokenUseCase.self)

protocol RefreshVpnTokenUseCaseType {
typealias Completion = ((NetworkRequestError?) -> Void)
func callAsFunction(completion: @escaping RefreshVpnTokenUseCaseType.Completion)
Expand All @@ -17,30 +19,35 @@ class RefreshVpnTokenUseCase: RefreshVpnTokenUseCaseType {
}

func callAsFunction(completion: @escaping RefreshVpnTokenUseCaseType.Completion) {

let configuration = RefreshVpnTokenRequestConfiguration()

networkClient.executeRequest(with: configuration) { [weak self] error, dataResponse in
guard let self else { return }

if let error {
// Clear old token on refresh failure to prevent repeated auth failures
log.info("Clearing VPN token due to error: \(error)")
self?.vpnTokenProvider.clearVpnToken()
completion(error)
} else if let dataResponse {
self.handleDataResponse(dataResponse, completion: completion)
self?.handleDataResponse(dataResponse, completion: completion)
} else {
// Clear old token on refresh failure to prevent repeated auth failures
log.info("Clearing VPN token due to allConnectionAttemptsFailed (no error and no response)")
self?.vpnTokenProvider.clearVpnToken()
completion(NetworkRequestError.allConnectionAttemptsFailed())
}

}
}

}


private extension RefreshVpnTokenUseCase {

private func handleDataResponse(_ dataResponse: NetworkRequestResponseType, completion: @escaping RefreshVpnTokenUseCaseType.Completion) {

guard let dataResponseContent = dataResponse.data else {
// Clear old token on refresh failure to prevent repeated auth failures
log.info("Clearing VPN token due to noDataContent in response")
vpnTokenProvider.clearVpnToken()
completion(NetworkRequestError.noDataContent)
return
}
Expand All @@ -49,9 +56,10 @@ private extension RefreshVpnTokenUseCase {
try vpnTokenProvider.saveVpnToken(from: dataResponseContent)
completion(nil)
} catch {
// Clear old token when unable to save new one to prevent repeated auth failures
log.info("Clearing VPN token due to save failure - error: \(error)")
vpnTokenProvider.clearVpnToken()
completion(NetworkRequestError.unableToSaveVpnToken)
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import Foundation
import NWHttpConnection

private let log = PIALogger.logger(for: NetworkRequestClient.self)

protocol NetworkRequestClientType {
typealias Completion = ((NetworkRequestError?, NetworkRequestResponseType?) -> Void)
Expand Down Expand Up @@ -42,8 +43,9 @@ private extension NetworkRequestClient {

/// Serial execution of all the connections until one succeeds or completes with an error when all connection attempts fail
func executeRecursivelyUntilSuccess(connections: [NWHttpConnectionType], completion: @escaping NetworkRequestClientType.Completion) {

guard !connections.isEmpty else {
log.error("All connection attempts failed: No connections available")
completion(.allConnectionAttemptsFailed(statusCode: nil), nil)
return
}
Expand All @@ -54,6 +56,7 @@ private extension NetworkRequestClient {
func tryNextConnectionOrFail(currentStatusCode: Int?) {
if remainingConnections.isEmpty {
// No more endpoints to try a connection
log.error("All connection attempts failed: No more endpoints to try (statusCode: \(currentStatusCode?.description ?? "nil"))")
let requestError = NetworkRequestError.allConnectionAttemptsFailed(statusCode: currentStatusCode)
completion(requestError, nil)
} else {
Expand All @@ -64,20 +67,23 @@ private extension NetworkRequestClient {

execute(connection: nextConnection) { error, responseData in

if error != nil {
if let error = error {
log.error("Connection error: \(error) (statusCode: \(responseData?.statusCode?.description ?? "nil"))")
tryNextConnectionOrFail(currentStatusCode: responseData?.statusCode)
} else if let responseData {
let statusCode: Int = responseData.statusCode ?? -1
let isSuccessStatusCode = statusCode > 199 && statusCode < 300

if isSuccessStatusCode {
completion(nil, responseData)
} else {
// Connection did not succeed, try the next one
log.error("Non-success status code received: \(statusCode)")
tryNextConnectionOrFail(currentStatusCode: responseData.statusCode)
}
} else {
// No error and no data
log.error("Connection completed with no error and no data")
tryNextConnectionOrFail(currentStatusCode: nil)
}
}
Expand All @@ -88,25 +94,29 @@ private extension NetworkRequestClient {
func execute(connection: NWHttpConnectionType, completion: @escaping NetworkRequestClientType.Completion) {
do {
var connectionHandled: Bool = false

try connection.connect { error, dataResponse in
if let error {
connectionHandled = true
log.error("Connection error in single connection: \(error.localizedDescription) (statusCode: \(dataResponse?.statusCode?.description ?? "nil"))")
completion(NetworkRequestError.connectionError(statusCode: dataResponse?.statusCode, message: error.localizedDescription), nil)
} else if let dataResponse = dataResponse as? NetworkRequestResponseType {
connectionHandled = true
completion(nil, dataResponse)
} else {
connectionHandled = true
log.error("No error and no response in single connection")
completion(NetworkRequestError.noErrorAndNoResponse, nil)
}
} completion: {
if connectionHandled == false {
log.error("Connection completed with no response")
completion(NetworkRequestError.connectionCompletedWithNoResponse, nil)
}
}

} catch {
log.error("Unknown error in connection execution: \(error.localizedDescription)")
completion(NetworkRequestError.unknown(message: error.localizedDescription), nil)
}
}
Expand Down