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