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