@dataxamas/tracker-js 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -201,7 +201,7 @@ Begin collecting. Waits for `DOMContentLoaded` if the document is still loading,
201
201
  - **`CLICK`** — element `tagName`, `text`, `href`, `id`, `classes`, plus heat-map coordinates (viewport ratios, scroll offsets, layout fingerprint) when `heatMapEnabled`.
202
202
  - **`SCROLL`** — scroll-depth percentage (debounced).
203
203
 
204
- Events are queued and flushed to `POST /api/v1/events` with a 4s spacing. Every request sends `Authorization: Bearer <apiKey>`. The key is visible to anyone who opens the page, and it can also read events through `GET /api/v1/events` — treat it as public and rotate it from the dashboard if abused. A visitor session id is generated once and stored in `localStorage` under `@dataxamas/session`.
204
+ Events are queued and flushed to `POST /api/v1/events` with a 4s spacing. Every request sends `Authorization: Bearer <apiKey>`. The key is visible to anyone who opens the page, and it can also read events through `GET /api/v1/events` — treat it as public and rotate it from the dashboard if abused. Nothing is stored in the browser to identify the visitor: the server derives the session id from `sha256(daily salt + IP + user agent + websiteId)`, and the salt is replaced every day.
205
205
 
206
206
  If `replayEnabled` is on and the server config allows it, session replay starts: `rrweb` is lazy-imported and recordings are sent to `POST /api/v1/replay`.
207
207
 
@@ -250,7 +250,7 @@ Exported for use in your own typings:
250
250
  ## Notes
251
251
 
252
252
  - **Default endpoint:** `https://dataxamas.com`. Override with `baseUrl`.
253
- - **Session storage key:** `localStorage["@dataxamas/session"]`.
253
+ - **Visitor identity:** computed server-side, no cookies or `localStorage`. Only replay touches the browser, in `sessionStorage["@dataxamas/replay-recording"]`, and only for sampled visitors.
254
254
  - **Event flush spacing:** 4 seconds between queued analytics events.
255
255
  - **Replay masking classes:** `dx-mask` (text) and `dx-block` (element); all inputs masked by default.
256
256
  - **Bundle:** `rrweb` is bundled but split into its own async chunk, loaded only when replay starts — the server/import path never touches it.
package/dist/index.cjs CHANGED
@@ -328,7 +328,7 @@ var REPLAY_RECORDING_KEY = `${STORAGE_PREFIX}/replay-recording`;
328
328
  function isStoredRecording(value) {
329
329
  if (typeof value !== "object" || value === null) return false;
330
330
  const stored = value;
331
- 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";
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";
332
332
  }
333
333
  var KEEPALIVE_LIMIT_BYTES = 60 * 1024;
