posthog-js-lite 3.6.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var version = "3.6.0";
5
+ var version = "4.1.0";
6
6
 
7
7
  var PostHogPersistedProperty;
8
8
  (function (PostHogPersistedProperty) {
@@ -26,12 +26,18 @@ var PostHogPersistedProperty;
26
26
  PostHogPersistedProperty["InstalledAppBuild"] = "installed_app_build";
27
27
  PostHogPersistedProperty["InstalledAppVersion"] = "installed_app_version";
28
28
  PostHogPersistedProperty["SessionReplay"] = "session_replay";
29
- PostHogPersistedProperty["DecideEndpointWasHit"] = "decide_endpoint_was_hit";
30
29
  PostHogPersistedProperty["SurveyLastSeenDate"] = "survey_last_seen_date";
31
30
  PostHogPersistedProperty["SurveysSeen"] = "surveys_seen";
32
31
  PostHogPersistedProperty["Surveys"] = "surveys";
33
32
  PostHogPersistedProperty["RemoteConfig"] = "remote_config";
33
+ PostHogPersistedProperty["FlagsEndpointWasHit"] = "flags_endpoint_was_hit";
34
34
  })(PostHogPersistedProperty || (PostHogPersistedProperty = {}));
35
+ // Any key prefixed with `attr__` can be added
36
+ var Compression;
37
+ (function (Compression) {
38
+ Compression["GZipJS"] = "gzip-js";
39
+ Compression["Base64"] = "base64";
40
+ })(Compression || (Compression = {}));
35
41
  var SurveyPosition;
36
42
  (function (SurveyPosition) {
37
43
  SurveyPosition["Left"] = "left";
@@ -92,27 +98,27 @@ var ActionStepStringMatching;
92
98
  ActionStepStringMatching["Regex"] = "regex";
93
99
  })(ActionStepStringMatching || (ActionStepStringMatching = {}));
94
100
 
