@posthog/core 1.1.0 → 1.2.0

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 (126) 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.js +11 -15
  18. package/dist/error-tracking/error-properties-builder.mjs +11 -15
  19. package/dist/error-tracking/parsers/index.js +2 -4
  20. package/dist/error-tracking/parsers/index.mjs +2 -4
  21. package/dist/error-tracking/parsers/node.js +3 -5
  22. package/dist/error-tracking/parsers/node.mjs +3 -5
  23. package/dist/error-tracking/utils.js +4 -4
  24. package/dist/error-tracking/utils.mjs +4 -4
  25. package/dist/eventemitter.js +4 -4
  26. package/dist/eventemitter.mjs +4 -4
  27. package/dist/featureFlagUtils.js +20 -45
  28. package/dist/featureFlagUtils.mjs +20 -45
  29. package/dist/gzip.js +1 -2
  30. package/dist/gzip.mjs +1 -2
  31. package/dist/index.d.ts +4 -366
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +54 -1225
  34. package/dist/index.mjs +5 -1190
  35. package/dist/posthog-core-stateless.d.ts +204 -0
  36. package/dist/posthog-core-stateless.d.ts.map +1 -0
  37. package/dist/posthog-core-stateless.js +675 -0
  38. package/dist/posthog-core-stateless.mjs +632 -0
  39. package/dist/posthog-core.d.ts +171 -0
  40. package/dist/posthog-core.d.ts.map +1 -0
  41. package/dist/posthog-core.js +554 -0
  42. package/dist/posthog-core.mjs +520 -0
  43. package/dist/testing/PostHogCoreTestClient.d.ts +2 -1
  44. package/dist/testing/PostHogCoreTestClient.d.ts.map +1 -1
  45. package/dist/testing/PostHogCoreTestClient.js +9 -11
  46. package/dist/testing/PostHogCoreTestClient.mjs +8 -10
  47. package/dist/testing/test-utils.js +1 -1
  48. package/dist/testing/test-utils.mjs +1 -1
  49. package/dist/utils/bucketed-rate-limiter.js +8 -12
  50. package/dist/utils/bucketed-rate-limiter.mjs +8 -12
  51. package/dist/utils/index.js +3 -3
  52. package/dist/utils/index.mjs +3 -3
  53. package/dist/utils/type-utils.js +1 -1
  54. package/dist/utils/type-utils.mjs +1 -1
  55. package/dist/vendor/uuidv7.js +12 -16
  56. package/dist/vendor/uuidv7.mjs +12 -16
  57. package/package.json +3 -2
  58. package/src/__tests__/featureFlagUtils.spec.ts +427 -0
  59. package/src/__tests__/gzip.spec.ts +69 -0
  60. package/src/__tests__/posthog.ai.spec.ts +110 -0
  61. package/src/__tests__/posthog.capture.spec.ts +91 -0
  62. package/src/__tests__/posthog.core.spec.ts +135 -0
  63. package/src/__tests__/posthog.debug.spec.ts +36 -0
  64. package/src/__tests__/posthog.enqueue.spec.ts +93 -0
  65. package/src/__tests__/posthog.featureflags.spec.ts +1106 -0
  66. package/src/__tests__/posthog.featureflags.v1.spec.ts +922 -0
  67. package/src/__tests__/posthog.flush.spec.ts +237 -0
  68. package/src/__tests__/posthog.gdpr.spec.ts +50 -0
  69. package/src/__tests__/posthog.groups.spec.ts +96 -0
  70. package/src/__tests__/posthog.identify.spec.ts +194 -0
  71. package/src/__tests__/posthog.init.spec.ts +110 -0
  72. package/src/__tests__/posthog.listeners.spec.ts +51 -0
  73. package/src/__tests__/posthog.register.spec.ts +47 -0
  74. package/src/__tests__/posthog.reset.spec.ts +76 -0
  75. package/src/__tests__/posthog.sessions.spec.ts +63 -0
  76. package/src/__tests__/posthog.setProperties.spec.ts +102 -0
  77. package/src/__tests__/posthog.shutdown.spec.ts +88 -0
  78. package/src/__tests__/utils.spec.ts +36 -0
  79. package/src/error-tracking/chunk-ids.ts +58 -0
  80. package/src/error-tracking/coercers/dom-exception-coercer.ts +38 -0
  81. package/src/error-tracking/coercers/error-coercer.ts +36 -0
  82. package/src/error-tracking/coercers/error-event-coercer.ts +24 -0
  83. package/src/error-tracking/coercers/event-coercer.ts +19 -0
  84. package/src/error-tracking/coercers/index.ts +8 -0
  85. package/src/error-tracking/coercers/object-coercer.ts +76 -0
  86. package/src/error-tracking/coercers/primitive-coercer.ts +19 -0
  87. package/src/error-tracking/coercers/promise-rejection-event.spec.ts +77 -0
  88. package/src/error-tracking/coercers/promise-rejection-event.ts +53 -0
  89. package/src/error-tracking/coercers/string-coercer.spec.ts +26 -0
  90. package/src/error-tracking/coercers/string-coercer.ts +31 -0
  91. package/src/error-tracking/coercers/utils.ts +33 -0
  92. package/src/error-tracking/error-properties-builder.coerce.spec.ts +202 -0
  93. package/src/error-tracking/error-properties-builder.parse.spec.ts +30 -0
  94. package/src/error-tracking/error-properties-builder.ts +169 -0
  95. package/src/error-tracking/index.ts +5 -0
  96. package/src/error-tracking/parsers/base.ts +29 -0
  97. package/src/error-tracking/parsers/chrome.ts +53 -0
  98. package/src/error-tracking/parsers/gecko.ts +38 -0
  99. package/src/error-tracking/parsers/index.ts +104 -0
  100. package/src/error-tracking/parsers/node.ts +111 -0
  101. package/src/error-tracking/parsers/opera.ts +18 -0
  102. package/src/error-tracking/parsers/react-native.ts +0 -0
  103. package/src/error-tracking/parsers/safari.ts +33 -0
  104. package/src/error-tracking/parsers/winjs.ts +12 -0
  105. package/src/error-tracking/types.ts +107 -0
  106. package/src/error-tracking/utils.ts +39 -0
  107. package/src/eventemitter.ts +27 -0
  108. package/src/featureFlagUtils.ts +192 -0
  109. package/src/gzip.ts +29 -0
  110. package/src/index.ts +8 -0
  111. package/src/posthog-core-stateless.ts +1226 -0
  112. package/src/posthog-core.ts +958 -0
  113. package/src/testing/PostHogCoreTestClient.ts +91 -0
  114. package/src/testing/index.ts +2 -0
  115. package/src/testing/test-utils.ts +47 -0
  116. package/src/types.ts +544 -0
  117. package/src/utils/bucketed-rate-limiter.spec.ts +33 -0
  118. package/src/utils/bucketed-rate-limiter.ts +85 -0
  119. package/src/utils/index.ts +98 -0
  120. package/src/utils/number-utils.spec.ts +89 -0
  121. package/src/utils/number-utils.ts +30 -0
  122. package/src/utils/promise-queue.spec.ts +55 -0
  123. package/src/utils/promise-queue.ts +30 -0
  124. package/src/utils/string-utils.ts +23 -0
  125. package/src/utils/type-utils.ts +134 -0
  126. package/src/vendor/uuidv7.ts +479 -0
