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.es5.js
CHANGED
|
@@ -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
|
|
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 -
|
|
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
|
|
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 -
|
|
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 (
|
|
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
|
-
*
|
|
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 (
|
|
733
|
-
* @param defaultValue -
|
|
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', {
|
|
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
|
|
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
|
|
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 -
|
|
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
|