-
Notifications
You must be signed in to change notification settings - Fork 4
fix: preserve content when promise is rejected with plain object #172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
28eb7b3
033bdfb
e2f8a61
47c6967
ad0313a
c335586
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| import Sanitizer from '../modules/sanitizer'; | ||
|
|
||
| /** | ||
| * Extracts and normalizes error from ErrorEvent or PromiseRejectionEvent | ||
| * | ||
| * @param event - The error or promise rejection event | ||
| */ | ||
| export function getErrorFromErrorEvent(event: ErrorEvent | PromiseRejectionEvent): Error | string { | ||
| /** | ||
| * Promise rejection reason is recommended to be an Error, but it can be a string: | ||
| * - Promise.reject(new Error('Reason message')) ——— recommended | ||
| * - Promise.reject('Reason message') | ||
| */ | ||
| let error = (event as ErrorEvent).error || (event as PromiseRejectionEvent).reason; | ||
|
|
||
| /** | ||
| * Case when error triggered in external script | ||
| * We can't access event error object because of CORS | ||
| * Event message will be 'Script error.' | ||
| */ | ||
| if (event instanceof ErrorEvent && error === undefined) { | ||
| error = (event as ErrorEvent).message; | ||
| } | ||
|
|
||
| /** | ||
| * Case when error rejected with an object | ||
| * Using a string instead of wrapping in Error is more natural, | ||
| * it doesn't fake the backtrace, also prefix added for dashboard readability | ||
| */ | ||
| if (error instanceof Object && !(error instanceof Error) && event instanceof PromiseRejectionEvent) { | ||
| // Extra sanitize is needed to handle objects with circular references before JSON.stringify | ||
| error = `Promise rejected with ${JSON.stringify(Sanitizer.sanitize(error))}`; | ||
| } | ||
|
|
||
| return Sanitizer.sanitize(error); | ||
| } | ||
|
|
||
| /** | ||
| * Converts a promise rejection reason to a string message. | ||
| * | ||
| * String(obj) gives "[object Object]" and JSON.stringify("str") | ||
| * adds unwanted quotes. | ||
| * | ||
| * @param reason - The rejection reason from PromiseRejectionEvent | ||
| */ | ||
| export function stringifyRejectionReason(reason: unknown): string { | ||
| if (reason instanceof Error) { | ||
| return reason.message; | ||
| } | ||
| if (typeof reason === 'string') { | ||
| return reason; | ||
| } | ||
|
|
||
| return JSON.stringify(Sanitizer.sanitize(reason)); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| import { describe, it, expect, vi, beforeEach } from 'vitest'; | ||
| import { getErrorFromErrorEvent } from '../../src/utils/error'; | ||
|
|
||
| vi.mock('@hawk.so/core', () => ({ log: vi.fn(), isLoggerSet: vi.fn(() => true), setLogger: vi.fn() })); | ||
|
|
||
| import Sanitizer from '../../src/modules/sanitizer'; | ||
|
|
||
| describe('getErrorFromErrorEvent', () => { | ||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| describe('ErrorEvent', () => { | ||
| it('should return the Error when event.error is an Error instance', () => { | ||
| const error = new Error('Test error'); | ||
| const event = new ErrorEvent('error', { error }); | ||
|
|
||
| const result = getErrorFromErrorEvent(event); | ||
|
|
||
| expect(result).toBe(error); | ||
| }); | ||
|
|
||
| it('should return the DOMException when event.error is a DOMException', () => { | ||
| const error = new DOMException('Network error', 'NetworkError'); | ||
| const event = new ErrorEvent('error', { error }); | ||
|
|
||
| const result = getErrorFromErrorEvent(event); | ||
|
|
||
| expect(result).toBe('<instance of DOMException>'); | ||
| }); | ||
|
|
||
| it('should return the message when event.error is not provided and message is a string', () => { | ||
| const event = new ErrorEvent('error', { message: 'Script error.' }); | ||
|
|
||
| const result = getErrorFromErrorEvent(event); | ||
|
|
||
| expect(result).toBe('Script error.'); | ||
| }); | ||
|
|
||
| it('should return empty string when event.error is not provided and message is empty', () => { | ||
| const event = new ErrorEvent('error', { message: '' }); | ||
|
|
||
| const result = getErrorFromErrorEvent(event); | ||
|
|
||
| expect(result).toBe(''); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not the best UX |
||
| }); | ||
| }); | ||
|
|
||
| describe('PromiseRejectionEvent', () => { | ||
| it('should return the Error when event.reason is an Error instance', () => { | ||
| const reason = new Error('Promise rejected'); | ||
| const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason }); | ||
|
|
||
| const result = getErrorFromErrorEvent(event); | ||
|
|
||
| expect(result).toBe(reason); | ||
| }); | ||
|
|
||
| it('should return the string when event.reason is a string', () => { | ||
| const reason = 'Promise rejected with string'; | ||
| const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason }); | ||
|
|
||
| const result = getErrorFromErrorEvent(event); | ||
|
|
||
| expect(result).toBe(reason); | ||
| }); | ||
|
|
||
| it('should return stringified object when event.reason is a plain object', () => { | ||
| const reason = { code: 'ERR_001', details: 'Something went wrong' }; | ||
| const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason }); | ||
|
|
||
| const result = getErrorFromErrorEvent(event); | ||
|
|
||
| expect(result).toBe('Promise rejected with {"code":"ERR_001","details":"Something went wrong"}'); | ||
| }); | ||
|
|
||
| it('should return undefined when event.reason is not provided', () => { | ||
| const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason: undefined }); | ||
|
|
||
| const result = getErrorFromErrorEvent(event); | ||
|
|
||
| expect(result).toBeUndefined(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not the best UX
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. method does not return null |
||
| }); | ||
|
|
||
| it('should return null when event.reason is null', () => { | ||
| const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason: null }); | ||
|
|
||
| const result = getErrorFromErrorEvent(event); | ||
|
|
||
| expect(result).toBeNull(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. method does not return null |
||
| }); | ||
|
|
||
| it('should handle circular references in object reason', () => { | ||
| const circularObj: Record<string, unknown> = { name: 'test' }; | ||
| circularObj.self = circularObj; | ||
|
|
||
| const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason: circularObj }); | ||
|
|
||
| const result = getErrorFromErrorEvent(event); | ||
|
|
||
| expect(result).toContain('Promise rejected with'); | ||
| expect(result).toContain('<circular>'); | ||
| }); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
describe cases when it returns string and when Error