@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.
- package/dist/error-tracking/chunk-ids.js +1 -1
- package/dist/error-tracking/chunk-ids.mjs +1 -1
- package/dist/error-tracking/coercers/error-event-coercer.js +4 -5
- package/dist/error-tracking/coercers/error-event-coercer.mjs +4 -5
- package/dist/error-tracking/coercers/event-coercer.js +1 -2
- package/dist/error-tracking/coercers/event-coercer.mjs +1 -2
- package/dist/error-tracking/coercers/object-coercer.js +1 -2
- package/dist/error-tracking/coercers/object-coercer.mjs +1 -2
- package/dist/error-tracking/coercers/primitive-coercer.js +1 -2
- package/dist/error-tracking/coercers/primitive-coercer.mjs +1 -2
- package/dist/error-tracking/coercers/promise-rejection-event.js +4 -5
- package/dist/error-tracking/coercers/promise-rejection-event.mjs +4 -5
- package/dist/error-tracking/coercers/string-coercer.js +3 -4
- package/dist/error-tracking/coercers/string-coercer.mjs +3 -4
- package/dist/error-tracking/coercers/utils.js +2 -4
- package/dist/error-tracking/coercers/utils.mjs +2 -4
- package/dist/error-tracking/error-properties-builder.js +11 -15
- package/dist/error-tracking/error-properties-builder.mjs +11 -15
- package/dist/error-tracking/parsers/index.js +2 -4
- package/dist/error-tracking/parsers/index.mjs +2 -4
- package/dist/error-tracking/parsers/node.js +3 -5
- package/dist/error-tracking/parsers/node.mjs +3 -5
- package/dist/error-tracking/utils.js +4 -4
- package/dist/error-tracking/utils.mjs +4 -4
- package/dist/eventemitter.js +4 -4
- package/dist/eventemitter.mjs +4 -4
- package/dist/featureFlagUtils.js +20 -45
- package/dist/featureFlagUtils.mjs +20 -45
- package/dist/gzip.js +1 -2
- package/dist/gzip.mjs +1 -2
- package/dist/index.d.ts +4 -366
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -1225
- package/dist/index.mjs +5 -1190
- package/dist/posthog-core-stateless.d.ts +204 -0
- package/dist/posthog-core-stateless.d.ts.map +1 -0
- package/dist/posthog-core-stateless.js +675 -0
- package/dist/posthog-core-stateless.mjs +632 -0
- package/dist/posthog-core.d.ts +171 -0
- package/dist/posthog-core.d.ts.map +1 -0
- package/dist/posthog-core.js +554 -0
- package/dist/posthog-core.mjs +520 -0
- package/dist/testing/PostHogCoreTestClient.d.ts +2 -1
- package/dist/testing/PostHogCoreTestClient.d.ts.map +1 -1
- package/dist/testing/PostHogCoreTestClient.js +9 -11
- package/dist/testing/PostHogCoreTestClient.mjs +8 -10
- package/dist/testing/test-utils.js +1 -1
- package/dist/testing/test-utils.mjs +1 -1
- package/dist/utils/bucketed-rate-limiter.js +8 -12
- package/dist/utils/bucketed-rate-limiter.mjs +8 -12
- package/dist/utils/index.js +3 -3
- package/dist/utils/index.mjs +3 -3
- package/dist/utils/type-utils.js +1 -1
- package/dist/utils/type-utils.mjs +1 -1
- package/dist/vendor/uuidv7.js +12 -16
- package/dist/vendor/uuidv7.mjs +12 -16
- package/package.json +3 -2
- package/src/__tests__/featureFlagUtils.spec.ts +427 -0
- package/src/__tests__/gzip.spec.ts +69 -0
- package/src/__tests__/posthog.ai.spec.ts +110 -0
- package/src/__tests__/posthog.capture.spec.ts +91 -0
- package/src/__tests__/posthog.core.spec.ts +135 -0
- package/src/__tests__/posthog.debug.spec.ts +36 -0
- package/src/__tests__/posthog.enqueue.spec.ts +93 -0
- package/src/__tests__/posthog.featureflags.spec.ts +1106 -0
- package/src/__tests__/posthog.featureflags.v1.spec.ts +922 -0
- package/src/__tests__/posthog.flush.spec.ts +237 -0
- package/src/__tests__/posthog.gdpr.spec.ts +50 -0
- package/src/__tests__/posthog.groups.spec.ts +96 -0
- package/src/__tests__/posthog.identify.spec.ts +194 -0
- package/src/__tests__/posthog.init.spec.ts +110 -0
- package/src/__tests__/posthog.listeners.spec.ts +51 -0
- package/src/__tests__/posthog.register.spec.ts +47 -0
- package/src/__tests__/posthog.reset.spec.ts +76 -0
- package/src/__tests__/posthog.sessions.spec.ts +63 -0
- package/src/__tests__/posthog.setProperties.spec.ts +102 -0
- package/src/__tests__/posthog.shutdown.spec.ts +88 -0
- package/src/__tests__/utils.spec.ts +36 -0
- package/src/error-tracking/chunk-ids.ts +58 -0
- package/src/error-tracking/coercers/dom-exception-coercer.ts +38 -0
- package/src/error-tracking/coercers/error-coercer.ts +36 -0
- package/src/error-tracking/coercers/error-event-coercer.ts +24 -0
- package/src/error-tracking/coercers/event-coercer.ts +19 -0
- package/src/error-tracking/coercers/index.ts +8 -0
- package/src/error-tracking/coercers/object-coercer.ts +76 -0
- package/src/error-tracking/coercers/primitive-coercer.ts +19 -0
- package/src/error-tracking/coercers/promise-rejection-event.spec.ts +77 -0
- package/src/error-tracking/coercers/promise-rejection-event.ts +53 -0
- package/src/error-tracking/coercers/string-coercer.spec.ts +26 -0
- package/src/error-tracking/coercers/string-coercer.ts +31 -0
- package/src/error-tracking/coercers/utils.ts +33 -0
- package/src/error-tracking/error-properties-builder.coerce.spec.ts +202 -0
- package/src/error-tracking/error-properties-builder.parse.spec.ts +30 -0
- package/src/error-tracking/error-properties-builder.ts +169 -0
- package/src/error-tracking/index.ts +5 -0
- package/src/error-tracking/parsers/base.ts +29 -0
- package/src/error-tracking/parsers/chrome.ts +53 -0
- package/src/error-tracking/parsers/gecko.ts +38 -0
- package/src/error-tracking/parsers/index.ts +104 -0
- package/src/error-tracking/parsers/node.ts +111 -0
- package/src/error-tracking/parsers/opera.ts +18 -0
- package/src/error-tracking/parsers/react-native.ts +0 -0
- package/src/error-tracking/parsers/safari.ts +33 -0
- package/src/error-tracking/parsers/winjs.ts +12 -0
- package/src/error-tracking/types.ts +107 -0
- package/src/error-tracking/utils.ts +39 -0
- package/src/eventemitter.ts +27 -0
- package/src/featureFlagUtils.ts +192 -0
- package/src/gzip.ts +29 -0
- package/src/index.ts +8 -0
- package/src/posthog-core-stateless.ts +1226 -0
- package/src/posthog-core.ts +958 -0
- package/src/testing/PostHogCoreTestClient.ts +91 -0
- package/src/testing/index.ts +2 -0
- package/src/testing/test-utils.ts +47 -0
- package/src/types.ts +544 -0
- package/src/utils/bucketed-rate-limiter.spec.ts +33 -0
- package/src/utils/bucketed-rate-limiter.ts +85 -0
- package/src/utils/index.ts +98 -0
- package/src/utils/number-utils.spec.ts +89 -0
- package/src/utils/number-utils.ts +30 -0
- package/src/utils/promise-queue.spec.ts +55 -0
- package/src/utils/promise-queue.ts +30 -0
- package/src/utils/string-utils.ts +23 -0
- package/src/utils/type-utils.ts +134 -0
- 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'
|