posthog-js-lite 3.4.2 → 3.5.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/lib/index.esm.js CHANGED
@@ -3,8 +3,10 @@ var PostHogPersistedProperty;
3
3
  PostHogPersistedProperty["AnonymousId"] = "anonymous_id";
4
4
  PostHogPersistedProperty["DistinctId"] = "distinct_id";
5
5
  PostHogPersistedProperty["Props"] = "props";
6
+ PostHogPersistedProperty["FeatureFlagDetails"] = "feature_flag_details";
6
7
  PostHogPersistedProperty["FeatureFlags"] = "feature_flags";
7
8
  PostHogPersistedProperty["FeatureFlagPayloads"] = "feature_flag_payloads";
9
+ PostHogPersistedProperty["BootstrapFeatureFlagDetails"] = "bootstrap_feature_flag_details";
8
10
  PostHogPersistedProperty["BootstrapFeatureFlags"] = "bootstrap_feature_flags";
9
11
  PostHogPersistedProperty["BootstrapFeatureFlagPayloads"] = "bootstrap_feature_flag_payloads";
10
12
  PostHogPersistedProperty["OverrideFeatureFlags"] = "override_feature_flags";
@@ -18,13 +20,284 @@ var PostHogPersistedProperty;
18
20
  PostHogPersistedProperty["InstalledAppVersion"] = "installed_app_version";
19
21
  PostHogPersistedProperty["SessionReplay"] = "session_replay";
20
22
  PostHogPersistedProperty["DecideEndpointWasHit"] = "decide_endpoint_was_hit";
21
- })(PostHogPersistedProperty || (PostHogPersistedProperty = {}));
23
+ PostHogPersistedProperty["SurveyLastSeenDate"] = "survey_last_seen_date";
24
+ PostHogPersistedProperty["SurveysSeen"] = "surveys_seen";
25
+ PostHogPersistedProperty["Surveys"] = "surveys";
26
+ PostHogPersistedProperty["RemoteConfig"] = "remote_config";
27
+ })(PostHogPersistedProperty || (PostHogPersistedProperty = {}));
28
+ var SurveyPosition;
29
+ (function (SurveyPosition) {
30
+ SurveyPosition["Left"] = "left";
31
+ SurveyPosition["Right"] = "right";
32
+ SurveyPosition["Center"] = "center";
33
+ })(SurveyPosition || (SurveyPosition = {}));
34
+ var SurveyWidgetType;
35
+ (function (SurveyWidgetType) {
36
+ SurveyWidgetType["Button"] = "button";
37
+ SurveyWidgetType["Tab"] = "tab";
38
+ SurveyWidgetType["Selector"] = "selector";
39
+ })(SurveyWidgetType || (SurveyWidgetType = {}));
40
+ var SurveyType;
41
+ (function (SurveyType) {
42
+ SurveyType["Popover"] = "popover";
43
+ SurveyType["API"] = "api";
44
+ SurveyType["Widget"] = "widget";
45
+ })(SurveyType || (SurveyType = {}));
46
+ var SurveyQuestionDescriptionContentType;
47
+ (function (SurveyQuestionDescriptionContentType) {
48
+ SurveyQuestionDescriptionContentType["Html"] = "html";
49
+ SurveyQuestionDescriptionContentType["Text"] = "text";
50
+ })(SurveyQuestionDescriptionContentType || (SurveyQuestionDescriptionContentType = {}));
51
+ var SurveyRatingDisplay;
52
+ (function (SurveyRatingDisplay) {
53
+ SurveyRatingDisplay["Number"] = "number";
54
+ SurveyRatingDisplay["Emoji"] = "emoji";
55
+ })(SurveyRatingDisplay || (SurveyRatingDisplay = {}));
56
+ var SurveyQuestionType;
57
+ (function (SurveyQuestionType) {
58
+ SurveyQuestionType["Open"] = "open";
59
+ SurveyQuestionType["MultipleChoice"] = "multiple_choice";
60
+ SurveyQuestionType["SingleChoice"] = "single_choice";
61
+ SurveyQuestionType["Rating"] = "rating";
62
+ SurveyQuestionType["Link"] = "link";
63
+ })(SurveyQuestionType || (SurveyQuestionType = {}));
64
+ var SurveyQuestionBranchingType;
65
+ (function (SurveyQuestionBranchingType) {
66
+ SurveyQuestionBranchingType["NextQuestion"] = "next_question";
67
+ SurveyQuestionBranchingType["End"] = "end";
68
+ SurveyQuestionBranchingType["ResponseBased"] = "response_based";
69
+ SurveyQuestionBranchingType["SpecificQuestion"] = "specific_question";
70
+ })(SurveyQuestionBranchingType || (SurveyQuestionBranchingType = {}));
71
+ var SurveyMatchType;
72
+ (function (SurveyMatchType) {
73
+ SurveyMatchType["Regex"] = "regex";
74
+ SurveyMatchType["NotRegex"] = "not_regex";
75
+ SurveyMatchType["Exact"] = "exact";
76
+ SurveyMatchType["IsNot"] = "is_not";
77
+ SurveyMatchType["Icontains"] = "icontains";
78
+ SurveyMatchType["NotIcontains"] = "not_icontains";
79
+ })(SurveyMatchType || (SurveyMatchType = {}));
80
+ /** Sync with plugin-server/src/types.ts */
81
+ var ActionStepStringMatching;
82
+ (function (ActionStepStringMatching) {
83
+ ActionStepStringMatching["Contains"] = "contains";
84
+ ActionStepStringMatching["Exact"] = "exact";
85
+ ActionStepStringMatching["Regex"] = "regex";
86
+ })(ActionStepStringMatching || (ActionStepStringMatching = {}));
22
87
 
