@dataxamas/tracker-js 2.1.0 → 2.1.3

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 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 = 10 * 60 * 1e3;
322
- var MAX_EVENTS_PER_RECORDING = 2e4;
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 da pageview liga o gravador; os seguintes só o religam se ele tiver parado
329
- * (aba que foi para segundo plano, por exemplo), sempre no mesmo `recordingId`.
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.replayCapped || this.isRecording()) return;
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.stopReplayRecorder(false);
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
- if (REPLAY_SAMPLE_RATE >= 1) return true;
534
- if (REPLAY_SAMPLE_RATE <= 0) return false;
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 stored = window.localStorage.getItem(`${STORAGE_PREFIX}/replay-sample`);
537
- if (stored === "1") return true;
538
- if (stored === "0") return false;
539
- const sampled = Math.random() < REPLAY_SAMPLE_RATE;
540
- window.localStorage.setItem(`${STORAGE_PREFIX}/replay-sample`, sampled ? "1" : "0");
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() < REPLAY_SAMPLE_RATE;
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 pageview, em vez de emendar um
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 pageview, em vez de emendar um
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 da pageview liga o gravador; os seguintes só o religam se ele tiver parado
169
- * (aba que foi para segundo plano, por exemplo), sempre no mesmo `recordingId`.
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 pageview, em vez de emendar um
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 da pageview liga o gravador; os seguintes só o religam se ele tiver parado
169
- * (aba que foi para segundo plano, por exemplo), sempre no mesmo `recordingId`.
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 = 10 * 60 * 1e3;
322
- var MAX_EVENTS_PER_RECORDING = 2e4;
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 da pageview liga o gravador; os seguintes só o religam se ele tiver parado
329
- * (aba que foi para segundo plano, por exemplo), sempre no mesmo `recordingId`.
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.replayCapped || this.isRecording()) return;
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.stopReplayRecorder(false);
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
- if (REPLAY_SAMPLE_RATE >= 1) return true;
534
- if (REPLAY_SAMPLE_RATE <= 0) return false;
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 stored = window.localStorage.getItem(`${STORAGE_PREFIX}/replay-sample`);
537
- if (stored === "1") return true;
538
- if (stored === "0") return false;
539
- const sampled = Math.random() < REPLAY_SAMPLE_RATE;
540
- window.localStorage.setItem(`${STORAGE_PREFIX}/replay-sample`, sampled ? "1" : "0");
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() < REPLAY_SAMPLE_RATE;
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 pageview, em vez de emendar um
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataxamas/tracker-js",
3
- "version": "2.1.0",
3
+ "version": "2.1.3",
4
4
  "type": "module",
5
5
  "description": "Node.js SDK for Dataxamas Analytics API",
6
6
  "license": "MIT",