@dataxamas/tracker-js 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,672 @@
1
+ // src/constants.ts
2
+ var BASE_URL = "https://dataxamas.izakdvlpr.com";
3
+
4
+ // src/utils.ts
5
+ var isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined";
6
+
7
+ // src/exceptions.ts
8
+ function normalizeError(error) {
9
+ if (error instanceof Error) {
10
+ return { name: error.name || "Error", message: error.message || String(error), stack: error.stack };
11
+ }
12
+ if (typeof error === "string") {
13
+ return { name: "Error", message: error };
14
+ }
15
+ try {
16
+ return { name: "Error", message: JSON.stringify(error) };
17
+ } catch {
18
+ return { name: "Error", message: String(error) };
19
+ }
20
+ }
21
+ var DataxamasExceptions = class {
22
+ constructor(config) {
23
+ this.config = config;
24
+ this.installed = false;
25
+ }
26
+ captureException(error, context) {
27
+ if (!this.config.enabled) return Promise.resolve();
28
+ const { name, message, stack } = normalizeError(error);
29
+ const payload = {
30
+ name,
31
+ message,
32
+ stack,
33
+ source: isBrowser() ? "CLIENT" : "SERVER",
34
+ level: "ERROR",
35
+ environment: this.config.environment,
36
+ release: this.config.release,
37
+ data: context
38
+ };
39
+ if (isBrowser()) {
40
+ payload.url = window.location.href;
41
+ payload.userAgent = window.navigator.userAgent;
42
+ }
43
+ return this.send(payload);
44
+ }
45
+ install() {
46
+ if (this.installed || !this.config.enabled) return;
47
+ this.installed = true;
48
+ if (isBrowser()) {
49
+ window.addEventListener("error", (event) => {
50
+ this.captureException(event.error ?? event.message);
51
+ });
52
+ window.addEventListener("unhandledrejection", (event) => {
53
+ this.captureException(event.reason ?? "Unhandled promise rejection");
54
+ });
55
+ return;
56
+ }
57
+ if (typeof process !== "undefined" && typeof process.on === "function") {
58
+ process.on("uncaughtException", (error) => this.captureException(error));
59
+ process.on("unhandledRejection", (reason) => this.captureException(reason ?? "Unhandled promise rejection"));
60
+ }
61
+ }
62
+ send(payload) {
63
+ return fetch(`${this.config.baseUrl}/api/v1/errors`, {
64
+ method: "POST",
65
+ headers: {
66
+ Authorization: `Bearer ${this.config.apiKey}`,
67
+ "Content-Type": "application/json"
68
+ },
69
+ body: JSON.stringify(payload)
70
+ }).then(async (response) => {
71
+ if (response.ok) return;
72
+ const body = await response.json().catch(() => null);
73
+ const errorMessage = body?.error?.message ?? `Request failed with status ${response.status}`;
74
+ console.error(`[Dataxamas] ${errorMessage}`);
75
+ }).catch(() => {
76
+ });
77
+ }
78
+ };
79
+
80
+ // src/logs.ts
81
+ import { pino } from "pino";
82
+ var CUSTOM_LEVELS = { log: 25, success: 35 };
83
+ var LEVEL_METHOD = {
84
+ DEBUG: "debug",
85
+ INFO: "info",
86
+ WARN: "warn",
87
+ LOG: "log",
88
+ SUCCESS: "success"
89
+ };
90
+ var ANSI = {
91
+ DEBUG: "\x1B[90m",
92
+ INFO: "\x1B[36m",
93
+ WARN: "\x1B[33m",
94
+ LOG: "\x1B[37m",
95
+ SUCCESS: "\x1B[32m",
96
+ ERROR: "\x1B[31m"
97
+ };
98
+ var CSS = {
99
+ DEBUG: "color:#888",
100
+ INFO: "color:#0aa",
101
+ WARN: "color:#c90",
102
+ LOG: "color:#bbb",
103
+ SUCCESS: "color:#0a0",
104
+ ERROR: "color:#e33"
105
+ };
106
+ var RESET = "\x1B[0m";
107
+ function formatData(data) {
108
+ if (data === void 0) return "";
109
+ try {
110
+ return ` ${JSON.stringify(data)}`;
111
+ } catch {
112
+ return ` ${String(data)}`;
113
+ }
114
+ }
115
+ var nodeDestination = {
116
+ write(line) {
117
+ try {
118
+ const record = JSON.parse(line);
119
+ const level = String(record.level ?? "LOG").toUpperCase();
120
+ const color = ANSI[level] ?? "";
121
+ process.stdout.write(`${color}[${level}]${RESET} ${record.msg ?? ""}${formatData(record.data)}
122
+ `);
123
+ } catch {
124
+ process.stdout.write(line);
125
+ }
126
+ }
127
+ };
128
+ function browserWriter(level) {
129
+ return (record) => {
130
+ const method = level === "WARN" ? "warn" : "log";
131
+ const args = [`%c[${level}]%c ${record.msg ?? ""}`, CSS[level] ?? "", ""];
132
+ if (record.data !== void 0) args.push(record.data);
133
+ console[method](...args);
134
+ };
135
+ }
136
+ function createLogger() {
137
+ return pino(
138
+ {
139
+ customLevels: CUSTOM_LEVELS,
140
+ level: "debug",
141
+ base: void 0,
142
+ formatters: { level: (label) => ({ level: label }) },
143
+ browser: {
144
+ asObject: true,
145
+ write: {
146
+ debug: browserWriter("DEBUG"),
147
+ info: browserWriter("INFO"),
148
+ warn: browserWriter("WARN"),
149
+ log: browserWriter("LOG"),
150
+ success: browserWriter("SUCCESS"),
151
+ error: browserWriter("WARN")
152
+ }
153
+ }
154
+ },
155
+ nodeDestination
156
+ );
157
+ }
158
+ var DataxamasLogs = class {
159
+ constructor(config) {
160
+ this.config = config;
161
+ this.logger = createLogger();
162
+ }
163
+ debug(message, data) {
164
+ return this.emit("DEBUG", message, data);
165
+ }
166
+ info(message, data) {
167
+ return this.emit("INFO", message, data);
168
+ }
169
+ warn(message, data) {
170
+ return this.emit("WARN", message, data);
171
+ }
172
+ log(message, data) {
173
+ return this.emit("LOG", message, data);
174
+ }
175
+ success(message, data) {
176
+ return this.emit("SUCCESS", message, data);
177
+ }
178
+ emit(level, message, data) {
179
+ if (!this.config.enableLogs) return Promise.resolve();
180
+ const method = LEVEL_METHOD[level];
181
+ const fn = this.logger[method];
182
+ if (data === void 0) {
183
+ fn.call(this.logger, message);
184
+ } else {
185
+ fn.call(this.logger, { data }, message);
186
+ }
187
+ return this.send(level, message, data);
188
+ }
189
+ send(level, message, data) {
190
+ return fetch(`${this.config.baseUrl}/api/v1/logs`, {
191
+ method: "POST",
192
+ headers: {
193
+ Authorization: `Bearer ${this.config.apiKey}`,
194
+ "Content-Type": "application/json"
195
+ },
196
+ body: JSON.stringify({ level, message, data, environment: this.config.environment })
197
+ }).then(async (response) => {
198
+ if (response.ok) return;
199
+ const body = await response.json().catch(() => null);
200
+ const errorMessage = body?.error?.message ?? `Request failed with status ${response.status}`;
201
+ this.logger.error(`[Dataxamas] ${errorMessage}`);
202
+ }).catch((error) => {
203
+ this.logger.error(`[Dataxamas] Failed to send log: ${error}`);
204
+ });
205
+ }
206
+ };
207
+
208
+ // src/tracking.ts
209
+ var QUEUE_DELAY_MS = 4e3;
210
+ var STORAGE_PREFIX = "@dataxamas";
211
+ var REPLAY_FLUSH_SIZE = 100;
212
+ var REPLAY_FLUSH_INTERVAL_MS = 5e3;
213
+ var CAP_CHECK_MS = 1e3;
214
+ var MAX_RECORDING_MS = 10 * 60 * 1e3;
215
+ var MAX_EVENTS_PER_RECORDING = 2e4;
216
+ var KEEPALIVE_LIMIT_BYTES = 60 * 1024;
217
+ var REPLAY_SAMPLE_RATE = 1;
218
+ var DataxamasTracking = class {
219
+ constructor(options) {
220
+ /**
221
+ * Primeiro gesto da pageview liga o gravador; os seguintes só o religam se ele tiver parado
222
+ * (aba que foi para segundo plano, por exemplo), sempre no mesmo `recordingId`.
223
+ */
224
+ this.markReplayActivity = () => {
225
+ if (this.replayHalted || this.replayCapped || this.isRecording()) return;
226
+ if (document.visibilityState === "hidden") return;
227
+ this.startReplayRecorder();
228
+ };
229
+ if (!options.apiKey) {
230
+ throw new Error("DataxamasTracking: apiKey is required.");
231
+ }
232
+ this.apiKey = options.apiKey;
233
+ this.baseUrl = options.baseUrl ?? BASE_URL;
234
+ this.allowLocalhost = options.allowLocalhost ?? false;
235
+ this.sessionId = null;
236
+ this.currentPage = isBrowser() ? window.location.pathname : "";
237
+ this.heatMapEnabled = options.heatMapEnabled ?? true;
238
+ this.replayEnabled = options.replayEnabled ?? true;
239
+ this.eventQueue = [];
240
+ this.isProcessing = false;
241
+ this.rrwebRecord = null;
242
+ this.stopRrweb = void 0;
243
+ this.replayBuffer = [];
244
+ this.snapshotFlushed = false;
245
+ this.replayHalted = false;
246
+ this.replayCapped = false;
247
+ this.cutoffPending = false;
248
+ this.replayStarting = false;
249
+ this.lifecycleReady = false;
250
+ this.recordingId = null;
251
+ this.recordingStartedAt = null;
252
+ this.recordingStartedAtMs = 0;
253
+ this.recordingSeq = 0;
254
+ this.recordingEvents = 0;
255
+ }
256
+ isLocalhost() {
257
+ return window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1";
258
+ }
259
+ getSessionId() {
260
+ try {
261
+ return window.localStorage.getItem(`${STORAGE_PREFIX}/session`);
262
+ } catch (_) {
263
+ return null;
264
+ }
265
+ }
266
+ createSessionId() {
267
+ const id = crypto.randomUUID();
268
+ try {
269
+ window.localStorage.setItem(`${STORAGE_PREFIX}/session`, id);
270
+ } catch (_) {
271
+ }
272
+ return id;
273
+ }
274
+ async processQueue() {
275
+ if (this.isProcessing || this.eventQueue.length === 0) return;
276
+ this.isProcessing = true;
277
+ const event = this.eventQueue.shift();
278
+ try {
279
+ await window.fetch(`${this.baseUrl}/api/v1/events`, {
280
+ method: "POST",
281
+ headers: {
282
+ Authorization: `Bearer ${this.apiKey}`,
283
+ "Content-Type": "application/json",
284
+ "x-screen-width": String(window.screen.width),
285
+ "x-screen-height": String(window.screen.height)
286
+ },
287
+ body: JSON.stringify(event)
288
+ });
289
+ } catch (_) {
290
+ }
291
+ setTimeout(() => {
292
+ this.isProcessing = false;
293
+ this.processQueue();
294
+ }, QUEUE_DELAY_MS);
295
+ }
296
+ async sendEvent({
297
+ name,
298
+ path = this.currentPage,
299
+ data = {}
300
+ }) {
301
+ if (!this.sessionId) return;
302
+ this.eventQueue.push({
303
+ name,
304
+ path,
305
+ referrer: document.referrer,
306
+ sessionId: this.sessionId,
307
+ data
308
+ });
309
+ await this.processQueue();
310
+ }
311
+ trackPageChanges() {
312
+ let lastPath = this.currentPage;
313
+ setInterval(() => {
314
+ const newPath = window.location.pathname;
315
+ if (newPath !== lastPath) {
316
+ this.stopReplayRecorder(false);
317
+ this.rotateRecording();
318
+ this.currentPage = newPath;
319
+ this.sendEvent({ name: "PAGE_VIEW", path: newPath });
320
+ lastPath = newPath;
321
+ }
322
+ }, 500);
323
+ }
324
+ async fetchWebsiteConfig() {
325
+ try {
326
+ const res = await window.fetch(`${this.baseUrl}/api/v1/config`, {
327
+ headers: { Authorization: `Bearer ${this.apiKey}` }
328
+ });
329
+ if (!res.ok) return;
330
+ const cfg = await res.json();
331
+ if (typeof cfg.heatMapEnabled === "boolean") this.heatMapEnabled = cfg.heatMapEnabled;
332
+ if (typeof cfg.replayEnabled === "boolean") this.replayEnabled = cfg.replayEnabled;
333
+ } catch (_) {
334
+ }
335
+ }
336
+ buildHeatMap(e) {
337
+ if (!this.heatMapEnabled) return null;
338
+ const viewportWidth = window.innerWidth;
339
+ const viewportHeight = window.innerHeight;
340
+ if (!viewportWidth || !viewportHeight) return null;
341
+ const doc = document.documentElement;
342
+ const pageWidth = Math.max(doc.scrollWidth, viewportWidth);
343
+ const pageHeight = Math.max(doc.scrollHeight, viewportHeight);
344
+ const xRatio = Math.min(1, Math.max(0, e.clientX / viewportWidth));
345
+ const yRatio = Math.min(1, Math.max(0, e.clientY / viewportHeight));
346
+ const interactionType = e.pointerType === "touch" ? "TAP" : "CLICK";
347
+ return {
348
+ interactionType,
349
+ xRatio,
350
+ yRatio,
351
+ viewportWidth,
352
+ viewportHeight,
353
+ pageWidth,
354
+ pageHeight,
355
+ scrollX: Math.round(window.scrollX || 0),
356
+ scrollY: Math.round(window.scrollY || 0),
357
+ layoutFingerprint: `${this.currentPage}:${Math.round(pageWidth / 100)}x${Math.round(pageHeight / 100)}`
358
+ };
359
+ }
360
+ safeText(t) {
361
+ const tag = t.tagName?.toLowerCase();
362
+ if (tag === "input" || tag === "textarea" || tag === "select" || tag === "option") return void 0;
363
+ if (t.isContentEditable) return void 0;
364
+ return t.textContent?.trim().substring(0, 50);
365
+ }
366
+ safeString(value, max) {
367
+ if (typeof value !== "string") return void 0;
368
+ const trimmed = value.trim();
369
+ return trimmed ? trimmed.substring(0, max) : void 0;
370
+ }
371
+ getTarget(e) {
372
+ const t = e.target;
373
+ if (!t || t.nodeType !== 1) return null;
374
+ return t;
375
+ }
376
+ trackClicks() {
377
+ document.addEventListener(
378
+ "click",
379
+ (e) => {
380
+ const t = this.getTarget(e);
381
+ if (!t) return;
382
+ this.sendEvent({
383
+ name: "CLICK",
384
+ data: {
385
+ tagName: t.tagName?.toLowerCase(),
386
+ text: this.safeText(t),
387
+ href: this.safeString(t.href, 2048),
388
+ id: this.safeString(t.id, 128),
389
+ classes: this.safeString(typeof t.className === "string" ? t.className : t.getAttribute?.("class"), 256),
390
+ heatMap: this.buildHeatMap(e)
391
+ }
392
+ });
393
+ },
394
+ true
395
+ );
396
+ }
397
+ trackScroll() {
398
+ let timeout;
399
+ window.addEventListener("scroll", () => {
400
+ clearTimeout(timeout);
401
+ timeout = setTimeout(() => {
402
+ const max = document.documentElement.scrollHeight - window.innerHeight;
403
+ if (max <= 0) return;
404
+ const percentage = Math.round(window.scrollY / max * 100);
405
+ this.sendEvent({
406
+ name: "SCROLL",
407
+ data: { percentage }
408
+ });
409
+ }, 1e3);
410
+ });
411
+ }
412
+ measureReplay() {
413
+ const doc = document.documentElement;
414
+ const viewportWidth = window.innerWidth;
415
+ const viewportHeight = window.innerHeight;
416
+ const pageWidth = Math.max(doc.scrollWidth, viewportWidth);
417
+ const pageHeight = Math.max(doc.scrollHeight, viewportHeight);
418
+ const layoutFingerprint = `${this.currentPage}:${Math.round(pageWidth / 100)}x${Math.round(pageHeight / 100)}`;
419
+ return { viewportWidth, viewportHeight, pageWidth, pageHeight, layoutFingerprint };
420
+ }
421
+ /**
422
+ * Sorteio por visitante, não por gravação: quem entrou no sorteio tem a sessão inteira, em vez
423
+ * de um replay picotado que não dá para assistir.
424
+ */
425
+ isReplaySampled() {
426
+ if (REPLAY_SAMPLE_RATE >= 1) return true;
427
+ if (REPLAY_SAMPLE_RATE <= 0) return false;
428
+ try {
429
+ const stored = window.localStorage.getItem(`${STORAGE_PREFIX}/replay-sample`);
430
+ if (stored === "1") return true;
431
+ if (stored === "0") return false;
432
+ const sampled = Math.random() < REPLAY_SAMPLE_RATE;
433
+ window.localStorage.setItem(`${STORAGE_PREFIX}/replay-sample`, sampled ? "1" : "0");
434
+ return sampled;
435
+ } catch (_) {
436
+ return Math.random() < REPLAY_SAMPLE_RATE;
437
+ }
438
+ }
439
+ isRecording() {
440
+ return Boolean(this.stopRrweb);
441
+ }
442
+ exceededReplayCaps() {
443
+ return this.recordingEvents >= MAX_EVENTS_PER_RECORDING || Date.now() - this.recordingStartedAtMs >= MAX_RECORDING_MS;
444
+ }
445
+ rotateRecording() {
446
+ this.replayCapped = false;
447
+ this.recordingId = crypto.randomUUID();
448
+ this.recordingStartedAt = (/* @__PURE__ */ new Date()).toISOString();
449
+ this.recordingStartedAtMs = Date.now();
450
+ this.recordingSeq = 0;
451
+ this.recordingEvents = 0;
452
+ }
453
+ flushReplay(urgent) {
454
+ if (this.replayBuffer.length === 0 || !this.recordingId) return;
455
+ const events = this.replayBuffer;
456
+ this.replayBuffer = [];
457
+ const payload = JSON.stringify({
458
+ sessionId: this.sessionId,
459
+ recordingId: this.recordingId,
460
+ path: this.currentPage,
461
+ startedAt: this.recordingStartedAt,
462
+ seq: this.recordingSeq++,
463
+ mode: "REPLAY",
464
+ ...this.measureReplay(),
465
+ events
466
+ });
467
+ void this.sendReplayBatch(payload, urgent);
468
+ }
469
+ /**
470
+ * Um full snapshot com CSS inline passa fácil de 100KB cru, e era isso que tomava 413 no
471
+ * ingest. O servidor aceita os dois formatos, então navegador sem `CompressionStream` manda cru.
472
+ */
473
+ async compressPayload(payload) {
474
+ if (typeof CompressionStream === "undefined") return null;
475
+ try {
476
+ const stream = new Blob([payload]).stream().pipeThrough(new CompressionStream("gzip"));
477
+ return await new Response(stream).blob();
478
+ } catch (_) {
479
+ return null;
480
+ }
481
+ }
482
+ async sendReplayBatch(payload, urgent) {
483
+ const url = `${this.baseUrl}/api/v1/replay`;
484
+ const rawSize = new Blob([payload]).size;
485
+ const compressed = urgent && rawSize < KEEPALIVE_LIMIT_BYTES ? null : await this.compressPayload(payload);
486
+ const headers = {
487
+ Authorization: `Bearer ${this.apiKey}`,
488
+ "Content-Type": "application/json"
489
+ };
490
+ if (compressed) headers["Content-Encoding"] = "gzip";
491
+ const size = compressed ? compressed.size : rawSize;
492
+ try {
493
+ const res = await window.fetch(url, {
494
+ method: "POST",
495
+ headers,
496
+ body: compressed ?? payload,
497
+ // O flush do full snapshot sai de dentro do `emit`, no exato momento em que o visitante
498
+ // interage: sem `keepalive` o unload logo em seguida cancelava o POST e a gravação ficava
499
+ // sem o `seq` 0. O browser recusa keepalive acima de 64KB, então só liga quando cabe.
500
+ keepalive: size < KEEPALIVE_LIMIT_BYTES,
501
+ // Sem isto um embed same-origin mandaria cookie e o `*` do ingest reprovaria a resposta.
502
+ credentials: "omit"
503
+ });
504
+ if (!res.ok) return;
505
+ const body = await res.json().catch(() => null);
506
+ if (body?.stop) {
507
+ this.replayHalted = true;
508
+ this.stopReplayRecorder(false);
509
+ }
510
+ } catch (_) {
511
+ }
512
+ }
513
+ stopReplayRecorder(urgent) {
514
+ this.flushReplay(urgent);
515
+ if (this.stopRrweb) {
516
+ this.stopRrweb();
517
+ this.stopRrweb = void 0;
518
+ }
519
+ }
520
+ /**
521
+ * Bateu o teto: encerra a gravação e fica parado até a próxima pageview, em vez de emendar um
522
+ * clipe atrás do outro enquanto a página só anima sozinha.
523
+ */
524
+ scheduleReplayCutoff() {
525
+ if (this.cutoffPending) return;
526
+ this.cutoffPending = true;
527
+ this.replayCapped = true;
528
+ setTimeout(() => {
529
+ this.cutoffPending = false;
530
+ this.stopReplayRecorder(false);
531
+ }, 0);
532
+ }
533
+ setupReplayLifecycle() {
534
+ if (this.lifecycleReady) return;
535
+ this.lifecycleReady = true;
536
+ for (const type of ["pointerdown", "keydown", "wheel", "touchstart", "scroll"]) {
537
+ window.addEventListener(type, this.markReplayActivity, { passive: true, capture: true });
538
+ }
539
+ setInterval(() => {
540
+ if (this.isRecording()) this.flushReplay(false);
541
+ }, REPLAY_FLUSH_INTERVAL_MS);
542
+ setInterval(() => {
543
+ if (!this.isRecording()) return;
544
+ if (this.exceededReplayCaps()) this.scheduleReplayCutoff();
545
+ }, CAP_CHECK_MS);
546
+ window.addEventListener("pagehide", () => this.stopReplayRecorder(true));
547
+ document.addEventListener("visibilitychange", () => {
548
+ if (document.visibilityState === "hidden") this.stopReplayRecorder(true);
549
+ });
550
+ }
551
+ async startReplayRecorder() {
552
+ if (this.isRecording() || this.replayHalted || this.replayStarting) return;
553
+ this.replayStarting = true;
554
+ try {
555
+ if (!this.rrwebRecord) {
556
+ const { record } = await import("./rrweb-F6UF6U3D.js");
557
+ this.rrwebRecord = record;
558
+ }
559
+ } catch (_) {
560
+ return;
561
+ } finally {
562
+ this.replayStarting = false;
563
+ }
564
+ if (this.isRecording() || this.replayHalted) return;
565
+ if (!this.recordingId) this.rotateRecording();
566
+ this.snapshotFlushed = false;
567
+ this.stopRrweb = this.rrwebRecord({
568
+ emit: (event) => {
569
+ this.replayBuffer.push(event);
570
+ this.recordingEvents++;
571
+ if (!this.snapshotFlushed && event.type === 2) {
572
+ this.snapshotFlushed = true;
573
+ this.flushReplay(false);
574
+ return;
575
+ }
576
+ if (this.exceededReplayCaps()) {
577
+ this.scheduleReplayCutoff();
578
+ return;
579
+ }
580
+ if (this.replayBuffer.length >= REPLAY_FLUSH_SIZE) this.flushReplay(false);
581
+ },
582
+ maskAllInputs: true,
583
+ maskTextClass: "dx-mask",
584
+ blockClass: "dx-block",
585
+ sampling: { mousemove: 150, scroll: 300, media: 800, input: "last" },
586
+ inlineStylesheet: true,
587
+ inlineImages: false,
588
+ collectFonts: false,
589
+ slimDOMOptions: {
590
+ script: true,
591
+ comment: true,
592
+ headFavicon: true,
593
+ headMetaDescKeywords: true,
594
+ headMetaSocial: true,
595
+ headMetaRobots: true,
596
+ headMetaHttpEquiv: true,
597
+ headMetaAuthorship: true,
598
+ headMetaVerification: true
599
+ }
600
+ });
601
+ }
602
+ startTracking() {
603
+ this.sessionId = this.getSessionId() ?? this.createSessionId();
604
+ this.sendEvent({ name: "PAGE_VIEW" });
605
+ this.trackPageChanges();
606
+ this.trackClicks();
607
+ this.trackScroll();
608
+ this.fetchWebsiteConfig().then(() => {
609
+ if (!this.replayEnabled || !this.isReplaySampled()) return;
610
+ this.setupReplayLifecycle();
611
+ });
612
+ }
613
+ start() {
614
+ if (!isBrowser()) {
615
+ throw new Error("DataxamasTracking: This SDK can only be used in a browser environment.");
616
+ }
617
+ if (!this.allowLocalhost && this.isLocalhost()) return;
618
+ if (document.readyState === "loading") {
619
+ document.addEventListener("DOMContentLoaded", this.startTracking.bind(this));
620
+ } else {
621
+ this.startTracking();
622
+ }
623
+ }
624
+ };
625
+
626
+ // src/client.ts
627
+ var Dataxamas = class {
628
+ constructor(options) {
629
+ if (!options.apiKey) throw new Error("Dataxamas: apiKey is required.");
630
+ const baseUrl = options.baseUrl ?? BASE_URL;
631
+ this.logs = new DataxamasLogs({
632
+ apiKey: options.apiKey,
633
+ baseUrl,
634
+ enableLogs: options.enableLogs ?? true,
635
+ environment: options.environment
636
+ });
637
+ this.exceptions = new DataxamasExceptions({
638
+ apiKey: options.apiKey,
639
+ baseUrl,
640
+ enabled: options.enableErrorTracking ?? true,
641
+ environment: options.environment,
642
+ release: options.release
643
+ });
644
+ if (options.captureGlobalErrors ?? true) {
645
+ this.exceptions.install();
646
+ }
647
+ this.tracking = new DataxamasTracking({
648
+ apiKey: options.apiKey,
649
+ baseUrl,
650
+ replayEnabled: options.replayEnabled ?? true,
651
+ heatMapEnabled: options.heatMapEnabled ?? true,
652
+ allowLocalhost: options.allowLocalhost ?? false
653
+ });
654
+ }
655
+ };
656
+
657
+ // src/errors.ts
658
+ var DataxamasApiError = class extends Error {
659
+ constructor(code, message, status) {
660
+ super(message);
661
+ this.code = code;
662
+ this.status = status;
663
+ this.name = "DataxamasApiError";
664
+ }
665
+ };
666
+ export {
667
+ Dataxamas,
668
+ DataxamasApiError,
669
+ DataxamasExceptions,
670
+ DataxamasLogs,
671
+ DataxamasTracking
672
+ };