88
+ const normalizeDecideResponse = (decideResponse) => {
89
+ if ('flags' in decideResponse) {
90
+ // Convert v4 format to v3 format
91
+ const featureFlags = getFlagValuesFromFlags(decideResponse.flags);
92
+ const featureFlagPayloads = getPayloadsFromFlags(decideResponse.flags);
93
+ return {
94
+ ...decideResponse,
95
+ featureFlags,
96
+ featureFlagPayloads,
97
+ };
98
+ }
99
+ else {
100
+ // Convert v3 format to v4 format
101
+ const featureFlags = decideResponse.featureFlags ?? {};
102
+ const featureFlagPayloads = Object.fromEntries(Object.entries(decideResponse.featureFlagPayloads || {}).map(([k, v]) => [k, parsePayload(v)]));
103
+ const flags = Object.fromEntries(Object.entries(featureFlags).map(([key, value]) => [
104
+ key,
105
+ getFlagDetailFromFlagAndPayload(key, value, featureFlagPayloads[key]),
106
+ ]));
107
+ return {
108
+ ...decideResponse,
109
+ featureFlags,
110
+ featureFlagPayloads,
111
+ flags,
112
+ };
113
+ }
114
+ };
115
+ function getFlagDetailFromFlagAndPayload(key, value, payload) {
116
+ return {
117
+ key: key,
118
+ enabled: typeof value === 'string' ? true : value,
119
+ variant: typeof value === 'string' ? value : undefined,
120
+ reason: undefined,
121
+ metadata: {
122
+ id: undefined,
123
+ version: undefined,
124
+ payload: payload ? JSON.stringify(payload) : undefined,
125
+ description: undefined,
126
+ },
127
+ };
128
+ }
129
+ /**
130
+ * Get the flag values from the flags v4 response.
131
+ * @param flags - The flags
132
+ * @returns The flag values
133
+ */
134
+ const getFlagValuesFromFlags = (flags) => {
135
+ return Object.fromEntries(Object.entries(flags ?? {})
136
+ .map(([key, detail]) => [key, getFeatureFlagValue(detail)])
137
+ .filter(([, value]) => value !== undefined));
138
+ };
139
+ /**
140
+ * Get the payloads from the flags v4 response.
141
+ * @param flags - The flags
142
+ * @returns The payloads
143
+ */
144
+ const getPayloadsFromFlags = (flags) => {
145
+ const safeFlags = flags ?? {};
146
+ return Object.fromEntries(Object.keys(safeFlags)
147
+ .filter((flag) => {
148
+ const details = safeFlags[flag];
149
+ return details.enabled && details.metadata && details.metadata.payload !== undefined;
150
+ })
151
+ .map((flag) => {
152
+ const payload = safeFlags[flag].metadata?.payload;
153
+ return [flag, payload ? parsePayload(payload) : undefined];
154
+ }));
155
+ };
156
+ const getFeatureFlagValue = (detail) => {
157
+ return detail === undefined ? undefined : detail.variant ?? detail.enabled;
158
+ };
159
+ const parsePayload = (response) => {
160
+ if (typeof response !== 'string') {
161
+ return response;
162
+ }
163
+ try {
164
+ return JSON.parse(response);
165
+ }
166
+ catch {
167
+ return response;
168
+ }
169
+ };
170
+ /**
171
+ * Get the normalized flag details from the flags and payloads.
172
+ * This is used to convert things like boostrap and stored feature flags and payloads to the v4 format.
173
+ * This helps us ensure backwards compatibility.
174
+ * If a key exists in the featureFlagPayloads that is not in the featureFlags, we treat it as a true feature flag.
175
+ *
176
+ * @param featureFlags - The feature flags
177
+ * @param featureFlagPayloads - The feature flag payloads
178
+ * @returns The normalized flag details
179
+ */
180
+ const createDecideResponseFromFlagsAndPayloads = (featureFlags, featureFlagPayloads) => {
181
+ // If a feature flag payload key is not in the feature flags, we treat it as true feature flag.
182
+ const allKeys = [...new Set([...Object.keys(featureFlags ?? {}), ...Object.keys(featureFlagPayloads ?? {})])];
183
+ const enabledFlags = allKeys
184
+ .filter((flag) => !!featureFlags[flag] || !!featureFlagPayloads[flag])
185
+ .reduce((res, key) => ((res[key] = featureFlags[key] ?? true), res), {});
186
+ const flagDetails = {
187
+ featureFlags: enabledFlags,
188
+ featureFlagPayloads: featureFlagPayloads ?? {},
189
+ };
190
+ return normalizeDecideResponse(flagDetails);
191
+ };
192
+ const updateFlagValue = (flag, value) => {
193
+ return {
194
+ ...flag,
195
+ enabled: getEnabledFromValue(value),
196
+ variant: getVariantFromValue(value),
197
+ };
198
+ };
199
+ function getEnabledFromValue(value) {
200
+ return typeof value === 'string' ? true : value;
201
+ }
202
+ function getVariantFromValue(value) {
203
+ return typeof value === 'string' ? value : undefined;
204
+ }
205
+
206
+ // Rollout constants
207
+ const NEW_FLAGS_ROLLOUT_PERCENTAGE = 1;
208
+ // The fnv1a hashes of the tokens that are explicitly excluded from the rollout
209
+ // see https://github.com/PostHog/posthog-js-lite/blob/main/posthog-core/src/utils.ts#L84
210
+ // are hashed API tokens from our top 10 for each category supported by this SDK.
211
+ const NEW_FLAGS_EXCLUDED_HASHES = new Set([
212
+ // Node
213
+ '61be3dd8',
214
+ '96f6df5f',
215
+ '8cfdba9b',
216
+ 'bf027177',
217
+ 'e59430a8',
218
+ '7fa5500b',
219
+ '569798e9',
220
+ '04809ff7',
221
+ '0ebc61a5',
222
+ '32de7f98',
223
+ '3beeb69a',
224
+ '12d34ad9',
225
+ '733853ec',
226
+ '0645bb64',
227
+ '5dcbee21',
228
+ 'b1f95fa3',
229
+ '2189e408',
230
+ '82b460c2',
231
+ '3a8cc979',
232
+ '29ef8843',
233
+ '2cdbf767',
234
+ '38084b54',
235
+ // React Native
236
+ '50f9f8de',
237
+ '41d0df91',
238
+ '5c236689',
239
+ 'c11aedd3',
240
+ 'ada46672',
241
+ 'f4331ee1',
242
+ '42fed62a',
243
+ 'c957462c',
244
+ 'd62f705a',
245
+ // Web (lots of teams per org, hence lots of API tokens)
246
+ 'e0162666',
247
+ '01b3e5cf',
248
+ '441cef7f',
249
+ 'bb9cafee',
250
+ '8f348eb0',
251
+ 'b2553f3a',
252
+ '97469d7d',
253
+ '39f21a76',
254
+ '03706dcc',
255
+ '27d50569',
256
+ '307584a7',
257
+ '6433e92e',
258
+ '150c7fbb',
259
+ '49f57f22',
260
+ '3772f65b',
261
+ '01eb8256',
262
+ '3c9e9234',
263
+ 'f853c7f7',
264
+ 'c0ac4b67',
265
+ 'cd609d40',
266
+ '10ca9b1a',
267
+ '8a87f11b',
268
+ '8e8e5216',
269
+ '1f6b63b3',
270
+ 'db7943dd',
271
+ '79b7164c',
272
+ '07f78e33',
273
+ '2d21b6fd',
274
+ '952db5ee',
275
+ 'a7d3b43f',
276
+ '1924dd9c',
277
+ '84e1b8f6',
278
+ 'dff631b6',
279
+ 'c5aa8a79',
280
+ 'fa133a95',
281
+ '498a4508',
282
+ '24748755',
283
+ '98f3d658',
284
+ '21bbda67',
285
+ '7dbfed69',
286
+ 'be3ec24c',
287
+ 'fc80b8e2',
288
+ '75cc0998',
289
+ ]);
23
290
  function assert(truthyValue, message) {
24
- if (!truthyValue) {
291
+ if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
25
292
  throw new Error(message);
26
293
  }
27
294
  }
