diff --git a/src/tracing/errors.test.ts b/src/tracing/errors.test.ts index 4ee1201c71..f40098f89f 100644 --- a/src/tracing/errors.test.ts +++ b/src/tracing/errors.test.ts @@ -79,7 +79,7 @@ describe('beforeSend', () => { describe('filters browser extension errors', () => { it('filters chrome-extension errors', () => { const originalException = new Error() - originalException.stack = ` + originalException.stack = ` TypeError: Cannot create proxy with a non-object as target or handler at pa(chrome-extension://kbjhmlgclljgdhmhffjofbobmficicjp/proxy-window-evm.a5430696.js:22:216604) at da(chrome-extension://kbjhmlgclljgdhmhffjofbobmficicjp/proxy-window-evm.a5430696.js:22:212968) @@ -90,13 +90,25 @@ describe('beforeSend', () => { it('filters moz-extension errors', () => { const originalException = new Error() - originalException.stack = ` + originalException.stack = ` Error: Permission denied to access property "apply" at WINDOW.onunhandledrejection(../node_modules/@sentry/src/instrument.ts:610:1) at y/h.onunhandledrejection(moz-extension://95cafb7b-6038-4bdd-b832-d3a58544601d/content_script/inpage_sol.js:143:16274) ` expect(beforeSend(ERROR, { originalException })).toBeNull() }) + + it('filters non-Error objects thrown from an extension', () => { + const originalException = { + message: '', + stack: ` + Error: Permission denied to access property "apply" + at WINDOW.onunhandledrejection(../node_modules/@sentry/src/instrument.ts:610:1) + at y/h.onunhandledrejection(moz-extension://95cafb7b-6038-4bdd-b832-d3a58544601d/content_script/inpage_sol.js:143:16274) + `, + } + expect(beforeSend(ERROR, { originalException })).toBeNull() + }) }) describe('OneKey', () => { diff --git a/src/tracing/errors.ts b/src/tracing/errors.ts index a1035c2880..65c4c2b81c 100644 --- a/src/tracing/errors.ts +++ b/src/tracing/errors.ts @@ -23,9 +23,14 @@ export const beforeSend: Required['beforeSend'] = (event: ErrorEv return event } +type ErrorLike = Partial & Required> +function isErrorLike(error: unknown): error is ErrorLike { + return error instanceof Object && 'message' in error && typeof (error as Partial)?.message === 'string' +} + /** Identifies ethers request errors (as thrown by {@type import(@ethersproject/web).fetchJson}). */ -function isEthersRequestError(error: Error): error is Error & { requestBody: string } { - return 'requestBody' in error && typeof (error as unknown as Record<'requestBody', unknown>).requestBody === 'string' +function isEthersRequestErrorLike(error: ErrorLike): error is ErrorLike & { requestBody: string } { + return 'requestBody' in error && typeof (error as Record<'requestBody', unknown>).requestBody === 'string' } // Since the interface currently uses HashRouter, URLs will have a # before the path. @@ -44,11 +49,12 @@ function updateRequestUrl(event: ErrorEvent) { // TODO(WEB-2400): Refactor to use a config instead of returning true for each condition. function shouldRejectError(error: EventHint['originalException']) { - if (error instanceof Error) { - // ethers aggressively polls for block number, and it sometimes fails (whether spuriously or through rate-limiting). - // If block number polling, it should not be considered an exception. - if (isEthersRequestError(error)) { + // Some libraries throw ErrorLike objects ({ code, message, stack }) instead of true Errors. + if (isErrorLike(error)) { + if (isEthersRequestErrorLike(error)) { const method = JSON.parse(error.requestBody).method + // ethers aggressively polls for block number, and it sometimes fails (whether spuriously or through rate-limiting). + // If block number polling, it should not be considered an exception. if (method === 'eth_blockNumber') return true } @@ -64,14 +70,6 @@ function shouldRejectError(error: EventHint['originalException']) { // Therefore, this can be ignored. if (error.message.match(/Unexpected token '<'/)) return true - // Errors coming from a browser extension can be ignored. These errors are usually caused by extensions injecting - // scripts into the page, which we cannot control. - if (error.stack?.match(/-extension:\/\//i)) return true - - // Errors coming from OneKey (a desktop wallet) can be ignored for now. - // These errors are either application-specific, or they will be thrown separately outside of OneKey. - if (error.stack?.match(/OneKey/i)) return true - // Content security policy 'unsafe-eval' errors can be filtered out because there are expected failures. // For example, if a user runs an eval statement in console this error would still get thrown. // TODO(WEB-2348): We should extend this to filter out any type of CSP error. @@ -92,8 +90,18 @@ function shouldRejectError(error: EventHint['originalException']) { return true } + if ('stack' in error && typeof error.stack === 'string') { + // Errors coming from a browser extension can be ignored. These errors are usually caused by extensions injecting + // scripts into the page, which we cannot control. + if (error.stack.match(/-extension:\/\//i)) return true + + // Errors coming from OneKey (a desktop wallet) can be ignored for now. + // These errors are either application-specific, or they will be thrown separately outside of OneKey. + if (error.stack.match(/OneKey/i)) return true + } + // These are caused by user navigation away from the page before a request has finished. - if (error instanceof DOMException && error.name === 'AbortError') return true + if (error instanceof DOMException && error?.name === 'AbortError') return true } return false