Skip to content

Commit 89c7a05

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 89c7a05

File tree

8 files changed

+492
-37
lines changed

8 files changed

+492
-37
lines changed

ui/app/AppLayouts/DAppBrowser/core/js/ethereum_injector.js renamed to ui/app/AppLayouts/Browser/provider/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 renamed to ui/app/AppLayouts/Browser/provider/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: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import QtQuick
2+
import QtQuick.Controls
3+
import QtWebEngine
4+
import QtWebChannel
5+
6+
import StatusQ.Core.Theme
7+
import utils
8+
9+
import "Utils.js" as Utils
10+
11+
/**
12+
* ConnectorBridge
13+
*
14+
* Simplified connector infrastructure for BrowserLayout.
15+
* Provides WebEngine profiles with script injection, WebChannel,
16+
* ConnectorManager, and direct connection to Nim backend.
17+
*
18+
* This component bridges the Browser UI with the Connector backend system.
19+
*/
20+
Item {
21+
id: root
22+
23+
property string userUID: ""
24+
property var connectorController: null
25+
property string defaultAccountAddress: ""
26+
property var accountsModel: null
27+
property string httpUserAgent: "" // Custom user agent for web profiles
28+
29+
readonly property alias webChannel: channel
30+
readonly property alias defaultProfile: defaultProfile
31+
readonly property alias otrProfile: otrProfile
32+
33+
readonly property alias manager: connectorManager
34+
35+
property alias dappUrl: connectorManager.dappUrl
36+
property alias dappOrigin: connectorManager.dappOrigin
37+
property alias dappName: connectorManager.dappName
38+
property alias dappIconUrl: connectorManager.dappIconUrl
39+
property alias clientId: connectorManager.clientId
40+
41+
function hasWalletConnected(hostname, address) {
42+
if (!connectorController) return false
43+
44+
const dApps = connectorController.getDApps()
45+
try {
46+
const dAppsObj = JSON.parse(dApps)
47+
if (Array.isArray(dAppsObj)) {
48+
return dAppsObj.some(function(dapp) {
49+
return dapp.url && dapp.url.indexOf(hostname) >= 0
50+
})
51+
}
52+
} catch (e) {
53+
console.warn("[ConnectorBridge] Error checking wallet connection:", e)
54+
}
55+
return false
56+
}
57+
58+
function disconnect(hostname) {
59+
if (!connectorController) return false
60+
return connectorController.disconnect(hostname)
61+
}
62+
63+
function updateDAppUrl(url, name) {
64+
if (!url) return
65+
66+
const urlStr = url.toString()
67+
connectorManager.dappUrl = urlStr
68+
connectorManager.dappOrigin = urlStr
69+
connectorManager.dappName = name || Utils.extractDomainName(urlStr)
70+
connectorManager.dappChainId = 1
71+
}
72+
73+
function createScript(scriptName, runOnSubframes = true) {
74+
return {
75+
name: scriptName,
76+
sourceUrl: Qt.resolvedUrl("../js/" + scriptName),
77+
injectionPoint: WebEngineScript.DocumentCreation,
78+
worldId: WebEngineScript.MainWorld,
79+
runOnSubframes: runOnSubframes
80+
}
81+
}
82+
83+
readonly property var _scripts: [
84+
createScript("qwebchannel.js", true),
85+
createScript("ethereum_wrapper.js", true),
86+
createScript("eip6963_announcer.js", false), // Only top-level window (EIP-6963 spec)
87+
createScript("ethereum_injector.js", true)
88+
]
89+
90+
WebEngineProfile {
91+
id: defaultProfile
92+
storageName: "Profile_%1".arg(root.userUID)
93+
offTheRecord: false
94+
httpUserAgent: root.httpUserAgent
95+
userScripts.collection: root._scripts
96+
}
97+
98+
WebEngineProfile {
99+
id: otrProfile
100+
storageName: "IncognitoProfile_%1".arg(root.userUID)
101+
offTheRecord: true
102+
persistentCookiesPolicy: WebEngineProfile.NoPersistentCookies
103+
httpUserAgent: root.httpUserAgent
104+
userScripts.collection: root._scripts
105+
}
106+
107+
ConnectorManager {
108+
id: connectorManager
109+
connectorController: root.connectorController // (shared_modules/connector/controller.nim)
110+
111+
dappUrl: ""
112+
dappOrigin: ""
113+
dappName: ""
114+
dappIconUrl: ""
115+
dappChainId: 1
116+
clientId: "status-desktop/dapp-browser"
117+
118+
// Forward events to Eip1193ProviderAdapter
119+
onConnectEvent: (info) => eip1193ProviderAdapter.connectEvent(info)
120+
onAccountsChangedEvent: (accounts) => eip1193ProviderAdapter.accountsChangedEvent(accounts)
121+
onChainChangedEvent: (chainId) => eip1193ProviderAdapter.chainChangedEvent(chainId)
122+
onRequestCompletedEvent: (payload) => eip1193ProviderAdapter.requestCompletedEvent(payload)
123+
onDisconnectEvent: (error) => eip1193ProviderAdapter.disconnectEvent(error)
124+
onMessageEvent: (message) => eip1193ProviderAdapter.messageEvent(message)
125+
126+
onProviderStateChanged: () => eip1193ProviderAdapter.providerStateChanged()
127+
}
128+
129+
WebChannel {
130+
id: channel
131+
registeredObjects: [eip1193ProviderAdapter]
132+
}
133+
134+
Eip1193ProviderAdapter {
135+
id: eip1193ProviderAdapter
136+
WebChannel.id: "ethereumProvider"
137+
138+
chainId: "0x" + connectorManager.dappChainId.toString(16) // Convert decimal to hex
139+
networkVersion: connectorManager.dappChainId.toString()
140+
selectedAddress: connectorManager.accounts.length > 0 ? connectorManager.accounts[0] : ""
141+
accounts: connectorManager.accounts
142+
connected: connectorManager.connected
143+
144+
function request(args) {
145+
return connectorManager.request(args)
146+
}
147+
}
148+
}
149+

0 commit comments

Comments
 (0)