swetrix 4.2.0 → 4.4.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.
@@ -90,13 +90,60 @@ const getPath = (options) => {
90
90
  return result;
91
91
  };
92
92
 
93
+ const SESSION_REPLAY_PRIVACY_VALUES = ['total', 'normal', 'none'];
93
94
  const defaultActions = {
94
95
  stop() { },
95
96
  };
97
+ const defaultSessionReplayActions = {
98
+ async stop() { },
99
+ async flush() { },
100
+ };
96
101
  const DEFAULT_API_HOST = 'https://api.swetrix.com/log';
97
102
  const DEFAULT_API_BASE = 'https://api.swetrix.com';
103
+ const DEFAULT_RRWEB_FILE = 'replaylibrary.min.js';
104
+ const DEFAULT_RRWEB_URL = `https://cdn.jsdelivr.net/npm/swetrix@latest/dist/${DEFAULT_RRWEB_FILE}`;
105
+ const DEFAULT_SESSION_REPLAY_FLUSH_INTERVAL = 5000;
106
+ const DEFAULT_SESSION_REPLAY_MAX_EVENTS = 100;
107
+ const DEFAULT_SESSION_REPLAY_MAX_CHUNK_BYTES = 512 * 1024;
108
+ const DEFAULT_SESSION_REPLAY_MAX_EVENT_BYTES = 5 * 1024 * 1024;
109
+ const DEFAULT_SESSION_REPLAY_MAX_DURATION_MS = 30 * 60 * 1000;
110
+ const DEFAULT_SESSION_REPLAY_PRIVACY = 'total';
111
+ const DEFAULT_SESSION_REPLAY_SAMPLING = {
112
+ mousemove: 50,
113
+ scroll: 150,
114
+ input: 'last',
115
+ };
116
+ const DEFAULT_SESSION_REPLAY_SLIM_DOM_OPTIONS = {
117
+ script: true,
118
+ comment: true,
119
+ headFavicon: true,
120
+ headWhitespace: true,
121
+ headMetaDescKeywords: true,
122
+ headMetaSocial: true,
123
+ headMetaRobots: true,
124
+ headMetaHttpEquiv: true,
125
+ headMetaAuthorship: true,
126
+ headMetaVerification: true,
127
+ };
128
+ const SESSION_REPLAY_ACTIVITY_EVENTS = [
129
+ 'click',
130
+ 'keydown',
131
+ 'mousedown',
132
+ 'mousemove',
133
+ 'scroll',
134
+ 'touchstart',
135
+ ];
98
136
  // Default cache duration: 5 minutes
99
137
  const DEFAULT_CACHE_DURATION = 5 * 60 * 1000;