295
+ function isEmpty(truthyValue) {
296
+ if (truthyValue.trim().length === 0) {
297
+ return true;
298
+ }
299
+ return false;
300
+ }
28
301
  function removeTrailingSlash(url) {
29
302
  return url?.replace(/\/+$/, '');
30
303
  }
@@ -67,6 +340,34 @@ const isError = (x) => {
67
340
  };
68
341
  function getFetch() {
69
342
  return typeof fetch !== 'undefined' ? fetch : typeof global.fetch !== 'undefined' ? global.fetch : undefined;
343
+ }
344
+ // copied from: https://github.com/PostHog/posthog-js/blob/main/react/src/utils/type-utils.ts#L4
345
+ const isFunction = function (f) {
346
+ return typeof f === 'function';
347
+ };
348
+ // FNV-1a hash function
349
+ // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
350
+ // I know, I know, I'm rolling my own hash function, but I didn't want to take on
351
+ // a crypto dependency and this is just temporary anyway
352
+ function fnv1a(str) {
353
+ let hash = 0x811c9dc5; // FNV offset basis
354
+ for (let i = 0; i < str.length; i++) {
355
+ hash ^= str.charCodeAt(i);
356
+ hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
357
+ }
358
+ // Convert to hex string, padding to 8 chars
359
+ return (hash >>> 0).toString(16).padStart(8, '0');
360
+ }
361
+ function isTokenInRollout(token, percentage = 0, excludedHashes) {
362
+ const tokenHash = fnv1a(token);
363
+ // Check excluded hashes (we're explicitly including these tokens from the rollout)
364
+ if (excludedHashes?.has(tokenHash)) {
365
+ return false;
366
+ }
367
+ // Convert hash to int and divide by max value to get number between 0-1
368
+ const hashInt = parseInt(tokenHash, 16);
369
+ const hashFloat = hashInt / 0xffffffff;
370
+ return hashFloat < percentage;
70
371
  }
71
372
 
72
373
  // Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
