@posthog/core 1.1.0 → 1.2.1

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.
Files changed (128) hide show
  1. package/dist/error-tracking/chunk-ids.js +1 -1
  2. package/dist/error-tracking/chunk-ids.mjs +1 -1
  3. package/dist/error-tracking/coercers/error-event-coercer.js +4 -5
  4. package/dist/error-tracking/coercers/error-event-coercer.mjs +4 -5
  5. package/dist/error-tracking/coercers/event-coercer.js +1 -2
  6. package/dist/error-tracking/coercers/event-coercer.mjs +1 -2
  7. package/dist/error-tracking/coercers/object-coercer.js +1 -2
  8. package/dist/error-tracking/coercers/object-coercer.mjs +1 -2
  9. package/dist/error-tracking/coercers/primitive-coercer.js +1 -2
  10. package/dist/error-tracking/coercers/primitive-coercer.mjs +1 -2
  11. package/dist/error-tracking/coercers/promise-rejection-event.js +4 -5
  12. package/dist/error-tracking/coercers/promise-rejection-event.mjs +4 -5
  13. package/dist/error-tracking/coercers/string-coercer.js +3 -4
  14. package/dist/error-tracking/coercers/string-coercer.mjs +3 -4
  15. package/dist/error-tracking/coercers/utils.js +2 -4
  16. package/dist/error-tracking/coercers/utils.mjs +2 -4
  17. package/dist/error-tracking/error-properties-builder.d.ts +6 -6
  18. package/dist/error-tracking/error-properties-builder.d.ts.map +1 -1
  19. package/dist/error-tracking/error-properties-builder.js +17 -27
  20. package/dist/error-tracking/error-properties-builder.mjs +16 -26
  21. package/dist/error-tracking/parsers/index.js +2 -4
  22. package/dist/error-tracking/parsers/index.mjs +2 -4
  23. package/dist/error-tracking/parsers/node.js +3 -5
  24. package/dist/error-tracking/parsers/node.mjs +3 -5
  25. package/dist/error-tracking/utils.js +4 -4
  26. package/dist/error-tracking/utils.mjs +4 -4
  27. package/dist/eventemitter.js +4 -4
  28. package/dist/eventemitter.mjs +4 -4
  29. package/dist/featureFlagUtils.js +20 -45
  30. package/dist/featureFlagUtils.mjs +20 -45
  31. package/dist/gzip.js +1 -2
  32. package/dist/gzip.mjs +1 -2
  33. package/dist/index.d.ts +4 -366
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +54 -1225
  36. package/dist/index.mjs +5 -1190
  37. package/dist/posthog-core-stateless.d.ts +204 -0
  38. package/dist/posthog-core-stateless.d.ts.map +1 -0
  39. package/dist/posthog-core-stateless.js +675 -0
  40. package/dist/posthog-core-stateless.mjs +632 -0
  41. package/dist/posthog-core.d.ts +171 -0
  42. package/dist/posthog-core.d.ts.map +1 -0
  43. package/dist/posthog-core.js +554 -0
  44. package/dist/posthog-core.mjs +520 -0
  45. package/dist/testing/PostHogCoreTestClient.d.ts +2 -1
  46. package/dist/testing/PostHogCoreTestClient.d.ts.map +1 -1
  47. package/dist/testing/PostHogCoreTestClient.js +9 -11
  48. package/dist/testing/PostHogCoreTestClient.mjs +8 -10
  49. package/dist/testing/test-utils.js +1 -1
  50. package/dist/testing/test-utils.mjs +1 -1
  51. package/dist/utils/bucketed-rate-limiter.js +8 -12
  52. package/dist/utils/bucketed-rate-limiter.mjs +8 -12
  53. package/dist/utils/index.js +3 -3
  54. package/dist/utils/index.mjs +3 -3
  55. package/dist/utils/type-utils.js +1 -1
  56. package/dist/utils/type-utils.mjs +1 -1
  57. package/dist/vendor/uuidv7.js +12 -16
  58. package/dist/vendor/uuidv7.mjs +12 -16
  59. package/package.json +3 -2
  60. package/src/__tests__/featureFlagUtils.spec.ts +427 -0
  61. package/src/__tests__/gzip.spec.ts +69 -0
  62. package/src/__tests__/posthog.ai.spec.ts +110 -0
  63. package/src/__tests__/posthog.capture.spec.ts +91 -0
  64. package/src/__tests__/posthog.core.spec.ts +135 -0
  65. package/src/__tests__/posthog.debug.spec.ts +36 -0
  66. package/src/__tests__/posthog.enqueue.spec.ts +93 -0
  67. package/src/__tests__/posthog.featureflags.spec.ts +1106 -0
  68. package/src/__tests__/posthog.featureflags.v1.spec.ts +922 -0
  69. package/src/__tests__/posthog.flush.spec.ts +237 -0
  70. package/src/__tests__/posthog.gdpr.spec.ts +50 -0
  71. package/src/__tests__/posthog.groups.spec.ts +96 -0
  72. package/src/__tests__/posthog.identify.spec.ts +194 -0
  73. package/src/__tests__/posthog.init.spec.ts +110 -0
  74. package/src/__tests__/posthog.listeners.spec.ts +51 -0
  75. package/src/__tests__/posthog.register.spec.ts +47 -0
  76. package/src/__tests__/posthog.reset.spec.ts +76 -0
  77. package/src/__tests__/posthog.sessions.spec.ts +63 -0
  78. package/src/__tests__/posthog.setProperties.spec.ts +102 -0
  79. package/src/__tests__/posthog.shutdown.spec.ts +88 -0
  80. package/src/__tests__/utils.spec.ts +36 -0
  81. package/src/error-tracking/chunk-ids.ts +58 -0
  82. package/src/error-tracking/coercers/dom-exception-coercer.ts +38 -0
  83. package/src/error-tracking/coercers/error-coercer.ts +36 -0
  84. package/src/error-tracking/coercers/error-event-coercer.ts +24 -0
  85. package/src/error-tracking/coercers/event-coercer.ts +19 -0
  86. package/src/error-tracking/coercers/index.ts +8 -0
  87. package/src/error-tracking/coercers/object-coercer.ts +76 -0
  88. package/src/error-tracking/coercers/primitive-coercer.ts +19 -0
  89. package/src/error-tracking/coercers/promise-rejection-event.spec.ts +77 -0
  90. package/src/error-tracking/coercers/promise-rejection-event.ts +53 -0
  91. package/src/error-tracking/coercers/string-coercer.spec.ts +26 -0
  92. package/src/error-tracking/coercers/string-coercer.ts +31 -0
  93. package/src/error-tracking/coercers/utils.ts +33 -0
  94. package/src/error-tracking/error-properties-builder.coerce.spec.ts +202 -0
  95. package/src/error-tracking/error-properties-builder.parse.spec.ts +30 -0
  96. package/src/error-tracking/error-properties-builder.ts +167 -0
  97. package/src/error-tracking/index.ts +5 -0
  98. package/src/error-tracking/parsers/base.ts +29 -0
  99. package/src/error-tracking/parsers/chrome.ts +53 -0
  100. package/src/error-tracking/parsers/gecko.ts +38 -0
  101. package/src/error-tracking/parsers/index.ts +104 -0
  102. package/src/error-tracking/parsers/node.ts +111 -0
  103. package/src/error-tracking/parsers/opera.ts +18 -0
  104. package/src/error-tracking/parsers/react-native.ts +0 -0
  105. package/src/error-tracking/parsers/safari.ts +33 -0
  106. package/src/error-tracking/parsers/winjs.ts +12 -0
  107. package/src/error-tracking/types.ts +107 -0
  108. package/src/error-tracking/utils.ts +39 -0
  109. package/src/eventemitter.ts +27 -0
  110. package/src/featureFlagUtils.ts +192 -0
  111. package/src/gzip.ts +29 -0
  112. package/src/index.ts +8 -0
  113. package/src/posthog-core-stateless.ts +1226 -0
  114. package/src/posthog-core.ts +958 -0
  115. package/src/testing/PostHogCoreTestClient.ts +91 -0
  116. package/src/testing/index.ts +2 -0
  117. package/src/testing/test-utils.ts +47 -0
  118. package/src/types.ts +544 -0
  119. package/src/utils/bucketed-rate-limiter.spec.ts +33 -0
  120. package/src/utils/bucketed-rate-limiter.ts +85 -0
  121. package/src/utils/index.ts +98 -0
  122. package/src/utils/number-utils.spec.ts +89 -0
  123. package/src/utils/number-utils.ts +30 -0
  124. package/src/utils/promise-queue.spec.ts +55 -0
  125. package/src/utils/promise-queue.ts +30 -0
  126. package/src/utils/string-utils.ts +23 -0
  127. package/src/utils/type-utils.ts +134 -0
  128. package/src/vendor/uuidv7.ts +479 -0
