fix: duck type error checking (#6924)

* fix: duck type error checking

* fix: use duck-typing

* simplify

* fix typings

* fix comments
This commit is contained in:
Zach Pomerantz 2023-07-18 09:21:00 -07:00 committed by GitHub
parent 252bf32d2f
commit 0ced5f2402
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 17 deletions

@ -79,7 +79,7 @@ describe('beforeSend', () => {
describe('filters browser extension errors', () => { describe('filters browser extension errors', () => {
it('filters chrome-extension errors', () => { it('filters chrome-extension errors', () => {
const originalException = new Error() const originalException = new Error()
originalException.stack = ` originalException.stack = `
TypeError: Cannot create proxy with a non-object as target or handler 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 pa(chrome-extension://kbjhmlgclljgdhmhffjofbobmficicjp/proxy-window-evm.a5430696.js:22:216604)
at da(chrome-extension://kbjhmlgclljgdhmhffjofbobmficicjp/proxy-window-evm.a5430696.js:22:212968) at da(chrome-extension://kbjhmlgclljgdhmhffjofbobmficicjp/proxy-window-evm.a5430696.js:22:212968)
@ -90,13 +90,25 @@ describe('beforeSend', () => {
it('filters moz-extension errors', () => { it('filters moz-extension errors', () => {
const originalException = new Error() const originalException = new Error()
originalException.stack = ` originalException.stack = `
Error: Permission denied to access property "apply" Error: Permission denied to access property "apply"
at WINDOW.onunhandledrejection(../node_modules/@sentry/src/instrument.ts:610:1) 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) at y/h.onunhandledrejection(moz-extension://95cafb7b-6038-4bdd-b832-d3a58544601d/content_script/inpage_sol.js:143:16274)
` `
expect(beforeSend(ERROR, { originalException })).toBeNull() 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', () => { describe('OneKey', () => {

@ -23,9 +23,14 @@ export const beforeSend: Required<ClientOptions>['beforeSend'] = (event: ErrorEv
return event return event
} }
type ErrorLike = Partial<Error> & Required<Pick<Error, 'message'>>
function isErrorLike(error: unknown): error is ErrorLike {
return error instanceof Object && 'message' in error && typeof (error as Partial<ErrorLike>)?.message === 'string'
}
/** Identifies ethers request errors (as thrown by {@type import(@ethersproject/web).fetchJson}). */ /** Identifies ethers request errors (as thrown by {@type import(@ethersproject/web).fetchJson}). */
function isEthersRequestError(error: Error): error is Error & { requestBody: string } { function isEthersRequestErrorLike(error: ErrorLike): error is ErrorLike & { requestBody: string } {
return 'requestBody' in error && typeof (error as unknown as Record<'requestBody', unknown>).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. // 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. // TODO(WEB-2400): Refactor to use a config instead of returning true for each condition.
function shouldRejectError(error: EventHint['originalException']) { function shouldRejectError(error: EventHint['originalException']) {
if (error instanceof Error) { // Some libraries throw ErrorLike objects ({ code, message, stack }) instead of true Errors.
// ethers aggressively polls for block number, and it sometimes fails (whether spuriously or through rate-limiting). if (isErrorLike(error)) {
// If block number polling, it should not be considered an exception. if (isEthersRequestErrorLike(error)) {
if (isEthersRequestError(error)) {
const method = JSON.parse(error.requestBody).method 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 if (method === 'eth_blockNumber') return true
} }
@ -64,14 +70,6 @@ function shouldRejectError(error: EventHint['originalException']) {
// Therefore, this can be ignored. // Therefore, this can be ignored.
if (error.message.match(/Unexpected token '<'/)) return true 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. // 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. // 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. // 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 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. // 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 return false