@posthog/core 1.48.3 → 1.48.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.d.ts.map +1 -1
- package/dist/error-tracking/coercers/error-coercer.js +4 -2
- package/dist/error-tracking/coercers/error-coercer.mjs +4 -2
- package/dist/error-tracking/parsers/base.d.ts.map +1 -1
- package/dist/error-tracking/parsers/base.js +2 -1
- package/dist/error-tracking/parsers/base.mjs +2 -1
- package/package.json +1 -1
- package/src/error-tracking/coercers/error-coercer.ts +8 -2
- package/src/error-tracking/error-properties-builder.coerce.spec.ts +15 -0
- package/src/error-tracking/parsers/base.spec.ts +34 -0
- package/src/error-tracking/parsers/base.ts +10 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-coercer.d.ts","sourceRoot":"","sources":["../../../src/error-tracking/coercers/error-coercer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE/E,qBAAa,YAAa,YAAW,oBAAoB,CAAC,KAAK,CAAC;IAC9D,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,KAAK;IAIjC,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,eAAe,GAAG,aAAa;
|
|
1
|
+
{"version":3,"file":"error-coercer.d.ts","sourceRoot":"","sources":["../../../src/error-tracking/coercers/error-coercer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE/E,qBAAa,YAAa,YAAW,oBAAoB,CAAC,KAAK,CAAC;IAC9D,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,KAAK;IAIjC,MAAM,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,eAAe,GAAG,aAAa;IAgBvD,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,QAAQ;CAGjB"}
|
|
@@ -32,12 +32,14 @@ class ErrorCoercer {
|
|
|
32
32
|
return (0, type_utils_js_namespaceObject.isError)(err);
|
|
33
33
|
}
|
|
34
34
|
coerce(err, ctx) {
|
|
35
|
+
const stack = this.getStack(err);
|
|
36
|
+
const synthetic = void 0 === stack;
|
|
35
37
|
return {
|
|
36
38
|
type: this.getType(err),
|
|
37
39
|
value: this.getMessage(err, ctx),
|
|
38
|
-
stack:
|
|
40
|
+
stack: stack ?? ctx.syntheticException?.stack,
|
|
39
41
|
cause: err.cause ? ctx.next(err.cause) : void 0,
|
|
40
|
-
synthetic
|
|
42
|
+
synthetic
|
|
41
43
|
};
|
|
42
44
|
}
|
|
43
45
|
getType(err) {
|
|
@@ -4,12 +4,14 @@ class ErrorCoercer {
|
|
|
4
4
|
return isError(err);
|
|
5
5
|
}
|
|
6
6
|
coerce(err, ctx) {
|
|
7
|
+
const stack = this.getStack(err);
|
|
8
|
+
const synthetic = void 0 === stack;
|
|
7
9
|
return {
|
|
8
10
|
type: this.getType(err),
|
|
9
11
|
value: this.getMessage(err, ctx),
|
|
10
|
-
stack:
|
|
12
|
+
stack: stack ?? ctx.syntheticException?.stack,
|
|
11
13
|
cause: err.cause ? ctx.next(err.cause) : void 0,
|
|
12
|
-
synthetic
|
|
14
|
+
synthetic
|
|
13
15
|
};
|
|
14
16
|
}
|
|
15
17
|
getType(err) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/error-tracking/parsers/base.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAErC,eAAO,MAAM,gBAAgB,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../src/error-tracking/parsers/base.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAErC,eAAO,MAAM,gBAAgB,MAAM,CAAA;AAiBnC,wBAAgB,WAAW,CACzB,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC,EAChC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,GACb,UAAU,CAmBZ"}
|
|
@@ -30,12 +30,13 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
30
30
|
const index_js_namespaceObject = require("../../utils/index.js");
|
|
31
31
|
const UNKNOWN_FUNCTION = '?';
|
|
32
32
|
const MASKED_URL_PREFIX = 'webkit-masked-url://';
|
|
33
|
+
const ANONYMOUS_FILENAME = '<anonymous>';
|
|
33
34
|
function createFrame(platform, filename, func, lineno, colno) {
|
|
34
35
|
const frame = {
|
|
35
36
|
platform,
|
|
36
37
|
filename,
|
|
37
38
|
function: '<anonymous>' === func ? UNKNOWN_FUNCTION : func,
|
|
38
|
-
in_app: !filename?.startsWith(MASKED_URL_PREFIX)
|
|
39
|
+
in_app: !filename?.startsWith(MASKED_URL_PREFIX) && filename !== ANONYMOUS_FILENAME
|
|
39
40
|
};
|
|
40
41
|
if (!(0, index_js_namespaceObject.isUndefined)(lineno)) frame.lineno = lineno;
|
|
41
42
|
if (!(0, index_js_namespaceObject.isUndefined)(colno)) frame.colno = colno;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { isUndefined } from "../../utils/index.mjs";
|
|
2
2
|
const UNKNOWN_FUNCTION = '?';
|
|
3
3
|
const MASKED_URL_PREFIX = 'webkit-masked-url://';
|
|
4
|
+
const ANONYMOUS_FILENAME = '<anonymous>';
|
|
4
5
|
function createFrame(platform, filename, func, lineno, colno) {
|
|
5
6
|
const frame = {
|
|
6
7
|
platform,
|
|
7
8
|
filename,
|
|
8
9
|
function: '<anonymous>' === func ? UNKNOWN_FUNCTION : func,
|
|
9
|
-
in_app: !filename?.startsWith(MASKED_URL_PREFIX)
|
|
10
|
+
in_app: !filename?.startsWith(MASKED_URL_PREFIX) && filename !== ANONYMOUS_FILENAME
|
|
10
11
|
};
|
|
11
12
|
if (!isUndefined(lineno)) frame.lineno = lineno;
|
|
12
13
|
if (!isUndefined(colno)) frame.colno = colno;
|
package/package.json
CHANGED
|
@@ -7,12 +7,18 @@ export class ErrorCoercer implements ErrorTrackingCoercer<Error> {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
coerce(err: Error, ctx: CoercingContext): ExceptionLike {
|
|
10
|
+
const stack = this.getStack(err)
|
|
11
|
+
// Some errors carry no usable stack (for example a Firefox network `fetch`
|
|
12
|
+
// `TypeError`). Fall back to the synthetic exception captured at the call
|
|
13
|
+
// site so the frames still point at the caller. Mark it synthetic so the
|
|
14
|
+
// parser trims the SDK's own frames.
|
|
15
|
+
const synthetic = stack === undefined
|
|
10
16
|
return {
|
|
11
17
|
type: this.getType(err),
|
|
12
18
|
value: this.getMessage(err, ctx),
|
|
13
|
-
stack:
|
|
19
|
+
stack: stack ?? ctx.syntheticException?.stack,
|
|
14
20
|
cause: err.cause ? ctx.next(err.cause) : undefined,
|
|
15
|
-
synthetic
|
|
21
|
+
synthetic,
|
|
16
22
|
}
|
|
17
23
|
}
|
|
18
24
|
|
|
@@ -112,6 +112,21 @@ describe('ErrorPropertiesBuilder', () => {
|
|
|
112
112
|
})
|
|
113
113
|
})
|
|
114
114
|
|
|
115
|
+
it('should fall back to the synthetic stack when the error has no stack', () => {
|
|
116
|
+
// Firefox rejects a network `fetch` with a `TypeError` that carries no
|
|
117
|
+
// stack, so the frames must come from the synthetic exception instead.
|
|
118
|
+
const errorObject = new CustomTestError('My special error')
|
|
119
|
+
errorObject.stack = ''
|
|
120
|
+
const syntheticError = new Error()
|
|
121
|
+
const exception = coerceInput(errorObject, syntheticError)
|
|
122
|
+
expect(exception).toMatchObject({
|
|
123
|
+
type: 'CustomTestError',
|
|
124
|
+
value: 'My special error',
|
|
125
|
+
stack: syntheticError.stack,
|
|
126
|
+
synthetic: true,
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
|
|
115
130
|
it('should preserve a cross-realm error', () => {
|
|
116
131
|
const crossRealmError = runInNewContext(
|
|
117
132
|
`new TypeError('cross-realm error', {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createFrame } from './base'
|
|
2
|
+
import { createDefaultStackParser } from './index'
|
|
2
3
|
|
|
3
4
|
describe('createFrame', () => {
|
|
4
5
|
const platform = 'web:javascript'
|
|
@@ -25,9 +26,42 @@ describe('createFrame', () => {
|
|
|
25
26
|
})
|
|
26
27
|
})
|
|
27
28
|
|
|
29
|
+
it('does not mark Chromium <anonymous> frames as in_app', () => {
|
|
30
|
+
// Chromium reports scripts that have no URL (extension-injected code, devtools, string eval)
|
|
31
|
+
// as `<anonymous>`. They can never be symbolicated and are not attributable to the page.
|
|
32
|
+
const frame = createFrame(platform, '<anonymous>', '?', 1, 394)
|
|
33
|
+
expect(frame.in_app).toBe(false)
|
|
34
|
+
expect(frame).toMatchObject({ filename: '<anonymous>', function: '?', lineno: 1, colno: 394 })
|
|
35
|
+
})
|
|
36
|
+
|
|
28
37
|
it('falls back to in_app when the parser could not determine a filename', () => {
|
|
29
38
|
// Regex capture groups that do not participate arrive here as undefined despite the type
|
|
30
39
|
const frame = createFrame(platform, undefined as unknown as string, 'doThing')
|
|
31
40
|
expect(frame.in_app).toBe(true)
|
|
32
41
|
})
|
|
33
42
|
})
|
|
43
|
+
|
|
44
|
+
describe('createDefaultStackParser in_app classification', () => {
|
|
45
|
+
const parse = createDefaultStackParser()
|
|
46
|
+
|
|
47
|
+
it('demotes a bare <anonymous> frame from injected code', () => {
|
|
48
|
+
const frames = parse("SyntaxError: Failed to execute 'appendChild' on 'Node': boom\n at <anonymous>:1:394")
|
|
49
|
+
expect(frames).toEqual([
|
|
50
|
+
{ platform: 'web:javascript', filename: '<anonymous>', function: '?', in_app: false, lineno: 1, colno: 394 },
|
|
51
|
+
])
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it("keeps the page's own eval'd code as in_app", () => {
|
|
55
|
+
// V8 attributes eval'd code to the script that called eval; the chrome parser already
|
|
56
|
+
// rewrites these frames to that URL, so they must not be caught by the <anonymous> rule.
|
|
57
|
+
const frames = parse(
|
|
58
|
+
'Error: boom\n' +
|
|
59
|
+
' at eval (eval at <anonymous> (https://example.com/app.js:10:5), <anonymous>:1:394)\n' +
|
|
60
|
+
' at https://example.com/app.js:10:5'
|
|
61
|
+
)
|
|
62
|
+
expect(frames.map((f) => [f.filename, f.in_app])).toEqual([
|
|
63
|
+
['https://example.com/app.js', true],
|
|
64
|
+
['https://example.com/app.js', true],
|
|
65
|
+
])
|
|
66
|
+
})
|
|
67
|
+
})
|
|
@@ -14,6 +14,14 @@ export const UNKNOWN_FUNCTION = '?'
|
|
|
14
14
|
// but do not let it count as in_app and pull the issue into the site's own stack.
|
|
15
15
|
const MASKED_URL_PREFIX = 'webkit-masked-url://'
|
|
16
16
|
|
|
17
|
+
// Chromium's counterpart: a script with no URL at all -- code injected by an extension
|
|
18
|
+
// (`chrome.scripting.executeScript`), pasted into devtools, or evaluated from a string -- shows
|
|
19
|
+
// up as a bare `<anonymous>:line:col` frame. The page's own eval'd code is *not* affected: V8
|
|
20
|
+
// reports it as `eval at <anonymous> (https://site/app.js:1:2)` and the chrome parser already
|
|
21
|
+
// rewrites that to the site URL. A bare `<anonymous>` can never be symbolicated, so treating it
|
|
22
|
+
// as in_app only ever pulled an unresolvable frame into the site's own stack.
|
|
23
|
+
const ANONYMOUS_FILENAME = '<anonymous>'
|
|
24
|
+
|
|
17
25
|
export function createFrame(
|
|
18
26
|
platform: StackFrame['platform'],
|
|
19
27
|
filename: string,
|
|
@@ -26,8 +34,8 @@ export function createFrame(
|
|
|
26
34
|
platform,
|
|
27
35
|
filename,
|
|
28
36
|
function: func === '<anonymous>' ? UNKNOWN_FUNCTION : func,
|
|
29
|
-
// Browser frames are considered in_app unless the runtime has masked their origin
|
|
30
|
-
in_app: !filename?.startsWith(MASKED_URL_PREFIX),
|
|
37
|
+
// Browser frames are considered in_app unless the runtime has masked or dropped their origin
|
|
38
|
+
in_app: !filename?.startsWith(MASKED_URL_PREFIX) && filename !== ANONYMOUS_FILENAME,
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
if (!isUndefined(lineno)) {
|