From 94dc7b04a511955c827d618dbe67b4680c492108 Mon Sep 17 00:00:00 2001 From: Stefan Date: Tue, 25 Jun 2024 00:26:53 +0300 Subject: [PATCH] feat(dapp) disconnect wallet connect dapps Closes: #15189 --- .../wallet_connect/controller.nim | 3 + .../service/wallet_connect/service.nim | 4 + storybook/pages/DAppsWorkflowPage.qml | 106 +++++++-- .../Wallet/controls/DappsComboBox.qml | 7 +- .../Wallet/panels/DAppsWorkflow.qml | 6 +- .../services/dapps/DAppsListProvider.qml | 5 - .../services/dapps/WalletConnectSDK.qml | 38 ++-- .../services/dapps/WalletConnectService.qml | 17 +- .../walletconnect/controls/DAppDelegate.qml | 211 +++++++++--------- ui/imports/shared/stores/DAppsStore.qml | 4 + 10 files changed, 248 insertions(+), 153 deletions(-) diff --git a/src/app/modules/shared_modules/wallet_connect/controller.nim b/src/app/modules/shared_modules/wallet_connect/controller.nim index 36a46345da..0f19512ed6 100644 --- a/src/app/modules/shared_modules/wallet_connect/controller.nim +++ b/src/app/modules/shared_modules/wallet_connect/controller.nim @@ -27,6 +27,9 @@ QtObject: proc addWalletConnectSession*(self: Controller, session_json: string): bool {.slot.} = return self.service.addSession(session_json) + proc deactivateWalletConnectSession*(self: Controller, topic: string): bool {.slot.} = + return self.service.deactivateSession(topic) + proc dappsListReceived*(self: Controller, dappsJson: string) {.signal.} # Emits signal dappsListReceived with the list of dApps diff --git a/src/app_service/service/wallet_connect/service.nim b/src/app_service/service/wallet_connect/service.nim index da1f83140a..5496ab9110 100644 --- a/src/app_service/service/wallet_connect/service.nim +++ b/src/app_service/service/wallet_connect/service.nim @@ -71,6 +71,10 @@ QtObject: # TODO #14588: call it async return status_go.addSession(session_json) + proc addSession*(self: Service, topic: string): bool = + # TODO #14588: call it async + return status_go.deactivateSession(topic) + proc getDapps*(self: Service): string = let validAtEpoch = now().toTime().toUnix() let testChains = self.settingsService.areTestNetworksEnabled() diff --git a/storybook/pages/DAppsWorkflowPage.qml b/storybook/pages/DAppsWorkflowPage.qml index 212b79b637..53d761668f 100644 --- a/storybook/pages/DAppsWorkflowPage.qml +++ b/storybook/pages/DAppsWorkflowPage.qml @@ -108,8 +108,11 @@ Item { color: "grey" } + StatusBaseText { text: "Requests Queue" } + ListView { Layout.fillWidth: true + Layout.preferredHeight: Math.min(50, contentHeight) model: walletConnectService.requestHandler.requestsModel delegate: RowLayout { StatusBaseText { @@ -119,6 +122,35 @@ Item { } } + Rectangle { + Layout.fillWidth: true + Layout.preferredHeight: 1 + color: "grey" + } + + StatusBaseText { text: "Persisted Sessions" } + + ListView { + Layout.fillWidth: true + Layout.preferredHeight: Math.min(100, contentHeight) + model: sessionsModel + delegate: RowLayout { + StatusBaseText { + text: SQUtils.Utils.elideAndFormatWalletAddress(model.topic, 6, 4) + Layout.fillWidth: true + } + } + } + + StatusButton { + text: qsTr("Clear Persistance") + visible: sessionsModel.count > 0 + onClicked: { + settings.persistedSessions = "[]" + d.updateSessionsModelAndAddNewIfNotNull(null) + } + } + // spacer ColumnLayout {} @@ -268,19 +300,26 @@ Item { console.info("Persist Session", sessionJson) let session = JSON.parse(sessionJson) + d.updateSessionsModelAndAddNewIfNotNull(session) - let firstIconUrl = session.peer.metadata.icons.length > 0 ? session.peer.metadata.icons[0] : "" - let persistedDapp = { - "name": session.peer.metadata.name, - "url": session.peer.metadata.url, - "iconUrl": firstIconUrl - } - d.persistedDapps.push(persistedDapp) + return true + } + + function deactivateWalletConnectSession(topic) { + console.info("Deactivate Persisted Session", topic) + + let sessions = JSON.parse(settings.persistedSessions) + let newSessions = sessions.filter(function(session) { + return session.topic !== topic + }) + settings.persistedSessions = JSON.stringify(newSessions) + d.updateSessionsModelAndAddNewIfNotNull(null) return true } function getDapps() { - this.dappsListReceived(JSON.stringify(d.persistedDapps)) + let dappsJson = JSON.stringify(d.persistedDapps) + this.dappsListReceived(dappsJson) return true } @@ -323,7 +362,7 @@ Item { } property var accounts: customAccountsModel.count > 0 ? customAccountsModel : defaultAccountsModel readonly property ListModel nonWatchAccounts: accounts - + function getNetworkShortNames(chainIds) { return "eth:oeth:arb" } @@ -355,15 +394,43 @@ Item { readonly property int openDappsTestCase: 1 readonly property int openPairTestCase: 2 - property var persistedDapps: [ - {"name":"Test dApp 1", "url":"https://dapp.test/1","iconUrl":"https://se-sdk-dapp.vercel.app/assets/eip155:1.png"}, - {"name":"Test dApp 2", "url":"https://dapp.test/2","iconUrl":"https://react-app.walletconnect.com/assets/eip155-1.png"}, - {"name":"Test dApp 3", "url":"https://dapp.test/3","iconUrl":"https://react-app.walletconnect.com/assets/eip155-1.png"}, - {"name":"Test dApp 4 - very long name !!!!!!!!!!!!!!!!", "url":"https://dapp.test/4","iconUrl":"https://react-app.walletconnect.com/assets/eip155-1.png"}, - {"name":"Test dApp 5 - very long url", "url":"https://dapp.test/very_long/url/unusual","iconUrl":"https://react-app.walletconnect.com/assets/eip155-1.png"}, - {"name":"Test dApp 6", "url":"https://dapp.test/6","iconUrl":"https://react-app.walletconnect.com/assets/eip155-1.png"} - ] + ListModel { + id: sessionsModel + } + function updateSessionsModelAndAddNewIfNotNull(newSession) { + var sessions = JSON.parse(settings.persistedSessions) + if (!!newSession) { + sessions.push(newSession) + settings.persistedSessions = JSON.stringify(sessions) + } + + sessionsModel.clear() + d.persistedDapps = [] + sessions.forEach(function(session) { + sessionsModel.append(session) + + let firstIconUrl = session.peer.metadata.icons.length > 0 ? session.peer.metadata.icons[0] : "" + let persistedDapp = { + "name": session.peer.metadata.name, + "url": session.peer.metadata.url, + "iconUrl": firstIconUrl, + "topic": session.topic + } + var found = false + for (var i = 0; i < d.persistedDapps.length; i++) { + if (d.persistedDapps[i].url == persistedDapp.url) { + found = true + break + } + } + if (!found) { + d.persistedDapps.push(persistedDapp) + } + }) + } + + property var persistedDapps: [] ListModel { id: customAccountsModel } @@ -387,6 +454,11 @@ Item { property bool enableSDK: true property bool pending : false property string customAccounts: "" + property string persistedSessions: "[]" + } + + Component.onCompleted: { + d.updateSessionsModelAndAddNewIfNotNull(null) } } diff --git a/ui/app/AppLayouts/Wallet/controls/DappsComboBox.qml b/ui/app/AppLayouts/Wallet/controls/DappsComboBox.qml index 389d523cbb..642b88dea8 100644 --- a/ui/app/AppLayouts/Wallet/controls/DappsComboBox.qml +++ b/ui/app/AppLayouts/Wallet/controls/DappsComboBox.qml @@ -19,6 +19,7 @@ ComboBox { signal dappsListReady signal pairDapp + signal disconnectDapp(string dappUrl) implicitHeight: 38 implicitWidth: 38 @@ -54,11 +55,15 @@ ComboBox { delegate: DAppDelegate { width: ListView.view.width + + onDisconnectDapp: (dappUrl) => { + root.disconnectDapp(dappUrl) + } } popup: DAppsListPopup { objectName: "dappsListPopup" - + x: root.width - width y: root.height + 4 diff --git a/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml b/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml index d3b32b2ead..2c22178f79 100644 --- a/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml +++ b/ui/app/AppLayouts/Wallet/panels/DAppsWorkflow.qml @@ -27,6 +27,10 @@ DappsComboBox { pairWCLoader.active = true } + onDisconnectDapp: (dappUrl) => { + root.wcService.disconnectDapp(dappUrl) + } + Loader { id: pairWCLoader @@ -94,7 +98,7 @@ DappsComboBox { onDisconnect: { connectDappLoader.active = false - root.wcService.disconnectDapp(sessionTopic) + root.wcService.disconnectSession(sessionTopic) } } } diff --git a/ui/app/AppLayouts/Wallet/services/dapps/DAppsListProvider.qml b/ui/app/AppLayouts/Wallet/services/dapps/DAppsListProvider.qml index 5c31b06d2b..5c62326bbd 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/DAppsListProvider.qml +++ b/ui/app/AppLayouts/Wallet/services/dapps/DAppsListProvider.qml @@ -45,11 +45,6 @@ QObject { root.store.dappsListReceived.disconnect(dappsListReceivedFn); } - // TODO DEV: check if still holds true - // Reasons to postpone `getDapps` call: - // - the first recent made session will always have `active` prop set to false - // - expiration date won't be the correct one, but one used in session proposal - // - the list of dapps will display successfully made pairing as inactive getActiveSessionsFn = () => { sdk.getActiveSessions((sessions) => { root.store.dappsListReceived.disconnect(dappsListReceivedFn); diff --git a/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectSDK.qml b/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectSDK.qml index bf824d88de..6b2cdd02df 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectSDK.qml +++ b/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectSDK.qml @@ -99,7 +99,7 @@ WalletConnectSDKBase { d.engine.runJavaScript(`wc.init("${root.projectId}").catch((error) => {wc.statusObject.sdkInitialized("SDK init error: "+error);})`, function(result) { - console.debug(`WC WalletConnectSDK.wcCall.init; response: ${JSON.stringify(result, null, 2)}`) + console.debug(`WC WalletConnectSDK.wcCall.init; response: ${JSON.stringify(result)}`) if (result && !!result.error) { @@ -113,7 +113,7 @@ WalletConnectSDKBase { if (d.engine) { d.engine.runJavaScript(`wc.getPairings()`, function(result) { - console.debug(`WC WalletConnectSDK.wcCall.getPairings; result: ${JSON.stringify(result, null, 2)}`) + console.debug(`WC WalletConnectSDK.wcCall.getPairings; result: ${JSON.stringify(result)}`) if (callback && result) { callback(result) @@ -127,12 +127,6 @@ WalletConnectSDKBase { if (d.engine) { d.engine.runJavaScript(`wc.getActiveSessions()`, function(result) { - var allSessions = "" - for (var key of Object.keys(result)) { - allSessions += `\nsessionTopic: ${key} relatedPairingTopic: ${result[key].pairingTopic}`; - } - console.debug(`WC WalletConnectSDK.wcCall.getActiveSessions; result: ${allSessions}`) - if (callback && result) { callback(result) } @@ -294,7 +288,7 @@ WalletConnectSDKBase { console.debug(`WC WalletConnectSDK.wcCall.auth; cacaoPayload: ${JSON.stringify(cacaoPayload)}, address: ${address}`) d.engine.runJavaScript(`wc.formatAuthMessage(${JSON.stringify(cacaoPayload)}, "${address}")`, function(result) { - console.debug(`WC WalletConnectSDK.wcCall.formatAuthMessage; response: ${JSON.stringify(result, null, 2)}`) + console.debug(`WC WalletConnectSDK.wcCall.formatAuthMessage; response: ${JSON.stringify(result)}`) root.authMessageFormated(result, address) }) @@ -373,12 +367,12 @@ WalletConnectSDKBase { } function onBuildApprovedNamespacesResponse(approvedNamespaces, error) { - console.debug(`WC WalletConnectSDK.onBuildApprovedNamespacesResponse; approvedNamespaces: ${approvedNamespaces ? JSON.stringify(approvedNamespaces, null, 2) : "-"}, error: ${error}`) + console.debug(`WC WalletConnectSDK.onBuildApprovedNamespacesResponse; approvedNamespaces: ${approvedNamespaces ? JSON.stringify(approvedNamespaces) : "-"}, error: ${error}`) root.buildApprovedNamespacesResult(approvedNamespaces, error) } function onApproveSessionResponse(session, error) { - console.debug(`WC WalletConnectSDK.onApproveSessionResponse; sessionTopic: ${JSON.stringify(session, null, 2)}, error: ${error}`) + console.debug(`WC WalletConnectSDK.onApproveSessionResponse; sessionTopic: ${JSON.stringify(session)}, error: ${error}`) root.approveSessionResult(session, error) } @@ -400,51 +394,51 @@ WalletConnectSDKBase { } function onSessionProposal(details) { - console.debug(`WC WalletConnectSDK.onSessionProposal; details: ${JSON.stringify(details, null, 2)}`) + console.debug(`WC WalletConnectSDK.onSessionProposal; details: ${JSON.stringify(details)}`) root.sessionProposal(details) } function onSessionUpdate(details) { - console.debug(`WC TODO WalletConnectSDK.onSessionUpdate; details: ${JSON.stringify(details, null, 2)}`) + console.debug(`WC TODO WalletConnectSDK.onSessionUpdate; details: ${JSON.stringify(details)}`) } function onSessionExtend(details) { - console.debug(`WC TODO WalletConnectSDK.onSessionExtend; details: ${JSON.stringify(details, null, 2)}`) + console.debug(`WC TODO WalletConnectSDK.onSessionExtend; details: ${JSON.stringify(details)}`) } function onSessionPing(details) { - console.debug(`WC TODO WalletConnectSDK.onSessionPing; details: ${JSON.stringify(details, null, 2)}`) + console.debug(`WC TODO WalletConnectSDK.onSessionPing; details: ${JSON.stringify(details)}`) } function onSessionDelete(details) { - console.debug(`WC WalletConnectSDK.onSessionDelete; details: ${JSON.stringify(details, null, 2)}`) + console.debug(`WC WalletConnectSDK.onSessionDelete; details: ${JSON.stringify(details)}`) root.sessionDelete(details.topic, "") } function onSessionExpire(details) { - console.debug(`WC TODO WalletConnectSDK.onSessionExpire; details: ${JSON.stringify(details, null, 2)}`) + console.debug(`WC TODO WalletConnectSDK.onSessionExpire; details: ${JSON.stringify(details)}`) } function onSessionRequest(details) { - console.debug(`WC WalletConnectSDK.onSessionRequest; details: ${JSON.stringify(details, null, 2)}`) + console.debug(`WC WalletConnectSDK.onSessionRequest; details: ${JSON.stringify(details)}`) root.sessionRequestEvent(details) } function onSessionRequestSent(details) { - console.debug(`WC TODO WalletConnectSDK.onSessionRequestSent; details: ${JSON.stringify(details, null, 2)}`) + console.debug(`WC TODO WalletConnectSDK.onSessionRequestSent; details: ${JSON.stringify(details)}`) } function onSessionEvent(details) { - console.debug(`WC TODO WalletConnectSDK.onSessionEvent; details: ${JSON.stringify(details, null, 2)}`) + console.debug(`WC TODO WalletConnectSDK.onSessionEvent; details: ${JSON.stringify(details)}`) } function onProposalExpire(details) { - console.debug(`WC WalletConnectSDK.onProposalExpire; details: ${JSON.stringify(details, null, 2)}`) + console.debug(`WC WalletConnectSDK.onProposalExpire; details: ${JSON.stringify(details)}`) root.sessionProposalExpired() } function onAuthRequest(details) { - console.debug(`WC WalletConnectSDK.onAuthRequest; details: ${JSON.stringify(details, null, 2)}`) + console.debug(`WC WalletConnectSDK.onAuthRequest; details: ${JSON.stringify(details)}`) root.authRequest(details) } diff --git a/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml b/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml index 555862d6e3..46676030f6 100644 --- a/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml +++ b/ui/app/AppLayouts/Wallet/services/dapps/WalletConnectService.qml @@ -64,10 +64,22 @@ QObject { wcSDK.rejectSession(id) } - function disconnectDapp(sessionTopic) { + function disconnectSession(sessionTopic) { wcSDK.disconnectSession(sessionTopic) } + function disconnectDapp(url) { + wcSDK.getActiveSessions((sessions) => { + for (let key in sessions) { + let dapp = sessions[key].peer.metadata + let topic = sessions[key].topic + if (dapp.url == url) { + wcSDK.disconnectSession(topic) + } + } + }); + } + signal connectDApp(var dappChains, var sessionProposal, var approvedNamespaces) signal approveSessionResult(var session, var error) signal sessionRequest(SessionRequestResolved request) @@ -133,6 +145,9 @@ 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) { diff --git a/ui/imports/shared/popups/walletconnect/controls/DAppDelegate.qml b/ui/imports/shared/popups/walletconnect/controls/DAppDelegate.qml index 144335a8da..ccb3a533ee 100644 --- a/ui/imports/shared/popups/walletconnect/controls/DAppDelegate.qml +++ b/ui/imports/shared/popups/walletconnect/controls/DAppDelegate.qml @@ -1,106 +1,105 @@ -import QtQuick 2.15 -import QtQuick.Layouts 1.15 -import QtGraphicalEffects 1.15 - -import StatusQ.Core 0.1 -import StatusQ.Core.Theme 0.1 -import StatusQ.Controls 0.1 -import StatusQ.Components 0.1 - -MouseArea { - id: root - implicitHeight: 50 - - hoverEnabled: true - - required property string name - required property string url - required property string iconUrl - - signal disconnectDapp() - - RowLayout { - anchors.fill: parent - anchors.margins: 8 - - Item { - Layout.preferredWidth: 32 - Layout.preferredHeight: 32 - - StatusImage { - id: iconImage - - anchors.fill: parent - - source: root.iconUrl - visible: !fallbackImage.visible - } - - StatusIcon { - id: fallbackImage - - anchors.fill: parent - - icon: "dapp" - color: Theme.palette.baseColor1 - - visible: iconImage.isLoading || iconImage.isError || !root.iconUrl - } - - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: iconImage.width - height: iconImage.height - radius: width / 2 - visible: false - } - } - } - - ColumnLayout { - Layout.leftMargin: 12 - Layout.rightMargin: 12 - - StatusBaseText { - text: root.name - - Layout.fillWidth: true - - font.pixelSize: 13 - font.bold: true - - elide: Text.ElideRight - - clip: true - } - StatusBaseText { - text: root.url - - Layout.fillWidth: true - - font.pixelSize: 12 - color: Theme.palette.baseColor1 - - elide: Text.ElideRight - - clip: true - } - } - - StatusFlatButton { - size: StatusBaseButton.Size.Large - - asset.color: root.containsMouse ? Theme.palette.directColor1 - : Theme.palette.baseColor1 - - icon.name: "disconnect" - tooltip.text: qsTr("Disconnect dApp") - - onClicked: { - console.debug(`TODO #14755 - Disconnect ${root.name}`) - // root.disconnectDapp() - } - } - } -} +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtGraphicalEffects 1.15 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Components 0.1 + +MouseArea { + id: root + implicitHeight: 50 + + hoverEnabled: true + + required property string name + required property string url + required property string iconUrl + + signal disconnectDapp(string dappUrl) + + RowLayout { + anchors.fill: parent + anchors.margins: 8 + + Item { + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + + StatusImage { + id: iconImage + + anchors.fill: parent + + source: root.iconUrl + visible: !fallbackImage.visible + } + + StatusIcon { + id: fallbackImage + + anchors.fill: parent + + icon: "dapp" + color: Theme.palette.baseColor1 + + visible: iconImage.isLoading || iconImage.isError || !root.iconUrl + } + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: iconImage.width + height: iconImage.height + radius: width / 2 + visible: false + } + } + } + + ColumnLayout { + Layout.leftMargin: 12 + Layout.rightMargin: 12 + + StatusBaseText { + text: root.name + + Layout.fillWidth: true + + font.pixelSize: 13 + font.bold: true + + elide: Text.ElideRight + + clip: true + } + StatusBaseText { + text: root.url + + Layout.fillWidth: true + + font.pixelSize: 12 + color: Theme.palette.baseColor1 + + elide: Text.ElideRight + + clip: true + } + } + + StatusFlatButton { + size: StatusBaseButton.Size.Large + + asset.color: root.containsMouse ? Theme.palette.directColor1 + : Theme.palette.baseColor1 + + icon.name: "disconnect" + tooltip.text: qsTr("Disconnect dApp") + + onClicked: { + root.disconnectDapp(root.url) + } + } + } +} diff --git a/ui/imports/shared/stores/DAppsStore.qml b/ui/imports/shared/stores/DAppsStore.qml index 6a7372db9d..2bed45fd98 100644 --- a/ui/imports/shared/stores/DAppsStore.qml +++ b/ui/imports/shared/stores/DAppsStore.qml @@ -16,6 +16,10 @@ QObject { return controller.addWalletConnectSession(sessionJson) } + function deactivateWalletConnectSession(topic) { + return controller.deactivateWalletConnectSession(topic) + } + function authenticateUser(topic, id, address) { let ok = controller.authenticateUser(topic, id, address) if(!ok) {