From fea4e8ed7695acbbabf3c39da955bf4bab1962c3 Mon Sep 17 00:00:00 2001 From: Alex Jbanca Date: Fri, 2 Aug 2024 14:18:58 +0300 Subject: [PATCH] fix(WalletConnect): Fixing disconnect notifications The disconnect notifications were operating on `WalletConnectService.currentSessionProposal`. This object stores the current session object on connect, but it's not necessarily the same session the user wants to disconnect. To fix this I'm getting the active sessions from status-go when the disconnect request is received (from Status or dapp). If the topic matches to any connection topic owned by the users accounts we'll show a notification. --- .../wallet_connect/controller.nim | 16 ++++- .../service/wallet_connect/service.nim | 5 ++ src/backend/wallet_connect.nim | 7 ++- .../services/dapps/WalletConnectService.qml | 59 +++++++++++++++---- ui/imports/shared/stores/DAppsStore.qml | 18 ++++++ 5 files changed, 91 insertions(+), 14 deletions(-) diff --git a/src/app/modules/shared_modules/wallet_connect/controller.nim b/src/app/modules/shared_modules/wallet_connect/controller.nim index d81e509c49..d21522f55e 100644 --- a/src/app/modules/shared_modules/wallet_connect/controller.nim +++ b/src/app/modules/shared_modules/wallet_connect/controller.nim @@ -1,5 +1,5 @@ import NimQml -import chronicles +import chronicles, times, json import app_service/service/wallet_connect/service as wallet_connect_service import app_service/service/wallet_account/service as wallet_account_service @@ -47,6 +47,20 @@ QtObject: self.dappsListReceived(res) return true + proc activeSessionsReceived(self: Controller, activeSessionsJson: string) {.signal.} + + # Emits signal activeSessionsReceived with the list of active sessions + # TODO: make it async + proc getActiveSessions(self: Controller): bool {.slot.} = + let validAtTimestamp = now().toTime().toUnix() + let res = self.service.getActiveSessions(validAtTimestamp) + if res.isNil: + return false + else: + let resultStr = $res + self.activeSessionsReceived(resultStr) + return true + proc userAuthenticationResult*(self: Controller, topic: string, id: string, error: bool, password: string, pin: string, payload: string) {.signal.} # Beware, it will fail if an authentication is already in progress diff --git a/src/app_service/service/wallet_connect/service.nim b/src/app_service/service/wallet_connect/service.nim index 9b04c10529..15c8a0c404 100644 --- a/src/app_service/service/wallet_connect/service.nim +++ b/src/app_service/service/wallet_connect/service.nim @@ -114,6 +114,11 @@ QtObject: let testChains = self.settingsService.areTestNetworksEnabled() # TODO #14588: call it async return status_go.getDapps(validAtEpoch, testChains) + + proc getActiveSessions*(self: Service, validAtTimestamp: int64): JsonNode = + # TODO #14588: call it async + return status_go.getActiveSessions(validAtTimestamp) + # Will fail if another authentication is in progress proc authenticateUser*(self: Service, keyUid: string, callback: AuthenticationResponseFn): bool = diff --git a/src/backend/wallet_connect.nim b/src/backend/wallet_connect.nim index d3863da3ba..d3abba0844 100644 --- a/src/backend/wallet_connect.nim +++ b/src/backend/wallet_connect.nim @@ -18,7 +18,7 @@ rpc(disconnectWalletConnectSession, "wallet"): topic: string rpc(getWalletConnectActiveSessions, "wallet"): - validAtTimestamp: int + validAtTimestamp: int64 rpc(hashMessageEIP191, "wallet"): message: string @@ -50,13 +50,14 @@ proc disconnectSession*(topic: string): bool = return false # returns nil if error -proc getActiveSessions*(validAtTimestamp: int): JsonNode = +proc getActiveSessions*(validAtTimestamp: int64): JsonNode = try: let rpcRes = getWalletConnectActiveSessions(validAtTimestamp) + if(not isSuccessResponse(rpcRes)): return nil - let jsonResultStr = rpcRes.result.getStr() + let jsonResultStr = $rpcRes.result if jsonResultStr == "null" or jsonResultStr == "": return newJArray() diff --git a/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml b/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml index dfca0c10d5..92589e0157 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml +++ b/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml @@ -210,16 +210,7 @@ QObject { } function onSessionDelete(topic, err) { - store.deactivateWalletConnectSession(topic) - dappsProvider.updateDapps() - - const app_url = d.currentSessionProposal ? d.currentSessionProposal.params.proposer.metadata.url : "-" - const app_domain = StringUtils.extractDomainFromLink(app_url) - if(err) { - root.displayToastMessage(qsTr("Failed to disconnect from %1").arg(app_domain), true) - } else { - root.displayToastMessage(qsTr("Disconnected from %1").arg(app_domain), false) - } + d.disconnectSessionRequested(topic, err) } } @@ -235,6 +226,54 @@ QObject { timeoutTimer.stop() root.pairingValidated(state) } + + function disconnectSessionRequested(topic, err) { + // Get all sessions and filter the active ones for known accounts + // Act on the first matching session with the same topic + const activeSessionsCallback = (allSessions, success) => { + store.activeSessionsReceived.disconnect(activeSessionsCallback) + + if (!success) { + // TODO #14754: implement custom dApp notification + d.notifyDappDisconnect("-", true) + return + } + + // Convert to original format + const webSdkSessions = allSessions.map((session) => { + return JSON.parse(session.sessionJson) + }) + + const sessions = DAppsHelpers.filterActiveSessionsForKnownAccounts(webSdkSessions, root.validAccounts) + + for (const sessionID in sessions) { + const session = sessions[sessionID] + if (session.topic === topic) { + store.deactivateWalletConnectSession(topic) + dappsProvider.updateDapps() + + const dappUrl = session.peer.metadata.url ?? "-" + d.notifyDappDisconnect(dappUrl, err) + break + } + } + } + + store.activeSessionsReceived.connect(activeSessionsCallback) + if (!store.getActiveSessions()) { + store.activeSessionsReceived.disconnect(activeSessionsCallback) + // TODO #14754: implement custom dApp notification + } + } + + function notifyDappDisconnect(dappUrl, err) { + const appDomain = StringUtils.extractDomainFromLink(dappUrl) + if(err) { + root.displayToastMessage(qsTr("Failed to disconnect from %1").arg(appDomain), true) + } else { + root.displayToastMessage(qsTr("Disconnected from %1").arg(appDomain), false) + } + } } Component.onCompleted: { diff --git a/ui/imports/shared/stores/DAppsStore.qml b/ui/imports/shared/stores/DAppsStore.qml index 5a6825af52..af316f2511 100644 --- a/ui/imports/shared/stores/DAppsStore.qml +++ b/ui/imports/shared/stores/DAppsStore.qml @@ -8,6 +8,7 @@ QObject { required property var controller /// \c dappsJson serialized from status-go.wallet.GetDapps signal dappsListReceived(string dappsJson) + signal activeSessionsReceived(var activeSessionsJsonObj, bool success) signal userAuthenticated(string topic, string id, string password, string pin, string payload) signal userAuthenticationFailed(string topic, string id) @@ -106,6 +107,12 @@ QObject { function getDapps() { return controller.getDapps() } + + /// \c getActiveSessions triggers an async response to \c activeSessionsReceived + /// \returns true if the request was sent successfully + function getActiveSessions() { + return controller.getActiveSessions() + } function hexToDec(hex) { return controller.hexToDecBigString(hex) @@ -124,6 +131,17 @@ QObject { root.dappsListReceived(dappsJson) } + function onActiveSessionsReceived(activeSessionsJson) { + try { + const jsonObj = JSON.parse(activeSessionsJson) + root.activeSessionsReceived(jsonObj, true) + } catch (e) { + console.error("Failed to parse activeSessionsJson", e) + root.activeSessionsReceived({}, false) + return + } + } + function onUserAuthenticationResult(topic, id, success, password, pin, payload) { if (success) { root.userAuthenticated(topic, id, password, pin, payload)