@dataxamas/tracker-js 2.1.3 → 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.
@@ -0,0 +1,7 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+
6
+
7
+ exports.__publicField = __publicField;
@@ -0,0 +1,7 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ export {
6
+ __publicField
7
+ };
package/dist/index.cjs CHANGED
@@ -1,4 +1,6 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/constants.ts
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }require('./chunk-ETV4XYOV.cjs');
2
+
3
+ // src/constants.ts
2
4
  var BASE_URL = "https://dataxamas.com";
3
5
 
4
6
  // src/utils.ts
@@ -326,7 +328,7 @@ var REPLAY_RECORDING_KEY = `${STORAGE_PREFIX}/replay-recording`;
326
328
  function isStoredRecording(value) {
327
329
  if (typeof value !== "object" || value === null) return false;
328
330
  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";
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";
330
332
  }
331
333
  var KEEPALIVE_LIMIT_BYTES = 60 * 1024;
332
334
  var DataxamasTracking = class {
@@ -354,11 +356,14 @@ var DataxamasTracking = class {
354
356
  this.apiKey = options.apiKey;
355
357
  this.baseUrl = _nullishCoalesce(options.baseUrl, () => ( BASE_URL));
356
358
  this.allowLocalhost = _nullishCoalesce(options.allowLocalhost, () => ( false));
357
- this.sessionId = null;
359
+ this.started = false;
358
360
  this.currentPage = isBrowser() ? window.location.pathname : "";
359
361
  this.heatMapEnabled = _nullishCoalesce(options.heatMapEnabled, () => ( true));
360
362
  this.replayEnabled = _nullishCoalesce(options.replayEnabled, () => ( true));
361
- this.replaySampleRate = 1;
363
+ this.webVitalsEnabled = _nullishCoalesce(options.webVitalsEnabled, () => ( true));
364
+ this.pendingVitals = /* @__PURE__ */ new Map();
365
+ this.vitalsPath = "";
366
+ this.replaySampled = false;
362
367
  this.eventQueue = [];
363
368
  this.isProcessing = false;
364
369
  this.rrwebRecord = null;
@@ -381,21 +386,6 @@ var DataxamasTracking = class {
381
386
  isLocalhost() {
382
387
  return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
383
388
  }
384
- getSessionId() {
385
- try {
386
- return window.localStorage.getItem(`${STORAGE_PREFIX}/session`);
387
- } catch (_) {
388
- return null;
389
- }
390
- }
391
- createSessionId() {
392
- const id = crypto.randomUUID();
393
- try {
394
- window.localStorage.setItem(`${STORAGE_PREFIX}/session`, id);
395
- } catch (_) {
396
- }
397
- return id;
398
- }
399
389
  async processQueue() {
400
390
  if (this.isProcessing || this.eventQueue.length === 0) return;
401
391
  this.isProcessing = true;
@@ -418,17 +408,25 @@ var DataxamasTracking = class {
418
408
  this.processQueue();
419
409
  }, QUEUE_DELAY_MS);
420
410
  }
411
+ // Só os pares `utm_*`: o resto da query pode carregar token ou dado pessoal e não sai do browser.
412
+ utmQuery() {
413
+ const utm = new URLSearchParams();
414
+ new URLSearchParams(window.location.search).forEach((value, key) => {
415
+ if (key.startsWith("utm_")) utm.append(key, value);
416
+ });
417
+ return utm.toString() || void 0;
418
+ }
421
419
  async sendEvent({
422
420
  name,
423
421
  path = this.currentPage,
424
422
  data = {}
425
423
  }) {
426
- if (!this.sessionId) return;
424
+ if (!this.started) return;
427
425
  this.eventQueue.push({
428
426
  name,
429
427
  path,
430
428
  referrer: document.referrer,
431
- sessionId: this.sessionId,
429
+ query: name === "PAGE_VIEW" ? this.utmQuery() : void 0,
432
430
  data
433
431
  });
434
432
  await this.processQueue();
@@ -454,9 +452,7 @@ var DataxamasTracking = class {
454
452
  const cfg = await res.json();
455
453
  if (typeof cfg.heatMapEnabled === "boolean") this.heatMapEnabled = cfg.heatMapEnabled;
456
454
  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
- }
455
+ if (typeof cfg.replaySampled === "boolean") this.replaySampled = cfg.replaySampled;
460
456
  } catch (_) {
461
457
  }
462
458
  }
@@ -536,6 +532,48 @@ var DataxamasTracking = class {
536
532
  }, 1e3);