95
- const normalizeDecideResponse = (decideResponse) => {
96
- if ('flags' in decideResponse) {
97
- // Convert v4 format to v3 format
98
- const featureFlags = getFlagValuesFromFlags(decideResponse.flags);
99
- const featureFlagPayloads = getPayloadsFromFlags(decideResponse.flags);
101
+ const normalizeFlagsResponse = (flagsResponse) => {
102
+ if ('flags' in flagsResponse) {
103
+ // Convert v2 format to v1 format
104
+ const featureFlags = getFlagValuesFromFlags(flagsResponse.flags);
105
+ const featureFlagPayloads = getPayloadsFromFlags(flagsResponse.flags);
100
106
  return {
101
- ...decideResponse,
107
+ ...flagsResponse,
102
108
  featureFlags,
103
109
  featureFlagPayloads,
104
110
  };
105
111
  }
106
112
  else {
107
- // Convert v3 format to v4 format
108
- const featureFlags = decideResponse.featureFlags ?? {};
109
- const featureFlagPayloads = Object.fromEntries(Object.entries(decideResponse.featureFlagPayloads || {}).map(([k, v]) => [k, parsePayload(v)]));
113
+ // Convert v1 format to v2 format
114
+ const featureFlags = flagsResponse.featureFlags ?? {};
115
+ const featureFlagPayloads = Object.fromEntries(Object.entries(flagsResponse.featureFlagPayloads || {}).map(([k, v]) => [k, parsePayload(v)]));
110
116
  const flags = Object.fromEntries(Object.entries(featureFlags).map(([key, value]) => [
111
117
  key,
112
118
  getFlagDetailFromFlagAndPayload(key, value, featureFlagPayloads[key]),
113
119
  ]));
114
120
  return {
115
- ...decideResponse,
121
+ ...flagsResponse,
116
122
  featureFlags,
117
123
  featureFlagPayloads,
118
124
  flags,
@@ -184,7 +190,7 @@ const parsePayload = (response) => {
184
190
  * @param featureFlagPayloads - The feature flag payloads
185
191
  * @returns The normalized flag details
186
192
  */
187
- const createDecideResponseFromFlagsAndPayloads = (featureFlags, featureFlagPayloads) => {
193
+ const createFlagsResponseFromFlagsAndPayloads = (featureFlags, featureFlagPayloads) => {
188
194
  // If a feature flag payload key is not in the feature flags, we treat it as true feature flag.
189
195
  const allKeys = [...new Set([...Object.keys(featureFlags ?? {}), ...Object.keys(featureFlagPayloads ?? {})])];
190
196
  const enabledFlags = allKeys
@@ -194,7 +200,7 @@ const createDecideResponseFromFlagsAndPayloads = (featureFlags, featureFlagPaylo
194
200
  featureFlags: enabledFlags,
195
201
  featureFlagPayloads: featureFlagPayloads ?? {},
196
202
  };
197
- return normalizeDecideResponse(flagDetails);
203
+ return normalizeFlagsResponse(flagDetails);
198
204
  };
199
205
  const updateFlagValue = (flag, value) => {
200
206
  return {
@@ -210,90 +216,6 @@ function getVariantFromValue(value) {
210
216
  return typeof value === 'string' ? value : undefined;
211
217
  }
212
218
 
213
- // Rollout constants
214
- const NEW_FLAGS_ROLLOUT_PERCENTAGE = 1;
215
- // The fnv1a hashes of the tokens that are explicitly excluded from the rollout
216
- // see https://github.com/PostHog/posthog-js-lite/blob/main/posthog-core/src/utils.ts#L84
217
- // are hashed API tokens from our top 10 for each category supported by this SDK.
218
- const NEW_FLAGS_EXCLUDED_HASHES = new Set([
219
- // Node
220
- '61be3dd8',
221
- '96f6df5f',
222
- '8cfdba9b',
223
- 'bf027177',
224
- 'e59430a8',
225
- '7fa5500b',
226
- '569798e9',
227
- '04809ff7',
228
- '0ebc61a5',
229
- '32de7f98',
230
- '3beeb69a',
231
- '12d34ad9',
232
- '733853ec',
233
- '0645bb64',
234
- '5dcbee21',
235
- 'b1f95fa3',
236
- '2189e408',
237
- '82b460c2',
238
- '3a8cc979',
239
- '29ef8843',
240
- '2cdbf767',
241
- '38084b54',
242
- // React Native
243
- '50f9f8de',
244
- '41d0df91',
245
- '5c236689',
246
- 'c11aedd3',
247
- 'ada46672',
248
- 'f4331ee1',
249
- '42fed62a',
250
- 'c957462c',
251
- 'd62f705a',
252
- // Web (lots of teams per org, hence lots of API tokens)
253
- 'e0162666',
254
- '01b3e5cf',
255
- '441cef7f',
256
- 'bb9cafee',
257
- '8f348eb0',
258
- 'b2553f3a',
259
- '97469d7d',
260
- '39f21a76',
261
- '03706dcc',
262
- '27d50569',
263
- '307584a7',
264
- '6433e92e',
265
- '150c7fbb',
266
- '49f57f22',
267
- '3772f65b',
268
- '01eb8256',
269
- '3c9e9234',
270
- 'f853c7f7',
271
- 'c0ac4b67',
272
- 'cd609d40',
273
- '10ca9b1a',
274
- '8a87f11b',
275
- '8e8e5216',
276
- '1f6b63b3',
277
- 'db7943dd',
278
- '79b7164c',
279
- '07f78e33',
280
- '2d21b6fd',
281
- '952db5ee',
282
- 'a7d3b43f',
283
- '1924dd9c',
284
- '84e1b8f6',
285
- 'dff631b6',
286
- 'c5aa8a79',
287
- 'fa133a95',
288
- '498a4508',
289
- '24748755',
290
- '98f3d658',
291
- '21bbda67',
292
- '7dbfed69',
293
- 'be3ec24c',
294
- 'fc80b8e2',
295
- '75cc0998',
296
- ]);
297
219
  const STRING_FORMAT = 'utf8';
298
220
  function assert(truthyValue, message) {
299
221
  if (!truthyValue || typeof truthyValue !== 'string' || isEmpty(truthyValue)) {
@@ -349,464 +271,37 @@ const isError = (x) => {
349
271
  function getFetch() {
350
272
  return typeof fetch !== 'undefined' ? fetch : typeof globalThis.fetch !== 'undefined' ? globalThis.fetch : undefined;
351
273
  }
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;
375
- }
376
274
  function allSettled(promises) {
377
275
  return Promise.all(promises.map((p) => (p ?? Promise.resolve()).then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }))));
378
276
  }
379
277
 
380
- // Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
381
- // This work is free. You can redistribute it and/or modify it
382
- // under the terms of the WTFPL, Version 2
383
- // For more information see LICENSE.txt or http://www.wtfpl.net/
384
- //
385
- // For more information, the home page:
386
- // http://pieroxy.net/blog/pages/lz-string/testing.html
387
- //
388
- // LZ-based compression algorithm, version 1.4.4
389
- // private property
390
- const f = String.fromCharCode;
391
- const keyStrBase64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
392
- const baseReverseDic = {};
393
- function getBaseValue(alphabet, character) {
394
- if (!baseReverseDic[alphabet]) {
395
- baseReverseDic[alphabet] = {};
396
- for (let i = 0; i < alphabet.length; i++) {
397
- baseReverseDic[alphabet][alphabet.charAt(i)] = i;
398
- }
399
- }
400
- return baseReverseDic[alphabet][character];
278
+ /**
279
+ * Older browsers and some runtimes don't support this yet
280
+ * This API (as of 2025-05-07) is not available on React Native.
281
+ */
282
+ function isGzipSupported() {
283
+ return 'CompressionStream' in globalThis;
401
284
  }
402
- const LZString = {
403
- compressToBase64: function (input) {
404
- if (input == null) {
405
- return '';
406
- }
407
- const res = LZString._compress(input, 6, function (a) {
408
- return keyStrBase64.charAt(a);
409
- });
410
- switch (res.length % 4 // To produce valid Base64
411
- ) {
412
- default: // When could this happen ?
413
- case 0:
414
- return res;
415
- case 1:
416
- return res + '===';
417
- case 2:
418
- return res + '==';
419
- case 3:
420
- return res + '=';
421
- }
422
- },
423
- decompressFromBase64: function (input) {
424
- if (input == null) {
425
- return '';
426
- }
427
- if (input == '') {
428
- return null;
429
- }
430
- return LZString._decompress(input.length, 32, function (index) {
431
- return getBaseValue(keyStrBase64, input.charAt(index));
432
- });
433
- },
434
- compress: function (uncompressed) {
435
- return LZString._compress(uncompressed, 16, function (a) {
436
- return f(a);
437
- });
438
- },
439
- _compress: function (uncompressed, bitsPerChar, getCharFromInt) {
440
- if (uncompressed == null) {
441
- return '';
442
- }
443
- const context_dictionary = {}, context_dictionaryToCreate = {}, context_data = [];
444
- let i, value, context_c = '', context_wc = '', context_w = '', context_enlargeIn = 2, // Compensate for the first entry which should not count
445
- context_dictSize = 3, context_numBits = 2, context_data_val = 0, context_data_position = 0, ii;
446
- for (ii = 0; ii < uncompressed.length; ii += 1) {
447
- context_c = uncompressed.charAt(ii);
448
- if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
449
- context_dictionary[context_c] = context_dictSize++;
450
- context_dictionaryToCreate[context_c] = true;
451
- }
452
- context_wc = context_w + context_c;
453
- if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
454
- context_w = context_wc;
455
- }
456
- else {
457
- if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
458
- if (context_w.charCodeAt(0) < 256) {
459
- for (i = 0; i < context_numBits; i++) {
460
- context_data_val = context_data_val << 1;
461
- if (context_data_position == bitsPerChar - 1) {
462
- context_data_position = 0;
463
- context_data.push(getCharFromInt(context_data_val));
464
- context_data_val = 0;
465
- }
466
- else {
467
- context_data_position++;
468
- }
469
- }
470
- value = context_w.charCodeAt(0);
471
- for (i = 0; i < 8; i++) {
472
- context_data_val = (context_data_val << 1) | (value & 1);
473
- if (context_data_position == bitsPerChar - 1) {
474
- context_data_position = 0;
475
- context_data.push(getCharFromInt(context_data_val));
476
- context_data_val = 0;
477
- }
478
- else {
479
- context_data_position++;
480
- }
481
- value = value >> 1;
482
- }
483
- }
484
- else {
485
- value = 1;
486
- for (i = 0; i < context_numBits; i++) {
487
- context_data_val = (context_data_val << 1) | value;
488
- if (context_data_position == bitsPerChar - 1) {
489
- context_data_position = 0;
490
- context_data.push(getCharFromInt(context_data_val));
491
- context_data_val = 0;
492
- }
493
- else {
494
- context_data_position++;
495
- }
496
- value = 0;
497
- }
498
- value = context_w.charCodeAt(0);
499
- for (i = 0; i < 16; i++) {
500
- context_data_val = (context_data_val << 1) | (value & 1);
501
- if (context_data_position == bitsPerChar - 1) {
502
- context_data_position = 0;
503
- context_data.push(getCharFromInt(context_data_val));
504
- context_data_val = 0;
505
- }
506
- else {
507
- context_data_position++;
508
- }
509
- value = value >> 1;
510
- }
511
- }
512
- context_enlargeIn--;
513
- if (context_enlargeIn == 0) {
514
- context_enlargeIn = Math.pow(2, context_numBits);
515
- context_numBits++;
516
- }
517
- delete context_dictionaryToCreate[context_w];
518
- }
519
- else {
520
- value = context_dictionary[context_w];
521
- for (i = 0; i < context_numBits; i++) {
522
- context_data_val = (context_data_val << 1) | (value & 1);
523
- if (context_data_position == bitsPerChar - 1) {
524
- context_data_position = 0;
525
- context_data.push(getCharFromInt(context_data_val));
526
- context_data_val = 0;
527
- }
528
- else {
529
- context_data_position++;
530
- }
531
- value = value >> 1;
532
- }
533
- }
534
- context_enlargeIn--;
535
- if (context_enlargeIn == 0) {
536
- context_enlargeIn = Math.pow(2, context_numBits);
537
- context_numBits++;
538
- }
539
- // Add wc to the dictionary.
540
- context_dictionary[context_wc] = context_dictSize++;
541
- context_w = String(context_c);
542
- }
543
- }
544
- // Output the code for w.
545
- if (context_w !== '') {
546
- if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
547
- if (context_w.charCodeAt(0) < 256) {
548
- for (i = 0; i < context_numBits; i++) {
549
- context_data_val = context_data_val << 1;
550
- if (context_data_position == bitsPerChar - 1) {
551
- context_data_position = 0;
552
- context_data.push(getCharFromInt(context_data_val));
553
- context_data_val = 0;
554
- }
555
- else {
556
- context_data_position++;
557
- }
558
- }
559
- value = context_w.charCodeAt(0);
560
- for (i = 0; i < 8; i++) {
561
- context_data_val = (context_data_val << 1) | (value & 1);
562
- if (context_data_position == bitsPerChar - 1) {
563
- context_data_position = 0;
564
- context_data.push(getCharFromInt(context_data_val));
565
- context_data_val = 0;
566
- }
567
- else {
568
- context_data_position++;
569
- }
570
- value = value >> 1;
571
- }
572
- }
573
- else {
574
- value = 1;
575
- for (i = 0; i < context_numBits; i++) {
576
- context_data_val = (context_data_val << 1) | value;
577
- if (context_data_position == bitsPerChar - 1) {
578
- context_data_position = 0;
579
- context_data.push(getCharFromInt(context_data_val));
580
- context_data_val = 0;
581
- }
582
- else {
583
- context_data_position++;
584
- }
585
- value = 0;
586
- }
587
- value = context_w.charCodeAt(0);
588
- for (i = 0; i < 16; i++) {
589
- context_data_val = (context_data_val << 1) | (value & 1);
590
- if (context_data_position == bitsPerChar - 1) {
591
- context_data_position = 0;
592
- context_data.push(getCharFromInt(context_data_val));
593
- context_data_val = 0;
594
- }
595
- else {
596
- context_data_position++;
597
- }
598
- value = value >> 1;
599
- }
600
- }
601
- context_enlargeIn--;
602
- if (context_enlargeIn == 0) {
603
- context_enlargeIn = Math.pow(2, context_numBits);
604
- context_numBits++;
605
- }
606
- delete context_dictionaryToCreate[context_w];
607
- }
608
- else {
609
- value = context_dictionary[context_w];
610
- for (i = 0; i < context_numBits; i++) {
611
- context_data_val = (context_data_val << 1) | (value & 1);
612
- if (context_data_position == bitsPerChar - 1) {
613
- context_data_position = 0;
614
- context_data.push(getCharFromInt(context_data_val));
615
- context_data_val = 0;
616
- }
617
- else {
618
- context_data_position++;
619
- }
620
- value = value >> 1;
621
- }
622
- }
623
- context_enlargeIn--;
624
- if (context_enlargeIn == 0) {
625
- context_enlargeIn = Math.pow(2, context_numBits);
626
- context_numBits++;
627
- }
628
- }
629
- // Mark the end of the stream
630
- value = 2;
631
- for (i = 0; i < context_numBits; i++) {
632
- context_data_val = (context_data_val << 1) | (value & 1);
633
- if (context_data_position == bitsPerChar - 1) {
634
- context_data_position = 0;
635
- context_data.push(getCharFromInt(context_data_val));
636
- context_data_val = 0;
637
- }
638
- else {
639
- context_data_position++;
640
- }
641
- value = value >> 1;
642
- }
643
- // Flush the last char
644
- while (true) {
645
- context_data_val = context_data_val << 1;
646
- if (context_data_position == bitsPerChar - 1) {
647
- context_data.push(getCharFromInt(context_data_val));
648
- break;
649
- }
650
- else {
651
- context_data_position++;
652
- }
653
- }
654
- return context_data.join('');
655
- },
656
- decompress: function (compressed) {
657
- if (compressed == null) {
658
- return '';
659
- }
660
- if (compressed == '') {
661
- return null;
662
- }
663
- return LZString._decompress(compressed.length, 32768, function (index) {
664
- return compressed.charCodeAt(index);
665
- });
666
- },
667
- _decompress: function (length, resetValue, getNextValue) {
668
- const dictionary = [], result = [], data = { val: getNextValue(0), position: resetValue, index: 1 };
669
- let enlargeIn = 4, dictSize = 4, numBits = 3, entry = '', i, w, bits, resb, maxpower, power, c;
670
- for (i = 0; i < 3; i += 1) {
671
- dictionary[i] = i;
672
- }
673
- bits = 0;
674
- maxpower = Math.pow(2, 2);
675
- power = 1;
676
- while (power != maxpower) {
677
- resb = data.val & data.position;
678
- data.position >>= 1;
679
- if (data.position == 0) {
680
- data.position = resetValue;
681
- data.val = getNextValue(data.index++);
682
- }
683
- bits |= (resb > 0 ? 1 : 0) * power;
684
- power <<= 1;
685
- }
686
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
687
- switch ((bits)) {
688
- case 0:
689
- bits = 0;
690
- maxpower = Math.pow(2, 8);
691
- power = 1;
692
- while (power != maxpower) {
693
- resb = data.val & data.position;
694
- data.position >>= 1;
695
- if (data.position == 0) {
696
- data.position = resetValue;
697
- data.val = getNextValue(data.index++);
698
- }
699
- bits |= (resb > 0 ? 1 : 0) * power;
700
- power <<= 1;
701
- }
702
- c = f(bits);
703
- break;
704
- case 1:
705
- bits = 0;
706
- maxpower = Math.pow(2, 16);
707
- power = 1;
708
- while (power != maxpower) {
709
- resb = data.val & data.position;
710
- data.position >>= 1;
711
- if (data.position == 0) {
712
- data.position = resetValue;
713
- data.val = getNextValue(data.index++);
714
- }
715
- bits |= (resb > 0 ? 1 : 0) * power;
716
- power <<= 1;
717
- }
718
- c = f(bits);
719
- break;
720
- case 2:
721
- return '';
722
- }
723
- dictionary[3] = c;
724
- w = c;
725
- result.push(c);
726
- while (true) {
727
- if (data.index > length) {
728
- return '';
729
- }
730
- bits = 0;
731
- maxpower = Math.pow(2, numBits);
732
- power = 1;
733
- while (power != maxpower) {
734
- resb = data.val & data.position;
735
- data.position >>= 1;
736
- if (data.position == 0) {
737
- data.position = resetValue;
738
- data.val = getNextValue(data.index++);
739
- }
740
- bits |= (resb > 0 ? 1 : 0) * power;
741
- power <<= 1;
742
- }
743
- switch ((c = bits)) {
744
- case 0:
745
- bits = 0;
746
- maxpower = Math.pow(2, 8);
747
- power = 1;
748
- while (power != maxpower) {
749
- resb = data.val & data.position;
750
- data.position >>= 1;
751
- if (data.position == 0) {
752
- data.position = resetValue;
753
- data.val = getNextValue(data.index++);
754
- }
755
- bits |= (resb > 0 ? 1 : 0) * power;
756
- power <<= 1;
757
- }
758
- dictionary[dictSize++] = f(bits);
759
- c = dictSize - 1;
760
- enlargeIn--;
761
- break;
762
- case 1:
763
- bits = 0;
764
- maxpower = Math.pow(2, 16);
765
- power = 1;
766
- while (power != maxpower) {
767
- resb = data.val & data.position;
768
- data.position >>= 1;
769
- if (data.position == 0) {
770
- data.position = resetValue;
771
- data.val = getNextValue(data.index++);
772
- }
773
- bits |= (resb > 0 ? 1 : 0) * power;
774
- power <<= 1;
775
- }
776
- dictionary[dictSize++] = f(bits);
777
- c = dictSize - 1;
778
- enlargeIn--;
779
- break;
780
- case 2:
781
- return result.join('');
782
- }
783
- if (enlargeIn == 0) {
784
- enlargeIn = Math.pow(2, numBits);
785
- numBits++;
786
- }
787
- if (dictionary[c]) {
788
- entry = dictionary[c];
789
- }
790
- else {
791
- if (c === dictSize) {
792
- entry = w + w.charAt(0);
793
- }
794
- else {
795
- return null;
796
- }
797
- }
798
- result.push(entry);
799
- // Add w+entry[0] to the dictionary.
800
- dictionary[dictSize++] = w + entry.charAt(0);
801
- enlargeIn--;
802
- w = entry;
803
- if (enlargeIn == 0) {
804
- enlargeIn = Math.pow(2, numBits);
805
- numBits++;
806
- }
285
+ /**
286
+ * Gzip a string using Compression Streams API if it's available
287
+ */
288
+ async function gzipCompress(input, isDebug = true) {
289
+ try {
290
+ // Turn the string into a stream using a Blob, and then compress it
291
+ const dataStream = new Blob([input], {
292
+ type: 'text/plain',
293
+ }).stream();
294
+ const compressedStream = dataStream.pipeThrough(new CompressionStream('gzip'));
295
+ // Using a Response to easily extract the readablestream value. Decoding into a string for fetch
296
+ return await new Response(compressedStream).blob();
297
+ }
298
+ catch (error) {
299
+ if (isDebug) {
300
+ console.error('Failed to gzip compress data', error);
807
301
  }
808
- },
809
- };
302
+ return null;
303
+ }
304
+ }
810
305
 
811
306
  class SimpleEventEmitter {
812
307
  constructor() {
@@ -1306,7 +801,6 @@ class PostHogCoreStateless {
1306
801
  this.maxBatchSize = Math.max(this.flushAt, options?.maxBatchSize ?? 100);
1307
802
  this.maxQueueSize = Math.max(this.flushAt, options?.maxQueueSize ?? 1000);
1308
803
  this.flushInterval = options?.flushInterval ?? 10000;
1309
- this.captureMode = options?.captureMode || 'json';
1310
804
  this.preloadFeatureFlags = options?.preloadFeatureFlags ?? true;
1311
805
  // If enable is explicitly set to false we override the optout
1312
806
  this.defaultOptIn = options?.defaultOptIn ?? true;
@@ -1325,6 +819,7 @@ class PostHogCoreStateless {
1325
819
  // Init promise allows the derived class to block calls until it is ready
1326
820
  this._initPromise = Promise.resolve();
1327
821
  this._isInitialized = true;
822
+ this.disableCompression = !isGzipSupported() || (options?.disableCompression ?? false);
1328
823
  }
1329
824
  logMsgIfDebug(fn) {
1330
825
  if (this.isDebug) {
@@ -1507,13 +1002,9 @@ class PostHogCoreStateless {
1507
1002
  /***
1508
1003
  *** FEATURE FLAGS
1509
1004
  ***/
1510
- async getDecide(distinctId, groups = {}, personProperties = {}, groupProperties = {}, extraPayload = {}) {
1005
+ async getFlags(distinctId, groups = {}, personProperties = {}, groupProperties = {}, extraPayload = {}) {
1511
1006
  await this._initPromise;
1512
- // Check if the API token is in the new flags rollout
1513
- // This is a temporary measure to ensure that we can still use the old flags API
1514
- // while we migrate to the new flags API
1515
- const useFlags = isTokenInRollout(this.apiKey, NEW_FLAGS_ROLLOUT_PERCENTAGE, NEW_FLAGS_EXCLUDED_HASHES);
1516
- const url = useFlags ? `${this.host}/flags/?v=2` : `${this.host}/decide/?v=4`;
1007
+ const url = `${this.host}/flags/?v=2&config=true`;
1517
1008
  const fetchOptions = {
1518
1009
  method: 'POST',
1519
1010
  headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
@@ -1526,11 +1017,11 @@ class PostHogCoreStateless {
1526
1017
  ...extraPayload,
1527
1018
  }),
1528
1019
  };
1529
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Decide URL', url));
1530
- // Don't retry /decide API calls
1020
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Flags URL', url));
1021
+ // Don't retry /flags API calls
1531
1022
  return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
1532
1023
  .then((response) => response.json())
1533
- .then((response) => normalizeDecideResponse(response))
1024
+ .then((response) => normalizeFlagsResponse(response))
1534
1025
  .catch((error) => {
1535
1026
  this._events.emit('error', error);
1536
1027
  return undefined;
@@ -1559,15 +1050,15 @@ class PostHogCoreStateless {
1559
1050
  }
1560
1051
  async getFeatureFlagDetailStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
1561
1052
  await this._initPromise;
1562
- const decideResponse = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
1563
- if (decideResponse === undefined) {
1053
+ const flagsResponse = await this.getFeatureFlagDetailsStateless(distinctId, groups, personProperties, groupProperties, disableGeoip, [key]);
1054
+ if (flagsResponse === undefined) {
1564
1055
  return undefined;
1565
1056
  }
1566
- const featureFlags = decideResponse.flags;
1057
+ const featureFlags = flagsResponse.flags;
1567
1058
  const flagDetail = featureFlags[key];
1568
1059
  return {
1569
1060
  response: flagDetail,
1570
- requestId: decideResponse.requestId,
1061
+ requestId: flagsResponse.requestId,
1571
1062
  };
1572
1063
  }
1573
1064
  async getFeatureFlagPayloadStateless(key, distinctId, groups = {}, personProperties = {}, groupProperties = {}, disableGeoip) {
@@ -1617,26 +1108,26 @@ class PostHogCoreStateless {
1617
1108
  if (flagKeysToEvaluate) {
1618
1109
  extraPayload['flag_keys_to_evaluate'] = flagKeysToEvaluate;
1619
1110
  }
1620
- const decideResponse = await this.getDecide(distinctId, groups, personProperties, groupProperties, extraPayload);
1621
- if (decideResponse === undefined) {
1111
+ const flagsResponse = await this.getFlags(distinctId, groups, personProperties, groupProperties, extraPayload);
1112
+ if (flagsResponse === undefined) {
1622
1113
  // We probably errored out, so return undefined
1623
1114
  return undefined;
1624
1115
  }
1625
- // if there's an error on the decideResponse, log a console error, but don't throw an error
1626
- if (decideResponse.errorsWhileComputingFlags) {
1116
+ // if there's an error on the flagsResponse, log a console error, but don't throw an error
1117
+ if (flagsResponse.errorsWhileComputingFlags) {
1627
1118
  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');
1628
1119
  }
1629
1120
  // Add check for quota limitation on feature flags
1630
- if (decideResponse.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
1121
+ if (flagsResponse.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
1631
1122
  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');
1632
1123
  return {
1633
1124
  flags: {},
1634
1125
  featureFlags: {},
1635
1126
  featureFlagPayloads: {},
1636
- requestId: decideResponse?.requestId,
1127
+ requestId: flagsResponse?.requestId,
1637
1128
  };
1638
1129
  }
1639
- return decideResponse;
1130
+ return flagsResponse;
1640
1131
  }
1641
1132
  /***
1642
1133
  *** SURVEYS
@@ -1746,22 +1237,17 @@ class PostHogCoreStateless {
1746
1237
  data.historical_migration = true;
1747
1238
  }
1748
1239
  const payload = JSON.stringify(data);
1749
- const url = this.captureMode === 'form'
1750
- ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1751
- : `${this.host}/batch/`;
1752
- const fetchOptions = this.captureMode === 'form'
1753
- ? {
1754
- method: 'POST',
1755
- mode: 'no-cors',
1756
- credentials: 'omit',
1757
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
1758
- body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1759
- }
1760
- : {
1761
- method: 'POST',
1762
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1763
- body: payload,
1764
- };
1240
+ const url = `${this.host}/batch/`;
1241
+ const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
1242
+ const fetchOptions = {
1243
+ method: 'POST',
1244
+ headers: {
1245
+ ...this.getCustomHeaders(),
1246
+ 'Content-Type': 'application/json',
1247
+ ...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
1248
+ },
1249
+ body: gzippedPayload || payload,
1250
+ };
1765
1251
  try {
1766
1252
  await this.fetchWithRetry(url, fetchOptions);
1767
1253
  }
@@ -1885,22 +1371,17 @@ class PostHogCoreStateless {
1885
1371
  data.historical_migration = true;
1886
1372
  }
1887
1373
  const payload = JSON.stringify(data);
1888
- const url = this.captureMode === 'form'
1889
- ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1890
- : `${this.host}/batch/`;
1891
- const fetchOptions = this.captureMode === 'form'
1892
- ? {
1893
- method: 'POST',
1894
- mode: 'no-cors',
1895
- credentials: 'omit',
1896
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
1897
- body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1898
- }
1899
- : {
1900
- method: 'POST',
1901
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1902
- body: payload,
1903
- };
1374
+ const url = `${this.host}/batch/`;
1375
+ const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
1376
+ const fetchOptions = {
1377
+ method: 'POST',
1378
+ headers: {
1379
+ ...this.getCustomHeaders(),
1380
+ 'Content-Type': 'application/json',
1381
+ ...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
1382
+ },
1383
+ body: gzippedPayload || payload,
1384
+ };
1904
1385
  const retryOptions = {
1905
1386
  retryCheck: (err) => {
1906
1387
  // don't automatically retry on 413 errors, we want to reduce the batch size first
@@ -1945,11 +1426,21 @@ class PostHogCoreStateless {
1945
1426
  const body = options.body ? options.body : '';
1946
1427
  let reqByteLength = -1;
1947
1428
  try {
1948
- reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
1429
+ if (body instanceof Blob) {
1430
+ reqByteLength = body.size;
1431
+ }
1432
+ else {
1433
+ reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
1434
+ }
1949
1435
  }
1950
1436
  catch {
1951
- const encoded = new TextEncoder().encode(body);
1952
- reqByteLength = encoded.length;
1437
+ if (body instanceof Blob) {
1438
+ reqByteLength = body.size;
1439
+ }
1440
+ else {
1441
+ const encoded = new TextEncoder().encode(body);
1442
+ reqByteLength = encoded.length;
1443
+ }
1953
1444
  }
1954
1445
  return await retriable(async () => {
1955
1446
  let res = null;
@@ -2068,7 +1559,7 @@ class PostHogCore extends PostHogCoreStateless {
2068
1559
  const bootstrapFeatureFlags = bootstrap.featureFlags;
2069
1560
  const bootstrapFeatureFlagPayloads = bootstrap.featureFlagPayloads ?? {};
2070
1561
  if (bootstrapFeatureFlags && Object.keys(bootstrapFeatureFlags).length) {
2071
- const normalizedBootstrapFeatureFlagDetails = createDecideResponseFromFlagsAndPayloads(bootstrapFeatureFlags, bootstrapFeatureFlagPayloads);
1562
+ const normalizedBootstrapFeatureFlagDetails = createFlagsResponseFromFlagsAndPayloads(bootstrapFeatureFlags, bootstrapFeatureFlagPayloads);
2072
1563
  if (Object.keys(normalizedBootstrapFeatureFlagDetails.flags).length > 0) {
2073
1564
  this.setBootstrappedFeatureFlagDetails(normalizedBootstrapFeatureFlagDetails);
2074
1565
  const currentFeatureFlagDetails = this.getKnownFeatureFlagDetails() || { flags: {}, requestId: undefined };
@@ -2210,7 +1701,7 @@ class PostHogCore extends PostHogCoreStateless {
2210
1701
  ...maybeAdd('$set_once', userPropsOnce),
2211
1702
  });
2212
1703
  if (distinctId !== previousDistinctId) {
2213
- // We keep the AnonymousId to be used by decide calls and identify to link the previousId
1704
+ // We keep the AnonymousId to be used by flags calls and identify to link the previousId
2214
1705
  this.setPersistedProperty(PostHogPersistedProperty.AnonymousId, previousDistinctId);
2215
1706
  this.setPersistedProperty(PostHogPersistedProperty.DistinctId, distinctId);
2216
1707
  this.reloadFeatureFlags();
@@ -2303,10 +1794,6 @@ class PostHogCore extends PostHogCoreStateless {
2303
1794
  this.setPersistedProperty(PostHogPersistedProperty.PersonProperties, null);
2304
1795
  });
2305
1796
  }
2306
- /** @deprecated - Renamed to setPersonPropertiesForFlags */
2307
- personProperties(properties) {
2308
- return this.setPersonPropertiesForFlags(properties);
2309
- }
2310
1797
  setGroupPropertiesForFlags(properties) {
2311
1798
  this.wrap(() => {
2312
1799
  // Get persisted group properties
@@ -2332,12 +1819,6 @@ class PostHogCore extends PostHogCoreStateless {
2332
1819
  this.setPersistedProperty(PostHogPersistedProperty.GroupProperties, null);
2333
1820
  });
2334
1821
  }
2335
- /** @deprecated - Renamed to setGroupPropertiesForFlags */
2336
- groupProperties(properties) {
2337
- this.wrap(() => {
2338
- this.setGroupPropertiesForFlags(properties);
2339
- });
2340
- }
2341
1822
  async remoteConfigAsync() {
2342
1823
  await this._initPromise;
2343
1824
  if (this._remoteConfigResponsePromise) {
@@ -2348,12 +1829,12 @@ class PostHogCore extends PostHogCoreStateless {
2348
1829
  /***
2349
1830
  *** FEATURE FLAGS
2350
1831
  ***/
2351
- async decideAsync(sendAnonDistinctId = true) {
1832
+ async flagsAsync(sendAnonDistinctId = true) {
2352
1833
  await this._initPromise;
2353
- if (this._decideResponsePromise) {
2354
- return this._decideResponsePromise;
1834
+ if (this._flagsResponsePromise) {
1835
+ return this._flagsResponsePromise;
2355
1836
  }
2356
- return this._decideAsync(sendAnonDistinctId);
1837
+ return this._flagsAsync(sendAnonDistinctId);
2357
1838
  }
2358
1839
  cacheSessionReplay(source, response) {
2359
1840
  const sessionReplay = response?.sessionRecording;
@@ -2411,6 +1892,9 @@ class PostHogCore extends PostHogCoreStateless {
2411
1892
  else if (this.preloadFeatureFlags !== false) {
2412
1893
  this.reloadFeatureFlags();
2413
1894
  }
1895
+ if (!response.supportedCompression?.includes(Compression.GZipJS)) {
1896
+ this.disableCompression = true;
1897
+ }
2414
1898
  remoteConfig = response;
2415
1899
  }
2416
1900
  return remoteConfig;
@@ -2421,8 +1905,8 @@ class PostHogCore extends PostHogCoreStateless {
2421
1905
  });
2422
1906
  return this._remoteConfigResponsePromise;
2423
1907
  }
2424
- async _decideAsync(sendAnonDistinctId = true) {
2425
- this._decideResponsePromise = this._initPromise
1908
+ async _flagsAsync(sendAnonDistinctId = true) {
1909
+ this._flagsResponsePromise = this._initPromise
2426
1910
  .then(async () => {
2427
1911
  const distinctId = this.getDistinctId();
2428
1912
  const groups = this.props.$groups || {};
@@ -2432,7 +1916,7 @@ class PostHogCore extends PostHogCoreStateless {
2432
1916
  const extraProperties = {
2433
1917
  $anon_distinct_id: sendAnonDistinctId ? this.getAnonymousId() : undefined,
2434
1918
  };
2435
- const res = await super.getDecide(distinctId, groups, personProperties, groupProperties, extraProperties);
1919
+ const res = await super.getFlags(distinctId, groups, personProperties, groupProperties, extraProperties);
2436
1920
  // Add check for quota limitation on feature flags
2437
1921
  if (res?.quotaLimited?.includes(QuotaLimitedFeature.FeatureFlags)) {
2438
1922
  // Unset all feature flags by setting to null
@@ -2456,22 +1940,22 @@ class PostHogCore extends PostHogCoreStateless {
2456
1940
  };
2457
1941
  }
2458
1942
  this.setKnownFeatureFlagDetails(newFeatureFlagDetails);
2459
- // Mark that we hit the /decide endpoint so we can capture this in the $feature_flag_called event
2460
- this.setPersistedProperty(PostHogPersistedProperty.DecideEndpointWasHit, true);
2461
- this.cacheSessionReplay('decide/flags', res);
1943
+ // Mark that we hit the /flags endpoint so we can capture this in the $feature_flag_called event
1944
+ this.setPersistedProperty(PostHogPersistedProperty.FlagsEndpointWasHit, true);
1945
+ this.cacheSessionReplay('flags', res);
2462
1946
  }
2463
1947
  return res;
2464
1948
  })
2465
1949
  .finally(() => {
2466
- this._decideResponsePromise = undefined;
1950
+ this._flagsResponsePromise = undefined;
2467
1951
  });
2468
- return this._decideResponsePromise;
1952
+ return this._flagsResponsePromise;
2469
1953
  }
2470
1954
  // We only store the flags and request id in the feature flag details storage key
2471
- setKnownFeatureFlagDetails(decideResponse) {
1955
+ setKnownFeatureFlagDetails(flagsResponse) {
2472
1956
  this.wrap(() => {
2473
- this.setPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails, decideResponse);
2474
- this._events.emit('featureflags', getFlagValuesFromFlags(decideResponse?.flags ?? {}));
1957
+ this.setPersistedProperty(PostHogPersistedProperty.FeatureFlagDetails, flagsResponse);
1958
+ this._events.emit('featureflags', getFlagValuesFromFlags(flagsResponse?.flags ?? {}));
2475
1959
  });
2476
1960
  }
2477
1961
  getKnownFeatureFlagDetails() {
@@ -2483,9 +1967,9 @@ class PostHogCore extends PostHogCoreStateless {
2483
1967
  if (featureFlags === undefined && featureFlagPayloads === undefined) {
2484
1968
  return undefined;
2485
1969
  }
2486
- return createDecideResponseFromFlagsAndPayloads(featureFlags ?? {}, featureFlagPayloads ?? {});
1970
+ return createFlagsResponseFromFlagsAndPayloads(featureFlags ?? {}, featureFlagPayloads ?? {});
2487
1971
  }
2488
- return normalizeDecideResponse(storedDetails);
1972
+ return normalizeFlagsResponse(storedDetails);
2489
1973
  }
2490
1974
  getKnownFeatureFlags() {
2491
1975
  const featureFlagDetails = this.getKnownFeatureFlagDetails();
@@ -2549,8 +2033,8 @@ class PostHogCore extends PostHogCoreStateless {
2549
2033
  ...maybeAdd('$feature_flag_reason', featureFlag?.reason?.description ?? featureFlag?.reason?.code),
2550
2034
  ...maybeAdd('$feature_flag_bootstrapped_response', bootstrappedResponse),
2551
2035
  ...maybeAdd('$feature_flag_bootstrapped_payload', bootstrappedPayload),
2552
- // If we haven't yet received a response from the /decide endpoint, we must have used the bootstrapped value
2553
- $used_bootstrap_value: !this.getPersistedProperty(PostHogPersistedProperty.DecideEndpointWasHit),
2036
+ // If we haven't yet received a response from the /flags endpoint, we must have used the bootstrapped value
2037
+ $used_bootstrap_value: !this.getPersistedProperty(PostHogPersistedProperty.FlagsEndpointWasHit),
2554
2038
  ...maybeAdd('$feature_flag_request_id', details.requestId),
2555
2039
  });
2556
2040
  }
@@ -2599,7 +2083,7 @@ class PostHogCore extends PostHogCoreStateless {
2599
2083
  ...details,
2600
2084
  flags,
2601
2085
  };
2602
- return normalizeDecideResponse(result);
2086
+ return normalizeFlagsResponse(result);
2603
2087
  }
2604
2088
  getFeatureFlagsAndPayloads() {
2605
2089
  const flags = this.getFeatureFlags();
@@ -2617,14 +2101,14 @@ class PostHogCore extends PostHogCoreStateless {
2617
2101
  return !!response;
2618
2102
  }
2619
2103
  // Used when we want to trigger the reload but we don't care about the result
2620
- reloadFeatureFlags(cb) {
2621
- this.decideAsync()
2104
+ reloadFeatureFlags(options) {
2105
+ this.flagsAsync(true)
2622
2106
  .then((res) => {
2623
- cb?.(undefined, res?.featureFlags);
2107
+ options?.cb?.(undefined, res?.featureFlags);
2624
2108
  })
2625
2109
  .catch((e) => {
2626
- cb?.(e, undefined);
2627
- if (!cb) {
2110
+ options?.cb?.(e, undefined);
2111
+ if (!options?.cb) {
2628
2112
  this.logMsgIfDebug(() => console.log('PostHog Debug', 'Error reloading feature flags', e));
2629
2113
  }
2630
2114
  });
@@ -2632,8 +2116,8 @@ class PostHogCore extends PostHogCoreStateless {
2632
2116
  async reloadRemoteConfigAsync() {
2633
2117
  return await this.remoteConfigAsync();
2634
2118
  }
2635
- async reloadFeatureFlagsAsync(sendAnonDistinctId = true) {
2636
- return (await this.decideAsync(sendAnonDistinctId))?.featureFlags;
2119
+ async reloadFeatureFlagsAsync(sendAnonDistinctId) {
2120
+ return (await this.flagsAsync(sendAnonDistinctId ?? true))?.featureFlags;
2637
2121
  }
2638
2122
  onFeatureFlags(cb) {
2639
2123
  return this.on('featureflags', async () => {