diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2023-08-21 12:38:20 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2023-08-29 14:31:39 +0200 |
| commit | 5e087240144bb64140b0d7e24bcd4f25a3506737 (patch) | |
| tree | 2f7f84b8fc0d1b00f3fa81cb0bb3b6800cddcc00 | |
| parent | 7a260eb36efe7395ad631765b758f30fc00e736c (diff) | |
| download | mullvadvpn-5e087240144bb64140b0d7e24bcd4f25a3506737.tar.xz mullvadvpn-5e087240144bb64140b0d7e24bcd4f25a3506737.zip | |
Introduce RESTRequestExecutor to support async flows
| -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/MullvadVPN.xcodeproj/project.pbxproj | 4 |
7 files changed, 150 insertions, 22 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..c03389f65d 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 either callback or async based 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 { + 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/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 1795a2f8e4..434327233b 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 */; }; @@ -1173,6 +1174,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>"; }; @@ -1618,6 +1620,7 @@ 06FAE66928F83CA30033DD93 /* RESTError.swift */, 06FAE66F28F83CA40033DD93 /* RESTNetworkOperation.swift */, 06FAE66E28F83CA40033DD93 /* RESTProxy.swift */, + 589E76BF2A9378F100E502F3 /* RESTRequestExecutor.swift */, 06FAE66728F83CA30033DD93 /* RESTProxyFactory.swift */, 06FAE66A28F83CA30033DD93 /* RESTRequestFactory.swift */, 06FAE67428F83CA40033DD93 /* RESTRequestHandler.swift */, @@ -3552,6 +3555,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 */, |
