@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,958 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PostHogAutocaptureElement,
|
|
3
|
+
PostHogFlagsResponse,
|
|
4
|
+
PostHogCoreOptions,
|
|
5
|
+
PostHogEventProperties,
|
|
6
|
+
PostHogCaptureOptions,
|
|
7
|
+
JsonType,
|
|
8
|
+
PostHogRemoteConfig,
|
|
9
|
+
FeatureFlagValue,
|
|
10
|
+
PostHogV2FlagsResponse,
|
|
11
|
+
PostHogV1FlagsResponse,
|
|
12
|
+
PostHogFeatureFlagDetails,
|
|
13
|
+
PostHogFlagsStorageFormat,
|
|
14
|
+
FeatureFlagDetail,
|
|
15
|
+
Survey,
|
|
16
|
+
SurveyResponse,
|
|
17
|
+
PostHogGroupProperties,
|
|
18
|
+
} from './types'
|
|
19
|
+
import {
|
|
20
|
+
createFlagsResponseFromFlagsAndPayloads,
|
|
21
|
+
getFeatureFlagValue,
|
|
22
|
+
getFlagValuesFromFlags,
|
|
23
|
+
getPayloadsFromFlags,
|
|
24
|
+
normalizeFlagsResponse,
|
|
25
|
+
updateFlagValue,
|
|
26
|
+
} from './featureFlagUtils'
|
|
27
|
+
import { Compression, PostHogPersistedProperty } from './types'
|
|
28
|
+
import { maybeAdd, PostHogCoreStateless, QuotaLimitedFeature } from './posthog-core-stateless'
|
|
29
|
+
import { uuidv7 } from './vendor/uuidv7'
|
|
30
|
+
import { isPlainError } from './utils'
|
|
31
|
+
|
|
32
|
+
export abstract class PostHogCore extends PostHogCoreStateless {
|
|
33
|
+
// options
|
|
34
|
+
private sendFeatureFlagEvent: boolean
|
|
35
|
+
private flagCallReported: { [key: string]: boolean } = {}
|
|
36
|
+
|
|
37
|
+
// internal
|
|
38
|
+
protected _flagsResponsePromise?: Promise<PostHogFlagsResponse | undefined> // TODO: come back to this, fix typing
|
|
39
|
+
protected _sessionExpirationTimeSeconds: number
|
|
40
|
+
private _sessionMaxLengthSeconds: number = 24 * 60 * 60 // 24 hours
|
|
41
|
+
protected sessionProps: PostHogEventProperties = {}
|
|
42
|
+
|
|
43
|
+
constructor(apiKey: string, options?: PostHogCoreOptions) {
|
|
44
|
+
// Default for stateful mode is to not disable geoip. Only override if explicitly set
|
|
45
|
+
const disableGeoipOption = options?.disableGeoip ?? false
|
|
46
|
+
|
|
47
|
+
// Default for stateful mode is to timeout at 10s. Only override if explicitly set
|
|
48
|
+
const featureFlagsRequestTimeoutMs = options?.featureFlagsRequestTimeoutMs ?? 10000 // 10 seconds
|
|
49
|
+
|
|
50
|
+
super(apiKey, { ...options, disableGeoip: disableGeoipOption, featureFlagsRequestTimeoutMs })
|
|
51
|
+
|
|
52
|
+
this.sendFeatureFlagEvent = options?.sendFeatureFlagEvent ?? true
|
|
53
|
+
this._sessionExpirationTimeSeconds = options?.sessionExpirationTimeSeconds ?? 1800 // 30 minutes
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
protected setupBootstrap(options?: Partial<PostHogCoreOptions>): void {
|
|
57
|
+
const bootstrap = options?.bootstrap
|
|
58
|
+
if (!bootstrap) {
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// bootstrap options are only set if no persisted values are found
|
|
63
|
+
// this is to prevent overwriting existing values
|
|
64
|
+
if (bootstrap.distinctId) {
|
|
65
|
+
if (bootstrap.isIdentifiedId) {
|
|
66
|
+
const distinctId = this.getPersistedProperty(PostHogPersistedProperty.DistinctId)
|
|
67
|
+
|
|
68
|
+
if (!distinctId) {
|
|
69
|
+
this.setPersistedProperty(PostHogPersistedProperty.DistinctId, bootstrap.distinctId)
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
const anonymousId = this.getPersistedProperty(PostHogPersistedProperty.AnonymousId)
|
|
73
|
+
|
|
74
|
+
if (!anonymousId) {
|
|
75
|
+
this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, bootstrap.distinctId)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const bootstrapFeatureFlags = bootstrap.featureFlags
|
|
81
|
+
const bootstrapFeatureFlagPayloads = bootstrap.featureFlagPayloads ?? {}
|
|
82
|
+
if (bootstrapFeatureFlags && Object.keys(bootstrapFeatureFlags).length) {
|
|
83
|
+
const normalizedBootstrapFeatureFlagDetails = createFlagsResponseFromFlagsAndPayloads(
|
|
84
|
+
bootstrapFeatureFlags,
|
|
85
|
+
bootstrapFeatureFlagPayloads
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if (Object.keys(normalizedBootstrapFeatureFlagDetails.flags).length > 0) {
|
|
89
|
+
this.setBootstrappedFeatureFlagDetails(normalizedBootstrapFeatureFlagDetails)
|
|
90
|
+
|
|
91
|
+
const currentFeatureFlagDetails = this.getKnownFeatureFlagDetails() || { flags: {}, requestId: undefined }
|
|
92
|
+
const newFeatureFlagDetails = {
|
|
93
|
+
flags: {
|
|
94
|
+
...normalizedBootstrapFeatureFlagDetails.flags,
|
|
95
|
+
...currentFeatureFlagDetails.flags,
|
|
96
|
+
},
|
|
97
|
+
requestId: normalizedBootstrapFeatureFlagDetails.requestId,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this.setKnownFeatureFlagDetails(newFeatureFlagDetails)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private clearProps(): void {
|
|
106
|
+
this.props = undefined
|
|
107
|
+
this.sessionProps = {}
|
|
108
|
+
this.flagCallReported = {}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
on(event: string, cb: (...args: any[]) => void): () => void {
|
|
112
|
+
return this._events.on(event, cb)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
reset(propertiesToKeep?: PostHogPersistedProperty[]): void {
|
|
116
|
+
this.wrap(() => {
|
|
117
|
+
const allPropertiesToKeep = [PostHogPersistedProperty.Queue, ...(propertiesToKeep || [])]
|
|
118
|
+
|
|
119
|
+
// clean up props
|
|
120
|
+
this.clearProps()
|
|
121
|
+
|
|
122
|
+
for (const key of <(keyof typeof PostHogPersistedProperty)[]>Object.keys(PostHogPersistedProperty)) {
|
|
123
|
+
if (!allPropertiesToKeep.includes(PostHogPersistedProperty[key])) {
|
|
124
|
+
this.setPersistedProperty((PostHogPersistedProperty as any)[key], null)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.reloadFeatureFlags()
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
protected getCommonEventProperties(): PostHogEventProperties {
|
|
133
|
+
const featureFlags = this.getFeatureFlags()
|
|
134
|
+
|
|
135
|
+
const featureVariantProperties: Record<string, FeatureFlagValue> = {}
|
|
136
|
+
if (featureFlags) {
|
|
137
|
+
for (const [feature, variant] of Object.entries(featureFlags)) {
|
|
138
|
+
featureVariantProperties[`$feature/${feature}`] = variant
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
...maybeAdd('$active_feature_flags', featureFlags ? Object.keys(featureFlags) : undefined),
|
|
143
|
+
...featureVariantProperties,
|
|
144
|
+
...super.getCommonEventProperties(),
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private enrichProperties(properties?: PostHogEventProperties): PostHogEventProperties {
|
|
149
|
+
return {
|
|
150
|
+
...this.props, // Persisted properties first
|
|
151
|
+
...this.sessionProps, // Followed by session properties
|
|
152
|
+
...(properties || {}), // Followed by user specified properties
|
|
153
|
+
...this.getCommonEventProperties(), // Followed by FF props
|
|
154
|
+
$session_id: this.getSessionId(),
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Returns the current session_id.
|
|
160
|
+
*
|
|
161
|
+
* @remarks
|
|
162
|
+
* This should only be used for informative purposes.
|
|
163
|
+
* Any actual internal use case for the session_id should be handled by the sessionManager.
|
|
164
|
+
*
|
|
165
|
+
* @public
|
|
166
|
+
*
|
|
167
|
+
* @returns The stored session ID for the current session. This may be an empty string if the client is not yet fully initialized.
|
|
168
|
+
*/
|
|
169
|
+
getSessionId(): string {
|
|
170
|
+
if (!this._isInitialized) {
|
|
171
|
+
return ''
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let sessionId = this.getPersistedProperty<string>(PostHogPersistedProperty.SessionId)
|
|
175
|
+
const sessionLastTimestamp = this.getPersistedProperty<number>(PostHogPersistedProperty.SessionLastTimestamp) || 0
|
|
176
|
+
const sessionStartTimestamp = this.getPersistedProperty<number>(PostHogPersistedProperty.SessionStartTimestamp) || 0
|
|
177
|
+
const now = Date.now()
|
|
178
|
+
const sessionLastDif = now - sessionLastTimestamp
|
|
179
|
+
const sessionStartDif = now - sessionStartTimestamp
|
|
180
|
+
if (
|
|
181
|
+
!sessionId ||
|
|
182
|
+
sessionLastDif > this._sessionExpirationTimeSeconds * 1000 ||
|
|
183
|
+
sessionStartDif > this._sessionMaxLengthSeconds * 1000
|
|
184
|
+
) {
|
|
185
|
+
sessionId = uuidv7()
|
|
186
|
+
this.setPersistedProperty(PostHogPersistedProperty.SessionId, sessionId)
|
|
187
|
+
this.setPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp, now)
|
|
188
|
+
}
|
|
189
|
+
this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, now)
|
|
190
|
+
|
|
191
|
+
return sessionId
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
resetSessionId(): void {
|
|
195
|
+
this.wrap(() => {
|
|
196
|
+
this.setPersistedProperty(PostHogPersistedProperty.SessionId, null)
|
|
197
|
+
this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, null)
|
|
198
|
+
this.setPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp, null)
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Returns the current anonymous ID.
|
|
204
|
+
*
|
|
205
|
+
* This is the ID assigned to users before they are identified. It's used to track
|
|
206
|
+
* anonymous users and link them to identified users when they sign up.
|
|
207
|
+
*
|
|
208
|
+
* {@label Identification}
|
|
209
|
+
*
|
|
210
|
+
* @example
|
|
211
|
+
* ```js
|
|
212
|
+
* // get the anonymous ID
|
|
213
|
+
* const anonId = posthog.getAnonymousId()
|
|
214
|
+
* console.log('Anonymous ID:', anonId)
|
|
215
|
+
* ```
|
|
216
|
+
*
|
|
217
|
+
* @public
|
|
218
|
+
*
|
|
219
|
+
* @returns {string} The stored anonymous ID. This may be an empty string if the client is not yet fully initialized.
|
|
220
|
+
*/
|
|
221
|
+
getAnonymousId(): string {
|
|
222
|
+
if (!this._isInitialized) {
|
|
223
|
+
return ''
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
let anonId = this.getPersistedProperty<string>(PostHogPersistedProperty.AnonymousId)
|
|
227
|
+
if (!anonId) {
|
|
228
|
+
anonId = uuidv7()
|
|
229
|
+
this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, anonId)
|
|
230
|
+
}
|
|
231
|
+
return anonId
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* * @returns {string} The stored distinct ID. This may be an empty string if the client is not yet fully initialized.
|
|
236
|
+
*/
|
|
237
|
+
getDistinctId(): string {
|
|
238
|
+
if (!this._isInitialized) {
|
|
239
|
+
return ''
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return this.getPersistedProperty<string>(PostHogPersistedProperty.DistinctId) || this.getAnonymousId()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
registerForSession(properties: PostHogEventProperties): void {
|
|
246
|
+
this.sessionProps = {
|
|
247
|
+
...this.sessionProps,
|
|
248
|
+
...properties,
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
unregisterForSession(property: string): void {
|
|
253
|
+
delete this.sessionProps[property]
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/***
|
|
257
|
+
*** TRACKING
|
|
258
|
+
***/
|
|
259
|
+
|
|
260
|
+
identify(distinctId?: string, properties?: PostHogEventProperties, options?: PostHogCaptureOptions): void {
|
|
261
|
+
this.wrap(() => {
|
|
262
|
+
const previousDistinctId = this.getDistinctId()
|
|
263
|
+
distinctId = distinctId || previousDistinctId
|
|
264
|
+
|
|
265
|
+
if (properties?.$groups) {
|
|
266
|
+
this.groups(properties.$groups as PostHogGroupProperties)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// promote $set and $set_once to top level
|
|
270
|
+
const userPropsOnce = properties?.$set_once
|
|
271
|
+
delete properties?.$set_once
|
|
272
|
+
|
|
273
|
+
// if no $set is provided we assume all properties are $set
|
|
274
|
+
const userProps = properties?.$set || properties
|
|
275
|
+
|
|
276
|
+
const allProperties = this.enrichProperties({
|
|
277
|
+
$anon_distinct_id: this.getAnonymousId(),
|
|
278
|
+
...maybeAdd('$set', userProps),
|
|
279
|
+
...maybeAdd('$set_once', userPropsOnce),
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
if (distinctId !== previousDistinctId) {
|
|
283
|
+
// We keep the AnonymousId to be used by flags calls and identify to link the previousId
|
|
284
|
+
this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, previousDistinctId)
|
|
285
|
+
this.setPersistedProperty(PostHogPersistedProperty.DistinctId, distinctId)
|
|
286
|
+
this.reloadFeatureFlags()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
super.identifyStateless(distinctId, allProperties, options)
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
capture(event: string, properties?: PostHogEventProperties, options?: PostHogCaptureOptions): void {
|
|
294
|
+
this.wrap(() => {
|
|
295
|
+
const distinctId = this.getDistinctId()
|
|
296
|
+
|
|
297
|
+
if (properties?.$groups) {
|
|
298
|
+
this.groups(properties.$groups as PostHogGroupProperties)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const allProperties = this.enrichProperties(properties)
|
|
302
|
+
|
|
303
|
+
super.captureStateless(distinctId, event, allProperties, options)
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
alias(alias: string): void {
|
|
308
|
+
this.wrap(() => {
|
|
309
|
+
const distinctId = this.getDistinctId()
|
|
310
|
+
const allProperties = this.enrichProperties({})
|
|
311
|
+
|
|
312
|
+
super.aliasStateless(alias, distinctId, allProperties)
|
|
313
|
+
})
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
autocapture(
|
|
317
|
+
eventType: string,
|
|
318
|
+
elements: PostHogAutocaptureElement[],
|
|
319
|
+
properties: PostHogEventProperties = {},
|
|
320
|
+
options?: PostHogCaptureOptions
|
|
321
|
+
): void {
|
|
322
|
+
this.wrap(() => {
|
|
323
|
+
const distinctId = this.getDistinctId()
|
|
324
|
+
const payload = {
|
|
325
|
+
distinct_id: distinctId,
|
|
326
|
+
event: '$autocapture',
|
|
327
|
+
properties: {
|
|
328
|
+
...this.enrichProperties(properties),
|
|
329
|
+
$event_type: eventType,
|
|
330
|
+
$elements: elements,
|
|
331
|
+
},
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
this.enqueue('autocapture', payload, options)
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/***
|
|
339
|
+
*** GROUPS
|
|
340
|
+
***/
|
|
341
|
+
|
|
342
|
+
groups(groups: PostHogGroupProperties): void {
|
|
343
|
+
this.wrap(() => {
|
|
344
|
+
// Get persisted groups
|
|
345
|
+
const existingGroups = this.props.$groups || {}
|
|
346
|
+
|
|
347
|
+
this.register({
|
|
348
|
+
$groups: {
|
|
349
|
+
...(existingGroups as PostHogGroupProperties),
|
|
350
|
+
...groups,
|
|
351
|
+
},
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
if (Object.keys(groups).find((type) => existingGroups[type as keyof typeof existingGroups] !== groups[type])) {
|
|
355
|
+
this.reloadFeatureFlags()
|
|
356
|
+
}
|
|
357
|
+
})
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
group(
|
|
361
|
+
groupType: string,
|
|
362
|
+
groupKey: string | number,
|
|
363
|
+
groupProperties?: PostHogEventProperties,
|
|
364
|
+
options?: PostHogCaptureOptions
|
|
365
|
+
): void {
|
|
366
|
+
this.wrap(() => {
|
|
367
|
+
this.groups({
|
|
368
|
+
[groupType]: groupKey,
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
if (groupProperties) {
|
|
372
|
+
this.groupIdentify(groupType, groupKey, groupProperties, options)
|
|
373
|
+
}
|
|
374
|
+
})
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
groupIdentify(
|
|
378
|
+
groupType: string,
|
|
379
|
+
groupKey: string | number,
|
|
380
|
+
groupProperties?: PostHogEventProperties,
|
|
381
|
+
options?: PostHogCaptureOptions
|
|
382
|
+
): void {
|
|
383
|
+
this.wrap(() => {
|
|
384
|
+
const distinctId = this.getDistinctId()
|
|
385
|
+
const eventProperties = this.enrichProperties({})
|
|
386
|
+
super.groupIdentifyStateless(groupType, groupKey, groupProperties, options, distinctId, eventProperties)
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/***
|
|
391
|
+
* PROPERTIES
|
|
392
|
+
***/
|
|
393
|
+
setPersonPropertiesForFlags(properties: { [type: string]: string }): void {
|
|
394
|
+
this.wrap(() => {
|
|
395
|
+
// Get persisted person properties
|
|
396
|
+
const existingProperties =
|
|
397
|
+
this.getPersistedProperty<Record<string, string>>(PostHogPersistedProperty.PersonProperties) || {}
|
|
398
|
+
|
|
399
|
+
this.setPersistedProperty<PostHogEventProperties>(PostHogPersistedProperty.PersonProperties, {
|
|
400
|
+
...existingProperties,
|
|
401
|
+
...properties,
|
|
402
|
+
})
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
resetPersonPropertiesForFlags(): void {
|
|
407
|
+
this.wrap(() => {
|
|
408
|
+
this.setPersistedProperty<PostHogEventProperties>(PostHogPersistedProperty.PersonProperties, null)
|
|
409
|
+
})
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
setGroupPropertiesForFlags(properties: { [type: string]: Record<string, string> }): void {
|
|
413
|
+
this.wrap(() => {
|
|
414
|
+
// Get persisted group properties
|
|
415
|
+
const existingProperties =
|
|
416
|
+
this.getPersistedProperty<Record<string, Record<string, string>>>(PostHogPersistedProperty.GroupProperties) ||
|
|
417
|
+
{}
|
|
418
|
+
|
|
419
|
+
if (Object.keys(existingProperties).length !== 0) {
|
|
420
|
+
Object.keys(existingProperties).forEach((groupType) => {
|
|
421
|
+
existingProperties[groupType] = {
|
|
422
|
+
...existingProperties[groupType],
|
|
423
|
+
...properties[groupType],
|
|
424
|
+
}
|
|
425
|
+
delete properties[groupType]
|
|
426
|
+
})
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
this.setPersistedProperty<PostHogEventProperties>(PostHogPersistedProperty.GroupProperties, {
|
|
430
|
+
...existingProperties,
|
|
431
|
+
...properties,
|
|
432
|
+
})
|
|
433
|
+
})
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
resetGroupPropertiesForFlags(): void {
|
|
437
|
+
this.wrap(() => {
|
|
438
|
+
this.setPersistedProperty<PostHogEventProperties>(PostHogPersistedProperty.GroupProperties, null)
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
private async remoteConfigAsync(): Promise<PostHogRemoteConfig | undefined> {
|
|
443
|
+
await this._initPromise
|
|
444
|
+
if (this._remoteConfigResponsePromise) {
|
|
445
|
+
return this._remoteConfigResponsePromise
|
|
446
|
+
}
|
|
447
|
+
return this._remoteConfigAsync()
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/***
|
|
451
|
+
*** FEATURE FLAGS
|
|
452
|
+
***/
|
|
453
|
+
private async flagsAsync(sendAnonDistinctId: boolean = true): Promise<PostHogFlagsResponse | undefined> {
|
|
454
|
+
await this._initPromise
|
|
455
|
+
if (this._flagsResponsePromise) {
|
|
456
|
+
return this._flagsResponsePromise
|
|
457
|
+
}
|
|
458
|
+
return this._flagsAsync(sendAnonDistinctId)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private cacheSessionReplay(source: string, response?: PostHogRemoteConfig): void {
|
|
462
|
+
const sessionReplay = response?.sessionRecording
|
|
463
|
+
if (sessionReplay) {
|
|
464
|
+
this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, sessionReplay)
|
|
465
|
+
this.logMsgIfDebug(() =>
|
|
466
|
+
console.log('PostHog Debug', `Session replay config from ${source}: `, JSON.stringify(sessionReplay))
|
|
467
|
+
)
|
|
468
|
+
} else if (typeof sessionReplay === 'boolean' && sessionReplay === false) {
|
|
469
|
+
// if session replay is disabled, we don't need to cache it
|
|
470
|
+
// we need to check for this because the response might be undefined (/flags does not return sessionRecording yet)
|
|
471
|
+
this.logMsgIfDebug(() => console.info('PostHog Debug', `Session replay config from ${source} disabled.`))
|
|
472
|
+
this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, null)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private async _remoteConfigAsync(): Promise<PostHogRemoteConfig | undefined> {
|
|
477
|
+
this._remoteConfigResponsePromise = this._initPromise
|
|
478
|
+
.then(() => {
|
|
479
|
+
let remoteConfig = this.getPersistedProperty<Omit<PostHogRemoteConfig, 'surveys'>>(
|
|
480
|
+
PostHogPersistedProperty.RemoteConfig
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Cached remote config: ', JSON.stringify(remoteConfig)))
|
|
484
|
+
|
|
485
|
+
return super.getRemoteConfig().then((response) => {
|
|
486
|
+
if (response) {
|
|
487
|
+
const remoteConfigWithoutSurveys = { ...response }
|
|
488
|
+
delete remoteConfigWithoutSurveys.surveys
|
|
489
|
+
|
|
490
|
+
this.logMsgIfDebug(() =>
|
|
491
|
+
console.log('PostHog Debug', 'Fetched remote config: ', JSON.stringify(remoteConfigWithoutSurveys))
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
if (this.disableSurveys === false) {
|
|
495
|
+
const surveys = response.surveys
|
|
496
|
+
|
|
497
|
+
let hasSurveys = true
|
|
498
|
+
|
|
499
|
+
if (!Array.isArray(surveys)) {
|
|
500
|
+
// If surveys is not an array, it means there are no surveys (its a boolean instead)
|
|
501
|
+
this.logMsgIfDebug(() => console.log('PostHog Debug', 'There are no surveys.'))
|
|
502
|
+
hasSurveys = false
|
|
503
|
+
} else {
|
|
504
|
+
this.logMsgIfDebug(() =>
|
|
505
|
+
console.log('PostHog Debug', 'Surveys fetched from remote config: ', JSON.stringify(surveys))
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (hasSurveys) {
|
|
510
|
+
this.setPersistedProperty<SurveyResponse['surveys']>(
|
|
511
|
+
PostHogPersistedProperty.Surveys,
|
|
512
|
+
surveys as Survey[]
|
|
513
|
+
)
|
|
514
|
+
} else {
|
|
515
|
+
this.setPersistedProperty<SurveyResponse['surveys']>(PostHogPersistedProperty.Surveys, null)
|
|
516
|
+
}
|
|
517
|
+
} else {
|
|
518
|
+
this.setPersistedProperty<SurveyResponse['surveys']>(PostHogPersistedProperty.Surveys, null)
|
|
519
|
+
}
|
|
520
|
+
// we cache the surveys in its own storage key
|
|
521
|
+
this.setPersistedProperty<Omit<PostHogRemoteConfig, 'surveys'>>(
|
|
522
|
+
PostHogPersistedProperty.RemoteConfig,
|
|
523
|
+
remoteConfigWithoutSurveys
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
this.cacheSessionReplay('remote config', response)
|
|
527
|
+
|
|
528
|
+
// we only dont load flags if the remote config has no feature flags
|
|
529
|
+
if (response.hasFeatureFlags === false) {
|
|
530
|
+
// resetting flags to empty object
|
|
531
|
+
this.setKnownFeatureFlagDetails({ flags: {} })
|
|
532
|
+
|
|
533
|
+
this.logMsgIfDebug(() => console.warn('Remote config has no feature flags, will not load feature flags.'))
|
|
534
|
+
} else if (this.preloadFeatureFlags !== false) {
|
|
535
|
+
this.reloadFeatureFlags()
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (!response.supportedCompression?.includes(Compression.GZipJS)) {
|
|
539
|
+
this.disableCompression = true
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
remoteConfig = response
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return remoteConfig
|
|
546
|
+
})
|
|
547
|
+
})
|
|
548
|
+
.finally(() => {
|
|
549
|
+
this._remoteConfigResponsePromise = undefined
|
|
550
|
+
})
|
|
551
|
+
return this._remoteConfigResponsePromise
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private async _flagsAsync(sendAnonDistinctId: boolean = true): Promise<PostHogFlagsResponse | undefined> {
|
|
555
|
+
this._flagsResponsePromise = this._initPromise
|
|
556
|
+
.then(async () => {
|
|
557
|
+
const distinctId = this.getDistinctId()
|
|
558
|
+
const groups = this.props.$groups || {}
|
|
559
|
+
const personProperties =
|
|
560
|
+
this.getPersistedProperty<Record<string, string>>(PostHogPersistedProperty.PersonProperties) || {}
|
|
561
|
+
const groupProperties =
|
|
562
|
+
this.getPersistedProperty<Record<string, Record<string, string>>>(PostHogPersistedProperty.GroupProperties) ||
|
|
563
|
+
{}
|
|
564
|
+
|
|
565
|
+
const extraProperties = {
|
|
566
|
+
$anon_distinct_id: sendAnonDistinctId ? this.getAnonymousId() : undefined,
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const res = await super.getFlags(
|
|
570
|
+
distinctId,
|
|
571
|
+
groups as PostHogGroupProperties,
|
|
572
|
+
personProperties,
|
|
573
|
+
groupProperties,
|
|
574
|
+
extraProperties
|
|
575
|
+
)
|
|
576
|
+
// Add check for quota limitation on feature flags
|
|
577
|
+
if (res?.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
|
|
578
|
+
// Unset all feature flags by setting to null
|
|
579
|
+
this.setKnownFeatureFlagDetails(null)
|
|
580
|
+
console.warn(
|
|
581
|
+
'[FEATURE FLAGS] Feature flags quota limit exceeded - unsetting all flags. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts'
|
|
582
|
+
)
|
|
583
|
+
return res
|
|
584
|
+
}
|
|
585
|
+
if (res?.featureFlags) {
|
|
586
|
+
// clear flag call reported if we have new flags since they might have changed
|
|
587
|
+
if (this.sendFeatureFlagEvent) {
|
|
588
|
+
this.flagCallReported = {}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
let newFeatureFlagDetails = res
|
|
592
|
+
if (res.errorsWhileComputingFlags) {
|
|
593
|
+
// if not all flags were computed, we upsert flags instead of replacing them
|
|
594
|
+
const currentFlagDetails = this.getKnownFeatureFlagDetails()
|
|
595
|
+
this.logMsgIfDebug(() =>
|
|
596
|
+
console.log('PostHog Debug', 'Cached feature flags: ', JSON.stringify(currentFlagDetails))
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
newFeatureFlagDetails = {
|
|
600
|
+
...res,
|
|
601
|
+
flags: { ...currentFlagDetails?.flags, ...res.flags },
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
this.setKnownFeatureFlagDetails(newFeatureFlagDetails)
|
|
605
|
+
// Mark that we hit the /flags endpoint so we can capture this in the $feature_flag_called event
|
|
606
|
+
this.setPersistedProperty(PostHogPersistedProperty.FlagsEndpointWasHit, true)
|
|
607
|
+
this.cacheSessionReplay('flags', res)
|
|
608
|
+
}
|
|
609
|
+
return res
|
|
610
|
+
})
|
|
611
|
+
.finally(() => {
|
|
612
|
+
this._flagsResponsePromise = undefined
|
|
613
|
+
})
|
|
614
|
+
return this._flagsResponsePromise
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// We only store the flags and request id in the feature flag details storage key
|
|
618
|
+
private setKnownFeatureFlagDetails(flagsResponse: PostHogFlagsStorageFormat | null): void {
|
|
619
|
+
this.wrap(() => {
|
|
620
|
+
this.setPersistedProperty<PostHogFlagsStorageFormat>(PostHogPersistedProperty.FeatureFlagDetails, flagsResponse)
|
|
621
|
+
|
|
622
|
+
this._events.emit('featureflags', getFlagValuesFromFlags(flagsResponse?.flags ?? {}))
|
|
623
|
+
})
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
private getKnownFeatureFlagDetails(): PostHogFeatureFlagDetails | undefined {
|
|
627
|
+
const storedDetails = this.getPersistedProperty<PostHogFlagsStorageFormat>(
|
|
628
|
+
PostHogPersistedProperty.FeatureFlagDetails
|
|
629
|
+
)
|
|
630
|
+
if (!storedDetails) {
|
|
631
|
+
// Rebuild from the stored feature flags and feature flag payloads
|
|
632
|
+
const featureFlags = this.getPersistedProperty<PostHogFlagsResponse['featureFlags']>(
|
|
633
|
+
PostHogPersistedProperty.FeatureFlags
|
|
634
|
+
)
|
|
635
|
+
const featureFlagPayloads = this.getPersistedProperty<PostHogFlagsResponse['featureFlagPayloads']>(
|
|
636
|
+
PostHogPersistedProperty.FeatureFlagPayloads
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
if (featureFlags === undefined && featureFlagPayloads === undefined) {
|
|
640
|
+
return undefined
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
return createFlagsResponseFromFlagsAndPayloads(featureFlags ?? {}, featureFlagPayloads ?? {})
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return normalizeFlagsResponse(
|
|
647
|
+
storedDetails as PostHogV1FlagsResponse | PostHogV2FlagsResponse
|
|
648
|
+
) as PostHogFeatureFlagDetails
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
protected getKnownFeatureFlags(): PostHogFlagsResponse['featureFlags'] | undefined {
|
|
652
|
+
const featureFlagDetails = this.getKnownFeatureFlagDetails()
|
|
653
|
+
if (!featureFlagDetails) {
|
|
654
|
+
return undefined
|
|
655
|
+
}
|
|
656
|
+
return getFlagValuesFromFlags(featureFlagDetails.flags)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
private getKnownFeatureFlagPayloads(): PostHogFlagsResponse['featureFlagPayloads'] | undefined {
|
|
660
|
+
const featureFlagDetails = this.getKnownFeatureFlagDetails()
|
|
661
|
+
if (!featureFlagDetails) {
|
|
662
|
+
return undefined
|
|
663
|
+
}
|
|
664
|
+
return getPayloadsFromFlags(featureFlagDetails.flags)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
private getBootstrappedFeatureFlagDetails(): PostHogFeatureFlagDetails | undefined {
|
|
668
|
+
const details = this.getPersistedProperty<PostHogFeatureFlagDetails>(
|
|
669
|
+
PostHogPersistedProperty.BootstrapFeatureFlagDetails
|
|
670
|
+
)
|
|
671
|
+
if (!details) {
|
|
672
|
+
return undefined
|
|
673
|
+
}
|
|
674
|
+
return details
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
private setBootstrappedFeatureFlagDetails(details: PostHogFeatureFlagDetails): void {
|
|
678
|
+
this.setPersistedProperty<PostHogFeatureFlagDetails>(PostHogPersistedProperty.BootstrapFeatureFlagDetails, details)
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
private getBootstrappedFeatureFlags(): PostHogFlagsResponse['featureFlags'] | undefined {
|
|
682
|
+
const details = this.getBootstrappedFeatureFlagDetails()
|
|
683
|
+
if (!details) {
|
|
684
|
+
return undefined
|
|
685
|
+
}
|
|
686
|
+
return getFlagValuesFromFlags(details.flags)
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
private getBootstrappedFeatureFlagPayloads(): PostHogFlagsResponse['featureFlagPayloads'] | undefined {
|
|
690
|
+
const details = this.getBootstrappedFeatureFlagDetails()
|
|
691
|
+
if (!details) {
|
|
692
|
+
return undefined
|
|
693
|
+
}
|
|
694
|
+
return getPayloadsFromFlags(details.flags)
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
getFeatureFlag(key: string): FeatureFlagValue | undefined {
|
|
698
|
+
const details = this.getFeatureFlagDetails()
|
|
699
|
+
|
|
700
|
+
if (!details) {
|
|
701
|
+
// If we haven't loaded flags yet, or errored out, we respond with undefined
|
|
702
|
+
return undefined
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const featureFlag = details.flags[key]
|
|
706
|
+
|
|
707
|
+
let response = getFeatureFlagValue(featureFlag)
|
|
708
|
+
|
|
709
|
+
if (response === undefined) {
|
|
710
|
+
// For cases where the flag is unknown, return false
|
|
711
|
+
response = false
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (this.sendFeatureFlagEvent && !this.flagCallReported[key]) {
|
|
715
|
+
const bootstrappedResponse = this.getBootstrappedFeatureFlags()?.[key]
|
|
716
|
+
const bootstrappedPayload = this.getBootstrappedFeatureFlagPayloads()?.[key]
|
|
717
|
+
|
|
718
|
+
this.flagCallReported[key] = true
|
|
719
|
+
this.capture('$feature_flag_called', {
|
|
720
|
+
$feature_flag: key,
|
|
721
|
+
$feature_flag_response: response,
|
|
722
|
+
...maybeAdd('$feature_flag_id', featureFlag?.metadata?.id),
|
|
723
|
+
...maybeAdd('$feature_flag_version', featureFlag?.metadata?.version),
|
|
724
|
+
...maybeAdd('$feature_flag_reason', featureFlag?.reason?.description ?? featureFlag?.reason?.code),
|
|
725
|
+
...maybeAdd('$feature_flag_bootstrapped_response', bootstrappedResponse),
|
|
726
|
+
...maybeAdd('$feature_flag_bootstrapped_payload', bootstrappedPayload),
|
|
727
|
+
// If we haven't yet received a response from the /flags endpoint, we must have used the bootstrapped value
|
|
728
|
+
$used_bootstrap_value: !this.getPersistedProperty(PostHogPersistedProperty.FlagsEndpointWasHit),
|
|
729
|
+
...maybeAdd('$feature_flag_request_id', details.requestId),
|
|
730
|
+
})
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// If we have flags we either return the value (true or string) or false
|
|
734
|
+
return response
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
getFeatureFlagPayload(key: string): JsonType | undefined {
|
|
738
|
+
const payloads = this.getFeatureFlagPayloads()
|
|
739
|
+
|
|
740
|
+
if (!payloads) {
|
|
741
|
+
return undefined
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const response = payloads[key]
|
|
745
|
+
|
|
746
|
+
// Undefined means a loading or missing data issue. Null means evaluation happened and there was no match
|
|
747
|
+
if (response === undefined) {
|
|
748
|
+
return null
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return response
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
getFeatureFlagPayloads(): PostHogFlagsResponse['featureFlagPayloads'] | undefined {
|
|
755
|
+
return this.getFeatureFlagDetails()?.featureFlagPayloads
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
getFeatureFlags(): PostHogFlagsResponse['featureFlags'] | undefined {
|
|
759
|
+
// NOTE: We don't check for _initPromise here as the function is designed to be
|
|
760
|
+
// callable before the state being loaded anyways
|
|
761
|
+
return this.getFeatureFlagDetails()?.featureFlags
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
getFeatureFlagDetails(): PostHogFeatureFlagDetails | undefined {
|
|
765
|
+
// NOTE: We don't check for _initPromise here as the function is designed to be
|
|
766
|
+
// callable before the state being loaded anyways
|
|
767
|
+
let details = this.getKnownFeatureFlagDetails()
|
|
768
|
+
const overriddenFlags = this.getPersistedProperty<PostHogFlagsResponse['featureFlags']>(
|
|
769
|
+
PostHogPersistedProperty.OverrideFeatureFlags
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
if (!overriddenFlags) {
|
|
773
|
+
return details
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
details = details ?? { featureFlags: {}, featureFlagPayloads: {}, flags: {} }
|
|
777
|
+
|
|
778
|
+
const flags: Record<string, FeatureFlagDetail> = details.flags ?? {}
|
|
779
|
+
|
|
780
|
+
for (const key in overriddenFlags) {
|
|
781
|
+
if (!overriddenFlags[key]) {
|
|
782
|
+
delete flags[key]
|
|
783
|
+
} else {
|
|
784
|
+
flags[key] = updateFlagValue(flags[key], overriddenFlags[key])
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const result = {
|
|
789
|
+
...details,
|
|
790
|
+
flags,
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
return normalizeFlagsResponse(result) as PostHogFeatureFlagDetails
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
getFeatureFlagsAndPayloads(): {
|
|
797
|
+
flags: PostHogFlagsResponse['featureFlags'] | undefined
|
|
798
|
+
payloads: PostHogFlagsResponse['featureFlagPayloads'] | undefined
|
|
799
|
+
} {
|
|
800
|
+
const flags = this.getFeatureFlags()
|
|
801
|
+
const payloads = this.getFeatureFlagPayloads()
|
|
802
|
+
|
|
803
|
+
return {
|
|
804
|
+
flags,
|
|
805
|
+
payloads,
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
isFeatureEnabled(key: string): boolean | undefined {
|
|
810
|
+
const response = this.getFeatureFlag(key)
|
|
811
|
+
if (response === undefined) {
|
|
812
|
+
return undefined
|
|
813
|
+
}
|
|
814
|
+
return !!response
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// Used when we want to trigger the reload but we don't care about the result
|
|
818
|
+
reloadFeatureFlags(options?: { cb?: (err?: Error, flags?: PostHogFlagsResponse['featureFlags']) => void }): void {
|
|
819
|
+
this.flagsAsync(true)
|
|
820
|
+
.then((res) => {
|
|
821
|
+
options?.cb?.(undefined, res?.featureFlags)
|
|
822
|
+
})
|
|
823
|
+
.catch((e) => {
|
|
824
|
+
options?.cb?.(e, undefined)
|
|
825
|
+
if (!options?.cb) {
|
|
826
|
+
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Error reloading feature flags', e))
|
|
827
|
+
}
|
|
828
|
+
})
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
async reloadRemoteConfigAsync(): Promise<PostHogRemoteConfig | undefined> {
|
|
832
|
+
return await this.remoteConfigAsync()
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async reloadFeatureFlagsAsync(
|
|
836
|
+
sendAnonDistinctId?: boolean
|
|
837
|
+
): Promise<PostHogFlagsResponse['featureFlags'] | undefined> {
|
|
838
|
+
return (await this.flagsAsync(sendAnonDistinctId ?? true))?.featureFlags
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
onFeatureFlags(cb: (flags: PostHogFlagsResponse['featureFlags']) => void): () => void {
|
|
842
|
+
return this.on('featureflags', async () => {
|
|
843
|
+
const flags = this.getFeatureFlags()
|
|
844
|
+
if (flags) {
|
|
845
|
+
cb(flags)
|
|
846
|
+
}
|
|
847
|
+
})
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
onFeatureFlag(key: string, cb: (value: FeatureFlagValue) => void): () => void {
|
|
851
|
+
return this.on('featureflags', async () => {
|
|
852
|
+
const flagResponse = this.getFeatureFlag(key)
|
|
853
|
+
if (flagResponse !== undefined) {
|
|
854
|
+
cb(flagResponse)
|
|
855
|
+
}
|
|
856
|
+
})
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
async overrideFeatureFlag(flags: PostHogFlagsResponse['featureFlags'] | null): Promise<void> {
|
|
860
|
+
this.wrap(() => {
|
|
861
|
+
if (flags === null) {
|
|
862
|
+
return this.setPersistedProperty(PostHogPersistedProperty.OverrideFeatureFlags, null)
|
|
863
|
+
}
|
|
864
|
+
return this.setPersistedProperty(PostHogPersistedProperty.OverrideFeatureFlags, flags)
|
|
865
|
+
})
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* Capture a caught exception manually
|
|
870
|
+
*
|
|
871
|
+
* {@label Error tracking}
|
|
872
|
+
*
|
|
873
|
+
* @public
|
|
874
|
+
*
|
|
875
|
+
* @example
|
|
876
|
+
* ```js
|
|
877
|
+
* // Capture a caught exception
|
|
878
|
+
* try {
|
|
879
|
+
* // something that might throw
|
|
880
|
+
* } catch (error) {
|
|
881
|
+
* posthog.captureException(error)
|
|
882
|
+
* }
|
|
883
|
+
* ```
|
|
884
|
+
*
|
|
885
|
+
* @example
|
|
886
|
+
* ```js
|
|
887
|
+
* // With additional properties
|
|
888
|
+
* posthog.captureException(error, {
|
|
889
|
+
* customProperty: 'value',
|
|
890
|
+
* anotherProperty: ['I', 'can be a list'],
|
|
891
|
+
* ...
|
|
892
|
+
* })
|
|
893
|
+
* ```
|
|
894
|
+
*
|
|
895
|
+
* @param {Error} error The error to capture
|
|
896
|
+
* @param {Object} [additionalProperties] Any additional properties to add to the error event
|
|
897
|
+
* @returns {CaptureResult} The result of the capture
|
|
898
|
+
*/
|
|
899
|
+
captureException(error: unknown, additionalProperties?: PostHogEventProperties): void {
|
|
900
|
+
const properties: { [key: string]: any } = {
|
|
901
|
+
$exception_level: 'error',
|
|
902
|
+
$exception_list: [
|
|
903
|
+
{
|
|
904
|
+
type: isPlainError(error) ? error.name : 'Error',
|
|
905
|
+
value: isPlainError(error) ? error.message : error,
|
|
906
|
+
mechanism: {
|
|
907
|
+
handled: true,
|
|
908
|
+
synthetic: false,
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
],
|
|
912
|
+
...additionalProperties,
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
properties.$exception_personURL = new URL(
|
|
916
|
+
`/project/${this.apiKey}/person/${this.getDistinctId()}`,
|
|
917
|
+
this.host
|
|
918
|
+
).toString()
|
|
919
|
+
|
|
920
|
+
this.capture('$exception', properties)
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Capture written user feedback for a LLM trace. Numeric values are converted to strings.
|
|
925
|
+
*
|
|
926
|
+
* {@label LLM analytics}
|
|
927
|
+
*
|
|
928
|
+
* @public
|
|
929
|
+
*
|
|
930
|
+
* @param traceId The trace ID to capture feedback for.
|
|
931
|
+
* @param userFeedback The feedback to capture.
|
|
932
|
+
*/
|
|
933
|
+
captureTraceFeedback(traceId: string | number, userFeedback: string): void {
|
|
934
|
+
this.capture('$ai_feedback', {
|
|
935
|
+
$ai_feedback_text: userFeedback,
|
|
936
|
+
$ai_trace_id: String(traceId),
|
|
937
|
+
})
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Capture a metric for a LLM trace. Numeric values are converted to strings.
|
|
942
|
+
*
|
|
943
|
+
* {@label LLM analytics}
|
|
944
|
+
*
|
|
945
|
+
* @public
|
|
946
|
+
*
|
|
947
|
+
* @param traceId The trace ID to capture the metric for.
|
|
948
|
+
* @param metricName The name of the metric to capture.
|
|
949
|
+
* @param metricValue The value of the metric to capture.
|
|
950
|
+
*/
|
|
951
|
+
captureTraceMetric(traceId: string | number, metricName: string, metricValue: string | number | boolean): void {
|
|
952
|
+
this.capture('$ai_metric', {
|
|
953
|
+
$ai_metric_name: metricName,
|
|
954
|
+
$ai_metric_value: String(metricValue),
|
|
955
|
+
$ai_trace_id: String(traceId),
|
|
956
|
+
})
|
|
957
|
+
}
|
|
958
|
+
}
|