swetrix 4.2.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.
- package/README.md +49 -8
- package/dist/esnext/Lib.d.ts +73 -11
- package/dist/esnext/Lib.js +414 -11
- package/dist/esnext/Lib.js.map +1 -1
- package/dist/esnext/index.d.ts +18 -15
- package/dist/esnext/index.js +23 -15
- package/dist/esnext/index.js.map +1 -1
- package/dist/replaylibrary.min.js +173 -0
- package/dist/swetrix.cjs.js +441 -25
- package/dist/swetrix.cjs.js.map +1 -1
- package/dist/swetrix.es5.js +441 -26
- package/dist/swetrix.es5.js.map +1 -1
- package/dist/swetrix.js +1 -1
- package/dist/swetrix.js.map +1 -1
- package/jest.config.js +2 -0
- package/package.json +10 -7
- package/rollup.config.mjs +20 -0
- package/src/Lib.ts +579 -12
- package/src/index.ts +29 -14
- package/src/types/rrweb-shim.d.ts +11 -0
- package/tests/sessionReplay.test.ts +389 -0
- package/tsconfig.esnext.json +5 -1
- package/tsconfig.json +6 -1
- package/tsconfig.test.json +7 -0
package/dist/swetrix.cjs.js
CHANGED
|
@@ -90,11 +90,29 @@ 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_PRIVACY = 'total';
|
|
108
|
+
const SESSION_REPLAY_ACTIVITY_EVENTS = [
|
|
109
|
+
'click',
|
|
110
|
+
'keydown',
|
|
111
|
+
'mousedown',
|
|
112
|
+
'mousemove',
|
|
113
|
+
'scroll',
|
|
114
|
+
'touchstart',
|
|
115
|
+
];
|
|
98
116
|
// Default cache duration: 5 minutes
|
|
99
117
|
const DEFAULT_CACHE_DURATION = 5 * 60 * 1000;
|
|
100
118
|
class Lib {
|
|
@@ -108,9 +126,15 @@ class Lib {
|
|
|
108
126
|
this.activePage = null;
|
|
109
127
|
this.errorListenerExists = false;
|
|
110
128
|
this.cachedData = null;
|
|
129
|
+
this.rrwebLoader = null;
|
|
130
|
+
this.sessionReplayActions = null;
|
|
131
|
+
this.sessionReplayInitPromise = null;
|
|
111
132
|
this.trackPathChange = this.trackPathChange.bind(this);
|
|
112
133
|
this.heartbeat = this.heartbeat.bind(this);
|
|
113
134
|
this.captureError = this.captureError.bind(this);
|
|
135
|
+
if (this.getSessionReplayPreloadOption()) {
|
|
136
|
+
void this.preloadSessionReplay().catch(() => undefined);
|
|
137
|
+
}
|
|
114
138
|
}
|
|
115
139
|
captureError(event) {
|
|
116
140
|
var _a, _b, _c, _d;
|
|
@@ -258,10 +282,10 @@ class Lib {
|
|
|
258
282
|
};
|
|
259
283
|
}
|
|
260
284
|
/**
|
|
261
|
-
* Fetches all feature flags
|
|
262
|
-
* 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.
|
|
263
287
|
*
|
|
264
|
-
* @param options - Options for evaluating feature flags.
|
|
288
|
+
* @param options - Options for evaluating feature flags (`profileId` only).
|
|
265
289
|
* @param forceRefresh - If true, bypasses the cache and fetches fresh data.
|
|
266
290
|
* @returns A promise that resolves to a record of flag keys to boolean values.
|
|
267
291
|
*/
|
|
@@ -328,8 +352,8 @@ class Lib {
|
|
|
328
352
|
* Gets the value of a single feature flag.
|
|
329
353
|
*
|
|
330
354
|
* @param key - The feature flag key.
|
|
331
|
-
* @param options - Options for evaluating the feature flag.
|
|
332
|
-
* @param defaultValue -
|
|
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.
|
|
333
357
|
* @returns A promise that resolves to the boolean value of the flag.
|
|
334
358
|
*/
|
|
335
359
|
async getFeatureFlag(key, options, defaultValue = false) {
|
|
@@ -344,16 +368,16 @@ class Lib {
|
|
|
344
368
|
this.cachedData = null;
|
|
345
369
|
}
|
|
346
370
|
/**
|
|
347
|
-
* Fetches
|
|
371
|
+
* Fetches variant assignments for running A/B test experiments returned by feature flag evaluation.
|
|
348
372
|
* Results are cached for 5 minutes by default (shared cache with feature flags).
|
|
349
373
|
*
|
|
350
|
-
* @param options - Options for evaluating experiments.
|
|
374
|
+
* @param options - Options for evaluating experiments (`profileId` only).
|
|
351
375
|
* @param forceRefresh - If true, bypasses the cache and fetches fresh data.
|
|
352
376
|
* @returns A promise that resolves to a record of experiment IDs to variant keys.
|
|
353
377
|
*
|
|
354
378
|
* @example
|
|
355
379
|
* ```typescript
|
|
356
|
-
* const experiments = await getExperiments()
|
|
380
|
+
* const experiments = await getExperiments({ profileId: 'user-123' })
|
|
357
381
|
* // experiments = { 'exp-123': 'variant-a', 'exp-456': 'control' }
|
|
358
382
|
* ```
|
|
359
383
|
*/
|
|
@@ -384,13 +408,16 @@ class Lib {
|
|
|
384
408
|
* Gets the variant key for a specific A/B test experiment.
|
|
385
409
|
*
|
|
386
410
|
* @param experimentId - The experiment ID.
|
|
387
|
-
* @param options - Options for evaluating the experiment.
|
|
388
|
-
* @param defaultVariant -
|
|
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.
|
|
389
413
|
* @returns A promise that resolves to the variant key assigned to this user, or defaultVariant if not found.
|
|
390
414
|
*
|
|
391
415
|
* @example
|
|
392
416
|
* ```typescript
|
|
393
|
-
* 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')
|
|
394
421
|
*
|
|
395
422
|
* if (variant === 'new-checkout') {
|
|
396
423
|
* // Show new checkout flow
|
|
@@ -506,6 +533,167 @@ class Lib {
|
|
|
506
533
|
return null;
|
|
507
534
|
}
|
|
508
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
|
+
}
|
|
509
697
|
/**
|
|
510
698
|
* Gets the API base URL (without /log suffix).
|
|
511
699
|
*/
|
|
@@ -595,6 +783,225 @@ class Lib {
|
|
|
595
783
|
}
|
|
596
784
|
return true;
|
|
597
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
|
+
}
|
|
598
1005
|
async sendRequest(path, body) {
|
|
599
1006
|
var _a;
|
|
600
1007
|
const host = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.apiURL) || DEFAULT_API_HOST;
|
|
@@ -670,6 +1077,12 @@ function trackErrors(options) {
|
|
|
670
1077
|
}
|
|
671
1078
|
return exports.LIB_INSTANCE.trackErrors(options);
|
|
672
1079
|
}
|
|
1080
|
+
function startSessionReplay(options) {
|
|
1081
|
+
if (!exports.LIB_INSTANCE) {
|
|
1082
|
+
return Promise.resolve(defaultSessionReplayActions);
|
|
1083
|
+
}
|
|
1084
|
+
return exports.LIB_INSTANCE.startSessionReplay(options);
|
|
1085
|
+
}
|
|
673
1086
|
/**
|
|
674
1087
|
* This function is used to manually track an error event.
|
|
675
1088
|
* It's useful if you want to track specific errors in your application.
|
|
@@ -704,17 +1117,16 @@ function pageview(options) {
|
|
|
704
1117
|
}
|
|
705
1118
|
/**
|
|
706
1119
|
* Fetches all feature flags for the project.
|
|
707
|
-
* Results are cached for 5 minutes by default.
|
|
1120
|
+
* Results are cached for 5 minutes by default and share a cache with experiments.
|
|
708
1121
|
*
|
|
709
|
-
* @param options - Options for evaluating feature flags (
|
|
1122
|
+
* @param options - Options for evaluating feature flags (`profileId` only).
|
|
710
1123
|
* @param forceRefresh - If true, bypasses the cache and fetches fresh flags.
|
|
711
1124
|
* @returns A promise that resolves to a record of flag keys to boolean values.
|
|
712
1125
|
*
|
|
713
1126
|
* @example
|
|
714
1127
|
* ```typescript
|
|
715
1128
|
* const flags = await getFeatureFlags({
|
|
716
|
-
*
|
|
717
|
-
* attributes: { cc: 'US', dv: 'desktop' }
|
|
1129
|
+
* profileId: 'user-123'
|
|
718
1130
|
* })
|
|
719
1131
|
*
|
|
720
1132
|
* if (flags['new-checkout']) {
|
|
@@ -731,13 +1143,13 @@ async function getFeatureFlags(options, forceRefresh) {
|
|
|
731
1143
|
* Gets the value of a single feature flag.
|
|
732
1144
|
*
|
|
733
1145
|
* @param key - The feature flag key.
|
|
734
|
-
* @param options - Options for evaluating the feature flag (
|
|
735
|
-
* @param defaultValue -
|
|
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.
|
|
736
1148
|
* @returns A promise that resolves to the boolean value of the flag.
|
|
737
1149
|
*
|
|
738
1150
|
* @example
|
|
739
1151
|
* ```typescript
|
|
740
|
-
* const isEnabled = await getFeatureFlag('dark-mode', {
|
|
1152
|
+
* const isEnabled = await getFeatureFlag('dark-mode', { profileId: 'user-123' })
|
|
741
1153
|
*
|
|
742
1154
|
* if (isEnabled) {
|
|
743
1155
|
* // Enable dark mode
|
|
@@ -750,8 +1162,8 @@ async function getFeatureFlag(key, options, defaultValue = false) {
|
|
|
750
1162
|
return exports.LIB_INSTANCE.getFeatureFlag(key, options, defaultValue);
|
|
751
1163
|
}
|
|
752
1164
|
/**
|
|
753
|
-
* Clears the cached feature flags, forcing a fresh fetch on the next call.
|
|
754
|
-
* Useful when you know the user's
|
|
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.
|
|
755
1167
|
*/
|
|
756
1168
|
function clearFeatureFlagsCache() {
|
|
757
1169
|
if (!exports.LIB_INSTANCE)
|
|
@@ -759,10 +1171,10 @@ function clearFeatureFlagsCache() {
|
|
|
759
1171
|
exports.LIB_INSTANCE.clearFeatureFlagsCache();
|
|
760
1172
|
}
|
|
761
1173
|
/**
|
|
762
|
-
* Fetches
|
|
1174
|
+
* Fetches variant assignments for running A/B test experiments returned by feature flag evaluation.
|
|
763
1175
|
* Results are cached for 5 minutes by default (shared cache with feature flags).
|
|
764
1176
|
*
|
|
765
|
-
* @param options - Options for evaluating experiments.
|
|
1177
|
+
* @param options - Options for evaluating experiments (`profileId` only).
|
|
766
1178
|
* @param forceRefresh - If true, bypasses the cache and fetches fresh data.
|
|
767
1179
|
* @returns A promise that resolves to a record of experiment IDs to variant keys.
|
|
768
1180
|
*
|
|
@@ -789,13 +1201,16 @@ async function getExperiments(options, forceRefresh) {
|
|
|
789
1201
|
* Gets the variant key for a specific A/B test experiment.
|
|
790
1202
|
*
|
|
791
1203
|
* @param experimentId - The experiment ID.
|
|
792
|
-
* @param options - Options for evaluating the experiment.
|
|
793
|
-
* @param defaultVariant -
|
|
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.
|
|
794
1206
|
* @returns A promise that resolves to the variant key assigned to this user, or defaultVariant if not found.
|
|
795
1207
|
*
|
|
796
1208
|
* @example
|
|
797
1209
|
* ```typescript
|
|
798
|
-
* 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')
|
|
799
1214
|
*
|
|
800
1215
|
* if (variant === 'new-checkout') {
|
|
801
1216
|
* // Show new checkout flow
|
|
@@ -889,6 +1304,7 @@ exports.getProfileId = getProfileId;
|
|
|
889
1304
|
exports.getSessionId = getSessionId;
|
|
890
1305
|
exports.init = init;
|
|
891
1306
|
exports.pageview = pageview;
|
|
1307
|
+
exports.startSessionReplay = startSessionReplay;
|
|
892
1308
|
exports.track = track;
|
|
893
1309
|
exports.trackError = trackError;
|
|
894
1310
|
exports.trackErrors = trackErrors;
|