From c83794470b13e009b71d949629735a92d1c04fb2 Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Tue, 21 Feb 2023 09:25:02 +0100 Subject: [PATCH] chore(keycard): sync keycard with the current app state updated This commit resolves a crash happened due to connection to `SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT` when keycard sync flow was run in the background. Also updated the keycard synchronization process with the current state of the application and is the first step of many which leads towards completion of entire syncing feature. --- src/app/boot/app_controller.nim | 67 ++------- .../core/signals/remote_signals/messages.nim | 11 ++ .../profile_section/keycard/controller.nim | 6 + .../profile_section/keycard/io_interface.nim | 3 + .../main/profile_section/keycard/module.nim | 3 + .../main/wallet_section/accounts/module.nim | 6 + .../keycard_popup/controller.nim | 39 ++--- .../internal/renaming_keycard_state.nim | 2 +- .../internal/wrong_seed_phrase_state.nim | 2 - .../models/key_pair_account_model.nim | 8 +- .../shared_modules/keycard_popup/module.nim | 56 ++++--- src/app/modules/startup/module.nim | 2 +- src/app_service/common/utils.nim | 9 +- .../service/wallet_account/async_tasks.nim | 4 +- .../service/wallet_account/key_pair_dto.nim | 55 +++++-- .../service/wallet_account/service.nim | 139 ++++++++++++++---- src/backend/backend.nim | 4 +- .../AppLayouts/Profile/views/KeycardView.qml | 4 + .../Profile/views/keycard/DetailsView.qml | 13 ++ vendor/status-go | 2 +- vendor/status-keycard-go | 2 +- 21 files changed, 285 insertions(+), 152 deletions(-) diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index 077e3b8715..76e8b6c68c 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -1,4 +1,4 @@ -import NimQml, sequtils, sugar, chronicles, os, uuids +import NimQml, sequtils, chronicles, os, uuids import ../../app_service/service/general/service as general_service import ../../app_service/service/keychain/service as keychain_service @@ -32,12 +32,11 @@ import ../../app_service/service/mailservers/service as mailservers_service import ../../app_service/service/gif/service as gif_service import ../../app_service/service/ens/service as ens_service import ../../app_service/service/community_tokens/service as tokens_service -import ../../app_service/common/account_constants +import ../modules/shared_modules/keycard_popup/module as keycard_shared_module import ../modules/startup/module as startup_module import ../modules/main/module as main_module import ../core/notifications/notifications_manager - import ../../constants as main_constants import ../global/global_singleton @@ -107,6 +106,7 @@ type proc load(self: AppController) proc buildAndRegisterLocalAccountSensitiveSettings(self: AppController) proc buildAndRegisterUserProfile(self: AppController) +proc tryKeycardSyncWithTheAppState(self: AppController) # Startup Module Delegate Interface proc startupDidLoad*(self: AppController) @@ -373,6 +373,7 @@ proc startupDidLoad*(self: AppController) = self.startupModule.startUpUIRaised() proc mainDidLoad*(self: AppController) = + self.tryKeycardSyncWithTheAppState() self.startupModule.moveToAppState() self.checkForStoringPasswordToKeychain() @@ -492,6 +493,7 @@ proc buildAndRegisterUserProfile(self: AppController) = singletonInstance.engine.setRootContextProperty("userProfile", self.userProfileVariant) +proc tryKeycardSyncWithTheAppState(self: AppController) = ############################################################################## store def kc sync with app kc uid ## Onboarding flows sync keycard state after login keypair | (inc. kp store) | update ## `I’m new to Status` -> `Generate new keys` -> na | na | na @@ -513,56 +515,19 @@ proc buildAndRegisterUserProfile(self: AppController) = ## `Login` -> if card was unlocked via seed phrase -> no | no | yes ## `Login` -> `Create replacement Keycard with seed phrase` -> no | yes | no (we don't know which kc is replaced if user has more kc for the same kp) ############################################################################## - if singletonInstance.userProfile.getIsKeycardUser(): - if self.storeDefaultKeyPair: - let allAccounts = self.walletAccountService.fetchAccounts() - let defaultWalletAccounts = allAccounts.filter(a => - a.walletType == WalletTypeDefaultStatusAccount and - a.path == account_constants.PATH_DEFAULT_WALLET and - not a.isChat and - a.isWallet + if singletonInstance.userProfile.getIsKeycardUser() or + self.syncKeycardBasedOnAppWalletState: + let data = SharedKeycarModuleArgs( + pin: self.startupModule.getPin(), + keyUid: singletonInstance.userProfile.getKeyUid() ) - if defaultWalletAccounts.len == 0: - error "default wallet account was not generated" - return - let defaultWalletAddress = defaultWalletAccounts[0].address - let keyPair = KeyPairDto(keycardUid: self.keycardService.getLastReceivedKeycardData().flowEvent.instanceUID, - keycardName: displayName, - keycardLocked: false, - accountsAddresses: @[defaultWalletAddress], - keyUid: loggedInAccount.keyUid) - discard self.walletAccountService.addMigratedKeyPair(keyPair) - if self.syncKeycardBasedOnAppWalletState: - let allAccounts = self.walletAccountService.fetchAccounts() - let accountsForLoggedInUser = allAccounts.filter(a => a.keyUid == loggedInAccount.keyUid) - var keyPair = KeyPairDto(keycardUid: "", - keycardName: displayName, - keycardLocked: false, - accountsAddresses: @[], - keyUid: loggedInAccount.keyUid) - var activeValidPathsToStoreToAKeycard: seq[string] - for acc in accountsForLoggedInUser: - activeValidPathsToStoreToAKeycard.add(acc.path) - keyPair.accountsAddresses.add(acc.address) - self.keycardService.startStoreMetadataFlow(displayName, self.startupModule.getPin(), activeValidPathsToStoreToAKeycard) - ## sleep for 3 seconds, since that is more than enough to store metadata to a keycard, if the reader is still plugged in - ## and the card is still inserted, otherwise we just skip that. - ## At the moment we're not able to sync later keycard without metadata, cause such card doesn't return instance uid for - ## loaded seed phrase, that's in the notes I am taking for discussion with keycard team. If they are able to provide - ## instance uid for GetMetadata flow we will be able to use SIGNAL_SHARED_KEYCARD_MODULE_TRY_KEYCARD_SYNC signal for syncing - ## otherwise we need to handle that way separatelly in `handleKeycardSyncing` of shared module - sleep(3000) - self.keycardService.cancelCurrentFlow() - let (_, kcEvent) = self.keycardService.getLastReceivedKeycardData() - if kcEvent.instanceUID.len > 0: - keyPair.keycardUid = kcEvent.instanceUID - discard self.walletAccountService.addMigratedKeyPair(keyPair) + self.statusFoundation.events.emit(SIGNAL_SHARED_KEYCARD_MODULE_TRY_KEYCARD_SYNC, data) - if self.changedKeycardUids.len > 0: - let oldUid = self.changedKeycardUids[0].oldKcUid - let newUid = self.changedKeycardUids[^1].newKcUid - discard self.walletAccountService.updateKeycardUid(oldUid, newUid) - discard self.walletAccountService.setKeycardUnlocked(loggedInAccount.keyUid, newUid) + if self.changedKeycardUids.len > 0: + let oldUid = self.changedKeycardUids[0].oldKcUid + let newUid = self.changedKeycardUids[^1].newKcUid + discard self.walletAccountService.updateKeycardUid(oldUid, newUid) + discard self.walletAccountService.setKeycardUnlocked(singletonInstance.userProfile.getKeyUid(), newUid) proc storeDefaultKeyPairForNewKeycardUser*(self: AppController) = self.storeDefaultKeyPair = true diff --git a/src/app/core/signals/remote_signals/messages.nim b/src/app/core/signals/remote_signals/messages.nim index 6ae5c45632..60bf7a8432 100644 --- a/src/app/core/signals/remote_signals/messages.nim +++ b/src/app/core/signals/remote_signals/messages.nim @@ -11,6 +11,7 @@ import ../../../../app_service/service/contacts/dto/[contacts, status_update] import ../../../../app_service/service/devices/dto/[device] import ../../../../app_service/service/settings/dto/[settings] import ../../../../app_service/service/saved_address/[dto] +import ../../../../app_service/service/wallet_account/[key_pair_dto] type MessageSignal* = ref object of Signal bookmarks*: seq[BookmarkDto] @@ -32,6 +33,8 @@ type MessageSignal* = ref object of Signal clearedHistories*: seq[ClearedHistoryDto] verificationRequests*: seq[VerificationRequest] savedAddresses*: seq[SavedAddressDto] + keycards*: seq[KeyPairDto] + keycardActions*: seq[KeycardActionDto] type MessageDeliveredSignal* = ref object of Signal chatId*: string @@ -130,5 +133,13 @@ proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal = for jsonSavedAddress in event["event"]["savedAddresses"]: signal.savedAddresses.add(jsonSavedAddress.toSavedAddressDto()) + if event["event"]{"keycards"} != nil: + for jsonKc in event["event"]["keycards"]: + signal.keycards.add(jsonKc.toKeyPairDto()) + + if event["event"]{"keycardActions"} != nil: + for jsonKc in event["event"]["keycardActions"]: + signal.keycardActions.add(jsonKc.toKeycardActionDto()) + result = signal diff --git a/src/app/modules/main/profile_section/keycard/controller.nim b/src/app/modules/main/profile_section/keycard/controller.nim index 13a64e274a..82d1561a5e 100644 --- a/src/app/modules/main/profile_section/keycard/controller.nim +++ b/src/app/modules/main/profile_section/keycard/controller.nim @@ -53,6 +53,12 @@ proc init*(self: Controller) = return self.delegate.onNewKeycardSet(args.keyPair) + self.events.on(SIGNAL_KEYCARDS_SYNCHRONIZED) do(e: Args): + let args = KeycardActivityArgs(e) + if not args.success: + return + self.delegate.onKeycardsSynchronized() + self.events.on(SIGNAL_KEYCARD_LOCKED) do(e: Args): let args = KeycardActivityArgs(e) self.delegate.onKeycardLocked(args.keyPair.keyUid, args.keyPair.keycardUid) diff --git a/src/app/modules/main/profile_section/keycard/io_interface.nim b/src/app/modules/main/profile_section/keycard/io_interface.nim index de54dbe48a..f36c6e6532 100644 --- a/src/app/modules/main/profile_section/keycard/io_interface.nim +++ b/src/app/modules/main/profile_section/keycard/io_interface.nim @@ -66,6 +66,9 @@ method runCreateNewPairingCodePopup*(self: AccessInterface, keyUid: string) {.ba method onLoggedInUserImageChanged*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") +method onKeycardsSynchronized*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + method onNewKeycardSet*(self: AccessInterface, keyPair: KeyPairDto) {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/profile_section/keycard/module.nim b/src/app/modules/main/profile_section/keycard/module.nim index 9de17c5aa7..9c527ad29c 100644 --- a/src/app/modules/main/profile_section/keycard/module.nim +++ b/src/app/modules/main/profile_section/keycard/module.nim @@ -276,6 +276,9 @@ method onLoggedInUserImageChanged*(self: Module) = return self.view.keycardDetailsModel().setImage(singletonInstance.userProfile.getPubKey(), singletonInstance.userProfile.getIcon()) +method onKeycardsSynchronized*(self: Module) = + self.buildKeycardList() + method onNewKeycardSet*(self: Module, keyPair: KeyPairDto) = let walletAccounts = self.controller.getWalletAccounts() var mainViewItem = self.view.keycardModel().getItemForKeyUid(keyPair.keyUid) diff --git a/src/app/modules/main/wallet_section/accounts/module.nim b/src/app/modules/main/wallet_section/accounts/module.nim index 37ec6f7dd4..d93359f9ac 100644 --- a/src/app/modules/main/wallet_section/accounts/module.nim +++ b/src/app/modules/main/wallet_section/accounts/module.nim @@ -150,6 +150,12 @@ method load*(self: Module) = return self.refreshWalletAccounts() + self.events.on(SIGNAL_KEYCARDS_SYNCHRONIZED) do(e: Args): + let args = KeycardActivityArgs(e) + if not args.success: + return + self.refreshWalletAccounts() + self.controller.init() self.view.load() diff --git a/src/app/modules/shared_modules/keycard_popup/controller.nim b/src/app/modules/shared_modules/keycard_popup/controller.nim index 510390cbce..9a3727df2c 100644 --- a/src/app/modules/shared_modules/keycard_popup/controller.nim +++ b/src/app/modules/shared_modules/keycard_popup/controller.nim @@ -165,7 +165,7 @@ proc disconnectAll*(self: Controller) = proc delete*(self: Controller) = self.disconnectAll() -proc init*(self: Controller) = +proc init*(self: Controller, fullConnect = true) = self.connectKeycardReponseSignal() var handlerId = self.events.onWithUUID(SIGNAL_SHARED_KEYCARD_MODULE_USER_AUTHENTICATED) do(e: Args): @@ -176,22 +176,23 @@ proc init*(self: Controller) = self.delegate.onUserAuthenticated(args.password, args.pin) self.connectionIds.add(handlerId) - handlerId = self.events.onWithUUID(SIGNAL_NEW_KEYCARD_SET) do(e: Args): - let args = KeycardActivityArgs(e) - self.tmpAddingMigratedKeypairSuccess = args.success - self.delegate.onSecondaryActionClicked() - self.connectionIds.add(handlerId) - - handlerId = self.events.onWithUUID(SIGNAL_CONVERTING_PROFILE_KEYPAIR) do(e: Args): - let args = ResultArgs(e) - self.tmpConvertingProfileSuccess = args.success - self.delegate.onSecondaryActionClicked() - self.connectionIds.add(handlerId) + if fullConnect: + handlerId = self.events.onWithUUID(SIGNAL_NEW_KEYCARD_SET) do(e: Args): + let args = KeycardActivityArgs(e) + self.tmpAddingMigratedKeypairSuccess = args.success + self.delegate.onSecondaryActionClicked() + self.connectionIds.add(handlerId) + + handlerId = self.events.onWithUUID(SIGNAL_CONVERTING_PROFILE_KEYPAIR) do(e: Args): + let args = ResultArgs(e) + self.tmpConvertingProfileSuccess = args.success + self.delegate.onSecondaryActionClicked() + self.connectionIds.add(handlerId) - handlerId = self.events.onWithUUID(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args): - let arg = TokensPerAccountArgs(e) - self.delegate.onTokensRebuilt(arg.accountsTokens) - self.connectionIds.add(handlerId) + handlerId = self.events.onWithUUID(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e:Args): + let arg = TokensPerAccountArgs(e) + self.delegate.onTokensRebuilt(arg.accountsTokens) + self.connectionIds.add(handlerId) proc switchToWalletSection*(self: Controller) = let data = ActiveSectionChatArgs(sectionId: conf.WALLET_SECTION_ID) @@ -661,11 +662,11 @@ proc setCurrentKeycardStateToUnlocked*(self: Controller, keyUid: string, keycard if not self.walletAccountService.setKeycardUnlocked(keyUid, keycardUid): info "updating keycard unlocked state failed", keyUid=keyUid, keycardUid=keycardUid -proc updateKeycardName*(self: Controller, keyUid: string, keycardUid: string, keycardName: string): bool = +proc updateKeycardName*(self: Controller, keycardUid: string, keycardName: string): bool = if not serviceApplicable(self.walletAccountService): return false - if not self.walletAccountService.updateKeycardName(keyUid, keycardUid, keycardName): - info "updating keycard name failed", keyUid=keyUid, keycardUid=keycardUid, keycardName=keycardName + if not self.walletAccountService.updateKeycardName(keycardUid, keycardName): + info "updating keycard name failed", keycardUid=keycardUid, keycardName=keycardName return false return true diff --git a/src/app/modules/shared_modules/keycard_popup/internal/renaming_keycard_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/renaming_keycard_state.nim index bac49653be..e50e5271fd 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/renaming_keycard_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/renaming_keycard_state.nim @@ -15,7 +15,7 @@ method executePrePrimaryStateCommand*(self: RenamingKeycardState, controller: Co let md = controller.getMetadataFromKeycard() let paths = md.walletAccounts.map(a => a.path) let name = controller.getKeyPairForProcessing().getName() - self.success = controller.updateKeycardName(controller.getKeyPairForProcessing().getKeyUid(), controller.getKeycardUid(), name) + self.success = controller.updateKeycardName(controller.getKeycardUid(), name) if self.success: controller.runStoreMetadataFlow(name, controller.getPin(), paths) else: diff --git a/src/app/modules/shared_modules/keycard_popup/internal/wrong_seed_phrase_state.nim b/src/app/modules/shared_modules/keycard_popup/internal/wrong_seed_phrase_state.nim index 91b3b06345..161d404fed 100644 --- a/src/app/modules/shared_modules/keycard_popup/internal/wrong_seed_phrase_state.nim +++ b/src/app/modules/shared_modules/keycard_popup/internal/wrong_seed_phrase_state.nim @@ -1,5 +1,3 @@ -import os - type WrongSeedPhraseState* = ref object of State verifiedSeedPhrase: bool diff --git a/src/app/modules/shared_modules/keycard_popup/models/key_pair_account_model.nim b/src/app/modules/shared_modules/keycard_popup/models/key_pair_account_model.nim index 743d99df31..fed808b4e7 100644 --- a/src/app/modules/shared_modules/keycard_popup/models/key_pair_account_model.nim +++ b/src/app/modules/shared_modules/keycard_popup/models/key_pair_account_model.nim @@ -1,7 +1,7 @@ import NimQml, Tables, strformat, strutils import key_pair_account_item -import ../../../../../app_service/common/account_constants +import ../../../../../app_service/common/utils export key_pair_account_item @@ -82,10 +82,8 @@ QtObject: proc containsPathOutOfTheDefaultStatusDerivationTree*(self: KeyPairAccountModel): bool = for it in self.items: - if not it.getPath().startsWith(account_constants.PATH_WALLET_ROOT&"/") or - it.getPath().count("'") != 3 or - it.getPath().count("/") != 5: - return true + if utils.isPathOutOfTheDefaultStatusDerivationTree(it.getPath()): + return true return false proc getItemAtIndex*(self: KeyPairAccountModel, index: int): KeyPairAccountItem = diff --git a/src/app/modules/shared_modules/keycard_popup/module.nim b/src/app/modules/shared_modules/keycard_popup/module.nim index 21155efd10..294965c662 100644 --- a/src/app/modules/shared_modules/keycard_popup/module.nim +++ b/src/app/modules/shared_modules/keycard_popup/module.nim @@ -1,4 +1,4 @@ -import NimQml, tables, random, strutils, marshal, sequtils, sugar, chronicles +import NimQml, tables, random, strutils, sequtils, sugar, chronicles import io_interface import view, controller @@ -66,10 +66,10 @@ method delete*[T](self: Module[T]) = self.viewVariant.delete self.controller.delete -proc init[T](self: Module[T]) = +proc init[T](self: Module[T], fullConnect = true) = if not self.initialized: self.initialized = true - self.controller.init() + self.controller.init(fullConnect) method getModuleAsVariant*[T](self: Module[T]): QVariant = return self.viewVariant @@ -241,13 +241,20 @@ proc handleKeycardSyncing[T](self: Module[T]) = accountsAddresses: @[], keyUid: flowEvent.keyUid) let alreadySetKeycards = self.controller.getAllKnownKeycards().filter(kp => kp.keycardUid == flowEvent.instanceUID) - if alreadySetKeycards.len == 1: - var accountsToRemove = alreadySetKeycards[0].accountsAddresses + if alreadySetKeycards.len <= 1: + var accountsToRemove: seq[string] + if alreadySetKeycards.len == 1: + accountsToRemove = alreadySetKeycards[0].accountsAddresses let appAccounts = self.controller.getWalletAccounts() var activeValidPathsToStoreToAKeycard: seq[string] + var containsPathOutOfTheDefaultStatusDerivationTree = false for appAcc in appAccounts: if appAcc.keyUid != flowEvent.keyUid: continue + # do not sync if any wallet's account has path out of the default Status derivation tree + if utils.isPathOutOfTheDefaultStatusDerivationTree(appAcc.path): + containsPathOutOfTheDefaultStatusDerivationTree = true + break activeValidPathsToStoreToAKeycard.add(appAcc.path) var index = -1 var found = false @@ -264,24 +271,25 @@ proc handleKeycardSyncing[T](self: Module[T]) = # we store to db only accounts we haven't stored before, accounts which are already on a keycard (in metadata) # we assume they are already in the db kpDto.accountsAddresses.add(appAcc.address) - if accountsToRemove.len > 0: - self.controller.removeMigratedAccountsForKeycard(kpDto.keyUid, kpDto.keycardUid, accountsToRemove) - if kpDto.accountsAddresses.len > 0: - self.controller.addMigratedKeyPair(kpDto) - # if all accounts are removed from the app, there is no point in storing empty accounts list to a keycard, cause in that case - # keypair which is on that keycard won't be known to the app, that means keypair was removed from the app - if activeValidPathsToStoreToAKeycard.len > 0: - ## we need to store paths to a keycard if the num of paths in the app and on a keycard is diffrent - ## or if the paths are different - var storeToKeycard = activeValidPathsToStoreToAKeycard.len != flowEvent.cardMetadata.walletAccounts.len - if not storeToKeycard: - for wa in flowEvent.cardMetadata.walletAccounts: - if not utils.arrayContains(activeValidPathsToStoreToAKeycard, wa.path): - storeToKeycard = true - break - if storeToKeycard: - self.controller.runStoreMetadataFlow(flowEvent.cardMetadata.name, self.controller.getPin(), activeValidPathsToStoreToAKeycard) - return + if not containsPathOutOfTheDefaultStatusDerivationTree: + if accountsToRemove.len > 0: + self.controller.removeMigratedAccountsForKeycard(kpDto.keyUid, kpDto.keycardUid, accountsToRemove) + if kpDto.accountsAddresses.len > 0: + self.controller.addMigratedKeyPair(kpDto) + # if all accounts are removed from the app, there is no point in storing empty accounts list to a keycard, cause in that case + # keypair which is on that keycard won't be known to the app, that means keypair was removed from the app + if activeValidPathsToStoreToAKeycard.len > 0: + ## we need to store paths to a keycard if the num of paths in the app and on a keycard is diffrent + ## or if the paths are different + var storeToKeycard = activeValidPathsToStoreToAKeycard.len != flowEvent.cardMetadata.walletAccounts.len + if not storeToKeycard: + for wa in flowEvent.cardMetadata.walletAccounts: + if not utils.arrayContains(activeValidPathsToStoreToAKeycard, wa.path): + storeToKeycard = true + break + if storeToKeycard: + self.controller.runStoreMetadataFlow(flowEvent.cardMetadata.name, self.controller.getPin(), activeValidPathsToStoreToAKeycard) + return elif alreadySetKeycards.len > 1: error "it's impossible to have more then one keycard with the same uid", keycarUid=flowEvent.instanceUID self.controller.terminateCurrentFlow(lastStepInTheCurrentFlow = true) @@ -295,7 +303,7 @@ method syncKeycardBasedOnAppState*[T](self: Module[T], keyUid: string, pin: stri if keyUid.len == 0: debug "cannot sync with the empty keyUid" return - self.init() + self.init(fullConnect = false) self.controller.setKeyUidWhichIsBeingSyncing(keyUid) self.controller.setPin(pin) self.controller.setKeycardSyncingInProgress(true) diff --git a/src/app/modules/startup/module.nim b/src/app/modules/startup/module.nim index 28bcdef7e5..3b1383b0b0 100644 --- a/src/app/modules/startup/module.nim +++ b/src/app/modules/startup/module.nim @@ -342,7 +342,7 @@ proc delayStartingApp[T](self: Module[T]) = ## - FlowType.FirstRunOldUserImportSeedPhrase ## - FlowType.FirstRunOldUserKeycardImport ## we want to delay app start just to be sure that messages from waku will be received - self.controller.connectToTimeoutEventAndStratTimer(timeoutInMilliseconds = 10000) # delay for 30 seconds + self.controller.connectToTimeoutEventAndStratTimer(timeoutInMilliseconds = 30000) # delay for 30 seconds method startAppAfterDelay*[T](self: Module[T]) = if not self.view.fetchingDataModel().allMessagesLoaded(): diff --git a/src/app_service/common/utils.nim b/src/app_service/common/utils.nim index 8eab410f97..3e6e72a172 100644 --- a/src/app_service/common/utils.nim +++ b/src/app_service/common/utils.nim @@ -1,6 +1,6 @@ import json, random, times, strutils, sugar, os, re, chronicles import nimcrypto -import signing_phrases +import signing_phrases, account_constants import ../../constants as main_constants @@ -74,3 +74,10 @@ proc validateLink*(link: string): bool = link, re"[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)", 0): error "Invalid social link", errDescription = link result = false + +proc isPathOutOfTheDefaultStatusDerivationTree*(path: string): bool = + if not path.startsWith(account_constants.PATH_WALLET_ROOT&"/") or + path.count("'") != 3 or + path.count("/") != 5: + return true + return false \ No newline at end of file diff --git a/src/app_service/service/wallet_account/async_tasks.nim b/src/app_service/service/wallet_account/async_tasks.nim index 3b9e6d9b62..975f2ea368 100644 --- a/src/app_service/service/wallet_account/async_tasks.nim +++ b/src/app_service/service/wallet_account/async_tasks.nim @@ -140,14 +140,14 @@ type const addMigratedKeyPairTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[AddMigratedKeyPairTaskArg](argEncoded) try: - let response = backend.addMigratedKeyPair( + let response = backend.addMigratedKeyPairOrAddAccountsIfKeyPairIsAdded( arg.keyPair.keycardUid, arg.keyPair.keycardName, arg.keyPair.keyUid, arg.keyPair.accountsAddresses, arg.password ) - let success = responseHasNoErrors("addMigratedKeyPair", response) + let success = responseHasNoErrors("addMigratedKeyPairOrAddAccountsIfKeyPairIsAdded", response) let responseJson = %*{ "success": success, "keyPair": arg.keyPair.toJsonNode() diff --git a/src/app_service/service/wallet_account/key_pair_dto.nim b/src/app_service/service/wallet_account/key_pair_dto.nim index ef9764126c..742bcd8312 100644 --- a/src/app_service/service/wallet_account/key_pair_dto.nim +++ b/src/app_service/service/wallet_account/key_pair_dto.nim @@ -2,12 +2,23 @@ import json include ../../common/json_utils -const KeycardUid = "keycard-uid" -const KeycardName = "keycard-name" -const KeycardLocked = "keycard-locked" -const KeyUid = "key-uid" -const AccountAddresses = "accounts-addresses" +const ParamKeycardUid = "keycard-uid" +const ParamKeycardName = "keycard-name" +const ParamKeycardLocked = "keycard-locked" +const ParamKeyUid = "key-uid" +const ParamAccountAddresses = "accounts-addresses" +const ParamAction = "action" +const ParamOldKeycardUid = "old-keycard-uid" +const ParamKeycard = "keycard" +const KeycardActionKeycardAdded* = "KEYCARD_ADDED" +const KeycardActionAccountsAdded* = "ACCOUNTS_ADDED" +const KeycardActionKeycardDeleted* = "KEYCARD_DELETED" +const KeycardActionAccountsRemoved* = "ACCOUNTS_REMOVED" +const KeycardActionLocked* = "LOCKED" +const KeycardActionUnlocked* = "UNLOCKED" +const KeycardActionUidUpdated* = "UID_UPDATED" +const KeycardActionNameChanged* = "NAME_CHANGED" type KeyPairDto* = object keycardUid*: string @@ -16,23 +27,37 @@ type KeyPairDto* = object accountsAddresses*: seq[string] keyUid*: string +type KeycardActionDto* = object + action*: string + oldKeycardUid*: string + keycard*: KeyPairDto + proc toKeyPairDto*(jsonObj: JsonNode): KeyPairDto = result = KeyPairDto() - discard jsonObj.getProp(KeycardUid, result.keycardUid) - discard jsonObj.getProp(KeycardName, result.keycardName) - discard jsonObj.getProp(KeycardLocked, result.keycardLocked) - discard jsonObj.getProp(KeyUid, result.keyUid) + discard jsonObj.getProp(ParamKeycardUid, result.keycardUid) + discard jsonObj.getProp(ParamKeycardName, result.keycardName) + discard jsonObj.getProp(ParamKeycardLocked, result.keycardLocked) + discard jsonObj.getProp(ParamKeyUid, result.keyUid) var jArr: JsonNode - if(jsonObj.getProp(AccountAddresses, jArr) and jArr.kind == JArray): + if(jsonObj.getProp(ParamAccountAddresses, jArr) and jArr.kind == JArray): for addrObj in jArr: result.accountsAddresses.add(addrObj.getStr) +proc toKeycardActionDto*(jsonObj: JsonNode): KeycardActionDto = + result = KeycardActionDto() + discard jsonObj.getProp(ParamAction, result.action) + discard jsonObj.getProp(ParamOldKeycardUid, result.oldKeycardUid) + + var keycardObj: JsonNode + if(jsonObj.getProp("keycard", keycardObj)): + result.keycard = toKeyPairDto(keycardObj) + proc toJsonNode*(self: KeyPairDto): JsonNode = result = %* { - KeycardUid: self.keycardUid, - KeycardName: self.keycardName, - KeycardLocked: self.keycardLocked, - KeyUid: self.keyUid, - AccountAddresses: self.accountsAddresses + ParamKeycardUid: self.keycardUid, + ParamKeycardName: self.keycardName, + ParamKeycardLocked: self.keycardLocked, + ParamKeyUid: self.keyUid, + ParamAccountAddresses: self.accountsAddresses } \ No newline at end of file diff --git a/src/app_service/service/wallet_account/service.nim b/src/app_service/service/wallet_account/service.nim index 0f37c90126..564cfd378f 100644 --- a/src/app_service/service/wallet_account/service.nim +++ b/src/app_service/service/wallet_account/service.nim @@ -34,7 +34,9 @@ const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY* = "walletAccount/derivedAddre const SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT* = "walletAccount/tokensRebuilt" const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_DETAILS_FETCHED* = "walletAccount/derivedAddressDetailsFetched" +const SIGNAL_KEYCARDS_SYNCHRONIZED* = "keycardsSynchronized" const SIGNAL_NEW_KEYCARD_SET* = "newKeycardSet" +const SIGNAL_KEYCARD_DELETED* = "keycardDeleted" const SIGNAL_KEYCARD_ACCOUNTS_REMOVED* = "keycardAccountsRemoved" const SIGNAL_KEYCARD_LOCKED* = "keycardLocked" const SIGNAL_KEYCARD_UNLOCKED* = "keycardUnlocked" @@ -124,6 +126,8 @@ QtObject: proc checkRecentHistory*(self: Service) proc checkConnected(self: Service) proc startWallet(self: Service) + proc handleKeycardActions(self: Service, keycardActions: seq[KeycardActionDto]) + proc handleKeycardsState(self: Service, keycardsState: seq[KeyPairDto]) proc delete*(self: Service) = self.closingApp = true @@ -227,6 +231,9 @@ QtObject: if settingsField.name == KEY_CURRENCY: self.events.emit(SIGNAL_WALLET_ACCOUNT_CURRENCY_UPDATED, CurrencyUpdated()) + self.handleKeycardsState(receivedData.keycards) + self.handleKeycardActions(receivedData.keycardActions) + self.events.on(SignalType.Wallet.event) do(e:Args): var data = WalletSignal(e) case data.eventType: @@ -614,24 +621,33 @@ QtObject: ) self.threadpool.start(arg) + proc emitAddKeycardAddAccountsChange(self: Service, success: bool, keyPair: KeyPairDto) = + let data = KeycardActivityArgs( + success: success, + keyPair: keyPair + ) + self.events.emit(SIGNAL_NEW_KEYCARD_SET, data) + proc onMigratedKeyPairAdded*(self: Service, response: string) {.slot.} = - var data = KeycardActivityArgs() - data.success = false try: let responseObj = response.parseJson - discard responseObj.getProp("success", data.success) - var kpJson: JsonNode - if responseObj.getProp("keyPair", kpJson): - data.keyPair = kpJson.toKeyPairDto() + var keyPair: KeyPairDto + var success = false + discard responseObj.getProp("success", success) + if success: + var kpJson: JsonNode + if responseObj.getProp("keyPair", kpJson): + keyPair = kpJson.toKeyPairDto() + self.emitAddKeycardAddAccountsChange(success, keyPair) except Exception as e: error "error handilng migrated keypair response", errDesription=e.msg - self.events.emit(SIGNAL_NEW_KEYCARD_SET, data) + self.emitAddKeycardAddAccountsChange(success = false, KeyPairDto()) proc addMigratedKeyPair*(self: Service, keyPair: KeyPairDto, password = ""): bool = # Providing a password corresponding local keystore file will be removed as well, though # in some contexts we just need to add keypair to the db, so password is not needed. try: - let response = backend.addMigratedKeyPair( + let response = backend.addMigratedKeyPairOrAddAccountsIfKeyPairIsAdded( keyPair.keycardUid, keyPair.keycardName, keyPair.keyUid, @@ -640,7 +656,7 @@ QtObject: ) result = responseHasNoErrors("addMigratedKeyPair", response) if result: - self.events.emit(SIGNAL_NEW_KEYCARD_SET, KeycardActivityArgs(success: true, keyPair: keyPair)) + self.emitAddKeycardAddAccountsChange(success = true, keyPair) except Exception as e: error "error: ", procName="addMigratedKeyPair", errName = e.name, errDesription = e.msg @@ -653,18 +669,28 @@ QtObject: ) self.threadpool.start(arg) + proc emitKeycardRemovedAccountsChange(self: Service, success: bool, keyUid: string, keycardUid: string, + removedAccounts: seq[string]) = + let data = KeycardActivityArgs( + success: success, + keyPair: KeyPairDto(keyUid: keyUid, keycardUid: keycardUid, accountsAddresses: removedAccounts) + ) + self.events.emit(SIGNAL_KEYCARD_ACCOUNTS_REMOVED, data) + proc onMigratedAccountsForKeycardRemoved*(self: Service, response: string) {.slot.} = - var data = KeycardActivityArgs() - data.success = false try: let responseObj = response.parseJson - discard responseObj.getProp("success", data.success) - var kpJson: JsonNode - if responseObj.getProp("keyPair", kpJson): - data.keyPair = kpJson.toKeyPairDto() + var keyPair: KeyPairDto + var success = false + discard responseObj.getProp("success", success) + if success: + var kpJson: JsonNode + if responseObj.getProp("keyPair", kpJson): + keyPair = kpJson.toKeyPairDto() + self.emitKeycardRemovedAccountsChange(success, keyPair.keyUid, keyPair.keycardUid, keyPair.accountsAddresses) except Exception as e: error "error handilng migrated keypair response", errDesription=e.msg - self.events.emit(SIGNAL_KEYCARD_ACCOUNTS_REMOVED, data) + self.emitKeycardRemovedAccountsChange(success = false, keyUid = "", keycardUid = "", removedAccounts = @[]) proc getAllKnownKeycards*(self: Service): seq[KeyPairDto] = try: @@ -674,6 +700,16 @@ QtObject: except Exception as e: error "error: ", procName="getAllKnownKeycards", errName = e.name, errDesription = e.msg + proc getKeycardWithKeycardUid*(self: Service, keycardUid: string): KeyPairDto = + let allKnownKeycards = self.getAllKnownKeycards() + let keycardsWithKeycardUid = allKnownKeycards.filter(kp => kp.keycardUid == keycardUid) + if keycardsWithKeycardUid.len == 0: + return + if keycardsWithKeycardUid.len > 1: + error "there are more than one keycard with the same uid", keycardUid=keycardUid + return + return keycardsWithKeycardUid[0] + proc getAllMigratedKeyPairs*(self: Service): seq[KeyPairDto] = try: let response = backend.getAllMigratedKeyPairs() @@ -690,50 +726,68 @@ QtObject: except Exception as e: error "error: ", procName="getMigratedKeyPairByKeyUid", errName = e.name, errDesription = e.msg - proc updateKeycardName*(self: Service, keyUid: string, keycardUid: string, name: string): bool = + proc emitKeycardNameChange(self: Service, keycardUid: string, name: string) = + let data = KeycardActivityArgs(success: true, keyPair: KeyPairDto(keycardUid: keycardUid, keycardName: name)) + self.events.emit(SIGNAL_KEYCARD_NAME_CHANGED, data) + + proc updateKeycardName*(self: Service, keycardUid: string, name: string): bool = try: let response = backend.setKeycardName(keycardUid, name) result = responseHasNoErrors("updateKeycardName", response) if result: - let data = KeycardActivityArgs(success: true, keyPair: KeyPairDto(keyUid: keyUid, keycardUid: keycardUid, keycardName: name)) - self.events.emit(SIGNAL_KEYCARD_NAME_CHANGED, data) + self.emitKeycardNameChange(keycardUid, name) except Exception as e: error "error: ", procName="updateKeycardName", errName = e.name, errDesription = e.msg + proc emitKeycardLockedChange(self: Service, keyUid: string, keycardUid: string) = + let data = KeycardActivityArgs(success: true, keyPair: KeyPairDto(keyUid: keyUid, keycardUid: keycardUid)) + self.events.emit(SIGNAL_KEYCARD_LOCKED, data) + proc setKeycardLocked*(self: Service, keyUid: string, keycardUid: string): bool = try: let response = backend.keycardLocked(keycardUid) result = responseHasNoErrors("setKeycardLocked", response) if result: - let data = KeycardActivityArgs(success: true, keyPair: KeyPairDto(keyUid: keyUid, keycardUid: keycardUid)) - self.events.emit(SIGNAL_KEYCARD_LOCKED, data) + self.emitKeycardLockedChange(keyUid, keycardUid) except Exception as e: error "error: ", procName="setKeycardLocked", errName = e.name, errDesription = e.msg + proc emitKeycardUnlockedChange(self: Service, keyUid: string, keycardUid: string) = + let data = KeycardActivityArgs(success: true, keyPair: KeyPairDto(keyUid: keyUid, keycardUid: keycardUid)) + self.events.emit(SIGNAL_KEYCARD_UNLOCKED, data) + proc setKeycardUnlocked*(self: Service, keyUid: string, keycardUid: string): bool = try: let response = backend.keycardUnlocked(keycardUid) result = responseHasNoErrors("setKeycardUnlocked", response) if result: - let data = KeycardActivityArgs(success: true, keyPair: KeyPairDto(keyUid: keyUid, keycardUid: keycardUid)) - self.events.emit(SIGNAL_KEYCARD_UNLOCKED, data) + self.emitKeycardUnlockedChange(keyUid, keycardUid) except Exception as e: error "error: ", procName="setKeycardUnlocked", errName = e.name, errDesription = e.msg + proc emitUpdateKeycardUidChange(self: Service, oldKeycardUid: string, newKeycardUid: string) = + let data = KeycardActivityArgs(success: true, oldKeycardUid: oldKeycardUid, keyPair: KeyPairDto(keycardUid: newKeycardUid)) + self.events.emit(SIGNAL_KEYCARD_UID_UPDATED, data) + proc updateKeycardUid*(self: Service, oldKeycardUid: string, newKeycardUid: string): bool = try: let response = backend.updateKeycardUID(oldKeycardUid, newKeycardUid) result = responseHasNoErrors("updateKeycardUid", response) if result: - let data = KeycardActivityArgs(success: true, oldKeycardUid: oldKeycardUid, keyPair: KeyPairDto(keycardUid: newKeycardUid)) - self.events.emit(SIGNAL_KEYCARD_UID_UPDATED, data) + self.emitUpdateKeycardUidChange(oldKeycardUid, newKeycardUid) except Exception as e: error "error: ", procName="updateKeycardUid", errName = e.name, errDesription = e.msg + proc emitDeleteKeycardChange(self: Service, keycardUid: string) = + let data = KeycardActivityArgs(success: true, keyPair: KeyPairDto(keycardUid: keycardUid)) + self.events.emit(SIGNAL_KEYCARD_DELETED, data) + proc deleteKeycard*(self: Service, keycardUid: string): bool = try: let response = backend.deleteKeycard(keycardUid) - return responseHasNoErrors("deleteKeycard", response) + result = responseHasNoErrors("deleteKeycard", response) + if result: + self.emitDeleteKeycardChange(keycardUid) except Exception as e: error "error: ", procName="deleteKeycard", errName = e.name, errDesription = e.msg return false @@ -743,4 +797,35 @@ QtObject: result = self.addOrReplaceWalletAccount(name, address, path, addressAccountIsDerivedFrom, publicKey, keyUid, accountType, color, emoji) if result.len == 0: - self.addNewAccountToLocalStore() \ No newline at end of file + self.addNewAccountToLocalStore() + + proc handleKeycardActions(self: Service, keycardActions: seq[KeycardActionDto]) = + if keycardActions.len == 0: + return + for kcAction in keycardActions: + if kcAction.action == KeycardActionKeycardAdded or + kcAction.action == KeycardActionAccountsAdded: + self.emitAddKeycardAddAccountsChange(success = true, kcAction.keycard) + elif kcAction.action == KeycardActionKeycardDeleted: + self.emitDeleteKeycardChange(kcAction.keycard.keycardUid) + elif kcAction.action == KeycardActionAccountsRemoved: + let keycard = self.getKeycardWithKeycardUid(kcAction.keycard.keycardUid) + self.emitKeycardRemovedAccountsChange(success = true, keycard.keyUid, kcAction.keycard.keycardUid, kcAction.keycard.accountsAddresses) + elif kcAction.action == KeycardActionLocked: + let keycard = self.getKeycardWithKeycardUid(kcAction.keycard.keycardUid) + self.emitKeycardLockedChange(keycard.keyUid, kcAction.keycard.keycardUid) + elif kcAction.action == KeycardActionUnlocked: + let keycard = self.getKeycardWithKeycardUid(kcAction.keycard.keycardUid) + self.emitKeycardUnlockedChange(keycard.keyUid, kcAction.keycard.keycardUid) + elif kcAction.action == KeycardActionUidUpdated: + self.emitUpdateKeycardUidChange(kcAction.oldKeycardUid, kcAction.keycard.keycardUid) + elif kcAction.action == KeycardActionNameChanged: + self.emitKeycardNameChange(kcAction.keycard.keycardUid, kcAction.keycard.keycardName) + else: + error "unsupported action received", action=kcAction.action + + proc handleKeycardsState(self: Service, keycardsState: seq[KeyPairDto]) = + if keycardsState.len == 0: + return + let data = KeycardActivityArgs(success: true) + self.events.emit(SIGNAL_KEYCARDS_SYNCHRONIZED, data) \ No newline at end of file diff --git a/src/backend/backend.nim b/src/backend/backend.nim index 80bc153e2a..b6aa1231ba 100644 --- a/src/backend/backend.nim +++ b/src/backend/backend.nim @@ -239,12 +239,12 @@ rpc(fetchMarketValues, "wallet"): rpc(fetchTokenDetails, "wallet"): symbols: seq[string] -rpc(addMigratedKeyPair, "accounts"): +rpc(addMigratedKeyPairOrAddAccountsIfKeyPairIsAdded, "accounts"): keycardUid: string keyPairName: string keyUid: string accountAddresses: seq[string] - keyStoreDir: string + password: string rpc(removeMigratedAccountsForKeycard, "accounts"): keycardUid: string diff --git a/ui/app/AppLayouts/Profile/views/KeycardView.qml b/ui/app/AppLayouts/Profile/views/KeycardView.qml index ecc81f24ca..10e3cad9ae 100644 --- a/ui/app/AppLayouts/Profile/views/KeycardView.qml +++ b/ui/app/AppLayouts/Profile/views/KeycardView.qml @@ -84,6 +84,10 @@ SettingsContentBase { Layout.preferredWidth: root.contentWidth keycardStore: root.keycardStore keyUid: d.observedKeyUid + + onChangeSectionTitle: { + root.sectionTitle = title + } } Connections { diff --git a/ui/app/AppLayouts/Profile/views/keycard/DetailsView.qml b/ui/app/AppLayouts/Profile/views/keycard/DetailsView.qml index f3a80adafe..d3bb13a0e4 100644 --- a/ui/app/AppLayouts/Profile/views/keycard/DetailsView.qml +++ b/ui/app/AppLayouts/Profile/views/keycard/DetailsView.qml @@ -18,11 +18,20 @@ ColumnLayout { property KeycardStore keycardStore property string keyUid: "" + signal changeSectionTitle(string title) + spacing: Constants.settingsSection.itemSpacing QtObject { id: d property bool collapsed: true + + function checkAndCheckTitleIfNeeded(newKeycardName) { + // We change title if there is only a single keycard for a keypair in keycard details view + if (root.keycardStore.keycardModule.keycardDetailsModel.count === 1) { + root.changeSectionTitle(newKeycardName) + } + } } StatusListView { @@ -42,6 +51,10 @@ ColumnLayout { keyPairIcon: model.keycard.icon keyPairImage: model.keycard.image keyPairAccounts: model.keycard.accounts + + onKeycardNameChanged: { + d.checkAndCheckTitleIfNeeded(keycardName) + } } } diff --git a/vendor/status-go b/vendor/status-go index d0cc036d48..2d16e7b891 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit d0cc036d486092262382d45967db11f19d66641e +Subproject commit 2d16e7b8910f40070086f3966ce8f9eb55ee8223 diff --git a/vendor/status-keycard-go b/vendor/status-keycard-go index b50cfe22ac..5bfafd14e6 160000 --- a/vendor/status-keycard-go +++ b/vendor/status-keycard-go @@ -1 +1 @@ -Subproject commit b50cfe22ac3802d508e34b0561095c7100f6efa8 +Subproject commit 5bfafd14e6361c551cfb55b527448de8e4646e83