138
+ const getStringByteLength = (value) => {
139
+ if (typeof TextEncoder !== 'undefined') {
140
+ return new TextEncoder().encode(value).length;
141
+ }
142
+ if (typeof Blob !== 'undefined') {
143
+ return new Blob([value]).size;
144
+ }
145
+ return value.length;
146
+ };
100
147
  class Lib {
101
148
  constructor(projectID, options) {
102
149
  this.projectID = projectID;
@@ -108,9 +155,15 @@ class Lib {
108
155
  this.activePage = null;
109
156
  this.errorListenerExists = false;
110
157
  this.cachedData = null;
158
+ this.rrwebLoader = null;
159
+ this.sessionReplayActions = null;
160
+ this.sessionReplayInitPromise = null;
111
161
  this.trackPathChange = this.trackPathChange.bind(this);
112
162
  this.heartbeat = this.heartbeat.bind(this);
113
163
  this.captureError = this.captureError.bind(this);
164
+ if (this.getSessionReplayPreloadOption()) {
165
+ void this.preloadSessionReplay().catch(() => undefined);
166
+ }
114
167
  }
115
168
  captureError(event) {
116
169
  var _a, _b, _c, _d;
@@ -258,10 +311,10 @@ class Lib {
258
311
  };
259
312
  }
260
313
  /**
261
- * Fetches all feature flags and experiments for the project.
262
- * Results are cached for 5 minutes by default.
314
+ * Fetches all feature flags for the project.
315
+ * Results are cached for 5 minutes by default and share a cache with experiments.
263
316
  *
264
- * @param options - Options for evaluating feature flags.
317
+ * @param options - Options for evaluating feature flags (`profileId` only).
265
318
  * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
266
319
  * @returns A promise that resolves to a record of flag keys to boolean values.
267
320
  */
@@ -328,8 +381,8 @@ class Lib {
328
381
  * Gets the value of a single feature flag.
329
382
  *
330
383
  * @param key - The feature flag key.
331
- * @param options - Options for evaluating the feature flag.
332
- * @param defaultValue - Default value to return if the flag is not found. Defaults to false.
384
+ * @param options - Options for evaluating the feature flag (`profileId` only).
385
+ * @param defaultValue - Optional default value to return if the flag is not found. Defaults to false.
333
386
  * @returns A promise that resolves to the boolean value of the flag.
334
387
  */
335
388
  async getFeatureFlag(key, options, defaultValue = false) {
@@ -344,16 +397,16 @@ class Lib {
344
397
  this.cachedData = null;
345
398
  }
346
399
  /**
347
- * Fetches all A/B test experiments for the project.
400
+ * Fetches variant assignments for running A/B test experiments returned by feature flag evaluation.
348
401
  * Results are cached for 5 minutes by default (shared cache with feature flags).
349
402
  *
350
- * @param options - Options for evaluating experiments.
403
+ * @param options - Options for evaluating experiments (`profileId` only).
351
404
  * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
352
405
  * @returns A promise that resolves to a record of experiment IDs to variant keys.
353
406
  *
354
407
  * @example
355
408
  * ```typescript
356
- * const experiments = await getExperiments()
409
+ * const experiments = await getExperiments({ profileId: 'user-123' })
357
410
  * // experiments = { 'exp-123': 'variant-a', 'exp-456': 'control' }
358
411
  * ```
359
412
  */
@@ -384,13 +437,16 @@ class Lib {
384
437
  * Gets the variant key for a specific A/B test experiment.
385
438
  *
386
439
  * @param experimentId - The experiment ID.
387
- * @param options - Options for evaluating the experiment.
388
- * @param defaultVariant - Default variant key to return if the experiment is not found. Defaults to null.
440
+ * @param options - Options for evaluating the experiment (`profileId` only).
441
+ * @param defaultVariant - Optional default variant key to return if the experiment is not found. Defaults to null.
389
442
  * @returns A promise that resolves to the variant key assigned to this user, or defaultVariant if not found.
390
443
  *
391
444
  * @example
392
445
  * ```typescript
393
- * const variant = await getExperiment('checkout-redesign')
446
+ * const variant = await getExperiment('checkout-redesign', { profileId: 'user-123' })
447
+ *
448
+ * // Optional fallback variant:
449
+ * const variantWithFallback = await getExperiment('checkout-redesign', undefined, 'control')
394
450
  *
395
451
  * if (variant === 'new-checkout') {
396
452
  * // Show new checkout flow
@@ -506,6 +562,196 @@ class Lib {
506
562
  return null;
507
563
  }
508
564
  }
565
+ async startSessionReplay(options = {}) {
566
+ if (this.sessionReplayActions) {
567
+ return this.sessionReplayActions;
568
+ }
569
+ if (this.sessionReplayInitPromise) {
570
+ return this.sessionReplayInitPromise;
571
+ }
572
+ const initPromise = this.initialiseSessionReplay(options);
573
+ this.sessionReplayInitPromise = initPromise;
574
+ try {
575
+ return await initPromise;
576
+ }
577
+ finally {
578
+ if (this.sessionReplayInitPromise === initPromise) {
579
+ this.sessionReplayInitPromise = null;
580
+ }
581
+ }
582
+ }
583
+ async initialiseSessionReplay(options) {
584
+ var _a;
585
+ if (this.sessionReplayActions) {
586
+ return this.sessionReplayActions;
587
+ }
588
+ if (!this.canTrack()) {
589
+ return defaultSessionReplayActions;
590
+ }
591
+ const maxDurationMs = typeof options.maxDurationMs === 'number' && options.maxDurationMs > 0
592
+ ? options.maxDurationMs
593
+ : DEFAULT_SESSION_REPLAY_MAX_DURATION_MS;
594
+ if (!this.shouldSampleSessionReplay(options.sampleRate)) {
595
+ return defaultSessionReplayActions;
596
+ }
597
+ try {
598
+ await this.preloadSessionReplay();
599
+ }
600
+ catch (_b) {
601
+ return defaultSessionReplayActions;
602
+ }
603
+ const rrweb = window.rrweb;
604
+ if (!(rrweb === null || rrweb === void 0 ? void 0 : rrweb.record)) {
605
+ return defaultSessionReplayActions;
606
+ }
607
+ const privacy = this.getSessionReplayPrivacy(options.privacy);
608
+ const proposedReplayId = this.createReplayId();
609
+ const started = await this.sendSessionReplayStart(proposedReplayId, privacy);
610
+ if (!started) {
611
+ return defaultSessionReplayActions;
612
+ }
613
+ const replayId = started.replayId;
614
+ const flushIntervalMs = typeof options.flushIntervalMs === 'number' && options.flushIntervalMs > 0
615
+ ? options.flushIntervalMs
616
+ : DEFAULT_SESSION_REPLAY_FLUSH_INTERVAL;
617
+ const maxEventsPerChunk = typeof options.maxEventsPerChunk === 'number' &&
618
+ options.maxEventsPerChunk > 0
619
+ ? Math.floor(options.maxEventsPerChunk)
620
+ : DEFAULT_SESSION_REPLAY_MAX_EVENTS;
621
+ const maxBytesPerChunkCandidate = typeof options.maxBytesPerChunk === 'number'
622
+ ? Math.floor(options.maxBytesPerChunk)
623
+ : Number.NaN;
624
+ const maxBytesPerChunk = maxBytesPerChunkCandidate >= 1
625
+ ? maxBytesPerChunkCandidate
626
+ : DEFAULT_SESSION_REPLAY_MAX_CHUNK_BYTES;
627
+ const maxBytesPerEventCandidate = typeof options.maxBytesPerEvent === 'number'
628
+ ? Math.floor(options.maxBytesPerEvent)
629
+ : Number.NaN;
630
+ const maxBytesPerEvent = maxBytesPerEventCandidate >= 1
631
+ ? maxBytesPerEventCandidate
632
+ : DEFAULT_SESSION_REPLAY_MAX_EVENT_BYTES;
633
+ const idleTimeoutMs = typeof options.idleTimeoutMs === 'number' && options.idleTimeoutMs > 0
634
+ ? options.idleTimeoutMs
635
+ : null;
636
+ let chunkIndex = started.nextChunkIndex;
637
+ let stopped = false;
638
+ let events = [];
639
+ let eventsByteLength = 0;
640
+ let flushing = Promise.resolve();
641
+ let maxDurationTimer;
642
+ let idleTimer;
643
+ const flush = async (useBeacon = false) => {
644
+ if (!events.length)
645
+ return;
646
+ const chunk = events;
647
+ events = [];
648
+ eventsByteLength = 0;
649
+ const currentChunkIndex = chunkIndex++;
650
+ flushing = flushing
651
+ .catch(() => undefined)
652
+ .then(() => this.sendSessionReplayChunk(replayId, privacy, currentChunkIndex, chunk, useBeacon));
653
+ await flushing;
654
+ };
655
+ const userEmit = (_a = options.rrweb) === null || _a === void 0 ? void 0 : _a.emit;
656
+ const recordOptions = this.getSessionReplayRecordOptions(privacy, options.rrweb, (event) => {
657
+ try {
658
+ userEmit === null || userEmit === void 0 ? void 0 : userEmit(event);
659
+ }
660
+ catch (_a) { }
661
+ let eventByteLength = 0;
662
+ try {
663
+ eventByteLength = getStringByteLength(JSON.stringify(event));
664
+ }
665
+ catch (_b) {
666
+ return;
667
+ }
668
+ if (eventByteLength > maxBytesPerEvent) {
669
+ return;
670
+ }
671
+ if (events.length &&
672
+ eventsByteLength + eventByteLength > maxBytesPerChunk) {
673
+ void flush();
674
+ }
675
+ events.push(event);
676
+ eventsByteLength += eventByteLength;
677
+ if (events.length >= maxEventsPerChunk ||
678
+ eventsByteLength >= maxBytesPerChunk) {
679
+ void flush();
680
+ }
681
+ }, Boolean(options.recordIframes), options.maskAllText);
682
+ const stopRecording = rrweb.record(recordOptions);
683
+ const timer = setInterval(() => void flush(), flushIntervalMs);
684
+ const flushOnPageExit = () => void flush(true);
685
+ const flushOnHidden = () => {
686
+ if (document.visibilityState === 'hidden') {
687
+ void flush(true);
688
+ }
689
+ };
690
+ const clearIdleTimer = () => {
691
+ if (idleTimer) {
692
+ clearTimeout(idleTimer);
693
+ idleTimer = undefined;
694
+ }
695
+ };
696
+ const stopSessionReplay = async () => {
697
+ if (stopped)
698
+ return;
699
+ stopped = true;
700
+ clearInterval(timer);
701
+ if (maxDurationTimer) {
702
+ clearTimeout(maxDurationTimer);
703
+ }
704
+ clearIdleTimer();
705
+ window.removeEventListener('pagehide', flushOnPageExit);
706
+ document.removeEventListener('visibilitychange', flushOnHidden);
707
+ SESSION_REPLAY_ACTIVITY_EVENTS.forEach((eventName) => {
708
+ window.removeEventListener(eventName, resetIdleTimer);
709
+ });
710
+ stopRecording === null || stopRecording === void 0 ? void 0 : stopRecording();
711
+ await flush();
712
+ this.sessionReplayActions = null;
713
+ this.sessionReplayInitPromise = null;
714
+ };
715
+ const resetIdleTimer = () => {
716
+ if (!idleTimeoutMs || stopped)
717
+ return;
718
+ clearIdleTimer();
719
+ idleTimer = setTimeout(() => void stopSessionReplay(), idleTimeoutMs);
720
+ };
721
+ window.addEventListener('pagehide', flushOnPageExit);
722
+ document.addEventListener('visibilitychange', flushOnHidden);
723
+ maxDurationTimer = setTimeout(() => void stopSessionReplay(), maxDurationMs);
724
+ if (idleTimeoutMs) {
725
+ SESSION_REPLAY_ACTIVITY_EVENTS.forEach((eventName) => {
726
+ window.addEventListener(eventName, resetIdleTimer, { passive: true });
727
+ });
728
+ resetIdleTimer();
729
+ }
730
+ this.sessionReplayActions = {
731
+ stop: stopSessionReplay,
732
+ flush: async () => {
733
+ await flush();
734
+ },
735
+ };
736
+ return this.sessionReplayActions;
737
+ }
738
+ shouldSampleSessionReplay(sampleRate) {
739
+ if (typeof sampleRate !== 'number') {
740
+ return true;
741
+ }
742
+ if (sampleRate <= 0) {
743
+ return false;
744
+ }
745
+ if (sampleRate >= 1) {
746
+ return true;
747
+ }
748
+ return Math.random() < sampleRate;
749
+ }
750
+ getSessionReplayPrivacy(privacy) {
751
+ return SESSION_REPLAY_PRIVACY_VALUES.includes(privacy)
752
+ ? privacy
753
+ : DEFAULT_SESSION_REPLAY_PRIVACY;
754
+ }
509
755
  /**
510
756
  * Gets the API base URL (without /log suffix).
511
757
  */
@@ -595,6 +841,289 @@ class Lib {
595
841
  }
596
842
  return true;
597
843
  }
844
+ getSessionReplayUrl() {
845
+ const replayOption = this.getSessionReplayPreloadOption();
846
+ if (replayOption &&
847
+ typeof replayOption === 'object' &&
848
+ replayOption.rrwebUrl) {
849
+ return replayOption.rrwebUrl;
850
+ }
851
+ return this.getDefaultSessionReplayUrl();
852
+ }
853
+ getSessionReplayPreloadOption() {
854
+ var _a;
855
+ return (_a = this.options) === null || _a === void 0 ? void 0 : _a.preloadSessionReplay;
856
+ }
857
+ getDefaultSessionReplayUrl() {
858
+ if (!isInBrowser()) {
859
+ return DEFAULT_RRWEB_URL;
860
+ }
861
+ const trackerScript = this.getTrackerScript();
862
+ if (trackerScript === null || trackerScript === void 0 ? void 0 : trackerScript.src) {
863
+ const { hostname, pathname } = new URL(trackerScript.src);
864
+ if (hostname === 'swetrix.org' &&
865
+ /^\/swetrix(\.min)?\.js$/i.test(pathname)) {
866
+ return DEFAULT_RRWEB_URL;
867
+ }
868
+ return new URL(DEFAULT_RRWEB_FILE, trackerScript.src).toString();
869
+ }
870
+ return DEFAULT_RRWEB_URL;
871
+ }
872
+ getTrackerScript() {
873
+ const trackerScript = Array.from(document.scripts).find((script) => {
874
+ if (!script.src) {
875
+ return false;
876
+ }
877
+ try {
878
+ const { pathname } = new URL(script.src);
879
+ return /(^|\/)swetrix(\.min)?\.js$/i.test(pathname);
880
+ }
881
+ catch (_a) {
882
+ return false;
883
+ }
884
+ });
885
+ return trackerScript;
886
+ }
887
+ preloadSessionReplay() {
888
+ var _a;
889
+ if (!isInBrowser()) {
890
+ return Promise.resolve();
891
+ }
892
+ if ((_a = window.rrweb) === null || _a === void 0 ? void 0 : _a.record) {
893
+ return Promise.resolve();
894
+ }
895
+ if (this.rrwebLoader) {
896
+ return this.rrwebLoader;
897
+ }
898
+ if (window.__SWETRIX_RRWEB_LOADING__) {
899
+ this.rrwebLoader = window.__SWETRIX_RRWEB_LOADING__;
900
+ void this.rrwebLoader.catch(() => {
901
+ if (window.__SWETRIX_RRWEB_LOADING__ === this.rrwebLoader) {
902
+ delete window.__SWETRIX_RRWEB_LOADING__;
903
+ }
904
+ this.rrwebLoader = null;
905
+ });
906
+ return this.rrwebLoader;
907
+ }
908
+ this.rrwebLoader = this.loadSessionReplayRecorder();
909
+ window.__SWETRIX_RRWEB_LOADING__ = this.rrwebLoader;
910
+ const loader = this.rrwebLoader;
911
+ void loader.catch(() => {
912
+ if (window.__SWETRIX_RRWEB_LOADING__ === loader) {
913
+ delete window.__SWETRIX_RRWEB_LOADING__;
914
+ }
915
+ if (this.rrwebLoader === loader) {
916
+ this.rrwebLoader = null;
917
+ }
918
+ });
919
+ return this.rrwebLoader;
920
+ }
921
+ async loadSessionReplayRecorder() {
922
+ const replayOption = this.getSessionReplayPreloadOption();
923
+ const hasCustomReplayUrl = replayOption && typeof replayOption === 'object' && replayOption.rrwebUrl;
924
+ if (hasCustomReplayUrl || this.getTrackerScript()) {
925
+ await this.loadSessionReplayScript(this.getSessionReplayUrl());
926
+ return;
927
+ }
928
+ if (await this.loadSessionReplayPackage()) {
929
+ return;
930
+ }
931
+ await this.loadSessionReplayScript(this.getDefaultSessionReplayUrl());
932
+ }
933
+ async loadSessionReplayPackage() {
934
+ try {
935
+ const rrwebModule = (await import('rrweb'));
936
+ const rrweb = rrwebModule.record ? rrwebModule : rrwebModule.default;
937
+ if (!(rrweb === null || rrweb === void 0 ? void 0 : rrweb.record)) {
938
+ return false;
939
+ }
940
+ window.rrweb = rrweb;
941
+ return true;
942
+ }
943
+ catch (_a) {
944
+ return false;
945
+ }
946
+ }
947
+ loadSessionReplayScript(url) {
948
+ return new Promise((resolve, reject) => {
949
+ const script = document.createElement('script');
950
+ script.async = true;
951
+ script.src = url;
952
+ script.crossOrigin = 'anonymous';
953
+ script.onload = () => resolve();
954
+ script.onerror = () => reject(new Error('Failed to load rrweb'));
955
+ document.head.appendChild(script);
956
+ });
957
+ }
958
+ getSessionReplayRecordOptions(privacy, userOptions, emit, recordIframes, maskAllText) {
959
+ const hasUserSampling = userOptions &&
960
+ Object.prototype.hasOwnProperty.call(userOptions, 'sampling');
961
+ const hasUserSlimDOMOptions = userOptions &&
962
+ Object.prototype.hasOwnProperty.call(userOptions, 'slimDOMOptions');
963
+ const sampling = typeof (userOptions === null || userOptions === void 0 ? void 0 : userOptions.sampling) === 'object' && userOptions.sampling !== null
964
+ ? {
965
+ ...DEFAULT_SESSION_REPLAY_SAMPLING,
966
+ ...userOptions.sampling,
967
+ }
968
+ : hasUserSampling
969
+ ? userOptions === null || userOptions === void 0 ? void 0 : userOptions.sampling
970
+ : DEFAULT_SESSION_REPLAY_SAMPLING;
971
+ const slimDOMOptions = typeof (userOptions === null || userOptions === void 0 ? void 0 : userOptions.slimDOMOptions) === 'object' &&
972
+ userOptions.slimDOMOptions !== null
973
+ ? {
974
+ ...DEFAULT_SESSION_REPLAY_SLIM_DOM_OPTIONS,
975
+ ...userOptions.slimDOMOptions,
976
+ }
977
+ : hasUserSlimDOMOptions
978
+ ? userOptions === null || userOptions === void 0 ? void 0 : userOptions.slimDOMOptions
979
+ : DEFAULT_SESSION_REPLAY_SLIM_DOM_OPTIONS;
980
+ const options = {
981
+ recordCanvas: false,
982
+ recordCrossOriginIframes: false,
983
+ collectFonts: false,
984
+ inlineImages: false,
985
+ ...userOptions,
986
+ sampling,
987
+ slimDOMOptions,
988
+ emit,
989
+ };
990
+ const maskInputOptions = typeof options.maskInputOptions === 'object' &&
991
+ options.maskInputOptions !== null
992
+ ? options.maskInputOptions
993
+ : {};
994
+ const resolvedPrivacy = this.getSessionReplayPrivacy(privacy);
995
+ const defaultBlockSelector = recordIframes ? undefined : 'iframe';
996
+ const resolvedMaskAllText = typeof maskAllText === 'boolean'
997
+ ? maskAllText
998
+ : resolvedPrivacy === 'total';
999
+ const textMaskingOptions = resolvedMaskAllText
1000
+ ? { maskTextSelector: '*' }
1001
+ : {};
1002
+ if (resolvedPrivacy === 'total') {
1003
+ return {
1004
+ ...options,
1005
+ ...textMaskingOptions,
1006
+ maskAllInputs: true,
1007
+ blockSelector: this.mergeSelectors(this.mergeSelectors(options.blockSelector, defaultBlockSelector), 'img, picture, video, audio, canvas, svg'),
1008
+ recordCanvas: false,
1009
+ inlineImages: false,
1010
+ emit,
1011
+ };
1012
+ }
1013
+ if (resolvedPrivacy === 'normal') {
1014
+ return {
1015
+ ...options,
1016
+ ...textMaskingOptions,
1017
+ maskAllInputs: true,
1018
+ blockSelector: this.mergeSelectors(options.blockSelector, defaultBlockSelector),
1019
+ emit,
1020
+ };
1021
+ }
1022
+ return {
1023
+ ...options,
1024
+ ...textMaskingOptions,
1025
+ blockSelector: this.mergeSelectors(options.blockSelector, defaultBlockSelector),
1026
+ maskInputOptions: {
1027
+ ...maskInputOptions,
1028
+ password: true,
1029
+ },
1030
+ emit,
1031
+ };
1032
+ }
1033
+ mergeSelectors(existing, required) {
1034
+ if (!required) {
1035
+ return typeof existing === 'string' ? existing : undefined;
1036
+ }
1037
+ if (typeof existing === 'string' && existing.trim()) {
1038
+ return `${existing}, ${required}`;
1039
+ }
1040
+ return required;
1041
+ }
1042
+ createReplayId() {
1043
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
1044
+ return crypto.randomUUID();
1045
+ }
1046
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
1047
+ }
1048
+ async sendSessionReplayStart(replayId, privacy) {
1049
+ var _a, _b, _c;
1050
+ try {
1051
+ const apiBase = this.getApiBase();
1052
+ const response = await fetch(`${apiBase}/log/session-replay/start`, {
1053
+ method: 'POST',
1054
+ headers: {
1055
+ 'Content-Type': 'application/json',
1056
+ },
1057
+ body: JSON.stringify({
1058
+ pid: this.projectID,
1059
+ replayId,
1060
+ privacy,
1061
+ pg: this.activePage ||
1062
+ getPath({
1063
+ hash: (_a = this.pageViewsOptions) === null || _a === void 0 ? void 0 : _a.hash,
1064
+ search: (_b = this.pageViewsOptions) === null || _b === void 0 ? void 0 : _b.search,
1065
+ }),
1066
+ lc: getLocale(),
1067
+ tz: getTimezone(),
1068
+ profileId: (_c = this.options) === null || _c === void 0 ? void 0 : _c.profileId,
1069
+ }),
1070
+ });
1071
+ if (!response.ok) {
1072
+ return null;
1073
+ }
1074
+ try {
1075
+ const result = (await response.json());
1076
+ const resolvedReplayId = typeof result.replayId === 'string' && result.replayId
1077
+ ? result.replayId
1078
+ : replayId;
1079
+ const resolvedChunkIndex = typeof result.nextChunkIndex === 'number' &&
1080
+ Number.isFinite(result.nextChunkIndex) &&
1081
+ result.nextChunkIndex >= 0
1082
+ ? Math.floor(result.nextChunkIndex)
1083
+ : 0;
1084
+ return {
1085
+ replayId: resolvedReplayId,
1086
+ nextChunkIndex: resolvedChunkIndex,
1087
+ };
1088
+ }
1089
+ catch (_d) {
1090
+ return {
1091
+ replayId,
1092
+ nextChunkIndex: 0,
1093
+ };
1094
+ }
1095
+ }
1096
+ catch (_e) {
1097
+ return null;
1098
+ }
1099
+ }
1100
+ async sendSessionReplayChunk(replayId, privacy, chunkIndex, events, useBeacon) {
1101
+ const apiBase = this.getApiBase();
1102
+ const url = `${apiBase}/log/session-replay/chunk`;
1103
+ const payload = JSON.stringify({
1104
+ pid: this.projectID,
1105
+ replayId,
1106
+ privacy,
1107
+ chunkIndex,
1108
+ events,
1109
+ });
1110
+ if (useBeacon && typeof navigator.sendBeacon === 'function') {
1111
+ const sent = navigator.sendBeacon(url, new Blob([payload], { type: 'application/json' }));
1112
+ if (sent)
1113
+ return;
1114
+ }
1115
+ try {
1116
+ await fetch(url, {
1117
+ method: 'POST',
1118
+ headers: {
1119
+ 'Content-Type': 'application/json',
1120
+ },
1121
+ keepalive: useBeacon,
1122
+ body: payload,
1123
+ });
1124
+ }
1125
+ catch (_a) { }
1126
+ }
598
1127
  async sendRequest(path, body) {
599
1128
  var _a;
600
1129
  const host = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.apiURL) || DEFAULT_API_HOST;
@@ -670,6 +1199,12 @@ function trackErrors(options) {
670
1199
  }
671
1200
  return exports.LIB_INSTANCE.trackErrors(options);
672
1201
  }
1202
+ function startSessionReplay(options) {
1203
+ if (!exports.LIB_INSTANCE) {
1204
+ return Promise.resolve(defaultSessionReplayActions);
1205
+ }
1206
+ return exports.LIB_INSTANCE.startSessionReplay(options);
1207
+ }
673
1208
  /**
674
1209
  * This function is used to manually track an error event.
675
1210
  * It's useful if you want to track specific errors in your application.
@@ -704,17 +1239,16 @@ function pageview(options) {
704
1239
  }
705
1240
  /**
706
1241
  * Fetches all feature flags for the project.
707
- * Results are cached for 5 minutes by default.
1242
+ * Results are cached for 5 minutes by default and share a cache with experiments.
708
1243
  *
709
- * @param options - Options for evaluating feature flags (visitorId, attributes).
1244
+ * @param options - Options for evaluating feature flags (`profileId` only).
710
1245
  * @param forceRefresh - If true, bypasses the cache and fetches fresh flags.
711
1246
  * @returns A promise that resolves to a record of flag keys to boolean values.
712
1247
  *
713
1248
  * @example
714
1249
  * ```typescript
715
1250
  * const flags = await getFeatureFlags({
716
- * visitorId: 'user-123',
717
- * attributes: { cc: 'US', dv: 'desktop' }
1251
+ * profileId: 'user-123'
718
1252
  * })
719
1253
  *
720
1254
  * if (flags['new-checkout']) {
@@ -731,13 +1265,13 @@ async function getFeatureFlags(options, forceRefresh) {
731
1265
  * Gets the value of a single feature flag.
732
1266
  *
733
1267
  * @param key - The feature flag key.
734
- * @param options - Options for evaluating the feature flag (visitorId, attributes).
735
- * @param defaultValue - Default value to return if the flag is not found. Defaults to false.
1268
+ * @param options - Options for evaluating the feature flag (`profileId` only).
1269
+ * @param defaultValue - Optional default value to return if the flag is not found. Defaults to false.
736
1270
  * @returns A promise that resolves to the boolean value of the flag.
737
1271
  *
738
1272
  * @example
739
1273
  * ```typescript
740
- * const isEnabled = await getFeatureFlag('dark-mode', { visitorId: 'user-123' })
1274
+ * const isEnabled = await getFeatureFlag('dark-mode', { profileId: 'user-123' })
741
1275
  *
742
1276
  * if (isEnabled) {
743
1277
  * // Enable dark mode
@@ -750,8 +1284,8 @@ async function getFeatureFlag(key, options, defaultValue = false) {
750
1284
  return exports.LIB_INSTANCE.getFeatureFlag(key, options, defaultValue);
751
1285
  }
752
1286
  /**
753
- * Clears the cached feature flags, forcing a fresh fetch on the next call.
754
- * Useful when you know the user's context has changed significantly.
1287
+ * Clears the cached feature flags and experiments, forcing a fresh fetch on the next call.
1288
+ * Useful when you know the user's profile has changed.
755
1289
  */
756
1290
  function clearFeatureFlagsCache() {
757
1291
  if (!exports.LIB_INSTANCE)
@@ -759,10 +1293,10 @@ function clearFeatureFlagsCache() {
759
1293
  exports.LIB_INSTANCE.clearFeatureFlagsCache();
760
1294
  }
761
1295
  /**
762
- * Fetches all A/B test experiments for the project.
1296
+ * Fetches variant assignments for running A/B test experiments returned by feature flag evaluation.
763
1297
  * Results are cached for 5 minutes by default (shared cache with feature flags).
764
1298
  *
765
- * @param options - Options for evaluating experiments.
1299
+ * @param options - Options for evaluating experiments (`profileId` only).
766
1300
  * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
767
1301
  * @returns A promise that resolves to a record of experiment IDs to variant keys.
768
1302
  *
@@ -789,13 +1323,16 @@ async function getExperiments(options, forceRefresh) {
789
1323
  * Gets the variant key for a specific A/B test experiment.
790
1324
  *
791
1325
  * @param experimentId - The experiment ID.
792
- * @param options - Options for evaluating the experiment.
793
- * @param defaultVariant - Default variant key to return if the experiment is not found. Defaults to null.
1326
+ * @param options - Options for evaluating the experiment (`profileId` only).
1327
+ * @param defaultVariant - Optional default variant key to return if the experiment is not found. Defaults to null.
794
1328
  * @returns A promise that resolves to the variant key assigned to this user, or defaultVariant if not found.
795
1329
  *
796
1330
  * @example
797
1331
  * ```typescript
798
- * const variant = await getExperiment('checkout-redesign-experiment-id')
1332
+ * const variant = await getExperiment('checkout-redesign-experiment-id', { profileId: 'user-123' })
1333
+ *
1334
+ * // Optional fallback variant:
1335
+ * const variantWithFallback = await getExperiment('checkout-redesign-experiment-id', undefined, 'control')
799
1336
  *
800
1337
  * if (variant === 'new-checkout') {
801
1338
  * // Show new checkout flow
@@ -889,6 +1426,7 @@ exports.getProfileId = getProfileId;
889
1426
  exports.getSessionId = getSessionId;
890
1427
  exports.init = init;
891
1428
  exports.pageview = pageview;
1429
+ exports.startSessionReplay = startSessionReplay;
892
1430
  exports.track = track;
893
1431
  exports.trackError = trackError;
894
1432
  exports.trackErrors = trackErrors;