Skip to content

Commit c335586

Browse files
committed
fix: extract error utilities
1 parent ad0313a commit c335586

File tree

4 files changed

+70
-69
lines changed

4 files changed

+70
-69
lines changed

packages/javascript/src/catcher.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import type {
1414
} from '@hawk.so/types';
1515
import type { JavaScriptCatcherIntegrations } from './types/integrations';
1616
import { EventRejectedError } from './errors';
17-
import { isErrorProcessed, markErrorAsProcessed, getErrorFromEvent } from './utils/event';
17+
import { isErrorProcessed, markErrorAsProcessed } from './utils/event';
18+
import { getErrorFromErrorEvent } from './utils/error';
1819
import { BrowserRandomGenerator } from './utils/random';
1920
import { ConsoleCatcher } from './addons/consoleCatcher';
2021
import { BreadcrumbManager } from './addons/breadcrumbs';
@@ -331,7 +332,7 @@ export default class Catcher {
331332
this.consoleCatcher!.addErrorEvent(event);
332333
}
333334

334-
const error = getErrorFromEvent(event);
335+
const error = getErrorFromErrorEvent(event);
335336

336337
void this.formatAndSend(error);
337338
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import Sanitizer from '../modules/sanitizer';
2+
3+
/**
4+
* Extracts and normalizes error from ErrorEvent or PromiseRejectionEvent
5+
*
6+
* @param event - The error or promise rejection event
7+
*/
8+
export function getErrorFromErrorEvent(event: ErrorEvent | PromiseRejectionEvent): Error | string {
9+
/**
10+
* Promise rejection reason is recommended to be an Error, but it can be a string:
11+
* - Promise.reject(new Error('Reason message')) ——— recommended
12+
* - Promise.reject('Reason message')
13+
*/
14+
let error = (event as ErrorEvent).error || (event as PromiseRejectionEvent).reason;
15+
16+
/**
17+
* Case when error triggered in external script
18+
* We can't access event error object because of CORS
19+
* Event message will be 'Script error.'
20+
*/
21+
if (event instanceof ErrorEvent && error === undefined) {
22+
error = (event as ErrorEvent).message;
23+
}
24+
25+
/**
26+
* Case when error rejected with an object
27+
* Using a string instead of wrapping in Error is more natural,
28+
* it doesn't fake the backtrace, also prefix added for dashboard readability
29+
*/
30+
if (error instanceof Object && !(error instanceof Error) && event instanceof PromiseRejectionEvent) {
31+
// Extra sanitize is needed to handle objects with circular references before JSON.stringify
32+
error = `Promise rejected with ${JSON.stringify(Sanitizer.sanitize(error))}`;
33+
}
34+
35+
return Sanitizer.sanitize(error);
36+
}
37+
38+
/**
39+
* Converts a promise rejection reason to a string message.
40+
*
41+
* String(obj) gives "[object Object]" and JSON.stringify("str")
42+
* adds unwanted quotes.
43+
*
44+
* @param reason - The rejection reason from PromiseRejectionEvent
45+
*/
46+
export function stringifyRejectionReason(reason: unknown): string {
47+
if (reason instanceof Error) {
48+
return reason.message;
49+
}
50+
if (typeof reason === 'string') {
51+
return reason;
52+
}
53+
54+
return JSON.stringify(Sanitizer.sanitize(reason));
55+
}
Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { log } from '@hawk.so/core';
2-
import Sanitizer from '../modules/sanitizer';
32

43
/**
54
* Symbol to mark error as processed by Hawk
@@ -45,57 +44,3 @@ export function markErrorAsProcessed(error: unknown): void {
4544
log('Failed to mark error as processed', 'error', e);
4645
}
4746
}
48-
49-
/**
50-
* Extracts and normalizes error from ErrorEvent or PromiseRejectionEvent
51-
*
52-
* @param event - The error or promise rejection event
53-
*/
54-
export function getErrorFromEvent(event: ErrorEvent | PromiseRejectionEvent): Error | string {
55-
/**
56-
* Promise rejection reason is recommended to be an Error, but it can be a string:
57-
* - Promise.reject(new Error('Reason message')) ——— recommended
58-
* - Promise.reject('Reason message')
59-
*/
60-
let error = (event as ErrorEvent).error || (event as PromiseRejectionEvent).reason;
61-
62-
/**
63-
* Case when error triggered in external script
64-
* We can't access event error object because of CORS
65-
* Event message will be 'Script error.'
66-
*/
67-
if (event instanceof ErrorEvent && error === undefined) {
68-
error = (event as ErrorEvent).message;
69-
}
70-
71-
/**
72-
* Case when error rejected with an object
73-
* Using a string instead of wrapping in Error is more natural,
74-
* it doesn't fake the backtrace, also prefix added for dashboard readability
75-
*/
76-
if (error instanceof Object && !(error instanceof Error) && event instanceof PromiseRejectionEvent) {
77-
// Extra sanitize is needed to handle objects with circular references before JSON.stringify
78-
error = `Promise rejected with ${JSON.stringify(Sanitizer.sanitize(error))}`;
79-
}
80-
81-
return Sanitizer.sanitize(error);
82-
}
83-
84-
/**
85-
* Converts a promise rejection reason to a string message.
86-
*
87-
* String(obj) gives "[object Object]" and JSON.stringify("str")
88-
* adds unwanted quotes.
89-
*
90-
* @param reason - The rejection reason from PromiseRejectionEvent
91-
*/
92-
export function stringifyRejectionReason(reason: unknown): string {
93-
if (reason instanceof Error) {
94-
return reason.message;
95-
}
96-
if (typeof reason === 'string') {
97-
return reason;
98-
}
99-
100-
return JSON.stringify(Sanitizer.sanitize(reason));
101-
}

