From d43b5c35aa49aaa27de6bdecc8ca3bcd7ec8d267 Mon Sep 17 00:00:00 2001 From: Andrey Bocharnikov Date: Wed, 29 Oct 2025 19:36:46 +0400 Subject: [PATCH 1/7] feat(dapp-browser-ens)_: integrate browserBridge to BrowserLayout fixes #19141 --- .../StatusQ/Core/Utils/SubscriptionBroker.qml | 16 ++--- .../Utils/SubscriptionBrokerCommunities.qml | 16 ++--- ui/app/AppLayouts/Browser/BrowserLayout.qml | 58 ++++++++++--------- .../Browser/provider/qml/ConnectorBridge.qml | 23 ++++---- .../provider/qml/Eip1193ProviderAdapter.qml | 25 +++----- .../AppLayouts/Browser/provider/qml/Utils.js | 4 +- ui/app/AppLayouts/Browser/provider/qml/qmldir | 4 ++ 7 files changed, 72 insertions(+), 74 deletions(-) create mode 100644 ui/app/AppLayouts/Browser/provider/qml/qmldir diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/SubscriptionBroker.qml b/ui/StatusQ/src/StatusQ/Core/Utils/SubscriptionBroker.qml index da888f7b8ad..77480e14527 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/SubscriptionBroker.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/SubscriptionBroker.qml @@ -126,6 +126,14 @@ QtObject { } } + const onDestructionHandler = () => { + if(!d.managedSubscriptions.hasOwnProperty(subscriptionId)) + return + + root.unsubscribed.disconnect(onUnsubscribedHandler) //object is destroyed, no need to listen to the signal anymore + unsubscribe(subscriptionId) + } + const onUnsubscribedHandler = (subscriptionId) => { if(subscriptionId !== subscription.subscriptionId) return @@ -135,14 +143,6 @@ QtObject { subscription.topicChanged.disconnect(onTopicChangeHandler) } - const onDestructionHandler = () => { - if(!d.managedSubscriptions.hasOwnProperty(subscriptionId)) - return - - root.unsubscribed.disconnect(onUnsubscribedHandler) //object is destroyed, no need to listen to the signal anymore - unsubscribe(subscriptionId) - } - subscription.Component.onDestruction.connect(onDestructionHandler) subscription.isReadyChanged.connect(onReadyChangeHandler) subscription.topicChanged.connect(onTopicChangeHandler) diff --git a/ui/StatusQ/src/StatusQ/Core/Utils/SubscriptionBrokerCommunities.qml b/ui/StatusQ/src/StatusQ/Core/Utils/SubscriptionBrokerCommunities.qml index 6aa261680fc..5d7e7309462 100644 --- a/ui/StatusQ/src/StatusQ/Core/Utils/SubscriptionBrokerCommunities.qml +++ b/ui/StatusQ/src/StatusQ/Core/Utils/SubscriptionBrokerCommunities.qml @@ -116,6 +116,14 @@ QtObject { } } + const onDestructionHandler = () => { + if(!d.managedSubscriptions.hasOwnProperty(subscriptionId)) + return + + root.unsubscribed.disconnect(onUnsubscribedHandler) //object is destroyed, no need to listen to the signal anymore + unsubscribe(subscriptionId) + } + const onUnsubscribedHandler = (subscriptionId) => { if(subscriptionId !== subscription.subscriptionId) return @@ -125,14 +133,6 @@ QtObject { subscription.topicChanged.disconnect(onTopicChangeHandler) } - const onDestructionHandler = () => { - if(!d.managedSubscriptions.hasOwnProperty(subscriptionId)) - return - - root.unsubscribed.disconnect(onUnsubscribedHandler) //object is destroyed, no need to listen to the signal anymore - unsubscribe(subscriptionId) - } - subscription.Component.onDestruction.connect(onDestructionHandler) subscription.isReadyChanged.connect(onReadyChangeHandler) subscription.topicChanged.connect(onTopicChangeHandler) diff --git a/ui/app/AppLayouts/Browser/BrowserLayout.qml b/ui/app/AppLayouts/Browser/BrowserLayout.qml index dfbb62494c2..d4346d3e995 100644 --- a/ui/app/AppLayouts/Browser/BrowserLayout.qml +++ b/ui/app/AppLayouts/Browser/BrowserLayout.qml @@ -18,6 +18,7 @@ import shared.stores.send import AppLayouts.Browser.stores as BrowserStores +import "provider/qml" import "popups" import "controls" import "views" @@ -41,6 +42,7 @@ StatusSectionLayout { required property BrowserStores.DownloadsStore downloadsStore required property BrowserStores.BrowserRootStore browserRootStore required property BrowserStores.BrowserWalletStore browserWalletStore + required property var connectorController signal sendToRecipientRequested(string address) @@ -49,6 +51,22 @@ StatusSectionLayout { tab.url = _internal.determineRealURL(url) } + ConnectorBridge { + id: connectorBridge + + userUID: root.userUID + connectorController: root.connectorController + defaultAccountAddress: root.browserWalletStore.dappBrowserAccount.address + accountsModel: root.browserWalletStore.accounts + httpUserAgent: { + if (localAccountSensitiveSettings.compatibilityMode) { + // Google doesn't let you connect if the user agent is Chrome-ish and doesn't satisfy some sort of hidden requirement + return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36" + } + return "" + } + } + QtObject { id: _internal @@ -91,32 +109,13 @@ StatusSectionLayout { standardButtons: Dialog.Ok } - property QtObject defaultProfile: WebEngineProfile { - storageName: "Profile_%1".arg(root.userUID) - offTheRecord: false - httpUserAgent: { - if (localAccountSensitiveSettings.compatibilityMode) { - // Google doesn't let you connect if the user agent is Chrome-ish and doesn't satisfy some sort of hidden requirement - return "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36" - } - return "" - } - } - - property QtObject otrProfile: WebEngineProfile { - storageName: "IncognitoProfile_%1".arg(root.userUID) - offTheRecord: true - persistentCookiesPolicy: WebEngineProfile.NoPersistentCookies - httpUserAgent: _internal.defaultProfile.httpUserAgent - } - function addNewDownloadTab() { - tabs.createDownloadTab(tabs.count !== 0 ? currentWebView.profile : defaultProfile); + tabs.createDownloadTab(tabs.count !== 0 ? currentWebView.profile : connectorBridge.defaultProfile); tabs.currentIndex = tabs.count - 1; } function addNewTab() { - var tab = tabs.createEmptyTab(tabs.count !== 0 ? currentWebView.profile : defaultProfile); + var tab = tabs.createEmptyTab(tabs.count !== 0 ? currentWebView.profile : connectorBridge.defaultProfile); browserHeader.addressBar.forceActiveFocus(); browserHeader.addressBar.selectAll(); @@ -218,9 +217,9 @@ StatusSectionLayout { } onOpenNewTabTriggered: _internal.addNewTab() Component.onCompleted: { - _internal.defaultProfile.downloadRequested.connect(_internal.onDownloadRequested); - _internal.otrProfile.downloadRequested.connect(_internal.onDownloadRequested); - var tab = createEmptyTab(_internal.defaultProfile, true); + connectorBridge.defaultProfile.downloadRequested.connect(_internal.onDownloadRequested); + connectorBridge.otrProfile.downloadRequested.connect(_internal.onDownloadRequested); + var tab = createEmptyTab(connectorBridge.defaultProfile, true); // For Devs: Uncomment the next line if you want to use the simpledapp on first load // tab.url = root.browserRootStore.determineRealURL("https://simpledapp.eth"); } @@ -312,11 +311,11 @@ StatusSectionLayout { id: settingsMenu x: parent.width - width y: browserHeader.y + browserHeader.height - isIncognito: _internal.currentWebView && _internal.currentWebView.profile === _internal.otrProfile + isIncognito: _internal.currentWebView && _internal.currentWebView.profile === connectorBridge.otrProfile onAddNewTab: _internal.addNewTab() onGoIncognito: function (checked) { if (_internal.currentWebView) { - _internal.currentWebView.profile = checked ? _internal.otrProfile : _internal.defaultProfile; + _internal.currentWebView.profile = checked ? connectorBridge.otrProfile : connectorBridge.defaultProfile; } } onZoomIn: { @@ -455,7 +454,7 @@ StatusSectionLayout { bookmarksStore: root.bookmarksStore downloadsStore: root.downloadsStore currentWebView: _internal.currentWebView - webChannel: channel + webChannel: connectorBridge.webChannel findBarComp: findBar favMenu: favoriteMenu addFavModal: addFavoriteModal @@ -542,6 +541,11 @@ StatusSectionLayout { function onUrlChanged() { browserHeader.addressBar.text = root.browserRootStore.obtainAddress(_internal.currentWebView.url) root.browserRootStore.currentTabConnected = false // TODO: Will be handled by connector https://github.com/status-im/status-desktop/issues/19223 + + // Update ConnectorBridge with current dApp metadata + if (_internal.currentWebView && _internal.currentWebView.url) { + connectorBridge.updateDAppUrl(_internal.currentWebView.url, _internal.currentWebView.title) + } } } diff --git a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml index 0d244a6ebe8..d8c7d9c0370 100644 --- a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml +++ b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml @@ -5,16 +5,15 @@ import QtWebChannel import StatusQ.Core.Theme import utils - import "Utils.js" as BrowserUtils /** * ConnectorBridge - * + * * Simplified connector infrastructure for BrowserLayout. - * Provides WebEngine profiles with script injection, WebChannel, + * Provides WebEngine profiles with script injection, WebChannel, * ConnectorManager, and direct connection to Nim backend. - * + * * This component bridges the Browser UI with the Connector backend system. */ QtObject { @@ -26,13 +25,13 @@ QtObject { readonly property alias webChannel: channel readonly property alias manager: connectorManager - + property alias dappUrl: connectorManager.dappUrl property alias dappOrigin: connectorManager.dappOrigin property alias dappName: connectorManager.dappName property alias dappIconUrl: connectorManager.dappIconUrl property alias clientId: connectorManager.clientId - + function hasWalletConnected(hostname, address) { if (!connectorController) return false @@ -54,10 +53,10 @@ QtObject { if (!connectorController) return false return connectorController.disconnect(hostname) } - + function updateDAppUrl(url, name) { if (!url) return - + const urlStr = url.toString() connectorManager.dappUrl = urlStr connectorManager.dappOrigin = Utils.normalizeOrigin(urlStr) @@ -99,7 +98,7 @@ QtObject { readonly property ConnectorManager connectorManager: ConnectorManager { connectorController: root.connectorController // (shared_modules/connector/controller.nim) - + dappUrl: "" dappOrigin: "" dappName: "" @@ -124,16 +123,14 @@ QtObject { readonly property Eip1193ProviderAdapter eip1193ProviderAdapter: Eip1193ProviderAdapter { WebChannel.id: "ethereumProvider" - + chainId: BrowserUtils.chainIdToHex(connectorManager.dappChainId) networkVersion: connectorManager.dappChainId.toString() selectedAddress: connectorManager.accounts.length > 0 ? connectorManager.accounts[0] : "" accounts: connectorManager.accounts connected: connectorManager.connected - function request(args) { - return connectorManager.request(args) - } + onRequestInternal: (args) => connectorManager.request(args) } } diff --git a/ui/app/AppLayouts/Browser/provider/qml/Eip1193ProviderAdapter.qml b/ui/app/AppLayouts/Browser/provider/qml/Eip1193ProviderAdapter.qml index dca3ff33967..4ddaaec3d4c 100644 --- a/ui/app/AppLayouts/Browser/provider/qml/Eip1193ProviderAdapter.qml +++ b/ui/app/AppLayouts/Browser/provider/qml/Eip1193ProviderAdapter.qml @@ -2,10 +2,8 @@ import QtQuick QtObject { id: root - - // ============================================================================ - // EIP-1193 PUBLIC PROPERTIES (exposed to JS via WebChannel) - // ============================================================================ + + // EIP-1193 PUBLIC PROPERTIES readonly property bool isStatus: true readonly property bool isMetaMask: false property string chainId: "0x1" // hex format for EIP-1193 @@ -13,30 +11,23 @@ QtObject { property string selectedAddress: "" // current active address property var accounts: [] property bool connected: false - - // ============================================================================ + // EIP-1193 EVENTS (for WebChannel) - // ============================================================================ - signal connectEvent(var info) signal disconnectEvent(var error) signal accountsChangedEvent(var accounts) signal chainChangedEvent(string chainId) signal messageEvent(var message) signal requestCompletedEvent(var payload) + signal requestInternal(var args) // Internal signal providerStateChanged() // re-read State - - // ============================================================================ + // EIP-1193 REQUEST METHOD STUB - // ============================================================================ function request(args) { - console.error("[Eip1193ProviderAdapter] request() not injected - should be overridden by ConnectorBridge") - return JSON.stringify({ - jsonrpc: "2.0", - id: args && args.requestId || 0, - error: { code: -32603, message: "Request function not properly injected" } - }) + requestInternal(args) + // Return immediately - async response comes via requestCompletedEvent + return { jsonrpc: "2.0", id: args.requestId || 0, result: null } } } diff --git a/ui/app/AppLayouts/Browser/provider/qml/Utils.js b/ui/app/AppLayouts/Browser/provider/qml/Utils.js index 7047f5f5ae1..617d5909022 100644 --- a/ui/app/AppLayouts/Browser/provider/qml/Utils.js +++ b/ui/app/AppLayouts/Browser/provider/qml/Utils.js @@ -1,3 +1,5 @@ +.pragma library + function accountsDidChange(oldAccounts, newAccounts) { const a = oldAccounts || [] const b = newAccounts || [] @@ -55,6 +57,6 @@ function extractDomainName(urlString) { return urlObj.hostname || "Unknown dApp" } catch (e) { return "Unknown dApp" - } +} } diff --git a/ui/app/AppLayouts/Browser/provider/qml/qmldir b/ui/app/AppLayouts/Browser/provider/qml/qmldir new file mode 100644 index 00000000000..f3fff6bc371 --- /dev/null +++ b/ui/app/AppLayouts/Browser/provider/qml/qmldir @@ -0,0 +1,4 @@ +ConnectorBridge 1.0 ConnectorBridge.qml +ConnectorManager 1.0 ConnectorManager.qml +Eip1193ProviderAdapter 1.0 Eip1193ProviderAdapter.qml + From 8f5fdc701c216f9608db651abfe61a99258dc586 Mon Sep 17 00:00:00 2001 From: Andrey Bocharnikov Date: Fri, 31 Oct 2025 17:29:26 +0400 Subject: [PATCH 2/7] feat(dapp-browser)_: remove history from popup fixes #19141 --- ui/app/AppLayouts/Browser/BrowserLayout.qml | 6 --- .../Browser/popups/BrowserWalletMenu.qml | 45 +++---------------- 2 files changed, 5 insertions(+), 46 deletions(-) diff --git a/ui/app/AppLayouts/Browser/BrowserLayout.qml b/ui/app/AppLayouts/Browser/BrowserLayout.qml index d4346d3e995..4e86d4ce0e3 100644 --- a/ui/app/AppLayouts/Browser/BrowserLayout.qml +++ b/ui/app/AppLayouts/Browser/BrowserLayout.qml @@ -34,9 +34,6 @@ StatusSectionLayout { required property bool thirdpartyServicesEnabled required property TransactionStore transactionStore - required property var assetsStore - required property var currencyStore - required property var tokensStore required property BrowserStores.BookmarksStore bookmarksStore required property BrowserStores.DownloadsStore downloadsStore @@ -351,9 +348,6 @@ StatusSectionLayout { Component { id: browserWalletMenu BrowserWalletMenu { - assetsStore: root.assetsStore - currencyStore: root.currencyStore - tokensStore: root.tokensStore currentTabConnected: root.browserRootStore.currentTabConnected browserWalletStore: root.browserWalletStore property point headerPoint: Qt.point(browserHeader.x, browserHeader.y) diff --git a/ui/app/AppLayouts/Browser/popups/BrowserWalletMenu.qml b/ui/app/AppLayouts/Browser/popups/BrowserWalletMenu.qml index ad108bacee3..7a8a470cacd 100644 --- a/ui/app/AppLayouts/Browser/popups/BrowserWalletMenu.qml +++ b/ui/app/AppLayouts/Browser/popups/BrowserWalletMenu.qml @@ -18,10 +18,6 @@ import AppLayouts.Browser.stores as BrowserStores Dialog { id: root - required property var assetsStore - required property var currencyStore - required property var tokensStore - required property bool currentTabConnected required property BrowserStores.BrowserWalletStore browserWalletStore @@ -193,42 +189,11 @@ Dialog { anchors.topMargin: Theme.bigPadding anchors.bottom: parent.bottom - StatusTabBar { - id: walletTabBar - width: parent.width - anchors.top: parent.top - - StatusTabButton { - id: assetBtn - width: implicitWidth - text: qsTr("Assets") - } - StatusTabButton { - id: historyBtn - width: implicitWidth - text: qsTr("History") - } - } - - StackLayout { - id: stackLayout - width: parent.width - anchors.top: walletTabBar.bottom - anchors.topMargin: Theme.bigPadding - anchors.bottom: parent.bottom - currentIndex: walletTabBar.currentIndex - - // FIXME integrate - // AssetsView { - // id: assetsTab - // controller: root.assetsStore.assetsController - // currencyStore: root.currencyStore - // tokensStore: root.tokensStore - // } - HistoryView { - id: historyTab - overview: root.browserWalletStore.dappBrowserAccount - } + // TODO: Add Assets and History tabs when ready + StatusBaseText { + anchors.centerIn: parent + text: qsTr("Wallet info will appear here") + color: Theme.palette.baseColor1 } } onClosed: { From b6490dfb20e580ee2fef29b18b08658c5a6d3328 Mon Sep 17 00:00:00 2001 From: Andrey Bocharnikov Date: Fri, 7 Nov 2025 16:34:20 +0400 Subject: [PATCH 3/7] feat(dapp-browser)_: fix PR fixes #19151 --- ui/app/mainui/AppMain.qml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 34b96759a94..e6ddb8c208e 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -2057,11 +2057,9 @@ Item { downloadsStore: BrowserStores.DownloadsStore {} browserRootStore: BrowserStores.BrowserRootStore {} browserWalletStore: BrowserStores.BrowserWalletStore {} + connectorController: WalletStores.RootStore.dappsConnectorController transactionStore: appMain.transactionStore - assetsStore: appMain.walletAssetsStore - currencyStore: appMain.currencyStore - tokensStore: appMain.tokensStore onSendToRecipientRequested: (address) => popupRequestsHandler.sendModalHandler.sendToRecipient(address) } } From 196adc3dfbc9ddda43a9f1d441722b159156d413 Mon Sep 17 00:00:00 2001 From: Andrey Bocharnikov Date: Fri, 7 Nov 2025 16:41:06 +0400 Subject: [PATCH 4/7] feat(dapp-browser)_: fix PR 2 fixes #19151 --- .../Browser/provider/qml/ConnectorBridge.qml | 1 - .../Browser/provider/qml/ConnectorManager.qml | 128 +++++++++--------- 2 files changed, 64 insertions(+), 65 deletions(-) diff --git a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml index d8c7d9c0370..a783628dff3 100644 --- a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml +++ b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml @@ -133,4 +133,3 @@ QtObject { onRequestInternal: (args) => connectorManager.request(args) } } - diff --git a/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml b/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml index 5740e0953cf..756bda4d764 100644 --- a/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml +++ b/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml @@ -32,29 +32,29 @@ QtObject { signal messageEvent(var message) signal requestCompletedEvent(var payload) - // PUBLIC API - EIP-1193 REQUEST - function request(args) { - if (!args || !args.method) { - console.error("[ConnectorManager] Invalid request - missing method") - return JSON.stringify({ - error: { code: -32600, message: "Missing method" } // EIP-1193: Invalid Request - }) - } + // PUBLIC API - EIP-1193 REQUEST + function request(args) { + if (!args || !args.method) { + console.error("[ConnectorManager] Invalid request - missing method") + return JSON.stringify({ + error: { code: -32600, message: "Missing method" } // EIP-1193: Invalid Request + }) + } - const method = args.method - const requestId = args.requestId || 0 + const method = args.method + const requestId = args.requestId || 0 - var rpcRequest = { - "jsonrpc": "2.0", - "id": requestId, - "method": method, - "params": args.params || [], - "url": dappOrigin || "", - "name": dappName || "Unknown dApp", - "clientId": clientId, - "chainId": dappChainId, - "iconUrl": dappIconUrl || "" - } + var rpcRequest = { + "jsonrpc": "2.0", + "id": requestId, + "method": method, + "params": args.params || [], + "url": dappOrigin || "", + "name": dappName || "Unknown dApp", + "clientId": clientId, + "chainId": dappChainId, + "iconUrl": dappIconUrl || "" + } // Direct call to Nim connectorCallRPC -> status-go connector/api.go if (connectorController) { @@ -81,13 +81,13 @@ QtObject { _hasPermission = newAccounts.length > 0 providerStateChanged() - accuntsChangedEvent(accounts) + accountsChangedEvent(accounts) console.log("[ConnectorManager] Accounts updated:", JSON.stringify(accounts)) - return true - } + return true + } - function setConnected(isConnected) { - if (connected === isConnected) { + function setConnected(isConnected) { + if (connected === isConnected) { return false } @@ -118,29 +118,29 @@ QtObject { _initialConnectionDone = false providerStateChanged() - disconnectEvent({ code: 4900, message: "User disconnected" }) // EIP-1193: Disconnected - accountsChangedEvent([]) - console.log("[ConnectorManager] State cleared") - return true - } + disconnectEvent({ code: 4900, message: "User disconnected" }) // EIP-1193: Disconnected + accountsChangedEvent([]) + console.log("[ConnectorManager] State cleared") + return true + } - // PUBLIC API - function disconnect() { - clearState() + // PUBLIC API + function disconnect() { + clearState() - if (connectorController) { - connectorController.disconnect(dappOrigin) - } - } + if (connectorController) { + connectorController.disconnect(dappOrigin) + } + } - function changeAccount(newAccount) { - if (connectorController) { - connectorController.changeAccount(dappOrigin, clientId, newAccount) - } - } + function changeAccount(newAccount) { + if (connectorController) { + connectorController.changeAccount(dappOrigin, clientId, newAccount) + } + } - // HELPER FUNCTIONS - function shouldProcessSignal(event) { + // HELPER FUNCTIONS + function shouldProcessSignal(event) { // Filter by origin if (event.url && Utils.normalizeOrigin(event.url) !== Utils.normalizeOrigin(dappOrigin)) { console.log("[ConnectorManager] Ignoring signal for other origin:", event.url, "expected:", dappOrigin) @@ -157,7 +157,7 @@ QtObject { } // SIGNAL HANDLERS - Connections { + readonly property Connections _connections: Connections { target: connectorController function onConnected(payload) { @@ -218,23 +218,23 @@ QtObject { const chainIdHex = Utils.chainIdToHex(chainIdDecimal) providerStateChanged() - chainChangedEvent(chainIdHex) - console.log("[ConnectorManager] Chain switched to:", chainIdHex) - } - } catch (error) { - console.error("[ConnectorManager] Error processing chainIdSwitched signal:", error) - } - } + chainChangedEvent(chainIdHex) + console.log("[ConnectorManager] Chain switched to:", chainIdHex) + } + } catch (error) { + console.error("[ConnectorManager] Error processing chainIdSwitched signal:", error) + } + } - function onAccountChanged(payload) { - try { - const data = JSON.parse(payload) - if (!shouldProcessSignal(data)) return - const newAccounts = data.sharedAccount ? [data.sharedAccount] : [] - updateAccounts(newAccounts) - } catch (error) { - console.error("[ConnectorManager] Error processing accountChanged signal:", error) - } - } - } + function onAccountChanged(payload) { + try { + const data = JSON.parse(payload) + if (!shouldProcessSignal(data)) return + const newAccounts = data.sharedAccount ? [data.sharedAccount] : [] + updateAccounts(newAccounts) + } catch (error) { + console.error("[ConnectorManager] Error processing accountChanged signal:", error) + } + } + } } From 8fa3fbd832913e405c306967bd036de684f5cbfa Mon Sep 17 00:00:00 2001 From: Andrey Bocharnikov Date: Fri, 7 Nov 2025 17:15:21 +0400 Subject: [PATCH 5/7] feat(dapp-browser)_: fix build fixes #19151 --- ui/app/AppLayouts/Browser/BrowserLayout.qml | 4 ++-- ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ui/app/AppLayouts/Browser/BrowserLayout.qml b/ui/app/AppLayouts/Browser/BrowserLayout.qml index 4e86d4ce0e3..8ba38005688 100644 --- a/ui/app/AppLayouts/Browser/BrowserLayout.qml +++ b/ui/app/AppLayouts/Browser/BrowserLayout.qml @@ -355,7 +355,7 @@ StatusSectionLayout { y: (Math.abs(browserHeader.mapFromGlobal(headerPoint).y) + browserHeader.anchors.topMargin + Theme.halfPadding) onSendTriggered: (address) => root.sendToRecipientRequested(address) - onAccountChanged: (newAddress) => connectorBridge.manager.changeAccount(newAddress) + onAccountChanged: (newAddress) => connectorBridge.connectorManager.changeAccount(newAddress) onReload: { for (let i = 0; i < tabs.count; ++i){ tabs.getTab(i).reload(); @@ -448,7 +448,7 @@ StatusSectionLayout { bookmarksStore: root.bookmarksStore downloadsStore: root.downloadsStore currentWebView: _internal.currentWebView - webChannel: connectorBridge.webChannel + webChannel: connectorBridge.channel findBarComp: findBar favMenu: favoriteMenu addFavModal: addFavoriteModal diff --git a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml index a783628dff3..8a706084263 100644 --- a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml +++ b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml @@ -23,9 +23,6 @@ QtObject { required property var connectorController property string httpUserAgent: "" // Custom user agent for web profiles - readonly property alias webChannel: channel - readonly property alias manager: connectorManager - property alias dappUrl: connectorManager.dappUrl property alias dappOrigin: connectorManager.dappOrigin property alias dappName: connectorManager.dappName From 1a53dfa39022440ad350811c66d36aabbe418f47 Mon Sep 17 00:00:00 2001 From: Andrey Bocharnikov Date: Fri, 7 Nov 2025 18:45:29 +0400 Subject: [PATCH 6/7] feat(dapp-browser)_: fix build fixes #19151 --- ui/app/AppLayouts/Browser/BrowserLayout.qml | 8 +- .../Browser/provider/qml/ConnectorBridge.qml | 123 ++++++++---------- .../Browser/provider/qml/ConnectorManager.qml | 23 +++- 3 files changed, 77 insertions(+), 77 deletions(-) diff --git a/ui/app/AppLayouts/Browser/BrowserLayout.qml b/ui/app/AppLayouts/Browser/BrowserLayout.qml index 8ba38005688..323faeb6850 100644 --- a/ui/app/AppLayouts/Browser/BrowserLayout.qml +++ b/ui/app/AppLayouts/Browser/BrowserLayout.qml @@ -53,8 +53,6 @@ StatusSectionLayout { userUID: root.userUID connectorController: root.connectorController - defaultAccountAddress: root.browserWalletStore.dappBrowserAccount.address - accountsModel: root.browserWalletStore.accounts httpUserAgent: { if (localAccountSensitiveSettings.compatibilityMode) { // Google doesn't let you connect if the user agent is Chrome-ish and doesn't satisfy some sort of hidden requirement @@ -538,7 +536,11 @@ StatusSectionLayout { // Update ConnectorBridge with current dApp metadata if (_internal.currentWebView && _internal.currentWebView.url) { - connectorBridge.updateDAppUrl(_internal.currentWebView.url, _internal.currentWebView.title) + connectorBridge.connectorManager.updateDAppUrl( + _internal.currentWebView.url, + _internal.currentWebView.title, + _internal.currentWebView.icon + ) } } } diff --git a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml index 8a706084263..6636facb0c7 100644 --- a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml +++ b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml @@ -23,52 +23,36 @@ QtObject { required property var connectorController property string httpUserAgent: "" // Custom user agent for web profiles - property alias dappUrl: connectorManager.dappUrl - property alias dappOrigin: connectorManager.dappOrigin - property alias dappName: connectorManager.dappName - property alias dappIconUrl: connectorManager.dappIconUrl - property alias clientId: connectorManager.clientId + readonly property alias dappUrl: connectorManager.dappUrl + readonly property alias dappOrigin: connectorManager.dappOrigin + readonly property alias dappName: connectorManager.dappName + readonly property alias dappIconUrl: connectorManager.dappIconUrl + readonly property alias clientId: connectorManager.clientId - function hasWalletConnected(hostname, address) { - if (!connectorController) return false - - const dApps = connectorController.getDApps() - try { - const dAppsObj = JSON.parse(dApps) - if (Array.isArray(dAppsObj)) { - return dAppsObj.some(function(dapp) { - return dapp.url && dapp.url.indexOf(hostname) >= 0 - }) - } - } catch (e) { - console.warn("[ConnectorBridge] Error checking wallet connection:", e) - } - return false - } + readonly property ConnectorManager connectorManager: ConnectorManager { + id: connectorManager + connectorController: root.connectorController // (shared_modules/connector/controller.nim) - function disconnect(hostname) { - if (!connectorController) return false - return connectorController.disconnect(hostname) + // Forward events to Eip1193ProviderAdapter + onConnectEvent: (info) => eip1193ProviderAdapter.connectEvent(info) + onAccountsChangedEvent: (accounts) => eip1193ProviderAdapter.accountsChangedEvent(accounts) + onChainChangedEvent: (chainId) => eip1193ProviderAdapter.chainChangedEvent(chainId) + onRequestCompletedEvent: (payload) => eip1193ProviderAdapter.requestCompletedEvent(payload) + onDisconnectEvent: (error) => eip1193ProviderAdapter.disconnectEvent(error) + onMessageEvent: (message) => eip1193ProviderAdapter.messageEvent(message) + onProviderStateChanged: () => eip1193ProviderAdapter.providerStateChanged() } - function updateDAppUrl(url, name) { - if (!url) return + readonly property Eip1193ProviderAdapter eip1193ProviderAdapter: Eip1193ProviderAdapter { + WebChannel.id: "ethereumProvider" - const urlStr = url.toString() - connectorManager.dappUrl = urlStr - connectorManager.dappOrigin = Utils.normalizeOrigin(urlStr) - connectorManager.dappName = name || BrowserUtils.extractDomainName(urlStr) - connectorManager.dappChainId = 1 - } + chainId: BrowserUtils.chainIdToHex(connectorManager.dappChainId) + networkVersion: connectorManager.dappChainId.toString() + selectedAddress: connectorManager.accounts.length > 0 ? connectorManager.accounts[0] : "" + accounts: connectorManager.accounts + connected: connectorManager.connected - function createScript(scriptName, runOnSubframes = true) { - return { - name: scriptName, - sourceUrl: Qt.resolvedUrl("../js/" + scriptName), - injectionPoint: WebEngineScript.DocumentCreation, - worldId: WebEngineScript.MainWorld, - runOnSubframes: runOnSubframes - } + onRequestInternal: (args) => connectorManager.request(args) } readonly property var _scripts: [ @@ -93,40 +77,43 @@ QtObject { userScripts.collection: root._scripts } - readonly property ConnectorManager connectorManager: ConnectorManager { - connectorController: root.connectorController // (shared_modules/connector/controller.nim) - - dappUrl: "" - dappOrigin: "" - dappName: "" - dappIconUrl: "" - dappChainId: 1 - clientId: "status-desktop/dapp-browser" + readonly property WebChannel channel: WebChannel { + registeredObjects: [eip1193ProviderAdapter] + } - // Forward events to Eip1193ProviderAdapter - onConnectEvent: (info) => eip1193ProviderAdapter.connectEvent(info) - onAccountsChangedEvent: (accounts) => eip1193ProviderAdapter.accountsChangedEvent(accounts) - onChainChangedEvent: (chainId) => eip1193ProviderAdapter.chainChangedEvent(chainId) - onRequestCompletedEvent: (payload) => eip1193ProviderAdapter.requestCompletedEvent(payload) - onDisconnectEvent: (error) => eip1193ProviderAdapter.disconnectEvent(error) - onMessageEvent: (message) => eip1193ProviderAdapter.messageEvent(message) + function hasWalletConnected(hostname, address) { + if (!connectorController) return false - onProviderStateChanged: () => eip1193ProviderAdapter.providerStateChanged() + const dApps = connectorController.getDApps() + try { + const dAppsObj = JSON.parse(dApps) + if (Array.isArray(dAppsObj)) { + return dAppsObj.some(function(dapp) { + return dapp.url && dapp.url.indexOf(hostname) >= 0 + }) + } + } catch (e) { + console.warn("[ConnectorBridge] Error checking wallet connection:", e) + } + return false } - readonly property WebChannel channel: WebChannel { - registeredObjects: [eip1193ProviderAdapter] + function disconnect(hostname) { + if (!connectorController) return false + return connectorController.disconnect(hostname) } - readonly property Eip1193ProviderAdapter eip1193ProviderAdapter: Eip1193ProviderAdapter { - WebChannel.id: "ethereumProvider" - - chainId: BrowserUtils.chainIdToHex(connectorManager.dappChainId) - networkVersion: connectorManager.dappChainId.toString() - selectedAddress: connectorManager.accounts.length > 0 ? connectorManager.accounts[0] : "" - accounts: connectorManager.accounts - connected: connectorManager.connected + function updateDAppUrl(url, name) { + connectorManager.updateDAppUrl(url, name) + } - onRequestInternal: (args) => connectorManager.request(args) + function createScript(scriptName, runOnSubframes = true) { + return { + name: scriptName, + sourceUrl: Qt.resolvedUrl("../js/" + scriptName), + injectionPoint: WebEngineScript.DocumentCreation, + worldId: WebEngineScript.MainWorld, + runOnSubframes: runOnSubframes + } } } diff --git a/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml b/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml index 756bda4d764..4de34856771 100644 --- a/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml +++ b/ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml @@ -7,12 +7,12 @@ QtObject { property var connectorController: null // dApp metadata - required property string dappUrl - required property string dappOrigin - required property string dappName - required property string dappIconUrl - required property int dappChainId - required property string clientId + property string dappUrl: "" + property string dappOrigin: "" + property string dappName: "" + property string dappIconUrl: "" + property int dappChainId: 1 + property string clientId: "status-desktop/dapp-browser" // STATE property bool connected: false @@ -139,6 +139,17 @@ QtObject { } } + function updateDAppUrl(url, name, iconUrl) { + if (!url) return + + const urlStr = url.toString() + dappUrl = urlStr + dappOrigin = Utils.normalizeOrigin(urlStr) + dappName = name || Utils.extractDomainName(urlStr) + dappIconUrl = iconUrl ? iconUrl.toString() : "" + dappChainId = 1 + } + // HELPER FUNCTIONS function shouldProcessSignal(event) { // Filter by origin From 4903151266747b073e136b4fcafe298318f0c774 Mon Sep 17 00:00:00 2001 From: Andrey Bocharnikov Date: Fri, 7 Nov 2025 20:33:18 +0400 Subject: [PATCH 7/7] feat(dapp-browser)_: fix PR fixes #19151 --- storybook/pages/BrowserLayoutPage.qml | 7 ++--- .../Browser/nim/ConnectorController.qml | 28 +++++++++++++++++++ storybook/stubs/AppLayouts/Browser/nim/qmldir | 2 ++ .../Browser/provider/js/ethereum_wrapper.js | 5 +++- .../Browser/provider/qml/ConnectorBridge.qml | 2 ++ .../provider/qml/Eip1193ProviderAdapter.qml | 1 + .../Browser/views/BrowserWebEngineView.qml | 10 +++++++ .../AppLayouts/Browser/views/ScriptUtils.js | 21 ++++++++++++++ 8 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 storybook/stubs/AppLayouts/Browser/nim/ConnectorController.qml create mode 100644 storybook/stubs/AppLayouts/Browser/nim/qmldir create mode 100644 ui/app/AppLayouts/Browser/views/ScriptUtils.js diff --git a/storybook/pages/BrowserLayoutPage.qml b/storybook/pages/BrowserLayoutPage.qml index 950c9b9e9d3..7d344c10310 100644 --- a/storybook/pages/BrowserLayoutPage.qml +++ b/storybook/pages/BrowserLayoutPage.qml @@ -13,6 +13,7 @@ import utils import AppLayouts.Browser import AppLayouts.Browser.stores as BrowserStores +import AppLayouts.Browser.nim as BrowserNim import AppLayouts.Wallet.stores import shared.stores @@ -26,10 +27,8 @@ Item { anchors.fill: parent userUID: "0xdeadbeef" transactionStore: TransactionStore {} - assetsStore: WalletAssetsStore {} - currencyStore: CurrenciesStore {} - tokensStore: TokensStore {} thirdpartyServicesEnabled: true + connectorController: BrowserNim.ConnectorController {} bookmarksStore: BrowserStores.BookmarksStore { property var bookmarksModel: ListModel {} @@ -105,7 +104,7 @@ Item { } function switchAccountByAddress(address) { - dappBrowserAccount.address = adress + dappBrowserAccount.address = address } } diff --git a/storybook/stubs/AppLayouts/Browser/nim/ConnectorController.qml b/storybook/stubs/AppLayouts/Browser/nim/ConnectorController.qml new file mode 100644 index 00000000000..11e9004d410 --- /dev/null +++ b/storybook/stubs/AppLayouts/Browser/nim/ConnectorController.qml @@ -0,0 +1,28 @@ +import QtQuick + +// Mock of src/app/modules/shared_modules/connector/controller.nim + +QtObject { + signal connected(string payload) + signal disconnected(string payload) + signal connectorCallRPCResult(int requestId, string payload) + signal chainIdSwitched(string payload) + signal accountChanged(string payload) + + function getDApps() { + return "[]" + } + + function disconnect(hostname) { + return false + } + + function connectorCallRPC(requestId, payload) { + // Mock + } + + function changeAccount(origin, clientId, newAccount) { + // Mock + } +} + diff --git a/storybook/stubs/AppLayouts/Browser/nim/qmldir b/storybook/stubs/AppLayouts/Browser/nim/qmldir new file mode 100644 index 00000000000..be5bb7e7bb4 --- /dev/null +++ b/storybook/stubs/AppLayouts/Browser/nim/qmldir @@ -0,0 +1,2 @@ +ConnectorController 1.0 ConnectorController.qml + diff --git a/ui/app/AppLayouts/Browser/provider/js/ethereum_wrapper.js b/ui/app/AppLayouts/Browser/provider/js/ethereum_wrapper.js index 43b20b460c5..15c376a5a6b 100644 --- a/ui/app/AppLayouts/Browser/provider/js/ethereum_wrapper.js +++ b/ui/app/AppLayouts/Browser/provider/js/ethereum_wrapper.js @@ -254,7 +254,10 @@ const EthereumWrapper = (function() { if (attempts < maxAttempts) { setTimeout(retry, retryInterval * Math.min(attempts, 5)); } else { - console.error('[Ethereum Wrapper] Failed to install after', maxAttempts, 'attempts'); + const isIframe = window.self !== window.top; + if (!isIframe) { + console.error('[Ethereum Wrapper] Failed to install after', maxAttempts, 'attempts'); + } } }; diff --git a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml index 6636facb0c7..9599169878d 100644 --- a/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml +++ b/ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml @@ -44,6 +44,8 @@ QtObject { } readonly property Eip1193ProviderAdapter eip1193ProviderAdapter: Eip1193ProviderAdapter { + id: eip1193Provider + objectName: "ethereumProvider" WebChannel.id: "ethereumProvider" chainId: BrowserUtils.chainIdToHex(connectorManager.dappChainId) diff --git a/ui/app/AppLayouts/Browser/provider/qml/Eip1193ProviderAdapter.qml b/ui/app/AppLayouts/Browser/provider/qml/Eip1193ProviderAdapter.qml index 4ddaaec3d4c..c5a0f68377b 100644 --- a/ui/app/AppLayouts/Browser/provider/qml/Eip1193ProviderAdapter.qml +++ b/ui/app/AppLayouts/Browser/provider/qml/Eip1193ProviderAdapter.qml @@ -1,4 +1,5 @@ import QtQuick +import QtWebChannel QtObject { id: root diff --git a/ui/app/AppLayouts/Browser/views/BrowserWebEngineView.qml b/ui/app/AppLayouts/Browser/views/BrowserWebEngineView.qml index ffbe17ebaf8..b0baf6dfdd1 100644 --- a/ui/app/AppLayouts/Browser/views/BrowserWebEngineView.qml +++ b/ui/app/AppLayouts/Browser/views/BrowserWebEngineView.qml @@ -8,6 +8,7 @@ import shared.controls import utils import "../panels" +import "ScriptUtils.js" as ScriptUtils import AppLayouts.Browser.stores as BrowserStores @@ -24,6 +25,7 @@ WebEngineView { property var downloadsMenu property var determineRealURLFn: function(url){} property bool isDownloadView + property bool enableJsLogs: false signal setCurrentWebUrl(url url) @@ -103,6 +105,14 @@ WebEngineView { } } + onJavaScriptConsoleMessage: function(level, message, lineNumber, sourceID) { + // Check if the message is from our injected scripts + const isOurScript = ScriptUtils.isOurInjectedScript(sourceID, root.profile); + if (isOurScript || root.enableJsLogs) { + console.log("[WebEngine]", sourceID + ":" + lineNumber, message); + } + } + Loader { active: root.isDownloadView width: parent.width diff --git a/ui/app/AppLayouts/Browser/views/ScriptUtils.js b/ui/app/AppLayouts/Browser/views/ScriptUtils.js new file mode 100644 index 00000000000..37561f21fed --- /dev/null +++ b/ui/app/AppLayouts/Browser/views/ScriptUtils.js @@ -0,0 +1,21 @@ +.pragma library + +// Check if a console message source is from one of our injected scripts +function isOurInjectedScript(sourceID, profile) { + if (!sourceID || !profile) { + return false; + } + + if (!profile.userScripts || !profile.userScripts.collection) { + return false; + } + + const scripts = profile.userScripts.collection; + for (let i = 0; i < scripts.length; i++) { + if (scripts[i] && scripts[i].name && sourceID.includes(scripts[i].name)) { + return true; + } + } + + return false; +}