1
1
import { RequestArguments } from 'common/typedefs/ipcBridge'
2
2
import { JsonRpcCallback } from 'common/types'
3
3
import { contextBridge , ipcRenderer , webFrame } from 'electron'
4
+ import type { JsonRpcResponse } from '@metamask/utils'
4
5
5
6
/**
6
7
* @dev Extension must be removed prior to loading a page with this preload script.
@@ -24,7 +25,11 @@ const enabledTopics = [
24
25
]
25
26
26
27
/* eslint-disable @typescript-eslint/no-explicit-any */
27
- const listenToRendererCalls = ( fxn : string , topic : string , cb : any ) => {
28
+ const listenToRendererCalls = (
29
+ fxn : 'once' | 'on' | 'addListener' ,
30
+ topic : string ,
31
+ cb : any
32
+ ) => {
28
33
if ( ! enabledTopics . includes ( topic ) ) {
29
34
throw `Tried to listen to ${ topic } through window.ethereum!`
30
35
}
@@ -66,29 +71,40 @@ const providerApi = {
66
71
request : async ( args : RequestArguments ) => {
67
72
return provRequest ( args )
68
73
} ,
69
- send : async ( ...args : unknown [ ] ) => {
70
- return sendRequest ( ...args )
74
+ send : async ( ...args : unknown [ ] ) : Promise < JsonRpcResponse < any > > => {
75
+ return sendRequest ( ...args ) as Promise < JsonRpcResponse < any > >
71
76
} ,
72
- sendAsync : async ( payload : any , callback : JsonRpcCallback ) => {
73
- return sendAsyncRequest ( payload , callback )
77
+ sendAsync : async (
78
+ payload : any ,
79
+ callback : JsonRpcCallback
80
+ ) : Promise < JsonRpcResponse < any > > => {
81
+ return sendAsyncRequest ( payload , callback ) as Promise <
82
+ JsonRpcResponse < any >
83
+ >
74
84
} ,
75
85
once : ( topic : string , cb : any ) => {
76
86
listenToRendererCalls ( 'once' , topic , cb )
87
+ return window . ethereum
77
88
} ,
78
89
on : ( topic : string , cb : any ) => {
79
90
listenToRendererCalls ( 'on' , topic , cb )
91
+ return window . ethereum
80
92
} ,
81
93
off : ( topic : string , cb : any ) => {
82
94
ipcRenderer . off ( 'providerApi' + topic , cb )
95
+ return window . ethereum
83
96
} ,
84
97
addListener : ( topic : string , cb : any ) => {
85
98
listenToRendererCalls ( 'addListener' , topic , cb )
99
+ return window . ethereum
86
100
} ,
87
101
removeListener : ( topic : string , cb : any ) => {
88
102
ipcRenderer . removeListener ( 'providerApi' + topic , cb )
103
+ return window . ethereum
89
104
} ,
90
- removeAllListeners : ( topic : string ) => {
105
+ removeAllListeners : ( topic ? : string ) => {
91
106
ipcRenderer . removeAllListeners ( 'providerApi' + topic )
107
+ return window . ethereum
92
108
} ,
93
109
enable : async ( ) => {
94
110
const args : RequestArguments = {
@@ -101,37 +117,60 @@ const providerApi = {
101
117
102
118
contextBridge ?. exposeInMainWorld ( 'providerApi' , providerApi )
103
119
120
+ declare global {
121
+ interface Window {
122
+ providerApi : typeof providerApi
123
+ }
124
+ }
125
+
104
126
function initProvider ( ) {
105
127
async function exposeWindowEthereum ( ) {
106
- console . log ( 'exposing window ethereum' )
107
- if ( ! Object . hasOwn ( window , 'ethereum' ) ) {
108
- const windowAny = window as any
109
- windowAny . ethereum = {
110
- request : windowAny . providerApi . provider . request ,
111
- send : windowAny . providerApi . provider . send ,
112
- sendAsync : windowAny . providerApi . provider . sendAsync ,
113
- once : windowAny . providerApi . provider . once ,
114
- on : windowAny . providerApi . provider . on ,
115
- off : windowAny . providerApi . provider . off ,
116
- addListener : windowAny . providerApi . provider . addListener ,
117
- removeListener : windowAny . providerApi . provider . removeListener ,
118
- removeAllListeners : windowAny . providerApi . provider . removeAllListeners ,
119
- isMetaMask : true ,
120
- enable : windowAny . providerApi . provider . enable ,
121
- selectedAddress : undefined ,
122
- accounts : undefined
123
- }
124
-
125
- windowAny . ethereum . on ( 'accountsChanged' , ( accounts : string [ ] ) => {
126
- console . log ( 'accounts changed' , accounts )
127
- windowAny . ethereum . selectedAddress = accounts [ 0 ]
128
- windowAny . ethereum . accounts = accounts
129
- } )
128
+ const windowAny = window
129
+ windowAny . ethereum = {
130
+ request : windowAny . providerApi . provider . request ,
131
+ // @ts -expect-error deprecated send call needs to be generic
132
+ send : windowAny . providerApi . provider . send ,
133
+ sendAsync : windowAny . providerApi . provider . sendAsync ,
134
+ once : windowAny . providerApi . provider . once ,
135
+ on : windowAny . providerApi . provider . on ,
136
+ off : windowAny . providerApi . provider . off ,
137
+ addListener : windowAny . providerApi . provider . addListener ,
138
+ removeListener : windowAny . providerApi . provider . removeListener ,
139
+ removeAllListeners : windowAny . providerApi . provider . removeAllListeners ,
140
+ isMetaMask : true ,
141
+ enable : windowAny . providerApi . provider . enable ,
142
+ selectedAddress : null ,
143
+ accounts : undefined
144
+ }
130
145
131
- const acct = await windowAny . ethereum . request ( { method : 'eth_accounts' } )
132
- windowAny . ethereum . selectedAddress =
133
- acct && acct . length > 0 ? acct [ 0 ] : ''
134
- windowAny . ethereum . accounts = acct
146
+ // @ts -expect-error TODO fix types in MetaMaskInpageProvider
147
+ windowAny . ethereum . on ( 'accountsChanged' , ( accounts : string [ ] ) => {
148
+ console . log ( 'accounts changed' , accounts )
149
+ // @ts -expect-error TODO fix types in MetaMaskInpageProvider
150
+ windowAny . ethereum . selectedAddress = accounts [ 0 ]
151
+ // @ts -expect-error TODO fix types in MetaMaskInpageProvider
152
+ windowAny . ethereum . accounts = accounts
153
+ } )
154
+
155
+ const ev = new Event ( 'ethereum#initialized' )
156
+ window . dispatchEvent ( ev )
157
+
158
+ const timeNow = Date . now ( )
159
+ const acct = await windowAny . ethereum . request ( { method : 'eth_accounts' } )
160
+ // @ts -expect-error TODO fix types in MetaMaskInpageProvider
161
+ windowAny . ethereum . selectedAddress = acct && acct . length > 0 ? acct [ 0 ] : ''
162
+ // @ts -expect-error TODO fix types in MetaMaskInpageProvider
163
+ windowAny . ethereum . accounts = acct
164
+
165
+ /**
166
+ * opensea performs 4 responsiveness checks by calling eth_accounts with 2 second timeouts.
167
+ * if all 4 fail, clicking MetaMask in the connection options will open the metamask.io download page.
168
+ * we reload the page so that MetaMask will connect as expected if the user unlocks their wallet
169
+ * after this time period.
170
+ */
171
+ const timeElapsed = Date . now ( ) - timeNow
172
+ if ( timeElapsed > 8000 ) {
173
+ window . location . reload ( )
135
174
}
136
175
}
137
176
@@ -154,9 +193,6 @@ function initProvider() {
154
193
window . addEventListener ( 'eip6963:requestProvider' , ( ) => {
155
194
announceProvider ( )
156
195
} )
157
-
158
- const ev = new Event ( 'ethereum#initialized' )
159
- window . dispatchEvent ( ev )
160
196
}
161
197
162
198
const exposeWindowEthereumProvider = `(${ initProvider . toString ( ) } )()`
0 commit comments