Skip to content

Commit 360bc18

Browse files
committed
feat(dapp-browser-eip1193)_: bridge between webengine and connector
feat(dapp-browser-eip1193)_: js object wrappers (eip1193, eip-6963) fixes #19131
1 parent 4aee222 commit 360bc18

File tree

7 files changed

+511
-37
lines changed

7 files changed

+511
-37
lines changed

ui/app/AppLayouts/DAppBrowser/core/js/ethereum_injector.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// WebChannel integration with retry mechanism
21
function initializeWebChannel() {
32
if (typeof qt !== 'undefined' && qt.webChannelTransport) {
43
console.log("[Ethereum Injector] WebChannel transport available, initializing...");
@@ -8,20 +7,13 @@ function initializeWebChannel() {
87
} catch (error) {
98
console.error("[Ethereum Injector] Error initializing WebChannel:", error);
109
}
11-
} else {
12-
// console.log("[Ethereum Injector] WebChannel transport not available, retrying...");
13-
// Retry after a short delay
14-
// setTimeout(initializeWebChannel, 100);
1510
}
1611
}
1712

18-
// Start initialization
1913
initializeWebChannel();
2014

21-
// Setup Ethereum provider
2215
function setupEthereumProvider(channel) {
23-
// Get the EIP-1193 provider QtObject object (WebChannel.id = "ethereumProvider")
24-
window.ethereumProvider = channel.objects.ethereumProvider;
16+
window.ethereumProvider = channel.objects.ethereumProvider; // Eip1193ProviderAdapter.qml
2517

2618
if (!window.ethereumProvider) {
2719
console.error("[Ethereum Injector] ethereumProvider not found in channel.objects");

ui/app/AppLayouts/DAppBrowser/core/js/ethereum_wrapper.js

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
"use strict";
22

33
// IIFE start (https://developer.mozilla.org/ru/docs/Glossary/IIFE)
4-
// Guard against multiple script loads
54
const EthereumWrapper = (function() {
6-
// If already loaded, return existing instance
75
if (window.__ETHEREUM_WRAPPER_INSTANCE__) {
86
return window.__ETHEREUM_WRAPPER_INSTANCE__;
97
}
@@ -29,7 +27,9 @@ const EthereumWrapper = (function() {
2927
// Set up EIP-1193 properties
3028
this.isStatus = true;
3129
this.isMetaMask = false;
32-
this.chainId = null; // Will be set on first eth_chainId request or chainChanged event
30+
this.chainId = null; // Will be set on first eth_chainId request or providerStateChanged event
31+
this.networkVersion = null; // decimal string format (deprecated)
32+
this.selectedAddress = null; // current active address
3333
this._connected = false;
3434
}
3535

@@ -45,15 +45,10 @@ const EthereumWrapper = (function() {
4545

4646
_wireSignals() {
4747
this._connectSignal('connectEvent', (info) => {
48-
this._connected = true;
49-
if (info && info.chainId) {
50-
this.chainId = info.chainId;
51-
}
5248
this._emit('connect', info);
5349
});
5450

5551
this._connectSignal('disconnectEvent', (error) => {
56-
this._connected = false;
5752
this._emit('disconnect', error);
5853
});
5954

@@ -62,23 +57,23 @@ const EthereumWrapper = (function() {
6257
});
6358

6459
this._connectSignal('chainChangedEvent', (chainId) => {
65-
this.chainId = chainId;
6660
this._emit('chainChanged', chainId);
6761
});
6862

6963
this._connectSignal('accountsChangedEvent', (accounts) => {
7064
this._emit('accountsChanged', accounts);
7165
});
7266

73-
const hasAsyncEvents = this._connectSignal('requestCompletedEvent',
74-
this.handleRequestCompleted.bind(this)
75-
);
76-
77-
if (!hasAsyncEvents) {
78-
console.warn('[Ethereum Wrapper] requestCompletedEvent not available on native provider');
79-
}
80-
81-
this._hasAsyncEvents = hasAsyncEvents;
67+
// Provider state changed - update all properties at once
68+
this._connectSignal('providerStateChanged', () => {
69+
this.chainId = this.nativeEthereum.chainId || this.chainId;
70+
this.networkVersion = this.nativeEthereum.networkVersion || this.networkVersion;
71+
this.selectedAddress = this.nativeEthereum.selectedAddress || null;
72+
this._connected = this.nativeEthereum.connected !== undefined ? this.nativeEthereum.connected : this._connected;
73+
});
74+
75+
// Handle async RPC responses
76+
this._connectSignal('requestCompletedEvent', this.handleRequestCompleted.bind(this));
8277
}
8378

8479
_emit(event, ...args) {
@@ -93,6 +88,10 @@ const EthereumWrapper = (function() {
9388
}
9489
}
9590

91+
isConnected() {
92+
return this._connected;
93+
}
94+
9695
request(args) {
9796
if (!args || typeof args !== 'object' || !args.method) {
9897
return Promise.reject(new Error('Invalid request: missing method'));
@@ -108,23 +107,15 @@ const EthereumWrapper = (function() {
108107
if (nativeResp && typeof nativeResp === 'object' && nativeResp.error) {
109108
this.pendingRequests.delete(requestId);
110109
reject(nativeResp.error);
111-
} else if (nativeResp && nativeResp.result !== undefined && !this._hasAsyncEvents) {
112-
this.pendingRequests.delete(requestId);
113-
resolve(nativeResp.result);
114110
}
111+
// Response will come via requestCompletedEvent
115112
} catch (e) {
116113
this.pendingRequests.delete(requestId);
117114
reject(e);
118115
}
119116
});
120117
}
121118

122-
_updateStateFromResponse(method, result) {
123-
if (method === 'eth_chainId' && result && this.chainId !== result) {
124-
this.chainId = result;
125-
}
126-
}
127-
128119
_processResponse(resp, method, entry) {
129120
if (resp && typeof resp === 'string') {
130121
try {
@@ -139,7 +130,6 @@ const EthereumWrapper = (function() {
139130
if (resp && resp.error) {
140131
entry.reject(resp.error);
141132
} else if (resp && resp.result !== undefined) {
142-
this._updateStateFromResponse(method, resp.result);
143133
entry.resolve(resp.result);
144134
} else {
145135
entry.resolve(resp);
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import QtQuick
2+
import QtQuick.Controls
3+
import QtWebEngine
4+
import QtWebChannel
5+
6+
import StatusQ.Core.Theme
7+
import utils
8+
9+
/**
10+
* ConnectorBridge
11+
*
12+
* Simplified connector infrastructure for BrowserLayout.
13+
* Provides WebEngine profiles with script injection, WebChannel,
14+
* ConnectorManager, and direct connection to Nim backend.
15+
*
16+
* This component bridges the Browser UI with the Connector backend system.
17+
*/
18+
Item {
19+
id: root
20+
21+
// Properties
22+
property string userUID: ""
23+
property var connectorController: null
24+
property string defaultAccountAddress: "" // Default wallet account address
25+
property var accountsModel: null // Model of all available accounts
26+
property string httpUserAgent: "" // Custom user agent for web profiles
27+
28+
// Expose profiles and channel
29+
readonly property alias webChannel: channel
30+
readonly property alias defaultProfile: defaultProfile
31+
readonly property alias otrProfile: otrProfile
32+
33+
// Expose manager for external URL updates
34+
readonly property alias manager: connectorManager
35+
36+
// Expose dApp metadata properties for direct binding
37+
property alias dappUrl: connectorManager.dappUrl
38+
property alias dappOrigin: connectorManager.dappOrigin
39+
property alias dappName: connectorManager.dappName
40+
property alias dappIconUrl: connectorManager.dappIconUrl
41+
property alias clientId: connectorManager.clientId
42+
43+
// Helper functions (replaces web3ProviderStore functions)
44+
function hasWalletConnected(hostname, address) {
45+
if (!connectorController) return false
46+
// Check if dApp has permission via connector
47+
const dApps = connectorController.getDApps()
48+
try {
49+
const dAppsObj = JSON.parse(dApps)
50+
if (Array.isArray(dAppsObj)) {
51+
return dAppsObj.some(function(dapp) {
52+
return dapp.url && dapp.url.indexOf(hostname) >= 0
53+
})
54+
}
55+
} catch (e) {
56+
console.warn("[ConnectorBridge] Error checking wallet connection:", e)
57+
}
58+
return false
59+
}
60+
61+
function disconnect(hostname) {
62+
if (!connectorController) return false
63+
return connectorController.disconnect(hostname)
64+
}
65+
66+
// Function to update dApp metadata from external source
67+
function updateDAppUrl(url, name) {
68+
if (!url) return
69+
70+
const urlStr = url.toString()
71+
connectorManager.dappUrl = urlStr
72+
connectorManager.dappOrigin = urlStr
73+
connectorManager.dappName = name || extractDomainName(urlStr)
74+
connectorManager.dappChainId = 1 // Default to Mainnet
75+
76+
console.log("[ConnectorBridge] Updated dApp metadata:")
77+
console.log("[ConnectorBridge] - URL:", urlStr)
78+
console.log("[ConnectorBridge] - Name:", connectorManager.dappName)
79+
console.log("[ConnectorBridge] - ChainId:", connectorManager.dappChainId)
80+
}
81+
82+
// Helper to extract domain name from URL
83+
function extractDomainName(urlString) {
84+
try {
85+
const urlObj = new URL(urlString)
86+
return urlObj.hostname || "Unknown dApp"
87+
} catch (e) {
88+
return "Unknown dApp"
89+
}
90+
}
91+
92+
// Helper function to create script config
93+
function createScript(scriptName, runOnSubframes = true) {
94+
return {
95+
name: scriptName,
96+
sourceUrl: Qt.resolvedUrl("../js/" + scriptName),
97+
injectionPoint: WebEngineScript.DocumentCreation,
98+
worldId: WebEngineScript.MainWorld,
99+
runOnSubframes: runOnSubframes
100+
}
101+
}
102+
103+
// Script injection collection
104+
readonly property var _scripts: [
105+
createScript("qwebchannel.js", true),
106+
createScript("ethereum_wrapper.js", true),
107+
createScript("eip6963_announcer.js", false), // Only top-level window (EIP-6963 spec)
108+
createScript("ethereum_injector.js", true)
109+
]
110+
111+
// Web Engine Profiles with connector script injection
112+
WebEngineProfile {
113+
id: defaultProfile
114+
storageName: "Profile_%1".arg(root.userUID)
115+
offTheRecord: false
116+
httpUserAgent: root.httpUserAgent
117+
userScripts.collection: root._scripts
118+
}
119+
120+
WebEngineProfile {
121+
id: otrProfile
122+
storageName: "IncognitoProfile_%1".arg(root.userUID)
123+
offTheRecord: true
124+
persistentCookiesPolicy: WebEngineProfile.NoPersistentCookies
125+
httpUserAgent: root.httpUserAgent
126+
userScripts.collection: root._scripts
127+
}
128+
129+
// ConnectorManager - Business Logic with direct Nim connection
130+
ConnectorManager {
131+
id: connectorManager
132+
connectorController: root.connectorController // (shared_modules/connector/controller.nim)
133+
134+
dappUrl: ""
135+
dappOrigin: ""
136+
dappName: ""
137+
dappIconUrl: ""
138+
dappChainId: 1
139+
clientId: "status-desktop/dapp-browser"
140+
141+
// Forward events to Eip1193ProviderAdapter
142+
onConnectEvent: (info) => eip1193ProviderAdapter.connectEvent(info)
143+
onAccountsChangedEvent: (accounts) => eip1193ProviderAdapter.accountsChangedEvent(accounts)
144+
onChainChangedEvent: (chainId) => eip1193ProviderAdapter.chainChangedEvent(chainId)
145+
onRequestCompletedEvent: (payload) => eip1193ProviderAdapter.requestCompletedEvent(payload)
146+
onDisconnectEvent: (error) => eip1193ProviderAdapter.disconnectEvent(error)
147+
onMessageEvent: (message) => eip1193ProviderAdapter.messageEvent(message)
148+
onProviderStateChanged: () => eip1193ProviderAdapter.providerStateChanged()
149+
}
150+
151+
WebChannel {
152+
id: channel
153+
registeredObjects: [eip1193ProviderAdapter]
154+
}
155+
156+
Eip1193ProviderAdapter {
157+
id: eip1193ProviderAdapter
158+
WebChannel.id: "ethereumProvider"
159+
160+
chainId: "0x" + connectorManager.dappChainId.toString(16) // Convert decimal to hex
161+
networkVersion: String(connectorManager.dappChainId)
162+
selectedAddress: connectorManager.accounts.length > 0 ? connectorManager.accounts[0] : ""
163+
accounts: connectorManager.accounts
164+
connected: connectorManager.connected
165+
166+
function request(args) {
167+
return connectorManager.request(args)
168+
}
169+
}
170+
}
171+

0 commit comments

Comments
 (0)