Skip to content

Commit cd5a3c5

Browse files
committed
fix(connector): fix pr issues
* improve error handling
1 parent c24a7f6 commit cd5a3c5

File tree

4 files changed

+125
-19
lines changed

4 files changed

+125
-19
lines changed

src/app_service/service/connector/service.nim

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ QtObject:
6060

6161
var data = ConnectorSendRequestAccountsSignal(e)
6262

63-
if not data.requestId.len() == 0:
63+
if data.requestId.len() == 0:
6464
error "ConnectorSendRequestAccountsSignal failed, requestId is empty"
6565
return
6666

@@ -72,7 +72,7 @@ QtObject:
7272

7373
var data = ConnectorSendTransactionSignal(e)
7474

75-
if not data.requestId.len() == 0:
75+
if data.requestId.len() == 0:
7676
error "ConnectorSendTransactionSignal failed, requestId is empty"
7777
return
7878

@@ -100,7 +100,7 @@ QtObject:
100100

101101
var data = ConnectorSignSignal(e)
102102

103-
if not data.requestId.len() == 0:
103+
if data.requestId.len() == 0:
104104
error "ConnectorSignSignal failed, requestId is empty"
105105
return
106106

ui/app/AppLayouts/Browser/provider/js/ethereum_wrapper.js