@@ -958,10 +1259,6 @@ var QuotaLimitedFeature;
958
1259
  class PostHogCoreStateless {
959
1260
  constructor(apiKey, options) {
960
1261
  this.flushPromise = null;
961
- this.disableGeoip = true;
962
- this.historicalMigration = false;
963
- this.disabled = false;
964
- this.defaultOptIn = true;
965
1262
  this.pendingPromises = {};
966
1263
  // internal
967
1264
  this._events = new SimpleEventEmitter();
@@ -974,8 +1271,10 @@ class PostHogCoreStateless {
974
1271
  this.maxQueueSize = Math.max(this.flushAt, options?.maxQueueSize ?? 1000);
975
1272
  this.flushInterval = options?.flushInterval ?? 10000;
976
1273
  this.captureMode = options?.captureMode || 'json';
1274
+ this.preloadFeatureFlags = options?.preloadFeatureFlags ?? true;
977
1275
  // If enable is explicitly set to false we override the optout
978
1276
  this.defaultOptIn = options?.defaultOptIn ?? true;
1277
+ this.disableSurveys = options?.disableSurveys ?? false;
979
1278
  this._retryOptions = {
980
1279
  retryCount: options?.fetchRetryCount ?? 3,
981
1280
  retryDelay: options?.fetchRetryDelay ?? 3000,
@@ -983,6 +1282,7 @@ class PostHogCoreStateless {
983
1282
  };
984
1283
  this.requestTimeout = options?.requestTimeout ?? 10000; // 10 seconds
985
1284
  this.featureFlagsRequestTimeoutMs = options?.featureFlagsRequestTimeoutMs ?? 3000; // 3 seconds
1285
+ this.remoteConfigRequestTimeoutMs = options?.remoteConfigRequestTimeoutMs ?? 3000; // 3 seconds
986
1286
  this.disableGeoip = options?.disableGeoip ?? true;
987
1287
  this.disabled = options?.disabled ?? false;
988
1288
  this.historicalMigration = options?.historicalMigration ?? false;
@@ -1119,12 +1419,39 @@ class PostHogCoreStateless {
1119
1419
  this.enqueue('capture', payload, options);
1120
1420
  });
1121
1421
  }
1422
+ async getRemoteConfig() {
1423
+ await this._initPromise;
1424
+ let host = this.host;
1425
+ if (host === 'https://us.i.posthog.com') {
1426
+ host = 'https://us-assets.i.posthog.com';
1427
+ }
1428
+ else if (host === 'https://eu.i.posthog.com') {
1429
+ host = 'https://eu-assets.i.posthog.com';
1430
+ }
1431
+ const url = `${host}/array/${this.apiKey}/config`;
1432
+ const fetchOptions = {
1433
+ method: 'GET',
1434
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1435
+ };
1436
+ // Don't retry remote config API calls
1437
+ return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.remoteConfigRequestTimeoutMs)
1438
+ .then((response) => response.json())
1439
+ .catch((error) => {
1440
+ this.logMsgIfDebug(() => console.error('Remote config could not be loaded', error));
1441
+ this._events.emit('error', error);
1442
+ return undefined;
1443
+ });
1444
+ }
1122
1445
  /***
1123
1446
  *** FEATURE FLAGS
1124
1447
  ***/
1125
1448
  async getDecide(distinctId, groups = {}, personProperties = {}, groupProperties = {}, extraPayload = {}) {
1126
1449
  await this._initPromise;
1127
- const url = `${this.host}/decide/?v=3`;
1450
+ // Check if the API token is in the new flags rollout
1451
+ // This is a temporary measure to ensure that we can still use the old flags API
1452
+ // while we migrate to the new flags API
1453
+ const useFlags = isTokenInRollout(this.apiKey, NEW_FLAGS_ROLLOUT_PERCENTAGE, NEW_FLAGS_EXCLUDED_HASHES);
1454
+ const url = useFlags ? `${this.host}/flags/?v=2` : `${this.host}/decide/?v=4`;
1128
1455
  const fetchOptions = {
1129
1456
  method: 'POST',
1130
1457
  headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
@@ -1140,6 +1467,7 @@ class PostHogCoreStateless {
1140
1467
  // Don't retry /decide API calls
1141
1468
  return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
1142
1469
  .then((response) => response.json())
1470
+ .then((response) => normalizeDecideResponse(response))
1143
1471
  .catch((error) => {
1144
1472
  this._events.emit('error', error);
1145
1473
  return undefined;
@@ -1147,23 +1475,41 @@ class PostHogCoreStateless {
1147
1475
  }
1148
1476
  async getFeatureFlagStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1149
1477
  await this._initPromise;
1150
- const featureFlags = await this.getFeatureFlagsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip);
1151
- if (!featureFlags) {
1478
+ const flagDetailResponse = await this.getFeatureFlagDetailStateless(key, distinctId, groups, personProperties, groupProperties, disableGeoip);
1479
+ if (flagDetailResponse === undefined) {
1152
1480
  // If we haven't loaded flags yet, or errored out, we respond with undefined
1153
- return undefined;
1481
+ return {
1482
+ response: undefined,
1483
+ requestId: undefined,
1484
+ };
1154
1485
  }
1155
- let response = featureFlags[key];
1156
- // `/decide` v3 returns all flags
1486
+ let response = getFeatureFlagValue(flagDetailResponse.response);
1157
1487
  if (response === undefined) {
1158
1488
  // For cases where the flag is unknown, return false
1159
1489
  response = false;
1160
1490
  }
1161
1491
  // If we have flags we either return the value (true or string) or false
1162
- return response;
1492
+ return {
1493
+ response,
1494
+ requestId: flagDetailResponse.requestId,
1495
+ };
1496
+ }
1497
+ async getFeatureFlagDetailStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1498
+ await this._initPromise;
1499
+ const decideResponse = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
1500
+ if (decideResponse === undefined) {
1501
+ return undefined;
1502
+ }
1503
+ const featureFlags = decideResponse.flags;
1504
+ const flagDetail = featureFlags[key];
1505
+ return {
1506
+ response: flagDetail,
1507
+ requestId: decideResponse.requestId,
1508
+ };
1163
1509
  }
1164
1510
  async getFeatureFlagPayloadStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1165
1511
  await this._initPromise;
1166
- const payloads = await this.getFeatureFlagPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip);
1512
+ const payloads = await this.getFeatureFlagPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
1167
1513
  if (!payloads) {
1168
1514
  return undefined;
1169
1515
  }
@@ -1174,48 +1520,96 @@ class PostHogCoreStateless {
1174
1520
  }
1175
1521
  return response;
1176
1522
  }
1177
- async getFeatureFlagPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1523
+ async getFeatureFlagPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1178
1524
  await this._initPromise;
1179
- const payloads = (await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip)).payloads;
1525
+ const payloads = (await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate)).payloads;
1180
1526
  return payloads;
1181
1527
  }
1182
- _parsePayload(response) {
1183
- try {
1184
- return JSON.parse(response);
1185
- }
1186
- catch {
1187
- return response;
1188
- }
1528
+ async getFeatureFlagsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1529
+ await this._initPromise;
1530
+ return await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
1189
1531
  }
1190
- async getFeatureFlagsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1532
+ async getFeatureFlagsAndPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1191
1533
  await this._initPromise;
1192
- return (await this.getFeatureFlagsAndPayloadsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip)).flags;
1534
+ const featureFlagDetails = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, flagKeysToEvaluate);
1535
+ if (!featureFlagDetails) {
1536
+ return {
1537
+ flags: undefined,
1538
+ payloads: undefined,
1539
+ requestId: undefined,
1540
+ };
1541
+ }
1542
+ return {
1543
+ flags: featureFlagDetails.featureFlags,
1544
+ payloads: featureFlagDetails.featureFlagPayloads,
1545
+ requestId: featureFlagDetails.requestId,
1546
+ };
1193
1547
  }
