@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 +2 -2
- package/dist/chunk-ETV4XYOV.cjs +7 -0
- package/dist/chunk-PKBMQBKP.js +7 -0
- package/dist/index.cjs +75 -53
- package/dist/index.d.cts +12 -9
- package/dist/index.d.ts +12 -9
- package/dist/index.js +74 -52
- package/dist/{rrweb-F6UF6U3D.js → rrweb-PPF5WV43.js} +2 -0
- package/dist/{rrweb-M7O3D3Q3.cjs → rrweb-QFJCWYDQ.cjs} +3 -1
- package/dist/web-vitals-6BV2S7VZ.js +345 -0
- package/dist/web-vitals-TFHN3UGU.cjs +345 -0
- package/package.json +3 -2
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.
|
|
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
|
-
- **
|
|
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; }
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
424
|
+
if (!this.started) return;
|
|
427
425
|
this.eventQueue.push({
|
|
428
426
|
name,
|
|
429
427
|
path,
|
|
430
428
|
referrer: document.referrer,
|
|
431
|
-
|
|
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.
|
|
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.
|
|
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 ||
|
|
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-
|
|
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.
|
|
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.
|
|
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
|
|
112
|
+
private started;
|
|
112
113
|
private currentPage;
|
|
113
114
|
private heatMapEnabled;
|
|
114
115
|
private replayEnabled;
|
|
115
|
-
private
|
|
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
|
|
112
|
+
private started;
|
|
112
113
|
private currentPage;
|
|
113
114
|
private heatMapEnabled;
|
|
114
115
|
private replayEnabled;
|
|
115
|
-
private
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
424
|
+
if (!this.started) return;
|
|
427
425
|
this.eventQueue.push({
|
|
428
426
|
name,
|
|
429
427
|
path,
|
|
430
428
|
referrer: document.referrer,
|
|
431
|
-
|
|
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.
|
|
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.
|
|
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 ||
|
|
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-
|
|
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.
|
|
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.
|
|
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(); } }
|
|
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);
|