import Foundation import RxSwift import RxCocoa public class Api { private static let session: URLSession = { let sessionConfig = URLSessionConfiguration.default sessionConfig.timeoutIntervalForRequest = 60.0 sessionConfig.timeoutIntervalForResource = 60.0 return URLSession(configuration: sessionConfig) }() // MARK: - Private wrappres private static func genError(_ msg: String, suggestion: String, code: Int = 0) -> Error { return NSError(domain: "", code: code, userInfo: [NSLocalizedDescriptionKey: msg, NSLocalizedRecoverySuggestionErrorKey: suggestion]) } private static func createRequest(api: String, method: String, body: B? = nil, params: [String:P]? = nil) -> URLRequest? where B: Encodable, P: LosslessStringConvertible { guard var urlComponents = URLComponents(string: Constants.baseUrl + api) else { return nil } if let params = params, method.uppercased() == "GET" { urlComponents.queryItems = params.map { URLQueryItem(name: $0, value: String($1)) } } var request = URLRequest(url: urlComponents.url!) request.httpMethod = method request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue("application/json", forHTTPHeaderField: "Accept") request.addValue("Bearer " + Settings.shared.user.token, forHTTPHeaderField: "Authorization") if let body = body, method.uppercased() != "GET" { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted if let data = try? encoder.encode(body) { request.httpBody = data } } return request } private static func makeRequest(api: String, method: String = "GET", body: B?, params: [String:P]? = nil) -> Single where T: Decodable, B: Encodable, P: LosslessStringConvertible { guard let request = self.createRequest(api: api, method: method, body: body, params: params) else { return Single.error(self.genError("Error creating request", suggestion: "")) } return self.session.rx.data(request: request).asSingle().map { data in // let str = String(data: data, encoding: .utf8) // print("================================") // if let string = str?.replacingOccurrences(of: "\\\"", with: "\"") // .replacingOccurrences(of: "\\'", with: "'") // .replacingOccurrences(of: "\\n", with: "") { // print(string) // } // print("================================") do { let resp = try JSONDecoder().decode(Response.self, from: data) if resp.success { return resp.data! } else { throw self.genError(resp.error!, suggestion: "") } } catch let error as Swift.DecodingError { throw CocoaError.error((error as CustomDebugStringConvertible).debugDescription) } catch { throw error } } } private static func makeGetRequest(api: String, params:[String: P]? = nil) -> Single where T: Decodable, P: LosslessStringConvertible { // Kind of hack to satisfy compiler return self.makeRequest(api: api, method: "GET", body: nil as Int?, params: params) } private static func makeEmptyGetRequest(api: String) -> Single where T: Decodable { // Same hack as before return self.makeRequest(api: api, method: "GET", body: nil as Int?, params: nil as [String:Int]?) } private static func makeEmptyBodyRequest(api: String, method: String = "POST") -> Single where T: Decodable { // Same hack as before return self.makeRequest(api: api, method: method, body: nil as Int?, params: nil as [String:Int]?) } private static func makeBodyRequest(api: String, body: B?, method: String = "POST") -> Single where T: Decodable, B: Encodable { // Same hack as before return self.makeRequest(api: api, method: method, body: body, params: nil as [String:Int]?) } // MARK: - Firebase API public static func refreshFbToken() -> Single { guard let token = Settings.shared.user.firebaseIdToken, let refreshToken = Settings.shared.user.firebaseRefreshToken, let jwt = JWT(string: token), jwt.expired else { return .just(()) } let refreshUrlString = Constants.fbRefreshToken + "?key=" + Constants.fbApiKey let body = [ "grantType": "refresh_token", "refreshToken": refreshToken ] if let url = URL(string: refreshUrlString) { var request = URLRequest(url: url) request.httpMethod = "POST" request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .prettyPrinted) request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue(Constants.fbClientVersion, forHTTPHeaderField: "X-Client-Version") request.addValue(Constants.secondProviderBundleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier") request.addValue(Constants.fbUserAgent, forHTTPHeaderField: "User-Agent") return URLSession.shared.rx.json(request: request).asSingle().map { resp in guard let json = resp as? [String: Any] else { return } if let newToken = json["id_token"] as? String { Settings.shared.user.firebaseIdToken = newToken //print("Token was successfully refresh to: \(newToken)") } if let newRefreshToken = json["refresh_token"] as? String { Settings.shared.user.firebaseRefreshToken = newRefreshToken } }.catchError { error in print(error) return .just(()) } } else { return .just(()) } } public static func fbVerifyAssertion(provider: String, idToken: String, accessToken: String? = nil) -> Single { let signupUrl = Constants.fbVerifyAssertion + "?key=" + (Constants.fbApiKey.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? Constants.fbApiKey) if let url = URL(string: signupUrl) { var innerBody = "providerId=" + provider + "&id_token=" + idToken if let accessToken = accessToken { innerBody += "&access_token=" + accessToken } let body: [String:Any] = [ "returnIdpCredential": true, "returnSecureToken": true, "autoCreate": true, "requestUri": "http://localhost", "postBody": innerBody ] var request = URLRequest(url: url) request.httpMethod = "POST" request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .prettyPrinted) request.addValue("application/json", forHTTPHeaderField: "Content-Type") request.addValue(Constants.fbClientVersion, forHTTPHeaderField: "X-Client-Version") request.addValue(Constants.secondProviderBundleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier") request.addValue(Constants.fbUserAgent, forHTTPHeaderField: "User-Agent") return URLSession.shared.rx.json(request: request).asSingle().map { response in guard let json = response as? [String: Any] else { return } if let newToken = json["idToken"] as? String { Settings.shared.user.firebaseIdToken = newToken print("Token: \(newToken)") } if let newRefreshToken = json["refreshToken"] as? String { Settings.shared.user.firebaseRefreshToken = newRefreshToken print("Refresh token: \(newRefreshToken)") } }.catchError { err in print(err) return .just(()) } } else { return .just(()) } } // MARK: - AutoCat public API public static func login(email: String, password: String) -> Single { let body = [ "email": email, "password": password ] return self.makeBodyRequest(api: "user/login", body: body) } public static func signIn(email: String, password: String) -> Single { let body = [ "email": email, "password": password ] return self.makeBodyRequest(api: "user/signIn", body: body) } public static func signUp(email: String, password: String) -> Single { let body = [ "email": email, "password": password ] return self.makeBodyRequest(api: "user/signup", body: body) } public static func getVehicles(with filter: Filter, pageToken: String? = nil) -> Single> { var params = filter.queryDictionary() if let token = pageToken { params["pageToken"] = token; } return self.makeGetRequest(api: "vehicles", params: params) } public static func checkVehicle(by number: String, notes: [VehicleNote], events: [VehicleEvent], force: Bool = false) -> Single { return self.refreshFbToken().flatMap { () -> Single in var body = [ "number": AnyEncodable(number), "forceUpdate": AnyEncodable(force) ] if let token = Settings.shared.user.firebaseIdToken { body["googleIdToken"] = AnyEncodable(token) } if !notes.isEmpty { body["notes"] = AnyEncodable(notes) } if !events.isEmpty { body["events"] = AnyEncodable(events) } return self.makeBodyRequest(api: "vehicles/check", body: body).map { (vehicle: Vehicle) -> Vehicle in vehicle.addedDate = Date().timeIntervalSince1970 return vehicle } } } public static func getReport(for number: String) -> Single { return self.makeGetRequest(api: "vehicles/report", params: ["number": number]) } public static func getBrands() -> Single<[String]> { return self.makeEmptyGetRequest(api: "vehicles/brands") } public static func getModels(of brand: String) -> Single<[String]> { return self.makeGetRequest(api: "vehicles/models", params: ["brand": brand]) } public static func getColors() -> Single<[String]> { return self.makeEmptyGetRequest(api: "vehicles/colors") } public static func getRegions() -> Single<[VehicleRegion]> { return self.makeEmptyGetRequest(api: "vehicles/regions") } public static func getYears() -> Single<[Int]> { return self.makeEmptyGetRequest(api: "vehicles/years") } public static func add(event: VehicleEvent, to number: String) -> Single { let body = ["number": AnyEncodable(number), "event": AnyEncodable(event)] return self.makeBodyRequest(api: "events", body: body).map { (vehicle: Vehicle) -> Vehicle in vehicle.addedDate = Date().timeIntervalSince1970 return vehicle } } public static func remove(event id: String) -> Single { let body = ["eventId": id] return self.makeBodyRequest(api: "events", body: body, method: "DELETE") } public static func edit(event: VehicleEvent) -> Single { let body = ["event": event] return self.makeBodyRequest(api: "events", body: body, method: "PUT") } public static func events(with filter: Filter) -> Single<[VehicleEvent]> { return self.makeGetRequest(api: "events", params: filter.queryDictionary()) } public static func checkOsago(number: String?, vin: String?, date: Date, token: String) -> Single { let body = [ "date": AnyEncodable(date.timeIntervalSince1970), "number": AnyEncodable(number), "vin": AnyEncodable(vin), "token": AnyEncodable(token) ] return self.makeBodyRequest(api: "vehicles/checkOsago", body: body) } public static func add(notes: [VehicleNote], to number: String) -> Single { let body = ["number": AnyEncodable(number), "notes": AnyEncodable(notes)] return self.makeBodyRequest(api: "notes", body: body) } public static func edit(note: VehicleNote) -> Single { return self.makeBodyRequest(api: "notes", body: ["note": note], method: "PUT") } public static func remove(note id: String) -> Single { return self.makeBodyRequest(api: "notes", body: ["noteId": id], method: "DELETE") } }