1194
- async getFeatureFlagsAndPayloadsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1548
+ async getFeatureFlagDetailsStateless(distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip, flagKeysToEvaluate) {
1195
1549
  await this._initPromise;
1196
1550
  const extraPayload = {};
1197
1551
  if (disableGeoip ?? this.disableGeoip) {
1198
1552
  extraPayload['geoip_disable'] = true;
1199
1553
  }
1554
+ if (flagKeysToEvaluate) {
1555
+ extraPayload['flag_keys_to_evaluate'] = flagKeysToEvaluate;
1556
+ }
1200
1557
  const decideResponse = await this.getDecide(distinctId, groups, personProperties, groupProperties, extraPayload);
1558
+ if (decideResponse === undefined) {
1559
+ // We probably errored out, so return undefined
1560
+ return undefined;
1561
+ }
1562
+ // if there's an error on the decideResponse, log a console error, but don't throw an error
1563
+ if (decideResponse.errorsWhileComputingFlags) {
1564
+ 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');
1565
+ }
1201
1566
  // Add check for quota limitation on feature flags
1202
- if (decideResponse?.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
1567
+ if (decideResponse.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
1203
1568
  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');
1204
1569
  return {
1205
- flags: undefined,
1206
- payloads: undefined,
1570
+ flags: {},
1571
+ featureFlags: {},
1572
+ featureFlagPayloads: {},
1573
+ requestId: decideResponse?.requestId,
1207
1574
  };
1208
1575
  }
1209
- const flags = decideResponse?.featureFlags;
1210
- const payloads = decideResponse?.featureFlagPayloads;
1211
- let parsedPayloads = payloads;
1212
- if (payloads) {
1213
- parsedPayloads = Object.fromEntries(Object.entries(payloads).map(([k, v]) => [k, this._parsePayload(v)]));
1576
+ return decideResponse;
1577
+ }
1578
+ /***
1579
+ *** SURVEYS
1580
+ ***/
1581
+ async getSurveysStateless() {
1582
+ await this._initPromise;
1583
+ if (this.disableSurveys === true) {
1584
+ this.logMsgIfDebug(() => console.log('Loading surveys is disabled.'));
1585
+ return [];
1214
1586
  }
1215
- return {
1216
- flags,
1217
- payloads: parsedPayloads,
1587
+ const url = `${this.host}/api/surveys/?token=${this.apiKey}`;
1588
+ const fetchOptions = {
1589
+ method: 'GET',
1590
+ headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1218
1591
  };
1592
+ const response = await this.fetchWithRetry(url, fetchOptions)
1593
+ .then((response) => {
1594
+ if (response.status !== 200 || !response.json) {
1595
+ const msg = `Surveys API could not be loaded: ${response.status}`;
1596
+ const error = new Error(msg);
1597
+ this.logMsgIfDebug(() => console.error(error));
1598
+ this._events.emit('error', new Error(msg));
1599
+ return undefined;
1600
+ }
1601
+ return response.json();
1602
+ })
1603
+ .catch((error) => {
1604
+ this.logMsgIfDebug(() => console.error('Surveys API could not be loaded', error));
1605
+ this._events.emit('error', error);
1606
+ return undefined;
1607
+ });
1608
+ const newSurveys = response?.surveys;
1609
+ if (newSurveys) {
1610
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from API: ', JSON.stringify(newSurveys)));
1611
+ }
1612
+ return newSurveys ?? [];
1219
1613
  }
1220
1614
  /***
1221
1615
  *** QUEUEING AND FLUSHING
@@ -1454,23 +1848,21 @@ class PostHogCore extends PostHogCoreStateless {
1454
1848
  }
1455
1849
  }
1456
1850
  }
1457
- const bootstrapfeatureFlags = bootstrap.featureFlags;
1458
- if (bootstrapfeatureFlags && Object.keys(bootstrapfeatureFlags).length) {
1459
- const bootstrapFlags = Object.keys(bootstrapfeatureFlags)
1460
- .filter((flag) => !!bootstrapfeatureFlags[flag])
1461
- .reduce((res, key) => ((res[key] = bootstrapfeatureFlags[key] || false), res), {});
1462
- if (Object.keys(bootstrapFlags).length) {
1463
- this.setPersistedProperty(PostHogPersistedProperty.BootstrapFeatureFlags, bootstrapFlags);
1464
- const currentFlags = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlags) || {};
1465
- const newFeatureFlags = { ...bootstrapFlags, ...currentFlags };
1466
- this.setKnownFeatureFlags(newFeatureFlags);
1467
- }
1468
- const bootstrapFlagPayloads = bootstrap.featureFlagPayloads;
1469
- if (bootstrapFlagPayloads && Object.keys(bootstrapFlagPayloads).length) {
1470
- this.setPersistedProperty(PostHogPersistedProperty.BootstrapFeatureFlagPayloads, bootstrapFlagPayloads);
1471
- const currentFlagPayloads = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlagPayloads) || {};
1472
- const newFeatureFlagPayloads = { ...bootstrapFlagPayloads, ...currentFlagPayloads };
1473
- this.setKnownFeatureFlagPayloads(newFeatureFlagPayloads);
1851
+ const bootstrapFeatureFlags = bootstrap.featureFlags;
1852
+ const bootstrapFeatureFlagPayloads = bootstrap.featureFlagPayloads ?? {};
1853
+ if (bootstrapFeatureFlags && Object.keys(bootstrapFeatureFlags).length) {
1854
+ const normalizedBootstrapFeatureFlagDetails = createDecideResponseFromFlagsAndPayloads(bootstrapFeatureFlags, bootstrapFeatureFlagPayloads);
1855
+ if (Object.keys(normalizedBootstrapFeatureFlagDetails.flags).length > 0) {
1856
+ this.setBootstrappedFeatureFlagDetails(normalizedBootstrapFeatureFlagDetails);
1857
+ const currentFeatureFlagDetails = this.getKnownFeatureFlagDetails() || { flags: {}, requestId: undefined };
1858
+ const newFeatureFlagDetails = {
1859
+ flags: {
1860
+ ...normalizedBootstrapFeatureFlagDetails.flags,
1861
+ ...currentFeatureFlagDetails.flags,
1862
+ },
1863
+ requestId: normalizedBootstrapFeatureFlagDetails.requestId,
1864
+ };
1865
+ this.setKnownFeatureFlagDetails(newFeatureFlagDetails);
1474
1866
  }
1475
1867
  }
1476
1868
  }
@@ -1708,7 +2100,7 @@ class PostHogCore extends PostHogCoreStateless {
1708
2100
  }
1709
2101
  resetPersonPropertiesForFlags() {
1710
2102
  this.wrap(() => {
1711
- this.setPersistedProperty(PostHogPersistedProperty.PersonProperties, {});
2103
+ this.setPersistedProperty(PostHogPersistedProperty.PersonProperties, null);
1712
2104
  });
1713
2105
  }
1714
2106
  /** @deprecated - Renamed to setPersonPropertiesForFlags */
@@ -1737,7 +2129,7 @@ class PostHogCore extends PostHogCoreStateless {
1737
2129
  }
1738
2130
  resetGroupPropertiesForFlags() {
1739
2131
  this.wrap(() => {
1740
- this.setPersistedProperty(PostHogPersistedProperty.GroupProperties, {});
2132
+ this.setPersistedProperty(PostHogPersistedProperty.GroupProperties, null);
1741
2133
  });
1742
2134
  }
1743
2135
  /** @deprecated - Renamed to setGroupPropertiesForFlags */
@@ -1746,6 +2138,13 @@ class PostHogCore extends PostHogCoreStateless {
1746
2138
  this.setGroupPropertiesForFlags(properties);
1747
2139
  });
1748
2140
  }
2141
+ async remoteConfigAsync() {
2142
+ await this._initPromise;
2143
+ if (this._remoteConfigResponsePromise) {
2144
+ return this._remoteConfigResponsePromise;
2145
+ }
2146
+ return this._remoteConfigAsync();
2147
+ }
1749
2148
  /***
1750
2149
  *** FEATURE FLAGS
1751
2150
  ***/
@@ -1756,6 +2155,65 @@ class PostHogCore extends PostHogCoreStateless {
1756
2155
  }
1757
2156
  return this._decideAsync(sendAnonDistinctId);
1758
2157
  }
