Skip to content

Commit 543463a

Browse files
authored
[Fix] ext lock connect (#1149)
* fix mm ext unlock after webview launch * run yarn setup * rm unused imports * improve window.ethereum typing across the app
1 parent 7a9cb7e commit 543463a

File tree

6 files changed

+178
-64
lines changed

6 files changed

+178
-64
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,8 @@
309309
"@babel/plugin-transform-arrow-functions": "^7.24.7",
310310
"@lavamoat/allow-scripts": "^3.2.0",
311311
"@lavamoat/preinstall-always-fail": "^2.1.0",
312+
"@metamask/providers": "^18.1.1",
313+
"@metamask/utils": "^10.0.1",
312314
"@playwright/test": "^1.46.0",
313315
"@tanstack/react-query-devtools": "^5.59.20",
314316
"@testing-library/dom": "^7.31.2",

src/backend/proxy/providerPreload.ts

Lines changed: 73 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { RequestArguments } from 'common/typedefs/ipcBridge'
22
import { JsonRpcCallback } from 'common/types'
33
import { contextBridge, ipcRenderer, webFrame } from 'electron'
4+
import type { JsonRpcResponse } from '@metamask/utils'
45

56
/**
67
* @dev Extension must be removed prior to loading a page with this preload script.
@@ -24,7 +25,11 @@ const enabledTopics = [
2425
]
2526

2627
/* 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+
) => {
2833
if (!enabledTopics.includes(topic)) {
2934
throw `Tried to listen to ${topic} through window.ethereum!`
3035
}
@@ -66,29 +71,40 @@ const providerApi = {
6671
request: async (args: RequestArguments) => {
6772
return provRequest(args)
6873
},
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>>
7176
},
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+
>
7484
},
7585
once: (topic: string, cb: any) => {
7686
listenToRendererCalls('once', topic, cb)
87+
return window.ethereum
7788
},
7889
on: (topic: string, cb: any) => {
7990
listenToRendererCalls('on', topic, cb)
91+
return window.ethereum
8092
},
8193
off: (topic: string, cb: any) => {
8294
ipcRenderer.off('providerApi' + topic, cb)
95+
return window.ethereum
8396
},
8497
addListener: (topic: string, cb: any) => {
8598
listenToRendererCalls('addListener', topic, cb)
99+
return window.ethereum
86100
},
87101
removeListener: (topic: string, cb: any) => {
88102
ipcRenderer.removeListener('providerApi' + topic, cb)
103+
return window.ethereum
89104
},
90-
removeAllListeners: (topic: string) => {
105+
removeAllListeners: (topic?: string) => {
91106
ipcRenderer.removeAllListeners('providerApi' + topic)
107+
return window.ethereum
92108
},
93109
enable: async () => {
94110
const args: RequestArguments = {
@@ -101,37 +117,60 @@ const providerApi = {
101117

102118
contextBridge?.exposeInMainWorld('providerApi', providerApi)
103119

120+
declare global {
121+
interface Window {
122+
providerApi: typeof providerApi
123+
}
124+
}
125+
104126
function initProvider() {
105127
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+
}
130145

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()
135174
}
136175
}
137176

@@ -154,9 +193,6 @@ function initProvider() {
154193
window.addEventListener('eip6963:requestProvider', () => {
155194
announceProvider()
156195
})
157-
158-
const ev = new Event('ethereum#initialized')
159-
window.dispatchEvent(ev)
160196
}
161197

162198
const exposeWindowEthereumProvider = `(${initProvider.toString()})()`

src/common/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
1414
import { DropdownItemType } from '@hyperplay/ui'
1515
export type { Quest } from '@hyperplay/utils'
1616

17+
import { MetaMaskInpageProvider } from '@metamask/providers'
18+
1719
export type {
1820
Listing as HyperPlayRelease,
1921
ProjectMetaApi as HyperPlayProjectMeta,
@@ -978,3 +980,9 @@ export interface PointsCollection {
978980
}
979981

980982
export type { GamePageActions } from '@hyperplay/utils'
983+
984+
declare global {
985+
interface Window {
986+
ethereum: MetaMaskInpageProvider
987+
}
988+
}

src/frontend/ExtensionHandler/index.tsx

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,6 @@ import { observer } from 'mobx-react-lite'
33
import React from 'react'
44
import { useNavigate } from 'react-router-dom'
55

6-
declare global {
7-
interface Window {
8-
ethereum: {
9-
/*eslint-disable-next-line @typescript-eslint/no-explicit-any */
10-
request: (args: any) => any
11-
/*eslint-disable-next-line @typescript-eslint/no-explicit-any */
12-
send: (...args: any) => any
13-
/*eslint-disable-next-line @typescript-eslint/no-explicit-any */
14-
sendAsync: (...args: any) => any
15-
/*eslint-disable-next-line @typescript-eslint/no-explicit-any */
16-
on: (topic: string, handler: (...args: any) => void) => void
17-
isConnected: () => boolean
18-
}
19-
}
20-
}
21-
226
const ExtensionHandler = observer(function () {
237
const navigate = useNavigate()
248

src/frontend/OverlayManager/index.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import React, { useRef } from 'react'
22
import BrowserGameStyles from './index.module.scss'
3-
import { PROVIDERS } from 'common/types/proxy-types'
43
import { observer } from 'mobx-react-lite'
54
import OverlayState from 'frontend/state/OverlayState'
6-
import WalletState from 'frontend/state/WalletState'
75
import { BrowserGameProps } from './types'
86
import { Overlay } from './Overlay'
97
import { WebviewTag } from 'electron'
@@ -64,12 +62,7 @@ const OverlayManager = observer(function ({
6462
<webview
6563
src={url}
6664
className={BrowserGameStyles.browserGame}
67-
partition={
68-
WalletState.provider === PROVIDERS.METAMASK_MOBILE ||
69-
PROVIDERS.WALLET_CONNECT
70-
? 'persist:InPageWindowEthereumExternalWallet'
71-
: undefined
72-
}
65+
partition={'persist:InPageWindowEthereumExternalWallet'}
7366
webpreferences="contextIsolation=true"
7467
// setting = to {true} does not work :(
7568
allowpopups={trueAsStr}

0 commit comments

Comments
 (0)