packages/javascript/tests/utils/event.test.ts renamed to packages/javascript/tests/utils/error.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { describe, it, expect, vi, beforeEach } from 'vitest';
2-
import { getErrorFromEvent } from '../../src/utils/event';
2+
import { getErrorFromErrorEvent } from '../../src/utils/error';
33

44
vi.mock('@hawk.so/core', () => ({ log: vi.fn(), isLoggerSet: vi.fn(() => true), setLogger: vi.fn() }));
55

66
import Sanitizer from '../../src/modules/sanitizer';
77

8-
describe('getErrorFromEvent', () => {
8+
describe('getErrorFromErrorEvent', () => {
99
beforeEach(() => {
1010
vi.clearAllMocks();
1111
});
@@ -15,7 +15,7 @@ describe('getErrorFromEvent', () => {
1515
const error = new Error('Test error');
1616
const event = new ErrorEvent('error', { error });
1717

18-
const result = getErrorFromEvent(event);
18+
const result = getErrorFromErrorEvent(event);
1919

2020
expect(result).toBe(error);
2121
});
@@ -24,23 +24,23 @@ describe('getErrorFromEvent', () => {
2424
const error = new DOMException('Network error', 'NetworkError');
2525
const event = new ErrorEvent('error', { error });
2626

27-
const result = getErrorFromEvent(event);
27+
const result = getErrorFromErrorEvent(event);
2828

2929
expect(result).toBe('<instance of DOMException>');
3030
});
3131

3232
it('should return the message when event.error is not provided and message is a string', () => {
3333
const event = new ErrorEvent('error', { message: 'Script error.' });
3434

35-
const result = getErrorFromEvent(event);
35+
const result = getErrorFromErrorEvent(event);
3636

3737
expect(result).toBe('Script error.');
3838
});
3939

4040
it('should return empty string when event.error is not provided and message is empty', () => {
4141
const event = new ErrorEvent('error', { message: '' });
4242

43-
const result = getErrorFromEvent(event);
43+
const result = getErrorFromErrorEvent(event);
4444

4545
expect(result).toBe('');
4646
});
@@ -51,7 +51,7 @@ describe('getErrorFromEvent', () => {
5151
const reason = new Error('Promise rejected');
5252
const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason });
5353

54-
const result = getErrorFromEvent(event);
54+
const result = getErrorFromErrorEvent(event);
5555

5656
expect(result).toBe(reason);
5757
});
@@ -60,7 +60,7 @@ describe('getErrorFromEvent', () => {
6060
const reason = 'Promise rejected with string';
6161
const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason });
6262

63-
const result = getErrorFromEvent(event);
63+
const result = getErrorFromErrorEvent(event);
6464

6565
expect(result).toBe(reason);
6666
});
@@ -69,23 +69,23 @@ describe('getErrorFromEvent', () => {
6969
const reason = { code: 'ERR_001', details: 'Something went wrong' };
7070
const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason });
7171

72-
const result = getErrorFromEvent(event);
72+
const result = getErrorFromErrorEvent(event);
7373

7474
expect(result).toBe('Promise rejected with {"code":"ERR_001","details":"Something went wrong"}');
7575
});
7676

7777
it('should return undefined when event.reason is not provided', () => {
7878
const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason: undefined });
7979

80-
const result = getErrorFromEvent(event);
80+
const result = getErrorFromErrorEvent(event);
8181

8282
expect(result).toBeUndefined();
8383
});
8484

8585
it('should return null when event.reason is null', () => {
8686
const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason: null });
8787

88-
const result = getErrorFromEvent(event);
88+
const result = getErrorFromErrorEvent(event);
8989

9090
expect(result).toBeNull();
9191
});
@@ -96,7 +96,7 @@ describe('getErrorFromEvent', () => {
9696

9797
const event = new PromiseRejectionEvent('unhandledrejection', { promise: Promise.resolve(), reason: circularObj });
9898

99-
const result = getErrorFromEvent(event);
99+
const result = getErrorFromErrorEvent(event);
100100

101101
expect(result).toContain('Promise rejected with');
102102
expect(result).toContain('<circular>');

0 commit comments

Comments
 (0)