networkmanager
unknown
swift
2 years ago
105 kB
1
Indexable
Never
// // NetworkManager.swift // eToto // // Created by Hristo Hristov on 4.09.20. // Copyright © 2020 Mobile Wave Solutions. All rights reserved. // import UIKit enum SBBErrorCodes: Int { case LogoutUser = 421 case SingleSlipToAcceptForTrader = 30 case MultiSlipToAcceptForTrader = 31 } extension Notification.Name { static let authenticate = Self.init("authenticate") } class NetworkManager: NSObject, NetworkManagerType { private let cache = ImageCache() static var shared = NetworkManager() private var session: URLSession { URLSession.shared } private lazy var temporarySession = URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil) private lazy var logInCompletions: [GetUserCompletion] = [] private(set) lazy var user = Observer<User?>(nil) private(set) lazy var fastBettingImage = Observer<UIImage?>(nil) private(set) var lastUser: User? { didSet { Storage.etoto.userID = lastUser?.id } } private var keepAliveTimer: Timer? private var updateBalanceTimer: Timer? private var updateEventsCacheTimer: Timer? private var footballEventsCache: [Event]? private var refreshChatOnChangeTask: URLSessionDataTask? private var refreshChatTask: URLSessionDataTask? private(set) var sessionID: String? //TODO: Can I create user property during registration process? private override init() { super.init() NotificationCenter.default.addObserver(self, selector: #selector(sentEvent(_:)), name: .sentEvent, object: UIApplication.shared) NotificationCenter.default.addObserver(self, selector: #selector(invalidateFootballCache), name: UIApplication.willResignActiveNotification, object: nil) startUpdateEventsCacheTimer() user.observe { [weak self] (new, _) in self?.startKeepAliveTimer() self?.startUpdateBalanceTimer() if new != nil { self?.lastUser = new } } } deinit { NotificationCenter.default.removeObserver(self) } @objc private func sentEvent(_ notification: Notification) { guard user.value != nil else { return } startKeepAliveTimer() } func invalidateTimers() { if updateEventsCacheTimer != nil { updateEventsCacheTimer?.invalidate() updateEventsCacheTimer = nil } } func initializeTimers() { startUpdateEventsCacheTimer() } private func startKeepAliveTimer() { DispatchQueue.main.async { [weak self] in if self?.keepAliveTimer != nil { self?.keepAliveTimer?.invalidate() self?.keepAliveTimer = nil } guard self?.user.value != nil else { return } self?.keepAliveTimer = Timer.scheduledTimer(withTimeInterval: 60 * 3, repeats: false) { (_) in self?.keepAlive() { self?.startKeepAliveTimer() } } } } private func startUpdateBalanceTimer() { DispatchQueue.main.async { [weak self] in if self?.updateBalanceTimer != nil { self?.updateBalanceTimer?.invalidate() self?.updateBalanceTimer = nil } guard self?.user.value != nil else { return } self?.updateBalanceTimer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false) { (_) in self?.updateBalance() { self?.startUpdateBalanceTimer() } } } } private func startUpdateEventsCacheTimer() { DispatchQueue.main.async { [weak self] in if self?.updateEventsCacheTimer != nil { self?.updateEventsCacheTimer?.invalidate() self?.updateEventsCacheTimer = nil } self?.updateEventsCacheTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: true) { (_) in self?.cacheFootballEvents() } self?.updateEventsCacheTimer?.fire() } } typealias ResponseBody = (data: Data, json: Any?) private func handle(request: URLRequest, data: Data?, response: URLResponse?, error: Error?) -> Result<ResponseBody, Error>? { let successRange = 200...299 if request.allHTTPHeaderFields?.contains(where: { $0.key == User.Key.sessionID.rawValue }) == true { startKeepAliveTimer() } if let code = (error as? URLError)?.code, case .cancelled = code { return nil } guard let statusCode = (response as? HTTPURLResponse)?.statusCode else { return .failure(error ?? URLError(.badServerResponse)) } guard let data = data else { return .failure(URLError(.badServerResponse)) } let json = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] switch statusCode { case 200...299: if let codeInBody = json?["code"] as? Int, !successRange.contains(codeInBody) { switch codeInBody { case SBBErrorCodes.MultiSlipToAcceptForTrader.rawValue, SBBErrorCodes.SingleSlipToAcceptForTrader.rawValue: let error = NSError(domain: "", code: codeInBody, userInfo: nil) as Error return .failure(error) case SBBErrorCodes.LogoutUser.rawValue: // Do not logout if there is no valid user // Otherwise the sessionID after mobile/user-validation/registrations is cleared // and mobile/user-validation/details fails if self.user.value?.id != nil { logoutUser { (_) in } } return nil case 423: // Password reset returns 423 when the email is invalid or isn't in the system. // When the email is invalid we get a description: Provided data is invalid. (email) // When the email isn't in the system we get a description: Provided data is invalid. (customer-not-found) // Presenting an error for email that isn't in the database would cause a security bridge so we have to handle this error as 'success' guard let desctiption = json?["description"] as? String else { return .failure(NSError(domain: "", code: codeInBody, userInfo: nil) as Error) } let error = EtotoAPIError(description: desctiption) return .failure(error) case 600: if let jsonData = (json?["data"] as? [[String: Any]]), let betslipErrorInBody = jsonData[safe: 0]?["betSlipErrorCode"] as? Int { if betslipErrorInBody == 46 { let mtsErrorDictionary = (jsonData[safe: 0]?["errorDetails"] as? [String: Any]) let mtsError = mtsErrorDictionary?["mtsErrorCode"] as? String let error = NSError(domain: "", code: Int(mtsError ?? "0") ?? 0, userInfo: ["type":"MTS"]) as Error return .failure(error) } let error = NSError(domain: "", code: betslipErrorInBody, userInfo: nil) as Error return .failure(error) } else { return .failure(URLError(.badServerResponse)) } case 200...299: return .success((data, json?["data"])) default: let apiError = try? JSONDecoder().decode(APIError.self, from: data) return .failure(apiError ?? URLError(.badServerResponse)) } } return .success((data, json?["data"])) case 600: return .failure(error ?? URLError(.badServerResponse)) case 421: user.value = nil return nil case 401: return .failure(URLError(.userAuthenticationRequired)) case 400...599: let apiError = try? JSONDecoder().decode(APIError.self, from: data) return .failure(apiError ?? URLError(.badServerResponse)) default: return .failure(error ?? URLError(.badServerResponse)) } } //MARK: - Betting @discardableResult func checkTradersDecision(completion: @escaping CheckTradersDecision) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.approvedSlips(sessionID: user.value?.sessionID) ) else { return nil } return checkTradersDecision(request: request, completion: completion) } private func checkTradersDecision(request: URLRequest, completion: @escaping CheckTradersDecision) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): if let json = body.json as? [String : Any] { completion(.success(json)) } case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func approveTraderChange(approved: Bool, completion: @escaping ApproveTraderChange) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.approveTraderChange(decision: approved, sessionID: user.value?.sessionID) ) else { return nil } return approveTraderChange(request: request, completion: completion) } private func approveTraderChange(request: URLRequest, completion: @escaping ApproveTraderChange) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(_ ): completion(.success("newOdds")) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func getLiveOdds(odds: [String], completion: @escaping GetLiveOddsCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoLiveBettingAPI(), endpoint: ETotoLiveBettingAPI.Path.refreshOdds(odds: odds) ) else { return nil } return getLiveOdds(request: request, completion: completion) } private func getLiveOdds(request: URLRequest, completion: @escaping GetLiveOddsCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): if let newOdds = body.json as? [[String : Any]] { completion(.success(newOdds)) } else { completion(.failure(URLError(.cannotParseResponse))) } case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func virtualsHistory(from: Date, to: Date, completion: @escaping (Result<[PlacedBet]?, Error>) -> Void) -> URLSessionWork? { guard let request = URLRequest(api: ETotoBetHistoryAPI(), endpoint: ETotoBetHistoryAPI.Path.virtualsHistory(from: from, to: to, sessionID: user.value?.sessionID)) else { return nil } let work = URLSessionWork() let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let json = try? JSONSerialization.jsonObject(with: body.data, options: []) as? [String: Any], let bodyJson = json["body"] else { completion(.failure(URLError(.cannotParseResponse))) return } let bets = PlacedBet.virtualPlacedBets(bettingHistory: bodyJson as? [String: [String: Any]]) guard let ids = bets?.compactMap({ $0.id }) else { return completion(.success(bets)) } work.append(self?.cashoutAmounts(bet: ids) { result in if case .success(let amounts) = result { amounts?.forEach({ (id, amount) in bets?.first(where: { $0.id == id })?.cashout = amount }) } completion(.success(bets)) }) case .failure(let error): completion(.failure(error)) case .none: break } } work.append(task) task.resume() return work } @discardableResult func resetPassword(email: String, completion: @escaping ResetPasswordCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.resetPassword(email: email)) else { return nil } return resetPassword(request: request, completion: completion) } private func resetPassword(request: URLRequest, completion: @escaping ResetPasswordCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(_): completion(.success(())) case .failure(let error): guard let error = error as? EtotoAPIError else { completion(.failure(.unknown)) return } completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func notifyTraderForBetWith(body: [String: Any], completion: @escaping PlaceBetCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoLiveBettingAPI.Path.notifyTraderForBetWith(body: body, sessionID: user.value?.sessionID) ) else { return nil } return notifyTraderForBetWith(request: request, completion: completion) } private func notifyTraderForBetWith(request: URLRequest, completion: @escaping PlaceBetCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): completion(.success(body.json as? [String: Any] ?? [String: Any]())) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func placeBet(body: [String: Any], completion: @escaping PlaceBetCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoLiveBettingAPI.Path.placeBet(body: body, sessionID: user.value?.sessionID)) else { return nil } return placeBet(request: request, completion: completion) } private func placeBet(request: URLRequest, completion: @escaping PlaceBetCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): completion(.success(body.json as? [String: Any] ?? [String: Any]())) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func getFreebets(completion: @escaping FreebetCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID else { return nil } guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.freebets(sessionID: sessionID)) else { return nil } return getFreebets(request: request, completion: completion) } private func getFreebets(request: URLRequest, completion: @escaping FreebetCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): if let bodyJson = body.json as? [String: Any], let freebetsJson = bodyJson["active"] as? [[String: Any]] { let freebets = freebetsJson.compactMap { (input: [String: Any] ) in return Freebet(with: input) } completion( .success(freebets) ) } else { completion(.failure(URLError(.cannotParseResponse))) } case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func cancelAllCreditBonus(completion: @escaping CancelAllCreditBonusCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID else { return nil } guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.cancelAllCreditBonus(sessionID: sessionID)) else { return nil } return cancelAllCreditBonus(request: request, completion: completion) } private func cancelAllCreditBonus(request: URLRequest, completion: @escaping CancelAllCreditBonusCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): if let bodyJson = body.json as? [String: Any] { completion( .success(bodyJson) ) } else { completion(.failure(URLError(.cannotParseResponse))) } case .failure(let error): completion(.failure(error)) default: break } } task.resume() return task } @discardableResult func getCarouselSegments(completion: @escaping GetCarouselSegmentsCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID else { return nil } guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.carouselSegments(sessionID: sessionID)) else { return nil } return getCarouselSegments(request: request, completion: completion) } private func getCarouselSegments(request: URLRequest, completion: @escaping GetCarouselSegmentsCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): guard let model = try? JSONDecoder().decode(GetCarouselSegmentsModel.self, from: body.data) else { completion(.failure(URLError(.cannotParseResponse))) return } completion(.success(model)) case .failure(let error): completion(.failure(error)) default: break } } task.resume() return task } @discardableResult func getTaxFactor(body: [String: Any], completion: @escaping GetTaxFactorCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoLiveBettingAPI.Path.calculateTaxFactor(body: body, sessionID: user.value?.sessionID)) else { return nil } return getTaxFactor(request: request, completion: completion) } private func getTaxFactor(request: URLRequest, completion: @escaping GetTaxFactorCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): if let taxFactor = body.json as? Double { completion(.success(taxFactor)) } else { completion(.failure(URLError(.cannotParseResponse))) } case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func getPotentialWinnings(body: [String: Any], completion: @escaping GetPotentialWinningCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoLiveBettingAPI.Path.calculateWinnings(body: body)) else { return nil } return getPotentialWinnings(request: request, completion: completion) } private func getPotentialWinnings(request: URLRequest, completion: @escaping GetPotentialWinningCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): completion(.success(body.json as? [String: Any] ?? [String: Any]())) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } //MARK: - Categories @discardableResult func getCategories(completion: @escaping GetCategoriesCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.categories) else { return nil } return getCategories(request: request, completion: completion) } @discardableResult func getSubCategories(category: Int16, completion: @escaping GetCategoriesCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.subCategories(category: category)) else { return nil } return getCategories(request: request, completion: completion) } private func getCategories(request: URLRequest, completion: @escaping GetCategoriesCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): let categories = SportCategory.categories(dictionaries: body.json as? [[String : Any]]) completion(.success(categories)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func getEvent(eventID: Int, completion: @escaping GetEventCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.event(eventID: eventID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let json = body.json as? [String: Any] else { return completion(.failure(URLError(.cannotParseResponse))) } completion(.success(Event(json))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func getEvents(category: Int16, completion: @escaping GetEventsCompletion<Event>) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.eventsForCategory(category: category)) else { return nil } return getEvents(request: request, completion: completion) } @discardableResult func getEvents(categories: [Int16], completion: @escaping GetEventsCompletion<Event>) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.eventsForCategories(categories: categories)) else { return nil } return getEvents(request: request, completion: completion) } private func getEvents(request: URLRequest, completion: @escaping GetEventsCompletion<Event>) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): let events = Event.events(dictionaries: body.json as? [[String: Any]]) completion(.success(Filters.filterOutShouldBeHidden(events))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } func getImage(categoryID: String, settings: Settings = Settings.etoto, completion: @escaping (UIImage?) -> ()) -> URLSessionDataTask? { guard let url = settings.categoryIcons[categoryID]?.url else { completion(UIImage(named: "sportIconsUniversalSport")) return nil } if let image = cache.image(for: url) { completion(image) return nil } let request = URLRequest(url: url) let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): if let image = UIImage(data: body.data) { self?.cache.insertImage(image, for: url) DispatchQueue.main.async { completion(image) } } case .failure: DispatchQueue.main.async { completion(nil) } case .none: break } } task.resume() return task } @discardableResult func getEvents(withSearchPattern pattern: String, completion: @escaping GetOfferEventsCompletion<Event,LiveEvent>) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.quickSearchEvents(searchPattern: pattern)) else { completion(.failure(CocoaError(.userCancelled))) return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): let events = Event.events(searchResult: body.json as? [[String : Any]]) completion(.success(events)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func getPopularCategories(completion: @escaping GetPopularCategoriesCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.popularCategories) else { return nil } return getPopularCategories(request: request, completion: completion) } private func getPopularCategories(request: URLRequest, completion: @escaping GetPopularCategoriesCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let model = try? JSONDecoder().decode(PopularCategoryResponseModel.self, from: body.data) else { completion(.failure(URLError(.cannotParseResponse))) return } completion(.success(model.data)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } //MARK: - Live - @discardableResult func getLiveSports(completion: @escaping GetCategoriesCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoLiveBettingAPI(), endpoint: ETotoLiveBettingAPI.Path.liveBettingCategories) else { return nil } return getLiveSports(request: request, completion: completion) } private func getLiveSports(request: URLRequest, completion: @escaping GetCategoriesCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): let categories = SportCategory.categories(dictionaries: body.json as? [[String : Any]])? .filter{ $0.level == 1 && ($0.eventsCount ?? 0) > 0} .sorted(by: { $0.sortOrder < $1.sortOrder }) completion(.success(categories)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func getLiveEvents(sportID: Int16, completion: @escaping GetEventsCompletion<LiveEvent>) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoLiveBettingAPI(), endpoint: ETotoLiveBettingAPI.Path.eventsForCategory(category: sportID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): completion(.success(LiveEvent.events(result: body.json as? [[String : Any]]))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func getLiveEventsForDate(dateString: String, completion: @escaping GetEventsCompletion<Event>) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.eventWithDate(date: dateString)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let events = Event.events(dictionaries: body.json as? [[String : Any]]) else { return } completion(.success(events)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func getLiveEvent(eventID: Int, completion: @escaping GetEventCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoLiveBettingAPI(), endpoint: ETotoLiveBettingAPI.Path.event(event: eventID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let eventDic = (body.json as? [Any])?.first as? [String : AnyObject] else { completion(.failure(URLError(.cannotParseResponse))) return } completion(.success(LiveEvent(eventDic))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } // MARK: - StatScore - @discardableResult func getStatScoreEventMapping(completion: @escaping GetStatScoreEventMappingCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.eventsStatScore) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let dictionaries = body.json as? StatScoreMapping else { completion(.failure(URLError(.cannotParseResponse))) return } completion(.success(dictionaries)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } //MARK: - User - // All, when completed, should reset the keep alive timer private lazy var isUserLoggingIn = false @discardableResult func loginUser(name: String?, password: String?, completion: GetUserCompletion?) -> URLSessionDataTask? { logInCompletions.appendOptional(completion) guard !isUserLoggingIn, let name = name, let password = password, let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.loginUser(name: name, password: password)) else { return nil } isUserLoggingIn = true let task = session.dataTask(with: request) { [weak self] (data, response, error) in self?.isUserLoggingIn = false switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): var dictionary = body.json as? [String : Any] dictionary?[User.Key.sessionID.rawValue] = (response as? HTTPURLResponse)?.allHeaderFields[User.Key.sessionID.rawValue] guard let user = User(dictionary: dictionary) else { self?.logInCompletions.forEach{ $0(.failure(URLError(.cannotParseResponse))) } self?.logInCompletions.removeAll() return } self?.logInCompletions.forEach{ $0(.success(user)) } self?.logInCompletions.removeAll() self?.forceUpdateBalance(force: false, completion: nil) case .failure(let error): log.error(error.localizedDescription) self?.logInCompletions.forEach{ $0(.failure(error)) } self?.logInCompletions.removeAll() case .none: break } } task.resume() return task } @discardableResult func loggedInUser(completion: GetUserCompletion?) -> URLSessionDataTask? { if let user = user.value { completion?(.success(user)) return nil } //TODO: ("Uncomment for autologin") return nil // guard !isUserLoggingIn else { // logInCompletions.appendOptional(completion) // return nil // } // guard let credentials = KeyChain.rememberedUserCredentials else { // completion?(.failure(URLError(.userAuthenticationRequired))) // return nil // } // return loginUser(name: credentials.name, // password: credentials.password, // completion: completion) } @discardableResult func loggedInUser() -> URLSessionDataTask? { return loggedInUser(completion: nil) } func keepAlive(completion: @escaping () -> ()) { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.keepAlive(sessionID: sessionID)) else { return } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): if let user = User(dictionary: body.json as? [String : Any]) { user.sessionID = self?.user.value?.sessionID self?.user.value = user } completion() case .failure(let error): print(error.localizedDescription) case .none: break } } task.resume() } private func updateBalance(completion: @escaping () -> ()) { updateBalance(force: false, immediately: false, completion: completion) } @discardableResult func forceUpdateBalance(force: Bool, completion: (() -> ())? = nil) -> URLSessionDataTask? { return updateBalance(force: force, immediately: true, completion: completion) } @discardableResult private func updateBalance(force: Bool, immediately: Bool, completion: (() -> ())? = nil) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID else { return nil } let endpoint: EndPointType if immediately { endpoint = ETotoAPI.Path.forceUpdateBalance(force: force, sessionID: sessionID) } else { endpoint = ETotoAPI.Path.updateBalance(sessionID: sessionID) } guard let request = URLRequest(api: ETotoAPI(), endpoint: endpoint) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): if let data = body.json as? [String : Any], let bonusData = data["creditBonusBalance"] as? [String : Any], let balance = data["balance"] as? Double { if let bonus = bonusData["amount"] as? Double { self?.user.value?.bonusAmount = bonus } else { self?.user.value?.bonusAmount = 0 } self?.user.value?.balance.value = balance } completion?() case .failure(let error): print(error.localizedDescription) case .none: break } } task.resume() return task } @discardableResult func logoutUser(completion: @escaping (Result<Bool, Error>) -> ()) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.logoutUser(sessionID: sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in //TODO: ("Move to the success case once error handling is decided upon") self?.user.value = nil switch self?.handle(request: request, data: data, response: response, error: error) { case .success: completion(.success(true)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func getUserDetails(completion: @escaping GetUserDetailsCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: UserProfileAPI(), endpoint: UserProfileAPI.Path.getUserDetails(sessionID: sessionID)) else { completion(.failure(CustomError(type: .genericError))) return nil } #if RELEASE let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success((let data, _)): let response = try? JSONDecoder().decode(UserDetails.self, from: data) completion(.success(response)) case .failure(let error): completion(.failure(error)) case .none: break } } #elseif APPSTORE let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success((let data, _)): let response = try? JSONDecoder().decode(UserDetails.self, from: data) completion(.success(response)) case .failure(let error): completion(.failure(error)) case .none: break } } #elseif DEBUG let task = temporarySession.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success((let data, _)): let response = try? JSONDecoder().decode(UserDetails.self, from: data) completion(.success(response)) case .failure(let error): completion(.failure(error)) case .none: break } } #endif task.resume() return task } func postUserDetails(userDetails: UserDetails.User, completion: @escaping GetEmptyCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: UserProfileAPI(), endpoint: UserProfileAPI.Path.postUserDetails(userDetails: userDetails, sessionID: sessionID)) else { completion(.failure(CustomError(type: .genericError))) return nil } #if RELEASE let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success: completion(.success(())) case .failure(let error): completion(.failure(error)) case .none: break } } #elseif APPSTORE let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success: completion(.success(())) case .failure(let error): completion(.failure(error)) case .none: break } } #elseif DEBUG let task = temporarySession.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success: completion(.success(())) case .failure(let error): completion(.failure(error)) case .none: break } } #endif task.resume() return task } func getDocuments(completion: @escaping GetDocumentsCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: UserProfileAPI(), endpoint: UserProfileAPI.Path.getDocuments(sessionID: sessionID)) else { completion(.failure(CustomError(type: .genericError))) return nil } #if RELEASE let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): do { let documentList = try JSONDecoder().decode(DocumentList.self, from: body.data) completion(.success(documentList.receivedUserDocuments)) } catch { completion(.failure(error)) } case .failure(let error): completion(.failure(error)) case .none: break } } #elseif APPSTORE let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): do { let documentList = try JSONDecoder().decode(DocumentList.self, from: body.data) completion(.success(documentList.receivedUserDocuments)) } catch { completion(.failure(error)) } case .failure(let error): completion(.failure(error)) case .none: break } } #elseif DEBUG let task = temporarySession.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): do { let documentList = try JSONDecoder().decode(DocumentList.self, from: body.data) completion(.success(documentList.receivedUserDocuments)) } catch { completion(.failure(error)) } case .failure(let error): completion(.failure(error)) case .none: break } } #endif task.resume() return task } func manulalUserConversion(userManualConversion: UserManualConversion, completion: @escaping GetEmptyCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: UserProfileAPI(), endpoint: UserProfileAPI.Path.manualConversion(userManualConversion: userManualConversion, sessionID: sessionID)) else { completion(.failure(CustomError(type: .genericError))) return nil } #if RELEASE let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success: completion(.success(())) case .failure(let error): completion(.failure(error)) case .none: break } } #elseif APPSTORE let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success: completion(.success(())) case .failure(let error): completion(.failure(error)) case .none: break } } #elseif DEBUG let task = temporarySession.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success: completion(.success(())) case .failure(let error): completion(.failure(error)) case .none: break } } #endif task.resume() return task } @discardableResult func changePassword(new: String, old: String, completion: @escaping (Result<Bool, Error>) -> ()) -> URLSessionDataTask? { guard let userID = user.value?.id, let sessionID = user.value?.sessionID, let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.changePassword(new: new, old: old, userID: userID, sessionID: sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success: completion(.success(true)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } func transactions(from: Date, to: Date, pageSize size: Int?, offset: Int?, completion: @escaping (Result<[Transaction]?, Error>) -> Void) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.transactions(from: from, to: to, pageSize: size, offset: offset, sessionID: user.value?.sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): completion(.success(Transaction.transactions(dictionaries: body.json as? [[String: Any]]))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } func transactionsWithdrawStatus(pageSize size: Int?, offset: Int?, completion: @escaping (Result<[Transaction]?, Error>) -> Void) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.transactionsWithdrawStatus(pageSize: size, offset: offset, sessionID: user.value?.sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): completion(.success(Transaction.transactions(dictionaries: body.json as? [[String: Any]]))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } func cancelTransactionWithdrawal(transactionID: Int?, completion: @escaping (Result<Void, Error>) -> ()) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.cancelTransactionWithdrawal(transactionID: transactionID, sessionID: user.value?.sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success: completion(.success(())) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func placedBets(from: Date, to: Date, size: Int, status: PlacedBet.Status?, offset: Int, completion: @escaping (Result<([PlacedBet]?, Int?), Error>) -> Void) -> URLSessionWork? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.bettingHistory(from: from, to: to, size: size, type: status, offset: offset, sessionID: user.value?.sessionID)) else { return nil } let work = URLSessionWork() let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): let betsAndTheirIDs = PlacedBet.sAndTheirIDs(from: body.json as? [[String: Any]]) let bets = betsAndTheirIDs.bets var count: Int? = nil if let responseBody = body.json as? [[String: Any]], responseBody.count > 0, let totalCount = responseBody[0]["transactionsCount"] as? Int { count = totalCount } guard let ids = betsAndTheirIDs.ids else { return completion(.success((bets,count))) } work.append(self?.cashoutAmounts(bet: ids) { result in if case .success(let amounts) = result { amounts?.forEach({ (id, amount) in bets?.first(where: { $0.id == id })?.cashout = amount }) } completion(.success((bets,count))) }) case .failure(let error): completion(.failure(error)) case .none: break } } work.append(task) task.resume() return work } @discardableResult func cashoutAmounts(bet ids: [Int], completion: @escaping (Result<[Int: Double]?, Error>) -> ()) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.cashoutAmounts(betIDs: ids, sessionID: user.value?.sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): var amounts: [Int: Double]? (body.json as? [String: [String: Any]])?.forEach { if let value = $0.value["currentCashoutAmount"] as? Double, let key = Int($0.key) { if amounts == nil { amounts = [:]} amounts?[key] = value } } completion(.success(amounts)) return case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func checkAndCashout(betWith id: Int, amount: Double, acceptedChanges: Bool, completion: @escaping (Result<Double, CashoutError>) -> ()) -> URLSessionWork? { let endpoint = ETotoAPI.Path.cashoutAmountAndCountdownRequired( betID: id, sessionID: user.value?.sessionID ) guard let request = URLRequest(api: ETotoAPI(), endpoint: endpoint) else { return nil } let work = URLSessionWork() let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): if let dict = body.json as? [AnyHashable: Any] { if !acceptedChanges, let current = dict["amount"] as? Double, current != amount { return completion(.failure(.changed(amount: current))) } else if (dict["countDownRequired"] as? Bool) == true { work.append(self?.cashoutCountdown(betID: id, amount: amount, acceptedChanges: acceptedChanges) { result in switch result { case .success(let countdown): if let miliseconds = countdown { DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + miliseconds / 1000) { guard !work.canceled else { return } work.append(self?.cashout(betWith: id, amount: amount, acceptedChanges: acceptedChanges, completion: completion)) } } else { work.append(self?.cashout(betWith: id, amount: amount, acceptedChanges: acceptedChanges, completion: completion)) } case .failure(let countdownError): completion(.failure(countdownError)) } }) } else { work.append(self?.cashout(betWith: id, amount: amount, acceptedChanges: acceptedChanges, completion: completion)) } } case .failure(let error): completion(.failure(.other(error: error))) case .none: break } } task.resume() work.append(task) return work } @discardableResult private func cashoutCountdown( betID: Int, amount: Double, acceptedChanges: Bool, completion: @escaping (Result<Double?, CashoutError>) -> ()) -> URLSessionDataTask? { let endpoint = ETotoAPI.Path.cashoutCountdown( betID: betID, amount: amount, acceptedChanges: acceptedChanges, sessionID: user.value?.sessionID) guard let request = URLRequest(api: ETotoAPI(), endpoint: endpoint) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): if let dict = body.json as? [AnyHashable: Any] { if let miliseconds = dict["value"] as? String { return completion(.success(Double(miliseconds))) } else if let changed = dict["amount"] as? Double ?? dict["currentCashoutAmount"] as? Double { return completion(.failure(.changed(amount: changed))) } } completion(.success(nil)) return case .failure(let error): completion(.failure(.other(error: error))) case .none: break } } task.resume() return task } @discardableResult func cashout(betWith id: Int, amount: Double, acceptedChanges: Bool, completion: @escaping (Result<Double, CashoutError>) -> ()) -> URLSessionDataTask? { let endpoint = ETotoAPI.Path.cashoutBet( id: id, amount: amount, acceptedChanges: acceptedChanges, sessionID: user.value?.sessionID) guard let request = URLRequest(api: ETotoAPI(), endpoint: endpoint) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): if let dict = body.json as? [AnyHashable: Any] { if dict["success"] as? Bool == true { return completion(.success(dict["currentCashoutAmount"] as? Double ?? amount)) } else { if let changed = dict["currentCashoutAmount"] as? Double { return completion(.failure(.changed(amount: changed))) } let error = URLError( .unknown, userInfo: [NSLocalizedDescriptionKey: dict["errorMessage"] as? String ?? ""]) return completion(.failure(.other(error: error))) } } completion(.success(amount)) return case .failure(let error): completion(.failure(.other(error: error))) case .none: break } } task.resume() return task } @discardableResult func getActivePaymentProviders(completion: @escaping GetActivePaymentProvidersCompletion) -> URLSessionDataTask? { guard let sessionId = user.value?.sessionID, let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.activePaymentProviders(sessionID:sessionId )) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let dictionaries = body.json as? [[String : Any]] else { completion(.failure(URLError(.cannotParseResponse))) return } completion(.success(PaymentProvider.providers(dictionaries))) return case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func initializePayment(providerId: Int, transactionAmount: Decimal, transactionCurrency:String, completion: @escaping PostInitializePaymentCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID else { return nil } guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.initializePayment(providerId: providerId, transactionAmount: transactionAmount, transactionCurrency: transactionCurrency, sessionID: sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let dictionaries = body.json as? [String:Any], let success = dictionaries["success"] as? Bool else { completion(.failure(WithdrawError.processFail)) return } if !success { let code = dictionaries["responseCode"] as? Int let error = self?.getMappedErrorForPayment(with: code) ?? WithdrawError.processFail return completion(.failure(error)) } completion(.success(())) case .none: return case .some(.failure(let error)): completion(.failure(error)) } } task.resume() return task } private func getMappedErrorForPayment(with code: Int?) -> Error { guard let code = code else { return WithdrawError.processFail } guard let mappedError = Settings.etoto.paymentErrorMessages.first(where: {$0.code == code}) else { if code == 47 { return WithdrawError.activeCreditBonus } let error: Error if let firebaseError = Settings.etoto.paymentErrorMessages.first(where: { $0.code == 0 }) { error = firebaseError } else { error = WithdrawError.processFail } return error } return mappedError } //MARK: - Register - @discardableResult func baseRegistration(baseRegistration: BaseRegistration, completion: @escaping GetEmptyCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoRegisterAPI(), endpoint: ETotoRegisterAPI.Path.baseRegistration(baseRegistration: baseRegistration)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(_): if let httpResponse = response as? HTTPURLResponse { print(httpResponse.allHeaderFields.keys) if let xOddsSession = httpResponse.allHeaderFields[User.Key.sessionID.rawValue] as? String { let user = User(dictionary: [:]) user?.sessionID = xOddsSession self?.user.value = user } } completion(.success(())) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } func detailsRegistration(detailsRegistration: DetailsRegistration, completion: @escaping GetEmptyCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: ETotoRegisterAPI(), endpoint: ETotoRegisterAPI.Path.detailRegistation(detailsRegistration: detailsRegistration, sessionID: sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success: completion(.success(())) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func processPayment(providerId: Int, transactionAmount: Double, transactionCurrency: String, callbackUrl: String, bankDetail: BankDetail? = nil, completion: @escaping PostProcessPaymentCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.processPayment(providerId: providerId, transactionAmount: transactionAmount, transactionCurrency: transactionCurrency, callback: callbackUrl, bankDetail: bankDetail, sessionID: sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let dictionaries = body.json as? [String:Any], let success = dictionaries["success"] as? Bool else { completion(.failure(WithdrawError.processFail)) return } if !success { let code = dictionaries["responseCode"] as? Int let error = self?.getMappedErrorForPayment(with: code) ?? WithdrawError.processFail completion(.failure(error)) } completion(.success(dictionaries["externalRequestUrl"] as? String ?? "")) case .none: return case .some(.failure(let error)): completion(.failure(error)) } } task.resume() return task } @discardableResult func customerBanksDetail(_ completion: @escaping GetCustomerBanksDetailCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.customerBanksDetail(sessionID: sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let dictionaries = body.json as? [[String : Any]]else { completion(.failure(URLError(.cannotParseResponse))) return } completion(.success(BankDetail.banksDeail(dictionaries))) return case .none: return case .some(.failure(let error)): completion(.failure(error)) } } task.resume() return task } //TODO: ("Uncomment sessionID after tests") func dateValidation(date: String, completion: @escaping GetValidationCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: ETotoRegisterAPI(baseURL: Settings.etoto.registrationValidationURL), endpoint: ETotoRegisterAPI.Path.dateValidation(date: date, sessionID: sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let data): //TODO: ("Remove force in error casting") guard let responseArray = try? JSONSerialization.jsonObject(with: data.data, options: []) as? [String] else { return } if let httpResponse = response as? HTTPURLResponse { if let xOddsSession = httpResponse.allHeaderFields[User.Key.sessionID.rawValue] as? String { print(xOddsSession) } } completion(.success((responseArray))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } func iDNumberValidation(iDNumber: String, completion: @escaping GetValidationCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: ETotoRegisterAPI(baseURL: Settings.etoto.baseRegistrationsURL), endpoint: ETotoRegisterAPI.Path.iDNumberValidation(iDNumber: iDNumber, sessionID: sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let data): guard let responseArray = try? JSONSerialization.jsonObject(with: data.data, options: []) as? [String] else { return } completion(.success((responseArray))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } func personalIDValidation(personalID: String, completion: @escaping GetValidationCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: ETotoRegisterAPI(baseURL: Settings.etoto.baseRegistrationsURL), endpoint: ETotoRegisterAPI.Path.personalIDValidation(personalID: personalID, sessionID: sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let data): guard let responseArray = try? JSONSerialization.jsonObject(with: data.data, options: []) as? [String] else { return } completion(.success((responseArray))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } func bonusValidation(completion: @escaping GetValidationCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoRegisterAPI(baseURL: Settings.etoto.baseRegistrationsURL), endpoint: ETotoRegisterAPI.Path.bonusValidation) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let data): guard let responseArray = try? JSONSerialization.jsonObject(with: data.data, options: []) as? [String: Any], let codes = responseArray["codes"] as? [String] else { return } completion(.success((codes))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func test( _ request: NSMutableURLRequest, completion: @escaping (Result<String?, Error>) -> ()) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID else { return nil } request.addValue(sessionID, forHTTPHeaderField: "X-ODDS-SESSION") let task = session.dataTask(with: request as URLRequest) { [weak self] (data, response, error) in switch self?.handle(request: request as URLRequest, data: data, response: response, error: error) { case .success(let body): completion(.success(String(data: body.data, encoding: .utf8))) return case .none: return case .some(.failure(let error)): completion(.failure(error)) return } } task.resume() return task } // MARK: - Virtuals @discardableResult func getVirtualsWebURL(query: [URLQueryItem]?,completion: @escaping GetVirtualsWebURL) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoVirtualsBettingAPI(), endpoint: ETotoVirtualsBettingAPI.Path.launchGame(sessionID: user.value?.sessionID, query: query)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let json = try? JSONSerialization.jsonObject(with: body.data, options: []) as? [String: Any] else { completion(.failure(.launchGame(errorMessage: URLError(.cannotParseResponse).localizedDescription))) return } if let errorMessage = json["ErrorMSg"] as? String { completion(.failure(ETotoVirtualsBettingAPIError.launchGame(errorMessage: errorMessage))) return } guard let urlString = json["url"] as? String else { completion(.failure(.launchGame(errorMessage: URLError(.cannotParseResponse).localizedDescription))) return } let url = URL(string: urlString) completion(.success(url)) case .failure(let error): let virtualsError: ETotoVirtualsBettingAPIError if let urlError = error as? URLError, urlError.code == .userAuthenticationRequired { virtualsError = .userAuthenticationRequired(errorMessage: urlError.localizedDescription) } else { virtualsError = .launchGame(errorMessage: error.localizedDescription) } completion(.failure(virtualsError)) case .none: break } } task.resume() return task } @discardableResult func setLimits(limits: [LimitRequestBody], completion: @escaping LimitCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.setLimits(sessionId: user.value?.sessionID, limits: limits)) else { return nil } return limit(request: request, completion: completion) } private func limit(request: URLRequest, completion: @escaping LimitCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): guard let body = body.json as? [[String: Any]] else { completion(.failure(.cannotParseResponse)) return } completion(.success(body.compactMap { LimitRequestResponse($0) })) case .failure: completion(.failure(.unknown)) case .none: break } } task.resume() return task } @discardableResult func acceptRegulations(completion: @escaping AcceptRegulationsCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: ETotoAPI(), endpoint: ETotoAPI.Path.acceptRegulations(sessionID: user.value?.sessionID)) else { return nil } return acceptRegulations(request: request, completion: completion) } private func acceptRegulations(request: URLRequest, completion: @escaping AcceptRegulationsCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success: completion(.success(())) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func limits(completion: @escaping (Result<[Limits]?, Error>) -> Void) -> URLSessionDataTask? { guard let request = URLRequest( api: ETotoAPI(), endpoint: ETotoAPI.Path.limits(sessionID: user.value?.sessionID)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): completion(.success(Limits.limits(from: body.json as? [[String: Any]]))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func activate( _ limitation: Limits.Limitation, completion: @escaping (Result<[Limits]?, Error>) -> Void) -> URLSessionDataTask? { let endpoint = ETotoAPI.Path.activate( limitation: limitation, sessionID: user.value?.sessionID) guard let request = URLRequest( api: ETotoAPI(), endpoint: endpoint) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): completion(.success(Limits.limits(from: body.json as? [[String: Any]]))) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } // MARK: - Football events cache func cachedFootballEvents() -> [Event]? { return footballEventsCache } func cacheFootballEvents() { getEvents(category: 1) { result in switch result { case .success(let events): self.footballEventsCache = events case .failure(_): break } } } @objc private func invalidateFootballCache() { footballEventsCache = nil } // MARK: - Notifications Settings @discardableResult func getNotificationsSettings(completion: @escaping GetNotificationsSettingsCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: UserProfileAPI(), endpoint: UserProfileAPI.Path.getNotificationsSettings(sessionID: sessionID)) else { return nil } return getNotificationsSettings(request: request, completion: completion) } private func getNotificationsSettings(request: URLRequest, completion: @escaping GetNotificationsSettingsCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): var string = String(data: body.data, encoding: .utf8) log.debug(string) guard let model = try? JSONDecoder().decode(NotificationConfigRequestModel.self, from: body.data) else { completion(.failure(.cannotParseResponse)) return } completion(.success(model)) case .failure: completion(.failure(.unknown)) case .none: break } } task.resume() return task } @discardableResult func postNotificationsSettings(model: NotificationConfigRequestModel,completion: @escaping PostNotificationsSettingsCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: UserProfileAPI(), endpoint: UserProfileAPI.Path.postNotificationsSettings(model: model, sessionID: sessionID)) else { return nil } return postNotificationsSettings(request: request, completion: completion) } private func postNotificationsSettings(request: URLRequest, completion: @escaping PostNotificationsSettingsCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success: completion(.success(())) case .failure: completion(.failure(.unknown)) case .none: break } } task.resume() return task } // MARK: - Chat @discardableResult func refreshChat(channelId: Int, completion: @escaping GetChatMessages) -> URLSessionDataTask? { guard let request = URLRequest(api: ChatAPI(), endpoint: ChatAPI.Path.refreshChat(channelId: channelId)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in guard let data = data else { return } let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): guard let model = try? JSONDecoder().decode([ChatMessage].self, from: body.data) else { completion(.failure(.cannotParseResponse)) return } completion(.success(model)) case .failure: completion(.failure(.unknown)) case .none: completion(.success([])) } } task.resume() return task } @discardableResult func saveMessage(channelId: Int, completion: @escaping GetChatMessages) -> URLSessionDataTask? { guard let request = URLRequest(api: ChatAPI(), endpoint: ChatAPI.Path.refreshChat(channelId: channelId)) else { return nil } refreshChatTask = session.dataTask(with: request) { [weak self] (data, response, error) in guard let data = data else { return } let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): guard let model = try? JSONDecoder().decode([ChatMessage].self, from: body.data) else { completion(.failure(.cannotParseResponse)) return } completion(.success(model)) case .failure: completion(.failure(.unknown)) case .none: break } } refreshChatTask?.resume() return refreshChatTask } @discardableResult func refreshChatOnChange(channelId: Int, lastId: Int, completion: @escaping GetChatMessages) -> URLSessionDataTask? { guard let request = URLRequest(api: ChatAPI(), endpoint: ChatAPI.Path.refreshOnChange(channelId: channelId, lastId: lastId)) else { return nil } refreshChatOnChangeTask = session.dataTask(with: request) { [weak self] (data, response, error) in guard let data = data else { return } let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): guard let model = try? JSONDecoder().decode([ChatMessage].self, from: body.data) else { completion(.failure(.cannotParseResponse)) return } completion(.success(model)) case .failure: completion(.failure(.unknown)) case .none: break } } refreshChatOnChangeTask?.resume() return refreshChatOnChangeTask } @discardableResult func postChatMessage(messageBody: MessageBody, completion: @escaping GetEmptyCompletion) -> URLSessionDataTask? { var cleanMessageBody = messageBody let messageText = messageBody.messageText.literalized() cleanMessageBody.messageText = messageText guard let sessionId = user.value?.sessionID, let request = URLRequest(api: ChatAPI(), endpoint: ChatAPI.Path.postMessage(messageBody: cleanMessageBody, sessionId: sessionId)) else { return nil } let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(_): completion(.success(())) case .failure(let error): completion(.failure(CustomError(type: .genericError))) case .none: break } } task.resume() return task } @discardableResult func getEventsValidForSubscription(completion: @escaping GetEventsValidForSubscriptionCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: SubscriptionAPI(), endpoint: SubscriptionAPI.Path.allEvents) else { return nil } return getEventsValidForSubscription(request: request, completion: completion) } private func getEventsValidForSubscription(request: URLRequest, completion: @escaping GetEventsValidForSubscriptionCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let model = try? JSONDecoder().decode(EventsValidForSubscriptionModel.self, from: body.data) else { completion(.failure(URLError(.cannotParseResponse))) return } completion(.success(model)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func getSubscribedEvents(completion: @escaping GetSubscribedEventsCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: SubscriptionAPI(), endpoint: SubscriptionAPI.Path.customerEvents(sessionId: user.value?.sessionID ?? "")) else { return nil } return getSubscribedEvents(request: request, completion: completion) } private func getSubscribedEvents(request: URLRequest, completion: @escaping GetSubscribedEventsCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let model = try? JSONDecoder().decode(SubscribedEventsModel.self, from: body.data) else { completion(.failure(URLError(.cannotParseResponse))) return } completion(.success(model)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func subscribeToEvent(eventId:Int,completion: @escaping SubscribeToEventCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: SubscriptionAPI(), endpoint: SubscriptionAPI.Path.subscribe(eventId: eventId, sessionId: user.value?.sessionID ?? "")) else { return nil } return subscribeToEvent(request: request, completion: completion) } private func subscribeToEvent(request: URLRequest, completion: @escaping SubscribeToEventCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let model = try? JSONDecoder().decode(SubscribeToEventModel.self, from: body.data) else { completion(.failure(URLError(.cannotParseResponse))) return } completion(.success(model)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } @discardableResult func unsubscribeFromEvent(eventId:Int,completion: @escaping SubscribeToEventCompletion) -> URLSessionDataTask? { guard let request = URLRequest(api: SubscriptionAPI(), endpoint: SubscriptionAPI.Path.unsubscribe(eventId: eventId, sessionId: user.value?.sessionID ?? "")) else { return nil } return unsubscribeFromEvent(request: request, completion: completion) } private func unsubscribeFromEvent(request: URLRequest, completion: @escaping SubscribeToEventCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in switch self?.handle(request: request, data: data, response: response, error: error) { case .success(let body): guard let model = try? JSONDecoder().decode(SubscribeToEventModel.self, from: body.data) else { completion(.failure(URLError(.cannotParseResponse))) return } completion(.success(model)) case .failure(let error): completion(.failure(error)) case .none: break } } task.resume() return task } func cancelRunningChatViewTasks() { refreshChatOnChangeTask?.cancel() refreshChatTask?.cancel() } //MARK: Deposit @discardableResult func getPaymentMethods(completion: @escaping GetPaymentMethodsCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: PaymentsAPI(), endpoint: PaymentsAPI.Path.getPaymentMethods(sessionId: sessionID)) else { return nil } return getPaymentMethods(request: request, completion: completion) } private func getPaymentMethods(request: URLRequest, completion: @escaping GetPaymentMethodsCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): let str = String(decoding: body.data, as: UTF8.self) print(str) guard var model = try? JSONDecoder().decode(GetPaymentMethodsResponse.self, from: body.data) else { completion(.failure(.cannotParseResponse)) return } model.methods.sort { $0.sortOrder < $1.sortOrder } completion(.success(model)) case .failure: completion(.failure(.unknown)) case .none: break } } task.resume() return task } @discardableResult func startPayment(requestModel: PostStartPaymentRequest,completion: @escaping StartPaymentCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: PaymentsAPI(), endpoint: PaymentsAPI.Path.startPayment(sessionId: sessionID, requestModel: requestModel)) else { return nil } return startPayment(request: request, completion: completion) } private func startPayment(request: URLRequest, completion: @escaping StartPaymentCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): let str = String(decoding: body.data, as: UTF8.self) print(str) guard let model = try? JSONDecoder().decode(PostStartPaymentResponse.self, from: body.data) else { completion(.failure(.cannotParseResponse)) return } completion(.success(model)) case .failure: completion(.failure(.unknown)) case .none: break } } task.resume() return task } @discardableResult func removeCardToken(token: String,completion: @escaping RemoveCardTokenCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: PaymentsAPI(), endpoint: PaymentsAPI.Path.removeCardToken(sessionId: sessionID, token: token)) else { return nil } return removeCardToken(request: request, completion: completion) } private func removeCardToken(request: URLRequest, completion: @escaping RemoveCardTokenCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): let str = String(decoding: body.data, as: UTF8.self) print(str) // guard let model = try? JSONDecoder().decode(PostStartPaymentResponse.self, from: body.data) else { // completion(.failure(.cannotParseResponse)) // return // } completion(.success(())) case .failure: completion(.failure(.unknown)) case .none: break } } task.resume() return task } @discardableResult func getStatus(orderID: String,completion: @escaping GetStatusCompletion) -> URLSessionDataTask? { guard let sessionID = user.value?.sessionID, let request = URLRequest(api: PaymentsAPI(), endpoint: PaymentsAPI.Path.getStatus(orderID: orderID)) else { return nil } return getStatus(request: request, completion: completion) } private func getStatus(request: URLRequest, completion: @escaping GetStatusCompletion) -> URLSessionDataTask? { let task = session.dataTask(with: request) { [weak self] (data, response, error) in let result = self?.handle(request: request, data: data, response: response, error: error) switch result { case .success(let body): let str = String(decoding: body.data, as: UTF8.self) print(str) guard let model = try? JSONDecoder().decode(GetPaymentStatusModel.self, from: body.data) else { completion(.failure(.cannotParseResponse)) return } completion(.success(model)) case .failure: completion(.failure(.unknown)) case .none: break } } task.resume() return task } } extension NetworkManager: URLSessionDelegate { public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { let urlCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!) completionHandler(.useCredential, urlCredential) } }