// // NetworkManager.swift // WOKA // // Created by MacBook Pro on 06/05/24. // import Alamofire class NetworkManager{ static let shareInstance = NetworkManager() private let alamofireLogger = AlamofireLogger() private init() {} enum APIError: Error, LocalizedError { case networkError case noNetwork(message:String) case invalidURL case parameterEncodingFailed case responseValidationFailed case unknown(message: String) case custom(message: String) var errorDescription: String? { switch self { case .networkError: return "A network error occurred." case .noNetwork(let message): return "No network: \(message)" case .invalidURL: return "The URL provided is invalid." case .parameterEncodingFailed: return "Failed to encode the parameters." case .responseValidationFailed: return "Response validation failed." case .unknown(let message): return "Unknown error: \(message)" case .custom(let message): return "Custom error: \(message)" } } } /// This function will do the network call for HTTPMethod & Encoding is URLEncoding with contentType ["application/json"] /// /// /// - Warning: The returned string is not localized. /// /// Usage: /// /// Alamofire network call Generic (.get , .post, .put.). /// /// - Parameter (header : HTTPHeaders , Params :[String : Any] , URL , Dedocable Generic T Struct. , queue : dispatchQueue decide) /// - NOTE Oncompletion will be called on main thread so no need to specify the main thread for UI updates /// - Returns: This function returns a GENERIC response base on the T Model & APIError . func apiRequest( url: URLConvertible, method: HTTPMethod, parameters: Parameters? = nil, encoding: ParameterEncoding = URLEncoding.default, headers: HTTPHeaders? = nil, queue: DispatchQueue = QueueHelper.background, completionHandler: @escaping (Result) -> Void ) { // Stop monitoring network reachability NetworkReachibility.shared.stopMonitoring() let loginCred = getLoginIDPass() // Execute the request on the specified queue queue.async { AF.request(url, method: method, parameters: parameters, encoding: encoding, headers: headers, requestModifier: { $0.timeoutInterval = 20 }) .authenticate(username: loginCred.0, password: loginCred.1) .validate(statusCode: 200..<300) .responseDecodable(of: T.self) { response in // alamofireLogger.request(response.da, didParseResponse: response) switch response.result { case .success(let value): /* if Sucess == 4 menas user has logined in some other device, logout that user by saying session timeout */ if value.success == 4{ if let topController = UIApplication.topViewController() { Utilities.dismissProgressHUD() let sb = UIStoryboard(name: K.StoryBoard.customAlerts, bundle: nil) let vcPush = sb.instantiateViewController(withIdentifier: K.StoryBoardID.CustomAlerts.alertCustomVC) as! AlertCustomVC vcPush.contentLabel = "Please Login Again" vcPush.mainTitleText = "Session Expired." vcPush.yesBtnText = "Login" vcPush.onDoneBlock = { isDone in AuthFunc.shareInstance.logout() UIApplication.setRootView(LoginNavVC.instantiate(from: .AuthenticationSB)) } vcPush.modalPresentationStyle = .overCurrentContext vcPush.modalTransitionStyle = .crossDissolve topController.present(vcPush, animated: true) // Utilities.alertWithBtnCompletion(title: "Session Expired", msgBody: "Please Login Again", okBtnStr: "OK", vc: topController) { isDone in // AuthFunc.shareInstance.logout() // UIApplication.setRootView(LoginNavVC.instantiate(from: .AuthenticationSB)) // } } return } // Handle successful response on the main thread DispatchQueue.main.async { completionHandler(.success(value)) } case .failure(let error): // Handle failure cases if let afError = error as? AFError { switch afError { case .sessionTaskFailed(let urlError): if urlError._code == -1020 || urlError._code == -1009 { // Handle network error on the main thread DispatchQueue.main.async { completionHandler(.failure(.noNetwork(message: K.ConstantString.noInternet))) } } else { // Handle other network errors on the main thread DispatchQueue.main.async { completionHandler(.failure(.networkError)) } } case .invalidURL: // Handle invalid URL error on the main thread DispatchQueue.main.async { completionHandler(.failure(.invalidURL)) } case .parameterEncodingFailed: // Handle parameter encoding failure on the main thread DispatchQueue.main.async { completionHandler(.failure(.parameterEncodingFailed)) } case .responseValidationFailed: // Handle response validation failure on the main thread DispatchQueue.main.async { completionHandler(.failure(.responseValidationFailed)) } default: // Handle other network errors on the main thread DispatchQueue.main.async { completionHandler(.failure(.networkError)) } } } else { // Handle unrecognized errors on the main thread DispatchQueue.main.async { completionHandler(.failure(.unknown(message: K.ConstantString.unRecognised))) } } } } } } func getLoginIDPass()-> (String, String){ if let loginID = Bundle.main.infoDictionary?["API_KEY_ID"] as? String , let loginPaass = Bundle.main.infoDictionary?["API_KEY_PASS"] as? String{ let cleanedLoginID = loginID.trimmingCharacters(in: CharacterSet(charactersIn: "\"")) let cleanedLoginPass = loginPaass.trimmingCharacters(in: CharacterSet(charactersIn: "\"")) return (cleanedLoginID, cleanedLoginPass) } return ("","") } /// This function will do the network call for POST With RAWJSON & Encoding is URLEncoding with contentType ["application/json"] /// /// /// - Warning: The returned string is not localized. /// /// Usage: /// /// Alamofire network call POST(RawJSON). /// /// - Parameter (header : HTTPHeaders , Params :[String : Any] , URL , Dedocable Generic T Struct.) /// /// - Returns: This function returns a GENERIC response base on the T Model & ERROR . func nwCallRawJSON(clicksData : [ClicksAnalytics], onCompletion : @escaping (Bool) -> Void){ let loginCred = getLoginIDPass() let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase guard let jsonData = try? encoder.encode(clicksData), let jsonArray = try? JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? [[String: Any]] else { print("Failed to encode totalClicks array to JSON") return } let url = APIEndPoints.Analytics.user_clicks var request = URLRequest(url: url) request.httpMethod = "POST" request.headers = ["device-id" : AuthFunc.shareInstance.getDeviceUUID(), "access-token" : AuthFunc.shareInstance.getAccessToken()] request.setValue("application/json", forHTTPHeaderField: "Content-Type") do { // Set the HTTP body with the JSON data request.httpBody = try JSONSerialization.data(withJSONObject: jsonArray) } catch let error { print("Error: \(error.localizedDescription)") return } AF.request(request).authenticate(username: loginCred.0, password: loginCred.1) .validate(statusCode: 200..<300) .responseDecodable(of: CommonResponseModel.self) { response in switch response.result { case .success(let data): onCompletion(true) case .failure(let error): onCompletion(false) } } } func nwCallRawJSONAds(adsData : [AdsClickImpressionsData], onCompletion : @escaping (Bool) -> Void){ let loginCred = getLoginIDPass() let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase guard let jsonData = try? encoder.encode(adsData), let jsonArray = try? JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? [[String: Any]] else { print("Failed to encode totalClicks array to JSON") return } let url = APIEndPoints.Analytics.update_ad_count var request = URLRequest(url: url) request.httpMethod = "POST" request.headers = ["device-id" : AuthFunc.shareInstance.getDeviceUUID(), "access-token" : AuthFunc.shareInstance.getAccessToken()] request.setValue("application/json", forHTTPHeaderField: "Content-Type") do { // Set the HTTP body with the JSON data request.httpBody = try JSONSerialization.data(withJSONObject: jsonArray) } catch let error { print("Error: \(error.localizedDescription)") return } AF.request(request).authenticate(username: loginCred.0, password: loginCred.1) .validate(statusCode: 200..<300) .responseDecodable(of: CommonResponseModel.self) { response in switch response.result { case .success(let data): onCompletion(true) case .failure(let error): onCompletion(false) } } } } final class AlamofireLogger: EventMonitor { func requestDidResume(_ request: Request) { let allHeaders = request.request.flatMap { $0.allHTTPHeaderFields.map { $0.description } } ?? "None" let headers = """ ⚡️⚡️⚡️⚡️ Request Started: \(request) ⚡️⚡️⚡️⚡️ Headers: \(allHeaders) """ NSLog(headers) let body = request.request.flatMap { $0.httpBody.map { String(decoding: $0, as: UTF8.self) } } ?? "None" let message = """ ⚡️⚡️⚡️⚡️ Request Started: \(request) ⚡️⚡️⚡️⚡️ Body Data: \(body) """ NSLog(message) } func request(_ request: DataRequest, didParseResponse response: AFDataResponse) { NSLog("⚡️⚡️⚡️⚡️ Response Received: \(response.debugDescription)") NSLog("⚡️⚡️⚡️⚡️ Response All Headers: \(String(describing: response.response?.allHeaderFields))") } } extension UIApplication { class func topViewController(_ base: UIViewController? = UIApplication.shared.mainKeyWindow?.rootViewController) -> UIViewController? { if let nav = base as? UINavigationController { return topViewController(nav.visibleViewController) } if let tab = base as? UITabBarController { if let selected = tab.selectedViewController { return topViewController(selected) } } if let presented = base?.presentedViewController { return topViewController(presented) } return base } }