swetrix 4.1.0 → 4.3.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.
@@ -36,6 +36,26 @@ const getTimezone = () => {
36
36
  const getReferrer = () => {
37
37
  return document.referrer || undefined;
38
38
  };
39
+ /**
40
+ * Returns the URL query string (without the leading `?`) of the current
41
+ * page, or `undefined` if there is none.
42
+ *
43
+ * Falls back to a query string embedded in `location.hash` (e.g. when a
44
+ * hash router uses `/#/path?foo=bar`) so we still capture click IDs in
45
+ * SPA hash-routed setups.
46
+ */
47
+ const getQueryString = () => {
48
+ if (location.search && location.search.length > 1) {
49
+ return location.search.slice(1);
50
+ }
51
+ const hashIndex = location.hash.indexOf('?');
52
+ if (hashIndex > -1) {
53
+ const hashQuery = location.hash.slice(hashIndex + 1);
54
+ if (hashQuery)
55
+ return hashQuery;
56
+ }
57
+ return undefined;
58
+ };
39
59
  const getUTMSource = () => findInSearch(utmSourceRegex);
40
60
  const getUTMMedium = () => findInSearch(utmMediumRegex) || getGclid();
41
61
  const getUTMCampaign = () => findInSearch(utmCampaignRegex);
@@ -70,11 +90,29 @@ const getPath = (options) => {
70
90
  return result;
71
91
  };
72
92
 
93
+ const SESSION_REPLAY_PRIVACY_VALUES = ['total', 'normal', 'none'];
73
94
  const defaultActions = {
74
95
  stop() { },
75
96
  };
97
+ const defaultSessionReplayActions = {
98
+ async stop() { },
99
+ async flush() { },
100
+ };
76
101
  const DEFAULT_API_HOST = 'https://api.swetrix.com/log';
77
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_PRIVACY = 'total';
108
+ const SESSION_REPLAY_ACTIVITY_EVENTS = [
109
+ 'click',
110
+ 'keydown',
111
+ 'mousedown',
112
+ 'mousemove',
113
+ 'scroll',
114
+ 'touchstart',
115
+ ];
78
116
  // Default cache duration: 5 minutes
79
117
  const DEFAULT_CACHE_DURATION = 5 * 60 * 1000;
80
118
  class Lib {
@@ -88,9 +126,15 @@ class Lib {
88
126
  this.activePage = null;
89
127
  this.errorListenerExists = false;
90
128
  this.cachedData = null;
129
+ this.rrwebLoader = null;
130
+ this.sessionReplayActions = null;
131
+ this.sessionReplayInitPromise = null;
91
132
  this.trackPathChange = this.trackPathChange.bind(this);
92
133
  this.heartbeat = this.heartbeat.bind(this);
93
134
  this.captureError = this.captureError.bind(this);
135
+ if (this.getSessionReplayPreloadOption()) {
136
+ void this.preloadSessionReplay().catch(() => undefined);
137
+ }
94
138
  }
95
139
  captureError(event) {
96
140
  var _a, _b, _c, _d;
@@ -176,6 +220,7 @@ class Lib {
176
220
  ca: getUTMCampaign(),
177
221
  te: getUTMTerm(),
178
222
  co: getUTMContent(),
223
+ qs: getQueryString(),
179
224
  profileId: (_c = event.profileId) !== null && _c !== void 0 ? _c : (_d = this.options) === null || _d === void 0 ? void 0 : _d.profileId,
180
225
  };
181
226
  await this.sendRequest('custom', data);
@@ -237,10 +282,10 @@ class Lib {
237
282
  };
238
283
  }
239
284
  /**
240
- * Fetches all feature flags and experiments for the project.
241
- * Results are cached for 5 minutes by default.
285
+ * Fetches all feature flags for the project.
286
+ * Results are cached for 5 minutes by default and share a cache with experiments.
242
287
  *
243
- * @param options - Options for evaluating feature flags.
288
+ * @param options - Options for evaluating feature flags (`profileId` only).
244
289
  * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
245
290
  * @returns A promise that resolves to a record of flag keys to boolean values.
246
291
  */
@@ -307,8 +352,8 @@ class Lib {
307
352
  * Gets the value of a single feature flag.
308
353
  *
309
354
  * @param key - The feature flag key.
310
- * @param options - Options for evaluating the feature flag.
311
- * @param defaultValue - Default value to return if the flag is not found. Defaults to false.
355
+ * @param options - Options for evaluating the feature flag (`profileId` only).
356
+ * @param defaultValue - Optional default value to return if the flag is not found. Defaults to false.
312
357
  * @returns A promise that resolves to the boolean value of the flag.
313
358
  */
314
359
  async getFeatureFlag(key, options, defaultValue = false) {
@@ -323,16 +368,16 @@ class Lib {
323
368
  this.cachedData = null;
324
369
  }
325
370
  /**
326
- * Fetches all A/B test experiments for the project.
371
+ * Fetches variant assignments for running A/B test experiments returned by feature flag evaluation.
327
372
  * Results are cached for 5 minutes by default (shared cache with feature flags).
328
373
  *
329
- * @param options - Options for evaluating experiments.
374
+ * @param options - Options for evaluating experiments (`profileId` only).
330
375
  * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
331
376
  * @returns A promise that resolves to a record of experiment IDs to variant keys.
332
377
  *
333
378
  * @example
334
379
  * ```typescript
335
- * const experiments = await getExperiments()
380
+ * const experiments = await getExperiments({ profileId: 'user-123' })
336
381
  * // experiments = { 'exp-123': 'variant-a', 'exp-456': 'control' }
337
382
  * ```
338
383
  */
@@ -363,13 +408,16 @@ class Lib {
363
408
  * Gets the variant key for a specific A/B test experiment.
364
409
  *
365
410
  * @param experimentId - The experiment ID.
366
- * @param options - Options for evaluating the experiment.
367
- * @param defaultVariant - Default variant key to return if the experiment is not found. Defaults to null.
411
+ * @param options - Options for evaluating the experiment (`profileId` only).
412
+ * @param defaultVariant - Optional default variant key to return if the experiment is not found. Defaults to null.
368
413
  * @returns A promise that resolves to the variant key assigned to this user, or defaultVariant if not found.
369
414
  *
370
415
  * @example
371
416
  * ```typescript
372
- * const variant = await getExperiment('checkout-redesign')
417
+ * const variant = await getExperiment('checkout-redesign', { profileId: 'user-123' })
418
+ *
419
+ * // Optional fallback variant:
420
+ * const variantWithFallback = await getExperiment('checkout-redesign', undefined, 'control')
373
421
  *
374
422
  * if (variant === 'new-checkout') {
375
423
  * // Show new checkout flow
@@ -485,6 +533,167 @@ class Lib {
485
533
  return null;
486
534
  }
487
535
  }
536
+ async startSessionReplay(options = {}) {
537
+ if (this.sessionReplayActions) {
538
+ return this.sessionReplayActions;
539
+ }
540
+ if (this.sessionReplayInitPromise) {
541
+ return this.sessionReplayInitPromise;
542
+ }
543
+ const initPromise = this.initialiseSessionReplay(options);
544
+ this.sessionReplayInitPromise = initPromise;
545
+ try {
546
+ return await initPromise;
547
+ }
548
+ finally {
549
+ if (this.sessionReplayInitPromise === initPromise) {
550
+ this.sessionReplayInitPromise = null;
551
+ }
552
+ }
553
+ }
554
+ async initialiseSessionReplay(options) {
555
+ var _a;
556
+ if (this.sessionReplayActions) {
557
+ return this.sessionReplayActions;
558
+ }
559
+ if (!this.canTrack()) {
560
+ return defaultSessionReplayActions;
561
+ }
562
+ if (!this.shouldSampleSessionReplay(options.sampleRate)) {
563
+ return defaultSessionReplayActions;
564
+ }
565
+ try {
566
+ await this.preloadSessionReplay();
567
+ }
568
+ catch (_b) {
569
+ return defaultSessionReplayActions;
570
+ }
571
+ const rrweb = window.rrweb;
572
+ if (!(rrweb === null || rrweb === void 0 ? void 0 : rrweb.record)) {
573
+ return defaultSessionReplayActions;
574
+ }
575
+ const privacy = this.getSessionReplayPrivacy(options.privacy);
576
+ const replayId = this.createReplayId();
577
+ const started = await this.sendSessionReplayStart(replayId, privacy);
578
+ if (!started) {
579
+ return defaultSessionReplayActions;
580
+ }
581
+ const flushIntervalMs = typeof options.flushIntervalMs === 'number' && options.flushIntervalMs > 0
582
+ ? options.flushIntervalMs
583
+ : DEFAULT_SESSION_REPLAY_FLUSH_INTERVAL;
584
+ const maxEventsPerChunk = typeof options.maxEventsPerChunk === 'number' &&
585
+ options.maxEventsPerChunk > 0
586
+ ? Math.floor(options.maxEventsPerChunk)
587
+ : DEFAULT_SESSION_REPLAY_MAX_EVENTS;
588
+ const maxDurationMs = typeof options.maxDurationMs === 'number' && options.maxDurationMs > 0
589
+ ? options.maxDurationMs
590
+ : null;
591
+ const idleTimeoutMs = typeof options.idleTimeoutMs === 'number' && options.idleTimeoutMs > 0
592
+ ? options.idleTimeoutMs
593
+ : null;
594
+ let chunkIndex = 0;
595
+ let stopped = false;
596
+ let events = [];
597
+ let flushing = Promise.resolve();
598
+ let maxDurationTimer;
599
+ let idleTimer;
600
+ const flush = async (useBeacon = false) => {
601
+ if (!events.length)
602
+ return;
603
+ const chunk = events;
604
+ events = [];
605
+ const currentChunkIndex = chunkIndex++;
606
+ flushing = flushing
607
+ .catch(() => undefined)
608
+ .then(() => this.sendSessionReplayChunk(replayId, privacy, currentChunkIndex, chunk, useBeacon));
609
+ await flushing;
610
+ };
611
+ const userEmit = (_a = options.rrweb) === null || _a === void 0 ? void 0 : _a.emit;
612
+ const recordOptions = this.getSessionReplayRecordOptions(privacy, options.rrweb, (event) => {
613
+ try {
614
+ userEmit === null || userEmit === void 0 ? void 0 : userEmit(event);
615
+ }
616
+ catch (_a) { }
617
+ events.push(event);
618
+ if (events.length >= maxEventsPerChunk) {
619
+ void flush();
620
+ }
621
+ });
622
+ const stopRecording = rrweb.record(recordOptions);
623
+ const timer = setInterval(() => void flush(), flushIntervalMs);
624
+ const flushOnPageExit = () => void flush(true);
625
+ const flushOnHidden = () => {
626
+ if (document.visibilityState === 'hidden') {
627
+ void flush(true);
628
+ }
629
+ };
630
+ const clearIdleTimer = () => {
631
+ if (idleTimer) {
632
+ clearTimeout(idleTimer);
633
+ idleTimer = undefined;
634
+ }
635
+ };
636
+ const stopSessionReplay = async () => {
637
+ if (stopped)
638
+ return;
639
+ stopped = true;
640
+ clearInterval(timer);
641
+ if (maxDurationTimer) {
642
+ clearTimeout(maxDurationTimer);
643
+ }
644
+ clearIdleTimer();
645
+ window.removeEventListener('pagehide', flushOnPageExit);
646
+ document.removeEventListener('visibilitychange', flushOnHidden);
647
+ SESSION_REPLAY_ACTIVITY_EVENTS.forEach((eventName) => {
648
+ window.removeEventListener(eventName, resetIdleTimer);
649
+ });
650
+ stopRecording === null || stopRecording === void 0 ? void 0 : stopRecording();
651
+ await flush();
652
+ this.sessionReplayActions = null;
653
+ this.sessionReplayInitPromise = null;
654
+ };
655
+ const resetIdleTimer = () => {
656
+ if (!idleTimeoutMs || stopped)
657
+ return;
658
+ clearIdleTimer();
659
+ idleTimer = setTimeout(() => void stopSessionReplay(), idleTimeoutMs);
660
+ };
661
+ window.addEventListener('pagehide', flushOnPageExit);
662
+ document.addEventListener('visibilitychange', flushOnHidden);
663
+ if (maxDurationMs) {
664
+ maxDurationTimer = setTimeout(() => void stopSessionReplay(), maxDurationMs);
665
+ }
666
+ if (idleTimeoutMs) {
667
+ SESSION_REPLAY_ACTIVITY_EVENTS.forEach((eventName) => {
668
+ window.addEventListener(eventName, resetIdleTimer, { passive: true });
669
+ });
670
+ resetIdleTimer();
671
+ }
672
+ this.sessionReplayActions = {
673
+ stop: stopSessionReplay,
674
+ flush: async () => {
675
+ await flush();
676
+ },
677
+ };
678
+ return this.sessionReplayActions;
679
+ }
680
+ shouldSampleSessionReplay(sampleRate) {
681
+ if (typeof sampleRate !== 'number') {
682
+ return true;
683
+ }
684
+ if (sampleRate <= 0) {
685
+ return false;
686
+ }
687
+ if (sampleRate >= 1) {
688
+ return true;
689
+ }
690
+ return Math.random() < sampleRate;
691
+ }
692
+ getSessionReplayPrivacy(privacy) {
693
+ return SESSION_REPLAY_PRIVACY_VALUES.includes(privacy)
694
+ ? privacy
695
+ : DEFAULT_SESSION_REPLAY_PRIVACY;
696
+ }
488
697
  /**
489
698
  * Gets the API base URL (without /log suffix).
490
699
  */
@@ -547,6 +756,7 @@ class Lib {
547
756
  ca: getUTMCampaign(),
548
757
  te: getUTMTerm(),
549
758
  co: getUTMContent(),
759
+ qs: getQueryString(),
550
760
  profileId: (_a = this.options) === null || _a === void 0 ? void 0 : _a.profileId,
551
761
  ...payload,
552
762
  };
@@ -573,6 +783,225 @@ class Lib {
573
783
  }
574
784
  return true;
575
785
  }
786
+ getSessionReplayUrl() {
787
+ const replayOption = this.getSessionReplayPreloadOption();
788
+ if (replayOption &&
789
+ typeof replayOption === 'object' &&
790
+ replayOption.rrwebUrl) {
791
+ return replayOption.rrwebUrl;
792
+ }
793
+ return this.getDefaultSessionReplayUrl();
794
+ }
795
+ getSessionReplayPreloadOption() {
796
+ var _a;
797
+ return (_a = this.options) === null || _a === void 0 ? void 0 : _a.preloadSessionReplay;
798
+ }
799
+ getDefaultSessionReplayUrl() {
800
+ if (!isInBrowser()) {
801
+ return DEFAULT_RRWEB_URL;
802
+ }
803
+ const trackerScript = this.getTrackerScript();
804
+ if (trackerScript === null || trackerScript === void 0 ? void 0 : trackerScript.src) {
805
+ const { hostname, pathname } = new URL(trackerScript.src);
806
+ if (hostname === 'swetrix.org' &&
807
+ /^\/swetrix(\.min)?\.js$/i.test(pathname)) {
808
+ return DEFAULT_RRWEB_URL;
809
+ }
810
+ return new URL(DEFAULT_RRWEB_FILE, trackerScript.src).toString();
811
+ }
812
+ return DEFAULT_RRWEB_URL;
813
+ }
814
+ getTrackerScript() {
815
+ const trackerScript = Array.from(document.scripts).find((script) => {
816
+ if (!script.src) {
817
+ return false;
818
+ }
819
+ try {
820
+ const { pathname } = new URL(script.src);
821
+ return /(^|\/)swetrix(\.min)?\.js$/i.test(pathname);
822
+ }
823
+ catch (_a) {
824
+ return false;
825
+ }
826
+ });
827
+ return trackerScript;
828
+ }
829
+ preloadSessionReplay() {
830
+ var _a;
831
+ if (!isInBrowser()) {
832
+ return Promise.resolve();
833
+ }
834
+ if ((_a = window.rrweb) === null || _a === void 0 ? void 0 : _a.record) {
835
+ return Promise.resolve();
836
+ }
837
+ if (this.rrwebLoader) {
838
+ return this.rrwebLoader;
839
+ }
840
+ if (window.__SWETRIX_RRWEB_LOADING__) {
841
+ this.rrwebLoader = window.__SWETRIX_RRWEB_LOADING__;
842
+ void this.rrwebLoader.catch(() => {
843
+ if (window.__SWETRIX_RRWEB_LOADING__ === this.rrwebLoader) {
844
+ delete window.__SWETRIX_RRWEB_LOADING__;
845
+ }
846
+ this.rrwebLoader = null;
847
+ });
848
+ return this.rrwebLoader;
849
+ }
850
+ this.rrwebLoader = this.loadSessionReplayRecorder();
851
+ window.__SWETRIX_RRWEB_LOADING__ = this.rrwebLoader;
852
+ const loader = this.rrwebLoader;
853
+ void loader.catch(() => {
854
+ if (window.__SWETRIX_RRWEB_LOADING__ === loader) {
855
+ delete window.__SWETRIX_RRWEB_LOADING__;
856
+ }
857
+ if (this.rrwebLoader === loader) {
858
+ this.rrwebLoader = null;
859
+ }
860
+ });
861
+ return this.rrwebLoader;
862
+ }
863
+ async loadSessionReplayRecorder() {
864
+ const replayOption = this.getSessionReplayPreloadOption();
865
+ const hasCustomReplayUrl = replayOption && typeof replayOption === 'object' && replayOption.rrwebUrl;
866
+ if (hasCustomReplayUrl || this.getTrackerScript()) {
867
+ await this.loadSessionReplayScript(this.getSessionReplayUrl());
868
+ return;
869
+ }
870
+ if (await this.loadSessionReplayPackage()) {
871
+ return;
872
+ }
873
+ await this.loadSessionReplayScript(this.getDefaultSessionReplayUrl());
874
+ }
875
+ async loadSessionReplayPackage() {
876
+ try {
877
+ const rrwebModule = (await import('rrweb'));
878
+ const rrweb = rrwebModule.record ? rrwebModule : rrwebModule.default;
879
+ if (!(rrweb === null || rrweb === void 0 ? void 0 : rrweb.record)) {
880
+ return false;
881
+ }
882
+ window.rrweb = rrweb;
883
+ return true;
884
+ }
885
+ catch (_a) {
886
+ return false;
887
+ }
888
+ }
889
+ loadSessionReplayScript(url) {
890
+ return new Promise((resolve, reject) => {
891
+ const script = document.createElement('script');
892
+ script.async = true;
893
+ script.src = url;
894
+ script.crossOrigin = 'anonymous';
895
+ script.onload = () => resolve();
896
+ script.onerror = () => reject(new Error('Failed to load rrweb'));
897
+ document.head.appendChild(script);
898
+ });
899
+ }
900
+ getSessionReplayRecordOptions(privacy, userOptions, emit) {
901
+ const options = {
902
+ ...userOptions,
903
+ emit,
904
+ };
905
+ const maskInputOptions = typeof options.maskInputOptions === 'object' &&
906
+ options.maskInputOptions !== null
907
+ ? options.maskInputOptions
908
+ : {};
909
+ const resolvedPrivacy = this.getSessionReplayPrivacy(privacy);
910
+ if (resolvedPrivacy === 'total') {
911
+ return {
912
+ ...options,
913
+ maskAllInputs: true,
914
+ maskTextSelector: '*',
915
+ blockSelector: this.mergeSelectors(options.blockSelector, 'img, picture, video, audio, canvas, svg'),
916
+ recordCanvas: false,
917
+ inlineImages: false,
918
+ emit,
919
+ };
920
+ }
921
+ if (resolvedPrivacy === 'normal') {
922
+ return {
923
+ ...options,
924
+ maskAllInputs: true,
925
+ emit,
926
+ };
927
+ }
928
+ return {
929
+ ...options,
930
+ maskInputOptions: {
931
+ ...maskInputOptions,
932
+ password: true,
933
+ },
934
+ emit,
935
+ };
936
+ }
937
+ mergeSelectors(existing, required) {
938
+ if (typeof existing === 'string' && existing.trim()) {
939
+ return `${existing}, ${required}`;
940
+ }
941
+ return required;
942
+ }
943
+ createReplayId() {
944
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
945
+ return crypto.randomUUID();
946
+ }
947
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
948
+ }
949
+ async sendSessionReplayStart(replayId, privacy) {
950
+ var _a, _b, _c;
951
+ try {
952
+ const apiBase = this.getApiBase();
953
+ const response = await fetch(`${apiBase}/log/session-replay/start`, {
954
+ method: 'POST',
955
+ headers: {
956
+ 'Content-Type': 'application/json',
957
+ },
958
+ body: JSON.stringify({
959
+ pid: this.projectID,
960
+ replayId,
961
+ privacy,
962
+ pg: this.activePage ||
963
+ getPath({
964
+ hash: (_a = this.pageViewsOptions) === null || _a === void 0 ? void 0 : _a.hash,
965
+ search: (_b = this.pageViewsOptions) === null || _b === void 0 ? void 0 : _b.search,
966
+ }),
967
+ lc: getLocale(),
968
+ tz: getTimezone(),
969
+ profileId: (_c = this.options) === null || _c === void 0 ? void 0 : _c.profileId,
970
+ }),
971
+ });
972
+ return response.ok;
973
+ }
974
+ catch (_d) {
975
+ return false;
976
+ }
977
+ }
978
+ async sendSessionReplayChunk(replayId, privacy, chunkIndex, events, useBeacon) {
979
+ const apiBase = this.getApiBase();
980
+ const url = `${apiBase}/log/session-replay/chunk`;
981
+ const payload = JSON.stringify({
982
+ pid: this.projectID,
983
+ replayId,
984
+ privacy,
985
+ chunkIndex,
986
+ events,
987
+ });
988
+ if (useBeacon && typeof navigator.sendBeacon === 'function') {
989
+ const sent = navigator.sendBeacon(url, new Blob([payload], { type: 'application/json' }));
990
+ if (sent)
991
+ return;
992
+ }
993
+ try {
994
+ await fetch(url, {
995
+ method: 'POST',
996
+ headers: {
997
+ 'Content-Type': 'application/json',
998
+ },
999
+ keepalive: useBeacon,
1000
+ body: payload,
1001
+ });
1002
+ }
1003
+ catch (_a) { }
1004
+ }
576
1005
  async sendRequest(path, body) {
577
1006
  var _a;
578
1007
  const host = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.apiURL) || DEFAULT_API_HOST;
@@ -648,6 +1077,12 @@ function trackErrors(options) {
648
1077
  }
649
1078
  return exports.LIB_INSTANCE.trackErrors(options);
650
1079
  }
1080
+ function startSessionReplay(options) {
1081
+ if (!exports.LIB_INSTANCE) {
1082
+ return Promise.resolve(defaultSessionReplayActions);
1083
+ }
1084
+ return exports.LIB_INSTANCE.startSessionReplay(options);
1085
+ }
651
1086
  /**
652
1087
  * This function is used to manually track an error event.
653
1088
  * It's useful if you want to track specific errors in your application.
@@ -682,17 +1117,16 @@ function pageview(options) {
682
1117
  }
683
1118
  /**
684
1119
  * Fetches all feature flags for the project.
685
- * Results are cached for 5 minutes by default.
1120
+ * Results are cached for 5 minutes by default and share a cache with experiments.
686
1121
  *
687
- * @param options - Options for evaluating feature flags (visitorId, attributes).
1122
+ * @param options - Options for evaluating feature flags (`profileId` only).
688
1123
  * @param forceRefresh - If true, bypasses the cache and fetches fresh flags.
689
1124
  * @returns A promise that resolves to a record of flag keys to boolean values.
690
1125
  *
691
1126
  * @example
692
1127
  * ```typescript
693
1128
  * const flags = await getFeatureFlags({
694
- * visitorId: 'user-123',
695
- * attributes: { cc: 'US', dv: 'desktop' }
1129
+ * profileId: 'user-123'
696
1130
  * })
697
1131
  *
698
1132
  * if (flags['new-checkout']) {
@@ -709,13 +1143,13 @@ async function getFeatureFlags(options, forceRefresh) {
709
1143
  * Gets the value of a single feature flag.
710
1144
  *
711
1145
  * @param key - The feature flag key.
712
- * @param options - Options for evaluating the feature flag (visitorId, attributes).
713
- * @param defaultValue - Default value to return if the flag is not found. Defaults to false.
1146
+ * @param options - Options for evaluating the feature flag (`profileId` only).
1147
+ * @param defaultValue - Optional default value to return if the flag is not found. Defaults to false.
714
1148
  * @returns A promise that resolves to the boolean value of the flag.
715
1149
  *
716
1150
  * @example
717
1151
  * ```typescript
718
- * const isEnabled = await getFeatureFlag('dark-mode', { visitorId: 'user-123' })
1152
+ * const isEnabled = await getFeatureFlag('dark-mode', { profileId: 'user-123' })
719
1153
  *
720
1154
  * if (isEnabled) {
721
1155
  * // Enable dark mode
@@ -728,8 +1162,8 @@ async function getFeatureFlag(key, options, defaultValue = false) {
728
1162
  return exports.LIB_INSTANCE.getFeatureFlag(key, options, defaultValue);
729
1163
  }
730
1164
  /**
731
- * Clears the cached feature flags, forcing a fresh fetch on the next call.
732
- * Useful when you know the user's context has changed significantly.
1165
+ * Clears the cached feature flags and experiments, forcing a fresh fetch on the next call.
1166
+ * Useful when you know the user's profile has changed.
733
1167
  */
734
1168
  function clearFeatureFlagsCache() {
735
1169
  if (!exports.LIB_INSTANCE)
@@ -737,10 +1171,10 @@ function clearFeatureFlagsCache() {
737
1171
  exports.LIB_INSTANCE.clearFeatureFlagsCache();
738
1172
  }
739
1173
  /**
740
- * Fetches all A/B test experiments for the project.
1174
+ * Fetches variant assignments for running A/B test experiments returned by feature flag evaluation.
741
1175
  * Results are cached for 5 minutes by default (shared cache with feature flags).
742
1176
  *
743
- * @param options - Options for evaluating experiments.
1177
+ * @param options - Options for evaluating experiments (`profileId` only).
744
1178
  * @param forceRefresh - If true, bypasses the cache and fetches fresh data.
745
1179
  * @returns A promise that resolves to a record of experiment IDs to variant keys.
746
1180
  *
@@ -767,13 +1201,16 @@ async function getExperiments(options, forceRefresh) {
767
1201
  * Gets the variant key for a specific A/B test experiment.
768
1202
  *
769
1203
  * @param experimentId - The experiment ID.
770
- * @param options - Options for evaluating the experiment.
771
- * @param defaultVariant - Default variant key to return if the experiment is not found. Defaults to null.
1204
+ * @param options - Options for evaluating the experiment (`profileId` only).
1205
+ * @param defaultVariant - Optional default variant key to return if the experiment is not found. Defaults to null.
772
1206
  * @returns A promise that resolves to the variant key assigned to this user, or defaultVariant if not found.
773
1207
  *
774
1208
  * @example
775
1209
  * ```typescript
776
- * const variant = await getExperiment('checkout-redesign-experiment-id')
1210
+ * const variant = await getExperiment('checkout-redesign-experiment-id', { profileId: 'user-123' })
1211
+ *
1212
+ * // Optional fallback variant:
1213
+ * const variantWithFallback = await getExperiment('checkout-redesign-experiment-id', undefined, 'control')
777
1214
  *
778
1215
  * if (variant === 'new-checkout') {
779
1216
  * // Show new checkout flow
@@ -867,6 +1304,7 @@ exports.getProfileId = getProfileId;
867
1304
  exports.getSessionId = getSessionId;
868
1305
  exports.init = init;
869
1306
  exports.pageview = pageview;
1307
+ exports.startSessionReplay = startSessionReplay;
870
1308
  exports.track = track;
871
1309
  exports.trackError = trackError;
872
1310
  exports.trackErrors = trackErrors;