Files
2022-05-27 20:21:01 +03:00

269 lines
9.8 KiB
Swift

//
// AccountService.swift
// List
//
// Created by Igor Danich on 29.06.2020.
// Copyright © 2020 Igor Danich. All rights reserved.
//
import Foundation
import LocalAuthentication
import Firebase
import FirebaseMessaging
import Alamofire
import Resolver
//swiftlint:disable:next identifier_name
@discardableResult func Accounts() -> Account.Service { .shared }
extension Notification.Name {
static let didChangeAccount = Notification.Name("Account.Service.didChangeAccount")
static let didUpdateLock = Notification.Name("Account.Service.didUpdateLock")
static let didUpdateAuthorization = Notification.Name("Account.Service.didUpdateAuthorization")
static let didChangeInterface = Notification.Name("Account.Interface.didUpdateTabBar")
}
extension Common.Model.Key {
fileprivate static let current = Common.Model.Key("Account.Service.current")
fileprivate static let collection = Common.Model.Key("Account.Service.collection")
fileprivate static let didLoadApp = Common.Model.Key("Account.Service.didLoadApp")
fileprivate static let password = Common.Model.Key("Account.Service.password")
fileprivate static let isPushEnabled = Common.Model.Key("Account.Service.isPushEnabled")
fileprivate static let isBiometricksEnabled = Common.Model.Key("Account.Service.isBiometricksEnabled")
fileprivate static let isAccountMigrated = Common.Model.Key("Account.Service.isAccountMigrated")
}
extension Account {
class Service: Common.Service.Provider {
static let shared = Service()
let cityId = 16
private func account(for username: String?, keyType: KeyType) -> Account.Model? {
collection.first(where: { $0.username == username && $0.keyType == keyType })
}
private var _current: String? {
get { Settings()[.current] }
set { Settings()[.current] = newValue }
}
var current: Account.Model? {
get {
let username = _current?.components(separatedBy: "@").first
let keyTypeString = _current?.components(separatedBy: "@").last ?? ""
let keyType = Account.KeyType.init(rawValue: keyTypeString) ?? Account.KeyType.active
if let current = account(for: username, keyType: keyType) { return current }
if let account = collection.first {
_current = "\(account.username)@\(account.keyType.rawValue)"
setup()
}
return account(for: username, keyType: keyType)
}
set {
// removePush()
_current = "\(newValue?.username ?? "")@\(newValue?.keyType.rawValue ?? "")"
setup()
ResolverScope.userSession.reset()
Notification.post(name: .didChangeAccount)
}
}
var exists: Bool { current != nil }
let quota = Account.Service.Resources2()
var isLocked = false {
didSet { Notification.post(name: .didUpdateLock) }
}
let contacts = Contacts()
private var didLoadApp: Bool {
get { Settings()[.didLoadApp] }
set { Settings()[.didLoadApp] = newValue }
}
private init() {
if !didLoadApp {
didLoadApp = true
password = nil
}
updatePush()
}
private func setup() {
updatePush()
quota.fetch(clear: true)
}
}
}
// MARK: - Edit
extension Account.Service {
private(set) var collection: [Account.Model] {
get { Settings()[.collection] ?? [] }
set { Settings()[.collection] = newValue }
}
func add(username: String, privateKey: String, password: String, keyType: Account.KeyType, completion: Completion.Network) {
guard !collection.contains(where: { $0.username == username && $0.keyType == keyType }) else {
completion(.error(L10n.Error.Accounts.exists))
return
}
guard let publicKey = privateKey.publicKey() else {
completion(.error(L10n.Error.Accounts.keyInvalid))
return
}
collection.append(.init(username: username, publicKey: publicKey, privateKey: privateKey, keyType: keyType, password: password))
try? Account.Service.History.shared.load(username: username)
current = collection.last
completion(nil)
}
func remove(username: String, keyType: Account.KeyType) {
guard let index = collection.lastIndex(where: { $0.username == username && $0.keyType == keyType }) else { return }
let value = collection[index]
let current = self.current
collection.remove(at: index)
value.clear()
if value.username == current?.username { self.current = collection.first }
updatePush()
}
func updateCredentialsIfNeeded(accounts: [Account.Model.ChangeKey], password: String) {
let collection = self.collection
for accountToChange in accounts {
guard var account = collection.first(where: {
$0.username == accountToChange.username && $0.keyType.rawValue == accountToChange.permission.permName
}) else { continue }
account.publicKey = accountToChange.publicKey
account.updatePrivateKey(privateKey: accountToChange.privateKey, password: password)
}
self.collection = collection
}
func removeAll() {
collection.forEach({
remove(username: $0.username, keyType: $0.keyType)
})
}
}
// MARK: - Authorization
extension Account.Service {
private var password: String? {
get { Keychain()[biometric: .password] }
set { Keychain()[.password, password: newValue ?? ""] = newValue }
}
var isAuthorized: Bool {
if UserDefaults.standard.bool(forKey: "isPinPwdMode") {
if let pin = Account.Service.Authorize.shared.password,
let password = getPassword(password: pin),
!password.isEmpty,
password == pin,
password.count == 4 {
return true
} else {
return false
}
} else {
return true
}
}
func getPassword(password: String?) -> String? {
if let password = password {
return Keychain()[.password, password: password]
} else {
return Keychain()[biometric: .password]
}
}
var isBiometricksEnabled: Bool {
get { Settings()[.isBiometricksEnabled] }
set { Settings()[.isBiometricksEnabled] = newValue }
}
var isPasswordExists: Bool { Keychain().exist(.password) }
var isFaceIdAvailable: Bool { LAContext().biometryType == .faceID }
var isBiometricksAvailable: Bool {
let context = LAContext()
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) ? context.biometryType != .none : false
}
func checkBiometricks(_ completion: @escaping (Bool) -> Void) {
LAContext().evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Enable bimetricks"
) { (success, _) in
DispatchQueue.main.async { completion(success) }
}
}
func update(password: String, old: String? = nil) {
self.password = password
if let old = old {
collection.forEach({ $0.update(password: password, old: old) })
}
}
func removePassword() { self.password = nil }
func validate(password: String) -> Bool { password.count >= 4 }
}
// MARK: - Push Notifications
extension Account.Service {
var isPushEnabled: Bool {
get { Settings()[.isPushEnabled] }
set { Settings()[.isPushEnabled] = newValue; updatePush() }
}
// private func updatePush() { isPushEnabled ? addPush() : removePush() }
private func updatePush() {
let variables = UpdatePushNotificationDeviceTokenGraphQLRequest.Variables(
deviceToken: Messaging.messaging().fcmToken ?? "",
deviceType: "IOS",
eosAccounts: collection.map({ $0.username }),
langCode: Common.Model.Language.current.rawValue
)
AF.request(
Network.servers.current.paycashQraphql,
method: .post,
parameters: UpdatePushNotificationDeviceTokenGraphQLRequest(variables: variables),
encoder: JSONParameterEncoder.default
)
.responseString(completionHandler: { print($0) })
.responseDecodable(of: GraphQLResponse<UpdatePushNotificationDeviceTokenGraphQLRequest.ResponseData>.self) {
print($0.value?.errors)
}
}
}
// MARK: - Migration
extension Account.Service {
var isAccountMigrated: Bool {
get { Settings()[.isAccountMigrated] }
set { Settings()[.isAccountMigrated] = newValue; }
}
func migrateAccountIfNeeded(password: String) {
if !isAccountMigrated,
let data = UserDefaults.standard.value(forKey: Common.Model.Key.collection.rawValue) as? Data,
let objects = try? JSONDecoder().decode([Account.OldModel].self, from: data) {
collection = []
var accounts: [Account.Model] = []
objects.forEach { oldAccount in
let account = Account.Model(username: oldAccount.username, publicKey: oldAccount.publicKey, keyType: .active)
account.migrate(password: password)
accounts.append(account)
}
collection = accounts
isAccountMigrated = true
Notification.post(name: .didChangeAccount)
}
}
}