summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2023-08-21 12:38:20 +0200
committerAndrej Mihajlov <and@mullvad.net>2023-08-29 14:31:39 +0200
commit5e087240144bb64140b0d7e24bcd4f25a3506737 (patch)
tree2f7f84b8fc0d1b00f3fa81cb0bb3b6800cddcc00
parent7a260eb36efe7395ad631765b758f30fc00e736c (diff)
downloadmullvadvpn-5e087240144bb64140b0d7e24bcd4f25a3506737.tar.xz
mullvadvpn-5e087240144bb64140b0d7e24bcd4f25a3506737.zip
Introduce RESTRequestExecutor to support async flows
-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/MullvadVPN.xcodeproj/project.pbxproj4
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 */,