diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2023-08-29 15:12:06 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2023-08-29 15:12:06 +0200 |
| commit | 90934dd1b94039fe5b1fc249485c07aee00be90e (patch) | |
| tree | ff5bb0b31904a27a7fcf22af15af95ab9c2795fa | |
| parent | 7a260eb36efe7395ad631765b758f30fc00e736c (diff) | |
| parent | 5269ced5a5cd3b323fc20c29d7105bcefd30d443 (diff) | |
| download | mullvadvpn-90934dd1b94039fe5b1fc249485c07aee00be90e.tar.xz mullvadvpn-90934dd1b94039fe5b1fc249485c07aee00be90e.zip | |
Merge branch 'async-rest'
| -rw-r--r-- | ios/MullvadREST/RESTAPIProxy.swift | 23 | ||||
| -rw-r--r-- | ios/MullvadREST/RESTAccountsProxy.swift | 22 | ||||
| -rw-r--r-- | ios/MullvadREST/RESTNetworkOperation.swift | 6 | ||||
| -rw-r--r-- | ios/MullvadREST/RESTProxy.swift | 86 | ||||
| -rw-r--r-- | ios/MullvadREST/RESTRequestExecutor.swift | 29 | ||||
| -rw-r--r-- | ios/MullvadREST/RESTResponseHandler.swift | 2 | ||||
| -rw-r--r-- | ios/MullvadREST/RESTTransportProvider.swift | 16 | ||||
| -rw-r--r-- | ios/MullvadRESTTests/Mocks/AnyTransport.swift | 93 | ||||
| -rw-r--r-- | ios/MullvadRESTTests/Mocks/MemoryCache.swift | 21 | ||||
| -rw-r--r-- | ios/MullvadRESTTests/Mocks/TimeServerProxy.swift | 48 | ||||
| -rw-r--r-- | ios/MullvadRESTTests/RequestExecutorTests.swift | 75 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 29 | ||||
| -rw-r--r-- | ios/MullvadVPN/AppDelegate.swift | 2 |
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 |