Lines changed: 114 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ const EthereumWrapper = (function() {
1919
this.listeners = new Map(); // event -> Set<handler>
2020
this.nativeEthereum = nativeEthereum;
2121
this.requestIdCounter = 1; // async requests
22-
this.pendingRequests = new Map(); // requestId -> { resolve, reject }
22+
this.pendingRequests = new Map(); // requestId -> { resolve, reject, timestamp }
23+
this.requestTimeout = 600000; // 10min timeout for pending requests. (nim side has it's own timeouts)
24+
this.timeoutCheckInterval = 10000;
2325

2426
// Wire native signals to events
2527
this._wireSignals();
2628

29+
// Setup page unload handler
30+
this._setupPageUnloadHandler();
31+
this._startTimeoutChecker();
32+
2733
// Set up EIP-1193 properties from QML
2834
this.isStatus = nativeEthereum.isStatus !== undefined ? nativeEthereum.isStatus : true;
2935
this.isMetaMask = nativeEthereum.isMetaMask !== undefined ? nativeEthereum.isMetaMask : false;
@@ -42,13 +48,89 @@ const EthereumWrapper = (function() {
4248
return false;
4349
}
4450

51+
_rejectRequests(requestsToReject, deleteFromPending = false) {
52+
for (const [requestId, entry, error] of requestsToReject) {
53+
try {
54+
entry.reject(error);
55+
} catch (e) {
56+
console.error('[Ethereum Wrapper] Error rejecting request:', e);
57+
}
58+
if (deleteFromPending) {
59+
this.pendingRequests.delete(requestId);
60+
}
61+
}
62+
}
63+
64+
_rejectAllPendingRequests(error) {
65+
const requestsToReject = Array.from(this.pendingRequests.entries())
66+
.map(([requestId, entry]) => [requestId, entry, error]);
67+
this.pendingRequests.clear();
68+
this._rejectRequests(requestsToReject, false);
69+
}
70+
71+
_checkTimedOutRequests() {
72+
const now = Date.now();
73+
const timedOutRequests = [];
74+
75+
for (const [requestId, entry] of this.pendingRequests.entries()) {
76+
if (now - entry.timestamp > this.requestTimeout) {
77+
timedOutRequests.push([
78+
requestId,
79+
entry,
80+
{
81+
code: -32603,
82+
message: `Request timed out after ${this.requestTimeout}ms`
83+
}
84+
]);
85+
}
86+
}
87+
88+
if (timedOutRequests.length > 0) {
89+
console.warn('[Ethereum Wrapper] Found', timedOutRequests.length, 'timed out requests');
90+
this._rejectRequests(timedOutRequests, true);
91+
}
92+
}
93+
94+
_startTimeoutChecker() {
95+
if (this.timeoutCheckIntervalId) {
96+
return;
97+
}
98+
99+
this.timeoutCheckIntervalId = setInterval(() => {
100+
if (this.pendingRequests.size > 0) {
101+
this._checkTimedOutRequests();
102+
}
103+
}, this.timeoutCheckInterval);
104+
}
105+
106+
_stopTimeoutChecker() {
107+
if (this.timeoutCheckIntervalId) {
108+
clearInterval(this.timeoutCheckIntervalId);
109+
this.timeoutCheckIntervalId = null;
110+
}
111+
}
112+
113+
_setupPageUnloadHandler() {
114+
window.addEventListener('beforeunload', () => {
115+
this._stopTimeoutChecker();
116+
this._rejectAllPendingRequests({
117+
code: -32603,
118+
message: 'Page is being unloaded'
119+
});
120+
});
121+
}
45122

46123
_wireSignals() {
47124
this._connectSignal('connectEvent', (info) => {
48125
this._emit('connect', info);
49126
});
50127

51128
this._connectSignal('disconnectEvent', (error) => {
129+
this._rejectAllPendingRequests({
130+
code: 4900,
131+
message: 'Provider disconnected'
132+
});
133+
52134
this._emit('disconnect', error);
53135
});
54136

@@ -96,28 +178,48 @@ const EthereumWrapper = (function() {
96178

97179
request(args) {
98180
if (!args || typeof args !== 'object' || !args.method) {
99-
return Promise.reject(new Error('Invalid request: missing method'));
181+
return Promise.reject({
182+
code: -32602,
183+
message: 'Invalid params: missing method'
184+
});
100185
}
101186
const requestId = this.requestIdCounter++;
102187
const payload = Object.assign({}, args, { requestId });
103188

104189
return new Promise((resolve, reject) => {
105-
this.pendingRequests.set(requestId, { resolve, reject, method: args.method });
190+
this.pendingRequests.set(requestId, {
191+
resolve,
192+
reject,
193+
method: args.method,
194+
timestamp: Date.now()
195+
});
106196

107197
try {
108198
const nativeResp = this.nativeEthereum.request(payload);
109-
if (nativeResp && typeof nativeResp === 'object' && nativeResp.error) {
110-
this.pendingRequests.delete(requestId);
111-
reject(nativeResp.error);
199+
200+
if (nativeResp && typeof nativeResp === 'string') {
201+
try {
202+
const parsedResp = JSON.parse(nativeResp);
203+
if (parsedResp?.error) {
204+
this.pendingRequests.delete(requestId);
205+
reject(parsedResp.error);
206+
return;
207+
}
208+
} catch {}
112209
}
113-
// Response will come via requestCompletedEvent
114210
} catch (e) {
211+
// Only catch synchronous exceptions (e.g., invalid payload)
115212
this.pendingRequests.delete(requestId);
116213
reject(e);
117214
}
118215
});
119216
}
120217

218+
// Method for backward compatibility with older dApps
219+
enable() {
220+
return this.request({ method: 'eth_requestAccounts' });
221+
}
222+
121223
_processResponse(resp, method, entry) {
122224
if (resp && typeof resp === 'string') {
123225
try {
@@ -139,19 +241,22 @@ const EthereumWrapper = (function() {
139241
}
140242

141243
handleRequestCompleted(payload) {
244+
const requestId = payload && (payload.requestId || (payload.response && payload.response.id)) || 0;
142245
try {
143-
const requestId = payload && (payload.requestId || (payload.response && payload.response.id)) || 0;
144246
const entry = this.pendingRequests.get(requestId);
145247

146248
if (!entry) {
147249
console.warn("[Ethereum Wrapper] No pending request found for ID:", requestId);
148250
return;
149251
}
150252

151-
this.pendingRequests.delete(requestId);
152253
this._processResponse(payload && payload.response, entry.method, entry);
153254
} catch (e) {
154255
console.error('[Ethereum Wrapper] requestCompletedEvent handler error', e);
256+
} finally {
257+
if (requestId) {
258+
this.pendingRequests.delete(requestId);
259+
}
155260
}
156261
}
157262

ui/app/AppLayouts/Browser/provider/qml/ConnectorBridge.qml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
import QtQuick
2-
import QtQuick.Controls
32
import QtWebEngine
43
import QtWebChannel
54

6-
import StatusQ.Core.Theme
7-
import utils
8-
95
import "Utils.js" as BrowserUtils
106

117
/**

ui/app/AppLayouts/Browser/provider/qml/ConnectorManager.qml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,17 @@ QtObject {
5757
}
5858

5959
// Direct call to Nim connectorCallRPC -> status-go connector/api.go
60-
if (connectorController) {
61-
connectorController.connectorCallRPC(requestId, JSON.stringify(rpcRequest))
62-
} else {
60+
if (!connectorController) {
6361
console.error("[ConnectorManager] connectorController not available")
62+
return JSON.stringify({
63+
jsonrpc: "2.0",
64+
id: requestId,
65+
error: { code: -32603, message: "Internal error: connector not available" }
66+
})
6467
}
6568

69+
connectorController.connectorCallRPC(requestId, JSON.stringify(rpcRequest))
70+
6671
// Return immediately - response comes via connectorCallRPCResult signal
6772
return JSON.stringify({
6873
jsonrpc: "2.0",

0 commit comments

Comments
 (0)