@dataxamas/tracker-js 2.1.0 → 2.1.2
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/dist/index.cjs +94 -17
- package/dist/index.d.cts +12 -3
- package/dist/index.d.ts +12 -3
- package/dist/index.js +94 -17
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -318,19 +318,34 @@ var STORAGE_PREFIX = "@dataxamas";
|
|
|
318
318
|
var REPLAY_FLUSH_SIZE = 100;
|
|
319
319
|
var REPLAY_FLUSH_INTERVAL_MS = 5e3;
|
|
320
320
|
var CAP_CHECK_MS = 1e3;
|
|
321
|
-
var MAX_RECORDING_MS =
|
|
322
|
-
var MAX_EVENTS_PER_RECORDING =
|
|
321
|
+
var MAX_RECORDING_MS = 30 * 60 * 1e3;
|
|
322
|
+
var MAX_EVENTS_PER_RECORDING = 5e4;
|
|
323
|
+
var REPLAY_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
324
|
+
var ACTIVITY_SAVE_THROTTLE_MS = 5e3;
|
|
325
|
+
var REPLAY_RECORDING_KEY = `${STORAGE_PREFIX}/replay-recording`;
|
|
326
|
+
function isStoredRecording(value) {
|
|
327
|
+
if (typeof value !== "object" || value === null) return false;
|
|
328
|
+
const stored = value;
|
|
329
|
+
return typeof stored.id === "string" && typeof stored.sessionId === "string" && typeof stored.startedAt === "string" && typeof stored.startedAtMs === "number" && typeof stored.seq === "number" && typeof stored.events === "number" && typeof stored.lastActivityAt === "number" && typeof stored.capped === "boolean";
|
|
330
|
+
}
|
|
323
331
|
var KEEPALIVE_LIMIT_BYTES = 60 * 1024;
|
|
324
|
-
var REPLAY_SAMPLE_RATE = 1;
|
|
325
332
|
var DataxamasTracking = class {
|
|
326
333
|
constructor(options) {
|
|
327
334
|
/**
|
|
328
|
-
* Primeiro gesto
|
|
329
|
-
*
|
|
335
|
+
* Primeiro gesto liga o gravador; os seguintes só o religam se ele tiver parado (aba que foi
|
|
336
|
+
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto ou de muito tempo parado,
|
|
337
|
+
* o gesto abre uma gravação nova.
|
|
330
338
|
*/
|
|
331
339
|
this.markReplayActivity = () => {
|
|
332
|
-
if (this.replayHalted || this.
|
|
340
|
+
if (this.replayHalted || this.cutoffPending) return;
|
|
333
341
|
if (document.visibilityState === "hidden") return;
|
|
342
|
+
const now = Date.now();
|
|
343
|
+
if (this.replayCapped || now - this.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
|
|
344
|
+
if (this.isRecording()) this.stopReplayRecorder(false);
|
|
345
|
+
this.rotateRecording();
|
|
346
|
+
}
|
|
347
|
+
this.touchActivity(now);
|
|
348
|
+
if (this.isRecording()) return;
|
|
334
349
|
this.startReplayRecorder();
|
|
335
350
|
};
|
|
336
351
|
if (!options.apiKey) {
|
|
@@ -343,6 +358,7 @@ var DataxamasTracking = class {
|
|
|
343
358
|
this.currentPage = isBrowser() ? window.location.pathname : "";
|
|
344
359
|
this.heatMapEnabled = _nullishCoalesce(options.heatMapEnabled, () => ( true));
|
|
345
360
|
this.replayEnabled = _nullishCoalesce(options.replayEnabled, () => ( true));
|
|
361
|
+
this.replaySampleRate = 1;
|
|
346
362
|
this.eventQueue = [];
|
|
347
363
|
this.isProcessing = false;
|
|
348
364
|
this.rrwebRecord = null;
|
|
@@ -359,6 +375,8 @@ var DataxamasTracking = class {
|
|
|
359
375
|
this.recordingStartedAtMs = 0;
|
|
360
376
|
this.recordingSeq = 0;
|
|
361
377
|
this.recordingEvents = 0;
|
|
378
|
+
this.lastActivityAt = 0;
|
|
379
|
+
this.lastActivitySavedAt = 0;
|
|
362
380
|
}
|
|
363
381
|
isLocalhost() {
|
|
364
382
|
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
@@ -420,8 +438,7 @@ var DataxamasTracking = class {
|
|
|
420
438
|
setInterval(() => {
|
|
421
439
|
const newPath = window.location.pathname;
|
|
422
440
|
if (newPath !== lastPath) {
|
|
423
|
-
this.
|
|
424
|
-
this.rotateRecording();
|
|
441
|
+
this.flushReplay(false);
|
|
425
442
|
this.currentPage = newPath;
|
|
426
443
|
this.sendEvent({ name: "PAGE_VIEW", path: newPath });
|
|
427
444
|
lastPath = newPath;
|
|
@@ -437,6 +454,9 @@ var DataxamasTracking = class {
|
|
|
437
454
|
const cfg = await res.json();
|
|
438
455
|
if (typeof cfg.heatMapEnabled === "boolean") this.heatMapEnabled = cfg.heatMapEnabled;
|
|
439
456
|
if (typeof cfg.replayEnabled === "boolean") this.replayEnabled = cfg.replayEnabled;
|
|
457
|
+
if (typeof cfg.replaySampleRate === "number" && cfg.replaySampleRate >= 0 && cfg.replaySampleRate <= 1) {
|
|
458
|
+
this.replaySampleRate = cfg.replaySampleRate;
|
|
459
|
+
}
|
|
440
460
|
} catch (_) {
|
|
441
461
|
}
|
|
442
462
|
}
|
|
@@ -530,17 +550,20 @@ var DataxamasTracking = class {
|
|
|
530
550
|
* de um replay picotado que não dá para assistir.
|
|
531
551
|
*/
|
|
532
552
|
isReplaySampled() {
|
|
533
|
-
|
|
534
|
-
if (
|
|
553
|
+
const rate = this.replaySampleRate;
|
|
554
|
+
if (rate >= 1) return true;
|
|
555
|
+
if (rate <= 0) return false;
|
|
556
|
+
const key = `${STORAGE_PREFIX}/replay-sample`;
|
|
535
557
|
try {
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
558
|
+
const [storedRate, storedDecision] = (_nullishCoalesce(window.localStorage.getItem(key), () => ( ""))).split(":");
|
|
559
|
+
if (Number(storedRate) === rate && (storedDecision === "1" || storedDecision === "0")) {
|
|
560
|
+
return storedDecision === "1";
|
|
561
|
+
}
|
|
562
|
+
const sampled = Math.random() < rate;
|
|
563
|
+
window.localStorage.setItem(key, `${rate}:${sampled ? "1" : "0"}`);
|
|
541
564
|
return sampled;
|
|
542
565
|
} catch (_) {
|
|
543
|
-
return Math.random() <
|
|
566
|
+
return Math.random() < rate;
|
|
544
567
|
}
|
|
545
568
|
}
|
|
546
569
|
isRecording() {
|
|
@@ -549,6 +572,31 @@ var DataxamasTracking = class {
|
|
|
549
572
|
exceededReplayCaps() {
|
|
550
573
|
return this.recordingEvents >= MAX_EVENTS_PER_RECORDING || Date.now() - this.recordingStartedAtMs >= MAX_RECORDING_MS;
|
|
551
574
|
}
|
|
575
|
+
loadRecording() {
|
|
576
|
+
try {
|
|
577
|
+
const parsed = JSON.parse(_nullishCoalesce(window.sessionStorage.getItem(REPLAY_RECORDING_KEY), () => ( "null")));
|
|
578
|
+
return isStoredRecording(parsed) ? parsed : null;
|
|
579
|
+
} catch (_) {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
saveRecording() {
|
|
584
|
+
if (!this.sessionId || !this.recordingId || !this.recordingStartedAt) return;
|
|
585
|
+
const stored = {
|
|
586
|
+
id: this.recordingId,
|
|
587
|
+
sessionId: this.sessionId,
|
|
588
|
+
startedAt: this.recordingStartedAt,
|
|
589
|
+
startedAtMs: this.recordingStartedAtMs,
|
|
590
|
+
seq: this.recordingSeq,
|
|
591
|
+
events: this.recordingEvents,
|
|
592
|
+
lastActivityAt: this.lastActivityAt,
|
|
593
|
+
capped: this.replayCapped
|
|
594
|
+
};
|
|
595
|
+
try {
|
|
596
|
+
window.sessionStorage.setItem(REPLAY_RECORDING_KEY, JSON.stringify(stored));
|
|
597
|
+
} catch (_) {
|
|
598
|
+
}
|
|
599
|
+
}
|
|
552
600
|
rotateRecording() {
|
|
553
601
|
this.replayCapped = false;
|
|
554
602
|
this.recordingId = crypto.randomUUID();
|
|
@@ -556,6 +604,29 @@ var DataxamasTracking = class {
|
|
|
556
604
|
this.recordingStartedAtMs = Date.now();
|
|
557
605
|
this.recordingSeq = 0;
|
|
558
606
|
this.recordingEvents = 0;
|
|
607
|
+
this.lastActivityAt = Date.now();
|
|
608
|
+
this.saveRecording();
|
|
609
|
+
}
|
|
610
|
+
/** Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa. */
|
|
611
|
+
resumeOrRotateRecording() {
|
|
612
|
+
const stored = this.loadRecording();
|
|
613
|
+
if (!stored || stored.capped || stored.sessionId !== this.sessionId || Date.now() - stored.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
|
|
614
|
+
this.rotateRecording();
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
this.replayCapped = false;
|
|
618
|
+
this.recordingId = stored.id;
|
|
619
|
+
this.recordingStartedAt = stored.startedAt;
|
|
620
|
+
this.recordingStartedAtMs = stored.startedAtMs;
|
|
621
|
+
this.recordingSeq = stored.seq;
|
|
622
|
+
this.recordingEvents = stored.events;
|
|
623
|
+
this.lastActivityAt = stored.lastActivityAt;
|
|
624
|
+
}
|
|
625
|
+
touchActivity(now) {
|
|
626
|
+
this.lastActivityAt = now;
|
|
627
|
+
if (now - this.lastActivitySavedAt < ACTIVITY_SAVE_THROTTLE_MS) return;
|
|
628
|
+
this.lastActivitySavedAt = now;
|
|
629
|
+
this.saveRecording();
|
|
559
630
|
}
|
|
560
631
|
flushReplay(urgent) {
|
|
561
632
|
if (this.replayBuffer.length === 0 || !this.recordingId) return;
|
|
@@ -571,6 +642,7 @@ var DataxamasTracking = class {
|
|
|
571
642
|
...this.measureReplay(),
|
|
572
643
|
events
|
|
573
644
|
});
|
|
645
|
+
this.saveRecording();
|
|
574
646
|
void this.sendReplayBatch(payload, urgent);
|
|
575
647
|
}
|
|
576
648
|
/**
|
|
@@ -612,6 +684,8 @@ var DataxamasTracking = class {
|
|
|
612
684
|
const body = await res.json().catch(() => null);
|
|
613
685
|
if (_optionalChain([body, 'optionalAccess', _28 => _28.stop])) {
|
|
614
686
|
this.replayHalted = true;
|
|
687
|
+
this.replayCapped = true;
|
|
688
|
+
this.saveRecording();
|
|
615
689
|
this.stopReplayRecorder(false);
|
|
616
690
|
}
|
|
617
691
|
} catch (_) {
|
|
@@ -625,13 +699,14 @@ var DataxamasTracking = class {
|
|
|
625
699
|
}
|
|
626
700
|
}
|
|
627
701
|
/**
|
|
628
|
-
* Bateu o teto: encerra a gravação e fica parado até a próxima
|
|
702
|
+
* Bateu o teto: encerra a gravação e fica parado até a próxima interação, em vez de emendar um
|
|
629
703
|
* clipe atrás do outro enquanto a página só anima sozinha.
|
|
630
704
|
*/
|
|
631
705
|
scheduleReplayCutoff() {
|
|
632
706
|
if (this.cutoffPending) return;
|
|
633
707
|
this.cutoffPending = true;
|
|
634
708
|
this.replayCapped = true;
|
|
709
|
+
this.saveRecording();
|
|
635
710
|
setTimeout(() => {
|
|
636
711
|
this.cutoffPending = false;
|
|
637
712
|
this.stopReplayRecorder(false);
|
|
@@ -708,6 +783,7 @@ var DataxamasTracking = class {
|
|
|
708
783
|
}
|
|
709
784
|
startTracking() {
|
|
710
785
|
this.sessionId = _nullishCoalesce(this.getSessionId(), () => ( this.createSessionId()));
|
|
786
|
+
this.resumeOrRotateRecording();
|
|
711
787
|
this.sendEvent({ name: "PAGE_VIEW" });
|
|
712
788
|
this.trackPageChanges();
|
|
713
789
|
this.trackClicks();
|
|
@@ -722,6 +798,7 @@ var DataxamasTracking = class {
|
|
|
722
798
|
throw new Error("DataxamasTracking: This SDK can only be used in a browser environment.");
|
|
723
799
|
}
|
|
724
800
|
if (!this.allowLocalhost && this.isLocalhost()) return;
|
|
801
|
+
if (navigator.webdriver) return;
|
|
725
802
|
if (document.readyState === "loading") {
|
|
726
803
|
document.addEventListener("DOMContentLoaded", this.startTracking.bind(this));
|
|
727
804
|
} else {
|
package/dist/index.d.cts
CHANGED
|
@@ -112,6 +112,7 @@ declare class DataxamasTracking {
|
|
|
112
112
|
private currentPage;
|
|
113
113
|
private heatMapEnabled;
|
|
114
114
|
private replayEnabled;
|
|
115
|
+
private replaySampleRate;
|
|
115
116
|
private eventQueue;
|
|
116
117
|
private isProcessing;
|
|
117
118
|
private rrwebRecord;
|
|
@@ -128,6 +129,8 @@ declare class DataxamasTracking {
|
|
|
128
129
|
private recordingStartedAtMs;
|
|
129
130
|
private recordingSeq;
|
|
130
131
|
private recordingEvents;
|
|
132
|
+
private lastActivityAt;
|
|
133
|
+
private lastActivitySavedAt;
|
|
131
134
|
constructor(options: DataxamasTrackingOptions);
|
|
132
135
|
private isLocalhost;
|
|
133
136
|
private getSessionId;
|
|
@@ -150,7 +153,12 @@ declare class DataxamasTracking {
|
|
|
150
153
|
private isReplaySampled;
|
|
151
154
|
private isRecording;
|
|
152
155
|
private exceededReplayCaps;
|
|
156
|
+
private loadRecording;
|
|
157
|
+
private saveRecording;
|
|
153
158
|
private rotateRecording;
|
|
159
|
+
/** Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa. */
|
|
160
|
+
private resumeOrRotateRecording;
|
|
161
|
+
private touchActivity;
|
|
154
162
|
private flushReplay;
|
|
155
163
|
/**
|
|
156
164
|
* Um full snapshot com CSS inline passa fácil de 100KB cru, e era isso que tomava 413 no
|
|
@@ -160,13 +168,14 @@ declare class DataxamasTracking {
|
|
|
160
168
|
private sendReplayBatch;
|
|
161
169
|
private stopReplayRecorder;
|
|
162
170
|
/**
|
|
163
|
-
* Bateu o teto: encerra a gravação e fica parado até a próxima
|
|
171
|
+
* Bateu o teto: encerra a gravação e fica parado até a próxima interação, em vez de emendar um
|
|
164
172
|
* clipe atrás do outro enquanto a página só anima sozinha.
|
|
165
173
|
*/
|
|
166
174
|
private scheduleReplayCutoff;
|
|
167
175
|
/**
|
|
168
|
-
* Primeiro gesto
|
|
169
|
-
*
|
|
176
|
+
* Primeiro gesto liga o gravador; os seguintes só o religam se ele tiver parado (aba que foi
|
|
177
|
+
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto ou de muito tempo parado,
|
|
178
|
+
* o gesto abre uma gravação nova.
|
|
170
179
|
*/
|
|
171
180
|
private markReplayActivity;
|
|
172
181
|
private setupReplayLifecycle;
|
package/dist/index.d.ts
CHANGED
|
@@ -112,6 +112,7 @@ declare class DataxamasTracking {
|
|
|
112
112
|
private currentPage;
|
|
113
113
|
private heatMapEnabled;
|
|
114
114
|
private replayEnabled;
|
|
115
|
+
private replaySampleRate;
|
|
115
116
|
private eventQueue;
|
|
116
117
|
private isProcessing;
|
|
117
118
|
private rrwebRecord;
|
|
@@ -128,6 +129,8 @@ declare class DataxamasTracking {
|
|
|
128
129
|
private recordingStartedAtMs;
|
|
129
130
|
private recordingSeq;
|
|
130
131
|
private recordingEvents;
|
|
132
|
+
private lastActivityAt;
|
|
133
|
+
private lastActivitySavedAt;
|
|
131
134
|
constructor(options: DataxamasTrackingOptions);
|
|
132
135
|
private isLocalhost;
|
|
133
136
|
private getSessionId;
|
|
@@ -150,7 +153,12 @@ declare class DataxamasTracking {
|
|
|
150
153
|
private isReplaySampled;
|
|
151
154
|
private isRecording;
|
|
152
155
|
private exceededReplayCaps;
|
|
156
|
+
private loadRecording;
|
|
157
|
+
private saveRecording;
|
|
153
158
|
private rotateRecording;
|
|
159
|
+
/** Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa. */
|
|
160
|
+
private resumeOrRotateRecording;
|
|
161
|
+
private touchActivity;
|
|
154
162
|
private flushReplay;
|
|
155
163
|
/**
|
|
156
164
|
* Um full snapshot com CSS inline passa fácil de 100KB cru, e era isso que tomava 413 no
|
|
@@ -160,13 +168,14 @@ declare class DataxamasTracking {
|
|
|
160
168
|
private sendReplayBatch;
|
|
161
169
|
private stopReplayRecorder;
|
|
162
170
|
/**
|
|
163
|
-
* Bateu o teto: encerra a gravação e fica parado até a próxima
|
|
171
|
+
* Bateu o teto: encerra a gravação e fica parado até a próxima interação, em vez de emendar um
|
|
164
172
|
* clipe atrás do outro enquanto a página só anima sozinha.
|
|
165
173
|
*/
|
|
166
174
|
private scheduleReplayCutoff;
|
|
167
175
|
/**
|
|
168
|
-
* Primeiro gesto
|
|
169
|
-
*
|
|
176
|
+
* Primeiro gesto liga o gravador; os seguintes só o religam se ele tiver parado (aba que foi
|
|
177
|
+
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto ou de muito tempo parado,
|
|
178
|
+
* o gesto abre uma gravação nova.
|
|
170
179
|
*/
|
|
171
180
|
private markReplayActivity;
|
|
172
181
|
private setupReplayLifecycle;
|
package/dist/index.js
CHANGED
|
@@ -318,19 +318,34 @@ var STORAGE_PREFIX = "@dataxamas";
|
|
|
318
318
|
var REPLAY_FLUSH_SIZE = 100;
|
|
319
319
|
var REPLAY_FLUSH_INTERVAL_MS = 5e3;
|
|
320
320
|
var CAP_CHECK_MS = 1e3;
|
|
321
|
-
var MAX_RECORDING_MS =
|
|
322
|
-
var MAX_EVENTS_PER_RECORDING =
|
|
321
|
+
var MAX_RECORDING_MS = 30 * 60 * 1e3;
|
|
322
|
+
var MAX_EVENTS_PER_RECORDING = 5e4;
|
|
323
|
+
var REPLAY_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
324
|
+
var ACTIVITY_SAVE_THROTTLE_MS = 5e3;
|
|
325
|
+
var REPLAY_RECORDING_KEY = `${STORAGE_PREFIX}/replay-recording`;
|
|
326
|
+
function isStoredRecording(value) {
|
|
327
|
+
if (typeof value !== "object" || value === null) return false;
|
|
328
|
+
const stored = value;
|
|
329
|
+
return typeof stored.id === "string" && typeof stored.sessionId === "string" && typeof stored.startedAt === "string" && typeof stored.startedAtMs === "number" && typeof stored.seq === "number" && typeof stored.events === "number" && typeof stored.lastActivityAt === "number" && typeof stored.capped === "boolean";
|
|
330
|
+
}
|
|
323
331
|
var KEEPALIVE_LIMIT_BYTES = 60 * 1024;
|
|
324
|
-
var REPLAY_SAMPLE_RATE = 1;
|
|
325
332
|
var DataxamasTracking = class {
|
|
326
333
|
constructor(options) {
|
|
327
334
|
/**
|
|
328
|
-
* Primeiro gesto
|
|
329
|
-
*
|
|
335
|
+
* Primeiro gesto liga o gravador; os seguintes só o religam se ele tiver parado (aba que foi
|
|
336
|
+
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto ou de muito tempo parado,
|
|
337
|
+
* o gesto abre uma gravação nova.
|
|
330
338
|
*/
|
|
331
339
|
this.markReplayActivity = () => {
|
|
332
|
-
if (this.replayHalted || this.
|
|
340
|
+
if (this.replayHalted || this.cutoffPending) return;
|
|
333
341
|
if (document.visibilityState === "hidden") return;
|
|
342
|
+
const now = Date.now();
|
|
343
|
+
if (this.replayCapped || now - this.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
|
|
344
|
+
if (this.isRecording()) this.stopReplayRecorder(false);
|
|
345
|
+
this.rotateRecording();
|
|
346
|
+
}
|
|
347
|
+
this.touchActivity(now);
|
|
348
|
+
if (this.isRecording()) return;
|
|
334
349
|
this.startReplayRecorder();
|
|
335
350
|
};
|
|
336
351
|
if (!options.apiKey) {
|
|
@@ -343,6 +358,7 @@ var DataxamasTracking = class {
|
|
|
343
358
|
this.currentPage = isBrowser() ? window.location.pathname : "";
|
|
344
359
|
this.heatMapEnabled = options.heatMapEnabled ?? true;
|
|
345
360
|
this.replayEnabled = options.replayEnabled ?? true;
|
|
361
|
+
this.replaySampleRate = 1;
|
|
346
362
|
this.eventQueue = [];
|
|
347
363
|
this.isProcessing = false;
|
|
348
364
|
this.rrwebRecord = null;
|
|
@@ -359,6 +375,8 @@ var DataxamasTracking = class {
|
|
|
359
375
|
this.recordingStartedAtMs = 0;
|
|
360
376
|
this.recordingSeq = 0;
|
|
361
377
|
this.recordingEvents = 0;
|
|
378
|
+
this.lastActivityAt = 0;
|
|
379
|
+
this.lastActivitySavedAt = 0;
|
|
362
380
|
}
|
|
363
381
|
isLocalhost() {
|
|
364
382
|
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
@@ -420,8 +438,7 @@ var DataxamasTracking = class {
|
|
|
420
438
|
setInterval(() => {
|
|
421
439
|
const newPath = window.location.pathname;
|
|
422
440
|
if (newPath !== lastPath) {
|
|
423
|
-
this.
|
|
424
|
-
this.rotateRecording();
|
|
441
|
+
this.flushReplay(false);
|
|
425
442
|
this.currentPage = newPath;
|
|
426
443
|
this.sendEvent({ name: "PAGE_VIEW", path: newPath });
|
|
427
444
|
lastPath = newPath;
|
|
@@ -437,6 +454,9 @@ var DataxamasTracking = class {
|
|
|
437
454
|
const cfg = await res.json();
|
|
438
455
|
if (typeof cfg.heatMapEnabled === "boolean") this.heatMapEnabled = cfg.heatMapEnabled;
|
|
439
456
|
if (typeof cfg.replayEnabled === "boolean") this.replayEnabled = cfg.replayEnabled;
|
|
457
|
+
if (typeof cfg.replaySampleRate === "number" && cfg.replaySampleRate >= 0 && cfg.replaySampleRate <= 1) {
|
|
458
|
+
this.replaySampleRate = cfg.replaySampleRate;
|
|
459
|
+
}
|
|
440
460
|
} catch (_) {
|
|
441
461
|
}
|
|
442
462
|
}
|
|
@@ -530,17 +550,20 @@ var DataxamasTracking = class {
|
|
|
530
550
|
* de um replay picotado que não dá para assistir.
|
|
531
551
|
*/
|
|
532
552
|
isReplaySampled() {
|
|
533
|
-
|
|
534
|
-
if (
|
|
553
|
+
const rate = this.replaySampleRate;
|
|
554
|
+
if (rate >= 1) return true;
|
|
555
|
+
if (rate <= 0) return false;
|
|
556
|
+
const key = `${STORAGE_PREFIX}/replay-sample`;
|
|
535
557
|
try {
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
558
|
+
const [storedRate, storedDecision] = (window.localStorage.getItem(key) ?? "").split(":");
|
|
559
|
+
if (Number(storedRate) === rate && (storedDecision === "1" || storedDecision === "0")) {
|
|
560
|
+
return storedDecision === "1";
|
|
561
|
+
}
|
|
562
|
+
const sampled = Math.random() < rate;
|
|
563
|
+
window.localStorage.setItem(key, `${rate}:${sampled ? "1" : "0"}`);
|
|
541
564
|
return sampled;
|
|
542
565
|
} catch (_) {
|
|
543
|
-
return Math.random() <
|
|
566
|
+
return Math.random() < rate;
|
|
544
567
|
}
|
|
545
568
|
}
|
|
546
569
|
isRecording() {
|
|
@@ -549,6 +572,31 @@ var DataxamasTracking = class {
|
|
|
549
572
|
exceededReplayCaps() {
|
|
550
573
|
return this.recordingEvents >= MAX_EVENTS_PER_RECORDING || Date.now() - this.recordingStartedAtMs >= MAX_RECORDING_MS;
|
|
551
574
|
}
|
|
575
|
+
loadRecording() {
|
|
576
|
+
try {
|
|
577
|
+
const parsed = JSON.parse(window.sessionStorage.getItem(REPLAY_RECORDING_KEY) ?? "null");
|
|
578
|
+
return isStoredRecording(parsed) ? parsed : null;
|
|
579
|
+
} catch (_) {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
saveRecording() {
|
|
584
|
+
if (!this.sessionId || !this.recordingId || !this.recordingStartedAt) return;
|
|
585
|
+
const stored = {
|
|
586
|
+
id: this.recordingId,
|
|
587
|
+
sessionId: this.sessionId,
|
|
588
|
+
startedAt: this.recordingStartedAt,
|
|
589
|
+
startedAtMs: this.recordingStartedAtMs,
|
|
590
|
+
seq: this.recordingSeq,
|
|
591
|
+
events: this.recordingEvents,
|
|
592
|
+
lastActivityAt: this.lastActivityAt,
|
|
593
|
+
capped: this.replayCapped
|
|
594
|
+
};
|
|
595
|
+
try {
|
|
596
|
+
window.sessionStorage.setItem(REPLAY_RECORDING_KEY, JSON.stringify(stored));
|
|
597
|
+
} catch (_) {
|
|
598
|
+
}
|
|
599
|
+
}
|
|
552
600
|
rotateRecording() {
|
|
553
601
|
this.replayCapped = false;
|
|
554
602
|
this.recordingId = crypto.randomUUID();
|
|
@@ -556,6 +604,29 @@ var DataxamasTracking = class {
|
|
|
556
604
|
this.recordingStartedAtMs = Date.now();
|
|
557
605
|
this.recordingSeq = 0;
|
|
558
606
|
this.recordingEvents = 0;
|
|
607
|
+
this.lastActivityAt = Date.now();
|
|
608
|
+
this.saveRecording();
|
|
609
|
+
}
|
|
610
|
+
/** Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa. */
|
|
611
|
+
resumeOrRotateRecording() {
|
|
612
|
+
const stored = this.loadRecording();
|
|
613
|
+
if (!stored || stored.capped || stored.sessionId !== this.sessionId || Date.now() - stored.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
|
|
614
|
+
this.rotateRecording();
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
this.replayCapped = false;
|
|
618
|
+
this.recordingId = stored.id;
|
|
619
|
+
this.recordingStartedAt = stored.startedAt;
|
|
620
|
+
this.recordingStartedAtMs = stored.startedAtMs;
|
|
621
|
+
this.recordingSeq = stored.seq;
|
|
622
|
+
this.recordingEvents = stored.events;
|
|
623
|
+
this.lastActivityAt = stored.lastActivityAt;
|
|
624
|
+
}
|
|
625
|
+
touchActivity(now) {
|
|
626
|
+
this.lastActivityAt = now;
|
|
627
|
+
if (now - this.lastActivitySavedAt < ACTIVITY_SAVE_THROTTLE_MS) return;
|
|
628
|
+
this.lastActivitySavedAt = now;
|
|
629
|
+
this.saveRecording();
|
|
559
630
|
}
|
|
560
631
|
flushReplay(urgent) {
|
|
561
632
|
if (this.replayBuffer.length === 0 || !this.recordingId) return;
|
|
@@ -571,6 +642,7 @@ var DataxamasTracking = class {
|
|
|
571
642
|
...this.measureReplay(),
|
|
572
643
|
events
|
|
573
644
|
});
|
|
645
|
+
this.saveRecording();
|
|
574
646
|
void this.sendReplayBatch(payload, urgent);
|
|
575
647
|
}
|
|
576
648
|
/**
|
|
@@ -612,6 +684,8 @@ var DataxamasTracking = class {
|
|
|
612
684
|
const body = await res.json().catch(() => null);
|
|
613
685
|
if (body?.stop) {
|
|
614
686
|
this.replayHalted = true;
|
|
687
|
+
this.replayCapped = true;
|
|
688
|
+
this.saveRecording();
|
|
615
689
|
this.stopReplayRecorder(false);
|
|
616
690
|
}
|
|
617
691
|
} catch (_) {
|
|
@@ -625,13 +699,14 @@ var DataxamasTracking = class {
|
|
|
625
699
|
}
|
|
626
700
|
}
|
|
627
701
|
/**
|
|
628
|
-
* Bateu o teto: encerra a gravação e fica parado até a próxima
|
|
702
|
+
* Bateu o teto: encerra a gravação e fica parado até a próxima interação, em vez de emendar um
|
|
629
703
|
* clipe atrás do outro enquanto a página só anima sozinha.
|
|
630
704
|
*/
|
|
631
705
|
scheduleReplayCutoff() {
|
|
632
706
|
if (this.cutoffPending) return;
|
|
633
707
|
this.cutoffPending = true;
|
|
634
708
|
this.replayCapped = true;
|
|
709
|
+
this.saveRecording();
|
|
635
710
|
setTimeout(() => {
|
|
636
711
|
this.cutoffPending = false;
|
|
637
712
|
this.stopReplayRecorder(false);
|
|
@@ -708,6 +783,7 @@ var DataxamasTracking = class {
|
|
|
708
783
|
}
|
|
709
784
|
startTracking() {
|
|
710
785
|
this.sessionId = this.getSessionId() ?? this.createSessionId();
|
|
786
|
+
this.resumeOrRotateRecording();
|
|
711
787
|
this.sendEvent({ name: "PAGE_VIEW" });
|
|
712
788
|
this.trackPageChanges();
|
|
713
789
|
this.trackClicks();
|
|
@@ -722,6 +798,7 @@ var DataxamasTracking = class {
|
|
|
722
798
|
throw new Error("DataxamasTracking: This SDK can only be used in a browser environment.");
|
|
723
799
|
}
|
|
724
800
|
if (!this.allowLocalhost && this.isLocalhost()) return;
|
|
801
|
+
if (navigator.webdriver) return;
|
|
725
802
|
if (document.readyState === "loading") {
|
|
726
803
|
document.addEventListener("DOMContentLoaded", this.startTracking.bind(this));
|
|
727
804
|
} else {
|