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