diff --git a/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
index 07e3a91..2358118 100644
--- a/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
+++ b/AutoCat.xcodeproj/xcuserdata/selim.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
@@ -24,5 +24,21 @@
stopOnStyle = "0">
+
+
+
+
diff --git a/AutoCat/AppDelegate.swift b/AutoCat/AppDelegate.swift
index 8ce7358..2bd49ef 100644
--- a/AutoCat/AppDelegate.swift
+++ b/AutoCat/AppDelegate.swift
@@ -18,10 +18,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
- let config = Realm.Configuration(
- schemaVersion: 3,
- migrationBlock: { migration, oldSchemaVersion in
- })
+ let config = Realm.Configuration(
+ schemaVersion: 5,
+ migrationBlock: { migration, oldSchemaVersion in
+ if oldSchemaVersion <= 3 {
+ var numbers: [String] = []
+ migration.enumerateObjects(ofType: "Vehicle") { old, new in
+ if let number = old?["number"] as? String {
+ if numbers.contains(number) {
+ migration.delete(old!)
+ } else {
+ numbers.append(number)
+ }
+ }
+ }
+ }
+ })
Realm.Configuration.defaultConfiguration = config
diff --git a/AutoCat/Controllers/CheckController.swift b/AutoCat/Controllers/CheckController.swift
index 6e91275..3c87d72 100644
--- a/AutoCat/Controllers/CheckController.swift
+++ b/AutoCat/Controllers/CheckController.swift
@@ -6,7 +6,7 @@ import SwiftDate
import RxRealm
import RxDataSources
-class CheckController: UIViewController, MaskedTextFieldDelegateListener {
+class CheckController: UIViewController, MaskedTextFieldDelegateListener, UITableViewDelegate {
@IBOutlet weak var number: UITextField!
@IBOutlet weak var check: UIButton!
@@ -32,7 +32,7 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener {
} else {
return UITableViewCell()
}
- })
+ }, canEditRowAtIndexPath: { _, _ in true })
ds.titleForHeaderInSection = { dataSourse, index in
return dataSourse.sectionModels[index].header
@@ -42,11 +42,14 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener {
.subscribe(onNext: self.updateDetailController(with:))
.disposed(by: self.bag)
+
Observable.collection(from: realm.objects(Vehicle.self)
.sorted(byKeyPath: "addedDate", ascending: false))
.map { $0.groupedByDate() }
.bind(to: self.history.rx.items(dataSource: ds))
.disposed(by: self.bag)
+
+ self.history.rx.setDelegate(self).disposed(by: self.bag)
}
override func viewWillAppear(_ animated: Bool) {
@@ -111,7 +114,7 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener {
func onReceivedVehicle(_ vehicle: Vehicle) {
if let realm = try? Realm() {
try? realm.write {
- realm.add(vehicle)
+ realm.add(vehicle, update: .modified)
}
}
@@ -138,5 +141,21 @@ class CheckController: UIViewController, MaskedTextFieldDelegateListener {
}
}
-
+ func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
+ guard let vehicle: Vehicle = try? self.history.rx.model(at: indexPath) else { return nil }
+
+ let updateAction = UIContextualAction(style: .normal, title: "Update") { action, view, completion in
+ IHProgressHUD.show()
+ Api.checkVehicle(by: vehicle.number, force: true)
+ .observeOn(MainScheduler.instance)
+ .subscribe(onNext: self.onReceivedVehicle(_:), onError: { err in
+ IHProgressHUD.showError(withStatus: err.localizedDescription)
+ print(err.localizedDescription)
+ }).disposed(by: self.bag)
+ completion(true)
+ }
+ updateAction.image = UIImage(systemName: "arrow.2.circlepath")
+ updateAction.backgroundColor = .systemBlue
+ return UISwipeActionsConfiguration(actions: [updateAction])
+ }
}
diff --git a/AutoCat/Controllers/GoogleSignInController.swift b/AutoCat/Controllers/GoogleSignInController.swift
index daec371..ba15d99 100644
--- a/AutoCat/Controllers/GoogleSignInController.swift
+++ b/AutoCat/Controllers/GoogleSignInController.swift
@@ -1,10 +1,11 @@
import UIKit
import WebKit
import CommonCrypto
+import RxSwift
-private struct TokenResponse: Codable {
+struct TokenResponse: Codable {
var id_token: String
- var refresh_token: String
+ var refresh_token: String?
var access_token: String
var expires_in: Int
var token_type: String
@@ -14,6 +15,7 @@ private struct TokenResponse: Codable {
class GoogleSignInController: UIViewController, WKNavigationDelegate {
@IBOutlet weak var webView: WKWebView!
+ private var bag = DisposeBag()
private var codeVerifier: String = ""
public var completion: (() -> Void)?
@@ -35,7 +37,7 @@ class GoogleSignInController: UIViewController, WKNavigationDelegate {
+ "&code_challenge_method=S256"
+ "&scope=email%20profile"
+ "&redirect_uri=" + Constants.googleRedirectURL
- + "&client_id=" + Constants.googleClientId
+ + "&client_id=" + Constants.fbClientId
+ "&code_challenge=" + codeChallenge
if let url = URL(string: authUrlString) {
@@ -50,23 +52,15 @@ class GoogleSignInController: UIViewController, WKNavigationDelegate {
if let queryItems = components.queryItems {
if let code = queryItems.first(where: { $0.name == "code" })?.value {
decisionHandler(.cancel)
- self.getToken(code: code) { error, idToken, refreshToken in
- if let err = error {
- DispatchQueue.main.async {
- self.dismiss(animated: true) {
- IHProgressHUD.showError(withStatus: err.localizedDescription)
- }
- }
- } else {
- Settings.shared.user.googleIdToken = idToken
- Settings.shared.user.googleRefreshToken = refreshToken
- DispatchQueue.main.async {
- self.dismiss(animated: true) {
- self.completion?()
- }
- }
- }
- }
+ self.getToken(code: code)
+ .flatMap(fbVerifyAssertion)
+ .observeOn(MainScheduler.instance)
+ .subscribe(onNext: { _ in
+ self.dismiss(animated: true, completion: self.completion)
+ }, onError: { error in
+ IHProgressHUD.showError(withStatus: error.localizedDescription)
+ })
+ .disposed(by: self.bag)
return
}
}
@@ -91,33 +85,65 @@ class GoogleSignInController: UIViewController, WKNavigationDelegate {
return String(data: Data(Base64FS.encode(data: hash)), encoding: .utf8)?.trimmingCharacters(in: CharacterSet(charactersIn: "="))
}
- func getToken(code: String, completion: @escaping (Error?, String?, String?) -> Void) {
+ func getToken(code: String) -> Observable {
let tokenUrlString = Constants.googleTokenURL
+ "?grant_type=authorization_code"
+ "&code=" + code
+ "&redirect_uri=" + Constants.googleRedirectURL
- + "&client_id=" + Constants.googleClientId
+ + "&client_id=" + Constants.fbClientId
+ "&code_verifier=" + self.codeVerifier
if let url = URL(string: tokenUrlString) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
- let task = URLSession.shared.dataTask(with: request) { data, response, error in
- if let data = data {
- if let resp = try? JSONDecoder().decode(TokenResponse.self, from: data) {
- completion(nil, resp.id_token, resp.refresh_token)
- } else {
- let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Response parsing error"])
- completion(error, nil, nil)
- }
- } else {
- completion(error, nil, nil)
- }
+ return URLSession.shared.rx.data(request: request).map { data in
+ return try JSONDecoder().decode(TokenResponse.self, from: data)
}
- task.resume()
} else {
let error = NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Bad URL"])
- completion(error, nil, nil)
+ return Observable.error(error)
}
}
+
+ public func fbVerifyAssertion(tokenResp: TokenResponse) -> Observable {
+ let signupUrl = Constants.fbVerifyAssertion + "?key=" + (Constants.fbApiKey.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? Constants.fbApiKey)
+ if let url = URL(string: signupUrl) {
+ let innerBody = "providerId=google.com"
+ + "&id_token=" + tokenResp.id_token
+ + "&access_token=" + tokenResp.access_token
+
+ 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.vin01BUndleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier")
+ request.addValue(Constants.fbUserAgent, forHTTPHeaderField: "User-Agent")
+
+ return URLSession.shared.rx.json(request: request).map { response in
+ guard let json = response as? [String: Any] else { return }
+ if let newToken = json["idToken"] as? String {
+ Settings.shared.user.googleIdToken = newToken
+ print("Token: \(newToken)")
+ }
+ if let newRefreshToken = json["refreshToken"] as? String {
+ Settings.shared.user.googleRefreshToken = newRefreshToken
+ print("Refresh token: \(newRefreshToken)")
+ }
+ }.catchError { err in
+ print(err)
+ return .just(())
+ }
+ } else {
+ return .just(())
+ }
+ }
}
diff --git a/AutoCat/Info.plist b/AutoCat/Info.plist
index f0170f0..afefe6a 100644
--- a/AutoCat/Info.plist
+++ b/AutoCat/Info.plist
@@ -22,6 +22,8 @@
NSAppTransportSecurity
+ NSAllowsArbitraryLoads
+
NSExceptionDomains
avto-nomer.ru
diff --git a/AutoCat/Models/Vehicle.swift b/AutoCat/Models/Vehicle.swift
index eb33882..7bd9084 100644
--- a/AutoCat/Models/Vehicle.swift
+++ b/AutoCat/Models/Vehicle.swift
@@ -41,7 +41,6 @@ class VehiclePhoto: Object, Decodable {
}
class Vehicle: Object, Decodable, IdentifiableType {
- @objc dynamic var _id: String = ""
@objc dynamic var brand: VehicleBrand?
@objc dynamic var model: VehicleModel?
@objc dynamic var color: String?
@@ -59,10 +58,9 @@ class Vehicle: Object, Decodable, IdentifiableType {
@objc dynamic var addedBy: String = ""
let photos = List()
- var identity: String { _id }
+ var identity: String { number }
enum CodingKeys: String, CodingKey {
- case _id
case brand
case model
case color
@@ -83,7 +81,6 @@ class Vehicle: Object, Decodable, IdentifiableType {
required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
- self._id = try container.decode(String.self, forKey: ._id)
self.brand = try container.decodeIfPresent(VehicleBrand.self, forKey: .brand)
self.model = try container.decodeIfPresent(VehicleModel.self, forKey: .model)
self.color = try container.decodeIfPresent(String.self, forKey: .color)
@@ -111,7 +108,10 @@ class Vehicle: Object, Decodable, IdentifiableType {
init(_ number: String) {
self.number = number
- self._id = UUID().uuidString
self.addedDate = Date().timeIntervalSince1970*1000
}
+
+ override static func primaryKey() -> String? {
+ return "number"
+ }
}
diff --git a/AutoCat/ThirdParty/Api.swift b/AutoCat/ThirdParty/Api.swift
index fb25795..887187e 100644
--- a/AutoCat/ThirdParty/Api.swift
+++ b/AutoCat/ThirdParty/Api.swift
@@ -2,7 +2,7 @@ import Foundation
import RxSwift
class Api {
- private static let baseUrl = "https://vps.aliencat.pro:8443/"
+ private static let baseUrl = "http://127.0.0.1:3000/" //"https://vps.aliencat.pro:8443/"
private static func genError(_ msg: String, suggestion: String, code: Int = 0) -> Error {
return NSError(domain: "", code: code, userInfo: [NSLocalizedDescriptionKey: msg, NSLocalizedRecoverySuggestionErrorKey: suggestion])
@@ -36,7 +36,7 @@ class Api {
return URLSession.shared.rx.data(request: request).map { data in
// let str = String(data: data, encoding: .utf8)
// print("================================")
-// print(str)
+// print(str?.replacingOccurrences(of: "\\\"", with: "\""))
// print("================================")
let resp = try JSONDecoder().decode(Response.self, from: data)
if resp.success {
@@ -47,6 +47,44 @@ class Api {
}
}
+ public static func refreshFbToken() -> Observable {
+ guard let token = Settings.shared.user.googleIdToken, let refreshToken = Settings.shared.user.googleRefreshToken, 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.vin01BUndleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier")
+ request.addValue(Constants.fbUserAgent, forHTTPHeaderField: "User-Agent")
+ return URLSession.shared.rx.json(request: request).map { resp in
+ guard let json = resp as? [String: Any] else { return }
+ if let newToken = json["id_token"] as? String {
+ Settings.shared.user.googleIdToken = newToken
+ print("Token was successfully refresh to: \(newToken)")
+ }
+ if let newRefreshToken = json["refresh_token"] as? String {
+ Settings.shared.user.googleRefreshToken = newRefreshToken
+ }
+ }.catchError { error in
+ print(error)
+ return .just(())
+ }
+ } else {
+ return .just(())
+ }
+ }
+
public static func login(username: String, password: String) -> Observable {
let body = [
"login": username,
@@ -71,14 +109,19 @@ class Api {
return self.makeRequest(api: "vehicles", method: "GET", body: body)
}
- public static func checkVehicle(by number: String) -> Observable {
- if let token = Settings.shared.user.googleIdToken, let jwt = JWT(string: token) {
-
- }
-
- return self.makeRequest(api: "vehicles/check", method: "POST", body: ["number": number]).map { (vehicle: Vehicle) -> Vehicle in
- vehicle.addedDate = Date().timeIntervalSince1970*1000
- return vehicle
+ public static func checkVehicle(by number: String, force: Bool = false) -> Observable {
+ return self.refreshFbToken().flatMap { () -> Observable in
+ var body = [
+ "number": number,
+ "forceUpdate": String(force)
+ ]
+ if let token = Settings.shared.user.googleIdToken {
+ body["googleIdToken"] = token
+ }
+ return self.makeRequest(api: "vehicles/check", method: "POST", body: body).map { (vehicle: Vehicle) -> Vehicle in
+ vehicle.addedDate = Date().timeIntervalSince1970*1000
+ return vehicle
+ }
}
}
}
diff --git a/AutoCat/Utils/Constants.swift b/AutoCat/Utils/Constants.swift
index 5672f45..86a21e5 100644
--- a/AutoCat/Utils/Constants.swift
+++ b/AutoCat/Utils/Constants.swift
@@ -5,6 +5,13 @@ enum Constants {
static let googleTokenURL = "https://oauth2.googleapis.com/token"
static let googleRedirectURL = "com.googleusercontent.apps.994679674451-k7clunkk4nicl6iuajdtc5u7hvustbdb:/oauth2callback"
- static let googleClientId = "994679674451-k7clunkk4nicl6iuajdtc5u7hvustbdb.apps.googleusercontent.com"
- static let googleApiKey = "AIzaSyDVlrQj_05y6AeZNf8enpSWFIiHhgwfnGI"
+ static let fbClientId = "994679674451-k7clunkk4nicl6iuajdtc5u7hvustbdb.apps.googleusercontent.com"
+ static let fbApiKey = "AIzaSyDVlrQj_05y6AeZNf8enpSWFIiHhgwfnGI"
+
+ static let fbClientVersion = "iOS/FirebaseSDK/6.5.1/FirebaseCore-iOS"
+ static let fbVerifyAssertion = "https://www.googleapis.com/identitytoolkit/v3/relyingparty/verifyAssertion"
+ static let fbRefreshToken = "https://securetoken.googleapis.com/v1/token"
+ static let fbUserAgent = "FirebaseAuth.iOS/6.5.1 ru.Vin01/1.0 iPhone/13.5 hw/sim"
+
+ static let vin01BUndleId = "ru.Vin01"
}