Skip to content

Commit

Permalink
Merge branch 'main' into fix-fuses
Browse files Browse the repository at this point in the history
  • Loading branch information
KillariDev authored Aug 7, 2024
2 parents 2dec257 + 370cb5f commit a89fc5e
Show file tree
Hide file tree
Showing 25 changed files with 235 additions and 123 deletions.
5 changes: 4 additions & 1 deletion app/css/interceptor.css
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
74 changes: 52 additions & 22 deletions app/inpage/ts/document_start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

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<any>) => {
globalThis.addEventListener('message', (messageEvent: MessageEvent<any>) => {
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
Expand All @@ -39,43 +45,67 @@ 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) {
console.error(error)
}
})

extensionPort.onDisconnect.addListener(() => {
try {
globalThis.removeEventListener('message', listener)
listenInContentScript(connectionNameNotUndefined)
if (pageHidden) connect()
checkAndThrowRuntimeLastError()
} catch (error) {
pageHidden = false
} catch (error: unknown) {
console.error(error)
}
})
}
globalThis.addEventListener('pageshow', () => bfCachePageShow(), false)
globalThis.addEventListener('pagehide', () => { pageHidden = true }, false)

try {
checkAndThrowRuntimeLastError()
} catch (error: unknown) {
console.error(error)
}
}


try {
const container = document.head || document.documentElement
const scriptTag = document.createElement('script')
scriptTag.setAttribute('async', 'false')
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)
Expand Down
48 changes: 29 additions & 19 deletions app/inpage/ts/inpage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.', { rawError: 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 (
Expand Down Expand Up @@ -553,24 +564,30 @@ 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
}
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)
Expand All @@ -581,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))
}
}

Expand Down
63 changes: 39 additions & 24 deletions app/inpage/ts/listenContentScript.ts
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -17,12 +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
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<any>) => {
globalThis.addEventListener('message', (messageEvent: MessageEvent<any>) => {
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
Expand All @@ -39,34 +41,47 @@ 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) {
console.error(error)
}
})

extensionPort.onDisconnect.addListener(() => {
try {
globalThis.removeEventListener('message', listener)
listenContentScript(connectionNameNotUndefined)
if (pageHidden) connect()
checkAndThrowRuntimeLastError()
pageHidden = false
} catch (error: unknown) {
console.error(error)
}
})
}
globalThis.addEventListener('pageshow', () => bfCachePageShow(), false)
globalThis.addEventListener('pagehide', () => { pageHidden = true }, false)

try {
checkAndThrowRuntimeLastError()
} catch (error: unknown) {
Expand Down
31 changes: 22 additions & 9 deletions app/ts/background/background-startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@ const catchAllErrorsAndCall = async (func: () => Promise<unknown>) => {
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)
}
}
Expand Down Expand Up @@ -69,13 +74,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)
Expand All @@ -85,6 +85,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) => {
Expand Down Expand Up @@ -142,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()
Expand All @@ -168,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()
Expand Down Expand Up @@ -196,7 +210,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)
Expand Down
Loading

0 comments on commit a89fc5e

Please sign in to comment.