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