summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2020-07-07 18:28:22 +0200
committerAndrej Mihajlov <and@mullvad.net>2020-07-07 18:28:22 +0200
commit8443875d3a7098082a1df1d882dc8908fe43e30a (patch)
tree2b49b7c0793b69fb87d1d3b1b712d1dee4f222ba
parent419dfade7c35b988ff575764a32bd0b51fd4738a (diff)
downloadmullvadvpn-8443875d3a7098082a1df1d882dc8908fe43e30a.tar.xz
mullvadvpn-8443875d3a7098082a1df1d882dc8908fe43e30a.zip
Uncombine MullvadRpc
-rw-r--r--ios/MullvadVPN/MullvadRpc.swift241
1 files changed, 167 insertions, 74 deletions
diff --git a/ios/MullvadVPN/MullvadRpc.swift b/ios/MullvadVPN/MullvadRpc.swift
index 95f1d36683..59c53348fe 100644
--- a/ios/MullvadVPN/MullvadRpc.swift
+++ b/ios/MullvadVPN/MullvadRpc.swift
@@ -8,7 +8,6 @@
import Foundation
import Network
-import Combine
/// API server URL
private let kMullvadAPIURL = URL(string: "https://api.mullvad.net/rpc/")!
@@ -73,7 +72,7 @@ class MullvadRpc {
}
/// An error type emitted by `MullvadRpc`
- enum Error: Swift.Error {
+ enum Error: ChainedError {
/// A network communication error
case network(URLError)
@@ -86,19 +85,19 @@ class MullvadRpc {
/// An error occured when encoding the JSON request
case encoding(Swift.Error)
- var localizedDescription: String {
+ var errorDescription: String? {
switch self {
- case .network(let urlError):
- return "Network error: \(urlError.localizedDescription)"
+ case .network:
+ return "Network error"
- case .server(let serverError):
- return "Server error: \(serverError.localizedDescription)"
+ case .server:
+ return "Server error"
- case .encoding(let encodingError):
- return "Encoding error: \(encodingError.localizedDescription)"
+ case .encoding:
+ return "Encoding error"
- case .decoding(let decodingError):
- return "Decoding error: \(decodingError.localizedDescription)"
+ case .decoding:
+ return "Decoding error"
}
}
}
@@ -108,119 +107,96 @@ class MullvadRpc {
return MullvadRpc(session: URLSession(configuration: .ephemeral))
}
+ class func makeJSONEncoder() -> JSONEncoder {
+ let encoder = JSONEncoder()
+ encoder.keyEncodingStrategy = .convertToSnakeCase
+ encoder.dateEncodingStrategy = .iso8601
+ encoder.dataEncodingStrategy = .base64
+ return encoder
+ }
+
+ class func makeJSONDecoder() -> JSONDecoder {
+ let decoder = JSONDecoder()
+ decoder.keyDecodingStrategy = .convertFromSnakeCase
+ decoder.dateDecodingStrategy = .iso8601
+ decoder.dataDecodingStrategy = .base64
+ return decoder
+ }
+
init(session: URLSession) {
self.session = session
}
- func createAccount() -> AnyPublisher<String, MullvadRpc.Error> {
+ func createAccount() -> MullvadRpc.Request<String> {
let request = JsonRpcRequest(method: "create_account", params: [])
- return makeDataTaskPublisher(request: request)
+ return MullvadRpc.Request(session: session, request: request)
}
- func getRelayList() -> AnyPublisher<RelayList, MullvadRpc.Error> {
+ func getRelayList() -> MullvadRpc.Request<RelayList> {
let request = JsonRpcRequest(method: "relay_list_v3", params: [])
- return makeDataTaskPublisher(request: request)
+ return MullvadRpc.Request(session: session, request: request)
}
- func getAccountExpiry(accountToken: String) -> AnyPublisher<Date, MullvadRpc.Error> {
+ func getAccountExpiry(accountToken: String) -> MullvadRpc.Request<Date> {
let request = JsonRpcRequest(method: "get_expiry", params: [AnyEncodable(accountToken)])
- return makeDataTaskPublisher(request: request)
+ return MullvadRpc.Request(session: session, request: request)
+ }
+
+ func getAccountExpiry(request: MullvadRpc.Request<Date>? = nil) -> MullvadRpc.Operation<Date> {
+ return MullvadRpc.Operation(request: request)
}
- func pushWireguardKey(accountToken: String, publicKey: Data) -> AnyPublisher<WireguardAssociatedAddresses, MullvadRpc.Error> {
+ func pushWireguardKey(accountToken: String, publicKey: Data) -> MullvadRpc.Request<WireguardAssociatedAddresses> {
let request = JsonRpcRequest(method: "push_wg_key", params: [
AnyEncodable(accountToken),
AnyEncodable(publicKey)
])
- return makeDataTaskPublisher(request: request)
+ return MullvadRpc.Request(session: session, request: request)
}
- func replaceWireguardKey(accountToken: String, oldPublicKey: Data, newPublicKey: Data) -> AnyPublisher<WireguardAssociatedAddresses, MullvadRpc.Error> {
+ func replaceWireguardKey(accountToken: String, oldPublicKey: Data, newPublicKey: Data) -> MullvadRpc.Request<WireguardAssociatedAddresses> {
let request = JsonRpcRequest(method: "replace_wg_key", params: [
AnyEncodable(accountToken),
AnyEncodable(oldPublicKey),
AnyEncodable(newPublicKey)
])
- return makeDataTaskPublisher(request: request)
+ return MullvadRpc.Request(session: session, request: request)
}
- func checkWireguardKey(accountToken: String, publicKey: Data) -> AnyPublisher<Bool, MullvadRpc.Error> {
+ func checkWireguardKey(accountToken: String, publicKey: Data) -> MullvadRpc.Request<Bool> {
let request = JsonRpcRequest(method: "check_wg_key", params: [
AnyEncodable(accountToken),
AnyEncodable(publicKey)
])
- return makeDataTaskPublisher(request: request)
+ return MullvadRpc.Request(session: session, request: request)
}
- func removeWireguardKey(accountToken: String, publicKey: Data) -> AnyPublisher<Bool, MullvadRpc.Error> {
+ func checkWireguardKey(request: MullvadRpc.Request<Bool>? = nil) -> MullvadRpc.Operation<Bool> {
+ return MullvadRpc.Operation(request: request)
+ }
+
+ func removeWireguardKey(accountToken: String, publicKey: Data) -> MullvadRpc.Request<Bool> {
let request = JsonRpcRequest(method: "remove_wg_key", params: [
AnyEncodable(accountToken),
AnyEncodable(publicKey)
])
- return makeDataTaskPublisher(request: request)
+ return MullvadRpc.Request(session: session, request: request)
}
- func sendAppStoreReceipt(accountToken: String, receiptData: Data) -> AnyPublisher<SendAppStoreReceiptResponse, MullvadRpc.Error> {
+ func sendAppStoreReceipt(accountToken: String, receiptData: Data) -> MullvadRpc.Request<SendAppStoreReceiptResponse> {
let request = JsonRpcRequest(method: "apple_payment", params: [
AnyEncodable(accountToken),
AnyEncodable(receiptData)
])
- return makeDataTaskPublisher(request: request)
- }
-
- private func makeDataTaskPublisher<T: Decodable>(request: JsonRpcRequest) -> AnyPublisher<T, MullvadRpc.Error> {
- return Just(request)
- .encode(encoder: Self.makeJSONEncoder())
- .mapError { MullvadRpc.Error.encoding($0) }
- .map { Self.makeURLRequest(httpBody: $0) }
- .flatMap {
- self.session.dataTaskPublisher(for: $0)
- .mapError { MullvadRpc.Error.network($0) }
- .flatMap { (data, httpResponse) in
- Just(data)
- .decode(type: JsonRpcResponse<T, ResponseCode>.self, decoder: Self.makeJSONDecoder())
- .mapError { MullvadRpc.Error.decoding($0) }
- .flatMap { (serverResponse) in
- // unwrap JsonRpcResponse.result
- serverResponse.result
- .mapError { MullvadRpc.Error.server($0) }
- .publisher
- }
- }
- }.eraseToAnyPublisher()
- }
-
- private static func makeURLRequest(httpBody: Data) -> URLRequest {
- var request = URLRequest(url: kMullvadAPIURL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: kNetworkTimeout)
- request.addValue("application/json", forHTTPHeaderField: "Content-Type")
- request.httpMethod = "POST"
- request.httpBody = httpBody
-
- return request
- }
-
- private static func makeJSONEncoder() -> JSONEncoder {
- let encoder = JSONEncoder()
- encoder.keyEncodingStrategy = .convertToSnakeCase
- encoder.dateEncodingStrategy = .iso8601
- encoder.dataEncodingStrategy = .base64
- return encoder
- }
-
- private static func makeJSONDecoder() -> JSONDecoder {
- let decoder = JSONDecoder()
- decoder.keyDecodingStrategy = .convertFromSnakeCase
- decoder.dateDecodingStrategy = .iso8601
- decoder.dataDecodingStrategy = .base64
- return decoder
+ return MullvadRpc.Request(session: session, request: request)
}
}
@@ -252,3 +228,120 @@ extension JsonRpcResponseError: LocalizedError
}
}
}
+
+
+extension MullvadRpc {
+
+ class Request<Response: Decodable> {
+ typealias RequestCompletionHandler = (Result<Response, MullvadRpc.Error>) -> Void
+
+ private let session: URLSession
+ private let request: JsonRpcRequest
+
+ private let lock = NSLock()
+ private var urlSessionTask: URLSessionTask?
+
+ fileprivate init(session: URLSession, request: JsonRpcRequest) {
+ self.session = session
+ self.request = request
+ }
+
+ func start(completionHandler: @escaping RequestCompletionHandler) {
+ lock.withCriticalBlock {
+ assert(self.urlSessionTask == nil)
+
+ switch makeURLRequest() {
+ case .success(let urlRequest):
+ let task = session.dataTask(with: urlRequest) { (responseData, urlResponse, error) in
+ switch (responseData, error) {
+ case (.some(let data), .none):
+ completionHandler(Self.decodeResponse(data))
+
+ case (.none, .some(let urlError as URLError)):
+ completionHandler(.failure(.network(urlError)))
+
+ default:
+ fatalError()
+ }
+ }
+ self.urlSessionTask = task
+ task.resume()
+
+ case .failure(let error):
+ completionHandler(.failure(error))
+ }
+ }
+ }
+
+ func cancel() {
+ lock.withCriticalBlock {
+ self.urlSessionTask?.cancel()
+ }
+ }
+
+ func operation() -> MullvadRpc.Operation<Response> {
+ return MullvadRpc.Operation(request: self)
+ }
+
+ private func makeURLRequest() -> Result<URLRequest, MullvadRpc.Error> {
+ do {
+ let data = try MullvadRpc.makeJSONEncoder().encode(request)
+
+ return .success(Self.makeURLRequest(httpBody: data))
+ } catch {
+ return .failure(.encoding(error))
+ }
+ }
+
+ private static func decodeResponse(_ responseData: Data) -> Result<Response, MullvadRpc.Error> {
+ do {
+ let serverResponse = try MullvadRpc.makeJSONDecoder()
+ .decode(JsonRpcResponse<Response, MullvadRpc.ResponseCode>.self, from: responseData)
+
+ // unwrap JsonRpcResponse.result
+ return serverResponse.result
+ .mapError { .server($0) }
+ } catch {
+ return .failure(.decoding(error))
+ }
+ }
+
+ private static func makeURLRequest(httpBody: Data) -> URLRequest {
+ var request = URLRequest(
+ url: kMullvadAPIURL,
+ cachePolicy: .useProtocolCachePolicy,
+ timeoutInterval: kNetworkTimeout
+ )
+ request.addValue("application/json", forHTTPHeaderField: "Content-Type")
+ request.httpMethod = "POST"
+ request.httpBody = httpBody
+
+ return request
+ }
+ }
+
+ class Operation<Response>: AsyncOperation, InputOperation, OutputOperation where Response: Decodable {
+ typealias Input = Request<Response>
+ typealias Output = Result<Response, MullvadRpc.Error>
+
+ init(request: Input? = nil) {
+ super.init()
+ self.input = request
+ }
+
+ override func main() {
+ guard let request = self.input else {
+ self.finish()
+ return
+ }
+
+ request.start { [weak self] (result) in
+ self?.finish(with: result)
+ }
+ }
+
+ override func operationDidCancel() {
+ input?.cancel()
+ }
+ }
+}