666 lines
33 KiB
Swift
666 lines
33 KiB
Swift
//
|
|
// RootInitialization.swift
|
|
// Zashi
|
|
//
|
|
// Created by Lukáš Korba on 01.12.2022.
|
|
//
|
|
|
|
import ComposableArchitecture
|
|
import Foundation
|
|
import ZcashLightClientKit
|
|
import Models
|
|
import NotEnoughFreeSpace
|
|
import Utils
|
|
import Generated
|
|
import WalletStorage
|
|
|
|
/// In this file is a collection of helpers that control all state and action related operations
|
|
/// for the `Root` with a connection to the app/wallet initialization and erasure of the wallet.
|
|
extension Root {
|
|
public enum Constants {
|
|
static let udIsRestoringWallet = "udIsRestoringWallet"
|
|
static let udLeavesScreenOpen = "udLeaves_screen_open"
|
|
static let noAuthenticationWithinXMinutes = 15
|
|
}
|
|
|
|
public enum InitializationAction {
|
|
case appDelegate(AppDelegateAction)
|
|
case checkBackupPhraseValidation
|
|
case checkRestoreWalletFlag(SyncStatus)
|
|
case checkWalletInitialization
|
|
case checkWalletConfig
|
|
case initializeSDK(WalletInitMode)
|
|
case initialSetups
|
|
case initializationFailed(ZcashError)
|
|
case initializationSuccessfullyDone
|
|
case loadedWalletAccounts([WalletAccount])
|
|
case resetZashi
|
|
case resetZashiRequest
|
|
case resetZashiRequestCanceled
|
|
case respondToWalletInitializationState(InitializationState)
|
|
case restoreExistingWallet
|
|
case seedValidationResult(Bool)
|
|
case synchronizerStartFailed(ZcashError)
|
|
case registerForSynchronizersUpdate
|
|
case retryStart
|
|
case walletConfigChanged(WalletConfig)
|
|
}
|
|
|
|
// swiftlint:disable:next cyclomatic_complexity function_body_length
|
|
public func initializationReduce() -> Reduce<Root.State, Root.Action> {
|
|
Reduce { state, action in
|
|
switch action {
|
|
case .initialization(.appDelegate(.didFinishLaunching)):
|
|
state.appStartState = .didFinishLaunching
|
|
// TODO: [#704], trigger the review request logic when approved by the team,
|
|
// https://github.com/Electric-Coin-Company/zashi-ios/issues/704
|
|
return .run { send in
|
|
try await mainQueue.sleep(for: .seconds(0.5))
|
|
await send(.initialization(.initialSetups))
|
|
}
|
|
.cancellable(id: DidFinishLaunchingId, cancelInFlight: true)
|
|
|
|
case .initialization(.appDelegate(.willEnterForeground)):
|
|
if state.featureFlags.appLaunchBiometric {
|
|
let now = Date()
|
|
let before = Date.init(timeIntervalSince1970: TimeInterval(state.lastAuthenticationTimestamp))
|
|
if let xMinutesAgo = Calendar.current.date(byAdding: .minute, value: -Constants.noAuthenticationWithinXMinutes, to: now),
|
|
before < xMinutesAgo {
|
|
state.splashAppeared = false
|
|
}
|
|
}
|
|
state.appStartState = .willEnterForeground
|
|
if state.isLockedInKeychainUnavailableState || !sdkSynchronizer.latestState().syncStatus.isPrepared {
|
|
return .send(.initialization(.initialSetups))
|
|
} else {
|
|
return .send(.initialization(.retryStart))
|
|
}
|
|
|
|
case .initialization(.appDelegate(.didEnterBackground)):
|
|
sdkSynchronizer.stop()
|
|
state.bgTask?.setTaskCompleted(success: false)
|
|
state.bgTask = nil
|
|
state.appStartState = .didEnterBackground
|
|
state.isLockedInKeychainUnavailableState = false
|
|
return .cancel(id: CancelStateId)
|
|
|
|
case .initialization(.appDelegate(.backgroundTask(let task))):
|
|
let keysPresent: Bool = (try? walletStorage.areKeysPresent()) ?? false
|
|
if state.appStartState == .didFinishLaunching {
|
|
state.appStartState = .backgroundTask
|
|
if keysPresent {
|
|
state.bgTask = task
|
|
return .none
|
|
} else {
|
|
state.isLockedInKeychainUnavailableState = true
|
|
task.setTaskCompleted(success: false)
|
|
return .cancel(id: DidFinishLaunchingId)
|
|
}
|
|
} else {
|
|
state.bgTask = task
|
|
state.appStartState = .backgroundTask
|
|
return .run { send in
|
|
await send(.initialization(.retryStart))
|
|
}
|
|
}
|
|
|
|
case .synchronizerStateChanged(let latestState):
|
|
let snapshot = SyncStatusSnapshot.snapshotFor(state: latestState.data.syncStatus)
|
|
|
|
guard let account = state.selectedWalletAccount else {
|
|
return .none
|
|
}
|
|
|
|
// update flexa balance
|
|
if let accountBalance = latestState.data.accountsBalances[account.id] {
|
|
let shieldedBalance = accountBalance.saplingBalance.spendableValue + accountBalance.orchardBalance.spendableValue
|
|
let shieldedWithPendingBalance = accountBalance.saplingBalance.total() + accountBalance.orchardBalance.total()
|
|
|
|
flexaHandler.updateBalance(shieldedWithPendingBalance, shieldedBalance)
|
|
}
|
|
|
|
// handle possible service unavailability
|
|
if case .error(let error) = snapshot.syncStatus, checkUnavailableService(error) {
|
|
if state.walletStatus != .disconnected {
|
|
state.alert = AlertState.serviceUnavailable()
|
|
}
|
|
state.wasRestoringWhenDisconnected = state.walletStatus == .restoring
|
|
state.$walletStatus.withLock { $0 = .disconnected }
|
|
} else if case .syncing = snapshot.syncStatus, state.walletStatus == .disconnected {
|
|
state.$walletStatus.withLock { $0 = state.wasRestoringWhenDisconnected ? .restoring : .none }
|
|
}
|
|
|
|
// handle BCGTask
|
|
guard state.bgTask != nil else {
|
|
return .send(.initialization(.checkRestoreWalletFlag(snapshot.syncStatus)))
|
|
}
|
|
|
|
var finishBGTask = false
|
|
var successOfBGTask = false
|
|
|
|
switch snapshot.syncStatus {
|
|
case .upToDate:
|
|
successOfBGTask = true
|
|
finishBGTask = true
|
|
case .stopped, .error:
|
|
successOfBGTask = false
|
|
finishBGTask = true
|
|
default: break
|
|
}
|
|
|
|
if finishBGTask {
|
|
LoggerProxy.event("BGTask setTaskCompleted(success: \(successOfBGTask)) from TCA")
|
|
state.bgTask?.setTaskCompleted(success: successOfBGTask)
|
|
state.bgTask = nil
|
|
return .cancel(id: CancelStateId)
|
|
}
|
|
|
|
return .send(.initialization(.checkRestoreWalletFlag(snapshot.syncStatus)))
|
|
|
|
case .initialization(.checkRestoreWalletFlag(let syncStatus)):
|
|
if state.isRestoringWallet && syncStatus == .upToDate {
|
|
state.isRestoringWallet = false
|
|
userDefaults.remove(Constants.udIsRestoringWallet)
|
|
state.$walletStatus.withLock { $0 = .none }
|
|
}
|
|
return .none
|
|
|
|
case .initialization(.synchronizerStartFailed):
|
|
return .none
|
|
|
|
case .initialization(.retryStart):
|
|
if !diskSpaceChecker.hasEnoughFreeSpaceForSync() {
|
|
state.destinationState.preNotEnoughFreeSpaceDestination = state.destinationState.internalDestination
|
|
return .send(.destination(.updateDestination(.notEnoughFreeSpace)))
|
|
} else if let preNotEnoughFreeSpaceDestination = state.destinationState.preNotEnoughFreeSpaceDestination {
|
|
state.destinationState.internalDestination = preNotEnoughFreeSpaceDestination
|
|
state.destinationState.preNotEnoughFreeSpaceDestination = nil
|
|
}
|
|
// Try the start only if the synchronizer has been already prepared
|
|
guard sdkSynchronizer.latestState().syncStatus.isPrepared else {
|
|
return .none
|
|
}
|
|
return .run { [state] send in
|
|
do {
|
|
try await sdkSynchronizer.start(true)
|
|
if state.bgTask != nil {
|
|
LoggerProxy.event("BGTask synchronizer.start() PASSED")
|
|
}
|
|
await send(.initialization(.registerForSynchronizersUpdate))
|
|
} catch {
|
|
if state.bgTask != nil {
|
|
LoggerProxy.event("BGTask synchronizer.start() failed \(error.toZcashError())")
|
|
}
|
|
await send(.initialization(.synchronizerStartFailed(error.toZcashError())))
|
|
}
|
|
}
|
|
|
|
case .initialization(.registerForSynchronizersUpdate):
|
|
let stateStreamEffect = Effect.publisher {
|
|
sdkSynchronizer.stateStream()
|
|
.throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
|
|
.map { $0.redacted }
|
|
.map(Root.Action.synchronizerStateChanged)
|
|
}
|
|
.cancellable(id: CancelStateId, cancelInFlight: true)
|
|
if state.bgTask != nil {
|
|
return stateStreamEffect
|
|
} else {
|
|
return .merge(
|
|
stateStreamEffect,
|
|
.send(.home(.smartBanner(.evaluatePriority1)))
|
|
)
|
|
}
|
|
|
|
case .initialization(.checkWalletConfig):
|
|
return .publisher {
|
|
walletConfigProvider.load()
|
|
.receive(on: mainQueue)
|
|
.map(Root.Action.walletConfigLoaded)
|
|
}
|
|
.cancellable(id: WalletConfigCancelId, cancelInFlight: true)
|
|
|
|
case .walletConfigLoaded(let walletConfig):
|
|
if walletConfig == WalletConfig.initial {
|
|
return .send(.initialization(.initialSetups))
|
|
} else {
|
|
return .send(.initialization(.walletConfigChanged(walletConfig)))
|
|
}
|
|
|
|
case .initialization(.walletConfigChanged(let walletConfig)):
|
|
return .concatenate(
|
|
.send(.updateStateAfterConfigUpdate(walletConfig)),
|
|
.send(.initialization(.initialSetups))
|
|
)
|
|
|
|
case .initialization(.initialSetups):
|
|
if !diskSpaceChecker.hasEnoughFreeSpaceForSync() {
|
|
state.destinationState.preNotEnoughFreeSpaceDestination = state.destinationState.internalDestination
|
|
return .send(.destination(.updateDestination(.notEnoughFreeSpace)))
|
|
} else if let preNotEnoughFreeSpaceDestination = state.destinationState.preNotEnoughFreeSpaceDestination {
|
|
state.destinationState.internalDestination = preNotEnoughFreeSpaceDestination
|
|
state.destinationState.preNotEnoughFreeSpaceDestination = nil
|
|
}
|
|
// TODO: [#524] finish all the wallet events according to definition, https://github.com/Electric-Coin-Company/zashi-ios/issues/524
|
|
LoggerProxy.event(".appDelegate(.didFinishLaunching)")
|
|
/// We need to fetch data from keychain, in order to be 100% sure the keychain can be read we delay the check a bit
|
|
return .send(.initialization(.checkWalletInitialization))
|
|
|
|
/// Evaluate the wallet's state based on keychain keys and database files presence
|
|
case .initialization(.checkWalletInitialization):
|
|
let walletState = Root.walletInitializationState(
|
|
databaseFiles: databaseFiles,
|
|
walletStorage: walletStorage,
|
|
zcashNetwork: zcashSDKEnvironment.network
|
|
)
|
|
return .send(.initialization(.respondToWalletInitializationState(walletState)))
|
|
|
|
/// Respond to all possible states of the wallet and initiate appropriate side effects including errors handling
|
|
case .initialization(.respondToWalletInitializationState(let walletState)):
|
|
switch walletState {
|
|
case .osStatus(let osStatus):
|
|
state.osStatusErrorState.osStatus = osStatus
|
|
return .send(.destination(.updateDestination(.osStatusError)))
|
|
case .failed:
|
|
state.appInitializationState = .failed
|
|
state.alert = AlertState.walletStateFailed(walletState)
|
|
return .none
|
|
case .keysMissing:
|
|
state.appInitializationState = .keysMissing
|
|
return .send(.destination(.updateDestination(.onboarding)))
|
|
case .filesMissing:
|
|
state.appInitializationState = .filesMissing
|
|
state.isRestoringWallet = true
|
|
userDefaults.setValue(true, Constants.udIsRestoringWallet)
|
|
state.$walletStatus.withLock { $0 = .restoring }
|
|
return .concatenate(
|
|
.send(.initialization(.initializeSDK(.restoreWallet))),
|
|
.send(.initialization(.checkBackupPhraseValidation))
|
|
)
|
|
case .initialized:
|
|
if let isRestoringWallet = userDefaults.objectForKey(Constants.udIsRestoringWallet) as? Bool, isRestoringWallet {
|
|
state.isRestoringWallet = true
|
|
state.$walletStatus.withLock { $0 = .restoring }
|
|
return .concatenate(
|
|
.send(.initialization(.initializeSDK(.restoreWallet))),
|
|
.send(.initialization(.checkBackupPhraseValidation))
|
|
)
|
|
}
|
|
return .concatenate(
|
|
.send(.initialization(.initializeSDK(.existingWallet))),
|
|
.send(.initialization(.checkBackupPhraseValidation))
|
|
)
|
|
case .uninitialized:
|
|
state.appInitializationState = .uninitialized
|
|
return .run { send in
|
|
try await mainQueue.sleep(for: .seconds(0.5))
|
|
await send(.destination(.updateDestination(.onboarding)))
|
|
}
|
|
.cancellable(id: CancelId, cancelInFlight: true)
|
|
}
|
|
|
|
/// Stored wallet is present, database files may or may not be present, trying to initialize app state variables and environments.
|
|
/// When initialization succeeds user is taken to the home screen.
|
|
case .initialization(.initializeSDK(let walletMode)):
|
|
do {
|
|
let storedWallet: StoredWallet
|
|
do {
|
|
storedWallet = try walletStorage.exportWallet()
|
|
} catch {
|
|
return .send(.destination(.updateDestination(.osStatusError)))
|
|
}
|
|
let birthday = storedWallet.birthday?.value() ?? zcashSDKEnvironment.latestCheckpoint
|
|
try mnemonic.isValid(storedWallet.seedPhrase.value())
|
|
let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value())
|
|
|
|
return .run { send in
|
|
do {
|
|
try await sdkSynchronizer.prepareWith(
|
|
seedBytes,
|
|
birthday,
|
|
walletMode,
|
|
L10n.Accounts.zashi,
|
|
L10n.Accounts.zashi.lowercased()
|
|
)
|
|
|
|
let walletAccounts = try await sdkSynchronizer.walletAccounts()
|
|
await send(.initialization(.loadedWalletAccounts(walletAccounts)))
|
|
await send(.resolveMetadataEncryptionKeys)
|
|
|
|
try await sdkSynchronizer.start(false)
|
|
|
|
var selectedAccount: WalletAccount?
|
|
|
|
for account in walletAccounts {
|
|
if account.vendor == .zcash {
|
|
selectedAccount = account
|
|
}
|
|
}
|
|
|
|
if let account = selectedAccount {
|
|
let addressBookEncryptionKeys = try? walletStorage.exportAddressBookEncryptionKeys()
|
|
if addressBookEncryptionKeys == nil {
|
|
do {
|
|
var keys = AddressBookEncryptionKeys.empty
|
|
try keys.cacheFor(
|
|
seed: seedBytes,
|
|
account: account.account,
|
|
network: zcashSDKEnvironment.network.networkType
|
|
)
|
|
try walletStorage.importAddressBookEncryptionKeys(keys)
|
|
} catch {
|
|
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
|
|
}
|
|
}
|
|
|
|
await send(.initialization(.initializationSuccessfullyDone))
|
|
} else {
|
|
await send(.initialization(.initializationSuccessfullyDone))
|
|
}
|
|
} catch {
|
|
await send(.initialization(.initializationFailed(error.toZcashError())))
|
|
}
|
|
}
|
|
} catch {
|
|
return .send(.initialization(.initializationFailed(error.toZcashError())))
|
|
}
|
|
|
|
case .initialization(.initializationSuccessfullyDone):
|
|
return .merge(
|
|
.send(.initialization(.registerForSynchronizersUpdate)),
|
|
.publisher {
|
|
autolockHandler.batteryStatePublisher()
|
|
.map(Root.Action.batteryStateChanged)
|
|
}
|
|
.cancellable(id: CancelBatteryStateId, cancelInFlight: true),
|
|
.send(.batteryStateChanged(nil)),
|
|
.send(.observeTransactions),
|
|
.send(.observeShieldingProcessor)
|
|
)
|
|
|
|
case .initialization(.loadedWalletAccounts(let walletAccounts)):
|
|
state.$walletAccounts.withLock { $0 = walletAccounts }
|
|
if state.selectedWalletAccount == nil {
|
|
for account in walletAccounts {
|
|
if account.vendor == .zcash {
|
|
state.$selectedWalletAccount.withLock { $0 = account }
|
|
state.$zashiWalletAccount.withLock { $0 = account }
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return .merge(
|
|
.send(.loadContacts),
|
|
.send(.loadUserMetadata)
|
|
)
|
|
|
|
case .resolveMetadataEncryptionKeys:
|
|
do {
|
|
let storedWallet: StoredWallet
|
|
do {
|
|
storedWallet = try walletStorage.exportWallet()
|
|
} catch {
|
|
return .send(.destination(.updateDestination(.osStatusError)))
|
|
}
|
|
try mnemonic.isValid(storedWallet.seedPhrase.value())
|
|
let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value())
|
|
|
|
return .run { [walletAccounts = state.walletAccounts] send in
|
|
do {
|
|
|
|
for account in walletAccounts {
|
|
let userMetadataEncryptionKeys = try? walletStorage.exportUserMetadataEncryptionKeys(account.account)
|
|
if userMetadataEncryptionKeys == nil {
|
|
do {
|
|
var keys = UserMetadataEncryptionKeys.empty
|
|
try keys.cacheFor(
|
|
seed: seedBytes,
|
|
account: account.account,
|
|
network: zcashSDKEnvironment.network.networkType
|
|
)
|
|
try walletStorage.importUserMetadataEncryptionKeys(keys, account.account)
|
|
} catch {
|
|
// TODO: [#1408] error handling https://github.com/Electric-Coin-Company/zashi-ios/issues/1408
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch { }
|
|
return .none
|
|
|
|
case .initialization(.checkBackupPhraseValidation):
|
|
do {
|
|
let _ = try walletStorage.exportWallet()
|
|
} catch {
|
|
return .send(.destination(.updateDestination(.osStatusError)))
|
|
}
|
|
|
|
state.appInitializationState = .initialized
|
|
let isAtDeeplinkWarningScreen = state.destinationState.destination == .deeplinkWarning
|
|
|
|
return .run { send in
|
|
try await mainQueue.sleep(for: .seconds(0.5))
|
|
if !isAtDeeplinkWarningScreen {
|
|
await send(.destination(.updateDestination(Root.DestinationState.Destination.home)))
|
|
}
|
|
}
|
|
.cancellable(id: CancelId, cancelInFlight: true)
|
|
|
|
case .initialization(.resetZashiRequest):
|
|
state.alert = AlertState.wipeRequest()
|
|
return .none
|
|
|
|
case .initialization(.resetZashiRequestCanceled):
|
|
state.alert = nil
|
|
for (id, element) in zip(state.settingsState.path.ids, state.settingsState.path) {
|
|
if element.is(\.resetZashi) {
|
|
return .send(.settings(.path(.element(id: id, action: .resetZashi(.deleteCanceled)))))
|
|
}
|
|
}
|
|
return .none
|
|
|
|
case .initialization(.resetZashi):
|
|
guard let wipePublisher = sdkSynchronizer.wipe() else {
|
|
return .send(.resetZashiSDKFailed)
|
|
}
|
|
return .publisher {
|
|
wipePublisher
|
|
.replaceEmpty(with: Void())
|
|
.map { _ in return Root.Action.resetZashiSDKSucceeded }
|
|
.replaceError(with: Root.Action.resetZashiSDKFailed)
|
|
.receive(on: mainQueue)
|
|
}
|
|
.cancellable(id: SynchronizerCancelId, cancelInFlight: true)
|
|
|
|
case .resetZashiSDKSucceeded:
|
|
if state.appInitializationState != .keysMissing {
|
|
state = .initial
|
|
}
|
|
state.splashAppeared = true
|
|
state.isRestoringWallet = false
|
|
userDefaults.remove(Constants.udIsRestoringWallet)
|
|
userDefaults.remove(Constants.udLeavesScreenOpen)
|
|
flexaHandler.signOut()
|
|
userStoredPreferences.removeAll()
|
|
try? readTransactionsStorage.resetZashi()
|
|
state.walletAccounts.forEach { account in
|
|
try? userMetadataProvider.resetAccount(account.account)
|
|
}
|
|
try? userMetadataProvider.reset()
|
|
state.$walletStatus.withLock { $0 = .none }
|
|
state.$selectedWalletAccount.withLock { $0 = nil }
|
|
// state.$selectedWalletAccountsUA.withLock { $0 = nil }
|
|
state.$walletAccounts.withLock { $0 = [] }
|
|
state.$zashiWalletAccount.withLock { $0 = nil }
|
|
state.$transactionMemos.withLock { $0 = [:] }
|
|
state.$addressBookContacts.withLock { $0 = .empty }
|
|
state.$transactions.withLock { $0 = [] }
|
|
state.path = nil
|
|
|
|
return .send(.resetZashiKeychainRequest)
|
|
|
|
case .resetZashiKeychainRequest:
|
|
return .run { send in
|
|
do {
|
|
try walletStorage.resetZashi()
|
|
await send(.resetZashiFinishProcessing)
|
|
} catch WalletStorage.KeychainError.unknown(let osStatus) {
|
|
await send(.resetZashiKeychainFailed(osStatus))
|
|
}
|
|
}
|
|
|
|
case .resetZashiFinishProcessing:
|
|
do {
|
|
let areKeysPresent = try walletStorage.areKeysPresent()
|
|
if areKeysPresent {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData("Keychain keys are still present"))
|
|
}
|
|
} catch WalletStorage.WalletStorageError.alreadyImported {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData("alreadyImported"))
|
|
} catch WalletStorage.WalletStorageError.uninitializedAddressBookEncryptionKeys {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData("uninitializedAddressBookEncryptionKeys"))
|
|
} catch WalletStorage.WalletStorageError.storageError(let error) {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData("storageError, \(error.localizedDescription)"))
|
|
} catch WalletStorage.WalletStorageError.unsupportedVersion(let version) {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData("unsupportedVersion \(version)"))
|
|
} catch WalletStorage.WalletStorageError.unsupportedLanguage(let language) {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData("unsupportedLanguage, \(language)"))
|
|
} catch WalletStorage.KeychainError.decoding {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData("decoding"))
|
|
} catch WalletStorage.KeychainError.duplicate {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData("duplicate"))
|
|
} catch WalletStorage.KeychainError.encoding {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData("encoding"))
|
|
} catch WalletStorage.KeychainError.noDataFound {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData("noDataFound"))
|
|
} catch WalletStorage.KeychainError.unknown(let osStatus) {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData("unknown, OSStatus \(osStatus)"))
|
|
} catch WalletStorage.WalletStorageError.uninitializedWallet {
|
|
// this is valid state and what we expect
|
|
} catch {
|
|
return .send(.resetZashiKeychainFailedWithCorruptedData(error.localizedDescription))
|
|
}
|
|
if state.appInitializationState == .keysMissing && state.onboardingState.destination == .importExistingWallet {
|
|
state.appInitializationState = .uninitialized
|
|
return .cancel(id: SynchronizerCancelId)
|
|
} else if state.appInitializationState == .keysMissing && state.onboardingState.destination == .createNewWallet {
|
|
state.appInitializationState = .uninitialized
|
|
return .concatenate(
|
|
.cancel(id: SynchronizerCancelId),
|
|
.send(.onboarding(.createNewWalletRequested))
|
|
)
|
|
} else {
|
|
return .concatenate(
|
|
.cancel(id: SynchronizerCancelId),
|
|
.send(.initialization(.checkWalletInitialization))
|
|
)
|
|
}
|
|
|
|
case .resetZashiKeychainFailedWithCorruptedData(let errMsg):
|
|
for element in state.settingsState.path {
|
|
if case .resetZashi(var resetZashiState) = element {
|
|
resetZashiState.isProcessing = false
|
|
break
|
|
}
|
|
}
|
|
state.alert = AlertState.wipeKeychainFailed(errMsg)
|
|
return .cancel(id: SynchronizerCancelId)
|
|
|
|
case .resetZashiKeychainFailed(let osStatus):
|
|
guard state.maxResetZashiAppAttempts == 0 else {
|
|
state.maxResetZashiAppAttempts -= 1
|
|
return .send(.resetZashiKeychainRequest)
|
|
}
|
|
state.maxResetZashiAppAttempts = ResetZashiConstants.maxResetZashiAppAttempts
|
|
for element in state.settingsState.path {
|
|
if case .resetZashi(var resetZashiState) = element {
|
|
resetZashiState.isProcessing = false
|
|
break
|
|
}
|
|
}
|
|
state.alert = AlertState.wipeFailed(osStatus)
|
|
return .cancel(id: SynchronizerCancelId)
|
|
|
|
case .resetZashiSDKFailed:
|
|
guard state.maxResetZashiSDKAttempts == 0 else {
|
|
state.maxResetZashiSDKAttempts -= 1
|
|
return .concatenate(
|
|
.cancel(id: SynchronizerCancelId),
|
|
.send(.initialization(.resetZashi))
|
|
)
|
|
}
|
|
state.maxResetZashiSDKAttempts = ResetZashiConstants.maxResetZashiSDKAttempts
|
|
for element in state.settingsState.path {
|
|
if case .resetZashi(var resetZashiState) = element {
|
|
resetZashiState.isProcessing = false
|
|
break
|
|
}
|
|
}
|
|
state.alert = AlertState.wipeFailed(Int32.max)
|
|
return .cancel(id: SynchronizerCancelId)
|
|
|
|
case .phraseDisplay(.finishedTapped), .onboarding(.newWalletSuccessfulyCreated):
|
|
state.destinationState.destination = .home
|
|
return .none
|
|
|
|
case .welcome(.debugMenuStartup)://, .tabs(.home(.walletBalances(.debugMenuStartup))):
|
|
return .concatenate(
|
|
Effect.cancel(id: CancelId),
|
|
.send(.destination(.updateDestination(.startup)))
|
|
)
|
|
|
|
case .onboarding(.createNewWalletTapped):
|
|
if state.appInitializationState == .keysMissing {
|
|
state.alert = AlertState.existingWallet()
|
|
return .none
|
|
} else {
|
|
return .send(.onboarding(.createNewWalletRequested))
|
|
}
|
|
|
|
case .initialization(.restoreExistingWallet):
|
|
return .run { send in
|
|
await send(.onboarding(.updateDestination(nil)))
|
|
try await mainQueue.sleep(for: .seconds(1))
|
|
await send(.onboarding(.importExistingWallet))
|
|
}
|
|
|
|
case .initialization(.seedValidationResult(let validSeed)):
|
|
if !validSeed {
|
|
state.alert = AlertState.differentSeed()
|
|
}
|
|
return .none
|
|
|
|
case .updateStateAfterConfigUpdate(let walletConfig):
|
|
state.walletConfig = walletConfig
|
|
state.onboardingState.walletConfig = walletConfig
|
|
return .none
|
|
|
|
case .initialization(.initializationFailed(let error)):
|
|
state.appInitializationState = .failed
|
|
state.alert = AlertState.initializationFailed(error)
|
|
return .none
|
|
|
|
default:
|
|
return .none
|
|
}
|
|
}
|
|
}
|
|
|
|
private func checkUnavailableService(_ error: Error) -> Bool {
|
|
switch error {
|
|
case ZcashError.serviceGetInfoFailed(.timeOut),
|
|
ZcashError.serviceLatestBlockFailed(.timeOut),
|
|
ZcashError.serviceLatestBlockHeightFailed(.timeOut),
|
|
ZcashError.serviceBlockRangeFailed(.timeOut),
|
|
ZcashError.serviceSubmitFailed(.timeOut),
|
|
ZcashError.serviceFetchTransactionFailed(.timeOut),
|
|
ZcashError.serviceFetchUTXOsFailed(.timeOut),
|
|
ZcashError.serviceBlockStreamFailed(.timeOut),
|
|
ZcashError.serviceSubtreeRootsStreamFailed(.timeOut):
|
|
return true
|
|
default: return false
|
|
}
|
|
}
|
|
}
|