@@ -0,0 +1,76 @@
1
+ import { isEmptyString, isError, isEvent, isString } from '@/utils'
2
+ import { CoercingContext, ErrorTrackingCoercer, ExceptionLike, SeverityLevel, severityLevels } from '../types'
3
+ import { extractExceptionKeysForMessage } from './utils'
4
+
5
+ type ObjectLike = Record<string, unknown>
6
+
7
+ export class ObjectCoercer implements ErrorTrackingCoercer<ObjectLike> {
8
+ match(candidate: unknown): candidate is ObjectLike {
9
+ return typeof candidate === 'object' && candidate !== null
10
+ }
11
+
12
+ coerce(candidate: ObjectLike, ctx: CoercingContext): ExceptionLike | undefined {
13
+ const errorProperty = this.getErrorPropertyFromObject(candidate)
14
+ if (errorProperty) {
15
+ return ctx.apply(errorProperty)
16
+ } else {
17
+ return {
18
+ type: this.getType(candidate),
19
+ value: this.getValue(candidate),
20
+ stack: ctx.syntheticException?.stack,
21
+ level: this.isSeverityLevel(candidate.level) ? candidate.level : 'error',
22
+ synthetic: true,
23
+ }
24
+ }
25
+ }
26
+
27
+ getType(err: Record<string, unknown>): string {
28
+ return isEvent(err) ? err.constructor.name : 'Error'
29
+ }
30
+
31
+ getValue(err: object) {
32
+ if ('name' in err && typeof err.name === 'string') {
33
+ let message = `'${err.name}' captured as exception`
34
+
35
+ if ('message' in err && typeof err.message === 'string') {
36
+ message += ` with message: '${err.message}'`
37
+ }
38
+
39
+ return message
40
+ } else if ('message' in err && typeof err.message === 'string') {
41
+ return err.message
42
+ }
43
+
44
+ const className = this.getObjectClassName(err)
45
+ const keys = extractExceptionKeysForMessage(err)
46
+
47
+ return `${className && className !== 'Object' ? `'${className}'` : 'Object'} captured as exception with keys: ${keys}`
48
+ }
49
+
50
+ private isSeverityLevel(x: unknown): x is SeverityLevel {
51
+ return isString(x) && !isEmptyString(x) && severityLevels.indexOf(x as SeverityLevel) >= 0
52
+ }
53
+
54
+ /** If a plain object has a property that is an `Error`, return this error. */
55
+ private getErrorPropertyFromObject(obj: Record<string, unknown>): Error | undefined {
56
+ for (const prop in obj) {
57
+ if (Object.prototype.hasOwnProperty.call(obj, prop)) {
58
+ const value = obj[prop]
59
+ if (isError(value)) {
60
+ return value
61
+ }
62
+ }
63
+ }
64
+
65
+ return undefined
66
+ }
67
+
68
+ private getObjectClassName(obj: unknown): string | undefined {
69
+ try {
70
+ const prototype: unknown | null = Object.getPrototypeOf(obj)
71
+ return prototype ? prototype.constructor.name : undefined
72
+ } catch (e) {
73
+ return undefined
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,19 @@
1
+ import { isPrimitive } from '@/utils'
2
+ import { CoercingContext, ErrorTrackingCoercer, ExceptionLike } from '../types'
3
+
4
+ export type PrimitiveType = null | undefined | boolean | number | string | symbol | bigint
5
+
6
+ export class PrimitiveCoercer implements ErrorTrackingCoercer<PrimitiveType> {
7
+ match(candidate: unknown): candidate is PrimitiveType {
8
+ return isPrimitive(candidate)
9
+ }
10
+
11
+ coerce(value: PrimitiveType, ctx: CoercingContext): ExceptionLike | undefined {
12
+ return {
13
+ type: 'Error',
14
+ value: `Primitive value captured as exception: ${String(value)}`,
15
+ stack: ctx.syntheticException?.stack,
16
+ synthetic: true,
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,77 @@
1
+ import { CoercingContext } from '../types'
2
+ import { PromiseRejectionEventCoercer } from './promise-rejection-event'
3
+
4
+ type PromiseRejectionEventTypes = 'rejectionhandled' | 'unhandledrejection'
5
+
6
+ type PromiseRejectionEventInit = {
7
+ promise: Promise<any>
8
+ reason: any
9
+ }
10
+
11
+ class PromiseRejectionEvent extends Event {
12
+ public readonly promise: Promise<any>
13
+ public readonly reason: any
14
+
15
+ public constructor(type: PromiseRejectionEventTypes, options: PromiseRejectionEventInit) {
16
+ super(type)
17
+
18
+ this.promise = options.promise
19
+ this.reason = options.reason
20
+ }
21
+ }
22
+
23
+ describe('PromiseRejectionEventCoercer', () => {
24
+ const coercer = new PromiseRejectionEventCoercer()
25
+
26
+ it('should coerce event with reason is a primitive', () => {
27
+ const pre = new PromiseRejectionEvent('unhandledrejection', {
28
+ promise: Promise.resolve('wat'),
29
+ reason: 'My house is on fire',
30
+ })
31
+
32
+ const ctx = {
33
+ apply: jest.fn(() => ({
34
+ type: 'MockType',
35
+ value: 'MockValue',
36
+ synthetic: true,
37
+ })),
38
+ next: jest.fn(),
39
+ } as CoercingContext
40
+
41
+ expect(coercer.coerce(pre, ctx)).toMatchObject({
42
+ type: 'UnhandledRejection',
43
+ value: 'Non-Error promise rejection captured with value: My house is on fire',
44
+ synthetic: true,
45
+ })
46
+ })
47
+
48
+ it('should coerce event with reason is an error', () => {
49
+ class CustomTestError extends Error {
50
+ constructor(message: string) {
51
+ super(message)
52
+ this.name = 'CustomTestError'
53
+ }
54
+ }
55
+
56
+ const pre = new PromiseRejectionEvent('unhandledrejection', {
57
+ promise: Promise.resolve('wat'),
58
+ reason: new CustomTestError('My house is on fire'),
59
+ })
60
+
61
+ const ctx = {
62
+ apply: jest.fn((err: Error) => ({
63
+ type: err.name,
64
+ value: err.message,
65
+ stack: err.stack,
66
+ synthetic: false,
67
+ })),
68
+ next: jest.fn(),
69
+ } as CoercingContext
70
+
71
+ expect(coercer.coerce(pre, ctx)).toMatchObject({
72
+ type: 'CustomTestError',
73
+ value: 'My house is on fire',
74
+ synthetic: false,
75
+ })
76
+ })
77
+ })
@@ -0,0 +1,53 @@
1
+ import { isBuiltin, isPrimitive } from '@/utils'
2
+ import { CoercingContext, ErrorTrackingCoercer, ExceptionLike } from '../types'
3
+
4
+ // Web only
5
+ export class PromiseRejectionEventCoercer implements ErrorTrackingCoercer<PromiseRejectionEvent> {
6
+ match(err: unknown): err is PromiseRejectionEvent {
7
+ return isBuiltin(err, 'PromiseRejectionEvent')
8
+ }
9
+
10
+ coerce(err: PromiseRejectionEvent, ctx: CoercingContext): ExceptionLike | undefined {
11
+ const reason = this.getUnhandledRejectionReason(err)
12
+ if (isPrimitive(reason)) {
13
+ return {
14
+ type: 'UnhandledRejection',
15
+ value: `Non-Error promise rejection captured with value: ${String(reason)}`,
16
+ stack: ctx.syntheticException?.stack,
17
+ synthetic: true,
18
+ }
19
+ } else {
20
+ return ctx.apply(reason)
21
+ }
22
+ }
23
+
24
+ private getUnhandledRejectionReason(error: unknown): unknown {
25
+ if (isPrimitive(error)) {
26
+ return error
27
+ }
28
+
29
+ // dig the object of the rejection out of known event types
30
+ try {
31
+ type ErrorWithReason = { reason: unknown }
32
+ // PromiseRejectionEvents store the object of the rejection under 'reason'
33
+ // see https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent
34
+ if ('reason' in (error as ErrorWithReason)) {
35
+ return (error as ErrorWithReason).reason
36
+ }
37
+
38
+ type CustomEventWithDetail = { detail: { reason: unknown } }
39
+ // something, somewhere, (likely a browser extension) effectively casts PromiseRejectionEvents
40
+ // to CustomEvents, moving the `promise` and `reason` attributes of the PRE into
41
+ // the CustomEvent's `detail` attribute, since they're not part of CustomEvent's spec
42
+ // see https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent and
43
+ // https://github.com/getsentry/sentry-javascript/issues/2380
44
+ if ('detail' in (error as CustomEventWithDetail) && 'reason' in (error as CustomEventWithDetail).detail) {
45
+ return (error as CustomEventWithDetail).detail.reason
46
+ }
47
+ } catch {
48
+ // no-empty
49
+ }
50
+
51
+ return error
52
+ }
53
+ }
@@ -0,0 +1,26 @@
1
+ import { CoercingContext } from '../types'
2
+ import { StringCoercer } from './string-coercer'
3
+
4
+ describe('PromiseRejectionEventCoercer', () => {
5
+ const coercer = new StringCoercer()
6
+
7
+ it('should parse string', () => {
8
+ const infos = coercer.getInfos('My house is on fire')
9
+ expect(infos).toMatchObject([undefined, 'My house is on fire'])
10
+ })
11
+
12
+ it('should parse errors', () => {
13
+ const infos = coercer.getInfos('ReferenceError: My house is on fire')
14
+ expect(infos).toMatchObject(['ReferenceError', 'My house is on fire'])
15
+ })
16
+
17
+ it('should discard prefix', () => {
18
+ const infos = coercer.getInfos('Uncaught exception: ReferenceError: My house is on fire')
19
+ expect(infos).toMatchObject(['ReferenceError', 'My house is on fire'])
20
+ })
21
+
22
+ it('should not match other patterns', () => {
23
+ const infos = coercer.getInfos('ValueError: ReferenceError: My house is on fire')
24
+ expect(infos).toMatchObject([undefined, 'ValueError: ReferenceError: My house is on fire'])
25
+ })
26
+ })
@@ -0,0 +1,31 @@
1
+ import { ExceptionLike, ErrorTrackingCoercer, CoercingContext } from '../types'
2
+
3
+ const ERROR_TYPES_PATTERN =
4
+ /^(?:[Uu]ncaught (?:exception: )?)?(?:((?:Eval|Internal|Range|Reference|Syntax|Type|URI|)Error): )?(.*)$/i
5
+
6
+ export class StringCoercer implements ErrorTrackingCoercer<string> {
7
+ match(input: unknown): input is string {
8
+ return typeof input === 'string'
9
+ }
10
+
11
+ coerce(input: string, ctx: CoercingContext): ExceptionLike {
12
+ const [type, value] = this.getInfos(input)
13
+ return {
14
+ type: type ?? 'Error',
15
+ value: value ?? input,
16
+ stack: ctx.syntheticException?.stack,
17
+ synthetic: true,
18
+ }
19
+ }
20
+
21
+ getInfos(candidate: string): [string, string] {
22
+ let type = 'Error'
23
+ let value = candidate
24
+ const groups = candidate.match(ERROR_TYPES_PATTERN)
25
+ if (groups) {
26
+ type = groups[1]
27
+ value = groups[2]
28
+ }
29
+ return [type, value]
30
+ }
31
+ }
@@ -0,0 +1,33 @@
1
+ export function truncate(str: string, max: number = 0): string {
2
+ if (typeof str !== 'string' || max === 0) {
3
+ return str
4
+ }
5
+ return str.length <= max ? str : `${str.slice(0, max)}...`
6
+ }
7
+
8
+ /**
9
+ * Given any captured exception, extract its keys and create a sorted
10
+ * and truncated list that will be used inside the event message.
11
+ * eg. `Non-error exception captured with keys: foo, bar, baz`
12
+ */
13
+ export function extractExceptionKeysForMessage(err: object, maxLength = 40): string {
14
+ const keys = Object.keys(err)
15
+ keys.sort()
16
+
17
+ if (!keys.length) {
18
+ return '[object has no keys]'
19
+ }
20
+
21
+ for (let i = keys.length; i > 0; i--) {
22
+ const serialized = keys.slice(0, i).join(', ')
23
+ if (serialized.length > maxLength) {
24
+ continue
25
+ }
26
+ if (i === keys.length) {
27
+ return serialized
28
+ }
29
+ return serialized.length <= maxLength ? serialized : `${serialized.slice(0, maxLength)}...`
30
+ }
31
+
32
+ return ''
33
+ }
@@ -0,0 +1,202 @@
1
+ import { DOMExceptionCoercer, ErrorEventCoercer, ErrorCoercer, ObjectCoercer, StringCoercer } from './coercers'
2
+ import { PrimitiveCoercer } from './coercers/primitive-coercer'
3
+ import { PromiseRejectionEventCoercer } from './coercers/promise-rejection-event'
4
+ import { ErrorPropertiesBuilder } from './error-properties-builder'
5
+ import { ExceptionLike } from './types'
6
+
7
+ describe('ErrorPropertiesBuilder', () => {
8
+ describe('coerceUnknown', () => {
9
+ class CustomTestError extends Error {
10
+ constructor(message: string, cause?: unknown) {
11
+ super(message)
12
+ this.name = 'CustomTestError'
13
+ this.cause = cause
14
+ }
15
+ }
16
+
17
+ const errorPropertiesBuilder = new ErrorPropertiesBuilder(
18
+ [
19
+ new DOMExceptionCoercer(),
20
+ new ErrorEventCoercer(),
21
+ new ErrorCoercer(),
22
+ new PromiseRejectionEventCoercer(),
23
+ new ObjectCoercer(),
24
+ new StringCoercer(),
25
+ new PrimitiveCoercer(),
26
+ ],
27
+ [],
28
+ []
29
+ )
30
+
31
+ function coerceInput(input: unknown, error: Error = new Error()): ExceptionLike | undefined {
32
+ const coercingContext = errorPropertiesBuilder.buildCoercingContext(
33
+ { handled: false },
34
+ {
35
+ syntheticException: error,
36
+ }
37
+ )
38
+ return coercingContext.apply(input)
39
+ }
40
+
41
+ it('should handle null values', async () => {
42
+ const syntheticError = new Error()
43
+ const exception = coerceInput(null, syntheticError)
44
+ expect(exception).toMatchObject({
45
+ type: 'Error',
46
+ value: 'Primitive value captured as exception: null',
47
+ stack: syntheticError.stack,
48
+ })
49
+ })
50
+
51
+ it('should handle string', () => {
52
+ const syntheticError = new Error()
53
+ const exception = coerceInput('test', syntheticError)
54
+ expect(exception).toMatchObject({
55
+ type: 'Error',
56
+ value: 'test',
57
+ stack: syntheticError.stack,
58
+ })
59
+ })
60
+
61
+ it('should handle exception string', () => {
62
+ const syntheticError = new Error()
63
+ const exception = coerceInput('Uncaught exception: InternalError: but somehow still a string', syntheticError)
64
+ expect(exception).toMatchObject({
65
+ type: 'InternalError',
66
+ value: 'but somehow still a string',
67
+ stack: syntheticError.stack,
68
+ })
69
+ })
70
+
71
+ it('should use keys in objects', async () => {
72
+ const syntheticError = new Error()
73
+ const errorObject = { foo: 'Foo value', bar: 'Bar value' }
74
+ const exception = coerceInput(errorObject, syntheticError)
75
+ expect(exception).toMatchObject({
76
+ type: 'Error',
77
+ value: 'Object captured as exception with keys: bar, foo',
78
+ stack: syntheticError.stack,
79
+ })
80
+ })
81
+
82
+ it('should handle object with an error property', () => {
83
+ const nestedError = new CustomTestError('My special error')
84
+ const errorObject = { error: nestedError }
85
+ const syntheticError = new Error()
86
+ const exception = coerceInput(errorObject, syntheticError)
87
+ expect(exception).toMatchObject({
88
+ type: 'CustomTestError',
89
+ value: 'My special error',
90
+ stack: nestedError.stack,
91
+ })
92
+ })
93
+
94
+ it('should handle error', () => {
95
+ const errorObject = new CustomTestError('My special error')
96
+ const exception = coerceInput(errorObject)
97
+ expect(exception).toMatchObject({
98
+ type: 'CustomTestError',
99
+ value: 'My special error',
100
+ stack: errorObject.stack,
101
+ })
102
+ })
103
+
104
+ it('should handle error with error cause', () => {
105
+ const secondError = new CustomTestError('My original error')
106
+ const firstError = new CustomTestError('My wrapped error', secondError)
107
+ const exception = coerceInput(firstError)
108
+ expect(exception).toMatchObject({
109
+ type: 'CustomTestError',
110
+ value: 'My wrapped error',
111
+ stack: firstError.stack,
112
+ cause: {
113
+ type: 'CustomTestError',
114
+ value: 'My original error',
115
+ stack: secondError.stack,
116
+ },
117
+ })
118
+ })
119
+
120
+ it('should handle error with object cause', () => {
121
+ const originalCause = { foo: 'bar', test: 'test' }
122
+ const kaboomError = new CustomTestError('Front error', originalCause)
123
+ const syntheticError = new Error()
124
+ const exception = coerceInput(kaboomError, syntheticError)
125
+ expect(exception).toMatchObject({
126
+ type: 'CustomTestError',
127
+ value: 'Front error',
128
+ stack: kaboomError.stack,
129
+ cause: {
130
+ type: 'Error',
131
+ value: 'Object captured as exception with keys: foo, test',
132
+ // Do we want to use the stack from the synthetic error?
133
+ stack: undefined,
134
+ },
135
+ })
136
+ })
137
+
138
+ it('should handle error with string cause', () => {
139
+ const originalCause = 'My original error'
140
+ const kaboomError = new CustomTestError('Front error', originalCause)
141
+ const syntheticError = new Error()
142
+ const exception = coerceInput(kaboomError, syntheticError)
143
+ expect(exception).toMatchObject({
144
+ type: 'CustomTestError',
145
+ value: 'Front error',
146
+ stack: kaboomError.stack,
147
+ cause: {
148
+ type: 'Error',
149
+ value: 'My original error',
150
+ // Do we want to use the stack from the synthetic error?
151
+ stack: undefined,
152
+ },
153
+ })
154
+ })
155
+
156
+ it('should convert a plain Event to an error', () => {
157
+ class MouseEvent extends Event {
158
+ constructor(type: string, eventInitDict?: EventInit) {
159
+ super(type, eventInitDict)
160
+ }
161
+ }
162
+ const event = new MouseEvent('click', { bubbles: true, cancelable: true, composed: true })
163
+ const syntheticError = new Error()
164
+ const exception = coerceInput(event, syntheticError)
165
+ expect(exception).toMatchObject({
166
+ type: 'MouseEvent',
167
+ value: "'MouseEvent' captured as exception with keys: [object has no keys]",
168
+ stack: syntheticError.stack,
169
+ synthetic: true,
170
+ })
171
+ })
172
+
173
+ it('should convert a DOM Error to an error', () => {
174
+ class FakeDomError {
175
+ constructor(
176
+ public name: string,
177
+ public message: string
178
+ ) {}
179
+ [Symbol.toStringTag] = 'DOMError'
180
+ }
181
+ const event = new FakeDomError('click', 'foo')
182
+ const exception = coerceInput(event)
183
+ expect(exception).toMatchObject({
184
+ type: 'DOMError',
185
+ value: 'click: foo',
186
+ stack: undefined,
187
+ synthetic: false,
188
+ })
189
+ })
190
+
191
+ it('should convert a DOM Exception to an error', () => {
192
+ const event = new DOMException('oh no disaster', 'dom-exception')
193
+ const exception = coerceInput(event)
194
+ expect(exception).toBeDefined()
195
+ expect(exception).toMatchObject({
196
+ type: 'DOMException',
197
+ value: 'dom-exception: oh no disaster',
198
+ synthetic: false,
199
+ })
200
+ })
201
+ })
202
+ })
@@ -0,0 +1,30 @@
1
+ import { ErrorPropertiesBuilder } from './error-properties-builder'
2
+ import { chromeStackLineParser } from './parsers'
3
+ import { StackFrame } from './types'
4
+
5
+ describe('ErrorPropertiesBuilder', () => {
6
+ describe('coerceUnknown', () => {
7
+ const errorPropertiesBuilder = new ErrorPropertiesBuilder([], [chromeStackLineParser], [])
8
+
9
+ function parseStack(error: Error): StackFrame[] | undefined {
10
+ const ctx = {}
11
+ const exception = errorPropertiesBuilder.parseStacktrace(
12
+ {
13
+ type: 'Error',
14
+ value: 'Whatever',
15
+ stack: error.stack,
16
+ synthetic: false,
17
+ },
18
+ ctx
19
+ )
20
+ return exception.stack
21
+ }
22
+
23
+ it('should parse stacktraces', () => {
24
+ const syntheticError = new Error()
25
+ const frames = parseStack(syntheticError)
26
+ expect(frames).toBeDefined()
27
+ expect(frames).toHaveLength(16)
28
+ })
29
+ })
30
+ })