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.
- package/README.md +84 -8
- package/dist/esnext/Lib.d.ts +77 -11
- package/dist/esnext/Lib.js +536 -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 +563 -25
- package/dist/swetrix.cjs.js.map +1 -1
- package/dist/swetrix.es5.js +563 -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 +751 -12
- package/src/index.ts +29 -14
- package/src/types/rrweb-shim.d.ts +11 -0
- package/tests/sessionReplay.test.ts +600 -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,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
|
|
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 -
|
|
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
|
|
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 -
|
|
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 (
|
|
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
|
-
*
|
|
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 (
|
|
735
|
-
* @param defaultValue -
|
|
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', {
|
|
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
|
|
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
|
|
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 -
|
|
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;
|