summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-08-29 15:12:06 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-08-29 15:12:06 +0200
commit90934dd1b94039fe5b1fc249485c07aee00be90e (patch)
treeff5bb0b31904a27a7fcf22af15af95ab9c2795fa
parent7a260eb36efe7395ad631765b758f30fc00e736c (diff)
parent5269ced5a5cd3b323fc20c29d7105bcefd30d443 (diff)
downloadmullvadvpn-90934dd1b94039fe5b1fc249485c07aee00be90e.tar.xz
mullvadvpn-90934dd1b94039fe5b1fc249485c07aee00be90e.zip
Merge branch 'async-rest'
-rw-r--r--ios/MullvadREST/RESTAPIProxy.swift23
-rw-r--r--ios/MullvadREST/RESTAccountsProxy.swift22
-rw-r--r--ios/MullvadREST/RESTNetworkOperation.swift6
-rw-r--r--ios/MullvadREST/RESTProxy.swift86
-rw-r--r--ios/MullvadREST/RESTRequestExecutor.swift29
-rw-r--r--ios/MullvadREST/RESTResponseHandler.swift2
-rw-r--r--ios/MullvadREST/RESTTransportProvider.swift16
-rw-r--r--ios/MullvadRESTTests/Mocks/AnyTransport.swift93
-rw-r--r--ios/MullvadRESTTests/Mocks/MemoryCache.swift21
-rw-r--r--ios/MullvadRESTTests/Mocks/TimeServerProxy.swift48
-rw-r--r--ios/MullvadRESTTests/RequestExecutorTests.swift75
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj29
-rw-r--r--ios/MullvadVPN/AppDelegate.swift2
13 files changed, 422 insertions, 30 deletions
diff --git a/ios/MullvadREST/RESTAPIProxy.swift b/ios/MullvadREST/RESTAPIProxy.swift
index fe7d795b9b..b547d92e45 100644
--- a/ios/MullvadREST/RESTAPIProxy.swift
+++ b/ios/MullvadREST/RESTAPIProxy.swift
@@ -110,10 +110,8 @@ extension REST {
public func createApplePayment(
accountNumber: String,
- receiptString: Data,
- retryStrategy: REST.RetryStrategy,
- completionHandler: @escaping CompletionHandler<CreateApplePaymentResponse>
- ) -> Cancellable {
+ receiptString: Data
+ ) -> any RESTRequestExecutor<CreateApplePaymentResponse> {
let requestHandler = AnyRequestHandler(
createURLRequest: { endpoint, authorization in
var requestBuilder = try self.requestFactory.createRequestBuilder(
@@ -160,11 +158,22 @@ extension REST {
}
}
- return addOperation(
+ return makeRequestExecutor(
name: "create-apple-payment",
- retryStrategy: retryStrategy,
requestHandler: requestHandler,
- responseHandler: responseHandler,
+ responseHandler: responseHandler
+ )
+ }
+
+ @available(*, deprecated, message: "Use createApplePayment(accountNumber:, receiptString:) instead")
+ public func createApplePayment(
+ accountNumber: String,
+ receiptString: Data,
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping CompletionHandler<CreateApplePaymentResponse>
+ ) -> Cancellable {
+ return createApplePayment(accountNumber: accountNumber, receiptString: receiptString).execute(
+ retryStrategy: retryStrategy,
completionHandler: completionHandler
)
}
diff --git a/ios/MullvadREST/RESTAccountsProxy.swift b/ios/MullvadREST/RESTAccountsProxy.swift
index 071616ec65..29e41e027e 100644
--- a/ios/MullvadREST/RESTAccountsProxy.swift
+++ b/ios/MullvadREST/RESTAccountsProxy.swift
@@ -49,11 +49,7 @@ extension REST {
)
}
- public func getAccountData(
- accountNumber: String,
- retryStrategy: REST.RetryStrategy,
- completion: @escaping CompletionHandler<Account>
- ) -> Cancellable {
+ public func getAccountData(accountNumber: String) -> any RESTRequestExecutor<Account> {
let requestHandler = AnyRequestHandler(
createURLRequest: { endpoint, authorization in
var requestBuilder = try self.requestFactory.createRequestBuilder(
@@ -74,11 +70,21 @@ extension REST {
with: responseDecoder
)
- return addOperation(
+ return makeRequestExecutor(
name: "get-my-account",
- retryStrategy: retryStrategy,
requestHandler: requestHandler,
- responseHandler: responseHandler,
+ responseHandler: responseHandler
+ )
+ }
+
+ @available(*, deprecated, message: "Use getAccountData(accountNumber:) instead")
+ public func getAccountData(
+ accountNumber: String,
+ retryStrategy: REST.RetryStrategy,
+ completion: @escaping CompletionHandler<Account>
+ ) -> Cancellable {
+ return getAccountData(accountNumber: accountNumber).execute(
+ retryStrategy: retryStrategy,
completionHandler: completion
)
}
diff --git a/ios/MullvadREST/RESTNetworkOperation.swift b/ios/MullvadREST/RESTNetworkOperation.swift
index c3dc45b263..0321c7be79 100644
--- a/ios/MullvadREST/RESTNetworkOperation.swift
+++ b/ios/MullvadREST/RESTNetworkOperation.swift
@@ -14,7 +14,7 @@ import Operations
extension REST {
class NetworkOperation<Success>: ResultOperation<Success> {
private let requestHandler: RESTRequestHandler
- private let responseHandler: AnyResponseHandler<Success>
+ private let responseHandler: any RESTResponseHandler<Success>
private let logger: Logger
private let transportProvider: RESTTransportProvider
@@ -37,8 +37,8 @@ extension REST {
configuration: ProxyConfiguration,
retryStrategy: RetryStrategy,
requestHandler: RESTRequestHandler,
- responseHandler: AnyResponseHandler<Success>,
- completionHandler: @escaping CompletionHandler
+ responseHandler: some RESTResponseHandler<Success>,
+ completionHandler: CompletionHandler? = nil
) {
addressCacheStore = configuration.addressCacheStore
transportProvider = configuration.transportProvider
diff --git a/ios/MullvadREST/RESTProxy.swift b/ios/MullvadREST/RESTProxy.swift
index 0206bef0df..4c6d138fd5 100644
--- a/ios/MullvadREST/RESTProxy.swift
+++ b/ios/MullvadREST/RESTProxy.swift
@@ -43,14 +43,55 @@ extension REST {
self.responseDecoder = responseDecoder
}
+ @available(*, deprecated, message: "Use makeRequestExecutor() instead")
func addOperation<Success>(
name: String,
retryStrategy: REST.RetryStrategy,
requestHandler: RESTRequestHandler,
- responseHandler: REST.AnyResponseHandler<Success>,
- completionHandler: @escaping NetworkOperation<Success>.CompletionHandler
+ responseHandler: some RESTResponseHandler<Success>,
+ completionHandler: @escaping CompletionHandler<Success>
) -> Cancellable {
- let operation = NetworkOperation(
+ let executor = makeRequestExecutor(
+ name: name,
+ requestHandler: requestHandler,
+ responseHandler: responseHandler
+ )
+
+ return executor.execute(retryStrategy: retryStrategy, completionHandler: completionHandler)
+ }
+
+ func makeRequestExecutor<Success>(
+ name: String,
+ requestHandler: RESTRequestHandler,
+ responseHandler: some RESTResponseHandler<Success>
+ ) -> any RESTRequestExecutor<Success> {
+ let operationFactory = NetworkOperationFactory(
+ dispatchQueue: dispatchQueue,
+ configuration: configuration,
+ name: name,
+ requestHandler: requestHandler,
+ responseHandler: responseHandler
+ )
+
+ return RequestExecutor(operationFactory: operationFactory, operationQueue: operationQueue)
+ }
+ }
+
+ /// Factory object producing instances of `NetworkOperation`.
+ private struct NetworkOperationFactory<Success, ConfigurationType: ProxyConfiguration> {
+ let dispatchQueue: DispatchQueue
+ let configuration: ConfigurationType
+
+ let name: String
+ let requestHandler: RESTRequestHandler
+ let responseHandler: any RESTResponseHandler<Success>
+
+ /// Creates new network operation but does not schedule it for execution.
+ func makeOperation(
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: ((Result<Success, Swift.Error>) -> Void)? = nil
+ ) -> NetworkOperation<Success> {
+ return NetworkOperation(
name: getTaskIdentifier(name: name),
dispatchQueue: dispatchQueue,
configuration: configuration,
@@ -59,11 +100,50 @@ extension REST {
responseHandler: responseHandler,
completionHandler: completionHandler
)
+ }
+ }
+
+ /// Network request executor that supports block-based and async execution flows.
+ private struct RequestExecutor<Success, ConfigurationType: ProxyConfiguration>: RESTRequestExecutor {
+ let operationFactory: NetworkOperationFactory<Success, ConfigurationType>
+ let operationQueue: AsyncOperationQueue
+
+ func execute(
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping (Result<Success, Swift.Error>) -> Void
+ ) -> Cancellable {
+ let operation = operationFactory.makeOperation(
+ retryStrategy: retryStrategy,
+ completionHandler: completionHandler
+ )
operationQueue.addOperation(operation)
return operation
}
+
+ func execute(retryStrategy: REST.RetryStrategy) async throws -> Success {
+ let operation = operationFactory.makeOperation(retryStrategy: retryStrategy)
+
+ return try await withTaskCancellationHandler {
+ return try await withCheckedThrowingContinuation { continuation in
+ operation.completionHandler = { result in
+ continuation.resume(with: result)
+ }
+ operationQueue.addOperation(operation)
+ }
+ } onCancel: {
+ operation.cancel()
+ }
+ }
+
+ func execute(completionHandler: @escaping (Result<Success, Swift.Error>) -> Void) -> Cancellable {
+ return execute(retryStrategy: .noRetry, completionHandler: completionHandler)
+ }
+
+ func execute() async throws -> Success {
+ return try await execute(retryStrategy: .noRetry)
+ }
}
public class ProxyConfiguration {
diff --git a/ios/MullvadREST/RESTRequestExecutor.swift b/ios/MullvadREST/RESTRequestExecutor.swift
new file mode 100644
index 0000000000..a705d1cda2
--- /dev/null
+++ b/ios/MullvadREST/RESTRequestExecutor.swift
@@ -0,0 +1,29 @@
+//
+// RESTRequestExecutor.swift
+// MullvadREST
+//
+// Created by pronebird on 21/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import protocol MullvadTypes.Cancellable
+
+public protocol RESTRequestExecutor<Success> {
+ associatedtype Success
+
+ /// Execute new network request with `.noRetry` strategy and receive the result in a completion handler on main queue.
+ func execute(completionHandler: @escaping (Result<Success, Swift.Error>) -> Void) -> Cancellable
+
+ /// Execute new network request and receive the result in a completion handler on main queue.
+ func execute(
+ retryStrategy: REST.RetryStrategy,
+ completionHandler: @escaping (Result<Success, Swift.Error>) -> Void
+ ) -> Cancellable
+
+ /// Execute new network request with `.noRetry` strategy and receive the result back via async flow.
+ func execute() async throws -> Success
+
+ /// Execute new network request and receive the result back via async flow.
+ func execute(retryStrategy: REST.RetryStrategy) async throws -> Success
+}
diff --git a/ios/MullvadREST/RESTResponseHandler.swift b/ios/MullvadREST/RESTResponseHandler.swift
index 7d16fcdf7f..fd07b639e9 100644
--- a/ios/MullvadREST/RESTResponseHandler.swift
+++ b/ios/MullvadREST/RESTResponseHandler.swift
@@ -9,7 +9,7 @@
import Foundation
import MullvadTypes
-protocol RESTResponseHandler {
+protocol RESTResponseHandler<Success> {
associatedtype Success
func handleURLResponse(_ response: HTTPURLResponse, data: Data) -> REST.ResponseHandlerResult<Success>
diff --git a/ios/MullvadREST/RESTTransportProvider.swift b/ios/MullvadREST/RESTTransportProvider.swift
index ed683fa163..5476338ece 100644
--- a/ios/MullvadREST/RESTTransportProvider.swift
+++ b/ios/MullvadREST/RESTTransportProvider.swift
@@ -12,14 +12,16 @@ public protocol RESTTransportProvider {
func makeTransport() -> RESTTransport?
}
-public struct AnyTransportProvider: RESTTransportProvider {
- private let block: () -> RESTTransport?
+extension REST {
+ public struct AnyTransportProvider: RESTTransportProvider {
+ private let block: () -> RESTTransport?
- public init(_ block: @escaping () -> RESTTransport?) {
- self.block = block
- }
+ public init(_ block: @escaping () -> RESTTransport?) {
+ self.block = block
+ }
- public func makeTransport() -> RESTTransport? {
- return block()
+ public func makeTransport() -> RESTTransport? {
+ return block()
+ }
}
}
diff --git a/ios/MullvadRESTTests/Mocks/AnyTransport.swift b/ios/MullvadRESTTests/Mocks/AnyTransport.swift
new file mode 100644
index 0000000000..b21a72b33f
--- /dev/null
+++ b/ios/MullvadRESTTests/Mocks/AnyTransport.swift
@@ -0,0 +1,93 @@
+//
+// AnyTransport.swift
+// MullvadRESTTests
+//
+// Created by pronebird on 25/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+@testable import MullvadREST
+import MullvadTypes
+
+/// Mock implementation of REST transport that can be used to handle requests without doing any actual networking.
+class AnyTransport: RESTTransport {
+ typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void
+
+ private let handleRequest: () -> AnyResponse
+
+ private let completionLock = NSLock()
+ private var completionHandlers: [UUID: CompletionHandler] = [:]
+
+ init(block: @escaping () -> AnyResponse) {
+ handleRequest = block
+ }
+
+ var name: String {
+ return "any-transport"
+ }
+
+ func sendRequest(
+ _ request: URLRequest,
+ completion: @escaping (Data?, URLResponse?, Error?) -> Void
+ ) -> Cancellable {
+ let response = handleRequest()
+ let id = storeCompletion(completionHandler: completion)
+
+ let dispatchWork = DispatchWorkItem {
+ let data = (try? response.encode()) ?? Data()
+ let httpResponse = HTTPURLResponse(
+ url: request.url!,
+ statusCode: response.statusCode,
+ httpVersion: "1.0",
+ headerFields: [:]
+ )!
+ self.sendCompletion(requestID: id, completion: .success((data, httpResponse)))
+ }
+
+ DispatchQueue.global().asyncAfter(deadline: .now() + response.delay, execute: dispatchWork)
+
+ return AnyCancellable {
+ dispatchWork.cancel()
+
+ self.sendCompletion(requestID: id, completion: .failure(URLError(.cancelled)))
+ }
+ }
+
+ private func storeCompletion(completionHandler: @escaping CompletionHandler) -> UUID {
+ return completionLock.withLock {
+ let id = UUID()
+ completionHandlers[id] = completionHandler
+ return id
+ }
+ }
+
+ private func sendCompletion(requestID: UUID, completion: Result<(Data, URLResponse), Error>) {
+ let complationHandler = completionLock.withLock {
+ return completionHandlers.removeValue(forKey: requestID)
+ }
+ switch completion {
+ case let .success((data, response)):
+ complationHandler?(data, response, nil)
+ case let .failure(error):
+ complationHandler?(nil, nil, error)
+ }
+ }
+}
+
+struct Response<T: Encodable>: AnyResponse {
+ var delay: TimeInterval
+ var statusCode: Int
+ var value: T
+
+ func encode() throws -> Data {
+ return try REST.Coding.makeJSONEncoder().encode(value)
+ }
+}
+
+protocol AnyResponse {
+ var delay: TimeInterval { get }
+ var statusCode: Int { get }
+
+ func encode() throws -> Data
+}
diff --git a/ios/MullvadRESTTests/Mocks/MemoryCache.swift b/ios/MullvadRESTTests/Mocks/MemoryCache.swift
new file mode 100644
index 0000000000..f01daa32b7
--- /dev/null
+++ b/ios/MullvadRESTTests/Mocks/MemoryCache.swift
@@ -0,0 +1,21 @@
+//
+// MemoryCache.swift
+// MullvadRESTTests
+//
+// Created by pronebird on 25/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+@testable import MullvadREST
+import MullvadTypes
+
+/// Mock implementation of a static memory cache passed to `AddressCache`.
+/// Since we don't do any actual networking in tests, the IP endpoint returned from cache is not important.
+struct MemoryCache: FileCacheProtocol {
+ func read() throws -> REST.StoredAddressCache {
+ return .init(updatedAt: .distantFuture, endpoint: .ipv4(IPv4Endpoint(ip: .loopback, port: 80)))
+ }
+
+ func write(_ content: REST.StoredAddressCache) throws {}
+}
diff --git a/ios/MullvadRESTTests/Mocks/TimeServerProxy.swift b/ios/MullvadRESTTests/Mocks/TimeServerProxy.swift
new file mode 100644
index 0000000000..8013b86e33
--- /dev/null
+++ b/ios/MullvadRESTTests/Mocks/TimeServerProxy.swift
@@ -0,0 +1,48 @@
+//
+// TimeServerProxy.swift
+// MullvadRESTTests
+//
+// Created by pronebird on 25/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+@testable import MullvadREST
+
+/// Simple API proxy used for testing purposes.
+final class TimeServerProxy: REST.Proxy<REST.ProxyConfiguration> {
+ init(configuration: REST.ProxyConfiguration) {
+ super.init(
+ name: "TimeServerProxy",
+ configuration: configuration,
+ requestFactory: REST.RequestFactory.withDefaultAPICredentials(
+ pathPrefix: "",
+ bodyEncoder: REST.Coding.makeJSONEncoder()
+ ),
+ responseDecoder: REST.Coding.makeJSONDecoder()
+ )
+ }
+
+ func getDateTime() -> any RESTRequestExecutor<TimeResponse> {
+ let requestHandler = REST.AnyRequestHandler { endpoint in
+ return try self.requestFactory.createRequest(endpoint: endpoint, method: .get, pathTemplate: "date-time")
+ }
+ let responseHandler = REST.defaultResponseHandler(decoding: TimeResponse.self, with: responseDecoder)
+
+ return makeRequestExecutor(
+ name: "get-date-time",
+ requestHandler: requestHandler,
+ responseHandler: responseHandler
+ )
+ }
+}
+
+struct TimeResponse: Codable {
+ var dateTime: Date
+}
+
+extension REST.ProxyFactory {
+ func createTimeServerProxy() -> TimeServerProxy {
+ return TimeServerProxy(configuration: configuration)
+ }
+}
diff --git a/ios/MullvadRESTTests/RequestExecutorTests.swift b/ios/MullvadRESTTests/RequestExecutorTests.swift
new file mode 100644
index 0000000000..e4ca6b9537
--- /dev/null
+++ b/ios/MullvadRESTTests/RequestExecutorTests.swift
@@ -0,0 +1,75 @@
+//
+// RequestExecutorTests.swift
+// MullvadRESTTests
+//
+// Created by pronebird on 25/08/2023.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+@testable import MullvadREST
+import MullvadTypes
+import XCTest
+
+final class RequestExecutorTests: XCTestCase {
+ let addressCache = REST.AddressCache(canWriteToCache: false, fileCache: MemoryCache())
+ var timerServerProxy: TimeServerProxy!
+
+ override func setUp() {
+ super.setUp()
+
+ let transportProvider = REST.AnyTransportProvider {
+ return AnyTransport {
+ return Response(delay: 1, statusCode: 200, value: TimeResponse(dateTime: Date()))
+ }
+ }
+
+ let proxyFactory = REST.ProxyFactory.makeProxyFactory(
+ transportProvider: transportProvider,
+ addressCache: addressCache
+ )
+ timerServerProxy = proxyFactory.createTimeServerProxy()
+ }
+
+ func testExecuteAsync() async throws {
+ _ = try await timerServerProxy.getDateTime().execute()
+ }
+
+ func testExecuteWithCompletionBlock() throws {
+ let expectation = self.expectation(description: "Wait for request to complete.")
+
+ _ = timerServerProxy.getDateTime().execute { result in
+ XCTAssertTrue(result.isSuccess)
+ expectation.fulfill()
+ }
+
+ waitForExpectations(timeout: 2)
+ }
+
+ func testCancelAsyncExecution() async throws {
+ let task = Task {
+ return try await timerServerProxy.getDateTime().execute()
+ }
+ task.cancel()
+
+ do {
+ _ = try await task.value
+ XCTFail("Should always throw OperationError.cancelled")
+ } catch {
+ XCTAssertTrue(error.isOperationCancellationError)
+ }
+ }
+
+ func testCancelExecutionWithCompletionBlock() {
+ let expectation = self.expectation(description: "Wait for request to complete.")
+
+ let cancellationToken = timerServerProxy.getDateTime().execute { result in
+ let isCancellationError = result.error?.isOperationCancellationError ?? false
+ XCTAssertTrue(isCancellationError)
+ expectation.fulfill()
+ }
+
+ cancellationToken.cancel()
+
+ waitForExpectations(timeout: 2)
+ }
+}
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 1795a2f8e4..ab803eb480 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -216,6 +216,7 @@
589C6A782A45AAB700DAD3EF /* tunnel_obfuscator_proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = 584023272A407679007B27AC /* tunnel_obfuscator_proxy.h */; settings = {ATTRIBUTES = (Private, ); }; };
589C6A7C2A45AE0100DAD3EF /* TunnelObfuscation.h in Headers */ = {isa = PBXBuildFile; fileRef = 589C6A7B2A45AE0100DAD3EF /* TunnelObfuscation.h */; settings = {ATTRIBUTES = (Public, ); }; };
589C6A7D2A45B06800DAD3EF /* TunnelObfuscation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5840231F2A406BF5007B27AC /* TunnelObfuscation.framework */; };
+ 589E76C02A9378F100E502F3 /* RESTRequestExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 589E76BF2A9378F100E502F3 /* RESTRequestExecutor.swift */; };
58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58A1AA8B23F5584B009F7EA6 /* ConnectionPanelView.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 */; };
@@ -234,11 +235,15 @@
58B26E282943527300D5980C /* SystemNotificationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E272943527300D5980C /* SystemNotificationProvider.swift */; };
58B26E2A2943545A00D5980C /* NotificationManagerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B26E292943545A00D5980C /* NotificationManagerDelegate.swift */; };
58B43C1925F77DB60002C8C3 /* TunnelControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B43C1825F77DB60002C8C3 /* TunnelControlView.swift */; };
+ 58B465702A98C53300467203 /* RequestExecutorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B4656F2A98C53300467203 /* RequestExecutorTests.swift */; };
58B8644629C7972F005E107C /* CustomDateComponentsFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5896AE83246D5889005B36CB /* CustomDateComponentsFormatting.swift */; };
58B93A1326C3F13600A55733 /* TunnelState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B93A1226C3F13600A55733 /* TunnelState.swift */; };
58B993B12608A34500BA7811 /* LoginContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58B993B02608A34500BA7811 /* LoginContentView.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 */; };
+ 58BDEB992A98F4ED00F578F2 /* AnyTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB982A98F4ED00F578F2 /* AnyTransport.swift */; };
+ 58BDEB9B2A98F58600F578F2 /* TimeServerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB9A2A98F58600F578F2 /* TimeServerProxy.swift */; };
+ 58BDEB9D2A98F69E00F578F2 /* MemoryCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BDEB9C2A98F69E00F578F2 /* MemoryCache.swift */; };
58BFA5C622A7C97F00A6173D /* RelayCacheTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5C522A7C97F00A6173D /* RelayCacheTracker.swift */; };
58BFA5CC22A7CE1F00A6173D /* ApplicationConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */; };
58C3A4B222456F1B00340BDB /* AccountInputGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */; };
@@ -1173,6 +1178,7 @@
589D28792846250500F9A7B3 /* OperationObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationObserver.swift; sourceTree = "<group>"; };
589D287F28462CB000F9A7B3 /* BackgroundObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackgroundObserver.swift; sourceTree = "<group>"; };
589D28812846306C00F9A7B3 /* GroupOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupOperation.swift; sourceTree = "<group>"; };
+ 589E76BF2A9378F100E502F3 /* RESTRequestExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTRequestExecutor.swift; sourceTree = "<group>"; };
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>"; };
@@ -1196,11 +1202,15 @@
58B26E272943527300D5980C /* SystemNotificationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemNotificationProvider.swift; sourceTree = "<group>"; };
58B26E292943545A00D5980C /* NotificationManagerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManagerDelegate.swift; sourceTree = "<group>"; };
58B43C1825F77DB60002C8C3 /* TunnelControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelControlView.swift; sourceTree = "<group>"; };
+ 58B4656F2A98C53300467203 /* RequestExecutorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestExecutorTests.swift; sourceTree = "<group>"; };
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 /* 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>"; };
+ 58BDEB982A98F4ED00F578F2 /* AnyTransport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyTransport.swift; sourceTree = "<group>"; };
+ 58BDEB9A2A98F58600F578F2 /* TimeServerProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeServerProxy.swift; sourceTree = "<group>"; };
+ 58BDEB9C2A98F69E00F578F2 /* MemoryCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryCache.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>"; };
58C3A4B122456F1A00340BDB /* AccountInputGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountInputGroupView.swift; sourceTree = "<group>"; };
@@ -1618,6 +1628,7 @@
06FAE66928F83CA30033DD93 /* RESTError.swift */,
06FAE66F28F83CA40033DD93 /* RESTNetworkOperation.swift */,
06FAE66E28F83CA40033DD93 /* RESTProxy.swift */,
+ 589E76BF2A9378F100E502F3 /* RESTRequestExecutor.swift */,
06FAE66728F83CA30033DD93 /* RESTProxyFactory.swift */,
06FAE66A28F83CA30033DD93 /* RESTRequestFactory.swift */,
06FAE67428F83CA40033DD93 /* RESTRequestHandler.swift */,
@@ -2215,6 +2226,16 @@
path = UI;
sourceTree = "<group>";
};
+ 58BDEB9E2A98F6B400F578F2 /* Mocks */ = {
+ isa = PBXGroup;
+ children = (
+ 58BDEB982A98F4ED00F578F2 /* AnyTransport.swift */,
+ 58BDEB9A2A98F58600F578F2 /* TimeServerProxy.swift */,
+ 58BDEB9C2A98F69E00F578F2 /* MemoryCache.swift */,
+ );
+ path = Mocks;
+ sourceTree = "<group>";
+ };
58C774C929AB543C003A1A56 /* Containers */ = {
isa = PBXGroup;
children = (
@@ -2507,6 +2528,8 @@
children = (
58FBFBE8291622580020E046 /* ExponentialBackoffTests.swift */,
A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */,
+ 58B4656F2A98C53300467203 /* RequestExecutorTests.swift */,
+ 58BDEB9E2A98F6B400F578F2 /* Mocks */,
);
path = MullvadRESTTests;
sourceTree = "<group>";
@@ -3552,6 +3575,7 @@
06799AF328F98E4800ACD94E /* RESTAuthenticationProxy.swift in Sources */,
06799AE628F98E4800ACD94E /* ServerRelaysResponse.swift in Sources */,
06799AF128F98E4800ACD94E /* RESTAPIProxy.swift in Sources */,
+ 589E76C02A9378F100E502F3 /* RESTRequestExecutor.swift in Sources */,
06799AE528F98E4800ACD94E /* HTTP.swift in Sources */,
A9D99B9A2A1F7C3200DE27D3 /* RESTTransport.swift in Sources */,
06799AE028F98E4800ACD94E /* RESTCoding.swift in Sources */,
@@ -4034,8 +4058,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 58B465702A98C53300467203 /* RequestExecutorTests.swift in Sources */,
A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */,
58FBFBE9291622580020E046 /* ExponentialBackoffTests.swift in Sources */,
+ 58FBFBF1291630700020E046 /* DurationTests.swift in Sources */,
+ 58BDEB9D2A98F69E00F578F2 /* MemoryCache.swift in Sources */,
+ 58BDEB9B2A98F58600F578F2 /* TimeServerProxy.swift in Sources */,
+ 58BDEB992A98F4ED00F578F2 /* AnyTransport.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift
index 930f700246..4c99cfd857 100644
--- a/ios/MullvadVPN/AppDelegate.swift
+++ b/ios/MullvadVPN/AppDelegate.swift
@@ -59,7 +59,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
addressCache.loadFromFile()
proxyFactory = REST.ProxyFactory.makeProxyFactory(
- transportProvider: AnyTransportProvider { [weak self] in
+ transportProvider: REST.AnyTransportProvider { [weak self] in
return self?.transportMonitor.makeTransport()
},
addressCache: addressCache