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