From 0f47bf0ecf6b9ea2907bf78d32b0cf2229bde2eb Mon Sep 17 00:00:00 2001 From: KillariDev Date: Fri, 19 Jul 2024 09:13:31 +0300 Subject: [PATCH 01/14] bfcache --- app/inpage/ts/document_start.ts | 17 +++++++++-------- app/inpage/ts/listenContentScript.ts | 14 +++++++++----- app/ts/background/background-startup.ts | 22 ++++++++++++++-------- app/ts/background/messageSending.ts | 4 +++- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/app/inpage/ts/document_start.ts b/app/inpage/ts/document_start.ts index dc841af9..aca9310d 100644 --- a/app/inpage/ts/document_start.ts +++ b/app/inpage/ts/document_start.ts @@ -22,7 +22,7 @@ function injectScript(_content: string) { return `0x${ Array.from(arr, dec2hex).join('') }` } const connectionNameNotUndefined = conectionName === undefined ? generateId(40) : conectionName - + let connected = true const extensionPort = browser.runtime.connect({ name: connectionNameNotUndefined }) // forward all message events to the background script, which will then filter and process them @@ -56,18 +56,19 @@ function injectScript(_content: string) { } }) - extensionPort.onDisconnect.addListener(() => { - try { + extensionPort.onDisconnect.addListener(() => { connected = false }) + + // https://web.dev/articles/bfcache + globalThis.addEventListener('pageshow', () => { + checkAndThrowRuntimeLastError() + if (!connected) { globalThis.removeEventListener('message', listener) - listenInContentScript(connectionNameNotUndefined) - checkAndThrowRuntimeLastError() - } catch (error) { - console.error(error) + listenContentScript(connectionNameNotUndefined) } + checkAndThrowRuntimeLastError() }) } - try { const container = document.head || document.documentElement const scriptTag = document.createElement('script') diff --git a/app/inpage/ts/listenContentScript.ts b/app/inpage/ts/listenContentScript.ts index e49d7f02..c914b9d6 100644 --- a/app/inpage/ts/listenContentScript.ts +++ b/app/inpage/ts/listenContentScript.ts @@ -18,6 +18,7 @@ function listenContentScript(conectionName: string | undefined) { return `0x${ Array.from(arr, dec2hex).join('') }` } const connectionNameNotUndefined = conectionName === undefined ? generateId(40) : conectionName + let connected = true const extensionPort = browser.runtime.connect({ name: connectionNameNotUndefined }) // forward all message events to the background script, which will then filter and process them @@ -58,15 +59,18 @@ function listenContentScript(conectionName: string | undefined) { } }) - extensionPort.onDisconnect.addListener(() => { - try { + extensionPort.onDisconnect.addListener(() => { connected = false }) + + // https://web.dev/articles/bfcache + globalThis.addEventListener('pageshow', () => { + checkAndThrowRuntimeLastError() + if (!connected) { globalThis.removeEventListener('message', listener) listenContentScript(connectionNameNotUndefined) - checkAndThrowRuntimeLastError() - } catch (error: unknown) { - console.error(error) } + checkAndThrowRuntimeLastError() }) + try { checkAndThrowRuntimeLastError() } catch (error: unknown) { diff --git a/app/ts/background/background-startup.ts b/app/ts/background/background-startup.ts index be4da02a..61f3cecf 100644 --- a/app/ts/background/background-startup.ts +++ b/app/ts/background/background-startup.ts @@ -69,13 +69,8 @@ async function onContentScriptConnected(simulator: Simulator, port: browser.runt const websitePromise = (async () => ({ websiteOrigin, ...await retrieveWebsiteDetails(socket.tabId) }))() const tabConnection = websiteTabConnections.get(socket.tabId) - const newConnection = { - port: port, - socket: socket, - websiteOrigin: websiteOrigin, - approved: false, - wantsToConnect: false, - } + const newConnection = { port, socket, websiteOrigin, approved: false, wantsToConnect: false } + port.onDisconnect.addListener(() => { catchAllErrorsAndCall(async () => { const tabConnection = websiteTabConnections.get(socket.tabId) @@ -85,6 +80,18 @@ async function onContentScriptConnected(simulator: Simulator, port: browser.runt websiteTabConnections.delete(socket.tabId) } }) + try { + checkAndThrowRuntimeLastError() + } catch (error) { + if (error instanceof Error) { + if (error.message?.includes('the message channel is closed')) { + // ignore bfcache error. It means that the page is hibernating and we cannot communicate with it anymore. We get a normal disconnect about it. + // https://developer.chrome.com/blog/bfcache-extension-messaging-changes + return + } + } + throw error + } }) port.onMessage.addListener((payload) => { @@ -196,7 +203,6 @@ async function startup() { browser.runtime.onConnect.addListener(async (port) => await catchAllErrorsAndCall(() => onContentScriptConnected(simulator, port, websiteTabConnections))) browser.runtime.onMessage.addListener(async (message: unknown) => await catchAllErrorsAndCall(async () => popupMessageHandler(websiteTabConnections, simulator, message, await getSettings()))) - const recursiveCheckIfInterceptorShouldSleep = async () => { await catchAllErrorsAndCall(async () => checkIfInterceptorShouldSleep(simulator.ethereum)) setTimeout(recursiveCheckIfInterceptorShouldSleep, 1000) diff --git a/app/ts/background/messageSending.ts b/app/ts/background/messageSending.ts index f9520a6c..3ef5e2fe 100644 --- a/app/ts/background/messageSending.ts +++ b/app/ts/background/messageSending.ts @@ -1,11 +1,12 @@ import { InterceptedRequestForward, InterceptorMessageToInpage, SubscriptionReplyOrCallBack } from "../types/interceptor-messages.js" -import { WebsiteSocket } from "../utils/requests.js" +import { WebsiteSocket, checkAndPrintRuntimeLastError } from "../utils/requests.js" import { WebsiteTabConnections } from "../types/user-interface-types.js" import { websiteSocketToString } from "./backgroundUtils.js" import { serialize } from "../types/wire-types.js" function postMessageToPortIfConnected(port: browser.runtime.Port, message: InterceptorMessageToInpage) { try { + checkAndPrintRuntimeLastError() port.postMessage(serialize(InterceptorMessageToInpage, message) as Object) } catch (error) { if (error instanceof Error) { @@ -18,6 +19,7 @@ function postMessageToPortIfConnected(port: browser.runtime.Port, message: Inter } throw error } + checkAndPrintRuntimeLastError() } export function replyToInterceptedRequest(websiteTabConnections: WebsiteTabConnections, message: InterceptedRequestForward) { From 500fb774ae2677d174b69fb41027e70b6c0e97f2 Mon Sep 17 00:00:00 2001 From: KillariDev Date: Mon, 22 Jul 2024 11:57:38 +0300 Subject: [PATCH 02/14] fix bfcache --- app/inpage/ts/document_start.ts | 73 +++++++++++++++++++--------- app/inpage/ts/listenContentScript.ts | 69 +++++++++++++++----------- 2 files changed, 91 insertions(+), 51 deletions(-) diff --git a/app/inpage/ts/document_start.ts b/app/inpage/ts/document_start.ts index aca9310d..a4d00066 100644 --- a/app/inpage/ts/document_start.ts +++ b/app/inpage/ts/document_start.ts @@ -7,7 +7,12 @@ function injectScript(_content: string) { if (error !== null && error !== undefined && error.message !== undefined) throw new Error(error.message) } - function listenInContentScript(conectionName: string | undefined) { + function listenContentScript(connectionName: string | undefined) { + const checkAndThrowRuntimeLastError = () => { + const error: browser.runtime._LastError | undefined | null = browser.runtime.lastError // firefox return `null` on no errors + if (error !== null && error !== undefined && error.message !== undefined) throw new Error(error.message) + } + /** * this script executed within the context of the active tab when the user clicks the extension bar button * this script serves as a _very thin_ proxy between the page scripts (dapp) and the extension, simply forwarding messages between the two @@ -21,13 +26,14 @@ function injectScript(_content: string) { globalThis.crypto.getRandomValues(arr) return `0x${ Array.from(arr, dec2hex).join('') }` } - const connectionNameNotUndefined = conectionName === undefined ? generateId(40) : conectionName - let connected = true - const extensionPort = browser.runtime.connect({ name: connectionNameNotUndefined }) + const connectionNameNotUndefined = connectionName === undefined ? generateId(40) : connectionName + let pageHidden = false + let extensionPort: browser.runtime.Port | undefined = undefined // forward all message events to the background script, which will then filter and process them // biome-ignore lint/suspicious/noExplicitAny: MessageEvent default signature - const listener = (messageEvent: MessageEvent) => { + globalThis.addEventListener('message', (messageEvent: MessageEvent) => { + if (extensionPort === undefined) return try { // we only want the data element, if it exists, and postMessage will fail if it can't clone the object fully (and it cannot clone a MessageEvent) if (!('data' in messageEvent)) return @@ -39,34 +45,57 @@ function injectScript(_content: string) { // this error happens when the extension is refreshed and the page cannot reach The Interceptor anymore return } + if (error.message?.includes('User denied')) return // user denied signature } extensionPort.postMessage({ data: { interceptorRequest: true, usingInterceptorWithoutSigner: false, requestId: -1, method: 'InterceptorError', params: [error] } }) throw error } + }) + + const connect = () => { + if (extensionPort) extensionPort.disconnect() + extensionPort = browser.runtime.connect({ name: connectionNameNotUndefined }) + + // forward all messages we get from the background script to the window so the page script can filter and process them + extensionPort.onMessage.addListener(messageEvent => { + if (typeof messageEvent !== 'object' || messageEvent === null || !('interceptorApproved' in messageEvent)) { + console.error('Malformed message:') + console.error(messageEvent) + if (extensionPort === undefined) return + extensionPort.postMessage({ data: { interceptorRequest: true, usingInterceptorWithoutSigner: false, requestId: -1, method: 'InterceptorError', params: [messageEvent] } }) + return + } + try { + globalThis.postMessage(messageEvent, '*') + checkAndThrowRuntimeLastError() + } catch (error) { + console.error(error) + } + }) + + extensionPort.onDisconnect.addListener(() => { pageHidden = true }) } - globalThis.addEventListener('message', listener) + connect() - // forward all messages we get from the background script to the window so the page script can filter and process them - extensionPort.onMessage.addListener(response => { + // https://web.dev/articles/bfcache + const bfCachePageShow = () => { try { - globalThis.postMessage(response, '*') checkAndThrowRuntimeLastError() - } catch (error) { + if (pageHidden) connect() + checkAndThrowRuntimeLastError() + pageHidden = false + } catch (error: unknown) { console.error(error) } - }) - - extensionPort.onDisconnect.addListener(() => { connected = false }) + } + globalThis.addEventListener('pageshow', () => bfCachePageShow(), false) + globalThis.addEventListener('pagehide', () => { pageHidden = true }, false) - // https://web.dev/articles/bfcache - globalThis.addEventListener('pageshow', () => { + try { checkAndThrowRuntimeLastError() - if (!connected) { - globalThis.removeEventListener('message', listener) - listenContentScript(connectionNameNotUndefined) - } - checkAndThrowRuntimeLastError() - }) + } catch (error: unknown) { + console.error(error) + } } try { @@ -76,7 +105,7 @@ function injectScript(_content: string) { scriptTag.src = browser.runtime.getURL('inpage/js/inpage.js') container.insertBefore(scriptTag, container.children[1]) container.removeChild(scriptTag) - listenInContentScript(undefined) + listenContentScript(undefined) checkAndThrowRuntimeLastError() } catch (error) { console.error('Interceptor: Provider injection failed.', error) diff --git a/app/inpage/ts/listenContentScript.ts b/app/inpage/ts/listenContentScript.ts index c914b9d6..28bf306b 100644 --- a/app/inpage/ts/listenContentScript.ts +++ b/app/inpage/ts/listenContentScript.ts @@ -1,4 +1,4 @@ -function listenContentScript(conectionName: string | undefined) { +function listenContentScript(connectionName: string | undefined) { const checkAndThrowRuntimeLastError = () => { const error: browser.runtime._LastError | undefined | null = browser.runtime.lastError // firefox return `null` on no errors if (error !== null && error !== undefined && error.message !== undefined) throw new Error(error.message) @@ -17,13 +17,14 @@ function listenContentScript(conectionName: string | undefined) { globalThis.crypto.getRandomValues(arr) return `0x${ Array.from(arr, dec2hex).join('') }` } - const connectionNameNotUndefined = conectionName === undefined ? generateId(40) : conectionName - let connected = true - const extensionPort = browser.runtime.connect({ name: connectionNameNotUndefined }) + const connectionNameNotUndefined = connectionName === undefined ? generateId(40) : connectionName + let pageHidden = false + let extensionPort: browser.runtime.Port | undefined = undefined // forward all message events to the background script, which will then filter and process them // biome-ignore lint/suspicious/noExplicitAny: MessageEvent default signature - const listener = (messageEvent: MessageEvent) => { + globalThis.addEventListener('message', (messageEvent: MessageEvent) => { + if (extensionPort === undefined) return try { // we only want the data element, if it exists, and postMessage will fail if it can't clone the object fully (and it cannot clone a MessageEvent) if (!('data' in messageEvent)) return @@ -40,36 +41,46 @@ function listenContentScript(conectionName: string | undefined) { extensionPort.postMessage({ data: { interceptorRequest: true, usingInterceptorWithoutSigner: false, requestId: -1, method: 'InterceptorError', params: [error] } }) throw error } + }) + + const connect = () => { + if (extensionPort) extensionPort.disconnect() + extensionPort = browser.runtime.connect({ name: connectionNameNotUndefined }) + + // forward all messages we get from the background script to the window so the page script can filter and process them + extensionPort.onMessage.addListener(messageEvent => { + if (typeof messageEvent !== 'object' || messageEvent === null || !('interceptorApproved' in messageEvent)) { + console.error('Malformed message:') + console.error(messageEvent) + if (extensionPort === undefined) return + extensionPort.postMessage({ data: { interceptorRequest: true, usingInterceptorWithoutSigner: false, requestId: -1, method: 'InterceptorError', params: [messageEvent] } }) + return + } + try { + globalThis.postMessage(messageEvent, '*') + checkAndThrowRuntimeLastError() + } catch (error) { + console.error(error) + } + }) + + extensionPort.onDisconnect.addListener(() => { pageHidden = true }) } - globalThis.addEventListener('message', listener) + connect() - // forward all messages we get from the background script to the window so the page script can filter and process them - extensionPort.onMessage.addListener(messageEvent => { - if (typeof messageEvent !== 'object' || messageEvent === null || !('interceptorApproved' in messageEvent)) { - console.error('Malformed message:') - console.error(messageEvent) - extensionPort.postMessage({ data: { interceptorRequest: true, usingInterceptorWithoutSigner: false, requestId: -1, method: 'InterceptorError', params: [messageEvent] } }) - return - } + // https://web.dev/articles/bfcache + const bfCachePageShow = () => { try { - globalThis.postMessage(messageEvent, '*') checkAndThrowRuntimeLastError() - } catch (error) { + if (pageHidden) connect() + checkAndThrowRuntimeLastError() + pageHidden = false + } catch (error: unknown) { console.error(error) } - }) - - extensionPort.onDisconnect.addListener(() => { connected = false }) - - // https://web.dev/articles/bfcache - globalThis.addEventListener('pageshow', () => { - checkAndThrowRuntimeLastError() - if (!connected) { - globalThis.removeEventListener('message', listener) - listenContentScript(connectionNameNotUndefined) - } - checkAndThrowRuntimeLastError() - }) + } + globalThis.addEventListener('pageshow', () => bfCachePageShow(), false) + globalThis.addEventListener('pagehide', () => { pageHidden = true }, false) try { checkAndThrowRuntimeLastError() From 9749d4d8dca94c2499a0dc14420772c189c9de58 Mon Sep 17 00:00:00 2001 From: KillariDev Date: Mon, 22 Jul 2024 12:04:01 +0300 Subject: [PATCH 03/14] ignore bfcache error, handled in connect/disconnect --- app/ts/background/background-startup.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/ts/background/background-startup.ts b/app/ts/background/background-startup.ts index 61f3cecf..c4e7942f 100644 --- a/app/ts/background/background-startup.ts +++ b/app/ts/background/background-startup.ts @@ -33,6 +33,11 @@ const catchAllErrorsAndCall = async (func: () => Promise) => { } catch(error: unknown) { console.error(error) if (error instanceof Error && error.message.startsWith('No tab with id')) return + if (error instanceof Error && error.message?.includes('the message channel is closed')) { + // ignore bfcache error. It means that the page is hibernating and we cannot communicate with it anymore. We get a normal disconnect about it. + // https://developer.chrome.com/blog/bfcache-extension-messaging-changes + return + } handleUnexpectedError(error) } } From 36fb3881c395319908447bd184c14df2a8d14a76 Mon Sep 17 00:00:00 2001 From: KillariDev Date: Mon, 22 Jul 2024 12:07:38 +0300 Subject: [PATCH 04/14] don't print if we are ignoring --- app/ts/background/background-startup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ts/background/background-startup.ts b/app/ts/background/background-startup.ts index c4e7942f..a622ae4d 100644 --- a/app/ts/background/background-startup.ts +++ b/app/ts/background/background-startup.ts @@ -31,13 +31,13 @@ const catchAllErrorsAndCall = async (func: () => Promise) => { await func() checkAndThrowRuntimeLastError() } catch(error: unknown) { - console.error(error) if (error instanceof Error && error.message.startsWith('No tab with id')) return if (error instanceof Error && error.message?.includes('the message channel is closed')) { // ignore bfcache error. It means that the page is hibernating and we cannot communicate with it anymore. We get a normal disconnect about it. // https://developer.chrome.com/blog/bfcache-extension-messaging-changes return } + console.error(error) handleUnexpectedError(error) } } From d9059066f0cf9c00b0436e3c6d4f192bdb797b15 Mon Sep 17 00:00:00 2001 From: KillariDev Date: Tue, 23 Jul 2024 09:40:24 +0300 Subject: [PATCH 05/14] unsupported networks --- app/inpage/ts/inpage.ts | 30 ++++++++++++++++++-- app/ts/background/background.ts | 6 +++- app/ts/background/providerMessageHandlers.ts | 7 ++--- app/ts/types/interceptor-messages.ts | 10 +++---- 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/app/inpage/ts/inpage.ts b/app/inpage/ts/inpage.ts index 4cb70ed7..20f2eea7 100644 --- a/app/inpage/ts/inpage.ts +++ b/app/inpage/ts/inpage.ts @@ -81,7 +81,7 @@ type InterceptedRequestForwardWithError = InterceptedRequestBase & { } } -type InterceptedRequestForwardToSigner = InterceptedRequestBase & { readonly type: 'forwardToSigner' } +type InterceptedRequestForwardToSigner = InterceptedRequestBase & { readonly type: 'forwardToSigner', readonly replyWithSignersReply?: true } type InterceptedRequestForward = InterceptedRequestForwardWithResult | InterceptedRequestForwardWithError | InterceptedRequestForwardToSigner @@ -553,13 +553,37 @@ class InterceptorMessageListener { const sendToSignerWithCatchError = async () => { try { const reply = await signerRequest({ method: forwardRequest.method, params: 'params' in forwardRequest ? forwardRequest.params : [] }) - return { success: true, forwardRequest, reply } + return { success: true as const, forwardRequest, reply } } catch(error: unknown) { - return { success: false, forwardRequest, error } + return { success: false as const, forwardRequest, error } } } const signerReply = await sendToSignerWithCatchError() try { + if ('replyWithSignersReply' in forwardRequest) { + if (signerReply.success) { + await this.handleReplyRequest({ + requestId: forwardRequest.requestId, + interceptorApproved: true, + method: forwardRequest.method, + type: 'result', + result: signerReply.reply, + }) + return + } else { + const pending = this.outstandingRequests.get(forwardRequest.requestId) + if (pending === undefined) throw new Error('Request did not exist anymore') + const error = signerReply.error + if (typeof error === 'object' && error !== null + && 'code' in error && error.code !== undefined && typeof error.code === 'number' + && 'message' in error && error.message !== undefined && typeof error.message === 'string' + ) { + return pendingRequest.reject(new EthereumJsonRpcError(error.code, error.message, 'data' in error && typeof error.data === 'object' && error.data !== null ? error.data : undefined)) + } + pending.reject(new EthereumJsonRpcError(METAMASK_ERROR_BLANKET_ERROR, 'Unexpected thrown value.', { error })) + return + } + } await this.sendMessageToBackgroundPage({ method: 'signer_reply', params: [ signerReply ] }) } catch(error: unknown) { if (error instanceof Error) return pendingRequest.reject(error) diff --git a/app/ts/background/background.ts b/app/ts/background/background.ts index 7b5594d3..18614d12 100644 --- a/app/ts/background/background.ts +++ b/app/ts/background/background.ts @@ -425,7 +425,7 @@ async function handleRPCRequest( console.warn(maybeParsedRequest.fullError) const maybePartiallyParsedRequest = SupportedEthereumJsonRpcRequestMethods.safeParse(request) // the method is some method that we are not supporting, forward it to the wallet if signer is available - if (maybePartiallyParsedRequest.success === false && forwardToSigner) return { type: 'forwardToSigner' as const, unknownMethod: true, ...request } + if (maybePartiallyParsedRequest.success === false && forwardToSigner) return { type: 'forwardToSigner' as const, replyWithSignersReply: true, ...request } return { type: 'result' as const, method: request.method, @@ -436,6 +436,10 @@ async function handleRPCRequest( } } } + if (settings.currentRpcNetwork.httpsRpc === undefined && forwardToSigner) { + // we are using network that is not supported by us + return { type: 'forwardToSigner' as const, replyWithSignersReply: true, ...request } + } const parsedRequest = maybeParsedRequest.value makeSureInterceptorIsNotSleeping(simulator.ethereum) switch (parsedRequest.method) { diff --git a/app/ts/background/providerMessageHandlers.ts b/app/ts/background/providerMessageHandlers.ts index c8730375..08c02e13 100644 --- a/app/ts/background/providerMessageHandlers.ts +++ b/app/ts/background/providerMessageHandlers.ts @@ -49,9 +49,8 @@ export async function ethAccountsReply(simulator: Simulator, websiteTabConnectio async function changeSignerChain(simulator: Simulator, websiteTabConnections: WebsiteTabConnections, port: browser.runtime.Port, signerChain: bigint, approval: ApprovalState, _activeAddress: bigint | undefined) { if (approval !== 'hasAccess') return if (port.sender?.tab?.id === undefined) return - if ((await getTabState(port.sender.tab.id)).signerChain === signerChain) return - await updateTabState(port.sender.tab.id, (previousState: TabState) => modifyObject(previousState, { signerChain })) - + const oldSignerChain = (await getTabState(port.sender.tab.id)).signerChain + if (oldSignerChain !== signerChain) await updateTabState(port.sender.tab.id, (previousState: TabState) => modifyObject(previousState, { signerChain })) // update active address if we are using signers address const settings = await getSettings() if ((settings.useSignersAddressAsActiveAddress || !settings.simulationMode) && settings.currentRpcNetwork.chainId !== signerChain) { @@ -60,7 +59,7 @@ async function changeSignerChain(simulator: Simulator, websiteTabConnections: We rpcNetwork: await getRpcNetworkForChain(signerChain), }) } - sendPopupMessageToOpenWindows({ method: 'popup_chain_update' }) + if (oldSignerChain !== signerChain) sendPopupMessageToOpenWindows({ method: 'popup_chain_update' }) } export async function signerChainChanged(simulator: Simulator, websiteTabConnections: WebsiteTabConnections, port: browser.runtime.Port, request: ProviderMessage, approval: ApprovalState, activeAddress: bigint | undefined) { diff --git a/app/ts/types/interceptor-messages.ts b/app/ts/types/interceptor-messages.ts index bcbfacbd..3a13bc17 100644 --- a/app/ts/types/interceptor-messages.ts +++ b/app/ts/types/interceptor-messages.ts @@ -117,11 +117,11 @@ const ForwardToWallet = funtypes.Intersect( // forward directly to wallet funtypes.Union(SendRawTransactionParams, SendTransactionParams, PersonalSignParams, SignTypedDataParams, OldSignTypedDataParams, WalletAddEthereumChain, EthGetStorageAtParams), ) -type UnknownMethodForward = funtypes.Static -const UnknownMethodForward = funtypes.Intersect( +type ReplyWithSignersReplyForward = funtypes.Static +const ReplyWithSignersReplyForward = funtypes.Intersect( funtypes.ReadonlyObject({ type: funtypes.Literal('forwardToSigner'), - unknownMethod: funtypes.Literal(true), + replyWithSignersReply: funtypes.Literal(true), method: funtypes.String, }), funtypes.Partial({ @@ -133,7 +133,7 @@ export type RPCReply = funtypes.Static export const RPCReply = funtypes.Union( NonForwardingRPCRequestReturnValue, ForwardToWallet, - UnknownMethodForward, + ReplyWithSignersReplyForward, funtypes.ReadonlyObject({ type: funtypes.Literal('doNotReply') }), ) @@ -338,7 +338,7 @@ export const ConnectedToSigner = funtypes.ReadonlyObject({ type SignerReplyForwardRequest = funtypes.Static const SignerReplyForwardRequest = funtypes.Intersect( funtypes.ReadonlyObject({ requestId: funtypes.Number }), - funtypes.Union(ForwardToWallet, UnknownMethodForward) + funtypes.Union(ForwardToWallet, ReplyWithSignersReplyForward) ) export type SignerReply = funtypes.Static From 8b87739aae6bfd3b2d1d3d055a3adc419cb8398b Mon Sep 17 00:00:00 2001 From: KillariDev Date: Tue, 23 Jul 2024 09:43:16 +0300 Subject: [PATCH 06/14] inform UI bettter on if we are retrying the connection or not, do not block poll rpc on unsupported network --- app/ts/background/background-startup.ts | 2 ++ app/ts/background/iconHandler.ts | 4 ++-- app/ts/background/popupMessageHandlers.ts | 3 +-- app/ts/background/sleeping.ts | 18 ++++++++++++++++-- app/ts/components/App.tsx | 7 +++---- app/ts/components/pages/Home.tsx | 2 +- app/ts/types/user-interface-types.ts | 1 + 7 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/ts/background/background-startup.ts b/app/ts/background/background-startup.ts index a622ae4d..9ae5b2e4 100644 --- a/app/ts/background/background-startup.ts +++ b/app/ts/background/background-startup.ts @@ -154,6 +154,7 @@ async function newBlockAttemptCallback(blockheader: EthereumBlockHeader, ethereu lastConnnectionAttempt: new Date(), latestBlock: blockheader, rpcNetwork: ethereumClientService.getRpcEntry(), + retrying: ethereumClientService.isBlockPolling(), } await setRpcConnectionStatus(rpcConnectionStatus) await updateExtensionBadge() @@ -180,6 +181,7 @@ async function onErrorBlockCallback(ethereumClientService: EthereumClientService lastConnnectionAttempt: new Date(), latestBlock: ethereumClientService.getCachedBlock(), rpcNetwork: ethereumClientService.getRpcEntry(), + retrying: ethereumClientService.isBlockPolling(), } await setRpcConnectionStatus(rpcConnectionStatus) await updateExtensionBadge() diff --git a/app/ts/background/iconHandler.ts b/app/ts/background/iconHandler.ts index be7df148..b4ca2da5 100644 --- a/app/ts/background/iconHandler.ts +++ b/app/ts/background/iconHandler.ts @@ -36,7 +36,7 @@ export async function updateExtensionIcon(websiteTabConnections: WebsiteTabConne return setIcon(ICON_ACCESS_DENIED, `The access to ${ activeAddress.name } for ${ websiteOrigin } has been DENIED!`) } if (settings.simulationMode) return setIcon(ICON_SIMULATING, 'The Interceptor simulates your sent transactions.') - if (settings.currentRpcNetwork.httpsRpc === undefined) return setIcon(ICON_SIGNING_NOT_SUPPORTED, 'Interceptor is on an unsupported network and simulation mode is disabled.') + if (settings.currentRpcNetwork.httpsRpc === undefined) return setIcon(ICON_SIGNING_NOT_SUPPORTED, `The Interceptor is disabled while it's on an unsupported network`) const tabState = await getTabState(tabId) return setIcon(ICON_SIGNING, `The Interceptor forwards your transactions to ${ getPrettySignerName(tabState.signerName) } once sent.`) } @@ -47,7 +47,7 @@ export function noNewBlockForOverTwoMins(connectionStatus: RpcConnectionStatus) export async function updateExtensionBadge() { const connectionStatus = await getRpcConnectionStatus() - if ((connectionStatus?.isConnected === false || noNewBlockForOverTwoMins(connectionStatus)) && connectionStatus) { + if (connectionStatus?.isConnected === false || noNewBlockForOverTwoMins(connectionStatus) && connectionStatus && connectionStatus.retrying) { const nextConnectionAttempt = new Date(connectionStatus.lastConnnectionAttempt.getTime() + TIME_BETWEEN_BLOCKS * 1000) if (nextConnectionAttempt.getTime() - new Date().getTime() > 0) { await setExtensionBadgeBackgroundColor({ color: WARNING_COLOR }) diff --git a/app/ts/background/popupMessageHandlers.ts b/app/ts/background/popupMessageHandlers.ts index 58eb4817..53367716 100644 --- a/app/ts/background/popupMessageHandlers.ts +++ b/app/ts/background/popupMessageHandlers.ts @@ -315,13 +315,11 @@ export const openNewTab = async (tabName: 'settingsView' | 'addressBook') => { export async function requestNewHomeData(simulator: Simulator, requestAbortController: AbortController | undefined) { const settings = await getSettings() - simulator.ethereum.setBlockPolling(true) // wakes up the RPC block querying if it was sleeping if (settings.simulationMode) await updateSimulationMetadata(simulator.ethereum, requestAbortController) await refreshHomeData(simulator) } export async function refreshHomeData(simulator: Simulator) { - makeSureInterceptorIsNotSleeping(simulator.ethereum) const settingsPromise = getSettings() const makeMeRichPromise = getMakeMeRich() const rpcConnectionStatusPromise = getRpcConnectionStatus() @@ -333,6 +331,7 @@ export async function refreshHomeData(simulator: Simulator) { const tabId = await getLastKnownCurrentTabId() const tabState = tabId === undefined ? await getTabState(-1) : await getTabState(tabId) const settings = await settingsPromise + if (settings.currentRpcNetwork.httpsRpc !== undefined) makeSureInterceptorIsNotSleeping(simulator.ethereum) const websiteOrigin = tabState.website?.websiteOrigin const interceptorDisabled = websiteOrigin === undefined ? false : settings.websiteAccess.find((entry) => entry.website.websiteOrigin === websiteOrigin && entry.interceptorDisabled === true) !== undefined const updatedPage: UpdateHomePage = { diff --git a/app/ts/background/sleeping.ts b/app/ts/background/sleeping.ts index 0de26bbd..3b12b9f5 100644 --- a/app/ts/background/sleeping.ts +++ b/app/ts/background/sleeping.ts @@ -1,13 +1,26 @@ import { EthereumClientService } from "../simulation/services/EthereumClientService.js" import { TIME_BETWEEN_BLOCKS } from "../utils/constants.js" -import { getInterceptorStartSleepingTimestamp, setInterceptorStartSleepingTimestamp } from "./storageVariables.js" +import { modifyObject } from "../utils/typescript.js" +import { sendPopupMessageToOpenWindows } from "./backgroundUtils.js" +import { updateExtensionBadge } from "./iconHandler.js" +import { getInterceptorStartSleepingTimestamp, getRpcConnectionStatus, setInterceptorStartSleepingTimestamp, setRpcConnectionStatus } from "./storageVariables.js" import { isConfirmTransactionFocused } from "./windows/confirmTransaction.js" -export const makeSureInterceptorIsNotSleeping = (ethereumClientService: EthereumClientService) => { +const updateConnectionStatusRetry = async (ethereumClientService: EthereumClientService) => { + const status = await getRpcConnectionStatus() + if (status === undefined) return + const rpcConnectionStatus = modifyObject(status, { retrying: ethereumClientService.isBlockPolling() }) + await setRpcConnectionStatus(rpcConnectionStatus) + await updateExtensionBadge() + await sendPopupMessageToOpenWindows({ method: 'popup_failed_to_get_block', data: { rpcConnectionStatus } }) +} + +export const makeSureInterceptorIsNotSleeping = async (ethereumClientService: EthereumClientService) => { setInterceptorStartSleepingTimestamp(Date.now() + TIME_BETWEEN_BLOCKS * 2 * 1000) if (!ethereumClientService.isBlockPolling()) { console.info('The Interceptor woke up! ⏰') ethereumClientService.setBlockPolling(true) + await updateConnectionStatusRetry(ethereumClientService) } } @@ -21,5 +34,6 @@ export const checkIfInterceptorShouldSleep = async (ethereumClientService: Ether if (startSleping < Date.now() && ethereumClientService.isBlockPolling()) { console.info('The Interceptor started to sleep 😴') ethereumClientService.setBlockPolling(false) + await updateConnectionStatusRetry(ethereumClientService) } } diff --git a/app/ts/components/App.tsx b/app/ts/components/App.tsx index 38656115..9f65380b 100644 --- a/app/ts/components/App.tsx +++ b/app/ts/components/App.tsx @@ -23,7 +23,6 @@ import { VisualizedPersonalSignRequest } from '../types/personal-message-definit import { RpcEntries, RpcEntry, RpcNetwork } from '../types/rpc.js' import { ErrorComponent, UnexpectedError } from './subcomponents/Error.js' import { SignersLogoName } from './subcomponents/signers.js' -import { useSignal } from '@preact/signals' import { SomeTimeAgo } from './subcomponents/SomeTimeAgo.js' import { noNewBlockForOverTwoMins } from '../background/iconHandler.js' import { humanReadableDate } from './ui-utils.js' @@ -47,14 +46,14 @@ type NetworkErrorParams = { export function NetworkErrors({ rpcConnectionStatus } : NetworkErrorParams) { if (rpcConnectionStatus === undefined) return <> const nextConnectionAttempt = new Date(rpcConnectionStatus.lastConnnectionAttempt.getTime() + TIME_BETWEEN_BLOCKS * 1000) - const retrying = useSignal((nextConnectionAttempt.getTime() - new Date().getTime()) > 0) + if (rpcConnectionStatus.retrying === false) return <> return <> - { rpcConnectionStatus.isConnected === false && retrying.value ? + { rpcConnectionStatus.isConnected === false ? Unable to connect to { rpcConnectionStatus.rpcNetwork.name }. Retrying in . }/> : <> } - { rpcConnectionStatus.latestBlock !== undefined && noNewBlockForOverTwoMins(rpcConnectionStatus) && retrying.value ? + { rpcConnectionStatus.latestBlock !== undefined && noNewBlockForOverTwoMins(rpcConnectionStatus) ? The connected RPC ({ rpcConnectionStatus.rpcNetwork.name }) seem to be stuck at block { rpcConnectionStatus.latestBlock.number } (occured on: { humanReadableDate(rpcConnectionStatus.latestBlock.timestamp) }). Retrying in . }/> diff --git a/app/ts/components/pages/Home.tsx b/app/ts/components/pages/Home.tsx index a2d8d4df..494925de 100644 --- a/app/ts/components/pages/Home.tsx +++ b/app/ts/components/pages/Home.tsx @@ -283,7 +283,7 @@ export function Home(param: HomeParams) { return <> { rpcNetwork.httpsRpc === undefined ? - + : <> } From 820f38efdee6b7efac6fa078b8bedfed6fedef17 Mon Sep 17 00:00:00 2001 From: KillariDev Date: Tue, 23 Jul 2024 10:07:59 +0300 Subject: [PATCH 07/14] refactor error parsing in inpage --- app/inpage/ts/inpage.ts | 42 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/app/inpage/ts/inpage.ts b/app/inpage/ts/inpage.ts index 20f2eea7..c4efc83b 100644 --- a/app/inpage/ts/inpage.ts +++ b/app/inpage/ts/inpage.ts @@ -496,6 +496,17 @@ class InterceptorMessageListener { return } + private parseRpcError = (maybeErrorObject: unknown) => { + if (typeof maybeErrorObject !== 'object' || maybeErrorObject === null) return new EthereumJsonRpcError(METAMASK_ERROR_BLANKET_ERROR, 'Unexpected thrown value.', { maybeErrorObject } ) + if ('code' in maybeErrorObject + && maybeErrorObject.code !== undefined && typeof maybeErrorObject.code === 'number' + && 'message' in maybeErrorObject && maybeErrorObject.message !== undefined && typeof maybeErrorObject.message === 'string' + ) { + return new EthereumJsonRpcError(maybeErrorObject.code, maybeErrorObject.message, 'data' in maybeErrorObject && typeof maybeErrorObject.data === 'object' && maybeErrorObject.data !== null ? maybeErrorObject.data : undefined) + } + return new EthereumJsonRpcError(METAMASK_ERROR_BLANKET_ERROR, 'Unexpected thrown value.', maybeErrorObject ) + } + public readonly onMessage = async (messageEvent: unknown) => { this.checkIfCoinbaseInjectionMessageAndInject(messageEvent) if ( @@ -570,31 +581,13 @@ class InterceptorMessageListener { result: signerReply.reply, }) return - } else { - const pending = this.outstandingRequests.get(forwardRequest.requestId) - if (pending === undefined) throw new Error('Request did not exist anymore') - const error = signerReply.error - if (typeof error === 'object' && error !== null - && 'code' in error && error.code !== undefined && typeof error.code === 'number' - && 'message' in error && error.message !== undefined && typeof error.message === 'string' - ) { - return pendingRequest.reject(new EthereumJsonRpcError(error.code, error.message, 'data' in error && typeof error.data === 'object' && error.data !== null ? error.data : undefined)) - } - pending.reject(new EthereumJsonRpcError(METAMASK_ERROR_BLANKET_ERROR, 'Unexpected thrown value.', { error })) - return } + return pendingRequest.reject(this.parseRpcError(signerReply.error)) } await this.sendMessageToBackgroundPage({ method: 'signer_reply', params: [ signerReply ] }) } catch(error: unknown) { if (error instanceof Error) return pendingRequest.reject(error) - if (typeof error === 'object' && error !== null - && 'code' in error && error.code !== undefined && typeof error.code === 'number' - && 'message' in error && error.message !== undefined && typeof error.message === 'string' - ) { - return pendingRequest.reject(new EthereumJsonRpcError(error.code, error.message, 'data' in error && typeof error.data === 'object' && error.data !== null ? error.data : undefined)) - } - // if the signer we are connected threw something besides an Error, wrap it up in an error - pendingRequest.reject(new EthereumJsonRpcError(METAMASK_ERROR_BLANKET_ERROR, 'Unexpected thrown value.', { error })) + return pendingRequest.reject(this.parseRpcError(error)) } } catch(error: unknown) { console.error(messageEvent) @@ -605,14 +598,7 @@ class InterceptorMessageListener { const pendingRequest = this.outstandingRequests.get(requestId) if (pendingRequest === undefined) throw new Error('Request did not exist anymore') if (error instanceof Error) return pendingRequest.reject(error) - if (typeof error === 'object' && error !== null - && 'code' in error && error.code !== undefined && typeof error.code === 'number' - && 'message' in error && error.message !== undefined && typeof error.message === 'string' - ) { - return pendingRequest.reject(new EthereumJsonRpcError(error.code, error.message, 'data' in error && typeof error.data === 'object' && error.data !== null ? error.data : undefined)) - } - // if the signer we are connected threw something besides an Error, wrap it up in an error - pendingRequest.reject(new EthereumJsonRpcError(METAMASK_ERROR_BLANKET_ERROR, 'Unexpected thrown value.', { error })) + return pendingRequest.reject(this.parseRpcError(error)) } } From aa6fec583e88e61bf701c341161e642d98280000 Mon Sep 17 00:00:00 2001 From: KillariDev Date: Thu, 25 Jul 2024 09:08:09 +0300 Subject: [PATCH 08/14] primary camera fix --- app/ts/background/background.ts | 3 ++- app/ts/background/settings.ts | 9 +++++---- app/ts/background/storageVariables.ts | 20 +++++++++++++++++--- app/ts/utils/storageUtils.ts | 6 ++++++ 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/app/ts/background/background.ts b/app/ts/background/background.ts index 18614d12..56914fa0 100644 --- a/app/ts/background/background.ts +++ b/app/ts/background/background.ts @@ -1,7 +1,7 @@ import { InpageScriptRequest, PopupMessage, RPCReply, Settings, SimulateExecutionReplyData } from '../types/interceptor-messages.js' import 'webextension-polyfill' import { Simulator, parseEvents, parseInputData, runProtectorsForTransaction } from '../simulation/simulator.js' -import { getSimulationResults, getTabState, getTransactionStack, setLatestUnexpectedError, updateSimulationResults, updateSimulationResultsWithCallBack, updateTransactionStack } from './storageVariables.js' +import { getSimulationResults, getTabState, getTransactionStack, promoteRpcAsPrimary, setLatestUnexpectedError, updateSimulationResults, updateSimulationResultsWithCallBack, updateTransactionStack } from './storageVariables.js' import { changeSimulationMode, getSettings, getWethForChainId } from './settings.js' import { blockNumber, call, chainId, estimateGas, gasPrice, getAccounts, getBalance, getBlockByNumber, getCode, getLogs, getPermissions, getSimulationStack, getTransactionByHash, getTransactionCount, getTransactionReceipt, netVersion, personalSign, sendTransaction, subscribe, switchEthereumChain, unsubscribe, web3ClientVersion, getBlockByHash, feeHistory, installNewFilter, uninstallNewFilter, getFilterChanges, getFilterLogs, handleIterceptorError } from './simulationModeHanders.js' import { changeActiveAddress, changeMakeMeRich, changePage, confirmDialog, refreshSimulation, removeTransactionOrSignedMessage, requestAccountsFromSigner, refreshPopupConfirmTransactionSimulation, confirmRequestAccess, changeInterceptorAccess, changeChainDialog, popupChangeActiveRpc, enableSimulationMode, addOrModifyAddressBookEntry, getAddressBookData, removeAddressBookEntry, refreshHomeData, interceptorAccessChangeAddressOrRefresh, refreshPopupConfirmTransactionMetadata, changeSettings, importSettings, exportSettings, setNewRpcList, simulateGovernanceContractExecutionOnPass, openNewTab, settingsOpened, changeAddOrModifyAddressWindowState, popupFetchAbiAndNameFromEtherscan, openWebPage, disableInterceptor, requestNewHomeData, setEnsNameForHash, simulateGnosisSafeTransactionOnPass } from './popupMessageHandlers.js' @@ -570,6 +570,7 @@ export async function changeActiveRpc(simulator: Simulator, websiteTabConnection }) sendMessageToApprovedWebsitePorts(websiteTabConnections, { method: 'request_signer_to_wallet_switchEthereumChain', result: rpcNetwork.chainId }) await sendPopupMessageToOpenWindows({ method: 'popup_settingsUpdated', data: await getSettings() }) + await promoteRpcAsPrimary(rpcNetwork) } function getProviderHandler(method: string) { diff --git a/app/ts/background/settings.ts b/app/ts/background/settings.ts index 459efb7e..8cbac975 100644 --- a/app/ts/background/settings.ts +++ b/app/ts/background/settings.ts @@ -5,7 +5,7 @@ import { Semaphore } from '../utils/semaphore.js' import { EthereumAddress } from '../types/wire-types.js' import { WebsiteAccessArray } from '../types/websiteAccessTypes.js' import { RpcNetwork } from '../types/rpc.js' -import { browserStorageLocalGet, browserStorageLocalSet } from '../utils/storageUtils.js' +import { browserStorageLocalGet, browserStorageLocalSafeParseGet, browserStorageLocalSet } from '../utils/storageUtils.js' import { getUserAddressBookEntries, updateUserAddressBookEntries } from './storageVariables.js' import { getUniqueItemsByProperties } from '../utils/typed-arrays.js' import { AddressBookEntries, AddressBookEntry } from '../types/addressBookTypes.js' @@ -92,21 +92,22 @@ const wethForChainId = new Map([ export const getWethForChainId = (chainId: bigint) => wethForChainId.get(chainId.toString()) export async function getSettings() : Promise { - const results = await browserStorageLocalGet([ + const resultsPromise = browserStorageLocalGet([ 'activeSimulationAddress', 'openedPageV2', 'useSignersAddressAsActiveAddress', 'websiteAccess', - 'currentRpcNetwork', 'simulationMode', ]) + const currentRpcNetwork = await browserStorageLocalSafeParseGet('currentRpcNetwork') + const results = await resultsPromise if (defaultRpcs[0] === undefined || defaultActiveAddresses[0] === undefined) throw new Error('default rpc or default address was missing') return { activeSimulationAddress: 'activeSimulationAddress' in results ? results.activeSimulationAddress : defaultActiveAddresses[0].address, openedPage: results.openedPageV2 ?? { page: 'Home' }, useSignersAddressAsActiveAddress: results.useSignersAddressAsActiveAddress ?? false, websiteAccess: results.websiteAccess ?? [], - currentRpcNetwork: results.currentRpcNetwork !== undefined ? results.currentRpcNetwork : defaultRpcs[0], + currentRpcNetwork: currentRpcNetwork?.currentRpcNetwork !== undefined ? currentRpcNetwork.currentRpcNetwork : defaultRpcs[0], simulationMode: results.simulationMode ?? true, } } diff --git a/app/ts/background/storageVariables.ts b/app/ts/background/storageVariables.ts index 185f6631..5bda8c3e 100644 --- a/app/ts/background/storageVariables.ts +++ b/app/ts/background/storageVariables.ts @@ -14,6 +14,7 @@ import { UnexpectedErrorOccured } from '../types/interceptor-messages.js' import { namehash } from 'ethers' import { bytesToUnsigned } from '../utils/bigint.js' import { keccak_256 } from '@noble/hashes/sha3' +import { modifyObject } from '../utils/typescript.js' export const getIdsOfOpenedTabs = async () => (await browserStorageLocalGet('idsOfOpenedTabs'))?.idsOfOpenedTabs ?? { settingsView: undefined, addressBook: undefined} export const setIdsOfOpenedTabs = async (ids: PartialIdsOfOpenedTabs) => await browserStorageLocalSet({ idsOfOpenedTabs: { ...await getIdsOfOpenedTabs(), ...ids } }) @@ -213,14 +214,25 @@ export const setInterceptorStartSleepingTimestamp = async(interceptorStartSleepi export const getInterceptorStartSleepingTimestamp = async () => (await browserStorageLocalGet('interceptorStartSleepingTimestamp'))?.interceptorStartSleepingTimestamp ?? 0 +export const promoteRpcAsPrimary = async (rpcNetwork: RpcNetwork) => { + if (rpcNetwork.primary) return + const rpcs = await getRpcList() + await setRpcList(rpcs.map((rpc) => rpc.chainId === rpcNetwork.chainId ? modifyObject(rpc, { primary: rpc.httpsRpc === rpcNetwork.httpsRpc }) : rpc)) +} + export const getPrimaryRpcForChain = async (chainId: bigint) => { const rpcs = await getRpcList() - return rpcs.find((rpc) => rpc.chainId === chainId && rpc.primary) + const primary = rpcs.find((rpc) => rpc.chainId === chainId && rpc.primary) + if (primary) return primary + + // no primary was found, try to find what ever we have for that chain id + const nonPrimary = rpcs.find((rpc) => rpc.chainId === chainId) + if (nonPrimary) return nonPrimary + return undefined } export const getRpcNetworkForChain = async (chainId: bigint): Promise => { - const rpcs = await getRpcList() - const rpc = rpcs.find((rpc) => rpc.chainId === chainId && rpc.primary) + const rpc = await getPrimaryRpcForChain(chainId) if (rpc !== undefined) return rpc return { chainId: chainId, @@ -228,6 +240,8 @@ export const getRpcNetworkForChain = async (chainId: bigint): Promise (await browserStorageLocalGet('userAddressBookEntriesV2'))?.userAddressBookEntriesV2 ?? [] diff --git a/app/ts/utils/storageUtils.ts b/app/ts/utils/storageUtils.ts index 3a302ffa..722806ce 100644 --- a/app/ts/utils/storageUtils.ts +++ b/app/ts/utils/storageUtils.ts @@ -117,6 +117,12 @@ export async function browserStorageLocalSet2(items: LocalStorageItems2) { export async function browserStorageLocalGet(keys: LocalStorageKey | LocalStorageKey[]): Promise { return LocalStorageItems.parse(await browser.storage.local.get(Array.isArray(keys) ? keys : [keys])) } +export async function browserStorageLocalSafeParseGet(keys: LocalStorageKey | LocalStorageKey[]): Promise { + const parsed = LocalStorageItems.safeParse(await browser.storage.local.get(Array.isArray(keys) ? keys : [keys])) + if (parsed.success) return parsed.value + return undefined +} + export async function browserStorageLocalRemove(keys: LocalStorageKey | LocalStorageKey[]) { return await browser.storage.local.remove(Array.isArray(keys) ? keys : [keys]) } From ecc2938d211fb46391484f264b3f4b384c372bcc Mon Sep 17 00:00:00 2001 From: KillariDev Date: Thu, 25 Jul 2024 10:34:12 +0300 Subject: [PATCH 09/14] types --- app/ts/types/rpc.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/ts/types/rpc.ts b/app/ts/types/rpc.ts index 0f8530ca..17427814 100644 --- a/app/ts/types/rpc.ts +++ b/app/ts/types/rpc.ts @@ -29,6 +29,8 @@ export const RpcNetwork = funtypes.Union( name: funtypes.String, currencyName: funtypes.Literal('Ether?'), currencyTicker: funtypes.Literal('ETH?'), + primary: funtypes.Literal(false), + minimized: funtypes.Literal(true), }) ) From e5411d15f936225416bed9813042c8a228a0aab0 Mon Sep 17 00:00:00 2001 From: KillariDev Date: Thu, 25 Jul 2024 11:01:51 +0300 Subject: [PATCH 10/14] remove font size constant --- app/css/interceptor.css | 5 ++++- .../customExplainers/GovernanceVoteVisualizer.tsx | 3 +-- app/ts/components/subcomponents/coins.tsx | 12 ++++++------ app/ts/utils/constants.ts | 3 --- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/css/interceptor.css b/app/css/interceptor.css index fc61f762..772d4a14 100644 --- a/app/css/interceptor.css +++ b/app/css/interceptor.css @@ -53,7 +53,10 @@ body { font-family: "Inter" !important; } --alpha-005: rgba(255, 255, 255, 0.05); --alpha-015: rgba(255, 255, 255, 0.15); - --modal-background-color: rgba(10, 10, 10, .7) + --modal-background-color: rgba(10, 10, 10, .7); + + --big-font-size: 28px; + --normal-font-size: 14px; } button:where(:not(.btn)) { diff --git a/app/ts/components/simulationExplaining/customExplainers/GovernanceVoteVisualizer.tsx b/app/ts/components/simulationExplaining/customExplainers/GovernanceVoteVisualizer.tsx index 957d0e1c..33e5d90e 100644 --- a/app/ts/components/simulationExplaining/customExplainers/GovernanceVoteVisualizer.tsx +++ b/app/ts/components/simulationExplaining/customExplainers/GovernanceVoteVisualizer.tsx @@ -5,7 +5,6 @@ import { RenameAddressCallBack, RpcConnectionStatus } from '../../../types/user- import { SimulatedAndVisualizedTransaction } from '../../../types/visualizer-types.js' import { EthereumQuantity } from '../../../types/wire-types.js' import { checksummedAddress, dataStringWith0xStart } from '../../../utils/bigint.js' -import { BIG_FONT_SIZE } from '../../../utils/constants.js' import { ErrorComponent } from '../../subcomponents/Error.js' import { EditEnsNamedHashCallBack } from '../../subcomponents/ens.js' import { CellElement } from '../../ui-utils.js' @@ -43,7 +42,7 @@ function VotePanel({ inputParams }: { inputParams: GovernanceVoteInputParameters return <>
-

