@dataxamas/tracker-js 2.3.0 → 2.5.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 +27 -2
- package/dist/index.cjs +22 -54
- package/dist/index.d.cts +8 -9
- package/dist/index.d.ts +8 -9
- package/dist/index.js +22 -54
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -201,10 +201,35 @@ 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. A second key with a monthly salt links a visitor's visits to the same site within the month, for retention cohorts.
|
|
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
|
|
|
208
|
+
#### `track(name, data?)`
|
|
209
|
+
|
|
210
|
+
Sends a custom event. No-op outside the browser.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
tracking.track("signup", { plan: "pro" });
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### `identify(userId)`
|
|
217
|
+
|
|
218
|
+
Ties the next events to a user of your system, so retention recognizes the same person across months, browsers and devices. No-op outside the browser.
|
|
219
|
+
|
|
220
|
+
```ts
|
|
221
|
+
// After sign-in, on every page load
|
|
222
|
+
tracking.identify(user.id);
|
|
223
|
+
|
|
224
|
+
// On sign-out
|
|
225
|
+
tracking.identify(null);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
- The id travels with each event, but the server stores only `sha256(websiteId + userId)` — never the raw value.
|
|
229
|
+
- It lives in memory only: call it again on every page load.
|
|
230
|
+
- Use an opaque, stable id — never an email or a name.
|
|
231
|
+
- Script tag: `window.dataxamas.track(name, data)` and `window.dataxamas.identify(userId)`.
|
|
232
|
+
|
|
208
233
|
**Behavior notes**
|
|
209
234
|
|
|
210
235
|
- Throws `"DataxamasTracking: This SDK can only be used in a browser environment."` if called on the server.
|
|
@@ -250,7 +275,7 @@ Exported for use in your own typings:
|
|
|
250
275
|
## Notes
|
|
251
276
|
|
|
252
277
|
- **Default endpoint:** `https://dataxamas.com`. Override with `baseUrl`.
|
|
253
|
-
- **
|
|
278
|
+
- **Visitor identity:** computed server-side, no cookies or `localStorage` — a daily key for the visit, a monthly key (or the `identify()` hash) for retention. Only replay touches the browser, in `sessionStorage["@dataxamas/replay-recording"]`, and only for sampled visitors.
|
|
254
279
|
- **Event flush spacing:** 4 seconds between queued analytics events.
|
|
255
280
|
- **Replay masking classes:** `dx-mask` (text) and `dx-block` (element); all inputs masked by default.
|
|
256
281
|
- **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.
|
|
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,15 @@ 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.
|
|
359
|
+
this.started = false;
|
|
360
360
|
this.currentPage = isBrowser() ? window.location.pathname : "";
|
|
361
|
+
this.userId = null;
|
|
361
362
|
this.heatMapEnabled = _nullishCoalesce(options.heatMapEnabled, () => ( true));
|
|
362
363
|
this.replayEnabled = _nullishCoalesce(options.replayEnabled, () => ( true));
|
|
363
364
|
this.webVitalsEnabled = _nullishCoalesce(options.webVitalsEnabled, () => ( true));
|
|
364
365
|
this.pendingVitals = /* @__PURE__ */ new Map();
|
|
365
366
|
this.vitalsPath = "";
|
|
366
|
-
this.
|
|
367
|
+
this.replaySampled = false;
|
|
367
368
|
this.eventQueue = [];
|
|
368
369
|
this.isProcessing = false;
|
|
369
370
|
this.rrwebRecord = null;
|
|
@@ -386,21 +387,6 @@ var DataxamasTracking = class {
|
|
|
386
387
|
isLocalhost() {
|
|
387
388
|
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
388
389
|
}
|
|
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
390
|
async processQueue() {
|
|
405
391
|
if (this.isProcessing || this.eventQueue.length === 0) return;
|
|
406
392
|
this.isProcessing = true;
|
|
@@ -436,14 +422,14 @@ var DataxamasTracking = class {
|
|
|
436
422
|
path = this.currentPage,
|
|
437
423
|
data = {}
|
|
438
424
|
}) {
|
|
439
|
-
if (!this.
|
|
425
|
+
if (!this.started) return;
|
|
440
426
|
this.eventQueue.push({
|
|
441
427
|
name,
|
|
442
428
|
path,
|
|
443
429
|
referrer: document.referrer,
|
|
444
430
|
query: name === "PAGE_VIEW" ? this.utmQuery() : void 0,
|
|
445
|
-
|
|
446
|
-
|
|
431
|
+
data,
|
|
432
|
+
userId: _nullishCoalesce(this.userId, () => ( void 0))
|
|
447
433
|
});
|
|
448
434
|
await this.processQueue();
|
|
449
435
|
}
|
|
@@ -468,9 +454,7 @@ var DataxamasTracking = class {
|
|
|
468
454
|
const cfg = await res.json();
|
|
469
455
|
if (typeof cfg.heatMapEnabled === "boolean") this.heatMapEnabled = cfg.heatMapEnabled;
|
|
470
456
|
if (typeof cfg.replayEnabled === "boolean") this.replayEnabled = cfg.replayEnabled;
|
|
471
|
-
if (typeof cfg.
|
|
472
|
-
this.replaySampleRate = cfg.replaySampleRate;
|
|
473
|
-
}
|
|
457
|
+
if (typeof cfg.replaySampled === "boolean") this.replaySampled = cfg.replaySampled;
|
|
474
458
|
} catch (_) {
|
|
475
459
|
}
|
|
476
460
|
}
|
|
@@ -551,14 +535,13 @@ var DataxamasTracking = class {
|
|
|
551
535
|
});
|
|
552
536
|
}
|
|
553
537
|
flushVitals() {
|
|
554
|
-
if (
|
|
538
|
+
if (this.pendingVitals.size === 0) return;
|
|
555
539
|
const metrics = Array.from(this.pendingVitals.values());
|
|
556
540
|
this.pendingVitals.clear();
|
|
557
541
|
window.fetch(`${this.baseUrl}/api/v1/vitals`, {
|
|
558
542
|
method: "POST",
|
|
559
543
|
headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
|
|
560
544
|
body: JSON.stringify({
|
|
561
|
-
sessionId: this.sessionId,
|
|
562
545
|
path: this.vitalsPath,
|
|
563
546
|
viewportWidth: window.innerWidth,
|
|
564
547
|
metrics
|
|
@@ -602,27 +585,6 @@ var DataxamasTracking = class {
|
|
|
602
585
|
const layoutFingerprint = `${this.currentPage}:${Math.round(pageWidth / 100)}x${Math.round(pageHeight / 100)}`;
|
|
603
586
|
return { viewportWidth, viewportHeight, pageWidth, pageHeight, layoutFingerprint };
|
|
604
587
|
}
|
|
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
588
|
isRecording() {
|
|
627
589
|
return Boolean(this.stopRrweb);
|
|
628
590
|
}
|
|
@@ -638,10 +600,9 @@ var DataxamasTracking = class {
|
|
|
638
600
|
}
|
|
639
601
|
}
|
|
640
602
|
saveRecording() {
|
|
641
|
-
if (!this.
|
|
603
|
+
if (!this.recordingId || !this.recordingStartedAt) return;
|
|
642
604
|
const stored = {
|
|
643
605
|
id: this.recordingId,
|
|
644
|
-
sessionId: this.sessionId,
|
|
645
606
|
startedAt: this.recordingStartedAt,
|
|
646
607
|
startedAtMs: this.recordingStartedAtMs,
|
|
647
608
|
seq: this.recordingSeq,
|
|
@@ -667,7 +628,7 @@ var DataxamasTracking = class {
|
|
|
667
628
|
/** Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa. */
|
|
668
629
|
resumeOrRotateRecording() {
|
|
669
630
|
const stored = this.loadRecording();
|
|
670
|
-
if (!stored || stored.capped ||
|
|
631
|
+
if (!stored || stored.capped || Date.now() - stored.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
|
|
671
632
|
this.rotateRecording();
|
|
672
633
|
return;
|
|
673
634
|
}
|
|
@@ -690,7 +651,6 @@ var DataxamasTracking = class {
|
|
|
690
651
|
const events = this.replayBuffer;
|
|
691
652
|
this.replayBuffer = [];
|
|
692
653
|
const payload = JSON.stringify({
|
|
693
|
-
sessionId: this.sessionId,
|
|
694
654
|
recordingId: this.recordingId,
|
|
695
655
|
path: this.currentPage,
|
|
696
656
|
startedAt: this.recordingStartedAt,
|
|
@@ -839,15 +799,15 @@ var DataxamasTracking = class {
|
|
|
839
799
|
});
|
|
840
800
|
}
|
|
841
801
|
startTracking() {
|
|
842
|
-
this.
|
|
843
|
-
this.resumeOrRotateRecording();
|
|
802
|
+
this.started = true;
|
|
844
803
|
this.sendEvent({ name: "PAGE_VIEW" });
|
|
845
804
|
this.trackPageChanges();
|
|
846
805
|
this.trackClicks();
|
|
847
806
|
this.trackScroll();
|
|
848
807
|
this.trackWebVitals();
|
|
849
808
|
this.fetchWebsiteConfig().then(() => {
|
|
850
|
-
if (!this.replayEnabled || !this.
|
|
809
|
+
if (!this.replayEnabled || !this.replaySampled) return;
|
|
810
|
+
this.resumeOrRotateRecording();
|
|
851
811
|
this.setupReplayLifecycle();
|
|
852
812
|
});
|
|
853
813
|
}
|
|
@@ -856,6 +816,14 @@ var DataxamasTracking = class {
|
|
|
856
816
|
if (!isBrowser()) return;
|
|
857
817
|
void this.sendEvent({ name, data });
|
|
858
818
|
}
|
|
819
|
+
/**
|
|
820
|
+
* Liga os próximos eventos ao usuário logado no site (o id do seu sistema), para a retenção reconhecer quem volta
|
|
821
|
+
* em outro navegador ou depois da virada do mês. O servidor guarda só um hash. `null` desfaz, no logout.
|
|
822
|
+
*/
|
|
823
|
+
identify(userId) {
|
|
824
|
+
if (!isBrowser()) return;
|
|
825
|
+
this.userId = _optionalChain([userId, 'optionalAccess', _29 => _29.trim, 'call', _30 => _30()]) || null;
|
|
826
|
+
}
|
|
859
827
|
start() {
|
|
860
828
|
if (!isBrowser()) {
|
|
861
829
|
throw new Error("DataxamasTracking: This SDK can only be used in a browser environment.");
|
package/dist/index.d.cts
CHANGED
|
@@ -109,14 +109,15 @@ declare class DataxamasTracking {
|
|
|
109
109
|
private apiKey;
|
|
110
110
|
private baseUrl;
|
|
111
111
|
private allowLocalhost;
|
|
112
|
-
private
|
|
112
|
+
private started;
|
|
113
113
|
private currentPage;
|
|
114
|
+
private userId;
|
|
114
115
|
private heatMapEnabled;
|
|
115
116
|
private replayEnabled;
|
|
116
117
|
private webVitalsEnabled;
|
|
117
118
|
private pendingVitals;
|
|
118
119
|
private vitalsPath;
|
|
119
|
-
private
|
|
120
|
+
private replaySampled;
|
|
120
121
|
private eventQueue;
|
|
121
122
|
private isProcessing;
|
|
122
123
|
private rrwebRecord;
|
|
@@ -137,8 +138,6 @@ declare class DataxamasTracking {
|
|
|
137
138
|
private lastActivitySavedAt;
|
|
138
139
|
constructor(options: DataxamasTrackingOptions);
|
|
139
140
|
private isLocalhost;
|
|
140
|
-
private getSessionId;
|
|
141
|
-
private createSessionId;
|
|
142
141
|
private processQueue;
|
|
143
142
|
private utmQuery;
|
|
144
143
|
private sendEvent;
|
|
@@ -153,11 +152,6 @@ declare class DataxamasTracking {
|
|
|
153
152
|
private flushVitals;
|
|
154
153
|
private trackWebVitals;
|
|
155
154
|
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
155
|
private isRecording;
|
|
162
156
|
private exceededReplayCaps;
|
|
163
157
|
private loadRecording;
|
|
@@ -190,6 +184,11 @@ declare class DataxamasTracking {
|
|
|
190
184
|
private startTracking;
|
|
191
185
|
/** Evento customizado (ex.: `signup_done`), usado em metas e funis. Antes do `start()` não envia nada. */
|
|
192
186
|
track(name: string, data?: Record<string, unknown>): void;
|
|
187
|
+
/**
|
|
188
|
+
* Liga os próximos eventos ao usuário logado no site (o id do seu sistema), para a retenção reconhecer quem volta
|
|
189
|
+
* em outro navegador ou depois da virada do mês. O servidor guarda só um hash. `null` desfaz, no logout.
|
|
190
|
+
*/
|
|
191
|
+
identify(userId: string | null): void;
|
|
193
192
|
start(): void;
|
|
194
193
|
}
|
|
195
194
|
|
package/dist/index.d.ts
CHANGED
|
@@ -109,14 +109,15 @@ declare class DataxamasTracking {
|
|
|
109
109
|
private apiKey;
|
|
110
110
|
private baseUrl;
|
|
111
111
|
private allowLocalhost;
|
|
112
|
-
private
|
|
112
|
+
private started;
|
|
113
113
|
private currentPage;
|
|
114
|
+
private userId;
|
|
114
115
|
private heatMapEnabled;
|
|
115
116
|
private replayEnabled;
|
|
116
117
|
private webVitalsEnabled;
|
|
117
118
|
private pendingVitals;
|
|
118
119
|
private vitalsPath;
|
|
119
|
-
private
|
|
120
|
+
private replaySampled;
|
|
120
121
|
private eventQueue;
|
|
121
122
|
private isProcessing;
|
|
122
123
|
private rrwebRecord;
|
|
@@ -137,8 +138,6 @@ declare class DataxamasTracking {
|
|
|
137
138
|
private lastActivitySavedAt;
|
|
138
139
|
constructor(options: DataxamasTrackingOptions);
|
|
139
140
|
private isLocalhost;
|
|
140
|
-
private getSessionId;
|
|
141
|
-
private createSessionId;
|
|
142
141
|
private processQueue;
|
|
143
142
|
private utmQuery;
|
|
144
143
|
private sendEvent;
|
|
@@ -153,11 +152,6 @@ declare class DataxamasTracking {
|
|
|
153
152
|
private flushVitals;
|
|
154
153
|
private trackWebVitals;
|
|
155
154
|
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
155
|
private isRecording;
|
|
162
156
|
private exceededReplayCaps;
|
|
163
157
|
private loadRecording;
|
|
@@ -190,6 +184,11 @@ declare class DataxamasTracking {
|
|
|
190
184
|
private startTracking;
|
|
191
185
|
/** Evento customizado (ex.: `signup_done`), usado em metas e funis. Antes do `start()` não envia nada. */
|
|
192
186
|
track(name: string, data?: Record<string, unknown>): void;
|
|
187
|
+
/**
|
|
188
|
+
* Liga os próximos eventos ao usuário logado no site (o id do seu sistema), para a retenção reconhecer quem volta
|
|
189
|
+
* em outro navegador ou depois da virada do mês. O servidor guarda só um hash. `null` desfaz, no logout.
|
|
190
|
+
*/
|
|
191
|
+
identify(userId: string | null): void;
|
|
193
192
|
start(): void;
|
|
194
193
|
}
|
|
195
194
|
|
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.
|
|
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,15 @@ 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.
|
|
359
|
+
this.started = false;
|
|
360
360
|
this.currentPage = isBrowser() ? window.location.pathname : "";
|
|
361
|
+
this.userId = null;
|
|
361
362
|
this.heatMapEnabled = options.heatMapEnabled ?? true;
|
|
362
363
|
this.replayEnabled = options.replayEnabled ?? true;
|
|
363
364
|
this.webVitalsEnabled = options.webVitalsEnabled ?? true;
|
|
364
365
|
this.pendingVitals = /* @__PURE__ */ new Map();
|
|
365
366
|
this.vitalsPath = "";
|
|
366
|
-
this.
|
|
367
|
+
this.replaySampled = false;
|
|
367
368
|
this.eventQueue = [];
|
|
368
369
|
this.isProcessing = false;
|
|
369
370
|
this.rrwebRecord = null;
|
|
@@ -386,21 +387,6 @@ var DataxamasTracking = class {
|
|
|
386
387
|
isLocalhost() {
|
|
387
388
|
return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
|
|
388
389
|
}
|
|
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
390
|
async processQueue() {
|
|
405
391
|
if (this.isProcessing || this.eventQueue.length === 0) return;
|
|
406
392
|
this.isProcessing = true;
|
|
@@ -436,14 +422,14 @@ var DataxamasTracking = class {
|
|
|
436
422
|
path = this.currentPage,
|
|
437
423
|
data = {}
|
|
438
424
|
}) {
|
|
439
|
-
if (!this.
|
|
425
|
+
if (!this.started) return;
|
|
440
426
|
this.eventQueue.push({
|
|
441
427
|
name,
|
|
442
428
|
path,
|
|
443
429
|
referrer: document.referrer,
|
|
444
430
|
query: name === "PAGE_VIEW" ? this.utmQuery() : void 0,
|
|
445
|
-
|
|
446
|
-
|
|
431
|
+
data,
|
|
432
|
+
userId: this.userId ?? void 0
|
|
447
433
|
});
|
|
448
434
|
await this.processQueue();
|
|
449
435
|
}
|
|
@@ -468,9 +454,7 @@ var DataxamasTracking = class {
|
|
|
468
454
|
const cfg = await res.json();
|
|
469
455
|
if (typeof cfg.heatMapEnabled === "boolean") this.heatMapEnabled = cfg.heatMapEnabled;
|
|
470
456
|
if (typeof cfg.replayEnabled === "boolean") this.replayEnabled = cfg.replayEnabled;
|
|
471
|
-
if (typeof cfg.
|
|
472
|
-
this.replaySampleRate = cfg.replaySampleRate;
|
|
473
|
-
}
|
|
457
|
+
if (typeof cfg.replaySampled === "boolean") this.replaySampled = cfg.replaySampled;
|
|
474
458
|
} catch (_) {
|
|
475
459
|
}
|
|
476
460
|
}
|
|
@@ -551,14 +535,13 @@ var DataxamasTracking = class {
|
|
|
551
535
|
});
|
|
552
536
|
}
|
|
553
537
|
flushVitals() {
|
|
554
|
-
if (
|
|
538
|
+
if (this.pendingVitals.size === 0) return;
|
|
555
539
|
const metrics = Array.from(this.pendingVitals.values());
|
|
556
540
|
this.pendingVitals.clear();
|
|
557
541
|
window.fetch(`${this.baseUrl}/api/v1/vitals`, {
|
|
558
542
|
method: "POST",
|
|
559
543
|
headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json" },
|
|
560
544
|
body: JSON.stringify({
|
|
561
|
-
sessionId: this.sessionId,
|
|
562
545
|
path: this.vitalsPath,
|
|
563
546
|
viewportWidth: window.innerWidth,
|
|
564
547
|
metrics
|
|
@@ -602,27 +585,6 @@ var DataxamasTracking = class {
|
|
|
602
585
|
const layoutFingerprint = `${this.currentPage}:${Math.round(pageWidth / 100)}x${Math.round(pageHeight / 100)}`;
|
|
603
586
|
return { viewportWidth, viewportHeight, pageWidth, pageHeight, layoutFingerprint };
|
|
604
587
|
}
|
|
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
588
|
isRecording() {
|
|
627
589
|
return Boolean(this.stopRrweb);
|
|
628
590
|
}
|
|
@@ -638,10 +600,9 @@ var DataxamasTracking = class {
|
|
|
638
600
|
}
|
|
639
601
|
}
|
|
640
602
|
saveRecording() {
|
|
641
|
-
if (!this.
|
|
603
|
+
if (!this.recordingId || !this.recordingStartedAt) return;
|
|
642
604
|
const stored = {
|
|
643
605
|
id: this.recordingId,
|
|
644
|
-
sessionId: this.sessionId,
|
|
645
606
|
startedAt: this.recordingStartedAt,
|
|
646
607
|
startedAtMs: this.recordingStartedAtMs,
|
|
647
608
|
seq: this.recordingSeq,
|
|
@@ -667,7 +628,7 @@ var DataxamasTracking = class {
|
|
|
667
628
|
/** Reload ou navegação completa na mesma aba continua a gravação, se a visita ainda estiver ativa. */
|
|
668
629
|
resumeOrRotateRecording() {
|
|
669
630
|
const stored = this.loadRecording();
|
|
670
|
-
if (!stored || stored.capped ||
|
|
631
|
+
if (!stored || stored.capped || Date.now() - stored.lastActivityAt >= REPLAY_IDLE_TIMEOUT_MS) {
|
|
671
632
|
this.rotateRecording();
|
|
672
633
|
return;
|
|
673
634
|
}
|
|
@@ -690,7 +651,6 @@ var DataxamasTracking = class {
|
|
|
690
651
|
const events = this.replayBuffer;
|
|
691
652
|
this.replayBuffer = [];
|
|
692
653
|
const payload = JSON.stringify({
|
|
693
|
-
sessionId: this.sessionId,
|
|
694
654
|
recordingId: this.recordingId,
|
|
695
655
|
path: this.currentPage,
|
|
696
656
|
startedAt: this.recordingStartedAt,
|
|
@@ -839,15 +799,15 @@ var DataxamasTracking = class {
|
|
|
839
799
|
});
|
|
840
800
|
}
|
|
841
801
|
startTracking() {
|
|
842
|
-
this.
|
|
843
|
-
this.resumeOrRotateRecording();
|
|
802
|
+
this.started = true;
|
|
844
803
|
this.sendEvent({ name: "PAGE_VIEW" });
|
|
845
804
|
this.trackPageChanges();
|
|
846
805
|
this.trackClicks();
|
|
847
806
|
this.trackScroll();
|
|
848
807
|
this.trackWebVitals();
|
|
849
808
|
this.fetchWebsiteConfig().then(() => {
|
|
850
|
-
if (!this.replayEnabled || !this.
|
|
809
|
+
if (!this.replayEnabled || !this.replaySampled) return;
|
|
810
|
+
this.resumeOrRotateRecording();
|
|
851
811
|
this.setupReplayLifecycle();
|
|
852
812
|
});
|
|
853
813
|
}
|
|
@@ -856,6 +816,14 @@ var DataxamasTracking = class {
|
|
|
856
816
|
if (!isBrowser()) return;
|
|
857
817
|
void this.sendEvent({ name, data });
|
|
858
818
|
}
|
|
819
|
+
/**
|
|
820
|
+
* Liga os próximos eventos ao usuário logado no site (o id do seu sistema), para a retenção reconhecer quem volta
|
|
821
|
+
* em outro navegador ou depois da virada do mês. O servidor guarda só um hash. `null` desfaz, no logout.
|
|
822
|
+
*/
|
|
823
|
+
identify(userId) {
|
|
824
|
+
if (!isBrowser()) return;
|
|
825
|
+
this.userId = userId?.trim() || null;
|
|
826
|
+
}
|
|
859
827
|
start() {
|
|
860
828
|
if (!isBrowser()) {
|
|
861
829
|
throw new Error("DataxamasTracking: This SDK can only be used in a browser environment.");
|