2158
+ cacheSessionReplay(response) {
2159
+ const sessionReplay = response?.sessionRecording;
2160
+ if (sessionReplay) {
2161
+ this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, sessionReplay);
2162
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Session replay config: ', JSON.stringify(sessionReplay)));
2163
+ }
2164
+ else {
2165
+ this.logMsgIfDebug(() => console.info('PostHog Debug', 'Session replay config disabled.'));
2166
+ this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, null);
2167
+ }
2168
+ }
2169
+ async _remoteConfigAsync() {
2170
+ this._remoteConfigResponsePromise = this._initPromise
2171
+ .then(() => {
2172
+ let remoteConfig = this.getPersistedProperty(PostHogPersistedProperty.RemoteConfig);
2173
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Cached remote config: ', JSON.stringify(remoteConfig)));
2174
+ return super.getRemoteConfig().then((response) => {
2175
+ if (response) {
2176
+ const remoteConfigWithoutSurveys = { ...response };
2177
+ delete remoteConfigWithoutSurveys.surveys;
2178
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Fetched remote config: ', JSON.stringify(remoteConfigWithoutSurveys)));
2179
+ const surveys = response.surveys;
2180
+ let hasSurveys = true;
2181
+ if (!Array.isArray(surveys)) {
2182
+ // If surveys is not an array, it means there are no surveys (its a boolean instead)
2183
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'There are no surveys.'));
2184
+ hasSurveys = false;
2185
+ }
2186
+ else {
2187
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from remote config: ', JSON.stringify(surveys)));
2188
+ }
2189
+ if (this.disableSurveys === false && hasSurveys) {
2190
+ this.setPersistedProperty(PostHogPersistedProperty.Surveys, surveys);
2191
+ }
2192
+ else {
2193
+ this.setPersistedProperty(PostHogPersistedProperty.Surveys, null);
2194
+ }
2195
+ // we cache the surveys in its own storage key
2196
+ this.setPersistedProperty(PostHogPersistedProperty.RemoteConfig, remoteConfigWithoutSurveys);
2197
+ this.cacheSessionReplay(response);
2198
+ // we only dont load flags if the remote config has no feature flags
2199
+ if (response.hasFeatureFlags === false) {
2200
+ // resetting flags to empty object
2201
+ this.setKnownFeatureFlagDetails({ flags: {} });
2202
+ this.logMsgIfDebug(() => console.warn('Remote config has no feature flags, will not load feature flags.'));
2203
+ }
2204
+ else if (this.preloadFeatureFlags !== false) {
2205
+ this.reloadFeatureFlags();
2206
+ }
2207
+ remoteConfig = response;
2208
+ }
2209
+ return remoteConfig;
2210
+ });
2211
+ })
2212
+ .finally(() => {
2213
+ this._remoteConfigResponsePromise = undefined;
2214
+ });
2215
+ return this._remoteConfigResponsePromise;
2216
+ }
1759
2217
  async _decideAsync(sendAnonDistinctId = true) {
1760
2218
  this._decideResponsePromise = this._initPromise
1761
2219
  .then(async () => {
@@ -1771,8 +2229,7 @@ class PostHogCore extends PostHogCoreStateless {
1771
2229
  // Add check for quota limitation on feature flags
1772
2230
  if (res?.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
1773
2231
  // Unset all feature flags by setting to null
1774
- this.setKnownFeatureFlags(null);
1775
- this.setKnownFeatureFlagPayloads(null);
2232
+ this.setKnownFeatureFlagDetails(null);
1776
2233
  console.warn('[FEATURE FLAGS] Feature flags quota limit exceeded - unsetting all flags. Learn more about billing limits at https://posthog.com/docs/billing/limits-alerts');
1777
2234
  return res;
1778
2235
  }
@@ -1781,29 +2238,20 @@ class PostHogCore extends PostHogCoreStateless {
1781
2238
  if (this.sendFeatureFlagEvent) {
1782
2239
  this.flagCallReported = {};
1783
2240
  }
1784
- let newFeatureFlags = res.featureFlags;
1785
- let newFeatureFlagPayloads = res.featureFlagPayloads;
2241
+ let newFeatureFlagDetails = res;
1786
2242
  if (res.errorsWhileComputingFlags) {
1787
2243
  // if not all flags were computed, we upsert flags instead of replacing them
1788
- const currentFlags = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlags);
1789
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Cached feature flags: ', JSON.stringify(currentFlags)));
1790
- const currentFlagPayloads = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlagPayloads);
1791
- newFeatureFlags = { ...currentFlags, ...res.featureFlags };
1792
- newFeatureFlagPayloads = { ...currentFlagPayloads, ...res.featureFlagPayloads };
2244
+ const currentFlagDetails = this.getKnownFeatureFlagDetails();
2245
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Cached feature flags: ', JSON.stringify(currentFlagDetails)));
2246
+ newFeatureFlagDetails = {
2247
+ ...res,
2248
+ flags: { ...currentFlagDetails?.flags, ...res.flags },
2249
+ };
1793
2250
  }
1794
- this.setKnownFeatureFlags(newFeatureFlags);
1795
- this.setKnownFeatureFlagPayloads(Object.fromEntries(Object.entries(newFeatureFlagPayloads || {}).map(([k, v]) => [k, this._parsePayload(v)])));
2251
+ this.setKnownFeatureFlagDetails(newFeatureFlagDetails);
1796
2252
  // Mark that we hit the /decide endpoint so we can capture this in the $feature_flag_called event
1797
2253
  this.setPersistedProperty(PostHogPersistedProperty.DecideEndpointWasHit, true);
1798
- const sessionReplay = res?.sessionRecording;
1799
- if (sessionReplay) {
1800
- this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, sessionReplay);
1801
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Session replay config: ', JSON.stringify(sessionReplay)));
1802
- }
1803
- else {
1804
- this.logMsgIfDebug(() => console.info('PostHog Debug', 'Session replay config disabled.'));
1805
- this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, null);
1806
- }
2254
+ this.cacheSessionReplay(res);
1807
2255
  }
1808
2256
  return res;
1809
2257
  })
@@ -1812,38 +2260,91 @@ class PostHogCore extends PostHogCoreStateless {
1812
2260
  });
1813
2261
  return this._decideResponsePromise;
1814
2262
  }