334
334
  var DataxamasTracking = class {
@@ -356,14 +356,14 @@ var DataxamasTracking = class {
356
356
  this.apiKey = options.apiKey;
357
357
  this.baseUrl = _nullishCoalesce(options.baseUrl, () => ( BASE_URL));
358
358
  this.allowLocalhost = _nullishCoalesce(options.allowLocalhost, () => ( false));
359
- this.sessionId = null;
359
+ this.started = false;
360
360
  this.currentPage = isBrowser() ? window.location.pathname : "";
361
361
  this.heatMapEnabled = _nullishCoalesce(options.heatMapEnabled, () => ( true));
362
362
  this.replayEnabled = _nullishCoalesce(options.replayEnabled, () => ( true));
363
363
  this.webVitalsEnabled = _nullishCoalesce(options.webVitalsEnabled, () => ( true));
364
364
  this.pendingVitals = /* @__PURE__ */ new Map();
365
365
  this.vitalsPath = "";
366
- this.replaySampleRate = 1;
366
+ this.replaySampled = false;
367
367
  this.eventQueue = [];
368
368
  this.isProcessing = false;
369
369
  this.rrwebRecord = null;
@@ -386,21 +386,6 @@ var DataxamasTracking = class {
386
386
  isLocalhost() {
387
387
  return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
388
388
  }
389
- getSessionId() {
390
- try {
391
- return window.localStorage.getItem(`${STORAGE_PREFIX}/session`);
392
- } catch (_) {
393
- return null;
394
- }
395
- }
396
- createSessionId() {
397
- const id = crypto.randomUUID();
398
- try {
399
- window.localStorage.setItem(`${STORAGE_PREFIX}/session`, id);
400
- } catch (_) {
401
- }
402
- return id;
403
- }
404
389
  async processQueue() {
405
390
  if (this.isProcessing || this.eventQueue.length === 0) return;
406
391
  this.isProcessing = true;
@@ -436,13 +421,12 @@ var DataxamasTracking = class {
436
421
  path = this.currentPage,
437
422
  data = {}
438
423
  }) {
439
- if (!this.sessionId) return;
424
+ if (!this.started) return;
440
425
  this.eventQueue.push({
441
426
  name,
442
427
  path,
443
428
  referrer: document.referrer,
444
429
  query: name === "PAGE_VIEW" ? this.utmQuery() : void 0,
445
- sessionId: this.sessionId,
446
430
  data
447
431
  });
448
432
  await this.processQueue();
@@ -468,9 +452,7 @@ var DataxamasTracking = class {
468
452
  const cfg = await res.json();
469
453
  if (typeof cfg.heatMapEnabled === "boolean") this.heatMapEnabled = cfg.heatMapEnabled;
470
454
  if (typeof cfg.replayEnabled === "boolean") this.replayEnabled = cfg.replayEnabled;
471
- if (typeof cfg.replaySampleRate === "number" && cfg.replaySampleRate >= 0 && cfg.replaySampleRate <= 1) {
472
- this.replaySampleRate = cfg.replaySampleRate;
473
- }
455
+ if (typeof cfg.replaySampled === "boolean") this.replaySampled = cfg.replaySampled;
474
456
  } catch (_) {
475
457
  }
476
458
  }
@@ -551,14 +533,13 @@ var DataxamasTracking = class {
551
533
  });
552
534
  }
553
535
  flushVitals() {
554
- if (!this.sessionId || this.pendingVitals.size === 0) return;
536
+ if (this.pendingVitals.size === 0) return;
555
537
  const metrics = Array.from(this.pendingVitals.values());
556
538
  this.pendingVitals.clear();
557
539
  window.fetch(`${this.baseUrl}/api/v1/vitals`, {
558
540
  method: "POST",
559
541
  headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
560
542
  body: JSON.stringify({
561
- sessionId: this.sessionId,
562
543
  path: this.vitalsPath,
563
544
  viewportWidth: window.innerWidth,
564
545
  metrics
@@ -602,27 +583,6 @@ var DataxamasTracking = class {
602
583
  const layoutFingerprint = `${this.currentPage}:${Math.round(pageWidth / 100)}x${Math.round(pageHeight / 100)}`;
603
584
  return { viewportWidth, viewportHeight, pageWidth, pageHeight, layoutFingerprint };
604
585
  }
605
- /**
606
- * Sorteio por visitante, não por gravação: quem entrou no sorteio tem a sessão inteira, em vez
607
- * de um replay picotado que não dá para assistir.
608
- */
609
- isReplaySampled() {
610
- const rate = this.replaySampleRate;
611
- if (rate >= 1) return true;
612
- if (rate <= 0) return false;
613
- const key = `${STORAGE_PREFIX}/replay-sample`;
614
- try {
615
- const [storedRate, storedDecision] = (_nullishCoalesce(window.localStorage.getItem(key), () => ( ""))).split(":");
616
- if (Number(storedRate) === rate && (storedDecision === "1" || storedDecision === "0")) {
617
- return storedDecision === "1";
618
- }
619
- const sampled = Math.random() < rate;
620
- window.localStorage.setItem(key, `${rate}:${sampled ? "1" : "0"}`);
621
- return sampled;
622
- } catch (_) {
623
- return Math.random() < rate;
624
- }
625
- }
626
586
  isRecording() {
627
587
  return Boolean(this.stopRrweb);
628
588
  }
@@ -638,10 +598,9 @@ var DataxamasTracking = class {
638
598
  }
639
599
  }
640
600
  saveRecording() {
641
- if (!this.sessionId || !this.recordingId || !this.recordingStartedAt) return;
601
+ if (!this.recordingId || !this.recordingStartedAt) return;
642
602
  const stored = {
643
603
  id: this.recordingId,
644
- sessionId: this.sessionId,
645
604
  startedAt: this.recordingStartedAt,
646
605
  startedAtMs: this.recordingStartedAtMs,
647
606
  seq: this.recordingSeq,
@@ -667,7 +626,7 @@ var DataxamasTracking = class {
667
626
  /** Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa. */
668
627
  resumeOrRotateRecording() {
669
628
  const stored = this.loadRecording();
670
- if (!stored || stored.capped || stored.sessionId !== this.sessionId || Date.now() - stored.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
629
+ if (!stored || stored.capped || Date.now() - stored.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
671
630
  this.rotateRecording();
672
631
  return;
673
632
  }
@@ -690,7 +649,6 @@ var DataxamasTracking = class {
690
649
  const events = this.replayBuffer;
691
650
  this.replayBuffer = [];
692
651
  const payload = JSON.stringify({
693
- sessionId: this.sessionId,
694
652
  recordingId: this.recordingId,
695
653
  path: this.currentPage,
696
654
  startedAt: this.recordingStartedAt,
@@ -839,15 +797,15 @@ var DataxamasTracking = class {
839
797
  });
840
798
  }
841
799
  startTracking() {
842
- this.sessionId = _nullishCoalesce(this.getSessionId(), () => ( this.createSessionId()));
843
- this.resumeOrRotateRecording();
800
+ this.started = true;
844
801
  this.sendEvent({ name: "PAGE_VIEW" });
845
802
  this.trackPageChanges();
846
803
  this.trackClicks();
847
804
  this.trackScroll();
848
805
  this.trackWebVitals();
849
806
  this.fetchWebsiteConfig().then(() => {
850
- if (!this.replayEnabled || !this.isReplaySampled()) return;
807
+ if (!this.replayEnabled || !this.replaySampled) return;
808
+ this.resumeOrRotateRecording();
851
809
  this.setupReplayLifecycle();
852
810
  });
853
811
  }
package/dist/index.d.cts CHANGED
@@ -109,14 +109,14 @@ declare class DataxamasTracking {
109
109
  private apiKey;
110
110
  private baseUrl;
111
111
  private allowLocalhost;
112
- private sessionId;
112
+ private started;
113
113
  private currentPage;
114
114
  private heatMapEnabled;
115
115
  private replayEnabled;
116
116
  private webVitalsEnabled;
117
117
  private pendingVitals;
118
118
  private vitalsPath;
119
- private replaySampleRate;
119
+ private replaySampled;
120
120
  private eventQueue;
121
121
  private isProcessing;
122
122
  private rrwebRecord;
@@ -137,8 +137,6 @@ declare class DataxamasTracking {
137
137
  private lastActivitySavedAt;
138
138
  constructor(options: DataxamasTrackingOptions);
139
139
  private isLocalhost;
140
- private getSessionId;
141
- private createSessionId;
142
140
  private processQueue;
143
141
  private utmQuery;
144
142
  private sendEvent;
@@ -153,11 +151,6 @@ declare class DataxamasTracking {
153
151
  private flushVitals;
154
152
  private trackWebVitals;
155
153
  private measureReplay;
156
- /**
157
- * Sorteio por visitante, não por gravação: quem entrou no sorteio tem a sessão inteira, em vez
158
- * de um replay picotado que não dá para assistir.
159
- */
160
- private isReplaySampled;
161
154
  private isRecording;
162
155
  private exceededReplayCaps;
163
156
  private loadRecording;
package/dist/index.d.ts CHANGED
@@ -109,14 +109,14 @@ declare class DataxamasTracking {
109
109
  private apiKey;
110
110
  private baseUrl;
111
111
  private allowLocalhost;
112
- private sessionId;
112
+ private started;
113
113
  private currentPage;
114
114
  private heatMapEnabled;
115
115
  private replayEnabled;
116
116
  private webVitalsEnabled;
117
117
  private pendingVitals;
118
118
  private vitalsPath;
119
- private replaySampleRate;
119
+ private replaySampled;
120
120
  private eventQueue;
121
121
  private isProcessing;
122
122
  private rrwebRecord;
@@ -137,8 +137,6 @@ declare class DataxamasTracking {
137
137
  private lastActivitySavedAt;
138
138
  constructor(options: DataxamasTrackingOptions);
139
139
  private isLocalhost;
140
- private getSessionId;
141
- private createSessionId;
142
140
  private processQueue;
143
141
  private utmQuery;
144
142
  private sendEvent;
@@ -153,11 +151,6 @@ declare class DataxamasTracking {
153
151
  private flushVitals;
154
152
  private trackWebVitals;
155
153
  private measureReplay;
156
- /**
157
- * Sorteio por visitante, não por gravação: quem entrou no sorteio tem a sessão inteira, em vez
158
- * de um replay picotado que não dá para assistir.
159
- */
160
- private isReplaySampled;
161
154
  private isRecording;
162
155
  private exceededReplayCaps;
163
156
  private loadRecording;
package/dist/index.js CHANGED
@@ -328,7 +328,7 @@ var REPLAY_RECORDING_KEY = `${STORAGE_PREFIX}/replay-recording`;
328
328
  function isStoredRecording(value) {
329
329
  if (typeof value !== "object" || value === null) return false;
330
330
  const stored = value;
331
- 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";
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";
332
332
  }
333
333
  var KEEPALIVE_LIMIT_BYTES = 60 * 1024;
334
334
  var DataxamasTracking = class {
@@ -356,14 +356,14 @@ var DataxamasTracking = class {
356
356
  this.apiKey = options.apiKey;
357
357
  this.baseUrl = options.baseUrl ?? BASE_URL;
358
358
  this.allowLocalhost = options.allowLocalhost ?? false;
359
- this.sessionId = null;
359
+ this.started = false;
360
360
  this.currentPage = isBrowser() ? window.location.pathname : "";
361
361
  this.heatMapEnabled = options.heatMapEnabled ?? true;
362
362
  this.replayEnabled = options.replayEnabled ?? true;
363
363
  this.webVitalsEnabled = options.webVitalsEnabled ?? true;
364
364
  this.pendingVitals = /* @__PURE__ */ new Map();
365
365
  this.vitalsPath = "";
366
- this.replaySampleRate = 1;
366
+ this.replaySampled = false;
367
367
  this.eventQueue = [];
368
368
  this.isProcessing = false;
369
369
  this.rrwebRecord = null;
@@ -386,21 +386,6 @@ var DataxamasTracking = class {
386
386
  isLocalhost() {
387
387
  return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
388
388
  }
389
- getSessionId() {
390
- try {
391
- return window.localStorage.getItem(`${STORAGE_PREFIX}/session`);
392
- } catch (_) {
393
- return null;
394
- }
395
- }
396
- createSessionId() {
397
- const id = crypto.randomUUID();
398
- try {
399
- window.localStorage.setItem(`${STORAGE_PREFIX}/session`, id);
400
- } catch (_) {
401
- }
402
- return id;
403
- }
404
389
  async processQueue() {
405
390
  if (this.isProcessing || this.eventQueue.length === 0) return;
406
391
  this.isProcessing = true;
@@ -436,13 +421,12 @@ var DataxamasTracking = class {
436
421
  path = this.currentPage,
437
422
  data = {}
438
423
  }) {
439
- if (!this.sessionId) return;
424
+ if (!this.started) return;
440
425
  this.eventQueue.push({
441
426
  name,
442
427
  path,
443
428
  referrer: document.referrer,
444
429
  query: name === "PAGE_VIEW" ? this.utmQuery() : void 0,
445
- sessionId: this.sessionId,
446
430
  data
447
431
  });
448
432
  await this.processQueue();
@@ -468,9 +452,7 @@ var DataxamasTracking = class {
468
452
  const cfg = await res.json();
469
453
  if (typeof cfg.heatMapEnabled === "boolean") this.heatMapEnabled = cfg.heatMapEnabled;
470
454
  if (typeof cfg.replayEnabled === "boolean") this.replayEnabled = cfg.replayEnabled;
471
- if (typeof cfg.replaySampleRate === "number" && cfg.replaySampleRate >= 0 && cfg.replaySampleRate <= 1) {
472
- this.replaySampleRate = cfg.replaySampleRate;
473
- }
455
+ if (typeof cfg.replaySampled === "boolean") this.replaySampled = cfg.replaySampled;
474
456
  } catch (_) {
475
457
  }
476
458
  }
@@ -551,14 +533,13 @@ var DataxamasTracking = class {
551
533
  });
552
534
  }
553
535
  flushVitals() {
554
- if (!this.sessionId || this.pendingVitals.size === 0) return;
536
+ if (this.pendingVitals.size === 0) return;
555
537
  const metrics = Array.from(this.pendingVitals.values());
556
538
  this.pendingVitals.clear();
557
539
  window.fetch(`${this.baseUrl}/api/v1/vitals`, {
558
540
  method: "POST",
559
541
  headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
560
542
  body: JSON.stringify({
561
- sessionId: this.sessionId,
562
543
  path: this.vitalsPath,
563
544
  viewportWidth: window.innerWidth,
564
545
  metrics
@@ -602,27 +583,6 @@ var DataxamasTracking = class {
602
583
  const layoutFingerprint = `${this.currentPage}:${Math.round(pageWidth / 100)}x${Math.round(pageHeight / 100)}`;
603
584
  return { viewportWidth, viewportHeight, pageWidth, pageHeight, layoutFingerprint };
604
585
  }
605
- /**
606
- * Sorteio por visitante, não por gravação: quem entrou no sorteio tem a sessão inteira, em vez
607
- * de um replay picotado que não dá para assistir.
608
- */
609
- isReplaySampled() {
610
- const rate = this.replaySampleRate;
611
- if (rate >= 1) return true;
612
- if (rate <= 0) return false;
613
- const key = `${STORAGE_PREFIX}/replay-sample`;
614
- try {
615
- const [storedRate, storedDecision] = (window.localStorage.getItem(key) ?? "").split(":");
616
- if (Number(storedRate) === rate && (storedDecision === "1" || storedDecision === "0")) {
617
- return storedDecision === "1";
618
- }
619
- const sampled = Math.random() < rate;
620
- window.localStorage.setItem(key, `${rate}:${sampled ? "1" : "0"}`);
621
- return sampled;
622
- } catch (_) {
623
- return Math.random() < rate;
624
- }
625
- }
626
586
  isRecording() {
627
587
  return Boolean(this.stopRrweb);
628
588
  }
@@ -638,10 +598,9 @@ var DataxamasTracking = class {
638
598
  }
639
599
  }
640
600
  saveRecording() {
641
- if (!this.sessionId || !this.recordingId || !this.recordingStartedAt) return;
601
+ if (!this.recordingId || !this.recordingStartedAt) return;
642
602
  const stored = {
643
603
  id: this.recordingId,
644
- sessionId: this.sessionId,
645
604
  startedAt: this.recordingStartedAt,
646
605
  startedAtMs: this.recordingStartedAtMs,
647
606
  seq: this.recordingSeq,
@@ -667,7 +626,7 @@ var DataxamasTracking = class {
667
626
  /** Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa. */
668
627
  resumeOrRotateRecording() {
669
628
  const stored = this.loadRecording();
670
- if (!stored || stored.capped || stored.sessionId !== this.sessionId || Date.now() - stored.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
629
+ if (!stored || stored.capped || Date.now() - stored.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
671
630
  this.rotateRecording();
672
631
  return;
673
632
  }
@@ -690,7 +649,6 @@ var DataxamasTracking = class {
690
649
  const events = this.replayBuffer;
691
650
  this.replayBuffer = [];
692
651
  const payload = JSON.stringify({
693
- sessionId: this.sessionId,
694
652
  recordingId: this.recordingId,
695
653
  path: this.currentPage,
696
654
  startedAt: this.recordingStartedAt,
@@ -839,15 +797,15 @@ var DataxamasTracking = class {
839
797
  });
840
798
  }
841
799
  startTracking() {
842
- this.sessionId = this.getSessionId() ?? this.createSessionId();
843
- this.resumeOrRotateRecording();
800
+ this.started = true;
844
801
  this.sendEvent({ name: "PAGE_VIEW" });
845
802
  this.trackPageChanges();
846
803
  this.trackClicks();
847
804
  this.trackScroll();
848
805
  this.trackWebVitals();
849
806
  this.fetchWebsiteConfig().then(() => {
850
- if (!this.replayEnabled || !this.isReplaySampled()) return;
807
+ if (!this.replayEnabled || !this.replaySampled) return;
808
+ this.resumeOrRotateRecording();
851
809
  this.setupReplayLifecycle();
852
810
  });
853
811
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataxamas/tracker-js",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "description": "Node.js SDK for Dataxamas Analytics API",
6
6
  "license": "MIT",