@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,39 @@
1
+ // Portions of this file are derived from getsentry/sentry-javascript by Software, Inc. dba Sentry
2
+ // Licensed under the MIT License
3
+
4
+ /** A simple Least Recently Used map */
5
+ export class ReduceableCache<K, V> {
6
+ private readonly _cache: Map<K, V>
7
+
8
+ public constructor(private readonly _maxSize: number) {
9
+ this._cache = new Map<K, V>()
10
+ }
11
+
12
+ /** Get an entry or undefined if it was not in the cache. Re-inserts to update the recently used order */
13
+ public get(key: K): V | undefined {
14
+ const value = this._cache.get(key)
15
+ if (value === undefined) {
16
+ return undefined
17
+ }
18
+ // Remove and re-insert to update the order
19
+ this._cache.delete(key)
20
+ this._cache.set(key, value)
21
+ return value
22
+ }
23
+
24
+ /** Insert an entry and evict an older entry if we've reached maxSize */
25
+ public set(key: K, value: V): void {
26
+ this._cache.set(key, value)
27
+ }
28
+
29
+ /** Remove an entry and return the entry if it was in the cache */
30
+ public reduce(): void {
31
+ while (this._cache.size >= this._maxSize) {
32
+ const value = this._cache.keys().next().value
33
+ if (value) {
34
+ // keys() returns an iterator in insertion order so keys().next() gives us the oldest key
35
+ this._cache.delete(value)
36
+ }
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,27 @@
1
+ export class SimpleEventEmitter {
2
+ events: { [key: string]: ((...args: any[]) => void)[] } = {}
3
+
4
+ constructor() {
5
+ this.events = {}
6
+ }
7
+
8
+ on(event: string, listener: (...args: any[]) => void): () => void {
9
+ if (!this.events[event]) {
10
+ this.events[event] = []
11
+ }
12
+ this.events[event].push(listener)
13
+
14
+ return () => {
15
+ this.events[event] = this.events[event].filter((x) => x !== listener)
16
+ }
17
+ }
18
+
19
+ emit(event: string, payload: any): void {
20
+ for (const listener of this.events[event] || []) {
21
+ listener(payload)
22
+ }
23
+ for (const listener of this.events['*'] || []) {
24
+ listener(event, payload)
25
+ }
26
+ }
27
+ }
@@ -0,0 +1,192 @@
1
+ import {
2
+ FeatureFlagDetail,
3
+ FeatureFlagValue,
4
+ JsonType,
5
+ PostHogFlagsResponse,
6
+ PostHogV1FlagsResponse,
7
+ PostHogV2FlagsResponse,
8
+ PostHogFlagsAndPayloadsResponse,
9
+ PartialWithRequired,
10
+ PostHogFeatureFlagsResponse,
11
+ } from './types'
12
+
13
+ export const normalizeFlagsResponse = (
14
+ flagsResponse:
15
+ | PartialWithRequired<PostHogV2FlagsResponse, 'flags'>
16
+ | PartialWithRequired<PostHogV1FlagsResponse, 'featureFlags' | 'featureFlagPayloads'>
17
+ ): PostHogFeatureFlagsResponse => {
18
+ if ('flags' in flagsResponse) {
19
+ // Convert v2 format to v1 format
20
+ const featureFlags = getFlagValuesFromFlags(flagsResponse.flags)
21
+ const featureFlagPayloads = getPayloadsFromFlags(flagsResponse.flags)
22
+
23
+ return {
24
+ ...flagsResponse,
25
+ featureFlags,
26
+ featureFlagPayloads,
27
+ }
28
+ } else {
29
+ // Convert v1 format to v2 format
30
+ const featureFlags = flagsResponse.featureFlags ?? {}
31
+ const featureFlagPayloads = Object.fromEntries(
32
+ Object.entries(flagsResponse.featureFlagPayloads || {}).map(([k, v]) => [k, parsePayload(v)])
33
+ )
34
+
35
+ const flags = Object.fromEntries(
36
+ Object.entries(featureFlags).map(([key, value]) => [
37
+ key,
38
+ getFlagDetailFromFlagAndPayload(key, value, featureFlagPayloads[key]),
39
+ ])
40
+ )
41
+
42
+ return {
43
+ ...flagsResponse,
44
+ featureFlags,
45
+ featureFlagPayloads,
46
+ flags,
47
+ }
48
+ }
49
+ }
50
+
51
+ function getFlagDetailFromFlagAndPayload(
52
+ key: string,
53
+ value: FeatureFlagValue,
54
+ payload: JsonType | undefined
55
+ ): FeatureFlagDetail {
56
+ return {
57
+ key: key,
58
+ enabled: typeof value === 'string' ? true : value,
59
+ variant: typeof value === 'string' ? value : undefined,
60
+ reason: undefined,
61
+ metadata: {
62
+ id: undefined,
63
+ version: undefined,
64
+ payload: payload ? JSON.stringify(payload) : undefined,
65
+ description: undefined,
66
+ },
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Get the flag values from the flags v4 response.
72
+ * @param flags - The flags
73
+ * @returns The flag values
74
+ */
75
+ export const getFlagValuesFromFlags = (flags: PostHogFlagsResponse['flags']): PostHogFlagsResponse['featureFlags'] => {
76
+ return Object.fromEntries(
77
+ Object.entries(flags ?? {})
78
+ .map(([key, detail]) => [key, getFeatureFlagValue(detail)])
79
+ .filter(([, value]): boolean => value !== undefined)
80
+ )
81
+ }
82
+
83
+ /**
84
+ * Get the payloads from the flags v4 response.
85
+ * @param flags - The flags
86
+ * @returns The payloads
87
+ */
88
+ export const getPayloadsFromFlags = (
89
+ flags: PostHogFlagsResponse['flags']
90
+ ): PostHogFlagsResponse['featureFlagPayloads'] => {
91
+ const safeFlags = flags ?? {}
92
+ return Object.fromEntries(
93
+ Object.keys(safeFlags)
94
+ .filter((flag) => {
95
+ const details = safeFlags[flag]
96
+ return details.enabled && details.metadata && details.metadata.payload !== undefined
97
+ })
98
+ .map((flag) => {
99
+ const payload = safeFlags[flag].metadata?.payload as string
100
+ return [flag, payload ? parsePayload(payload) : undefined]
101
+ })
102
+ )
103
+ }
104
+
105
+ /**
106
+ * Get the flag details from the legacy v1 flags and payloads. As such, it will lack the reason, id, version, and description.
107
+ * @param flagsResponse - The flags response
108
+ * @returns The flag details
109
+ */
110
+ export const getFlagDetailsFromFlagsAndPayloads = (
111
+ flagsResponse: PostHogFeatureFlagsResponse
112
+ ): PostHogFlagsResponse['flags'] => {
113
+ const flags = flagsResponse.featureFlags ?? {}
114
+ const payloads = flagsResponse.featureFlagPayloads ?? {}
115
+ return Object.fromEntries(
116
+ Object.entries(flags).map(([key, value]) => [
117
+ key,
118
+ {
119
+ key: key,
120
+ enabled: typeof value === 'string' ? true : value,
121
+ variant: typeof value === 'string' ? value : undefined,
122
+ reason: undefined,
123
+ metadata: {
124
+ id: undefined,
125
+ version: undefined,
126
+ payload: payloads?.[key] ? JSON.stringify(payloads[key]) : undefined,
127
+ description: undefined,
128
+ },
129
+ },
130
+ ])
131
+ )
132
+ }
133
+
134
+ export const getFeatureFlagValue = (detail: FeatureFlagDetail | undefined): FeatureFlagValue | undefined => {
135
+ return detail === undefined ? undefined : (detail.variant ?? detail.enabled)
136
+ }
137
+
138
+ export const parsePayload = (response: any): any => {
139
+ if (typeof response !== 'string') {
140
+ return response
141
+ }
142
+
143
+ try {
144
+ return JSON.parse(response)
145
+ } catch {
146
+ return response
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Get the normalized flag details from the flags and payloads.
152
+ * This is used to convert things like boostrap and stored feature flags and payloads to the v4 format.
153
+ * This helps us ensure backwards compatibility.
154
+ * If a key exists in the featureFlagPayloads that is not in the featureFlags, we treat it as a true feature flag.
155
+ *
156
+ * @param featureFlags - The feature flags
157
+ * @param featureFlagPayloads - The feature flag payloads
158
+ * @returns The normalized flag details
159
+ */
160
+ export const createFlagsResponseFromFlagsAndPayloads = (
161
+ featureFlags: PostHogV1FlagsResponse['featureFlags'],
162
+ featureFlagPayloads: PostHogV1FlagsResponse['featureFlagPayloads']
163
+ ): PostHogFeatureFlagsResponse => {
164
+ // If a feature flag payload key is not in the feature flags, we treat it as true feature flag.
165
+ const allKeys = [...new Set([...Object.keys(featureFlags ?? {}), ...Object.keys(featureFlagPayloads ?? {})])]
166
+ const enabledFlags = allKeys
167
+ .filter((flag) => !!featureFlags[flag] || !!featureFlagPayloads[flag])
168
+ .reduce((res: Record<string, FeatureFlagValue>, key) => ((res[key] = featureFlags[key] ?? true), res), {})
169
+
170
+ const flagDetails: PostHogFlagsAndPayloadsResponse = {
171
+ featureFlags: enabledFlags,
172
+ featureFlagPayloads: featureFlagPayloads ?? {},
173
+ }
174
+
175
+ return normalizeFlagsResponse(flagDetails as PostHogV1FlagsResponse)
176
+ }
177
+
178
+ export const updateFlagValue = (flag: FeatureFlagDetail, value: FeatureFlagValue): FeatureFlagDetail => {
179
+ return {
180
+ ...flag,
181
+ enabled: getEnabledFromValue(value),
182
+ variant: getVariantFromValue(value),
183
+ }
184
+ }
185
+
186
+ function getEnabledFromValue(value: FeatureFlagValue): boolean {
187
+ return typeof value === 'string' ? true : value
188
+ }
189
+
190
+ function getVariantFromValue(value: FeatureFlagValue): string | undefined {
191
+ return typeof value === 'string' ? value : undefined
192
+ }
package/src/gzip.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Older browsers and some runtimes don't support this yet
3
+ * This API (as of 2025-05-07) is not available on React Native.
4
+ */
5
+ export function isGzipSupported(): boolean {
6
+ return 'CompressionStream' in globalThis
7
+ }
8
+
9
+ /**
10
+ * Gzip a string using Compression Streams API if it's available
11
+ */
12
+ export async function gzipCompress(input: string, isDebug = true): Promise<Blob | null> {
13
+ try {
14
+ // Turn the string into a stream using a Blob, and then compress it
15
+ const dataStream = new Blob([input], {
16
+ type: 'text/plain',
17
+ }).stream()
18
+
19
+ const compressedStream = dataStream.pipeThrough(new CompressionStream('gzip'))
20
+
21
+ // Using a Response to easily extract the readablestream value. Decoding into a string for fetch
22
+ return await new Response(compressedStream).blob()
23
+ } catch (error) {
24
+ if (isDebug) {
25
+ console.error('Failed to gzip compress data', error)
26
+ }
27
+ return null
28
+ }
29
+ }
package/src/index.ts ADDED
@@ -0,0 +1,8 @@
1
+ export { getFeatureFlagValue } from './featureFlagUtils'
2
+ export * from './utils'
3
+ export * as ErrorTracking from './error-tracking'
4
+ export { uuidv7 } from './vendor/uuidv7'
5
+ export * as testing from './testing'
6
+ export * from './posthog-core'
7
+ export * from './posthog-core-stateless'
8
+ export * from './types'