1815
- setKnownFeatureFlags(featureFlags) {
2263
+ // We only store the flags and request id in the feature flag details storage key
2264
+ setKnownFeatureFlagDetails(decideResponse) {
1816
2265
  this.wrap(() => {
1817
- this.setPersistedProperty(PostHogPersistedProperty.FeatureFlags, featureFlags);
1818
- this._events.emit('featureflags', featureFlags);
2266
+ this.setPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails, decideResponse);
2267
+ this._events.emit('featureflags', getFlagValuesFromFlags(decideResponse?.flags ?? {}));
1819
2268
  });
1820
2269
  }
1821
- setKnownFeatureFlagPayloads(featureFlagPayloads) {
1822
- this.wrap(() => {
1823
- this.setPersistedProperty(PostHogPersistedProperty.FeatureFlagPayloads, featureFlagPayloads);
1824
- });
2270
+ getKnownFeatureFlagDetails() {
2271
+ const storedDetails = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails);
2272
+ if (!storedDetails) {
2273
+ // Rebuild from the stored feature flags and feature flag payloads
2274
+ const featureFlags = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlags);
2275
+ const featureFlagPayloads = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlagPayloads);
2276
+ if (featureFlags === undefined && featureFlagPayloads === undefined) {
2277
+ return undefined;
2278
+ }
2279
+ return createDecideResponseFromFlagsAndPayloads(featureFlags ?? {}, featureFlagPayloads ?? {});
2280
+ }
2281
+ return normalizeDecideResponse(storedDetails);
2282
+ }
2283
+ getKnownFeatureFlags() {
2284
+ const featureFlagDetails = this.getKnownFeatureFlagDetails();
2285
+ if (!featureFlagDetails) {
2286
+ return undefined;
2287
+ }
2288
+ return getFlagValuesFromFlags(featureFlagDetails.flags);
2289
+ }
2290
+ getKnownFeatureFlagPayloads() {
2291
+ const featureFlagDetails = this.getKnownFeatureFlagDetails();
2292
+ if (!featureFlagDetails) {
2293
+ return undefined;
2294
+ }
2295
+ return getPayloadsFromFlags(featureFlagDetails.flags);
2296
+ }
2297
+ getBootstrappedFeatureFlagDetails() {
2298
+ const details = this.getPersistedProperty(PostHogPersistedProperty.BootstrapFeatureFlagDetails);
2299
+ if (!details) {
2300
+ return undefined;
2301
+ }
2302
+ return details;
2303
+ }
2304
+ setBootstrappedFeatureFlagDetails(details) {
2305
+ this.setPersistedProperty(PostHogPersistedProperty.BootstrapFeatureFlagDetails, details);
2306
+ }
2307
+ getBootstrappedFeatureFlags() {
2308
+ const details = this.getBootstrappedFeatureFlagDetails();
2309
+ if (!details) {
2310
+ return undefined;
2311
+ }
2312
+ return getFlagValuesFromFlags(details.flags);
2313
+ }
2314
+ getBootstrappedFeatureFlagPayloads() {
2315
+ const details = this.getBootstrappedFeatureFlagDetails();
2316
+ if (!details) {
2317
+ return undefined;
2318
+ }
2319
+ return getPayloadsFromFlags(details.flags);
1825
2320
  }
1826
2321
  getFeatureFlag(key) {
1827
- const featureFlags = this.getFeatureFlags();
1828
- if (!featureFlags) {
2322
+ const details = this.getFeatureFlagDetails();
2323
+ if (!details) {
1829
2324
  // If we haven't loaded flags yet, or errored out, we respond with undefined
1830
2325
  return undefined;
1831
2326
  }
1832
- let response = featureFlags[key];
1833
- // `/decide` v3 returns all flags
2327
+ const featureFlag = details.flags[key];
2328
+ let response = getFeatureFlagValue(featureFlag);
1834
2329
  if (response === undefined) {
1835
2330
  // For cases where the flag is unknown, return false
1836
2331
  response = false;
1837
2332
  }
1838
2333
  if (this.sendFeatureFlagEvent && !this.flagCallReported[key]) {
2334
+ const bootstrappedResponse = this.getBootstrappedFeatureFlags()?.[key];
2335
+ const bootstrappedPayload = this.getBootstrappedFeatureFlagPayloads()?.[key];
1839
2336
  this.flagCallReported[key] = true;
1840
2337
  this.capture('$feature_flag_called', {
1841
2338
  $feature_flag: key,
1842
2339
  $feature_flag_response: response,
1843
- $feature_flag_bootstrapped_response: this.getPersistedProperty(PostHogPersistedProperty.BootstrapFeatureFlags)?.[key],
1844
- $feature_flag_bootstrapped_payload: this.getPersistedProperty(PostHogPersistedProperty.BootstrapFeatureFlagPayloads)?.[key],
2340
+ $feature_flag_id: featureFlag?.metadata?.id,
2341
+ $feature_flag_version: featureFlag?.metadata?.version,
2342
+ $feature_flag_reason: featureFlag?.reason?.description ?? featureFlag?.reason?.code,
2343
+ $feature_flag_bootstrapped_response: bootstrappedResponse,
2344
+ $feature_flag_bootstrapped_payload: bootstrappedPayload,
1845
2345
  // If we haven't yet received a response from the /decide endpoint, we must have used the bootstrapped value
1846
2346
  $used_bootstrap_value: !this.getPersistedProperty(PostHogPersistedProperty.DecideEndpointWasHit),
2347
+ $feature_flag_request_id: details.requestId,
1847
2348
  });
1848
2349
  }
1849
2350
  // If we have flags we either return the value (true or string) or false
@@ -1862,27 +2363,36 @@ class PostHogCore extends PostHogCoreStateless {
1862
2363
  return response;
1863
2364
  }
1864
2365
  getFeatureFlagPayloads() {
1865
- const payloads = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlagPayloads);
1866
- return payloads;
2366
+ return this.getFeatureFlagDetails()?.featureFlagPayloads;
1867
2367
  }
1868
2368
  getFeatureFlags() {
1869
2369
  // NOTE: We don't check for _initPromise here as the function is designed to be
1870
2370
  // callable before the state being loaded anyways
1871
- let flags = this.getPersistedProperty(PostHogPersistedProperty.FeatureFlags);
2371
+ return this.getFeatureFlagDetails()?.featureFlags;
2372
+ }
2373
+ getFeatureFlagDetails() {
2374
+ // NOTE: We don't check for _initPromise here as the function is designed to be
2375
+ // callable before the state being loaded anyways
2376
+ let details = this.getKnownFeatureFlagDetails();
1872
2377
  const overriddenFlags = this.getPersistedProperty(PostHogPersistedProperty.OverrideFeatureFlags);
1873
2378
  if (!overriddenFlags) {
1874
- return flags;
2379
+ return details;
1875
2380
  }
1876
- flags = flags || {};
2381
+ details = details ?? { featureFlags: {}, featureFlagPayloads: {}, flags: {} };
2382
+ const flags = details.flags ?? {};
1877
2383
  for (const key in overriddenFlags) {
1878
2384
  if (!overriddenFlags[key]) {
1879
2385
  delete flags[key];
1880
2386
  }
1881
2387
  else {
1882
- flags[key] = overriddenFlags[key];
2388
+ flags[key] = updateFlagValue(flags[key], overriddenFlags[key]);
1883
2389
  }
1884
2390
  }
1885
- return flags;
2391
+ const result = {
2392
+ ...details,
2393
+ flags,
2394
+ };
2395
+ return normalizeDecideResponse(result);
1886
2396
  }
1887
2397
  getFeatureFlagsAndPayloads() {
1888
2398
  const flags = this.getFeatureFlags();
@@ -1912,6 +2422,9 @@ class PostHogCore extends PostHogCoreStateless {
1912
2422
  }
1913
2423
  });