+

Vote { interpretSupport(inputParams.support) } {`for proposal: ${ inputParams.proposalId } `}

diff --git a/app/ts/components/subcomponents/coins.tsx b/app/ts/components/subcomponents/coins.tsx index c191dcff..ca0ac1d9 100644 --- a/app/ts/components/subcomponents/coins.tsx +++ b/app/ts/components/subcomponents/coins.tsx @@ -7,7 +7,7 @@ import { JSX } from 'preact/jsx-runtime' import { useEffect } from 'preact/hooks' import { Erc1155Entry, Erc20TokenEntry, Erc721Entry } from '../../types/addressBookTypes.js' import { RenameAddressCallBack } from '../../types/user-interface-types.js' -import { BIG_FONT_SIZE, ETHEREUM_COIN_ICON, ETHEREUM_LOGS_LOGGER_ADDRESS, NORMAL_FONT_SIZE } from '../../utils/constants.js' +import { ETHEREUM_COIN_ICON, ETHEREUM_LOGS_LOGGER_ADDRESS } from '../../utils/constants.js' import { RpcNetwork } from '../../types/rpc.js' import { Blockie } from './SVGBlockie.js' import { AbbreviatedValue } from './AbbreviatedValue.js' @@ -46,7 +46,7 @@ export function EtherAmount(param: EtherAmountParams) { 'text-overflow': 'ellipsis', color: 'var(--text-color)', ...(param.style === undefined ? {} : param.style), - 'font-size': param.fontSize === 'big' ? BIG_FONT_SIZE : NORMAL_FONT_SIZE + 'font-size': param.fontSize === 'big' ? 'var(--big-font-size)' : 'var(--normal-font-size)' } return <> @@ -71,7 +71,7 @@ export function EtherSymbol(param: EtherSymbolParams) { overflow: 'hidden', 'text-overflow': 'ellipsis', ...(param.style === undefined ? {} : param.style), - 'font-size': param.fontSize === 'big' ? BIG_FONT_SIZE : NORMAL_FONT_SIZE + 'font-size': param.fontSize === 'big' ? 'var(--big-font-size)' : 'var(--normal-font-size)' } const etheName = param.useFullTokenName ? param.rpcNetwork.currencyName : param.rpcNetwork.currencyTicker @@ -145,7 +145,7 @@ export function TokenSymbol(param: TokenSymbolParams) { color: 'var(--text-color)', ...(param.style === undefined ? {} : param.style), ...unTrusted ? { color: 'var(--warning-color)' } : {}, - 'font-size': param.fontSize === 'big' ? BIG_FONT_SIZE : NORMAL_FONT_SIZE + 'font-size': param.fontSize === 'big' ? 'var(--big-font-size)' : 'var(--normal-font-size)' } const name = param.useFullTokenName ? param.tokenEntry.name : param.tokenEntry.symbol @@ -191,7 +191,7 @@ export function TokenAmount(param: TokenAmountParams) { display: 'inline-flex', 'align-items': 'center', ...(param.style === undefined ? {} : param.style), - 'font-size': param.fontSize === 'big' ? BIG_FONT_SIZE : NORMAL_FONT_SIZE + 'font-size': param.fontSize === 'big' ? 'var(--big-font-size)' : 'var(--normal-font-size)' } if (!('decimals' in param.tokenEntry) || param.tokenEntry.decimals === undefined) { @@ -262,7 +262,7 @@ export function AllApproval(param: AllApprovalParams ) { const style = { color: 'var(--text-color)', ...(param.style === undefined ? {} : param.style), - 'font-size': param.fontSize === 'big' ? BIG_FONT_SIZE : NORMAL_FONT_SIZE + 'font-size': param.fontSize === 'big' ? 'var(--big-font-size)' : 'var(--normal-font-size)' } if (!param.allApprovalAdded) return

NONE

return

ALL

diff --git a/app/ts/utils/constants.ts b/app/ts/utils/constants.ts index e738f892..dbce3929 100644 --- a/app/ts/utils/constants.ts +++ b/app/ts/utils/constants.ts @@ -172,9 +172,6 @@ export const PRIMARY_COLOR = '#58a5b3' export const CANNOT_SIMULATE_OFF_LEGACY_BLOCK = 'Cannot simulate off a legacy block' -export const BIG_FONT_SIZE = '28px' -export const NORMAL_FONT_SIZE = '14px' - export const NEW_BLOCK_ABORT = 'New Block Abort' export const MAKE_YOU_RICH_TRANSACTION = { From 7999085c06e706515e8cd8dfc27f17ddd916e0cc Mon Sep 17 00:00:00 2001 From: KillariDev Date: Thu, 25 Jul 2024 17:49:42 +0300 Subject: [PATCH 11/14] ethSimulateV1ParamObject -> EthSimulateV1ParamObject --- app/ts/types/ethSimulate-types.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/ts/types/ethSimulate-types.ts b/app/ts/types/ethSimulate-types.ts index 65845d26..dac69c10 100644 --- a/app/ts/types/ethSimulate-types.ts +++ b/app/ts/types/ethSimulate-types.ts @@ -56,8 +56,8 @@ export const BlockCalls = funtypes.Intersect( }) ) -export type ethSimulateV1ParamObject = funtypes.Static -const ethSimulateV1ParamObject = funtypes.ReadonlyObject({ +export type EthSimulateV1ParamObject = funtypes.Static +const EthSimulateV1ParamObject = funtypes.ReadonlyObject({ blockStateCalls: funtypes.ReadonlyArray(BlockCalls), traceTransfers: funtypes.Boolean, validation: funtypes.Boolean, @@ -66,7 +66,7 @@ const ethSimulateV1ParamObject = funtypes.ReadonlyObject({ export type EthSimulateV1Params = funtypes.Static export const EthSimulateV1Params = funtypes.ReadonlyObject({ method: funtypes.Literal('eth_simulateV1'), - params: funtypes.ReadonlyTuple(ethSimulateV1ParamObject, EthereumBlockTag), + params: funtypes.ReadonlyTuple(EthSimulateV1ParamObject, EthereumBlockTag), }) export type EthereumEvent = funtypes.Static From ff6a430f9ec401c317dfce619551db28d21447a2 Mon Sep 17 00:00:00 2001 From: KillariDev Date: Fri, 26 Jul 2024 10:50:53 +0300 Subject: [PATCH 12/14] retrieveEnsNodeHashes refactor --- app/ts/background/metadataUtils.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/ts/background/metadataUtils.ts b/app/ts/background/metadataUtils.ts index 680423ce..8b2c678f 100644 --- a/app/ts/background/metadataUtils.ts +++ b/app/ts/background/metadataUtils.ts @@ -209,10 +209,13 @@ export const extractEnsEvents = (events: readonly EnrichedEthereumEventWithMetad } export async function retrieveEnsNodeHashes(ethereumClientService: EthereumClientService, events: EnrichedEthereumEvents, addressBookEntriesToMatchReverseResolutions: readonly AddressBookEntry[]) { - const hashes = events.map((event) => 'logInformation' in event && 'node' in event.logInformation ? event.logInformation.node : undefined).filter((maybeNodeHash): maybeNodeHash is bigint => maybeNodeHash !== undefined) - const deduplicatedHashes = Array.from(new Set(hashes)) + const hashes = new Set() + for (const event of events) { + if (!('logInformation' in event && 'node' in event.logInformation)) continue + hashes.add(event.logInformation.node) + } const reverseEnsLabelHashes = addressBookEntriesToMatchReverseResolutions.map((addressBookEntry) => getEnsReverseNodeHash(addressBookEntry.address)) - return await Promise.all(deduplicatedHashes.map((hash) => getAndCacheEnsNodeHash(ethereumClientService, hash, reverseEnsLabelHashes))) + return Promise.all([...hashes].map((hash) => getAndCacheEnsNodeHash(ethereumClientService, hash, reverseEnsLabelHashes))) } export async function retrieveEnsLabelHashes(events: EnrichedEthereumEvents, addressBookEntriesToMatchReverseResolutions: readonly AddressBookEntry[]) { From 9437b8c4e4174abd1914ade55dae4984b18f1dfa Mon Sep 17 00:00:00 2001 From: KillariDev Date: Fri, 26 Jul 2024 11:04:35 +0300 Subject: [PATCH 13/14] change wording to be about the transaction and not about the user --- app/ts/simulation/protectors/sendToNonContactAddress.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ts/simulation/protectors/sendToNonContactAddress.ts b/app/ts/simulation/protectors/sendToNonContactAddress.ts index ddb68db4..1f8439d2 100644 --- a/app/ts/simulation/protectors/sendToNonContactAddress.ts +++ b/app/ts/simulation/protectors/sendToNonContactAddress.ts @@ -8,7 +8,7 @@ export async function sendToNonContact(transaction: EthereumUnsignedTransaction, async function checkSendToAddress(to: EthereumAddress) { const sendingTo = await identifyAddress(ethereum, requestAbortController, to) if (sendingTo.entrySource !== 'OnChain') return - return `You are about to send funds to "${ sendingTo.name }", which is not in your addressbook. Please add the address to addressbook to dismiss this error in the future.` + return `This transaction sends funds to "${ sendingTo.name }", which is not in the addressbook. Please add the address to addressbook to dismiss this error in the future.` } const transferInfo = parseTransaction(transaction) From b90ba3ddc6459f5db43f7d444478720792edec50 Mon Sep 17 00:00:00 2001 From: KillariDev <13102010+KillariDev@users.noreply.github.com> Date: Mon, 29 Jul 2024 13:52:05 +0300 Subject: [PATCH 14/14] Update app/inpage/ts/inpage.ts Co-authored-by: Jubal Mabaquiao --- app/inpage/ts/inpage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/inpage/ts/inpage.ts b/app/inpage/ts/inpage.ts index c4efc83b..4323b62a 100644 --- a/app/inpage/ts/inpage.ts +++ b/app/inpage/ts/inpage.ts @@ -497,7 +497,7 @@ class InterceptorMessageListener { } private parseRpcError = (maybeErrorObject: unknown) => { - if (typeof maybeErrorObject !== 'object' || maybeErrorObject === null) return new EthereumJsonRpcError(METAMASK_ERROR_BLANKET_ERROR, 'Unexpected thrown value.', { maybeErrorObject } ) + if (typeof maybeErrorObject !== 'object' || maybeErrorObject === null) return new EthereumJsonRpcError(METAMASK_ERROR_BLANKET_ERROR, 'Unexpected thrown value.', { rawError: maybeErrorObject } ) if ('code' in maybeErrorObject && maybeErrorObject.code !== undefined && typeof maybeErrorObject.code === 'number' && 'message' in maybeErrorObject && maybeErrorObject.message !== undefined && typeof maybeErrorObject.message === 'string'