537
533
  });
538
534
  }
535
+ flushVitals() {
536
+ if (this.pendingVitals.size === 0) return;
537
+ const metrics = Array.from(this.pendingVitals.values());
538
+ this.pendingVitals.clear();
539
+ window.fetch(`${this.baseUrl}/api/v1/vitals`, {
540
+ method: "POST",
541
+ headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
542
+ body: JSON.stringify({
543
+ path: this.vitalsPath,
544
+ viewportWidth: window.innerWidth,
545
+ metrics
546
+ }),
547
+ keepalive: true,
548
+ credentials: "omit"
549
+ }).catch(() => {
550
+ });
551
+ }
552
+ async trackWebVitals() {
553
+ if (!this.webVitalsEnabled) return;
554
+ this.vitalsPath = window.location.pathname;
555
+ let webVitals;
556
+ try {
557
+ webVitals = await Promise.resolve().then(() => _interopRequireWildcard(require("./web-vitals-TFHN3UGU.cjs")));
558
+ } catch (_) {
559
+ return;
560
+ }
561
+ const queue = (metric) => {
562
+ this.pendingVitals.set(`${metric.name}:${metric.id}`, {
563
+ id: metric.id,
564
+ name: metric.name,
565
+ value: metric.value,
566
+ navigationType: metric.navigationType
567
+ });
568
+ };
569
+ webVitals.onLCP(queue);
570
+ webVitals.onINP(queue);
571
+ webVitals.onCLS(queue);
572
+ document.addEventListener("visibilitychange", () => {
573
+ if (document.visibilityState === "hidden") this.flushVitals();
574
+ });
575
+ window.addEventListener("pagehide", () => this.flushVitals());
576
+ }
539
577
  measureReplay() {
540
578
  const doc = document.documentElement;
541
579
  const viewportWidth = window.innerWidth;
@@ -545,27 +583,6 @@ var DataxamasTracking = class {
545
583
  const layoutFingerprint = `${this.currentPage}:${Math.round(pageWidth / 100)}x${Math.round(pageHeight / 100)}`;
546
584
  return { viewportWidth, viewportHeight, pageWidth, pageHeight, layoutFingerprint };
547
585
  }
548
- /**
549
- * Sorteio por visitante, não por gravação: quem entrou no sorteio tem a sessão inteira, em vez
550
- * de um replay picotado que não dá para assistir.
551
- */
552
- isReplaySampled() {
553
- const rate = this.replaySampleRate;
554
- if (rate >= 1) return true;
555
- if (rate <= 0) return false;
556
- const key = `${STORAGE_PREFIX}/replay-sample`;
557
- try {
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"}`);
564
- return sampled;
565
- } catch (_) {
566
- return Math.random() < rate;
567
- }
568
- }
569
586
  isRecording() {
570
587
  return Boolean(this.stopRrweb);
571
588
  }
@@ -581,10 +598,9 @@ var DataxamasTracking = class {
581
598
  }
582
599
  }
