// // DataManager.swift // DocumentRepositoryMobile // // Created by Tobias on 23.10.18. // Copyright © 2018 Tobias. All rights reserved. // import Foundation import Future import os.log public protocol HttpResponseFilter { func handleResponse(request: Request, data: Data?, responseHandler: ResponseHandler?, sender: Any?, promise: Promise<Data?, ResponseError>) } public protocol HttpSuccessFilter { func handleResponse(data: Data?, response: HTTPURLResponse, responseHandler: ResponseHandler?, sender: Any?, promise: Promise<Data?, ResponseError>) } public protocol DataManagerDelegate { func manipulateRequest(request: Request) -> Request } public class DataManager: NSObject, URLSessionDelegate { public enum DataManagerError: Error { case authError } public struct Host: Equatable { public let `protocol`: String public let host: String public let port: Int public let context: String public init(`protocol`: String, host: String, port: Int, context: String) { self.protocol = `protocol` self.host = host self.port = port self.context = context } } private let timeout: TimeInterval public private(set) var host: Host private lazy var urlSession: URLSession = { URLSession(configuration: .default, delegate: self, delegateQueue: OperationQueue.main) }() private var responseOkFilter: HttpSuccessFilter private var responseFilters: [Int: HttpResponseFilter] = [:] public var delegate: DataManagerDelegate? public init(host: Host, responseOkFilter: HttpSuccessFilter, responseFilters: [Int: HttpResponseFilter] = [:], timeout: TimeInterval = 10.0) { self.host = host self.responseOkFilter = responseOkFilter self.responseFilters = responseFilters self.timeout = timeout super.init() } public func url(username: String = "", password: String = "", path: String) -> String { var url: String = host.protocol if !username.isEmpty || !password.isEmpty { url += username + ":" + password + "@" } url += "\(host.host):\(host.port)\(host.context)" url += path return url } @discardableResult public func request(request r: Request, responseHandler: ResponseHandler? = nil, sender: Any? = nil, promise: Promise<Data?, ResponseError> = Promise<Data?, ResponseError>()) -> Future<Data?, ResponseError> { if #available(iOS 14.0, *) { os_log("Request: \(r)") } else { print("Request: \(r)") } let request = delegate?.manipulateRequest(request: r) ?? r if var req: URLRequest = createUrlRequest(request: request) { if let authentication = request.authentication { if let headerKey = authentication.headerKey, let headerValue = authentication.headerValue { req.setValue(headerValue, forHTTPHeaderField: headerKey) } } let task = urlSession.dataTask(with: req) { data, response, error in if let error: NSError = error as NSError? { promise.fail(error: self.determineResponseError(error)) return } guard let response: HTTPURLResponse = response as? HTTPURLResponse else { promise.fail(error: ResponseError.networkError(error: .unknown())) return } if response.is2xx { self.responseOkFilter.handleResponse(data: data, response: response, responseHandler: responseHandler, sender: sender, promise: promise) } else if let filter = self.responseFilters[response.statusCode] { filter.handleResponse(request: request, data: data, responseHandler: responseHandler, sender: sender, promise: promise) } else { promise.fail(error: ResponseError.getErrorForStatusCode(code: response.statusCode)) } } task.resume() } return promise.future } private func createUrlRequest(request: Request) -> URLRequest? { if let url: URL = request.constructUrl() { var req: URLRequest = URLRequest(url: url) req.httpMethod = request.method.rawValue if let payload = request.payload { req.httpBody = payload req.setValue(request.contentType, forHTTPHeaderField: "Content-Type") req.setValue("application/json", forHTTPHeaderField: "Accept") } req.timeoutInterval = self.timeout return req } return nil } fileprivate func determineResponseError(_ error: NSError) -> ResponseError { if error.code == NSURLErrorNotConnectedToInternet { return ResponseError.networkError(error: .noInternet) } else if error.code == NSURLErrorTimedOut { return ResponseError.networkError(error: .timeout) } else { return ResponseError.networkError(error: .unknown(error: error)) } } public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!)) } }