@@ -0,0 +1,33 @@
1
+ import { Logger } from '@/types'
2
+ import { BucketedRateLimiter } from './bucketed-rate-limiter'
3
+
4
+ jest.useFakeTimers()
5
+
6
+ describe('BucketedRateLimiter', () => {
7
+ let rateLimiter: BucketedRateLimiter<string>
8
+
9
+ beforeEach(() => {
10
+ rateLimiter = new BucketedRateLimiter({
11
+ bucketSize: 10,
12
+ refillRate: 1,
13
+ refillInterval: 1000,
14
+ _logger: {} as unknown as Logger,
15
+ })
16
+ })
17
+
18
+ afterEach(() => {
19
+ jest.clearAllMocks()
20
+ })
21
+
22
+ test('it is not rate limited by default', () => {
23
+ const result = rateLimiter.consumeRateLimit('ResizeObserver')
24
+ expect(result).toBe(false)
25
+ })
26
+
27
+ test('returns true if no mutations are left', () => {
28
+ rateLimiter['_buckets']['ResizeObserver'] = 0
29
+
30
+ const result = rateLimiter.consumeRateLimit('ResizeObserver')
31
+ expect(result).toBe(true)
32
+ })
33
+ })
@@ -0,0 +1,85 @@
1
+ import { Logger } from '../types'
2
+ import { clampToRange } from './number-utils'
3
+
4
+ export class BucketedRateLimiter<T extends string | number> {
5
+ private _bucketSize
6
+ private _refillRate
7
+ private _refillInterval
8
+ private _onBucketRateLimited?: (key: T) => void
9
+
10
+ private _buckets: Record<string, number> = {}
11
+ private _removeInterval: NodeJS.Timeout | undefined
12
+
13
+ constructor(
14
+ private readonly _options: {
15
+ bucketSize: number
16
+ refillRate: number
17
+ refillInterval: number
18
+ _logger: Logger
19
+ _onBucketRateLimited?: (key: T) => void
20
+ }
21
+ ) {
22
+ this._onBucketRateLimited = this._options._onBucketRateLimited
23
+ this._bucketSize = clampToRange(this._options.bucketSize, 0, 100, this._options._logger)
24
+ this._refillRate = clampToRange(
25
+ this._options.refillRate,
26
+ 0,
27
+ this._bucketSize, // never refill more than bucket size
28
+ this._options._logger
29
+ )
30
+ this._refillInterval = clampToRange(
31
+ this._options.refillInterval,
32
+ 0,
33
+ 86400000, // one day in milliseconds
34
+ this._options._logger
35
+ )
36
+ this._removeInterval = setInterval(() => {
37
+ this._refillBuckets()
38
+ }, this._refillInterval)
39
+ }
40
+
41
+ private _refillBuckets = () => {
42
+ Object.keys(this._buckets).forEach((key) => {
43
+ const newTokens = this._getBucket(key) + this._refillRate
44
+
45
+ if (newTokens >= this._bucketSize) {
46
+ delete this._buckets[key]
47
+ } else {
48
+ this._setBucket(key, newTokens)
49
+ }
50
+ })
51
+ }
52
+
53
+ private _getBucket = (key: T | string) => {
54
+ return this._buckets[String(key)]
55
+ }
56
+ private _setBucket = (key: T | string, value: number) => {
57
+ this._buckets[String(key)] = value
58
+ }
59
+
60
+ public consumeRateLimit = (key: T) => {
61
+ let tokens = this._getBucket(key) ?? this._bucketSize
62
+ tokens = Math.max(tokens - 1, 0)
63
+
64
+ if (tokens === 0) {
65
+ return true
66
+ }
67
+
68
+ this._setBucket(key, tokens)
69
+
70
+ const hasReachedZero = tokens === 0
71
+
72
+ if (hasReachedZero) {
73
+ this._onBucketRateLimited?.(key)
74
+ }
75
+
76
+ return hasReachedZero
77
+ }
78
+
79
+ public stop() {
80
+ if (this._removeInterval) {
81
+ clearInterval(this._removeInterval)
82
+ this._removeInterval = undefined
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,98 @@
1
+ import { FetchLike } from '../types'
2
+
3
+ export * from './bucketed-rate-limiter'
4
+ export * from './number-utils'
5
+ export * from './string-utils'
6
+ export * from './type-utils'
7
+ export * from './promise-queue'
8
+
9
+ export const STRING_FORMAT = 'utf8'
10
+
11
+ export function assert(truthyValue: any, message: string): void {
12
+ if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
13
+ throw new Error(message)
14
+ }
15
+ }
16
+
17
+ function isEmpty(truthyValue: string): boolean {
18
+ if (truthyValue.trim().length === 0) {
19
+ return true
20
+ }
21
+ return false
22
+ }
23
+
24
+ export function removeTrailingSlash(url: string): string {
25
+ return url?.replace(/\/+$/, '')
26
+ }
27
+
28
+ export interface RetriableOptions {
29
+ retryCount: number
30
+ retryDelay: number
31
+ retryCheck: (err: unknown) => boolean
32
+ }
33
+
34
+ export async function retriable<T>(fn: () => Promise<T>, props: RetriableOptions): Promise<T> {
35
+ let lastError = null
36
+
37
+ for (let i = 0; i < props.retryCount + 1; i++) {
38
+ if (i > 0) {
39
+ // don't wait when it's the last try
40
+ await new Promise<void>((r) => setTimeout(r, props.retryDelay))
41
+ }
42
+
43
+ try {
44
+ const res = await fn()
45
+ return res
46
+ } catch (e) {
47
+ lastError = e
48
+ if (!props.retryCheck(e)) {
49
+ throw e
50
+ }
51
+ }
52
+ }
53
+
54
+ throw lastError
55
+ }
56
+
57
+ export function currentTimestamp(): number {
58
+ return new Date().getTime()
59
+ }
60
+
61
+ export function currentISOTime(): string {
62
+ return new Date().toISOString()
63
+ }
64
+
65
+ export function safeSetTimeout(fn: () => void, timeout: number): any {
66
+ // NOTE: we use this so rarely that it is totally fine to do `safeSetTimeout(fn, 0)``
67
+ // rather than setImmediate.
68
+ const t = setTimeout(fn, timeout) as any
69
+ // We unref if available to prevent Node.js hanging on exit
70
+ t?.unref && t?.unref()
71
+ return t
72
+ }
73
+
74
+ // NOTE: We opt for this slightly imperfect check as the global "Promise" object can get mutated in certain environments
75
+ export const isPromise = (obj: any): obj is Promise<any> => {
76
+ return obj && typeof obj.then === 'function'
77
+ }
78
+
79
+ export const isError = (x: unknown): x is Error => {
80
+ return x instanceof Error
81
+ }
82
+
83
+ export function getFetch(): FetchLike | undefined {
84
+ return typeof fetch !== 'undefined' ? fetch : typeof globalThis.fetch !== 'undefined' ? globalThis.fetch : undefined
85
+ }
86
+
87
+ export function allSettled<T>(
88
+ promises: (Promise<T> | null | undefined)[]
89
+ ): Promise<({ status: 'fulfilled'; value: T } | { status: 'rejected'; reason: any })[]> {
90
+ return Promise.all(
91
+ promises.map((p) =>
92
+ (p ?? Promise.resolve()).then(
93
+ (value: any) => ({ status: 'fulfilled' as const, value }),
94
+ (reason: any) => ({ status: 'rejected' as const, reason })
95
+ )
96
+ )
97
+ )
98
+ }
@@ -0,0 +1,89 @@
1
+ import { createMockLogger } from '@/testing'
2
+ import { clampToRange } from './number-utils'
3
+
4
+ describe('number-utils', () => {
5
+ const mockLogger = createMockLogger()
6
+
7
+ describe('clampToRange', () => {
8
+ it.each([
9
+ [
10
+ 'returns max when value is not a number',
11
+ {
12
+ value: null,
13
+ min: 10,
14
+ max: 100,
15
+ expected: 100,
16
+ fallback: undefined,
17
+ },
18
+ ],
19
+ [
20
+ 'returns max when value is not a number',
21
+ {
22
+ value: 'not-a-number',
23
+ min: 10,
24
+ max: 100,
25
+ expected: 100,
26
+ fallback: undefined,
27
+ },
28
+ ],
29
+ [
30
+ 'returns max when value is greater than max',
31
+ {
32
+ value: 150,
33
+ min: 10,
34
+ max: 100,
35
+ expected: 100,
36
+ fallback: undefined,
37
+ },
38
+ ],
39
+ [
40
+ 'returns min when value is less than min',
41
+ {
42
+ value: 5,
43
+ min: 10,
44
+ max: 100,
45
+ expected: 10,
46
+ fallback: undefined,
47
+ },
48
+ ],
49
+ [
50
+ 'returns the value when it is within the range',
51
+ {
52
+ value: 50,
53
+ min: 10,
54
+ max: 100,
55
+ expected: 50,
56
+ fallback: undefined,
57
+ },
58
+ ],
59
+ [
60
+ 'returns the fallback value when provided is not valid',
61
+ {
62
+ value: 'invalid',
63
+ min: 10,
64
+ max: 100,
65
+ expected: 20,
66
+ fallback: 20,
67
+ },
68
+ ],
69
+ [
70
+ 'returns the max value when fallback is not valid',
71
+ {
72
+ value: 'invalid',
73
+ min: 10,
74
+ max: 75,
75
+ expected: 75,
76
+ fallback: '20',
77
+ },
78
+ ],
79
+ ])('%s', (_description, { value, min, max, expected, fallback }) => {
80
+ const result = clampToRange(value, min, max, mockLogger, fallback as any)
81
+ expect(result).toBe(expected)
82
+ })
83
+
84
+ it('logs a warning when min is greater than max', () => {
85
+ expect(clampToRange(50, 100, 10, mockLogger)).toBe(10)
86
+ expect(mockLogger.warn).toHaveBeenCalledWith('min cannot be greater than max.')
87
+ })
88
+ })
89
+ })
@@ -0,0 +1,30 @@
1
+ import { Logger } from '../types'
2
+ import { isNumber } from './type-utils'
3
+
4
+ /**
5
+ * Clamps a value to a range.
6
+ * @param value the value to clamp
7
+ * @param min the minimum value
8
+ * @param max the maximum value
9
+ * @param label if provided then enables logging and prefixes all logs with labels
10
+ * @param fallbackValue if provided then returns this value if the value is not a valid number
11
+ */
12
+ export function clampToRange(value: unknown, min: number, max: number, logger: Logger, fallbackValue?: number): number {
13
+ if (min > max) {
14
+ logger.warn('min cannot be greater than max.')
15
+ min = max
16
+ }
17
+
18
+ if (!isNumber(value)) {
19
+ logger.warn(' must be a number. using max or fallback. max: ' + max + ', fallback: ' + fallbackValue)
20
+ return clampToRange(fallbackValue || max, min, max, logger)
21
+ } else if (value > max) {
22
+ logger.warn(' cannot be greater than max: ' + max + '. Using max value instead.')
23
+ return max
24
+ } else if (value < min) {
25
+ logger.warn(' cannot be less than min: ' + min + '. Using min value instead.')
26
+ return min
27
+ } else {
28
+ return value
29
+ }
30
+ }
@@ -0,0 +1,55 @@
1
+ import { PromiseQueue } from './promise-queue'
2
+
3
+ function buildPromise(time: number): Promise<number> {
4
+ return new Promise((res, rej) => setTimeout(() => res(42), time))
5
+ }
6
+
7
+ function buildRecursivePromise(time: number, cb: () => void) {
8
+ return new Promise((res, rej) => {
9
+ setTimeout(() => {
10
+ cb()
11
+ res(42)
12
+ }, time)
13
+ })
14
+ }
15
+
16
+ describe('promise-queue', () => {
17
+ beforeAll(() => jest.useRealTimers())
18
+ afterAll(() => jest.useFakeTimers())
19
+
20
+ it('should exit directly if the queue is empty', async () => {
21
+ const queue = new PromiseQueue()
22
+ expect(queue.length).toBe(0)
23
+ expect(queue.join()).resolves.toBe(undefined)
24
+ })
25
+
26
+ it('should add a promise to the queue', async () => {
27
+ const queue = new PromiseQueue()
28
+ queue.add(buildPromise(100))
29
+ expect(queue.length).toBe(1)
30
+ await queue.join()
31
+ expect(queue.length).toBe(0)
32
+ })
33
+
34
+ it('should wait even when promises create other promises', async () => {
35
+ const queue = new PromiseQueue()
36
+ const addSpy = jest.spyOn(queue, 'add')
37
+ queue.add(
38
+ buildRecursivePromise(100, () => {
39
+ queue.add(buildPromise(100))
40
+ })
41
+ )
42
+ expect(queue.length).toBe(1)
43
+ await queue.join()
44
+ expect(queue.length).toBe(0)
45
+ expect(addSpy).toHaveBeenCalledTimes(2)
46
+ })
47
+
48
+ it('it should reject if a promise reject', async () => {
49
+ const queue = new PromiseQueue()
50
+ queue.add(Promise.reject(new Error('test')))
51
+ expect(queue.length).toBe(1)
52
+ await expect(queue.join()).rejects.toHaveProperty('message', 'test')
53
+ expect(queue.length).toBe(0)
54
+ })
55
+ })
@@ -0,0 +1,30 @@
1
+ import { uuidv7 } from '../vendor/uuidv7'
2
+
3
+ export class PromiseQueue {
4
+ private promiseByIds: Record<string, Promise<any>> = {}
5
+
6
+ public add(promise: Promise<any>): Promise<any> {
7
+ const promiseUUID = uuidv7()
8
+ this.promiseByIds[promiseUUID] = promise
9
+ promise
10
+ .catch(() => {})
11
+ .finally(() => {
12
+ delete this.promiseByIds[promiseUUID]
13
+ })
14
+ return promise
15
+ }
16
+
17
+ public async join(): Promise<void> {
18
+ let promises = Object.values(this.promiseByIds)
19
+ let length = promises.length
20
+ while (length > 0) {
21
+ await Promise.all(promises)
22
+ promises = Object.values(this.promiseByIds)
23
+ length = promises.length
24
+ }
25
+ }
26
+
27
+ public get length(): number {
28
+ return Object.keys(this.promiseByIds).length
29
+ }
30
+ }
@@ -0,0 +1,23 @@
1
+ export function includes(str: string, needle: string): boolean
2
+ export function includes<T>(arr: T[], needle: T): boolean
3
+ export function includes(str: unknown[] | string, needle: unknown): boolean {
4
+ return (str as any).indexOf(needle) !== -1
5
+ }
6
+
7
+ export const trim = function (str: string): string {
8
+ // Previous implementation was using underscore's trim function.
9
+ // When switching to just using the native trim() function, we ran some tests to make sure that it was able to trim both the BOM character \uFEFF and the NBSP character \u00A0.
10
+ // We tested modern Chrome (134.0.6998.118) and Firefox (136.0.2), and IE11 running on Windows 10, and all of them were able to trim both characters.
11
+ // See https://posthog.slack.com/archives/C0113360FFV/p1742811455647359
12
+ return str.trim()
13
+ }
14
+
15
+ // UNDERSCORE
16
+ // Embed part of the Underscore Library
17
+ export const stripLeadingDollar = function (s: string): string {
18
+ return s.replace(/^\$/, '')
19
+ }
20
+
21
+ export function isDistinctIdStringLike(value: string): boolean {
22
+ return ['distinct_id', 'distinctid'].includes(value.toLowerCase())
23
+ }
@@ -0,0 +1,134 @@
1
+ import { knownUnsafeEditableEvent, KnownUnsafeEditableEvent } from '../types'
2
+ import { includes } from './string-utils'
3
+
4
+ // eslint-disable-next-line posthog-js/no-direct-array-check
5
+ const nativeIsArray = Array.isArray
6
+ const ObjProto = Object.prototype
7
+ export const hasOwnProperty = ObjProto.hasOwnProperty
8
+ const toString = ObjProto.toString
9
+
10
+ export const isArray =
11
+ nativeIsArray ||
12
+ function (obj: any): obj is any[] {
13
+ return toString.call(obj) === '[object Array]'
14
+ }
15
+
16
+ // from a comment on http://dbj.org/dbj/?p=286
17
+ // fails on only one very rare and deliberate custom object:
18
+ // let bomb = { toString : undefined, valueOf: function(o) { return "function BOMBA!"; }};
19
+ export const isFunction = (x: unknown): x is (...args: any[]) => any => {
20
+ // eslint-disable-next-line posthog-js/no-direct-function-check
21
+ return typeof x === 'function'
22
+ }
23
+
24
+ export const isNativeFunction = (x: unknown): x is (...args: any[]) => any =>
25
+ isFunction(x) && x.toString().indexOf('[native code]') !== -1
26
+
27
+ // Underscore Addons
28
+ export const isObject = (x: unknown): x is Record<string, any> => {
29
+ // eslint-disable-next-line posthog-js/no-direct-object-check
30
+ return x === Object(x) && !isArray(x)
31
+ }
32
+ export const isEmptyObject = (x: unknown) => {
33
+ if (isObject(x)) {
34
+ for (const key in x) {
35
+ if (hasOwnProperty.call(x, key)) {
36
+ return false
37
+ }
38
+ }
39
+ return true
40
+ }
41
+ return false
42
+ }
43
+ export const isUndefined = (x: unknown): x is undefined => x === void 0
44
+
45
+ export const isString = (x: unknown): x is string => {
46
+ // eslint-disable-next-line posthog-js/no-direct-string-check
47
+ return toString.call(x) == '[object String]'
48
+ }
49
+
50
+ export const isEmptyString = (x: unknown): boolean => isString(x) && x.trim().length === 0
51
+
52
+ export const isNull = (x: unknown): x is null => {
53
+ // eslint-disable-next-line posthog-js/no-direct-null-check
54
+ return x === null
55
+ }
56
+
57
+ /*
58
+ sometimes you want to check if something is null or undefined
59
+ that's what this is for
60
+ */
61
+ export const isNullish = (x: unknown): x is null | undefined => isUndefined(x) || isNull(x)
62
+
63
+ export const isNumber = (x: unknown): x is number => {
64
+ // eslint-disable-next-line posthog-js/no-direct-number-check
65
+ return toString.call(x) == '[object Number]'
66
+ }
67
+ export const isBoolean = (x: unknown): x is boolean => {
68
+ // eslint-disable-next-line posthog-js/no-direct-boolean-check
69
+ return toString.call(x) === '[object Boolean]'
70
+ }
71
+
72
+ export const isFormData = (x: unknown): x is FormData => {
73
+ // eslint-disable-next-line posthog-js/no-direct-form-data-check
74
+ return x instanceof FormData
75
+ }
76
+
77
+ export const isFile = (x: unknown): x is File => {
78
+ // eslint-disable-next-line posthog-js/no-direct-file-check
79
+ return x instanceof File
80
+ }
81
+
82
+ export const isPlainError = (x: unknown): x is Error => {
83
+ return x instanceof Error
84
+ }
85
+
86
+ export const isKnownUnsafeEditableEvent = (x: unknown): x is KnownUnsafeEditableEvent => {
87
+ return includes(knownUnsafeEditableEvent as unknown as string[], x)
88
+ }
89
+
90
+ export function isInstanceOf(candidate: unknown, base: any): boolean {
91
+ try {
92
+ return candidate instanceof base
93
+ } catch {
94
+ return false
95
+ }
96
+ }
97
+
98
+ export function isPrimitive(value: unknown): boolean {
99
+ return value === null || typeof value !== 'object'
100
+ }
101
+
102
+ export function isBuiltin(candidate: unknown, className: string): boolean {
103
+ return Object.prototype.toString.call(candidate) === `[object ${className}]`
104
+ }
105
+
106
+ export function isError(candidate: unknown): candidate is Error {
107
+ switch (Object.prototype.toString.call(candidate)) {
108
+ case '[object Error]':
109
+ case '[object Exception]':
110
+ case '[object DOMException]':
111
+ case '[object DOMError]':
112
+ case '[object WebAssembly.Exception]':
113
+ return true
114
+ default:
115
+ return isInstanceOf(candidate, Error)
116
+ }
117
+ }
118
+
119
+ export function isErrorEvent(event: unknown): boolean {
120
+ return isBuiltin(event, 'ErrorEvent')
121
+ }
122
+
123
+ export function isEvent(candidate: unknown): candidate is Event {
124
+ return !isUndefined(Event) && isInstanceOf(candidate, Event)
125
+ }
126
+
127
+ export function isPlainObject(candidate: unknown): candidate is Record<string, unknown> {
128
+ return isBuiltin(candidate, 'Object')
129
+ }
130
+
131
+ export const yesLikeValues = [true, 'true', 1, '1', 'yes']
132
+ export const isYesLike = (val: string | boolean | number): boolean => includes(yesLikeValues, val)
133
+ export const noLikeValues = [false, 'false', 0, '0', 'no']
134
+ export const isNoLike = (val: string | boolean | number): boolean => includes(noLikeValues, val)