@posthog/core 1.46.4 → 1.46.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/error-tracking/coercers/error-coercer.js +2 -2
- package/dist/error-tracking/coercers/error-coercer.mjs +2 -2
- package/dist/error-tracking/coercers/error-event-coercer.d.ts +2 -0
- package/dist/error-tracking/coercers/error-event-coercer.d.ts.map +1 -1
- package/dist/error-tracking/coercers/error-event-coercer.js +19 -7
- package/dist/error-tracking/coercers/error-event-coercer.mjs +20 -8
- package/dist/error-tracking/coercers/object-coercer.d.ts +1 -0
- package/dist/error-tracking/coercers/object-coercer.d.ts.map +1 -1
- package/dist/error-tracking/coercers/object-coercer.js +9 -1
- package/dist/error-tracking/coercers/object-coercer.mjs +9 -1
- package/package.json +1 -1
- package/src/error-tracking/coercers/error-coercer.ts +2 -2
- package/src/error-tracking/coercers/error-event-coercer.spec.ts +109 -0
- package/src/error-tracking/coercers/error-event-coercer.ts +36 -12
- package/src/error-tracking/coercers/object-coercer.ts +12 -1
- package/src/error-tracking/error-properties-builder.coerce.spec.ts +42 -0
|
@@ -26,10 +26,10 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
26
26
|
__webpack_require__.d(__webpack_exports__, {
|
|
27
27
|
ErrorCoercer: ()=>ErrorCoercer
|
|
28
28
|
});
|
|
29
|
-
const
|
|
29
|
+
const type_utils_js_namespaceObject = require("../../utils/type-utils.js");
|
|
30
30
|
class ErrorCoercer {
|
|
31
31
|
match(err) {
|
|
32
|
-
return (0,
|
|
32
|
+
return (0, type_utils_js_namespaceObject.isError)(err);
|
|
33
33
|
}
|
|
34
34
|
coerce(err, ctx) {
|
|
35
35
|
return {
|
|
@@ -7,6 +7,8 @@ export declare class ErrorEventCoercer implements ErrorTrackingCoercer<ErrorEven
|
|
|
7
7
|
constructor();
|
|
8
8
|
match(err: unknown): err is ErrorEventLike;
|
|
9
9
|
coerce(err: ErrorEventLike, ctx: CoercingContext): ExceptionLike;
|
|
10
|
+
private _hasUsableMessage;
|
|
11
|
+
private _buildLocationStack;
|
|
10
12
|
}
|
|
11
13
|
export {};
|
|
12
14
|
//# sourceMappingURL=error-event-coercer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-event-coercer.d.ts","sourceRoot":"","sources":["../../../src/error-tracking/coercers/error-event-coercer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAO/E,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;
|
|
1
|
+
{"version":3,"file":"error-event-coercer.d.ts","sourceRoot":"","sources":["../../../src/error-tracking/coercers/error-event-coercer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAO/E,UAAU,cAAc;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAQD,qBAAa,iBAAkB,YAAW,oBAAoB,CAAC,cAAc,CAAC;;IAG5E,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,cAAc;IAQ1C,MAAM,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,GAAG,aAAa;IAahE,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,mBAAmB;CAU5B"}
|
|
@@ -30,17 +30,29 @@ const index_js_namespaceObject = require("../../utils/index.js");
|
|
|
30
30
|
class ErrorEventCoercer {
|
|
31
31
|
constructor(){}
|
|
32
32
|
match(err) {
|
|
33
|
-
|
|
33
|
+
if (!(0, index_js_namespaceObject.isErrorEvent)(err)) return false;
|
|
34
|
+
const errorEvent = err;
|
|
35
|
+
return void 0 != errorEvent.error || this._hasUsableMessage(errorEvent);
|
|
34
36
|
}
|
|
35
37
|
coerce(err, ctx) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
stack:
|
|
38
|
+
if (void 0 != err.error) return ctx.apply(err.error);
|
|
39
|
+
const exceptionLike = ctx.apply(err.message);
|
|
40
|
+
return {
|
|
41
|
+
...exceptionLike,
|
|
42
|
+
stack: this._buildLocationStack(err) ?? exceptionLike.stack,
|
|
41
43
|
synthetic: true
|
|
42
44
|
};
|
|
43
|
-
|
|
45
|
+
}
|
|
46
|
+
_hasUsableMessage(err) {
|
|
47
|
+
return (0, index_js_namespaceObject.isString)(err.message) && err.message.length > 0;
|
|
48
|
+
}
|
|
49
|
+
_buildLocationStack(err) {
|
|
50
|
+
const location = err;
|
|
51
|
+
if ((0, index_js_namespaceObject.isString)(location.filename) && location.filename.length > 0) {
|
|
52
|
+
const lineno = location.lineno ?? 0;
|
|
53
|
+
const colno = location.colno ?? 0;
|
|
54
|
+
return `Error\n at ${location.filename}:${lineno}:${colno}`;
|
|
55
|
+
}
|
|
44
56
|
}
|
|
45
57
|
}
|
|
46
58
|
exports.ErrorEventCoercer = __webpack_exports__.ErrorEventCoercer;
|
|
@@ -1,18 +1,30 @@
|
|
|
1
|
-
import { isErrorEvent } from "../../utils/index.mjs";
|
|
1
|
+
import { isErrorEvent, isString } from "../../utils/index.mjs";
|
|
2
2
|
class ErrorEventCoercer {
|
|
3
3
|
constructor(){}
|
|
4
4
|
match(err) {
|
|
5
|
-
|
|
5
|
+
if (!isErrorEvent(err)) return false;
|
|
6
|
+
const errorEvent = err;
|
|
7
|
+
return void 0 != errorEvent.error || this._hasUsableMessage(errorEvent);
|
|
6
8
|
}
|
|
7
9
|
coerce(err, ctx) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
stack:
|
|
10
|
+
if (void 0 != err.error) return ctx.apply(err.error);
|
|
11
|
+
const exceptionLike = ctx.apply(err.message);
|
|
12
|
+
return {
|
|
13
|
+
...exceptionLike,
|
|
14
|
+
stack: this._buildLocationStack(err) ?? exceptionLike.stack,
|
|
13
15
|
synthetic: true
|
|
14
16
|
};
|
|
15
|
-
|
|
17
|
+
}
|
|
18
|
+
_hasUsableMessage(err) {
|
|
19
|
+
return isString(err.message) && err.message.length > 0;
|
|
20
|
+
}
|
|
21
|
+
_buildLocationStack(err) {
|
|
22
|
+
const location = err;
|
|
23
|
+
if (isString(location.filename) && location.filename.length > 0) {
|
|
24
|
+
const lineno = location.lineno ?? 0;
|
|
25
|
+
const colno = location.colno ?? 0;
|
|
26
|
+
return `Error\n at ${location.filename}:${lineno}:${colno}`;
|
|
27
|
+
}
|
|
16
28
|
}
|
|
17
29
|
}
|
|
18
30
|
export { ErrorEventCoercer };
|
|
@@ -6,6 +6,7 @@ export declare class ObjectCoercer implements ErrorTrackingCoercer<ObjectLike> {
|
|
|
6
6
|
getType(err: Record<string, unknown>): string;
|
|
7
7
|
getValue(err: object): string;
|
|
8
8
|
private isSeverityLevel;
|
|
9
|
+
private getStack;
|
|
9
10
|
/** If a plain object has a property that is an `Error`, return this error. */
|
|
10
11
|
private getErrorPropertyFromObject;
|
|
11
12
|
private getObjectClassName;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"object-coercer.d.ts","sourceRoot":"","sources":["../../../src/error-tracking/coercers/object-coercer.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,aAAa,EAAiC,MAAM,UAAU,CAAA;AAG9G,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEzC,qBAAa,aAAc,YAAW,oBAAoB,CAAC,UAAU,CAAC;IACpE,KAAK,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,IAAI,UAAU;IAIlD,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,GAAG,EAAE,eAAe,GAAG,aAAa,GAAG,SAAS;IAe9E,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAI7C,QAAQ,CAAC,GAAG,EAAE,MAAM;IAmBpB,OAAO,CAAC,eAAe;IAIvB,8EAA8E;IAC9E,OAAO,CAAC,0BAA0B;IAalC,OAAO,CAAC,kBAAkB;CAQ3B"}
|
|
1
|
+
{"version":3,"file":"object-coercer.d.ts","sourceRoot":"","sources":["../../../src/error-tracking/coercers/object-coercer.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,aAAa,EAAiC,MAAM,UAAU,CAAA;AAG9G,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEzC,qBAAa,aAAc,YAAW,oBAAoB,CAAC,UAAU,CAAC;IACpE,KAAK,CAAC,SAAS,EAAE,OAAO,GAAG,SAAS,IAAI,UAAU;IAIlD,MAAM,CAAC,SAAS,EAAE,UAAU,EAAE,GAAG,EAAE,eAAe,GAAG,aAAa,GAAG,SAAS;IAe9E,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAI7C,QAAQ,CAAC,GAAG,EAAE,MAAM;IAmBpB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,QAAQ;IAWhB,8EAA8E;IAC9E,OAAO,CAAC,0BAA0B;IAalC,OAAO,CAAC,kBAAkB;CAQ3B"}
|
|
@@ -39,7 +39,7 @@ class ObjectCoercer {
|
|
|
39
39
|
return {
|
|
40
40
|
type: this.getType(candidate),
|
|
41
41
|
value: this.getValue(candidate),
|
|
42
|
-
stack: ctx.syntheticException?.stack,
|
|
42
|
+
stack: this.getStack(candidate) ?? ctx.syntheticException?.stack,
|
|
43
43
|
level: this.isSeverityLevel(candidate.level) ? candidate.level : 'error',
|
|
44
44
|
synthetic: true
|
|
45
45
|
};
|
|
@@ -61,6 +61,14 @@ class ObjectCoercer {
|
|
|
61
61
|
isSeverityLevel(x) {
|
|
62
62
|
return (0, index_js_namespaceObject.isString)(x) && !(0, index_js_namespaceObject.isEmptyString)(x) && external_types_js_namespaceObject.severityLevels.indexOf(x) >= 0;
|
|
63
63
|
}
|
|
64
|
+
getStack(candidate) {
|
|
65
|
+
try {
|
|
66
|
+
if ((0, index_js_namespaceObject.isString)(candidate.stacktrace) && candidate.stacktrace.length > 0) return candidate.stacktrace;
|
|
67
|
+
return (0, index_js_namespaceObject.isString)(candidate.stack) && candidate.stack.length > 0 ? candidate.stack : void 0;
|
|
68
|
+
} catch {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
64
72
|
getErrorPropertyFromObject(obj) {
|
|
65
73
|
for(const prop in obj)if (Object.prototype.hasOwnProperty.call(obj, prop)) {
|
|
66
74
|
const value = obj[prop];
|
|
@@ -11,7 +11,7 @@ class ObjectCoercer {
|
|
|
11
11
|
return {
|
|
12
12
|
type: this.getType(candidate),
|
|
13
13
|
value: this.getValue(candidate),
|
|
14
|
-
stack: ctx.syntheticException?.stack,
|
|
14
|
+
stack: this.getStack(candidate) ?? ctx.syntheticException?.stack,
|
|
15
15
|
level: this.isSeverityLevel(candidate.level) ? candidate.level : 'error',
|
|
16
16
|
synthetic: true
|
|
17
17
|
};
|
|
@@ -33,6 +33,14 @@ class ObjectCoercer {
|
|
|
33
33
|
isSeverityLevel(x) {
|
|
34
34
|
return isString(x) && !isEmptyString(x) && severityLevels.indexOf(x) >= 0;
|
|
35
35
|
}
|
|
36
|
+
getStack(candidate) {
|
|
37
|
+
try {
|
|
38
|
+
if (isString(candidate.stacktrace) && candidate.stacktrace.length > 0) return candidate.stacktrace;
|
|
39
|
+
return isString(candidate.stack) && candidate.stack.length > 0 ? candidate.stack : void 0;
|
|
40
|
+
} catch {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
36
44
|
getErrorPropertyFromObject(obj) {
|
|
37
45
|
for(const prop in obj)if (Object.prototype.hasOwnProperty.call(obj, prop)) {
|
|
38
46
|
const value = obj[prop];
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isError } from '@/utils/type-utils'
|
|
2
2
|
import { CoercingContext, ErrorTrackingCoercer, ExceptionLike } from '../types'
|
|
3
3
|
|
|
4
4
|
export class ErrorCoercer implements ErrorTrackingCoercer<Error> {
|
|
5
5
|
match(err: unknown): err is Error {
|
|
6
|
-
return
|
|
6
|
+
return isError(err)
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
coerce(err: Error, ctx: CoercingContext): ExceptionLike {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ErrorPropertiesBuilder } from '../error-properties-builder'
|
|
2
|
+
import { createDefaultStackParser } from '../parsers'
|
|
3
|
+
import { Exception } from '../types'
|
|
4
|
+
import { ErrorCoercer } from './error-coercer'
|
|
5
|
+
import { ErrorEventCoercer } from './error-event-coercer'
|
|
6
|
+
import { EventCoercer } from './event-coercer'
|
|
7
|
+
import { ObjectCoercer } from './object-coercer'
|
|
8
|
+
import { PrimitiveCoercer } from './primitive-coercer'
|
|
9
|
+
import { StringCoercer } from './string-coercer'
|
|
10
|
+
|
|
11
|
+
// `ErrorEvent` is not available in the Node test environment, so use its runtime brand.
|
|
12
|
+
class FakeErrorEvent {
|
|
13
|
+
message?: string
|
|
14
|
+
error?: unknown
|
|
15
|
+
filename?: string
|
|
16
|
+
lineno?: number
|
|
17
|
+
colno?: number;
|
|
18
|
+
[Symbol.toStringTag] = 'ErrorEvent'
|
|
19
|
+
|
|
20
|
+
constructor(init: Partial<Omit<FakeErrorEvent, typeof Symbol.toStringTag>> = {}) {
|
|
21
|
+
Object.assign(this, init)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe('ErrorEventCoercer', () => {
|
|
26
|
+
const coercer = new ErrorEventCoercer()
|
|
27
|
+
const errorPropertiesBuilder = new ErrorPropertiesBuilder(
|
|
28
|
+
[coercer, new ErrorCoercer(), new EventCoercer(), new ObjectCoercer(), new StringCoercer(), new PrimitiveCoercer()],
|
|
29
|
+
createDefaultStackParser()
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
function buildException(input: unknown, syntheticException?: Error): Exception {
|
|
33
|
+
return errorPropertiesBuilder.buildFromUnknown(input, { syntheticException }).$exception_list[0]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
it.each([
|
|
37
|
+
{
|
|
38
|
+
name: 'an ErrorEvent carrying an Error',
|
|
39
|
+
input: new FakeErrorEvent({ error: new Error('boom') }),
|
|
40
|
+
expected: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'an ErrorEvent carrying a message',
|
|
44
|
+
input: new FakeErrorEvent({ message: 'Script error.' }),
|
|
45
|
+
expected: true,
|
|
46
|
+
},
|
|
47
|
+
{ name: 'an ErrorEvent without an error or message', input: new FakeErrorEvent(), expected: false },
|
|
48
|
+
{ name: 'an ErrorEvent with an empty message', input: new FakeErrorEvent({ message: '' }), expected: false },
|
|
49
|
+
{ name: 'a non-ErrorEvent', input: { message: 'not an ErrorEvent' }, expected: false },
|
|
50
|
+
])('matches $name: $expected', ({ input, expected }) => {
|
|
51
|
+
expect(coercer.match(input)).toBe(expected)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('unwraps the Error carried by the ErrorEvent', () => {
|
|
55
|
+
const error = new Error('Something broke')
|
|
56
|
+
error.name = 'CustomTestError'
|
|
57
|
+
|
|
58
|
+
expect(buildException(new FakeErrorEvent({ message: 'ignored', error }))).toMatchObject({
|
|
59
|
+
type: 'CustomTestError',
|
|
60
|
+
value: 'Something broke',
|
|
61
|
+
mechanism: { synthetic: false },
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('preserves the message and location when there is no Error object', () => {
|
|
66
|
+
const exception = buildException(
|
|
67
|
+
new FakeErrorEvent({
|
|
68
|
+
message: 'Uncaught TypeError: x is not a function',
|
|
69
|
+
filename: 'https://example.com/app.js',
|
|
70
|
+
lineno: 42,
|
|
71
|
+
colno: 13,
|
|
72
|
+
})
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
expect(exception).toMatchObject({
|
|
76
|
+
type: 'TypeError',
|
|
77
|
+
value: 'x is not a function',
|
|
78
|
+
mechanism: { synthetic: true },
|
|
79
|
+
stacktrace: {
|
|
80
|
+
frames: [
|
|
81
|
+
expect.objectContaining({
|
|
82
|
+
filename: 'https://example.com/app.js',
|
|
83
|
+
lineno: 42,
|
|
84
|
+
colno: 13,
|
|
85
|
+
}),
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('does not parse frame-shaped lines from a multiline message', () => {
|
|
92
|
+
const exception = buildException(
|
|
93
|
+
new FakeErrorEvent({
|
|
94
|
+
message: 'Something broke\n at injected (https://example.com/injected.js:1:2)',
|
|
95
|
+
filename: 'https://example.com/app.js',
|
|
96
|
+
lineno: 42,
|
|
97
|
+
colno: 13,
|
|
98
|
+
})
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
expect(exception.stacktrace?.frames).toEqual([
|
|
102
|
+
expect.objectContaining({
|
|
103
|
+
filename: 'https://example.com/app.js',
|
|
104
|
+
lineno: 42,
|
|
105
|
+
colno: 13,
|
|
106
|
+
}),
|
|
107
|
+
])
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CoercingContext, ErrorTrackingCoercer, ExceptionLike } from '../types'
|
|
2
|
-
import { isErrorEvent } from '@/utils'
|
|
2
|
+
import { isErrorEvent, isString } from '@/utils'
|
|
3
3
|
|
|
4
4
|
// Structural subset of the DOM `ErrorEvent`. Avoids leaking a DOM-only global
|
|
5
5
|
// into the public type surface so non-DOM consumers (e.g. React Native, whose
|
|
@@ -10,24 +10,48 @@ interface ErrorEventLike {
|
|
|
10
10
|
error?: unknown
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
interface ErrorEventLocation {
|
|
14
|
+
filename?: string
|
|
15
|
+
lineno?: number
|
|
16
|
+
colno?: number
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
export class ErrorEventCoercer implements ErrorTrackingCoercer<ErrorEventLike> {
|
|
14
20
|
constructor() {}
|
|
15
21
|
|
|
16
22
|
match(err: unknown): err is ErrorEventLike {
|
|
17
|
-
|
|
23
|
+
if (!isErrorEvent(err)) {
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
const errorEvent = err as ErrorEventLike
|
|
27
|
+
return errorEvent.error != undefined || this._hasUsableMessage(errorEvent)
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
coerce(err: ErrorEventLike, ctx: CoercingContext): ExceptionLike {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
if (err.error != undefined) {
|
|
32
|
+
return ctx.apply(err.error)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const exceptionLike = ctx.apply(err.message)
|
|
36
|
+
return {
|
|
37
|
+
...exceptionLike,
|
|
38
|
+
stack: this._buildLocationStack(err) ?? exceptionLike.stack,
|
|
39
|
+
synthetic: true,
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private _hasUsableMessage(err: ErrorEventLike): boolean {
|
|
44
|
+
return isString(err.message) && err.message.length > 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private _buildLocationStack(err: ErrorEventLike): string | undefined {
|
|
48
|
+
const location = err as ErrorEventLike & ErrorEventLocation
|
|
49
|
+
if (isString(location.filename) && location.filename.length > 0) {
|
|
50
|
+
const lineno = location.lineno ?? 0
|
|
51
|
+
const colno = location.colno ?? 0
|
|
52
|
+
// The message stays in `value`, which prevents multiline messages from being parsed as extra frames.
|
|
53
|
+
return `Error\n at ${location.filename}:${lineno}:${colno}`
|
|
31
54
|
}
|
|
55
|
+
return undefined
|
|
32
56
|
}
|
|
33
57
|
}
|
|
@@ -21,7 +21,7 @@ export class ObjectCoercer implements ErrorTrackingCoercer<ObjectLike> {
|
|
|
21
21
|
return {
|
|
22
22
|
type: this.getType(candidate),
|
|
23
23
|
value: this.getValue(candidate),
|
|
24
|
-
stack: ctx.syntheticException?.stack,
|
|
24
|
+
stack: this.getStack(candidate) ?? ctx.syntheticException?.stack,
|
|
25
25
|
level: this.isSeverityLevel(candidate.level) ? candidate.level : 'error',
|
|
26
26
|
synthetic: true,
|
|
27
27
|
}
|
|
@@ -55,6 +55,17 @@ export class ObjectCoercer implements ErrorTrackingCoercer<ObjectLike> {
|
|
|
55
55
|
return isString(x) && !isEmptyString(x) && severityLevels.indexOf(x as SeverityLevel) >= 0
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
private getStack(candidate: ObjectLike): string | undefined {
|
|
59
|
+
try {
|
|
60
|
+
if (isString(candidate.stacktrace) && candidate.stacktrace.length > 0) {
|
|
61
|
+
return candidate.stacktrace
|
|
62
|
+
}
|
|
63
|
+
return isString(candidate.stack) && candidate.stack.length > 0 ? candidate.stack : undefined
|
|
64
|
+
} catch {
|
|
65
|
+
return undefined
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
/** If a plain object has a property that is an `Error`, return this error. */
|
|
59
70
|
private getErrorPropertyFromObject(obj: Record<string, unknown>): Error | undefined {
|
|
60
71
|
for (const prop in obj) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { runInNewContext } from 'node:vm'
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
DOMExceptionCoercer,
|
|
3
5
|
ErrorEventCoercer,
|
|
@@ -110,6 +112,28 @@ describe('ErrorPropertiesBuilder', () => {
|
|
|
110
112
|
})
|
|
111
113
|
})
|
|
112
114
|
|
|
115
|
+
it('should preserve a cross-realm error', () => {
|
|
116
|
+
const crossRealmError = runInNewContext(
|
|
117
|
+
`new TypeError('cross-realm error', {
|
|
118
|
+
cause: new Error('cross-realm cause')
|
|
119
|
+
})`
|
|
120
|
+
)
|
|
121
|
+
expect(crossRealmError).not.toBeInstanceOf(Error)
|
|
122
|
+
|
|
123
|
+
expect(coerceInput(crossRealmError)).toMatchObject({
|
|
124
|
+
type: 'TypeError',
|
|
125
|
+
value: 'cross-realm error',
|
|
126
|
+
stack: crossRealmError.stack,
|
|
127
|
+
synthetic: false,
|
|
128
|
+
cause: {
|
|
129
|
+
type: 'Error',
|
|
130
|
+
value: 'cross-realm cause',
|
|
131
|
+
stack: crossRealmError.cause.stack,
|
|
132
|
+
synthetic: false,
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
113
137
|
it('should handle error with error cause', () => {
|
|
114
138
|
const secondError = new CustomTestError('My original error')
|
|
115
139
|
const firstError = new CustomTestError('My wrapped error', secondError)
|
|
@@ -225,5 +249,23 @@ describe('ErrorPropertiesBuilder', () => {
|
|
|
225
249
|
stack: buriedError.stack,
|
|
226
250
|
})
|
|
227
251
|
})
|
|
252
|
+
|
|
253
|
+
it('should preserve an error-like stack from a CustomEvent wrapping a PromiseRejectionEvent', () => {
|
|
254
|
+
const stack = 'TypeError: Extension context invalidated.\n at extension.js:1:2'
|
|
255
|
+
const customEvent = new CustomEvent('unhandledrejection', {
|
|
256
|
+
detail: {
|
|
257
|
+
reason: {
|
|
258
|
+
name: 'TypeError',
|
|
259
|
+
message: 'Extension context invalidated.',
|
|
260
|
+
stack,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
expect(coerceInput(customEvent)).toMatchObject({
|
|
266
|
+
value: "'TypeError' captured as exception with message: 'Extension context invalidated.'",
|
|
267
|
+
stack,
|
|
268
|
+
})
|
|
269
|
+
})
|
|
228
270
|
})
|
|
229
271
|
})
|