@dataxamas/tracker-js 2.6.0 → 2.8.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/CHANGELOG.md +61 -0
- package/LICENSE +21 -0
- package/dist/index.cjs +36 -19
- package/dist/index.d.cts +9 -5
- package/dist/index.d.ts +9 -5
- package/dist/index.js +36 -19
- package/package.json +6 -9
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
## 2.8.0
|
|
2
|
+
|
|
3
|
+
- Session replay records at most 30 seconds per visit, counted from the visitor's first gesture instead of page
|
|
4
|
+
load. After the cap the visit is not recorded again; a new recording only starts after 10 minutes without
|
|
5
|
+
interaction.
|
|
6
|
+
- The 1-minute idle pause is gone: the 30-second cap already ends the recording first.
|
|
7
|
+
|
|
8
|
+
## 2.7.0
|
|
9
|
+
|
|
10
|
+
- Web Vitals also collect FCP (First Contentful Paint) and TTFB (Time to First Byte), sent in the same batch as
|
|
11
|
+
LCP, INP and CLS. Needs a Dataxamas server that already accepts both metrics.
|
|
12
|
+
- Session replay pauses the recorder after 1 minute without a gesture and resumes on the next one, in the same
|
|
13
|
+
recording. Limits now match the `dataxamas.js` script: 10 minutes, 15,000 events and 5 MB per recording.
|
|
14
|
+
- Element scroll no longer counts as activity, so an auto-scrolling carousel does not keep the recorder running.
|
|
15
|
+
|
|
16
|
+
## 2.6.0
|
|
17
|
+
|
|
18
|
+
- No code changes; same build as 2.5.0.
|
|
19
|
+
|
|
20
|
+
## 2.5.0
|
|
21
|
+
|
|
22
|
+
- `tracking.identify(userId | null)` links the visitor to your own user id for retention cohorts; the server stores
|
|
23
|
+
only a hash of it.
|
|
24
|
+
|
|
25
|
+
## 2.4.0
|
|
26
|
+
|
|
27
|
+
- Visitors are identified server-side: the SDK no longer creates a session id and stores nothing in the browser
|
|
28
|
+
besides the replay key in `sessionStorage`.
|
|
29
|
+
- Replay sampling is decided by the server.
|
|
30
|
+
|
|
31
|
+
## 2.3.0
|
|
32
|
+
|
|
33
|
+
- UTM attribution: only `utm_*` query params are sent.
|
|
34
|
+
- Web Vitals (LCP, INP, CLS), flushed on `visibilitychange` / `pagehide`.
|
|
35
|
+
- `tracking.track(name, data)` custom events for goals and funnels.
|
|
36
|
+
|
|
37
|
+
## 2.1.3
|
|
38
|
+
|
|
39
|
+
- Tuned the replay idle timeout and recording limit.
|
|
40
|
+
|
|
41
|
+
## 2.1.2
|
|
42
|
+
|
|
43
|
+
- Session replay keeps one recording per tab visit across route changes and reloads, closed after idle time or at the
|
|
44
|
+
size cap.
|
|
45
|
+
- Per-site replay sample rate.
|
|
46
|
+
- Skips automated browsers (`navigator.webdriver`).
|
|
47
|
+
|
|
48
|
+
## 2.1.0
|
|
49
|
+
|
|
50
|
+
- Crawler tracking: `DataxamasCrawlers` (`trackRequest`, `track`), `isCrawlerUserAgent`, `withCrawlerTracking` and
|
|
51
|
+
`createExpressCrawlerMiddleware`.
|
|
52
|
+
- Default base URL is now `https://dataxamas.com`.
|
|
53
|
+
|
|
54
|
+
## 2.0.1
|
|
55
|
+
|
|
56
|
+
- Packaging fix, no code changes.
|
|
57
|
+
|
|
58
|
+
## 2.0.0
|
|
59
|
+
|
|
60
|
+
- Renamed to `@dataxamas/tracker-js` (was `dataxamas`).
|
|
61
|
+
- `apiKey` replaces `websiteId`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dataxamas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
CHANGED
|
@@ -314,40 +314,43 @@ var DataxamasLogs = class {
|
|
|
314
314
|
}
|
|
315
315
|
};
|
|
316
316
|
|
|
317
|
+
// ../tracker-shared/src/constants.ts
|
|
318
|
+
var MAX_RECORDING_MS = 30 * 1e3;
|
|
319
|
+
var MAX_EVENTS_PER_RECORDING = 15e3;
|
|
320
|
+
var MAX_BYTES_PER_RECORDING = 5 * 1024 * 1024;
|
|
321
|
+
var REPLAY_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
322
|
+
|
|
317
323
|
// src/tracking.ts
|
|
318
324
|
var QUEUE_DELAY_MS = 4e3;
|
|
319
325
|
var STORAGE_PREFIX = "@dataxamas";
|
|
320
326
|
var REPLAY_FLUSH_SIZE = 100;
|
|
321
327
|
var REPLAY_FLUSH_INTERVAL_MS = 5e3;
|
|
322
328
|
var CAP_CHECK_MS = 1e3;
|
|
323
|
-
var MAX_RECORDING_MS = 30 * 60 * 1e3;
|
|
324
|
-
var MAX_EVENTS_PER_RECORDING = 5e4;
|
|
325
|
-
var REPLAY_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
326
329
|
var ACTIVITY_SAVE_THROTTLE_MS = 5e3;
|
|
327
330
|
var REPLAY_RECORDING_KEY = `${STORAGE_PREFIX}/replay-recording`;
|
|
328
331
|
function isStoredRecording(value) {
|
|
329
332
|
if (typeof value !== "object" || value === null) return false;
|
|
330
333
|
const stored = value;
|
|
331
|
-
return typeof stored.id === "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";
|
|
334
|
+
return typeof stored.id === "string" && typeof stored.startedAt === "string" && typeof stored.startedAtMs === "number" && typeof stored.seq === "number" && typeof stored.events === "number" && typeof stored.bytes === "number" && typeof stored.lastActivityAt === "number" && typeof stored.capped === "boolean";
|
|
332
335
|
}
|
|
333
336
|
var KEEPALIVE_LIMIT_BYTES = 60 * 1024;
|
|
334
337
|
var DataxamasTracking = class {
|
|
335
338
|
constructor(options) {
|
|
336
339
|
/**
|
|
337
340
|
* Primeiro gesto liga o gravador; os seguintes só o religam se ele tiver parado (aba que foi
|
|
338
|
-
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto
|
|
339
|
-
*
|
|
341
|
+
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto a visita não grava mais;
|
|
342
|
+
* só um gesto depois de muito tempo parado abre uma gravação nova.
|
|
340
343
|
*/
|
|
341
344
|
this.markReplayActivity = () => {
|
|
342
345
|
if (this.replayHalted || this.cutoffPending) return;
|
|
343
346
|
if (document.visibilityState === "hidden") return;
|
|
344
347
|
const now = Date.now();
|
|
345
|
-
if (
|
|
348
|
+
if (now - this.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
|
|
346
349
|
if (this.isRecording()) this.stopReplayRecorder(false);
|
|
347
350
|
this.rotateRecording();
|
|
348
351
|
}
|
|
349
352
|
this.touchActivity(now);
|
|
350
|
-
if (this.isRecording()) return;
|
|
353
|
+
if (this.replayCapped || this.isRecording()) return;
|
|
351
354
|
this.startReplayRecorder();
|
|
352
355
|
};
|
|
353
356
|
if (!options.apiKey) {
|
|
@@ -381,6 +384,7 @@ var DataxamasTracking = class {
|
|
|
381
384
|
this.recordingStartedAtMs = 0;
|
|
382
385
|
this.recordingSeq = 0;
|
|
383
386
|
this.recordingEvents = 0;
|
|
387
|
+
this.recordingBytes = 0;
|
|
384
388
|
this.lastActivityAt = 0;
|
|
385
389
|
this.lastActivitySavedAt = 0;
|
|
386
390
|
}
|
|
@@ -571,6 +575,8 @@ var DataxamasTracking = class {
|
|
|
571
575
|
webVitals.onLCP(queue);
|
|
572
576
|
webVitals.onINP(queue);
|
|
573
577
|
webVitals.onCLS(queue);
|
|
578
|
+
webVitals.onFCP(queue);
|
|
579
|
+
webVitals.onTTFB(queue);
|
|
574
580
|
document.addEventListener("visibilitychange", () => {
|
|
575
581
|
if (document.visibilityState === "hidden") this.flushVitals();
|
|
576
582
|
});
|
|
@@ -589,7 +595,7 @@ var DataxamasTracking = class {
|
|
|
589
595
|
return Boolean(this.stopRrweb);
|
|
590
596
|
}
|
|
591
597
|
exceededReplayCaps() {
|
|
592
|
-
return this.recordingEvents >= MAX_EVENTS_PER_RECORDING || Date.now() - this.recordingStartedAtMs >= MAX_RECORDING_MS;
|
|
598
|
+
return this.recordingEvents >= MAX_EVENTS_PER_RECORDING || this.recordingBytes >= MAX_BYTES_PER_RECORDING || Date.now() - this.recordingStartedAtMs >= MAX_RECORDING_MS;
|
|
593
599
|
}
|
|
594
600
|
loadRecording() {
|
|
595
601
|
try {
|
|
@@ -607,6 +613,7 @@ var DataxamasTracking = class {
|
|
|
607
613
|
startedAtMs: this.recordingStartedAtMs,
|
|
608
614
|
seq: this.recordingSeq,
|
|
609
615
|
events: this.recordingEvents,
|
|
616
|
+
bytes: this.recordingBytes,
|
|
610
617
|
lastActivityAt: this.lastActivityAt,
|
|
611
618
|
capped: this.replayCapped
|
|
612
619
|
};
|
|
@@ -622,22 +629,27 @@ var DataxamasTracking = class {
|
|
|
622
629
|
this.recordingStartedAtMs = Date.now();
|
|
623
630
|
this.recordingSeq = 0;
|
|
624
631
|
this.recordingEvents = 0;
|
|
632
|
+
this.recordingBytes = 0;
|
|
625
633
|
this.lastActivityAt = Date.now();
|
|
626
634
|
this.saveRecording();
|
|
627
635
|
}
|
|
628
|
-
/**
|
|
636
|
+
/**
|
|
637
|
+
* Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa.
|
|
638
|
+
* Se ela já bateu o teto, segue sem gravar.
|
|
639
|
+
*/
|
|
629
640
|
resumeOrRotateRecording() {
|
|
630
641
|
const stored = this.loadRecording();
|
|
631
|
-
if (!stored ||
|
|
642
|
+
if (!stored || Date.now() - stored.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
|
|
632
643
|
this.rotateRecording();
|
|
633
644
|
return;
|
|
634
645
|
}
|
|
635
|
-
this.replayCapped =
|
|
646
|
+
this.replayCapped = stored.capped;
|
|
636
647
|
this.recordingId = stored.id;
|
|
637
648
|
this.recordingStartedAt = stored.startedAt;
|
|
638
649
|
this.recordingStartedAtMs = stored.startedAtMs;
|
|
639
650
|
this.recordingSeq = stored.seq;
|
|
640
651
|
this.recordingEvents = stored.events;
|
|
652
|
+
this.recordingBytes = stored.bytes;
|
|
641
653
|
this.lastActivityAt = stored.lastActivityAt;
|
|
642
654
|
}
|
|
643
655
|
touchActivity(now) {
|
|
@@ -659,6 +671,7 @@ var DataxamasTracking = class {
|
|
|
659
671
|
...this.measureReplay(),
|
|
660
672
|
events
|
|
661
673
|
});
|
|
674
|
+
this.recordingBytes += payload.length;
|
|
662
675
|
this.saveRecording();
|
|
663
676
|
void this.sendReplayBatch(payload, urgent);
|
|
664
677
|
}
|
|
@@ -716,8 +729,8 @@ var DataxamasTracking = class {
|
|
|
716
729
|
}
|
|
717
730
|
}
|
|
718
731
|
/**
|
|
719
|
-
* Bateu o teto: encerra a gravação e
|
|
720
|
-
*
|
|
732
|
+
* Bateu o teto: encerra a gravação e a visita não grava mais, em vez de emendar um clipe atrás do
|
|
733
|
+
* outro.
|
|
721
734
|
*/
|
|
722
735
|
scheduleReplayCutoff() {
|
|
723
736
|
if (this.cutoffPending) return;
|
|
@@ -732,7 +745,7 @@ var DataxamasTracking = class {
|
|
|
732
745
|
setupReplayLifecycle() {
|
|
733
746
|
if (this.lifecycleReady) return;
|
|
734
747
|
this.lifecycleReady = true;
|
|
735
|
-
for (const type of ["pointerdown", "keydown", "wheel", "touchstart"
|
|
748
|
+
for (const type of ["pointerdown", "keydown", "wheel", "touchstart"]) {
|
|
736
749
|
window.addEventListener(type, this.markReplayActivity, { passive: true, capture: true });
|
|
737
750
|
}
|
|
738
751
|
setInterval(() => {
|
|
@@ -762,9 +775,17 @@ var DataxamasTracking = class {
|
|
|
762
775
|
}
|
|
763
776
|
if (this.isRecording() || this.replayHalted) return;
|
|
764
777
|
if (!this.recordingId) this.rotateRecording();
|
|
778
|
+
if (this.recordingSeq === 0) {
|
|
779
|
+
this.recordingStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
780
|
+
this.recordingStartedAtMs = Date.now();
|
|
781
|
+
}
|
|
765
782
|
this.snapshotFlushed = false;
|
|
766
783
|
this.stopRrweb = this.rrwebRecord({
|
|
767
784
|
emit: (event) => {
|
|
785
|
+
if (this.exceededReplayCaps()) {
|
|
786
|
+
this.scheduleReplayCutoff();
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
768
789
|
this.replayBuffer.push(event);
|
|
769
790
|
this.recordingEvents++;
|
|
770
791
|
if (!this.snapshotFlushed && event.type === 2) {
|
|
@@ -772,10 +793,6 @@ var DataxamasTracking = class {
|
|
|
772
793
|
this.flushReplay(false);
|
|
773
794
|
return;
|
|
774
795
|
}
|
|
775
|
-
if (this.exceededReplayCaps()) {
|
|
776
|
-
this.scheduleReplayCutoff();
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
796
|
if (this.replayBuffer.length >= REPLAY_FLUSH_SIZE) this.flushReplay(false);
|
|
780
797
|
},
|
|
781
798
|
maskAllInputs: true,
|
package/dist/index.d.cts
CHANGED
|
@@ -134,6 +134,7 @@ declare class DataxamasTracking {
|
|
|
134
134
|
private recordingStartedAtMs;
|
|
135
135
|
private recordingSeq;
|
|
136
136
|
private recordingEvents;
|
|
137
|
+
private recordingBytes;
|
|
137
138
|
private lastActivityAt;
|
|
138
139
|
private lastActivitySavedAt;
|
|
139
140
|
constructor(options: DataxamasTrackingOptions);
|
|
@@ -157,7 +158,10 @@ declare class DataxamasTracking {
|
|
|
157
158
|
private loadRecording;
|
|
158
159
|
private saveRecording;
|
|
159
160
|
private rotateRecording;
|
|
160
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa.
|
|
163
|
+
* Se ela já bateu o teto, segue sem gravar.
|
|
164
|
+
*/
|
|
161
165
|
private resumeOrRotateRecording;
|
|
162
166
|
private touchActivity;
|
|
163
167
|
private flushReplay;
|
|
@@ -169,14 +173,14 @@ declare class DataxamasTracking {
|
|
|
169
173
|
private sendReplayBatch;
|
|
170
174
|
private stopReplayRecorder;
|
|
171
175
|
/**
|
|
172
|
-
* Bateu o teto: encerra a gravação e
|
|
173
|
-
*
|
|
176
|
+
* Bateu o teto: encerra a gravação e a visita não grava mais, em vez de emendar um clipe atrás do
|
|
177
|
+
* outro.
|
|
174
178
|
*/
|
|
175
179
|
private scheduleReplayCutoff;
|
|
176
180
|
/**
|
|
177
181
|
* Primeiro gesto liga o gravador; os seguintes só o religam se ele tiver parado (aba que foi
|
|
178
|
-
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto
|
|
179
|
-
*
|
|
182
|
+
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto a visita não grava mais;
|
|
183
|
+
* só um gesto depois de muito tempo parado abre uma gravação nova.
|
|
180
184
|
*/
|
|
181
185
|
private markReplayActivity;
|
|
182
186
|
private setupReplayLifecycle;
|
package/dist/index.d.ts
CHANGED
|
@@ -134,6 +134,7 @@ declare class DataxamasTracking {
|
|
|
134
134
|
private recordingStartedAtMs;
|
|
135
135
|
private recordingSeq;
|
|
136
136
|
private recordingEvents;
|
|
137
|
+
private recordingBytes;
|
|
137
138
|
private lastActivityAt;
|
|
138
139
|
private lastActivitySavedAt;
|
|
139
140
|
constructor(options: DataxamasTrackingOptions);
|
|
@@ -157,7 +158,10 @@ declare class DataxamasTracking {
|
|
|
157
158
|
private loadRecording;
|
|
158
159
|
private saveRecording;
|
|
159
160
|
private rotateRecording;
|
|
160
|
-
/**
|
|
161
|
+
/**
|
|
162
|
+
* Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa.
|
|
163
|
+
* Se ela já bateu o teto, segue sem gravar.
|
|
164
|
+
*/
|
|
161
165
|
private resumeOrRotateRecording;
|
|
162
166
|
private touchActivity;
|
|
163
167
|
private flushReplay;
|
|
@@ -169,14 +173,14 @@ declare class DataxamasTracking {
|
|
|
169
173
|
private sendReplayBatch;
|
|
170
174
|
private stopReplayRecorder;
|
|
171
175
|
/**
|
|
172
|
-
* Bateu o teto: encerra a gravação e
|
|
173
|
-
*
|
|
176
|
+
* Bateu o teto: encerra a gravação e a visita não grava mais, em vez de emendar um clipe atrás do
|
|
177
|
+
* outro.
|
|
174
178
|
*/
|
|
175
179
|
private scheduleReplayCutoff;
|
|
176
180
|
/**
|
|
177
181
|
* Primeiro gesto liga o gravador; os seguintes só o religam se ele tiver parado (aba que foi
|
|
178
|
-
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto
|
|
179
|
-
*
|
|
182
|
+
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto a visita não grava mais;
|
|
183
|
+
* só um gesto depois de muito tempo parado abre uma gravação nova.
|
|
180
184
|
*/
|
|
181
185
|
private markReplayActivity;
|
|
182
186
|
private setupReplayLifecycle;
|
package/dist/index.js
CHANGED
|
@@ -314,40 +314,43 @@ var DataxamasLogs = class {
|
|
|
314
314
|
}
|
|
315
315
|
};
|
|
316
316
|
|
|
317
|
+
// ../tracker-shared/src/constants.ts
|
|
318
|
+
var MAX_RECORDING_MS = 30 * 1e3;
|
|
319
|
+
var MAX_EVENTS_PER_RECORDING = 15e3;
|
|
320
|
+
var MAX_BYTES_PER_RECORDING = 5 * 1024 * 1024;
|
|
321
|
+
var REPLAY_IDLE_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
322
|
+
|
|
317
323
|
// src/tracking.ts
|
|
318
324
|
var QUEUE_DELAY_MS = 4e3;
|
|
319
325
|
var STORAGE_PREFIX = "@dataxamas";
|
|
320
326
|
var REPLAY_FLUSH_SIZE = 100;
|
|
321
327
|
var REPLAY_FLUSH_INTERVAL_MS = 5e3;
|
|
322
328
|
var CAP_CHECK_MS = 1e3;
|
|
323
|
-
var MAX_RECORDING_MS = 30 * 60 * 1e3;
|
|
324
|
-
var MAX_EVENTS_PER_RECORDING = 5e4;
|
|
325
|
-
var REPLAY_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
|
|
326
329
|
var ACTIVITY_SAVE_THROTTLE_MS = 5e3;
|
|
327
330
|
var REPLAY_RECORDING_KEY = `${STORAGE_PREFIX}/replay-recording`;
|
|
328
331
|
function isStoredRecording(value) {
|
|
329
332
|
if (typeof value !== "object" || value === null) return false;
|
|
330
333
|
const stored = value;
|
|
331
|
-
return typeof stored.id === "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";
|
|
334
|
+
return typeof stored.id === "string" && typeof stored.startedAt === "string" && typeof stored.startedAtMs === "number" && typeof stored.seq === "number" && typeof stored.events === "number" && typeof stored.bytes === "number" && typeof stored.lastActivityAt === "number" && typeof stored.capped === "boolean";
|
|
332
335
|
}
|
|
333
336
|
var KEEPALIVE_LIMIT_BYTES = 60 * 1024;
|
|
334
337
|
var DataxamasTracking = class {
|
|
335
338
|
constructor(options) {
|
|
336
339
|
/**
|
|
337
340
|
* Primeiro gesto liga o gravador; os seguintes só o religam se ele tiver parado (aba que foi
|
|
338
|
-
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto
|
|
339
|
-
*
|
|
341
|
+
* para segundo plano, reload), no mesmo `recordingId`. Depois do teto a visita não grava mais;
|
|
342
|
+
* só um gesto depois de muito tempo parado abre uma gravação nova.
|
|
340
343
|
*/
|
|
341
344
|
this.markReplayActivity = () => {
|
|
342
345
|
if (this.replayHalted || this.cutoffPending) return;
|
|
343
346
|
if (document.visibilityState === "hidden") return;
|
|
344
347
|
const now = Date.now();
|
|
345
|
-
if (
|
|
348
|
+
if (now - this.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
|
|
346
349
|
if (this.isRecording()) this.stopReplayRecorder(false);
|
|
347
350
|
this.rotateRecording();
|
|
348
351
|
}
|
|
349
352
|
this.touchActivity(now);
|
|
350
|
-
if (this.isRecording()) return;
|
|
353
|
+
if (this.replayCapped || this.isRecording()) return;
|
|
351
354
|
this.startReplayRecorder();
|
|
352
355
|
};
|
|
353
356
|
if (!options.apiKey) {
|
|
@@ -381,6 +384,7 @@ var DataxamasTracking = class {
|
|
|
381
384
|
this.recordingStartedAtMs = 0;
|
|
382
385
|
this.recordingSeq = 0;
|
|
383
386
|
this.recordingEvents = 0;
|
|
387
|
+
this.recordingBytes = 0;
|
|
384
388
|
this.lastActivityAt = 0;
|
|
385
389
|
this.lastActivitySavedAt = 0;
|
|
386
390
|
}
|
|
@@ -571,6 +575,8 @@ var DataxamasTracking = class {
|
|
|
571
575
|
webVitals.onLCP(queue);
|
|
572
576
|
webVitals.onINP(queue);
|
|
573
577
|
webVitals.onCLS(queue);
|
|
578
|
+
webVitals.onFCP(queue);
|
|
579
|
+
webVitals.onTTFB(queue);
|
|
574
580
|
document.addEventListener("visibilitychange", () => {
|
|
575
581
|
if (document.visibilityState === "hidden") this.flushVitals();
|
|
576
582
|
});
|
|
@@ -589,7 +595,7 @@ var DataxamasTracking = class {
|
|
|
589
595
|
return Boolean(this.stopRrweb);
|
|
590
596
|
}
|
|
591
597
|
exceededReplayCaps() {
|
|
592
|
-
return this.recordingEvents >= MAX_EVENTS_PER_RECORDING || Date.now() - this.recordingStartedAtMs >= MAX_RECORDING_MS;
|
|
598
|
+
return this.recordingEvents >= MAX_EVENTS_PER_RECORDING || this.recordingBytes >= MAX_BYTES_PER_RECORDING || Date.now() - this.recordingStartedAtMs >= MAX_RECORDING_MS;
|
|
593
599
|
}
|
|
594
600
|
loadRecording() {
|
|
595
601
|
try {
|
|
@@ -607,6 +613,7 @@ var DataxamasTracking = class {
|
|
|
607
613
|
startedAtMs: this.recordingStartedAtMs,
|
|
608
614
|
seq: this.recordingSeq,
|
|
609
615
|
events: this.recordingEvents,
|
|
616
|
+
bytes: this.recordingBytes,
|
|
610
617
|
lastActivityAt: this.lastActivityAt,
|
|
611
618
|
capped: this.replayCapped
|
|
612
619
|
};
|
|
@@ -622,22 +629,27 @@ var DataxamasTracking = class {
|
|
|
622
629
|
this.recordingStartedAtMs = Date.now();
|
|
623
630
|
this.recordingSeq = 0;
|
|
624
631
|
this.recordingEvents = 0;
|
|
632
|
+
this.recordingBytes = 0;
|
|
625
633
|
this.lastActivityAt = Date.now();
|
|
626
634
|
this.saveRecording();
|
|
627
635
|
}
|
|
628
|
-
/**
|
|
636
|
+
/**
|
|
637
|
+
* Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa.
|
|
638
|
+
* Se ela já bateu o teto, segue sem gravar.
|
|
639
|
+
*/
|
|
629
640
|
resumeOrRotateRecording() {
|
|
630
641
|
const stored = this.loadRecording();
|
|
631
|
-
if (!stored ||
|
|
642
|
+
if (!stored || Date.now() - stored.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
|
|
632
643
|
this.rotateRecording();
|
|
633
644
|
return;
|
|
634
645
|
}
|
|
635
|
-
this.replayCapped =
|
|
646
|
+
this.replayCapped = stored.capped;
|
|
636
647
|
this.recordingId = stored.id;
|
|
637
648
|
this.recordingStartedAt = stored.startedAt;
|
|
638
649
|
this.recordingStartedAtMs = stored.startedAtMs;
|
|
639
650
|
this.recordingSeq = stored.seq;
|
|
640
651
|
this.recordingEvents = stored.events;
|
|
652
|
+
this.recordingBytes = stored.bytes;
|
|
641
653
|
this.lastActivityAt = stored.lastActivityAt;
|
|
642
654
|
}
|
|
643
655
|
touchActivity(now) {
|
|
@@ -659,6 +671,7 @@ var DataxamasTracking = class {
|
|
|
659
671
|
...this.measureReplay(),
|
|
660
672
|
events
|
|
661
673
|
});
|
|
674
|
+
this.recordingBytes += payload.length;
|
|
662
675
|
this.saveRecording();
|
|
663
676
|
void this.sendReplayBatch(payload, urgent);
|
|
664
677
|
}
|
|
@@ -716,8 +729,8 @@ var DataxamasTracking = class {
|
|
|
716
729
|
}
|
|
717
730
|
}
|
|
718
731
|
/**
|
|
719
|
-
* Bateu o teto: encerra a gravação e
|
|
720
|
-
*
|
|
732
|
+
* Bateu o teto: encerra a gravação e a visita não grava mais, em vez de emendar um clipe atrás do
|
|
733
|
+
* outro.
|
|
721
734
|
*/
|
|
722
735
|
scheduleReplayCutoff() {
|
|
723
736
|
if (this.cutoffPending) return;
|
|
@@ -732,7 +745,7 @@ var DataxamasTracking = class {
|
|
|
732
745
|
setupReplayLifecycle() {
|
|
733
746
|
if (this.lifecycleReady) return;
|
|
734
747
|
this.lifecycleReady = true;
|
|
735
|
-
for (const type of ["pointerdown", "keydown", "wheel", "touchstart"
|
|
748
|
+
for (const type of ["pointerdown", "keydown", "wheel", "touchstart"]) {
|
|
736
749
|
window.addEventListener(type, this.markReplayActivity, { passive: true, capture: true });
|
|
737
750
|
}
|
|
738
751
|
setInterval(() => {
|
|
@@ -762,9 +775,17 @@ var DataxamasTracking = class {
|
|
|
762
775
|
}
|
|
763
776
|
if (this.isRecording() || this.replayHalted) return;
|
|
764
777
|
if (!this.recordingId) this.rotateRecording();
|
|
778
|
+
if (this.recordingSeq === 0) {
|
|
779
|
+
this.recordingStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
780
|
+
this.recordingStartedAtMs = Date.now();
|
|
781
|
+
}
|
|
765
782
|
this.snapshotFlushed = false;
|
|
766
783
|
this.stopRrweb = this.rrwebRecord({
|
|
767
784
|
emit: (event) => {
|
|
785
|
+
if (this.exceededReplayCaps()) {
|
|
786
|
+
this.scheduleReplayCutoff();
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
768
789
|
this.replayBuffer.push(event);
|
|
769
790
|
this.recordingEvents++;
|
|
770
791
|
if (!this.snapshotFlushed && event.type === 2) {
|
|
@@ -772,10 +793,6 @@ var DataxamasTracking = class {
|
|
|
772
793
|
this.flushReplay(false);
|
|
773
794
|
return;
|
|
774
795
|
}
|
|
775
|
-
if (this.exceededReplayCaps()) {
|
|
776
|
-
this.scheduleReplayCutoff();
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
796
|
if (this.replayBuffer.length >= REPLAY_FLUSH_SIZE) this.flushReplay(false);
|
|
780
797
|
},
|
|
781
798
|
maskAllInputs: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dataxamas/tracker-js",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Node.js SDK for Dataxamas Analytics API",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"module": "./dist/index.js",
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
16
|
"files": [
|
|
17
|
-
"dist"
|
|
17
|
+
"dist",
|
|
18
|
+
"CHANGELOG.md"
|
|
18
19
|
],
|
|
19
20
|
"exports": {
|
|
20
21
|
".": {
|
|
@@ -26,14 +27,11 @@
|
|
|
26
27
|
"types": "./dist/index.d.cts",
|
|
27
28
|
"default": "./dist/index.cjs"
|
|
28
29
|
}
|
|
29
|
-
}
|
|
30
|
-
"./script": "./src/script/bundle.ts",
|
|
31
|
-
"./script/vite": "./src/script/vite.ts",
|
|
32
|
-
"./script/virtual": "./src/script/virtual.d.ts"
|
|
30
|
+
}
|
|
33
31
|
},
|
|
34
32
|
"scripts": {
|
|
35
33
|
"build": "tsup",
|
|
36
|
-
"types": "tsc --noEmit
|
|
34
|
+
"types": "tsc --noEmit",
|
|
37
35
|
"lint": "biome lint",
|
|
38
36
|
"lint:fix": "biome lint --write",
|
|
39
37
|
"check": "biome check",
|
|
@@ -48,13 +46,12 @@
|
|
|
48
46
|
],
|
|
49
47
|
"devDependencies": {
|
|
50
48
|
"@dataxamas/biome-config": "workspace:*",
|
|
49
|
+
"@dataxamas/tracker-shared": "workspace:*",
|
|
51
50
|
"@dataxamas/tsconfig": "workspace:*",
|
|
52
51
|
"@types/node": "^26.1.0",
|
|
53
|
-
"esbuild": "^0.28.1",
|
|
54
52
|
"rrweb": "^2.1.0",
|
|
55
53
|
"tsup": "^8.5.1",
|
|
56
54
|
"typescript": "^5",
|
|
57
|
-
"vite": "^8.0.16",
|
|
58
55
|
"web-vitals": "^6.2.3"
|
|
59
56
|
},
|
|
60
57
|
"dependencies": {
|