posthog-js-lite 3.5.1 → 4.0.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.5.1";
5
+ var version = "4.0.0";
6
6
 
7
7
  var PostHogPersistedProperty;
8
8
  (function (PostHogPersistedProperty) {
@@ -19,6 +19,7 @@ var PostHogPersistedProperty;
19
19
  PostHogPersistedProperty["Queue"] = "queue";
20
20
  PostHogPersistedProperty["OptedOut"] = "opted_out";
21
21
  PostHogPersistedProperty["SessionId"] = "session_id";
22
+ PostHogPersistedProperty["SessionStartTimestamp"] = "session_start_timestamp";
22
23
  PostHogPersistedProperty["SessionLastTimestamp"] = "session_timestamp";
23
24
  PostHogPersistedProperty["PersonProperties"] = "person_properties";
24
25
  PostHogPersistedProperty["GroupProperties"] = "group_properties";
@@ -31,6 +32,12 @@ var PostHogPersistedProperty;
31
32
  PostHogPersistedProperty["Surveys"] = "surveys";
32
33
  PostHogPersistedProperty["RemoteConfig"] = "remote_config";
33
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 = {}));
34
41
  var SurveyPosition;
35
42
  (function (SurveyPosition) {
36
43
  SurveyPosition["Left"] = "left";
@@ -371,438 +378,38 @@ function isTokenInRollout(token, percentage = 0, excludedHashes) {
371
378
  const hashInt = parseInt(tokenHash, 16);
372
379
  const hashFloat = hashInt / 0xffffffff;
373
380
  return hashFloat < percentage;
381
+ }
382
+ function allSettled(promises) {
383
+ return Promise.all(promises.map((p) => (p ?? Promise.resolve()).then((value) => ({ status: 'fulfilled', value }), (reason) => ({ status: 'rejected', reason }))));
374
384
  }
375
385
 
376
- // Copyright (c) 2013 Pieroxy <pieroxy@pieroxy.net>
377
- // This work is free. You can redistribute it and/or modify it
378
- // under the terms of the WTFPL, Version 2
379
- // For more information see LICENSE.txt or http://www.wtfpl.net/
380
- //
381
- // For more information, the home page:
382
- // http://pieroxy.net/blog/pages/lz-string/testing.html
383
- //
384
- // LZ-based compression algorithm, version 1.4.4
385
- // private property
386
- const f = String.fromCharCode;
387
- const keyStrBase64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
388
- const baseReverseDic = {};
389
- function getBaseValue(alphabet, character) {
390
- if (!baseReverseDic[alphabet]) {
391
- baseReverseDic[alphabet] = {};
392
- for (let i = 0; i < alphabet.length; i++) {
393
- baseReverseDic[alphabet][alphabet.charAt(i)] = i;
394
- }
395
- }
396
- return baseReverseDic[alphabet][character];
386
+ /**
387
+ * Older browsers and some runtimes don't support this yet
388
+ * This API (as of 2025-05-07) is not available on React Native.
389
+ */
390
+ function isGzipSupported() {
391
+ return 'CompressionStream' in globalThis;
397
392
  }
398
- const LZString = {
399
- compressToBase64: function (input) {
400
- if (input == null) {
401
- return '';
402
- }
403
- const res = LZString._compress(input, 6, function (a) {
404
- return keyStrBase64.charAt(a);
405
- });
406
- switch (res.length % 4 // To produce valid Base64
407
- ) {
408
- default: // When could this happen ?
409
- case 0:
410
- return res;
411
- case 1:
412
- return res + '===';
413
- case 2:
414
- return res + '==';
415
- case 3:
416
- return res + '=';
417
- }
418
- },
419
- decompressFromBase64: function (input) {
420
- if (input == null) {
421
- return '';
422
- }
423
- if (input == '') {
424
- return null;
425
- }
426
- return LZString._decompress(input.length, 32, function (index) {
427
- return getBaseValue(keyStrBase64, input.charAt(index));
428
- });
429
- },
430
- compress: function (uncompressed) {
431
- return LZString._compress(uncompressed, 16, function (a) {
432
- return f(a);
433
- });
434
- },
435
- _compress: function (uncompressed, bitsPerChar, getCharFromInt) {
436
- if (uncompressed == null) {
437
- return '';
438
- }
439
- const context_dictionary = {}, context_dictionaryToCreate = {}, context_data = [];
440
- let i, value, context_c = '', context_wc = '', context_w = '', context_enlargeIn = 2, // Compensate for the first entry which should not count
441
- context_dictSize = 3, context_numBits = 2, context_data_val = 0, context_data_position = 0, ii;
442
- for (ii = 0; ii < uncompressed.length; ii += 1) {
443
- context_c = uncompressed.charAt(ii);
444
- if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) {
445
- context_dictionary[context_c] = context_dictSize++;
446
- context_dictionaryToCreate[context_c] = true;
447
- }
448
- context_wc = context_w + context_c;
449
- if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) {
450
- context_w = context_wc;
451
- }
452
- else {
453
- if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
454
- if (context_w.charCodeAt(0) < 256) {
455
- for (i = 0; i < context_numBits; i++) {
456
- context_data_val = context_data_val << 1;
457
- if (context_data_position == bitsPerChar - 1) {
458
- context_data_position = 0;
459
- context_data.push(getCharFromInt(context_data_val));
460
- context_data_val = 0;
461
- }
462
- else {
463
- context_data_position++;
464
- }
465
- }
466
- value = context_w.charCodeAt(0);
467
- for (i = 0; i < 8; i++) {
468
- context_data_val = (context_data_val << 1) | (value & 1);
469
- if (context_data_position == bitsPerChar - 1) {
470
- context_data_position = 0;
471
- context_data.push(getCharFromInt(context_data_val));
472
- context_data_val = 0;
473
- }
474
- else {
475
- context_data_position++;
476
- }
477
- value = value >> 1;
478
- }
479
- }
480
- else {
481
- value = 1;
482
- for (i = 0; i < context_numBits; i++) {
483
- context_data_val = (context_data_val << 1) | value;
484
- if (context_data_position == bitsPerChar - 1) {
485
- context_data_position = 0;
486
- context_data.push(getCharFromInt(context_data_val));
487
- context_data_val = 0;
488
- }
489
- else {
490
- context_data_position++;
491
- }
492
- value = 0;
493
- }
494
- value = context_w.charCodeAt(0);
495
- for (i = 0; i < 16; i++) {
496
- context_data_val = (context_data_val << 1) | (value & 1);
497
- if (context_data_position == bitsPerChar - 1) {
498
- context_data_position = 0;
499
- context_data.push(getCharFromInt(context_data_val));
500
- context_data_val = 0;
501
- }
502
- else {
503
- context_data_position++;
504
- }
505
- value = value >> 1;
506
- }
507
- }
508
- context_enlargeIn--;
509
- if (context_enlargeIn == 0) {
510
- context_enlargeIn = Math.pow(2, context_numBits);
511
- context_numBits++;
512
- }
513
- delete context_dictionaryToCreate[context_w];
514
- }
515
- else {
516
- value = context_dictionary[context_w];
517
- for (i = 0; i < context_numBits; i++) {
518
- context_data_val = (context_data_val << 1) | (value & 1);
519
- if (context_data_position == bitsPerChar - 1) {
520
- context_data_position = 0;
521
- context_data.push(getCharFromInt(context_data_val));
522
- context_data_val = 0;
523
- }
524
- else {
525
- context_data_position++;
526
- }
527
- value = value >> 1;
528
- }
529
- }
530
- context_enlargeIn--;
531
- if (context_enlargeIn == 0) {
532
- context_enlargeIn = Math.pow(2, context_numBits);
533
- context_numBits++;
534
- }
535
- // Add wc to the dictionary.
536
- context_dictionary[context_wc] = context_dictSize++;
537
- context_w = String(context_c);
538
- }
539
- }
540
- // Output the code for w.
541
- if (context_w !== '') {
542
- if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) {
543
- if (context_w.charCodeAt(0) < 256) {
544
- for (i = 0; i < context_numBits; i++) {
545
- context_data_val = context_data_val << 1;
546
- if (context_data_position == bitsPerChar - 1) {
547
- context_data_position = 0;
548
- context_data.push(getCharFromInt(context_data_val));
549
- context_data_val = 0;
550
- }
551
- else {
552
- context_data_position++;
553
- }
554
- }
555
- value = context_w.charCodeAt(0);
556
- for (i = 0; i < 8; i++) {
557
- context_data_val = (context_data_val << 1) | (value & 1);
558
- if (context_data_position == bitsPerChar - 1) {
559
- context_data_position = 0;
560
- context_data.push(getCharFromInt(context_data_val));
561
- context_data_val = 0;
562
- }
563
- else {
564
- context_data_position++;
565
- }
566
- value = value >> 1;
567
- }
568
- }
569
- else {
570
- value = 1;
571
- for (i = 0; i < context_numBits; i++) {
572
- context_data_val = (context_data_val << 1) | value;
573
- if (context_data_position == bitsPerChar - 1) {
574
- context_data_position = 0;
575
- context_data.push(getCharFromInt(context_data_val));
576
- context_data_val = 0;
577
- }
578
- else {
579
- context_data_position++;
580
- }
581
- value = 0;
582
- }
583
- value = context_w.charCodeAt(0);
584
- for (i = 0; i < 16; i++) {
585
- context_data_val = (context_data_val << 1) | (value & 1);
586
- if (context_data_position == bitsPerChar - 1) {
587
- context_data_position = 0;
588
- context_data.push(getCharFromInt(context_data_val));
589
- context_data_val = 0;
590
- }
591
- else {
592
- context_data_position++;
593
- }
594
- value = value >> 1;
595
- }
596
- }
597
- context_enlargeIn--;
598
- if (context_enlargeIn == 0) {
599
- context_enlargeIn = Math.pow(2, context_numBits);
600
- context_numBits++;
601
- }
602
- delete context_dictionaryToCreate[context_w];
603
- }
604
- else {
605
- value = context_dictionary[context_w];
606
- for (i = 0; i < context_numBits; i++) {
607
- context_data_val = (context_data_val << 1) | (value & 1);
608
- if (context_data_position == bitsPerChar - 1) {
609
- context_data_position = 0;
610
- context_data.push(getCharFromInt(context_data_val));
611
- context_data_val = 0;
612
- }
613
- else {
614
- context_data_position++;
615
- }
616
- value = value >> 1;
617
- }
618
- }
619
- context_enlargeIn--;
620
- if (context_enlargeIn == 0) {
621
- context_enlargeIn = Math.pow(2, context_numBits);
622
- context_numBits++;
623
- }
624
- }
625
- // Mark the end of the stream
626
- value = 2;
627
- for (i = 0; i < context_numBits; i++) {
628
- context_data_val = (context_data_val << 1) | (value & 1);
629
- if (context_data_position == bitsPerChar - 1) {
630
- context_data_position = 0;
631
- context_data.push(getCharFromInt(context_data_val));
632
- context_data_val = 0;
633
- }
634
- else {
635
- context_data_position++;
636
- }
637
- value = value >> 1;
638
- }
639
- // Flush the last char
640
- while (true) {
641
- context_data_val = context_data_val << 1;
642
- if (context_data_position == bitsPerChar - 1) {
643
- context_data.push(getCharFromInt(context_data_val));
644
- break;
645
- }
646
- else {
647
- context_data_position++;
648
- }
649
- }
650
- return context_data.join('');
651
- },
652
- decompress: function (compressed) {
653
- if (compressed == null) {
654
- return '';
655
- }
656
- if (compressed == '') {
657
- return null;
658
- }
659
- return LZString._decompress(compressed.length, 32768, function (index) {
660
- return compressed.charCodeAt(index);
661
- });
662
- },
663
- _decompress: function (length, resetValue, getNextValue) {
664
- const dictionary = [], result = [], data = { val: getNextValue(0), position: resetValue, index: 1 };
665
- let enlargeIn = 4, dictSize = 4, numBits = 3, entry = '', i, w, bits, resb, maxpower, power, c;
666
- for (i = 0; i < 3; i += 1) {
667
- dictionary[i] = i;
668
- }
669
- bits = 0;
670
- maxpower = Math.pow(2, 2);
671
- power = 1;
672
- while (power != maxpower) {
673
- resb = data.val & data.position;
674
- data.position >>= 1;
675
- if (data.position == 0) {
676
- data.position = resetValue;
677
- data.val = getNextValue(data.index++);
678
- }
679
- bits |= (resb > 0 ? 1 : 0) * power;
680
- power <<= 1;
681
- }
682
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
683
- switch ((bits)) {
684
- case 0:
685
- bits = 0;
686
- maxpower = Math.pow(2, 8);
687
- power = 1;
688
- while (power != maxpower) {
689
- resb = data.val & data.position;
690
- data.position >>= 1;
691
- if (data.position == 0) {
692
- data.position = resetValue;
693
- data.val = getNextValue(data.index++);
694
- }
695
- bits |= (resb > 0 ? 1 : 0) * power;
696
- power <<= 1;
697
- }
698
- c = f(bits);
699
- break;
700
- case 1:
701
- bits = 0;
702
- maxpower = Math.pow(2, 16);
703
- power = 1;
704
- while (power != maxpower) {
705
- resb = data.val & data.position;
706
- data.position >>= 1;
707
- if (data.position == 0) {
708
- data.position = resetValue;
709
- data.val = getNextValue(data.index++);
710
- }
711
- bits |= (resb > 0 ? 1 : 0) * power;
712
- power <<= 1;
713
- }
714
- c = f(bits);
715
- break;
716
- case 2:
717
- return '';
718
- }
719
- dictionary[3] = c;
720
- w = c;
721
- result.push(c);
722
- while (true) {
723
- if (data.index > length) {
724
- return '';
725
- }
726
- bits = 0;
727
- maxpower = Math.pow(2, numBits);
728
- power = 1;
729
- while (power != maxpower) {
730
- resb = data.val & data.position;
731
- data.position >>= 1;
732
- if (data.position == 0) {
733
- data.position = resetValue;
734
- data.val = getNextValue(data.index++);
735
- }
736
- bits |= (resb > 0 ? 1 : 0) * power;
737
- power <<= 1;
738
- }
739
- switch ((c = bits)) {
740
- case 0:
741
- bits = 0;
742
- maxpower = Math.pow(2, 8);
743
- power = 1;
744
- while (power != maxpower) {
745
- resb = data.val & data.position;
746
- data.position >>= 1;
747
- if (data.position == 0) {
748
- data.position = resetValue;
749
- data.val = getNextValue(data.index++);
750
- }
751
- bits |= (resb > 0 ? 1 : 0) * power;
752
- power <<= 1;
753
- }
754
- dictionary[dictSize++] = f(bits);
755
- c = dictSize - 1;
756
- enlargeIn--;
757
- break;
758
- case 1:
759
- bits = 0;
760
- maxpower = Math.pow(2, 16);
761
- power = 1;
762
- while (power != maxpower) {
763
- resb = data.val & data.position;
764
- data.position >>= 1;
765
- if (data.position == 0) {
766
- data.position = resetValue;
767
- data.val = getNextValue(data.index++);
768
- }
769
- bits |= (resb > 0 ? 1 : 0) * power;
770
- power <<= 1;
771
- }
772
- dictionary[dictSize++] = f(bits);
773
- c = dictSize - 1;
774
- enlargeIn--;
775
- break;
776
- case 2:
777
- return result.join('');
778
- }
779
- if (enlargeIn == 0) {
780
- enlargeIn = Math.pow(2, numBits);
781
- numBits++;
782
- }
783
- if (dictionary[c]) {
784
- entry = dictionary[c];
785
- }
786
- else {
787
- if (c === dictSize) {
788
- entry = w + w.charAt(0);
789
- }
790
- else {
791
- return null;
792
- }
793
- }
794
- result.push(entry);
795
- // Add w+entry[0] to the dictionary.
796
- dictionary[dictSize++] = w + entry.charAt(0);
797
- enlargeIn--;
798
- w = entry;
799
- if (enlargeIn == 0) {
800
- enlargeIn = Math.pow(2, numBits);
801
- numBits++;
802
- }
393
+ /**
394
+ * Gzip a string using Compression Streams API if it's available
395
+ */
396
+ async function gzipCompress(input, isDebug = true) {
397
+ try {
398
+ // Turn the string into a stream using a Blob, and then compress it
399
+ const dataStream = new Blob([input], {
400
+ type: 'text/plain',
401
+ }).stream();
402
+ const compressedStream = dataStream.pipeThrough(new CompressionStream('gzip'));
403
+ // Using a Response to easily extract the readablestream value. Decoding into a string for fetch
404
+ return await new Response(compressedStream).blob();
405
+ }
406
+ catch (error) {
407
+ if (isDebug) {
408
+ console.error('Failed to gzip compress data', error);
803
409
  }
804
- },
805
- };
410
+ return null;
411
+ }
412
+ }
806
413
 
