posthog-js-lite 4.0.0 → 4.1.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/CHANGELOG.md +4 -0
- package/lib/index.cjs +59 -171
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.ts +27 -25
- package/lib/index.mjs +59 -171
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/test/posthog-web.spec.ts +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -77,11 +77,11 @@ declare enum PostHogPersistedProperty {
|
|
|
77
77
|
InstalledAppBuild = "installed_app_build",
|
|
78
78
|
InstalledAppVersion = "installed_app_version",
|
|
79
79
|
SessionReplay = "session_replay",
|
|
80
|
-
DecideEndpointWasHit = "decide_endpoint_was_hit",
|
|
81
80
|
SurveyLastSeenDate = "survey_last_seen_date",
|
|
82
81
|
SurveysSeen = "surveys_seen",
|
|
83
82
|
Surveys = "surveys",
|
|
84
|
-
RemoteConfig = "remote_config"
|
|
83
|
+
RemoteConfig = "remote_config",
|
|
84
|
+
FlagsEndpointWasHit = "flags_endpoint_was_hit"
|
|
85
85
|
}
|
|
86
86
|
type PostHogFetchOptions = {
|
|
87
87
|
method: 'GET' | 'POST' | 'PUT' | 'PATCH';
|
|
@@ -141,7 +141,7 @@ type PostHogRemoteConfig = {
|
|
|
141
141
|
hasFeatureFlags?: boolean;
|
|
142
142
|
};
|
|
143
143
|
type FeatureFlagValue = string | boolean;
|
|
144
|
-
type
|
|
144
|
+
type PostHogFlagsResponse = Omit<PostHogRemoteConfig, 'surveys' | 'hasFeatureFlags'> & {
|
|
145
145
|
featureFlags: {
|
|
146
146
|
[key: string]: FeatureFlagValue;
|
|
147
147
|
};
|
|
@@ -187,9 +187,9 @@ type PartialWithRequired<T, K extends keyof T> = {
|
|
|
187
187
|
[P in Exclude<keyof T, K>]?: T[P];
|
|
188
188
|
};
|
|
189
189
|
/**
|
|
190
|
-
* These are the fields we care about from
|
|
190
|
+
* These are the fields we care about from PostHogFlagsResponse for feature flags.
|
|
191
191
|
*/
|
|
192
|
-
type PostHogFeatureFlagDetails = PartialWithRequired<
|
|
192
|
+
type PostHogFeatureFlagDetails = PartialWithRequired<PostHogFlagsResponse, 'flags' | 'featureFlags' | 'featureFlagPayloads' | 'requestId'>;
|
|
193
193
|
type JsonType = string | number | boolean | null | {
|
|
194
194
|
[key: string]: JsonType;
|
|
195
195
|
} | Array<JsonType> | JsonType[];
|
|
@@ -465,7 +465,7 @@ declare abstract class PostHogCoreStateless {
|
|
|
465
465
|
/***
|
|
466
466
|
*** FEATURE FLAGS
|
|
467
467
|
***/
|
|
468
|
-
protected
|
|
468
|
+
protected getFlags(distinctId: string, groups?: Record<string, string | number>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>, extraPayload?: Record<string, any>): Promise<PostHogFlagsResponse | undefined>;
|
|
469
469
|
protected getFeatureFlagStateless(key: string, distinctId: string, groups?: Record<string, string>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>, disableGeoip?: boolean): Promise<{
|
|
470
470
|
response: FeatureFlagValue | undefined;
|
|
471
471
|
requestId: string | undefined;
|
|
@@ -475,16 +475,16 @@ declare abstract class PostHogCoreStateless {
|
|
|
475
475
|
requestId: string | undefined;
|
|
476
476
|
} | undefined>;
|
|
477
477
|
protected getFeatureFlagPayloadStateless(key: string, distinctId: string, groups?: Record<string, string>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>, disableGeoip?: boolean): Promise<JsonType | undefined>;
|
|
478
|
-
protected getFeatureFlagPayloadsStateless(distinctId: string, groups?: Record<string, string>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>, disableGeoip?: boolean, flagKeysToEvaluate?: string[]): Promise<
|
|
478
|
+
protected getFeatureFlagPayloadsStateless(distinctId: string, groups?: Record<string, string>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>, disableGeoip?: boolean, flagKeysToEvaluate?: string[]): Promise<PostHogFlagsResponse['featureFlagPayloads'] | undefined>;
|
|
479
479
|
protected getFeatureFlagsStateless(distinctId: string, groups?: Record<string, string | number>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>, disableGeoip?: boolean, flagKeysToEvaluate?: string[]): Promise<{
|
|
480
|
-
flags:
|
|
481
|
-
payloads:
|
|
482
|
-
requestId:
|
|
480
|
+
flags: PostHogFlagsResponse['featureFlags'] | undefined;
|
|
481
|
+
payloads: PostHogFlagsResponse['featureFlagPayloads'] | undefined;
|
|
482
|
+
requestId: PostHogFlagsResponse['requestId'] | undefined;
|
|
483
483
|
}>;
|
|
484
484
|
protected getFeatureFlagsAndPayloadsStateless(distinctId: string, groups?: Record<string, string | number>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>, disableGeoip?: boolean, flagKeysToEvaluate?: string[]): Promise<{
|
|
485
|
-
flags:
|
|
486
|
-
payloads:
|
|
487
|
-
requestId:
|
|
485
|
+
flags: PostHogFlagsResponse['featureFlags'] | undefined;
|
|
486
|
+
payloads: PostHogFlagsResponse['featureFlagPayloads'] | undefined;
|
|
487
|
+
requestId: PostHogFlagsResponse['requestId'] | undefined;
|
|
488
488
|
}>;
|
|
489
489
|
protected getFeatureFlagDetailsStateless(distinctId: string, groups?: Record<string, string | number>, personProperties?: Record<string, string>, groupProperties?: Record<string, Record<string, string>>, disableGeoip?: boolean, flagKeysToEvaluate?: string[]): Promise<PostHogFeatureFlagDetails | undefined>;
|
|
490
490
|
/***
|
|
@@ -550,7 +550,7 @@ declare abstract class PostHogCoreStateless {
|
|
|
550
550
|
declare abstract class PostHogCore extends PostHogCoreStateless {
|
|
551
551
|
private sendFeatureFlagEvent;
|
|
552
552
|
private flagCallReported;
|
|
553
|
-
protected
|
|
553
|
+
protected _flagsResponsePromise?: Promise<PostHogFlagsResponse | undefined>;
|
|
554
554
|
protected _sessionExpirationTimeSeconds: number;
|
|
555
555
|
private _sessionMaxLengthSeconds;
|
|
556
556
|
protected sessionProps: PostHogEventProperties;
|
|
@@ -604,13 +604,13 @@ declare abstract class PostHogCore extends PostHogCoreStateless {
|
|
|
604
604
|
/***
|
|
605
605
|
*** FEATURE FLAGS
|
|
606
606
|
***/
|
|
607
|
-
private
|
|
607
|
+
private flagsAsync;
|
|
608
608
|
private cacheSessionReplay;
|
|
609
609
|
private _remoteConfigAsync;
|
|
610
|
-
private
|
|
610
|
+
private _flagsAsync;
|
|
611
611
|
private setKnownFeatureFlagDetails;
|
|
612
612
|
private getKnownFeatureFlagDetails;
|
|
613
|
-
protected getKnownFeatureFlags():
|
|
613
|
+
protected getKnownFeatureFlags(): PostHogFlagsResponse['featureFlags'] | undefined;
|
|
614
614
|
private getKnownFeatureFlagPayloads;
|
|
615
615
|
private getBootstrappedFeatureFlagDetails;
|
|
616
616
|
private setBootstrappedFeatureFlagDetails;
|
|
@@ -618,20 +618,22 @@ declare abstract class PostHogCore extends PostHogCoreStateless {
|
|
|
618
618
|
private getBootstrappedFeatureFlagPayloads;
|
|
619
619
|
getFeatureFlag(key: string): FeatureFlagValue | undefined;
|
|
620
620
|
getFeatureFlagPayload(key: string): JsonType | undefined;
|
|
621
|
-
getFeatureFlagPayloads():
|
|
622
|
-
getFeatureFlags():
|
|
621
|
+
getFeatureFlagPayloads(): PostHogFlagsResponse['featureFlagPayloads'] | undefined;
|
|
622
|
+
getFeatureFlags(): PostHogFlagsResponse['featureFlags'] | undefined;
|
|
623
623
|
getFeatureFlagDetails(): PostHogFeatureFlagDetails | undefined;
|
|
624
624
|
getFeatureFlagsAndPayloads(): {
|
|
625
|
-
flags:
|
|
626
|
-
payloads:
|
|
625
|
+
flags: PostHogFlagsResponse['featureFlags'] | undefined;
|
|
626
|
+
payloads: PostHogFlagsResponse['featureFlagPayloads'] | undefined;
|
|
627
627
|
};
|
|
628
628
|
isFeatureEnabled(key: string): boolean | undefined;
|
|
629
|
-
reloadFeatureFlags(
|
|
629
|
+
reloadFeatureFlags(options?: {
|
|
630
|
+
cb?: (err?: Error, flags?: PostHogFlagsResponse['featureFlags']) => void;
|
|
631
|
+
}): void;
|
|
630
632
|
reloadRemoteConfigAsync(): Promise<PostHogRemoteConfig | undefined>;
|
|
631
|
-
reloadFeatureFlagsAsync(sendAnonDistinctId?: boolean): Promise<
|
|
632
|
-
onFeatureFlags(cb: (flags:
|
|
633
|
+
reloadFeatureFlagsAsync(sendAnonDistinctId?: boolean): Promise<PostHogFlagsResponse['featureFlags'] | undefined>;
|
|
634
|
+
onFeatureFlags(cb: (flags: PostHogFlagsResponse['featureFlags']) => void): () => void;
|
|
633
635
|
onFeatureFlag(key: string, cb: (value: FeatureFlagValue) => void): () => void;
|
|
634
|
-
overrideFeatureFlag(flags:
|
|
636
|
+
overrideFeatureFlag(flags: PostHogFlagsResponse['featureFlags'] | null): Promise<void>;
|
|
635
637
|
/***
|
|
636
638
|
*** ERROR TRACKING
|
|
637
639
|
***/
|
package/lib/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var version = "4.
|
|
1
|
+
var version = "4.1.0";
|
|
2
2
|
|
|
3
3
|
var PostHogPersistedProperty;
|
|
4
4
|
(function (PostHogPersistedProperty) {
|
|
@@ -22,11 +22,11 @@ var PostHogPersistedProperty;
|
|
|
22
22
|
PostHogPersistedProperty["InstalledAppBuild"] = "installed_app_build";
|
|
23
23
|
PostHogPersistedProperty["InstalledAppVersion"] = "installed_app_version";
|
|
24
24
|
PostHogPersistedProperty["SessionReplay"] = "session_replay";
|
|
25
|
-
PostHogPersistedProperty["DecideEndpointWasHit"] = "decide_endpoint_was_hit";
|
|
26
25
|
PostHogPersistedProperty["SurveyLastSeenDate"] = "survey_last_seen_date";
|
|
27
26
|
PostHogPersistedProperty["SurveysSeen"] = "surveys_seen";
|
|
28
27
|
PostHogPersistedProperty["Surveys"] = "surveys";
|
|
29
28
|
PostHogPersistedProperty["RemoteConfig"] = "remote_config";
|
|
29
|
+
PostHogPersistedProperty["FlagsEndpointWasHit"] = "flags_endpoint_was_hit";
|
|
30
30
|
})(PostHogPersistedProperty || (PostHogPersistedProperty = {}));
|
|
31
31
|
// Any key prefixed with `attr__` can be added
|
|
32
32
|
var Compression;
|
|
@@ -94,27 +94,27 @@ var ActionStepStringMatching;
|
|
|
94
94
|
ActionStepStringMatching["Regex"] = "regex";
|
|
95
95
|
})(ActionStepStringMatching || (ActionStepStringMatching = {}));
|
|
96
96
|
|
|
97
|
-
const
|
|
98
|
-
if ('flags' in
|
|
99
|
-
// Convert
|
|
100
|
-
const featureFlags = getFlagValuesFromFlags(
|
|
101
|
-
const featureFlagPayloads = getPayloadsFromFlags(
|
|
97
|
+
const normalizeFlagsResponse = (flagsResponse) => {
|
|
98
|
+
if ('flags' in flagsResponse) {
|
|
99
|
+
// Convert v2 format to v1 format
|
|
100
|
+
const featureFlags = getFlagValuesFromFlags(flagsResponse.flags);
|
|
101
|
+
const featureFlagPayloads = getPayloadsFromFlags(flagsResponse.flags);
|
|
102
102
|
return {
|
|
103
|
-
...
|
|
103
|
+
...flagsResponse,
|
|
104
104
|
featureFlags,
|
|
105
105
|
featureFlagPayloads,
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
108
|
else {
|
|
109
|
-
// Convert
|
|
110
|
-
const featureFlags =
|
|
111
|
-
const featureFlagPayloads = Object.fromEntries(Object.entries(
|
|
109
|
+
// Convert v1 format to v2 format
|
|
110
|
+
const featureFlags = flagsResponse.featureFlags ?? {};
|
|
111
|
+
const featureFlagPayloads = Object.fromEntries(Object.entries(flagsResponse.featureFlagPayloads || {}).map(([k, v]) => [k, parsePayload(v)]));
|
|
112
112
|
const flags = Object.fromEntries(Object.entries(featureFlags).map(([key, value]) => [
|
|
113
113
|
key,
|
|
114
114
|
getFlagDetailFromFlagAndPayload(key, value, featureFlagPayloads[key]),
|
|
115
115
|
]));
|
|
116
116
|
return {
|
|
117
|
-
...
|
|
117
|
+
...flagsResponse,
|
|
118
118
|
featureFlags,
|
|
119
119
|
featureFlagPayloads,
|
|
120
120
|
flags,
|
|
@@ -186,7 +186,7 @@ const parsePayload = (response) => {
|
|
|
186
186
|
* @param featureFlagPayloads - The feature flag payloads
|
|
187
187
|
* @returns The normalized flag details
|
|
188
188
|
*/
|
|
189
|
-
const
|
|
189
|
+
const createFlagsResponseFromFlagsAndPayloads = (featureFlags, featureFlagPayloads) => {
|
|
190
190
|
// If a feature flag payload key is not in the feature flags, we treat it as true feature flag.
|
|
191
191
|
const allKeys = [...new Set([...Object.keys(featureFlags ?? {}), ...Object.keys(featureFlagPayloads ?? {})])];
|
|
192
192
|
const enabledFlags = allKeys
|
|
@@ -196,7 +196,7 @@ const createDecideResponseFromFlagsAndPayloads = (featureFlags, featureFlagPaylo
|
|
|
196
196
|
featureFlags: enabledFlags,
|
|
197
197
|
featureFlagPayloads: featureFlagPayloads ?? {},
|
|
198
198
|
};
|
|
199
|
-
return
|
|
199
|
+
return normalizeFlagsResponse(flagDetails);
|
|
200
200
|
};
|
|
201
201
|
const updateFlagValue = (flag, value) => {
|
|
202
202
|
return {
|
|
@@ -212,90 +212,6 @@ function getVariantFromValue(value) {
|
|
|
212
212
|
return typeof value === 'string' ? value : undefined;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
// Rollout constants
|
|
216
|
-
const NEW_FLAGS_ROLLOUT_PERCENTAGE = 1;
|
|
217
|
-
// The fnv1a hashes of the tokens that are explicitly excluded from the rollout
|
|
218
|
-
// see https://github.com/PostHog/posthog-js-lite/blob/main/posthog-core/src/utils.ts#L84
|
|
219
|
-
// are hashed API tokens from our top 10 for each category supported by this SDK.
|
|
220
|
-
const NEW_FLAGS_EXCLUDED_HASHES = new Set([
|
|
221
|
-
// Node
|
|
222
|
-
'61be3dd8',
|
|
223
|
-
'96f6df5f',
|
|
224
|
-
'8cfdba9b',
|
|
225
|
-
'bf027177',
|
|
226
|
-
'e59430a8',
|
|
227
|
-
'7fa5500b',
|
|
228
|
-
'569798e9',
|
|
229
|
-
'04809ff7',
|
|
230
|
-
'0ebc61a5',
|
|
231
|
-
'32de7f98',
|
|
232
|
-
'3beeb69a',
|
|
233
|
-
'12d34ad9',
|
|
234
|
-
'733853ec',
|
|
235
|
-
'0645bb64',
|
|
236
|
-
'5dcbee21',
|
|
237
|
-
'b1f95fa3',
|
|
238
|
-
'2189e408',
|
|
239
|
-
'82b460c2',
|
|
240
|
-
'3a8cc979',
|
|
241
|
-
'29ef8843',
|
|
242
|
-
'2cdbf767',
|
|
243
|
-
'38084b54',
|
|
244
|
-
// React Native
|
|
245
|
-
'50f9f8de',
|
|
246
|
-
'41d0df91',
|
|
247
|
-
'5c236689',
|
|
248
|
-
'c11aedd3',
|
|
249
|
-
'ada46672',
|
|
250
|
-
'f4331ee1',
|
|
251
|
-
'42fed62a',
|
|
252
|
-
'c957462c',
|
|
253
|
-
'd62f705a',
|
|
254
|
-
// Web (lots of teams per org, hence lots of API tokens)
|
|
255
|
-
'e0162666',
|
|
256
|
-
'01b3e5cf',
|
|
257
|
-
'441cef7f',
|
|
258
|
-
'bb9cafee',
|
|
259
|
-
'8f348eb0',
|
|
260
|
-
'b2553f3a',
|
|
261
|
-
'97469d7d',
|
|
262
|
-
'39f21a76',
|
|
263
|
-
'03706dcc',
|
|
264
|
-
'27d50569',
|
|
265
|
-
'307584a7',
|
|
266
|
-
'6433e92e',
|
|
267
|
-
'150c7fbb',
|
|
268
|
-
'49f57f22',
|
|
269
|
-
'3772f65b',
|
|
270
|
-
'01eb8256',
|
|
271
|
-
'3c9e9234',
|
|
272
|
-
'f853c7f7',
|
|
273
|
-
'c0ac4b67',
|
|
274
|
-
'cd609d40',
|
|
275
|
-
'10ca9b1a',
|
|
276
|
-
'8a87f11b',
|
|
277
|
-
'8e8e5216',
|
|
278
|
-
'1f6b63b3',
|
|
279
|
-
'db7943dd',
|
|
280
|
-
'79b7164c',
|
|
281
|
-
'07f78e33',
|
|
282
|
-
'2d21b6fd',
|
|
283
|
-
'952db5ee',
|
|
284
|
-
'a7d3b43f',
|
|
285
|
-
'1924dd9c',
|
|
286
|
-
'84e1b8f6',
|
|
287
|
-
'dff631b6',
|
|
288
|
-
'c5aa8a79',
|
|
289
|
-
'fa133a95',
|
|
290
|
-
'498a4508',
|
|
291
|
-
'24748755',
|
|
292
|
-
'98f3d658',
|
|
293
|
-
'21bbda67',
|
|
294
|
-
'7dbfed69',
|
|
295
|
-
'be3ec24c',
|
|
296
|
-
'fc80b8e2',
|
|
297
|
-
'75cc0998',
|
|
298
|
-
]);
|
|
299
215
|
const STRING_FORMAT = 'utf8';
|
|
300
216
|
function assert(truthyValue, message) {
|
|
301
217
|
if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
|
|
@@ -351,30 +267,6 @@ const isError = (x) => {
|
|
|
351
267
|
function getFetch() {
|
|
352
268
|
return typeof fetch !== 'undefined' ? fetch : typeof globalThis.fetch !== 'undefined' ? globalThis.fetch : undefined;
|
|
353
269
|
}
|
|
354
|
-
// FNV-1a hash function
|
|
355
|
-
// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
|
|
356
|
-
// I know, I know, I'm rolling my own hash function, but I didn't want to take on
|
|
357
|
-
// a crypto dependency and this is just temporary anyway
|
|
358
|
-
function fnv1a(str) {
|
|
359
|
-
let hash = 0x811c9dc5; // FNV offset basis
|
|
360
|
-
for (let i = 0; i < str.length; i++) {
|
|
361
|
-
hash ^= str.charCodeAt(i);
|
|
362
|
-
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
|
363
|
-
}
|
|
364
|
-
// Convert to hex string, padding to 8 chars
|
|
365
|
-
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
366
|
-
}
|
|
367
|
-
function isTokenInRollout(token, percentage = 0, excludedHashes) {
|
|
368
|
-
const tokenHash = fnv1a(token);
|
|
369
|
-
// Check excluded hashes (we're explicitly including these tokens from the rollout)
|
|
370
|
-
if (excludedHashes?.has(tokenHash)) {
|
|
371
|
-
return false;
|
|
372
|
-
}
|
|
373
|
-
// Convert hash to int and divide by max value to get number between 0-1
|
|
374
|
-
const hashInt = parseInt(tokenHash, 16);
|
|
375
|
-
const hashFloat = hashInt / 0xffffffff;
|
|
376
|
-
return hashFloat < percentage;
|
|
377
|
-
}
|
|
378
270
|
function allSettled(promises) {
|
|
379
271
|
return Promise.all(promises.map((p) => (p ?? Promise.resolve()).then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }))));
|
|
380
272
|
}
|
|
@@ -1106,13 +998,9 @@ class PostHogCoreStateless {
|
|
|
1106
998
|
/***
|
|
1107
999
|
*** FEATURE FLAGS
|
|
1108
1000
|
***/
|
|
1109
|
-
async
|
|
1001
|
+
async getFlags(distinctId, groups = {}, personProperties = {}, groupProperties = {}, extraPayload = {}) {
|
|
1110
1002
|
await this._initPromise;
|
|
1111
|
-
|
|
1112
|
-
// This is a temporary measure to ensure that we can still use the old flags API
|
|
1113
|
-
// while we migrate to the new flags API
|
|
1114
|
-
const useFlags = isTokenInRollout(this.apiKey, NEW_FLAGS_ROLLOUT_PERCENTAGE, NEW_FLAGS_EXCLUDED_HASHES);
|
|
1115
|
-
const url = useFlags ? `${this.host}/flags/?v=2` : `${this.host}/decide/?v=4`;
|
|
1003
|
+
const url = `${this.host}/flags/?v=2&config=true`;
|
|
1116
1004
|
const fetchOptions = {
|
|
1117
1005
|
method: 'POST',
|
|
1118
1006
|
headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
|
|
@@ -1125,11 +1013,11 @@ class PostHogCoreStateless {
|
|
|
1125
1013
|
...extraPayload,
|
|
1126
1014
|
}),
|
|
1127
1015
|
};
|
|
1128
|
-
this.logMsgIfDebug(() => console.log('PostHog Debug', '
|
|
1129
|
-
// Don't retry /
|
|
1016
|
+
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Flags URL', url));
|
|
1017
|
+
// Don't retry /flags API calls
|
|
1130
1018
|
return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
|
|
1131
1019
|
.then((response) => response.json())
|
|
1132
|
-
.then((response) =>
|
|
1020
|
+
.then((response) => normalizeFlagsResponse(response))
|
|
1133
1021
|
.catch((error) => {
|
|
1134
1022
|
this._events.emit('error', error);
|
|
1135
1023
|
return undefined;
|
|
@@ -1158,15 +1046,15 @@ class PostHogCoreStateless {
|
|
|
1158
1046
|
}
|
|
1159
1047
|
async getFeatureFlagDetailStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
|
|
1160
1048
|
await this._initPromise;
|
|
1161
|
-
const
|
|
1162
|
-
if (
|
|
1049
|
+
const flagsResponse = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
|
|
1050
|
+
if (flagsResponse === undefined) {
|
|
1163
1051
|
return undefined;
|
|
1164
1052
|
}
|
|
1165
|
-
const featureFlags =
|
|
1053
|
+
const featureFlags = flagsResponse.flags;
|
|
1166
1054
|
const flagDetail = featureFlags[key];
|
|
1167
1055
|
return {
|
|
1168
1056
|
response: flagDetail,
|
|
1169
|
-
requestId:
|
|
1057
|
+
requestId: flagsResponse.requestId,
|
|
1170
1058
|
};
|
|
1171
1059
|
}
|
|
1172
1060
|
async getFeatureFlagPayloadStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
|
|
@@ -1216,26 +1104,26 @@ class PostHogCoreStateless {
|
|
|
1216
1104
|
if (flagKeysToEvaluate) {
|
|
1217
1105
|
extraPayload['flag_keys_to_evaluate'] = flagKeysToEvaluate;
|
|
1218
1106
|
}
|
|
1219
|
-
const
|
|
1220
|
-
if (
|
|
1107
|
+
const flagsResponse = await this.getFlags(distinctId, groups, personProperties, groupProperties, extraPayload);
|
|
1108
|
+
if (flagsResponse === undefined) {
|
|
1221
1109
|
// We probably errored out, so return undefined
|
|
1222
1110
|
return undefined;
|
|
1223
1111
|
}
|
|
1224
|
-
// if there's an error on the
|
|
1225
|
-
if (
|
|
1112
|
+
// if there's an error on the flagsResponse, log a console error, but don't throw an error
|
|
1113
|
+
if (flagsResponse.errorsWhileComputingFlags) {
|
|
1226
1114
|
console.error('[FEATURE FLAGS] Error while computing feature flags, some flags may be missing or incorrect. Learn more at https://posthog.com/docs/feature-flags/best-practices');
|
|
1227
1115
|
}
|
|
1228
1116
|
// Add check for quota limitation on feature flags
|
|
1229
|
-
if (
|
|
1117
|
+
if (flagsResponse.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
|
|
1230
1118
|
console.warn('[FEATURE FLAGS] Feature flags quota limit exceeded - feature flags unavailable. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts');
|
|
1231
1119
|
return {
|
|
1232
1120
|
flags: {},
|
|
1233
1121
|
featureFlags: {},
|
|
1234
1122
|
featureFlagPayloads: {},
|
|
1235
|
-
requestId:
|
|
1123
|
+
requestId: flagsResponse?.requestId,
|
|
1236
1124
|
};
|
|
1237
1125
|
}
|
|
1238
|
-
return
|
|
1126
|
+
return flagsResponse;
|
|
1239
1127
|
}
|
|
1240
1128
|
/***
|
|
1241
1129
|
*** SURVEYS
|
|
@@ -1667,7 +1555,7 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
1667
1555
|
const bootstrapFeatureFlags = bootstrap.featureFlags;
|
|
1668
1556
|
const bootstrapFeatureFlagPayloads = bootstrap.featureFlagPayloads ?? {};
|
|
1669
1557
|
if (bootstrapFeatureFlags && Object.keys(bootstrapFeatureFlags).length) {
|
|
1670
|
-
const normalizedBootstrapFeatureFlagDetails =
|
|
1558
|
+
const normalizedBootstrapFeatureFlagDetails = createFlagsResponseFromFlagsAndPayloads(bootstrapFeatureFlags, bootstrapFeatureFlagPayloads);
|
|
1671
1559
|
if (Object.keys(normalizedBootstrapFeatureFlagDetails.flags).length > 0) {
|
|
1672
1560
|
this.setBootstrappedFeatureFlagDetails(normalizedBootstrapFeatureFlagDetails);
|
|
1673
1561
|
const currentFeatureFlagDetails = this.getKnownFeatureFlagDetails() || { flags: {}, requestId: undefined };
|
|
@@ -1809,7 +1697,7 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
1809
1697
|
...maybeAdd('$set_once', userPropsOnce),
|
|
1810
1698
|
});
|
|
1811
1699
|
if (distinctId !== previousDistinctId) {
|
|
1812
|
-
// We keep the AnonymousId to be used by
|
|
1700
|
+
// We keep the AnonymousId to be used by flags calls and identify to link the previousId
|
|
1813
1701
|
this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, previousDistinctId);
|
|
1814
1702
|
this.setPersistedProperty(PostHogPersistedProperty.DistinctId, distinctId);
|
|
1815
1703
|
this.reloadFeatureFlags();
|
|
@@ -1937,12 +1825,12 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
1937
1825
|
/***
|
|
1938
1826
|
*** FEATURE FLAGS
|
|
1939
1827
|
***/
|
|
1940
|
-
async
|
|
1828
|
+
async flagsAsync(sendAnonDistinctId = true) {
|
|
1941
1829
|
await this._initPromise;
|
|
1942
|
-
if (this.
|
|
1943
|
-
return this.
|
|
1830
|
+
if (this._flagsResponsePromise) {
|
|
1831
|
+
return this._flagsResponsePromise;
|
|
1944
1832
|
}
|
|
1945
|
-
return this.
|
|
1833
|
+
return this._flagsAsync(sendAnonDistinctId);
|
|
1946
1834
|
}
|
|
1947
1835
|
cacheSessionReplay(source, response) {
|
|
1948
1836
|
const sessionReplay = response?.sessionRecording;
|
|
@@ -2013,8 +1901,8 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
2013
1901
|
});
|
|
2014
1902
|
return this._remoteConfigResponsePromise;
|
|
2015
1903
|
}
|
|
2016
|
-
async
|
|
2017
|
-
this.
|
|
1904
|
+
async _flagsAsync(sendAnonDistinctId = true) {
|
|
1905
|
+
this._flagsResponsePromise = this._initPromise
|
|
2018
1906
|
.then(async () => {
|
|
2019
1907
|
const distinctId = this.getDistinctId();
|
|
2020
1908
|
const groups = this.props.$groups || {};
|
|
@@ -2024,7 +1912,7 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
2024
1912
|
const extraProperties = {
|
|
2025
1913
|
$anon_distinct_id: sendAnonDistinctId ? this.getAnonymousId() : undefined,
|
|
2026
1914
|
};
|
|
2027
|
-
const res = await super.
|
|
1915
|
+
const res = await super.getFlags(distinctId, groups, personProperties, groupProperties, extraProperties);
|
|
2028
1916
|
// Add check for quota limitation on feature flags
|
|
2029
1917
|
if (res?.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
|
|
2030
1918
|
// Unset all feature flags by setting to null
|
|
@@ -2048,22 +1936,22 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
2048
1936
|
};
|
|
2049
1937
|
}
|
|
2050
1938
|
this.setKnownFeatureFlagDetails(newFeatureFlagDetails);
|
|
2051
|
-
// Mark that we hit the /
|
|
2052
|
-
this.setPersistedProperty(PostHogPersistedProperty.
|
|
2053
|
-
this.cacheSessionReplay('
|
|
1939
|
+
// Mark that we hit the /flags endpoint so we can capture this in the $feature_flag_called event
|
|
1940
|
+
this.setPersistedProperty(PostHogPersistedProperty.FlagsEndpointWasHit, true);
|
|
1941
|
+
this.cacheSessionReplay('flags', res);
|
|
2054
1942
|
}
|
|
2055
1943
|
return res;
|
|
2056
1944
|
})
|
|
2057
1945
|
.finally(() => {
|
|
2058
|
-
this.
|
|
1946
|
+
this._flagsResponsePromise = undefined;
|
|
2059
1947
|
});
|
|
2060
|
-
return this.
|
|
1948
|
+
return this._flagsResponsePromise;
|
|
2061
1949
|
}
|
|
2062
1950
|
// We only store the flags and request id in the feature flag details storage key
|
|
2063
|
-
setKnownFeatureFlagDetails(
|
|
1951
|
+
setKnownFeatureFlagDetails(flagsResponse) {
|
|
2064
1952
|
this.wrap(() => {
|
|
2065
|
-
this.setPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails,
|
|
2066
|
-
this._events.emit('featureflags', getFlagValuesFromFlags(
|
|
1953
|
+
this.setPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails, flagsResponse);
|
|
1954
|
+
this._events.emit('featureflags', getFlagValuesFromFlags(flagsResponse?.flags ?? {}));
|
|
2067
1955
|
});
|
|
2068
1956
|
}
|
|
2069
1957
|
getKnownFeatureFlagDetails() {
|
|
@@ -2075,9 +1963,9 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
2075
1963
|
if (featureFlags === undefined && featureFlagPayloads === undefined) {
|
|
2076
1964
|
return undefined;
|
|
2077
1965
|
}
|
|
2078
|
-
return
|
|
1966
|
+
return createFlagsResponseFromFlagsAndPayloads(featureFlags ?? {}, featureFlagPayloads ?? {});
|
|
2079
1967
|
}
|
|
2080
|
-
return
|
|
1968
|
+
return normalizeFlagsResponse(storedDetails);
|
|
2081
1969
|
}
|
|
2082
1970
|
getKnownFeatureFlags() {
|
|
2083
1971
|
const featureFlagDetails = this.getKnownFeatureFlagDetails();
|
|
@@ -2141,8 +2029,8 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
2141
2029
|
...maybeAdd('$feature_flag_reason', featureFlag?.reason?.description ?? featureFlag?.reason?.code),
|
|
2142
2030
|
...maybeAdd('$feature_flag_bootstrapped_response', bootstrappedResponse),
|
|
2143
2031
|
...maybeAdd('$feature_flag_bootstrapped_payload', bootstrappedPayload),
|
|
2144
|
-
// If we haven't yet received a response from the /
|
|
2145
|
-
$used_bootstrap_value: !this.getPersistedProperty(PostHogPersistedProperty.
|
|
2032
|
+
// If we haven't yet received a response from the /flags endpoint, we must have used the bootstrapped value
|
|
2033
|
+
$used_bootstrap_value: !this.getPersistedProperty(PostHogPersistedProperty.FlagsEndpointWasHit),
|
|
2146
2034
|
...maybeAdd('$feature_flag_request_id', details.requestId),
|
|
2147
2035
|
});
|
|
2148
2036
|
}
|
|
@@ -2191,7 +2079,7 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
2191
2079
|
...details,
|
|
2192
2080
|
flags,
|
|
2193
2081
|
};
|
|
2194
|
-
return
|
|
2082
|
+
return normalizeFlagsResponse(result);
|
|
2195
2083
|
}
|
|
2196
2084
|
getFeatureFlagsAndPayloads() {
|
|
2197
2085
|
const flags = this.getFeatureFlags();
|
|
@@ -2209,14 +2097,14 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
2209
2097
|
return !!response;
|
|
2210
2098
|
}
|
|
2211
2099
|
// Used when we want to trigger the reload but we don't care about the result
|
|
2212
|
-
reloadFeatureFlags(
|
|
2213
|
-
this.
|
|
2100
|
+
reloadFeatureFlags(options) {
|
|
2101
|
+
this.flagsAsync(true)
|
|
2214
2102
|
.then((res) => {
|
|
2215
|
-
cb?.(undefined, res?.featureFlags);
|
|
2103
|
+
options?.cb?.(undefined, res?.featureFlags);
|
|
2216
2104
|
})
|
|
2217
2105
|
.catch((e) => {
|
|
2218
|
-
cb?.(e, undefined);
|
|
2219
|
-
if (!cb) {
|
|
2106
|
+
options?.cb?.(e, undefined);
|
|
2107
|
+
if (!options?.cb) {
|
|
2220
2108
|
this.logMsgIfDebug(() => console.log('PostHog Debug', 'Error reloading feature flags', e));
|
|
2221
2109
|
}
|
|
2222
2110
|
});
|
|
@@ -2224,8 +2112,8 @@ class PostHogCore extends PostHogCoreStateless {
|
|
|
2224
2112
|
async reloadRemoteConfigAsync() {
|
|
2225
2113
|
return await this.remoteConfigAsync();
|
|
2226
2114
|
}
|
|
2227
|
-
async reloadFeatureFlagsAsync(sendAnonDistinctId
|
|
2228
|
-
return (await this.
|
|
2115
|
+
async reloadFeatureFlagsAsync(sendAnonDistinctId) {
|
|
2116
|
+
return (await this.flagsAsync(sendAnonDistinctId ?? true))?.featureFlags;
|
|
2229
2117
|
}
|
|
2230
2118
|
onFeatureFlags(cb) {
|
|
2231
2119
|
return this.on('featureflags', async () => {
|