// // 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 { case networkError case noNetwork(message:String) case invalidURL case parameterEncodingFailed case responseValidationFailed case unknown(message: String) case custom(message: String) } /// 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() { 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 ("","") } } 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 } }