1914
2424
  }
2425
+ async reloadRemoteConfigAsync() {
2426
+ return await this.remoteConfigAsync();
2427
+ }
1915
2428
  async reloadFeatureFlagsAsync(sendAnonDistinctId = true) {
1916
2429
  return (await this.decideAsync(sendAnonDistinctId))?.featureFlags;
1917
2430
  }
@@ -1986,7 +2499,7 @@ class PostHogCore extends PostHogCoreStateless {
1986
2499
  }
1987
2500
  }
1988
2501
 
1989
- var version = "3.4.2";
2502
+ var version = "3.5.0";
1990
2503
 
1991
2504
  function getContext(window) {
1992
2505
  let context = {};
@@ -2291,9 +2804,45 @@ const getStorage = (type, window) => {
2291
2804
  }
2292
2805
  };
2293
2806
 
2807
+ // import { patch } from 'rrweb/typings/utils'
2808
+ function patch(source, name, replacement) {
2809
+ try {
2810
+ if (!(name in source)) {
2811
+ return () => {
2812
+ //
2813
+ };
2814
+ }
2815
+ const original = source[name];
2816
+ const wrapped = replacement(original);
2817
+ // Make sure it's a function first, as we need to attach an empty prototype for `defineProperties` to work
2818
+ // otherwise it'll throw "TypeError: Object.defineProperties called on non-object"
2819
+ if (isFunction(wrapped)) {
2820
+ wrapped.prototype = wrapped.prototype || {};
2821
+ Object.defineProperties(wrapped, {
2822
+ __posthog_wrapped__: {
2823
+ enumerable: false,
2824
+ value: true,
2825
+ },
2826
+ });
2827
+ }
2828
+ source[name] = wrapped;
2829
+ return () => {
2830
+ source[name] = original;
2831
+ };
2832
+ }
2833
+ catch {
2834
+ return () => {
2835
+ //
2836
+ };
2837
+ // This can throw if multiple fill happens on a global object like XMLHttpRequest
2838
+ // Fixes https://github.com/getsentry/sentry-javascript/issues/2043
2839
+ }
2840
+ }
2841
+
2294
2842
  class PostHog extends PostHogCore {
2295
2843
  constructor(apiKey, options) {
2296
2844
  super(apiKey, options);
2845
+ this._lastPathname = '';
2297
2846
  // posthog-js stores options in one object on
2298
2847
  this._storageKey = options?.persistence_name ? `ph_${options.persistence_name}` : `ph_${apiKey}_posthog`;
2299
2848
  this._storage = getStorage(options?.persistence || 'localStorage', this.getWindow());
@@ -2301,6 +2850,10 @@ class PostHog extends PostHogCore {
2301
2850
  if (options?.preloadFeatureFlags !== false) {
2302
2851
  this.reloadFeatureFlags();
2303
2852
  }
2853
+ if (options?.captureHistoryEvents && typeof window !== 'undefined') {
2854
+ this._lastPathname = window?.location?.pathname || '';
2855
+ this.setupHistoryEventTracking();
2856
+ }
2304
2857
  }
2305
2858
  getWindow() {
2306
2859
  return typeof window !== 'undefined' ? window : undefined;
@@ -2345,6 +2898,47 @@ class PostHog extends PostHogCore {
2345
2898
  ...getContext(this.getWindow())
2346
2899
  };
2347
2900
  }
2901
+ setupHistoryEventTracking() {
2902
+ const window = this.getWindow();
2903
+ if (!window) {
2904
+ return;
2905
+ }
2906
+ // Old fashioned, we could also use arrow functions but I think the closure for a patch is more reliable
2907
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
2908
+ const self = this;
2909
+ patch(window.history, 'pushState', originalPushState => {
2910
+ return function patchedPushState(state, title, url) {
2911
+ ;
2912
+ originalPushState.call(this, state, title, url);
2913
+ self.captureNavigationEvent('pushState');
2914
+ };
2915
+ });
2916
+ patch(window.history, 'replaceState', originalReplaceState => {
2917
+ return function patchedReplaceState(state, title, url) {
2918
+ ;
2919
+ originalReplaceState.call(this, state, title, url);
2920
+ self.captureNavigationEvent('replaceState');
2921
+ };
2922
+ });
2923
+ // For popstate we need to listen to the event instead of overriding a method
2924
+ window.addEventListener('popstate', () => {
2925
+ this.captureNavigationEvent('popstate');
2926
+ });
2927
+ }
2928
+ captureNavigationEvent(navigationType) {
2929
+ const window = this.getWindow();
2930
+ if (!window) {
2931
+ return;
2932
+ }
2933
+ const currentPathname = window.location.pathname;
2934
+ // Only capture pageview if the pathname has changed
2935
+ if (currentPathname !== this._lastPathname) {
2936
+ this.capture('$pageview', {
2937
+ navigation_type: navigationType
2938
+ });
2939
+ this._lastPathname = currentPathname;
2940
+ }
2941
+ }
2348
2942
  }
2349
2943
 
2350
2944
  export { PostHog, PostHog as default };