Skip to content

Commit 28eb7b3

Browse files
committed
fix: preserve content when promise is rejected with plain object
1 parent f460ea3 commit 28eb7b3

File tree

3 files changed

+58
-17
lines changed

3 files changed

+58
-17
lines changed

packages/javascript/src/addons/consoleCatcher.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,25 @@ export class ConsoleCatcher {
189189
this.consoleOutput.push(logEvent);
190190
}
191191

192+
/**
193+
* Converts a promise rejection reason to a string message.
194+
*
195+
* String(obj) gives "[object Object]" and JSON.stringify("str")
196+
* adds unwanted quotes.
197+
*
198+
* @param reason - The rejection reason from PromiseRejectionEvent
199+
*/
200+
private stringifyReason(reason: unknown): string {
201+
if (reason instanceof Error) {
202+
return reason.message;
203+
}
204+
if (typeof reason === 'string') {
205+
return reason;
206+
}
207+
208+
return JSON.stringify(Sanitizer.sanitize(reason));
209+
}
210+
192211
/**
193212
* Creates a console log event from an error or promise rejection
194213
*
@@ -212,7 +231,7 @@ export class ConsoleCatcher {
212231
method: 'error',
213232
timestamp: new Date(),
214233
type: 'UnhandledRejection',
215-
message: event.reason?.message || String(event.reason),
234+
message: this.stringifyReason(event.reason),
216235
stack: event.reason?.stack || '',
217236
fileLine: '',
218237
};

packages/javascript/src/catcher.ts

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
} from '@hawk.so/types';
1515
import type { JavaScriptCatcherIntegrations } from './types/integrations';
1616
import { EventRejectedError } from './errors';
17-
import { isErrorProcessed, markErrorAsProcessed } from './utils/event';
17+
import { isErrorProcessed, markErrorAsProcessed, getErrorFromEvent } from './utils/event';
1818
import { BrowserRandomGenerator } from './utils/random';
1919
import { ConsoleCatcher } from './addons/consoleCatcher';
2020
import { BreadcrumbManager } from './addons/breadcrumbs';
@@ -331,21 +331,7 @@ export default class Catcher {
331331
this.consoleCatcher!.addErrorEvent(event);
332332
}
333333

334-
/**
335-
* Promise rejection reason is recommended to be an Error, but it can be a string:
336-
* - Promise.reject(new Error('Reason message')) ——— recommended
337-
* - Promise.reject('Reason message')
338-
*/
339-
let error = (event as ErrorEvent).error || (event as PromiseRejectionEvent).reason;
340-
341-
/**
342-
* Case when error triggered in external script
343-
* We can't access event error object because of CORS
344-
* Event message will be 'Script error.'
345-
*/
346-
if (event instanceof ErrorEvent && error === undefined) {
347-
error = (event as ErrorEvent).message;
348-
}
334+
const error = getErrorFromEvent(event);
349335

350336
void this.formatAndSend(error);
351337
}

packages/javascript/src/utils/event.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { log } from '@hawk.so/core';
2+
import Sanitizer from '../modules/sanitizer';
23

34
/**
45
* Symbol to mark error as processed by Hawk
@@ -44,3 +45,38 @@ export function markErrorAsProcessed(error: unknown): void {
4445
log('Failed to mark error as processed', 'error', e);
4546
}
4647
}
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+
}

0 commit comments

Comments
 (0)