305 lines
11 KiB
Swift
305 lines
11 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
|
|
import WalletKit
|
|
import WalletNetwork
|
|
|
|
//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.CommonResources()
|
|
|
|
var isLocked = false {
|
|
didSet { Notification.post(name: .didUpdateLock) }
|
|
}
|
|
|
|
var hasActiveAccounts: Bool {
|
|
guard !self.collection.isEmpty else { return false }
|
|
let filteredAccounts = self.collection
|
|
.filter {
|
|
switch $0.state {
|
|
case .declined, .pending:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
guard !filteredAccounts.isEmpty else { return false }
|
|
return true
|
|
}
|
|
|
|
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 addAccounts(usernames: [String], privateKey: String, password: String, keyType: Account.KeyType, state: WalletState) throws {
|
|
|
|
guard let publicKey = privateKey.publicKey() else {
|
|
throw NSError.error(L10n.Error.Accounts.keyInvalid)
|
|
}
|
|
|
|
for username in usernames {
|
|
guard !collection.contains(where: { $0.username == username && $0.keyType == keyType }) else {
|
|
if usernames.count == 1 {
|
|
throw NSError.error(L10n.Error.Accounts.exists)
|
|
}
|
|
continue
|
|
}
|
|
|
|
collection.append(.init(username: username,
|
|
publicKey: publicKey,
|
|
privateKey: privateKey,
|
|
keyType: keyType,
|
|
password: password,
|
|
state: state))
|
|
}
|
|
|
|
try? Account.Service.History.shared.load(username: collection.last?.username ?? "")
|
|
current = collection.last
|
|
}
|
|
|
|
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 migrateChats(accounts: [Account.Model.ChangeKey], oldPrivateKey: String) {
|
|
for accountToChange in accounts {
|
|
guard let msgsService = try? CryptoChat.Service.MsgsHistory(username: accountToChange.username, encryptionKey: oldPrivateKey) else { continue }
|
|
msgsService.copy(username: accountToChange.username, privateKey: accountToChange.privateKey, publicKey: accountToChange.publicKey)
|
|
msgsService.removeDatabase()
|
|
}
|
|
}
|
|
|
|
func removeAll() {
|
|
collection.forEach({
|
|
remove(username: $0.username, keyType: $0.keyType)
|
|
})
|
|
}
|
|
}
|
|
|
|
// MARK: - Authorization
|
|
extension Account.Service {
|
|
private var password: String? {
|
|
get { WalletKeychain.instance[biometric: .password] }
|
|
set { WalletKeychain.instance[.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 WalletKeychain.instance[.password, password: password]
|
|
} else {
|
|
return WalletKeychain.instance[biometric: .password]
|
|
}
|
|
}
|
|
var isBiometricksEnabled: Bool {
|
|
get { Settings()[.isBiometricksEnabled] }
|
|
set { Settings()[.isBiometricksEnabled] = newValue }
|
|
}
|
|
var isPasswordExists: Bool { WalletKeychain.instance.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() {
|
|
// TODO: - Update only when have accounts!
|
|
guard let url = URL(string: ApplicationEnvironment.shared().current.paycash.paycashGraphQl),
|
|
let token = Messaging.messaging().fcmToken else {
|
|
return
|
|
}
|
|
|
|
let environment = CommonEnvironment(url: url)
|
|
let service = AccountService(enmironment: environment)
|
|
Task {
|
|
await service.updateNotification(token: token,
|
|
accounts: collection.map { $0.username },
|
|
language: Common.Model.Language.current.rawValue)
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
state: .accepted)
|
|
account.migrate(password: password)
|
|
accounts.append(account)
|
|
// try? Account.Service.History.shared.load(username: account.username)
|
|
}
|
|
collection = accounts
|
|
Notification.post(name: .didChangeAccount)
|
|
}
|
|
isAccountMigrated = true
|
|
}
|
|
}
|