@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.
@@ -26,10 +26,10 @@ __webpack_require__.r(__webpack_exports__);
26
26
  __webpack_require__.d(__webpack_exports__, {
27
27
  ErrorCoercer: ()=>ErrorCoercer
28
28
  });
29
- const index_js_namespaceObject = require("../../utils/index.js");
29
+ const type_utils_js_namespaceObject = require("../../utils/type-utils.js");
30
30
  class ErrorCoercer {
31
31
  match(err) {
32
- return (0, index_js_namespaceObject.isPlainError)(err);
32
+ return (0, type_utils_js_namespaceObject.isError)(err);
33
33
  }
34
34
  coerce(err, ctx) {
35
35
  return {
@@ -1,7 +1,7 @@
1
- import { isPlainError } from "../../utils/index.mjs";
1
+ import { isError } from "../../utils/type-utils.mjs";
2
2
  class ErrorCoercer {
3
3
  match(err) {
4
- return isPlainError(err);
4
+ return isError(err);
5
5
  }
6
6
  coerce(err, ctx) {
7
7
  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;AAED,qBAAa,iBAAkB,YAAW,oBAAoB,CAAC,cAAc,CAAC;;IAG5E,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,cAAc;IAI1C,MAAM,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,eAAe,GAAG,aAAa;CAajE"}
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
- return (0, index_js_namespaceObject.isErrorEvent)(err) && void 0 != err.error;
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
- const exceptionLike = ctx.apply(err.error);
37
- if (!exceptionLike) return {
38
- type: 'ErrorEvent',
39
- value: err.message,
40
- stack: ctx.syntheticException?.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
- return exceptionLike;
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
- return isErrorEvent(err) && void 0 != err.error;
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
- const exceptionLike = ctx.apply(err.error);
9
- if (!exceptionLike) return {
10
- type: 'ErrorEvent',
11
- value: err.message,
12
- stack: ctx.syntheticException?.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
- return exceptionLike;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@posthog/core",
3
- "version": "1.46.4",
3
+ "version": "1.46.5",
4
4
  "bugs": {
5
5
  "url": "https://github.com/PostHog/posthog-js/issues"
6
6
  },
@@ -1,9 +1,9 @@
1
- import { isPlainError } from '@/utils'
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 isPlainError(err)
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
- return isErrorEvent(err) && (err as ErrorEventLike).error != undefined
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
- const exceptionLike = ctx.apply(err.error)
22
- if (!exceptionLike) {
23
- return {
24
- type: 'ErrorEvent',
25
- value: err.message,
26
- stack: ctx.syntheticException?.stack,
27
- synthetic: true,
28
- }
29
- } else {
30
- return exceptionLike
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
  })