@@ -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
0 commit comments