583
600
  saveRecording() {
584
- if (!this.sessionId || !this.recordingId || !this.recordingStartedAt) return;
601
+ if (!this.recordingId || !this.recordingStartedAt) return;
585
602
  const stored = {
586
603
  id: this.recordingId,
587
- sessionId: this.sessionId,
588
604
  startedAt: this.recordingStartedAt,
589
605
  startedAtMs: this.recordingStartedAtMs,
590
606
  seq: this.recordingSeq,
@@ -610,7 +626,7 @@ var DataxamasTracking = class {
610
626
  /** Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa. */
611
627
  resumeOrRotateRecording() {
612
628
  const stored = this.loadRecording();
613
- 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) {
614
630
  this.rotateRecording();
615
631
  return;
616
632
  }
@@ -633,7 +649,6 @@ var DataxamasTracking = class {
633
649
  const events = this.replayBuffer;
634
650
  this.replayBuffer = [];
635
651
  const payload = JSON.stringify({
636
- sessionId: this.sessionId,
637
652
  recordingId: this.recordingId,
638
653
  path: this.currentPage,
639
654
  startedAt: this.recordingStartedAt,
@@ -735,7 +750,7 @@ var DataxamasTracking = class {
735
750
  this.replayStarting = true;
736
751
  try {
737
752
  if (!this.rrwebRecord) {
738
- const { record } = await Promise.resolve().then(() => _interopRequireWildcard(require("./rrweb-M7O3D3Q3.cjs")));
753
+ const { record } = await Promise.resolve().then(() => _interopRequireWildcard(require("./rrweb-QFJCWYDQ.cjs")));
739
754
  this.rrwebRecord = record;
740
755
  }
741
756
  } catch (_) {
@@ -782,17 +797,23 @@ var DataxamasTracking = class {
782
797
  });
783
798
  }
784
799
  startTracking() {
785
- this.sessionId = _nullishCoalesce(this.getSessionId(), () => ( this.createSessionId()));
786
- this.resumeOrRotateRecording();
800
+ this.started = true;
787
801
  this.sendEvent({ name: "PAGE_VIEW" });
788
802
  this.trackPageChanges();
789
803
  this.trackClicks();
790
804
  this.trackScroll();
805
+ this.trackWebVitals();
791
806
  this.fetchWebsiteConfig().then(() => {
792
- if (!this.replayEnabled || !this.isReplaySampled()) return;
807
+ if (!this.replayEnabled || !this.replaySampled) return;
808
+ this.resumeOrRotateRecording();
793
809
  this.setupReplayLifecycle();
794
810
  });
795
811
  }
812
+ /** Evento customizado (ex.: `signup_done`), usado em metas e funis. Antes do `start()` não envia nada. */
813
+ track(name, data) {
814
+ if (!isBrowser()) return;
815
+ void this.sendEvent({ name, data });
816
+ }
796
817
  start() {
797
818
  if (!isBrowser()) {
798
819
  throw new Error("DataxamasTracking: This SDK can only be used in a browser environment.");
@@ -833,6 +854,7 @@ var Dataxamas = class {
833
854
  baseUrl,
834
855
  replayEnabled: _nullishCoalesce(options.replayEnabled, () => ( true)),
835
856
  heatMapEnabled: _nullishCoalesce(options.heatMapEnabled, () => ( true)),
857
+ webVitalsEnabled: _nullishCoalesce(options.webVitalsEnabled, () => ( true)),
836
858
  allowLocalhost: _nullishCoalesce(options.allowLocalhost, () => ( false))
837
859
  });
838
860
  this.crawlers = new DataxamasCrawlers({
package/dist/index.d.cts CHANGED
@@ -103,16 +103,20 @@ interface DataxamasTrackingOptions {
103
103
  allowLocalhost?: boolean;
104
104
  replayEnabled?: boolean;
105
105
  heatMapEnabled?: boolean;
106
+ webVitalsEnabled?: boolean;
106
107
  }
107
108
  declare class DataxamasTracking {
108
109
  private apiKey;
109
110
  private baseUrl;
110
111
  private allowLocalhost;
111
- private sessionId;
112
+ private started;
112
113
  private currentPage;
113
114
  private heatMapEnabled;
114
115
  private replayEnabled;
115
- private replaySampleRate;
116
+ private webVitalsEnabled;
117
+ private pendingVitals;
118
+ private vitalsPath;
119
+ private replaySampled;
116
120
  private eventQueue;
117
121
  private isProcessing;
118
122
  private rrwebRecord;
@@ -133,9 +137,8 @@ declare class DataxamasTracking {
133
137
  private lastActivitySavedAt;
134
138
  constructor(options: DataxamasTrackingOptions);
135
139
  private isLocalhost;
136
- private getSessionId;
137
- private createSessionId;
138
140
  private processQueue;
141
+ private utmQuery;
139
142
  private sendEvent;
140
143
  private trackPageChanges;
141
144
  private fetchWebsiteConfig;
@@ -145,12 +148,9 @@ declare class DataxamasTracking {
145
148
  private getTarget;
146
149
  private trackClicks;
147
150
  private trackScroll;
151
+ private flushVitals;
152
+ private trackWebVitals;
148
153
  private measureReplay;
149
- /**
150
- * Sorteio por visitante, não por gravação: quem entrou no sorteio tem a sessão inteira, em vez
151
- * de um replay picotado que não dá para assistir.
152
- */
153
- private isReplaySampled;
154
154
  private isRecording;
155
155
  private exceededReplayCaps;
156
156
  private loadRecording;
@@ -181,6 +181,8 @@ declare class DataxamasTracking {
181
181
  private setupReplayLifecycle;
182
182
  private startReplayRecorder;
183
183
  private startTracking;
184
+ /** Evento customizado (ex.: `signup_done`), usado em metas e funis. Antes do `start()` não envia nada. */
185
+ track(name: string, data?: Record<string, unknown>): void;
184
186
  start(): void;
185
187
  }
186
188
 
@@ -196,6 +198,7 @@ interface DataxamasOptions {
196
198
  captureGlobalErrors?: boolean;
197
199
  replayEnabled?: boolean;
198
200
  heatMapEnabled?: boolean;
201
+ webVitalsEnabled?: boolean;
199
202
  }
200
203
  declare class Dataxamas {
201
204
  readonly logs: DataxamasLogs;
package/dist/index.d.ts CHANGED
@@ -103,16 +103,20 @@ interface DataxamasTrackingOptions {
103
103
  allowLocalhost?: boolean;
104
104
  replayEnabled?: boolean;
105
105
  heatMapEnabled?: boolean;
106
+ webVitalsEnabled?: boolean;
106
107
  }
107
108
  declare class DataxamasTracking {
108
109
  private apiKey;
109
110
  private baseUrl;
110
111
  private allowLocalhost;
111
- private sessionId;
112
+ private started;
112
113
  private currentPage;
113
114
  private heatMapEnabled;
114
115
  private replayEnabled;
115
- private replaySampleRate;
116
+ private webVitalsEnabled;
117
+ private pendingVitals;
118
+ private vitalsPath;
119
+ private replaySampled;
116
120
  private eventQueue;
117
121
  private isProcessing;
118
122
  private rrwebRecord;
@@ -133,9 +137,8 @@ declare class DataxamasTracking {
133
137
  private lastActivitySavedAt;
134
138
  constructor(options: DataxamasTrackingOptions);
135
139
  private isLocalhost;
136
- private getSessionId;
137
- private createSessionId;
138
140
  private processQueue;
141
+ private utmQuery;
139
142
  private sendEvent;
140
143
  private trackPageChanges;
141
144
  private fetchWebsiteConfig;
@@ -145,12 +148,9 @@ declare class DataxamasTracking {
145
148
  private getTarget;
146
149
  private trackClicks;
147
150
  private trackScroll;
151
+ private flushVitals;
152
+ private trackWebVitals;
148
153
  private measureReplay;
149
- /**
150
- * Sorteio por visitante, não por gravação: quem entrou no sorteio tem a sessão inteira, em vez
151
- * de um replay picotado que não dá para assistir.
152
- */
153
- private isReplaySampled;
154
154
  private isRecording;
155
155
  private exceededReplayCaps;
156
156
  private loadRecording;
@@ -181,6 +181,8 @@ declare class DataxamasTracking {
181
181
  private setupReplayLifecycle;
182
182
  private startReplayRecorder;
183
183
  private startTracking;
184
+ /** Evento customizado (ex.: `signup_done`), usado em metas e funis. Antes do `start()` não envia nada. */
185
+ track(name: string, data?: Record<string, unknown>): void;
184
186
  start(): void;
185
187
  }
186
188
 
@@ -196,6 +198,7 @@ interface DataxamasOptions {
196
198
  captureGlobalErrors?: boolean;
197
199
  replayEnabled?: boolean;
198
200
  heatMapEnabled?: boolean;
201
+ webVitalsEnabled?: boolean;
199
202
  }
200
203
  declare class Dataxamas {
201
204
  readonly logs: DataxamasLogs;
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import "./chunk-PKBMQBKP.js";
2
+
1
3
  // src/constants.ts
2
4
  var BASE_URL = "https://dataxamas.com";
3
5
 
@@ -326,7 +328,7 @@ var REPLAY_RECORDING_KEY = `${STORAGE_PREFIX}/replay-recording`;
326
328
  function isStoredRecording(value) {
327
329
  if (typeof value !== "object" || value === null) return false;
328
330
  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";
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";
330
332
  }
331
333
  var KEEPALIVE_LIMIT_BYTES = 60 * 1024;
332
334
  var DataxamasTracking = class {
@@ -354,11 +356,14 @@ var DataxamasTracking = class {
354
356
  this.apiKey = options.apiKey;
355
357
  this.baseUrl = options.baseUrl ?? BASE_URL;
356
358
  this.allowLocalhost = options.allowLocalhost ?? false;
357
- this.sessionId = null;
359
+ this.started = false;
358
360
  this.currentPage = isBrowser() ? window.location.pathname : "";
359
361
  this.heatMapEnabled = options.heatMapEnabled ?? true;
360
362
  this.replayEnabled = options.replayEnabled ?? true;
361
- this.replaySampleRate = 1;
363
+ this.webVitalsEnabled = options.webVitalsEnabled ?? true;
364
+ this.pendingVitals = /* @__PURE__ */ new Map();
365
+ this.vitalsPath = "";
366
+ this.replaySampled = false;
362
367
  this.eventQueue = [];
363
368
  this.isProcessing = false;
364
369
  this.rrwebRecord = null;
@@ -381,21 +386,6 @@ var DataxamasTracking = class {
381
386
  isLocalhost() {
382
387
  return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
383
388
  }
384
- getSessionId() {
385
- try {
386
- return window.localStorage.getItem(`${STORAGE_PREFIX}/session`);
387
- } catch (_) {
388
- return null;
389
- }
390
- }
391
- createSessionId() {
392
- const id = crypto.randomUUID();
393
- try {
394
- window.localStorage.setItem(`${STORAGE_PREFIX}/session`, id);
395
- } catch (_) {
396
- }
397
- return id;
398
- }
399
389
  async processQueue() {
400
390
  if (this.isProcessing || this.eventQueue.length === 0) return;
401
391
  this.isProcessing = true;
@@ -418,17 +408,25 @@ var DataxamasTracking = class {
418
408
  this.processQueue();
419
409
  }, QUEUE_DELAY_MS);
420
410
  }
411
+ // Só os pares `utm_*`: o resto da query pode carregar token ou dado pessoal e não sai do browser.
412
+ utmQuery() {
413
+ const utm = new URLSearchParams();
414
+ new URLSearchParams(window.location.search).forEach((value, key) => {
415
+ if (key.startsWith("utm_")) utm.append(key, value);
416
+ });
417
+ return utm.toString() || void 0;
418
+ }
421
419
  async sendEvent({
422
420
  name,
423
421
  path = this.currentPage,
424
422
  data = {}
425
423
  }) {
426
- if (!this.sessionId) return;
424
+ if (!this.started) return;
427
425
  this.eventQueue.push({
428
426
  name,
429
427
  path,
430
428
  referrer: document.referrer,
431
- sessionId: this.sessionId,
429
+ query: name === "PAGE_VIEW" ? this.utmQuery() : void 0,
432
430
  data
433
431
  });
434
432
  await this.processQueue();
@@ -454,9 +452,7 @@ var DataxamasTracking = class {
454
452
  const cfg = await res.json();
455
453
  if (typeof cfg.heatMapEnabled === "boolean") this.heatMapEnabled = cfg.heatMapEnabled;
456
454
  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
- }
455
+ if (typeof cfg.replaySampled === "boolean") this.replaySampled = cfg.replaySampled;
460
456
  } catch (_) {
461
457
  }
462
458
  }
@@ -536,6 +532,48 @@ var DataxamasTracking = class {
536
532
  }, 1e3);
537
533
  });
538
534
  }
535
+ flushVitals() {
536
+ if (this.pendingVitals.size === 0) return;
537
+ const metrics = Array.from(this.pendingVitals.values());
538
+ this.pendingVitals.clear();
539
+ window.fetch(`${this.baseUrl}/api/v1/vitals`, {
540
+ method: "POST",
541
+ headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
542
+ body: JSON.stringify({
543
+ path: this.vitalsPath,
544
+ viewportWidth: window.innerWidth,
545
+ metrics
546
+ }),
547
+ keepalive: true,
548
+ credentials: "omit"
549
+ }).catch(() => {
550
+ });
551
+ }
552
+ async trackWebVitals() {
553
+ if (!this.webVitalsEnabled) return;
554
+ this.vitalsPath = window.location.pathname;
555
+ let webVitals;
556
+ try {
557
+ webVitals = await import("./web-vitals-6BV2S7VZ.js");
558
+ } catch (_) {
559
+ return;
560
+ }
561
+ const queue = (metric) => {
562
+ this.pendingVitals.set(`${metric.name}:${metric.id}`, {
563
+ id: metric.id,
564
+ name: metric.name,
565
+ value: metric.value,
566
+ navigationType: metric.navigationType
567
+ });
568
+ };
569
+ webVitals.onLCP(queue);
570
+ webVitals.onINP(queue);
571
+ webVitals.onCLS(queue);
572
+ document.addEventListener("visibilitychange", () => {
573
+ if (document.visibilityState === "hidden") this.flushVitals();
574
+ });
575
+ window.addEventListener("pagehide", () => this.flushVitals());
576
+ }
539
577
  measureReplay() {
540
578
  const doc = document.documentElement;
541
579
  const viewportWidth = window.innerWidth;
@@ -545,27 +583,6 @@ var DataxamasTracking = class {
545
583
  const layoutFingerprint = `${this.currentPage}:${Math.round(pageWidth / 100)}x${Math.round(pageHeight / 100)}`;
546
584
  return { viewportWidth, viewportHeight, pageWidth, pageHeight, layoutFingerprint };
547
585
  }
548
- /**
549
- * Sorteio por visitante, não por gravação: quem entrou no sorteio tem a sessão inteira, em vez
550
- * de um replay picotado que não dá para assistir.
551
- */
552
- isReplaySampled() {
553
- const rate = this.replaySampleRate;
554
- if (rate >= 1) return true;
555
- if (rate <= 0) return false;
556
- const key = `${STORAGE_PREFIX}/replay-sample`;
557
- try {
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"}`);
564
- return sampled;
565
- } catch (_) {
566
- return Math.random() < rate;
567
- }
568
- }
569
586
  isRecording() {
570
587
  return Boolean(this.stopRrweb);
571
588
  }
@@ -581,10 +598,9 @@ var DataxamasTracking = class {
581
598
  }
582
599
  }
583
600
  saveRecording() {
584
- if (!this.sessionId || !this.recordingId || !this.recordingStartedAt) return;
601
+ if (!this.recordingId || !this.recordingStartedAt) return;
585
602
  const stored = {
586
603
  id: this.recordingId,
587
- sessionId: this.sessionId,
588
604
  startedAt: this.recordingStartedAt,
589
605
  startedAtMs: this.recordingStartedAtMs,
590
606
  seq: this.recordingSeq,
@@ -610,7 +626,7 @@ var DataxamasTracking = class {
610
626
  /** Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa. */
611
627
  resumeOrRotateRecording() {
612
628
  const stored = this.loadRecording();
613
- 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) {
614
630
  this.rotateRecording();
615
631
  return;
616
632
  }
@@ -633,7 +649,6 @@ var DataxamasTracking = class {
633
649
  const events = this.replayBuffer;
634
650
  this.replayBuffer = [];
635
651
  const payload = JSON.stringify({
636
- sessionId: this.sessionId,
637
652
  recordingId: this.recordingId,
638
653
  path: this.currentPage,
639
654
  startedAt: this.recordingStartedAt,
@@ -735,7 +750,7 @@ var DataxamasTracking = class {
735
750
  this.replayStarting = true;
736
751
  try {
737
752
  if (!this.rrwebRecord) {
738
- const { record } = await import("./rrweb-F6UF6U3D.js");
753
+ const { record } = await import("./rrweb-PPF5WV43.js");
739
754
  this.rrwebRecord = record;
740
755
  }
741
756
  } catch (_) {
@@ -782,17 +797,23 @@ var DataxamasTracking = class {
782
797
  });
783
798
  }
784
799
  startTracking() {
785
- this.sessionId = this.getSessionId() ?? this.createSessionId();
786
- this.resumeOrRotateRecording();
800
+ this.started = true;
787
801
  this.sendEvent({ name: "PAGE_VIEW" });
788
802
  this.trackPageChanges();
789
803
  this.trackClicks();
790
804
  this.trackScroll();
805
+ this.trackWebVitals();
791
806
  this.fetchWebsiteConfig().then(() => {
792
- if (!this.replayEnabled || !this.isReplaySampled()) return;
807
+ if (!this.replayEnabled || !this.replaySampled) return;
808
+ this.resumeOrRotateRecording();
793
809
  this.setupReplayLifecycle();
794
810
  });
795
811
  }
812
+ /** Evento customizado (ex.: `signup_done`), usado em metas e funis. Antes do `start()` não envia nada. */
813
+ track(name, data) {
814
+ if (!isBrowser()) return;
815
+ void this.sendEvent({ name, data });
816
+ }
796
817
  start() {
797
818
  if (!isBrowser()) {
798
819
  throw new Error("DataxamasTracking: This SDK can only be used in a browser environment.");
@@ -833,6 +854,7 @@ var Dataxamas = class {
833
854
  baseUrl,
834
855
  replayEnabled: options.replayEnabled ?? true,
835
856
  heatMapEnabled: options.heatMapEnabled ?? true,
857
+ webVitalsEnabled: options.webVitalsEnabled ?? true,
836
858
  allowLocalhost: options.allowLocalhost ?? false
837
859
  });
838
860
  this.crawlers = new DataxamasCrawlers({
@@ -1,3 +1,5 @@
1
+ import "./chunk-PKBMQBKP.js";
2
+
1
3
  // ../../node_modules/rrweb/dist/rrweb.js
2
4
  var __defProp = Object.defineProperty;
3
5
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -1,4 +1,6 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }// ../../node_modules/rrweb/dist/rrweb.js
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }require('./chunk-ETV4XYOV.cjs');
2
+
3
+ // ../../node_modules/rrweb/dist/rrweb.js
2
4
  var __defProp = Object.defineProperty;
3
5
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
6
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);