807
414
  class SimpleEventEmitter {
808
415
  constructor() {
@@ -1261,6 +868,7 @@ class PostHogFetchNetworkError extends Error {
1261
868
  this.name = 'PostHogFetchNetworkError';
1262
869
  }
1263
870
  }
871
+ const maybeAdd = (key, value) => value !== undefined ? { [key]: value } : {};
1264
872
  async function logFlushError(err) {
1265
873
  if (err instanceof PostHogFetchHttpError) {
1266
874
  let text = '';
@@ -1301,7 +909,6 @@ class PostHogCoreStateless {
1301
909
  this.maxBatchSize = Math.max(this.flushAt, options?.maxBatchSize ?? 100);
1302
910
  this.maxQueueSize = Math.max(this.flushAt, options?.maxQueueSize ?? 1000);
1303
911
  this.flushInterval = options?.flushInterval ?? 10000;
1304
- this.captureMode = options?.captureMode || 'json';
1305
912
  this.preloadFeatureFlags = options?.preloadFeatureFlags ?? true;
1306
913
  // If enable is explicitly set to false we override the optout
1307
914
  this.defaultOptIn = options?.defaultOptIn ?? true;
@@ -1320,6 +927,7 @@ class PostHogCoreStateless {
1320
927
  // Init promise allows the derived class to block calls until it is ready
1321
928
  this._initPromise = Promise.resolve();
1322
929
  this._isInitialized = true;
930
+ this.disableCompression = !isGzipSupported() || (options?.disableCompression ?? false);
1323
931
  }
1324
932
  logMsgIfDebug(fn) {
1325
933
  if (this.isDebug) {
@@ -1521,6 +1129,7 @@ class PostHogCoreStateless {
1521
1129
  ...extraPayload,
1522
1130
  }),
1523
1131
  };
1132
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Decide URL', url));
1524
1133
  // Don't retry /decide API calls
1525
1134
  return this.fetchWithRetry(url, fetchOptions, { retryCount: 0 }, this.featureFlagsRequestTimeoutMs)
1526
1135
  .then((response) => response.json())
@@ -1638,7 +1247,7 @@ class PostHogCoreStateless {
1638
1247
  async getSurveysStateless() {
1639
1248
  await this._initPromise;
1640
1249
  if (this.disableSurveys === true) {
1641
- this.logMsgIfDebug(() => console.log('Loading surveys is disabled.'));
1250
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Loading surveys is disabled.'));
1642
1251
  return [];
1643
1252
  }
1644
1253
  const url = `${this.host}/api/surveys/?token=${this.apiKey}`;
@@ -1740,28 +1349,22 @@ class PostHogCoreStateless {
1740
1349
  data.historical_migration = true;
1741
1350
  }
1742
1351
  const payload = JSON.stringify(data);
1743
- const url = this.captureMode === 'form'
1744
- ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1745
- : `${this.host}/batch/`;
1746
- const fetchOptions = this.captureMode === 'form'
1747
- ? {
1748
- method: 'POST',
1749
- mode: 'no-cors',
1750
- credentials: 'omit',
1751
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
1752
- body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1753
- }
1754
- : {
1755
- method: 'POST',
1756
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1757
- body: payload,
1758
- };
1352
+ const url = `${this.host}/batch/`;
1353
+ const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
1354
+ const fetchOptions = {
1355
+ method: 'POST',
1356
+ headers: {
1357
+ ...this.getCustomHeaders(),
1358
+ 'Content-Type': 'application/json',
1359
+ ...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
1360
+ },
1361
+ body: gzippedPayload || payload,
1362
+ };
1759
1363
  try {
1760
1364
  await this.fetchWithRetry(url, fetchOptions);
1761
1365
  }
1762
1366
  catch (err) {
1763
1367
  this._events.emit('error', err);
1764
- throw err;
1765
1368
  }
1766
1369
  }
1767
1370
  prepareMessage(type, _message, options) {
@@ -1801,15 +1404,38 @@ class PostHogCoreStateless {
1801
1404
  await logFlushError(err);
1802
1405
  });
1803
1406
  }
1407
+ /**
1408
+ * Flushes the queue
1409
+ *
1410
+ * This function will return a promise that will resolve when the flush is complete,
1411
+ * or reject if there was an error (for example if the server or network is down).
1412
+ *
1413
+ * If there is already a flush in progress, this function will wait for that flush to complete.
1414
+ *
1415
+ * It's recommended to do error handling in the callback of the promise.
1416
+ *
1417
+ * @example
1418
+ * posthog.flush().then(() => {
1419
+ * console.log('Flush complete')
1420
+ * }).catch((err) => {
1421
+ * console.error('Flush failed', err)
1422
+ * })
1423
+ *
1424
+ *
1425
+ * @throws PostHogFetchHttpError
1426
+ * @throws PostHogFetchNetworkError
1427
+ * @throws Error
1428
+ */
1804
1429
  async flush() {
1805
1430
  // Wait for the current flush operation to finish (regardless of success or failure), then try to flush again.
1806
1431
  // Use allSettled instead of finally to be defensive around flush throwing errors immediately rather than rejecting.
1807
- const nextFlushPromise = Promise.allSettled([this.flushPromise]).then(() => {
1432
+ // Use a custom allSettled implementation to avoid issues with patching Promise on RN
1433
+ const nextFlushPromise = allSettled([this.flushPromise]).then(() => {
1808
1434
  return this._flush();
1809
1435
  });
1810
1436
  this.flushPromise = nextFlushPromise;
1811
1437
  void this.addPendingPromise(nextFlushPromise);
1812
- Promise.allSettled([nextFlushPromise]).then(() => {
1438
+ allSettled([nextFlushPromise]).then(() => {
1813
1439
  // If there are no others waiting to flush, clear the promise.
1814
1440
  // We don't strictly need to do this, but it could make debugging easier
1815
1441
  if (this.flushPromise === nextFlushPromise) {
@@ -1835,7 +1461,7 @@ class PostHogCoreStateless {
1835
1461
  await this._initPromise;
1836
1462
  let queue = this.getPersistedProperty(PostHogPersistedProperty.Queue) || [];
1837
1463
  if (!queue.length) {
1838
- return [];
1464
+ return;
1839
1465
  }
1840
1466
  const sentMessages = [];
1841
1467
  const originalQueueLength = queue.length;
@@ -1857,22 +1483,17 @@ class PostHogCoreStateless {
1857
1483
  data.historical_migration = true;
1858
1484
  }
1859
1485
  const payload = JSON.stringify(data);
1860
- const url = this.captureMode === 'form'
1861
- ? `${this.host}/e/?ip=1&_=${currentTimestamp()}&v=${this.getLibraryVersion()}`
1862
- : `${this.host}/batch/`;
1863
- const fetchOptions = this.captureMode === 'form'
1864
- ? {
1865
- method: 'POST',
1866
- mode: 'no-cors',
1867
- credentials: 'omit',
1868
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/x-www-form-urlencoded' },
1869
- body: `data=${encodeURIComponent(LZString.compressToBase64(payload))}&compression=lz64`,
1870
- }
1871
- : {
1872
- method: 'POST',
1873
- headers: { ...this.getCustomHeaders(), 'Content-Type': 'application/json' },
1874
- body: payload,
1875
- };
1486
+ const url = `${this.host}/batch/`;
1487
+ const gzippedPayload = !this.disableCompression ? await gzipCompress(payload, this.isDebug) : null;
1488
+ const fetchOptions = {
1489
+ method: 'POST',
1490
+ headers: {
1491
+ ...this.getCustomHeaders(),
1492
+ 'Content-Type': 'application/json',
1493
+ ...(gzippedPayload !== null && { 'Content-Encoding': 'gzip' }),
1494
+ },
1495
+ body: gzippedPayload || payload,
1496
+ };
1876
1497
  const retryOptions = {
1877
1498
  retryCheck: (err) => {
1878
1499
  // don't automatically retry on 413 errors, we want to reduce the batch size first
@@ -1906,7 +1527,6 @@ class PostHogCoreStateless {
1906
1527
  sentMessages.push(...batchMessages);
1907
1528
  }
1908
1529
  this._events.emit('flush', sentMessages);
1909
- return sentMessages;
1910
1530
  }
1911
1531
  async fetchWithRetry(url, options, retryOptions, requestTimeout) {
1912
1532
  var _a;
@@ -1916,7 +1536,24 @@ class PostHogCoreStateless {
1916
1536
  return ctrl.signal;
1917
1537
  });
1918
1538
  const body = options.body ? options.body : '';
1919
- const reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
1539
+ let reqByteLength = -1;
1540
+ try {
1541
+ if (body instanceof Blob) {
1542
+ reqByteLength = body.size;
1543
+ }
1544
+ else {
1545
+ reqByteLength = Buffer.byteLength(body, STRING_FORMAT);
1546
+ }
1547
+ }
1548
+ catch {
1549
+ if (body instanceof Blob) {
1550
+ reqByteLength = body.size;
1551
+ }
1552
+ else {
1553
+ const encoded = new TextEncoder().encode(body);
1554
+ reqByteLength = encoded.length;
1555
+ }
1556
+ }
1920
1557
  return await retriable(async () => {
1921
1558
  let res = null;
1922
1559
  try {
@@ -2005,6 +1642,7 @@ class PostHogCore extends PostHogCoreStateless {
2005
1642
  const featureFlagsRequestTimeoutMs = options?.featureFlagsRequestTimeoutMs ?? 10000; // 10 seconds
2006
1643
  super(apiKey, { ...options, disableGeoip: disableGeoipOption, featureFlagsRequestTimeoutMs });
2007
1644
  this.flagCallReported = {};
1645
+ this._sessionMaxLengthSeconds = 24 * 60 * 60; // 24 hours
2008
1646
  this.sessionProps = {};
2009
1647
  this.sendFeatureFlagEvent = options?.sendFeatureFlagEvent ?? true;
2010
1648
  this._sessionExpirationTimeSeconds = options?.sessionExpirationTimeSeconds ?? 1800; // 30 minutes
@@ -2078,7 +1716,7 @@ class PostHogCore extends PostHogCoreStateless {
2078
1716
  }
2079
1717
  }
2080
1718
  return {
2081
- $active_feature_flags: featureFlags ? Object.keys(featureFlags) : undefined,
1719
+ ...maybeAdd('$active_feature_flags', featureFlags ? Object.keys(featureFlags) : undefined),
2082
1720
  ...featureVariantProperties,
2083
1721
  ...super.getCommonEventProperties(),
2084
1722
  };
@@ -2100,18 +1738,26 @@ class PostHogCore extends PostHogCoreStateless {
2100
1738
  return '';
2101
1739
  }
2102
1740
  let sessionId = this.getPersistedProperty(PostHogPersistedProperty.SessionId);
2103
- const sessionTimestamp = this.getPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp) || 0;
2104
- if (!sessionId || Date.now() - sessionTimestamp > this._sessionExpirationTimeSeconds * 1000) {
1741
+ const sessionLastTimestamp = this.getPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp) || 0;
1742
+ const sessionStartTimestamp = this.getPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp) || 0;
1743
+ const now = Date.now();
1744
+ const sessionLastDif = now - sessionLastTimestamp;
1745
+ const sessionStartDif = now - sessionStartTimestamp;
1746
+ if (!sessionId ||
1747
+ sessionLastDif > this._sessionExpirationTimeSeconds * 1000 ||
1748
+ sessionStartDif > this._sessionMaxLengthSeconds * 1000) {
2105
1749
  sessionId = uuidv7();
2106
1750
  this.setPersistedProperty(PostHogPersistedProperty.SessionId, sessionId);
1751
+ this.setPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp, now);
2107
1752
  }
2108
- this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, Date.now());
1753
+ this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, now);
2109
1754
  return sessionId;
2110
1755
  }
2111
1756
  resetSessionId() {
2112
1757
  this.wrap(() => {
2113
1758
  this.setPersistedProperty(PostHogPersistedProperty.SessionId, null);
2114
1759
  this.setPersistedProperty(PostHogPersistedProperty.SessionLastTimestamp, null);
1760
+ this.setPersistedProperty(PostHogPersistedProperty.SessionStartTimestamp, null);
2115
1761
  });
2116
1762
  }
2117
1763
  /**
@@ -2163,8 +1809,8 @@ class PostHogCore extends PostHogCoreStateless {
2163
1809
  const userProps = properties?.$set || properties;
2164
1810
  const allProperties = this.enrichProperties({
2165
1811
  $anon_distinct_id: this.getAnonymousId(),
2166
- $set: userProps,
2167
- $set_once: userPropsOnce,
1812
+ ...maybeAdd('$set', userProps),
1813
+ ...maybeAdd('$set_once', userPropsOnce),
2168
1814
  });
2169
1815
  if (distinctId !== previousDistinctId) {
2170
1816
  // We keep the AnonymousId to be used by decide calls and identify to link the previousId
@@ -2260,10 +1906,6 @@ class PostHogCore extends PostHogCoreStateless {
2260
1906
  this.setPersistedProperty(PostHogPersistedProperty.PersonProperties, null);
2261
1907
  });
2262
1908
  }
2263
- /** @deprecated - Renamed to setPersonPropertiesForFlags */
2264
- personProperties(properties) {
2265
- return this.setPersonPropertiesForFlags(properties);
2266
- }
2267
1909
  setGroupPropertiesForFlags(properties) {
2268
1910
  this.wrap(() => {
2269
1911
  // Get persisted group properties
@@ -2289,12 +1931,6 @@ class PostHogCore extends PostHogCoreStateless {
2289
1931
  this.setPersistedProperty(PostHogPersistedProperty.GroupProperties, null);
2290
1932
  });
2291
1933
  }
2292
- /** @deprecated - Renamed to setGroupPropertiesForFlags */
2293
- groupProperties(properties) {
2294
- this.wrap(() => {
2295
- this.setGroupPropertiesForFlags(properties);
2296
- });
2297
- }
2298
1934
  async remoteConfigAsync() {
2299
1935
  await this._initPromise;
2300
1936
  if (this._remoteConfigResponsePromise) {
@@ -2312,14 +1948,16 @@ class PostHogCore extends PostHogCoreStateless {
2312
1948
  }
2313
1949
  return this._decideAsync(sendAnonDistinctId);
2314
1950
  }
2315
- cacheSessionReplay(response) {
1951
+ cacheSessionReplay(source, response) {
2316
1952
  const sessionReplay = response?.sessionRecording;
2317
1953
  if (sessionReplay) {
2318
1954
  this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, sessionReplay);
2319
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Session replay config: ', JSON.stringify(sessionReplay)));
1955
+ this.logMsgIfDebug(() => console.log('PostHog Debug', `Session replay config from ${source}: `, JSON.stringify(sessionReplay)));
2320
1956
  }
2321
- else {
2322
- this.logMsgIfDebug(() => console.info('PostHog Debug', 'Session replay config disabled.'));
1957
+ else if (typeof sessionReplay === 'boolean' && sessionReplay === false) {
1958
+ // if session replay is disabled, we don't need to cache it
1959
+ // we need to check for this because the response might be undefined (/flags does not return sessionRecording yet)
1960
+ this.logMsgIfDebug(() => console.info('PostHog Debug', `Session replay config from ${source} disabled.`));
2323
1961
  this.setPersistedProperty(PostHogPersistedProperty.SessionReplay, null);
2324
1962
  }
2325
1963
  }
@@ -2333,25 +1971,30 @@ class PostHogCore extends PostHogCoreStateless {
2333
1971
  const remoteConfigWithoutSurveys = { ...response };
2334
1972
  delete remoteConfigWithoutSurveys.surveys;
2335
1973
  this.logMsgIfDebug(() => console.log('PostHog Debug', 'Fetched remote config: ', JSON.stringify(remoteConfigWithoutSurveys)));
2336
- const surveys = response.surveys;
2337
- let hasSurveys = true;
2338
- if (!Array.isArray(surveys)) {
2339
- // If surveys is not an array, it means there are no surveys (its a boolean instead)
2340
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'There are no surveys.'));
2341
- hasSurveys = false;
2342
- }
2343
- else {
2344
- this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from remote config: ', JSON.stringify(surveys)));
2345
- }
2346
- if (this.disableSurveys === false && hasSurveys) {
2347
- this.setPersistedProperty(PostHogPersistedProperty.Surveys, surveys);
1974
+ if (this.disableSurveys === false) {
1975
+ const surveys = response.surveys;
1976
+ let hasSurveys = true;
1977
+ if (!Array.isArray(surveys)) {
1978
+ // If surveys is not an array, it means there are no surveys (its a boolean instead)
1979
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'There are no surveys.'));
1980
+ hasSurveys = false;
1981
+ }
1982
+ else {
1983
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Surveys fetched from remote config: ', JSON.stringify(surveys)));
1984
+ }
1985
+ if (hasSurveys) {
1986
+ this.setPersistedProperty(PostHogPersistedProperty.Surveys, surveys);
1987
+ }
1988
+ else {
1989
+ this.setPersistedProperty(PostHogPersistedProperty.Surveys, null);
1990
+ }
2348
1991
  }
2349
1992
  else {
2350
1993
  this.setPersistedProperty(PostHogPersistedProperty.Surveys, null);
2351
1994
  }
2352
1995
  // we cache the surveys in its own storage key
2353
1996
  this.setPersistedProperty(PostHogPersistedProperty.RemoteConfig, remoteConfigWithoutSurveys);
2354
- this.cacheSessionReplay(response);
1997
+ this.cacheSessionReplay('remote config', response);
2355
1998
  // we only dont load flags if the remote config has no feature flags
2356
1999
  if (response.hasFeatureFlags === false) {
2357
2000
  // resetting flags to empty object
@@ -2361,6 +2004,9 @@ class PostHogCore extends PostHogCoreStateless {
2361
2004
  else if (this.preloadFeatureFlags !== false) {
2362
2005
  this.reloadFeatureFlags();
2363
2006
  }
2007
+ if (!response.supportedCompression?.includes(Compression.GZipJS)) {
2008
+ this.disableCompression = true;
2009
+ }
2364
2010
  remoteConfig = response;
2365
2011
  }
2366
2012
  return remoteConfig;
@@ -2408,7 +2054,7 @@ class PostHogCore extends PostHogCoreStateless {
2408
2054
  this.setKnownFeatureFlagDetails(newFeatureFlagDetails);
2409
2055
  // Mark that we hit the /decide endpoint so we can capture this in the $feature_flag_called event
2410
2056
  this.setPersistedProperty(PostHogPersistedProperty.DecideEndpointWasHit, true);
2411
- this.cacheSessionReplay(res);
2057
+ this.cacheSessionReplay('decide/flags', res);
2412
2058
  }
2413
2059
  return res;
2414
2060
  })
@@ -2494,14 +2140,14 @@ class PostHogCore extends PostHogCoreStateless {
2494
2140
  this.capture('$feature_flag_called', {
2495
2141
  $feature_flag: key,
2496
2142
  $feature_flag_response: response,
2497
- $feature_flag_id: featureFlag?.metadata?.id,
2498
- $feature_flag_version: featureFlag?.metadata?.version,
2499
- $feature_flag_reason: featureFlag?.reason?.description ?? featureFlag?.reason?.code,
2500
- $feature_flag_bootstrapped_response: bootstrappedResponse,
2501
- $feature_flag_bootstrapped_payload: bootstrappedPayload,
2143
+ ...maybeAdd('$feature_flag_id', featureFlag?.metadata?.id),
2144
+ ...maybeAdd('$feature_flag_version', featureFlag?.metadata?.version),
2145
+ ...maybeAdd('$feature_flag_reason', featureFlag?.reason?.description ?? featureFlag?.reason?.code),
2146
+ ...maybeAdd('$feature_flag_bootstrapped_response', bootstrappedResponse),
2147
+ ...maybeAdd('$feature_flag_bootstrapped_payload', bootstrappedPayload),
2502
2148
  // If we haven't yet received a response from the /decide endpoint, we must have used the bootstrapped value
2503
2149
  $used_bootstrap_value: !this.getPersistedProperty(PostHogPersistedProperty.DecideEndpointWasHit),
2504
- $feature_flag_request_id: details.requestId,
2150
+ ...maybeAdd('$feature_flag_request_id', details.requestId),
2505
2151
  });
2506
2152
  }
2507
2153
  // If we have flags we either return the value (true or string) or false
@@ -2575,7 +2221,7 @@ class PostHogCore extends PostHogCoreStateless {
2575
2221
  .catch((e) => {
2576
2222
  cb?.(e, undefined);
2577
2223
  if (!cb) {
2578
- this.logMsgIfDebug(() => console.log('[PostHog] Error reloading feature flags', e));
2224
+ this.logMsgIfDebug(() => console.log('PostHog Debug', 'Error reloading feature flags', e));
2579
2225
  }
2580
2226
  });
2581
2227
  }