298 lines
9.3 KiB
Swift
298 lines
9.3 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 Alamofire
|
|
import Resolver
|
|
import WalletFoundation
|
|
import WalletKit
|
|
import WalletNetwork
|
|
import Combine
|
|
|
|
// swiftlint:disable:next identifier_name
|
|
@discardableResult func Accounts() -> Account.Service { .shared }
|
|
|
|
extension Notification.Name {
|
|
static let didChangeInterface = Notification.Name("Account.Interface.didUpdateTabBar")
|
|
}
|
|
|
|
private let didLoadAppKey = CommonKey("Account.Service.didLoadApp")
|
|
private let isPushEnabledKey = CommonKey("Account.Service.isPushEnabled")
|
|
private let isBiometricksEnabledKey = CommonKey("Account.Service.isBiometricksEnabled")
|
|
private let isAccountMigratedKey = CommonKey("Account.Service.isAccountMigrated")
|
|
|
|
extension Account {
|
|
|
|
final class Service: Common.Service.Provider {
|
|
|
|
let bank: Bank
|
|
let messageShelf: MessageBookShelfService
|
|
|
|
// TODO: - Remove "!"
|
|
static var shared: Service!
|
|
|
|
private var cancellables = Set<AnyCancellable>()
|
|
|
|
let cityId = 16
|
|
|
|
var collection: [WalletKit.Wallet] { self.bank.wallets }
|
|
|
|
var current: WalletKit.Wallet? {
|
|
get { self.bank.active }
|
|
set {
|
|
guard let wallet = newValue else { return }
|
|
do {
|
|
try self.bank.activate(wallet: wallet)
|
|
|
|
self.setup()
|
|
ResolverScope.userSession.reset()
|
|
} catch {}
|
|
}
|
|
}
|
|
|
|
var activePublisher: AnyPublisher<WalletKit.Wallet?, Never> { self.bank.activePublisher }
|
|
|
|
var accountsPublisher: AnyPublisher<[WalletKit.Wallet], Never> { self.bank.walletsPublisher }
|
|
|
|
lazy var lockedPublisher: AnyPublisher<Bool, Never> = self.lockedSubject.eraseToAnyPublisher()
|
|
|
|
let quota = Account.Service.CommonResources()
|
|
|
|
private let lockedSubject = CurrentValueSubject<Bool, Never>(false)
|
|
|
|
var isLocked: Bool {
|
|
get { self.lockedSubject.value }
|
|
set { self.lockedSubject.send(newValue) }
|
|
}
|
|
|
|
var hasActiveAccounts: Bool {
|
|
let collection = self.collection
|
|
guard !collection.isEmpty else { return false }
|
|
let filteredAccounts = collection
|
|
.filter {
|
|
switch $0.state {
|
|
case .declined, .pending:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
guard !filteredAccounts.isEmpty else { return false }
|
|
return true
|
|
}
|
|
|
|
let contacts = Contacts()
|
|
|
|
init(using bank: Bank) {
|
|
|
|
self.bank = bank
|
|
self.messageShelf = MessageBookShelfService(with: bank)
|
|
|
|
if !self.didLoadApp {
|
|
self.didLoadApp = true
|
|
}
|
|
|
|
self.setup()
|
|
}
|
|
|
|
func fetchQuota(force: Bool = false) {
|
|
self.quota.fetch(using: self.bank.active, force: force)
|
|
}
|
|
|
|
private var didLoadApp: Bool {
|
|
get { Settings.shared[didLoadAppKey] }
|
|
set { Settings.shared[didLoadAppKey] = newValue }
|
|
}
|
|
|
|
private func setup() {
|
|
self.quota.fetch(using: self.bank.active, clear: true)
|
|
|
|
self.accountsPublisher.sink { [weak self] _ in self?.updatePush() }.store(in: &self.cancellables)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Edit
|
|
extension Account.Service {
|
|
|
|
func addAccounts(purses: [Purse], password: String) throws {
|
|
|
|
try self.bank.add(using: purses, password: password)
|
|
|
|
if let wallet = self.collection.last,
|
|
let book = self.messageShelf.book(for: wallet) {
|
|
|
|
book.fetch()
|
|
|
|
try self.bank.activate(wallet: wallet)
|
|
}
|
|
}
|
|
|
|
func remove(wallet: WalletKit.Wallet, isSingle: Bool = true) {
|
|
|
|
try? self.bank.remove(wallet: wallet)
|
|
|
|
guard isSingle else { return }
|
|
|
|
self.selectDefaultAccount()
|
|
}
|
|
|
|
func updateCredentialsIfNeeded(accounts: [ChangeKeyModel], password: String) {
|
|
let collection = self.collection
|
|
for accountToChange in accounts {
|
|
guard let account = collection.first(where: {
|
|
$0.name == accountToChange.wallet.name && $0.keyType == accountToChange.wallet.keyType
|
|
}) else { continue }
|
|
|
|
account.updateKeychain(key: accountToChange.key, using: password)
|
|
}
|
|
}
|
|
|
|
func migrateChats(accounts: [ChangeKeyModel], password: String) {
|
|
for accountToChange in accounts {
|
|
|
|
guard let oldPrivateKey = Accounts().collection.first(where: { $0.name == accountToChange.wallet.name
|
|
&& $0.keyType == accountToChange.keyType })?.privateKey(password: password) else { continue }
|
|
|
|
guard let msgsService = try? CryptoChat.Service.MsgsHistory(username: accountToChange.wallet.name, encryptionKey: oldPrivateKey) else { continue }
|
|
|
|
msgsService.copy(username: accountToChange.wallet.name, privateKey: accountToChange.privateKey, publicKey: accountToChange.publicKey)
|
|
|
|
let hasOwnerAccount = Accounts().collection
|
|
.contains(where: { $0.name == accountToChange.wallet.name && $0.keyType == WalletKeyType.owner})
|
|
|
|
if !hasOwnerAccount {
|
|
msgsService.removeDatabase()
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
func removeAll() {
|
|
let wallets = self.bank.wallets
|
|
wallets.forEach { self.remove(wallet: $0, isSingle: false) }
|
|
}
|
|
|
|
private func selectDefaultAccount() {
|
|
|
|
let activeWallets = self.collection.filter { $0.state == .accepted }
|
|
if activeWallets.count == 0 {
|
|
self.current = self.collection.filter {
|
|
switch $0.state {
|
|
case .declined, .pending: return false
|
|
default: return true
|
|
}
|
|
}.first
|
|
} else {
|
|
self.current = activeWallets.first
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Remove accounts
|
|
|
|
extension Account {
|
|
|
|
static func reset() {
|
|
if let service = Self.Service.shared {
|
|
service.removeAll()
|
|
} else {
|
|
Village.reset()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Authorization
|
|
extension Account.Service {
|
|
|
|
var isAuthorized: Bool {
|
|
if UserDefaults.standard.bool(forKey: "isPinPwdMode") {
|
|
if let pin = Account.Service.Authorize.shared.password,
|
|
Self.check(password: pin),
|
|
!pin.isEmpty,
|
|
pin.count == 4 {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
} else {
|
|
return true
|
|
}
|
|
}
|
|
|
|
static var isPasswordExists: Bool { Village.isPasswordExists }
|
|
|
|
static var isBiometricksEnabled: Bool {
|
|
get { Settings.shared[isBiometricksEnabledKey] }
|
|
set { Settings.shared[isBiometricksEnabledKey] = newValue }
|
|
}
|
|
|
|
static var isAccountMigrated: Bool {
|
|
get { Settings.shared[isAccountMigratedKey] }
|
|
set { Settings.shared[isAccountMigratedKey] = newValue }
|
|
}
|
|
|
|
static var isFaceIdAvailable: Bool { LAContext().biometryType == .faceID }
|
|
static var isBiometricksAvailable: Bool {
|
|
let context = LAContext()
|
|
return context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) ? context.biometryType != .none : false
|
|
}
|
|
static func checkBiometricks(_ completion: @escaping (Bool) -> Void) {
|
|
LAContext().evaluatePolicy(
|
|
.deviceOwnerAuthenticationWithBiometrics,
|
|
localizedReason: "Enable bimetricks"
|
|
) { (success, _) in
|
|
Self.isBiometricksEnabled = success
|
|
DispatchQueue.main.async { completion(success) }
|
|
}
|
|
}
|
|
|
|
static func check(password: String) -> Bool { Village.accept(password: password) }
|
|
|
|
static func passwordViaBiometrics() -> String? { Village.passwordViaBiometrics() }
|
|
|
|
func update(password: String, old: String? = nil) throws {
|
|
try old >>- { try self.bank.switchPassword(password, old: $0) }
|
|
}
|
|
|
|
func validate(password value: String) -> Bool { value.count >= 4 }
|
|
}
|
|
|
|
// MARK: - Push Notifications
|
|
extension Account.Service {
|
|
|
|
func updatePush(token: String? = nil) {
|
|
|
|
if let token {
|
|
Settings.shared[isPushEnabledKey] = token
|
|
self.updatePush()
|
|
return
|
|
}
|
|
|
|
guard let pushToken: String = Settings.shared[isPushEnabledKey] else { return }
|
|
|
|
let record = ApplicationEnvironment.shared().current
|
|
let collection = self.collection
|
|
|
|
Task {
|
|
let service = try AccountService(environment: record.networkEnvironment())
|
|
do {
|
|
try await service.updateNotification(token: pushToken,
|
|
accounts: collection.map { $0.name },
|
|
language: Common.Model.Language.current.rawValue)
|
|
} catch {
|
|
print(error)
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
}
|