@techts/sdk 3.0.2

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,708 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ DebuggerSDK: () => DebuggerSDK
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+ var DebuggerSDK = class {
27
+ constructor(config) {
28
+ this.queue = [];
29
+ this.batchTimer = null;
30
+ this.metricsTimer = null;
31
+ this.navigationCount = 0;
32
+ this.errorCount = 0;
33
+ this.networkFailCount = 0;
34
+ this.longTaskCount = 0;
35
+ this.originalFetch = null;
36
+ this.originalXhrOpen = null;
37
+ this.originalXhrSend = null;
38
+ this.originalWsConstructor = null;
39
+ this.config = {
40
+ platform: "Browser",
41
+ version: "1.0.0",
42
+ batchInterval: 2e3,
43
+ maxBatchSize: 50,
44
+ captureConsole: true,
45
+ captureErrors: true,
46
+ captureNetwork: true,
47
+ captureResources: true,
48
+ captureWebVitals: true,
49
+ captureLongTasks: true,
50
+ captureNavigation: true,
51
+ captureVisibility: true,
52
+ captureCSP: true,
53
+ captureWebSockets: true,
54
+ slowRequestThresholdMs: 3e3,
55
+ metricsInterval: 3e4,
56
+ sessionId: "",
57
+ userId: "",
58
+ ...config
59
+ };
60
+ this.sessionId = this.config.sessionId || this.generateSessionId();
61
+ this.pageLoadTime = performance.now();
62
+ this.originalConsole = {};
63
+ for (const m of ["log", "warn", "error", "debug", "info", "trace", "assert"]) {
64
+ this.originalConsole[m] = console[m]?.bind(console);
65
+ }
66
+ if (this.config.captureConsole) this.interceptConsole();
67
+ if (this.config.captureErrors) this.captureGlobalErrors();
68
+ if (this.config.captureNetwork) this.interceptNetwork();
69
+ if (this.config.captureResources) this.captureResourceErrors();
70
+ if (this.config.captureWebVitals) this.captureWebVitals();
71
+ if (this.config.captureLongTasks) this.captureLongTasks();
72
+ if (this.config.captureNavigation) this.captureNavigation();
73
+ if (this.config.captureVisibility) this.captureVisibilityChanges();
74
+ if (this.config.captureCSP) this.captureCSPViolations();
75
+ if (this.config.captureWebSockets) this.interceptWebSockets();
76
+ this.batchTimer = setInterval(() => this.flush(), this.config.batchInterval);
77
+ if (this.config.metricsInterval > 0) {
78
+ this.metricsTimer = setInterval(
79
+ () => this.collectBrowserMetrics(),
80
+ this.config.metricsInterval
81
+ );
82
+ }
83
+ this.heartbeat();
84
+ this.log("info", `debugger.help SDK v3 initialized (session: ${this.sessionId})`, {
85
+ userAgent: navigator.userAgent,
86
+ url: location.href,
87
+ viewport: `${innerWidth}x${innerHeight}`,
88
+ devicePixelRatio,
89
+ language: navigator.language,
90
+ cookiesEnabled: navigator.cookieEnabled,
91
+ onLine: navigator.onLine
92
+ });
93
+ if (typeof window !== "undefined") {
94
+ window.addEventListener("beforeunload", () => this.flush());
95
+ window.addEventListener("online", () => this.log("info", "Network: back online"));
96
+ window.addEventListener("offline", () => this.log("warn", "Network: went offline"));
97
+ }
98
+ }
99
+ // ── Public API ───────────────────────────────────────────
100
+ log(level, message, context) {
101
+ this.enqueue({
102
+ type: "log",
103
+ level,
104
+ message,
105
+ context: { ...context, sessionId: this.sessionId, userId: this.config.userId || void 0 }
106
+ });
107
+ }
108
+ debug(msg, ctx) {
109
+ this.log("debug", msg, ctx);
110
+ }
111
+ info(msg, ctx) {
112
+ this.log("info", msg, ctx);
113
+ }
114
+ warn(msg, ctx) {
115
+ this.log("warn", msg, ctx);
116
+ }
117
+ error(msg, ctx) {
118
+ this.log("error", msg, ctx);
119
+ }
120
+ critical(msg, ctx) {
121
+ this.log("critical", msg, ctx);
122
+ }
123
+ captureError(err, context) {
124
+ this.errorCount++;
125
+ this.enqueue({
126
+ type: "error",
127
+ title: err.message,
128
+ stackTrace: err.stack || "",
129
+ context: {
130
+ name: err.name,
131
+ sessionId: this.sessionId,
132
+ userId: this.config.userId || void 0,
133
+ errorCount: this.errorCount,
134
+ ...context
135
+ }
136
+ });
137
+ }
138
+ /**
139
+ * Capture React error boundary errors.
140
+ * Call from componentDidCatch or ErrorBoundary fallback.
141
+ */
142
+ captureReactError(error, errorInfo) {
143
+ this.captureError(error, {
144
+ capturedFrom: "react_error_boundary",
145
+ componentStack: errorInfo.componentStack?.slice(0, 3e3)
146
+ });
147
+ }
148
+ metric(data) {
149
+ this.enqueue({
150
+ type: "metric",
151
+ ...data,
152
+ custom: { ...data.custom || {}, sessionId: this.sessionId }
153
+ });
154
+ }
155
+ inspect(variables) {
156
+ this.enqueue({ type: "inspect", variables });
157
+ }
158
+ heartbeat() {
159
+ this.enqueue({ type: "heartbeat" });
160
+ }
161
+ setUserId(userId) {
162
+ this.config.userId = userId;
163
+ this.log("info", `User identified: ${userId}`);
164
+ }
165
+ /**
166
+ * Track image generation results from the frontend
167
+ */
168
+ captureImageGenResult(jobId, model, params, result, error, durationMs) {
169
+ const ctx = { jobId, model, params, durationMs };
170
+ if (error) {
171
+ this.enqueue({
172
+ type: "error",
173
+ title: `Image gen failed: ${model} \u2014 ${error}`,
174
+ context: { ...ctx, capturedFrom: "image_gen" }
175
+ });
176
+ } else {
177
+ this.log("info", `Image gen complete: ${model} (${durationMs}ms)`, {
178
+ ...ctx,
179
+ result,
180
+ capturedFrom: "image_gen"
181
+ });
182
+ }
183
+ }
184
+ // ── Console Interception ─────────────────────────────────
185
+ interceptConsole() {
186
+ const self = this;
187
+ const levelMap = {
188
+ log: "info",
189
+ info: "info",
190
+ warn: "warn",
191
+ error: "error",
192
+ debug: "debug",
193
+ trace: "debug"
194
+ };
195
+ for (const [method, level] of Object.entries(levelMap)) {
196
+ const original = self.originalConsole[method];
197
+ if (!original) continue;
198
+ console[method] = function(...args) {
199
+ original(...args);
200
+ const message = args.map((a) => {
201
+ if (a instanceof Error) return `${a.name}: ${a.message}
202
+ ${a.stack}`;
203
+ if (typeof a === "object") {
204
+ try {
205
+ return JSON.stringify(a, null, 0);
206
+ } catch {
207
+ return String(a);
208
+ }
209
+ }
210
+ return String(a);
211
+ }).join(" ");
212
+ self.enqueue({
213
+ type: "log",
214
+ level,
215
+ message: `[console.${method}] ${message.slice(0, 3e3)}`,
216
+ context: { capturedFrom: "console", sessionId: self.sessionId }
217
+ });
218
+ };
219
+ }
220
+ const origAssert = self.originalConsole.assert;
221
+ if (origAssert) {
222
+ console.assert = function(condition, ...args) {
223
+ origAssert(condition, ...args);
224
+ if (!condition) {
225
+ const message = args.map((a) => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ");
226
+ self.enqueue({
227
+ type: "error",
228
+ title: `Assertion failed: ${message.slice(0, 500)}`,
229
+ context: { capturedFrom: "console.assert", sessionId: self.sessionId }
230
+ });
231
+ }
232
+ };
233
+ }
234
+ }
235
+ // ── Global Error Capture ─────────────────────────────────
236
+ captureGlobalErrors() {
237
+ if (typeof window === "undefined") return;
238
+ window.addEventListener("error", (event) => {
239
+ if (event.target && event.target.tagName) return;
240
+ this.captureError(event.error || new Error(event.message), {
241
+ filename: event.filename,
242
+ lineno: event.lineno,
243
+ colno: event.colno,
244
+ capturedFrom: "window.onerror"
245
+ });
246
+ });
247
+ window.addEventListener("unhandledrejection", (event) => {
248
+ const err = event.reason instanceof Error ? event.reason : new Error(String(event.reason));
249
+ this.captureError(err, { capturedFrom: "unhandledrejection" });
250
+ });
251
+ }
252
+ // ── Resource Error Capture ───────────────────────────────
253
+ captureResourceErrors() {
254
+ if (typeof window === "undefined") return;
255
+ window.addEventListener("error", (event) => {
256
+ const target = event.target;
257
+ if (!target?.tagName) return;
258
+ const tag = target.tagName.toLowerCase();
259
+ const src = target.src || target.href || target.src;
260
+ if (src) {
261
+ this.enqueue({
262
+ type: "error",
263
+ title: `Resource failed to load: <${tag}> ${src}`,
264
+ context: {
265
+ capturedFrom: "resource_error",
266
+ tagName: tag,
267
+ src,
268
+ sessionId: this.sessionId
269
+ }
270
+ });
271
+ }
272
+ }, true);
273
+ }
274
+ // ── Network Interception (Fetch + XHR) ───────────────────
275
+ interceptNetwork() {
276
+ if (typeof window === "undefined") return;
277
+ this.interceptFetch();
278
+ this.interceptXHR();
279
+ }
280
+ interceptFetch() {
281
+ if (typeof fetch === "undefined") return;
282
+ this.originalFetch = fetch.bind(window);
283
+ const self = this;
284
+ const originalFetch = this.originalFetch;
285
+ window.fetch = async function(input, init) {
286
+ const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
287
+ const method = init?.method || "GET";
288
+ const start = performance.now();
289
+ try {
290
+ const response = await originalFetch(input, init);
291
+ const duration = Math.round(performance.now() - start);
292
+ if (!url.includes("/ingest") && !url.includes("/debug")) {
293
+ if (response.status >= 400) {
294
+ self.networkFailCount++;
295
+ let responseBody = "";
296
+ try {
297
+ const cloned = response.clone();
298
+ responseBody = (await cloned.text()).slice(0, 1e3);
299
+ } catch {
300
+ }
301
+ self.enqueue({
302
+ type: response.status >= 500 ? "error" : "log",
303
+ ...response.status >= 500 ? { title: `[fetch] ${method} ${url} \u2192 ${response.status}` } : {},
304
+ level: response.status >= 500 ? "error" : "warn",
305
+ message: `[fetch] ${method} ${url} \u2192 ${response.status} (${duration}ms)`,
306
+ context: {
307
+ capturedFrom: "fetch",
308
+ url,
309
+ method,
310
+ status: response.status,
311
+ statusText: response.statusText,
312
+ duration,
313
+ responseBody,
314
+ requestHeaders: self.sanitizeHeaders(init?.headers),
315
+ sessionId: self.sessionId
316
+ }
317
+ });
318
+ } else if (duration > self.config.slowRequestThresholdMs) {
319
+ self.log("warn", `[fetch] Slow request: ${method} ${url} (${duration}ms)`, {
320
+ capturedFrom: "fetch",
321
+ url,
322
+ method,
323
+ status: response.status,
324
+ duration
325
+ });
326
+ }
327
+ }
328
+ return response;
329
+ } catch (err) {
330
+ const duration = Math.round(performance.now() - start);
331
+ if (!url.includes("/ingest") && !url.includes("/debug")) {
332
+ self.networkFailCount++;
333
+ self.enqueue({
334
+ type: "error",
335
+ title: `Network request failed: ${method} ${url}`,
336
+ stackTrace: err instanceof Error ? err.stack || "" : "",
337
+ context: {
338
+ capturedFrom: "fetch",
339
+ url,
340
+ method,
341
+ duration,
342
+ errorMessage: err instanceof Error ? err.message : String(err),
343
+ sessionId: self.sessionId
344
+ }
345
+ });
346
+ }
347
+ throw err;
348
+ }
349
+ };
350
+ }
351
+ interceptXHR() {
352
+ if (typeof XMLHttpRequest === "undefined") return;
353
+ const self = this;
354
+ const origOpen = XMLHttpRequest.prototype.open;
355
+ const origSend = XMLHttpRequest.prototype.send;
356
+ this.originalXhrOpen = origOpen;
357
+ this.originalXhrSend = origSend;
358
+ XMLHttpRequest.prototype.open = function(method, url, ...rest) {
359
+ this.__ys_method = method;
360
+ this.__ys_url = typeof url === "string" ? url : url.href;
361
+ return origOpen.apply(this, [method, url, ...rest]);
362
+ };
363
+ XMLHttpRequest.prototype.send = function(body) {
364
+ const xhrUrl = this.__ys_url;
365
+ const xhrMethod = this.__ys_method;
366
+ if (xhrUrl?.includes("/ingest") || xhrUrl?.includes("/debug")) {
367
+ return origSend.call(this, body);
368
+ }
369
+ const start = performance.now();
370
+ this.addEventListener("loadend", function() {
371
+ const duration = Math.round(performance.now() - start);
372
+ if (this.status >= 400) {
373
+ self.networkFailCount++;
374
+ self.enqueue({
375
+ type: this.status >= 500 ? "error" : "log",
376
+ ...this.status >= 500 ? { title: `[xhr] ${xhrMethod} ${xhrUrl} \u2192 ${this.status}` } : {},
377
+ level: this.status >= 500 ? "error" : "warn",
378
+ message: `[xhr] ${xhrMethod} ${xhrUrl} \u2192 ${this.status} (${duration}ms)`,
379
+ context: {
380
+ capturedFrom: "xhr",
381
+ url: xhrUrl,
382
+ method: xhrMethod,
383
+ status: this.status,
384
+ duration,
385
+ responseBody: (this.responseText || "").slice(0, 500),
386
+ sessionId: self.sessionId
387
+ }
388
+ });
389
+ } else if (duration > self.config.slowRequestThresholdMs) {
390
+ self.log("warn", `[xhr] Slow request: ${xhrMethod} ${xhrUrl} (${duration}ms)`, {
391
+ capturedFrom: "xhr",
392
+ url: xhrUrl,
393
+ method: xhrMethod,
394
+ status: this.status,
395
+ duration
396
+ });
397
+ }
398
+ });
399
+ this.addEventListener("error", function() {
400
+ self.networkFailCount++;
401
+ self.enqueue({
402
+ type: "error",
403
+ title: `XHR request failed: ${xhrMethod} ${xhrUrl}`,
404
+ context: { capturedFrom: "xhr", url: xhrUrl, method: xhrMethod, sessionId: self.sessionId }
405
+ });
406
+ });
407
+ return origSend.call(this, body);
408
+ };
409
+ }
410
+ // ── Web Vitals ───────────────────────────────────────────
411
+ captureWebVitals() {
412
+ if (typeof PerformanceObserver === "undefined") return;
413
+ try {
414
+ const lcpObserver = new PerformanceObserver((list) => {
415
+ const entries = list.getEntries();
416
+ const last = entries[entries.length - 1];
417
+ if (last) {
418
+ this.log("info", `[perf] LCP: ${Math.round(last.startTime)}ms`, {
419
+ capturedFrom: "web_vitals",
420
+ metric: "LCP",
421
+ value: Math.round(last.startTime),
422
+ element: last.element?.tagName
423
+ });
424
+ }
425
+ });
426
+ lcpObserver.observe({ type: "largest-contentful-paint", buffered: true });
427
+ } catch {
428
+ }
429
+ try {
430
+ const fidObserver = new PerformanceObserver((list) => {
431
+ for (const entry of list.getEntries()) {
432
+ const fid = entry.processingStart - entry.startTime;
433
+ this.log("info", `[perf] FID: ${Math.round(fid)}ms`, {
434
+ capturedFrom: "web_vitals",
435
+ metric: "FID",
436
+ value: Math.round(fid)
437
+ });
438
+ }
439
+ });
440
+ fidObserver.observe({ type: "first-input", buffered: true });
441
+ } catch {
442
+ }
443
+ try {
444
+ let clsValue = 0;
445
+ const clsObserver = new PerformanceObserver((list) => {
446
+ for (const entry of list.getEntries()) {
447
+ if (!entry.hadRecentInput) {
448
+ clsValue += entry.value;
449
+ }
450
+ }
451
+ });
452
+ clsObserver.observe({ type: "layout-shift", buffered: true });
453
+ if (typeof document !== "undefined") {
454
+ document.addEventListener("visibilitychange", () => {
455
+ if (document.visibilityState === "hidden") {
456
+ this.log("info", `[perf] CLS: ${clsValue.toFixed(4)}`, {
457
+ capturedFrom: "web_vitals",
458
+ metric: "CLS",
459
+ value: clsValue
460
+ });
461
+ }
462
+ });
463
+ }
464
+ } catch {
465
+ }
466
+ try {
467
+ const fcpObserver = new PerformanceObserver((list) => {
468
+ for (const entry of list.getEntries()) {
469
+ if (entry.name === "first-contentful-paint") {
470
+ this.log("info", `[perf] FCP: ${Math.round(entry.startTime)}ms`, {
471
+ capturedFrom: "web_vitals",
472
+ metric: "FCP",
473
+ value: Math.round(entry.startTime)
474
+ });
475
+ }
476
+ }
477
+ });
478
+ fcpObserver.observe({ type: "paint", buffered: true });
479
+ } catch {
480
+ }
481
+ try {
482
+ const nav = performance.getEntriesByType("navigation")[0];
483
+ if (nav) {
484
+ const ttfb = Math.round(nav.responseStart - nav.requestStart);
485
+ this.log("info", `[perf] TTFB: ${ttfb}ms`, {
486
+ capturedFrom: "web_vitals",
487
+ metric: "TTFB",
488
+ value: ttfb
489
+ });
490
+ }
491
+ } catch {
492
+ }
493
+ }
494
+ // ── Long Tasks ───────────────────────────────────────────
495
+ captureLongTasks() {
496
+ if (typeof PerformanceObserver === "undefined") return;
497
+ try {
498
+ const observer = new PerformanceObserver((list) => {
499
+ for (const entry of list.getEntries()) {
500
+ this.longTaskCount++;
501
+ if (entry.duration > 100) {
502
+ this.log("warn", `[perf] Long task: ${Math.round(entry.duration)}ms`, {
503
+ capturedFrom: "long_task",
504
+ duration: Math.round(entry.duration),
505
+ startTime: Math.round(entry.startTime),
506
+ longTaskCount: this.longTaskCount
507
+ });
508
+ }
509
+ }
510
+ });
511
+ observer.observe({ type: "longtask", buffered: true });
512
+ } catch {
513
+ }
514
+ }
515
+ // ── Navigation Tracking ──────────────────────────────────
516
+ captureNavigation() {
517
+ if (typeof window === "undefined") return;
518
+ window.addEventListener("popstate", () => {
519
+ this.navigationCount++;
520
+ this.log("info", `[nav] popstate \u2192 ${location.href}`, {
521
+ capturedFrom: "navigation",
522
+ type: "popstate",
523
+ navigationCount: this.navigationCount
524
+ });
525
+ });
526
+ const origPush = history.pushState.bind(history);
527
+ const origReplace = history.replaceState.bind(history);
528
+ const self = this;
529
+ history.pushState = function(...args) {
530
+ origPush(...args);
531
+ self.navigationCount++;
532
+ self.log("info", `[nav] pushState \u2192 ${location.href}`, {
533
+ capturedFrom: "navigation",
534
+ type: "pushState",
535
+ navigationCount: self.navigationCount
536
+ });
537
+ };
538
+ history.replaceState = function(...args) {
539
+ origReplace(...args);
540
+ self.log("info", `[nav] replaceState \u2192 ${location.href}`, {
541
+ capturedFrom: "navigation",
542
+ type: "replaceState"
543
+ });
544
+ };
545
+ }
546
+ // ── Visibility Changes ───────────────────────────────────
547
+ captureVisibilityChanges() {
548
+ if (typeof document === "undefined") return;
549
+ document.addEventListener("visibilitychange", () => {
550
+ const state = document.visibilityState;
551
+ this.log("info", `[visibility] Tab ${state}`, {
552
+ capturedFrom: "visibility",
553
+ state,
554
+ hiddenDuration: state === "visible" ? Math.round(performance.now() - this.pageLoadTime) : void 0
555
+ });
556
+ });
557
+ }
558
+ // ── CSP Violations ───────────────────────────────────────
559
+ captureCSPViolations() {
560
+ if (typeof document === "undefined") return;
561
+ document.addEventListener("securitypolicyviolation", (e) => {
562
+ this.enqueue({
563
+ type: "error",
564
+ title: `CSP violation: ${e.violatedDirective}`,
565
+ context: {
566
+ capturedFrom: "csp_violation",
567
+ blockedURI: e.blockedURI,
568
+ violatedDirective: e.violatedDirective,
569
+ effectiveDirective: e.effectiveDirective,
570
+ originalPolicy: e.originalPolicy?.slice(0, 500),
571
+ sourceFile: e.sourceFile,
572
+ lineNumber: e.lineNumber,
573
+ sessionId: this.sessionId
574
+ }
575
+ });
576
+ });
577
+ }
578
+ // ── WebSocket Interception ───────────────────────────────
579
+ interceptWebSockets() {
580
+ if (typeof WebSocket === "undefined") return;
581
+ const self = this;
582
+ const OriginalWebSocket = WebSocket;
583
+ this.originalWsConstructor = OriginalWebSocket;
584
+ window.WebSocket = function(url, protocols) {
585
+ const ws = new OriginalWebSocket(url, protocols);
586
+ const wsUrl = typeof url === "string" ? url : url.href;
587
+ ws.addEventListener("error", () => {
588
+ self.enqueue({
589
+ type: "error",
590
+ title: `WebSocket error: ${wsUrl}`,
591
+ context: { capturedFrom: "websocket", url: wsUrl, readyState: ws.readyState }
592
+ });
593
+ });
594
+ ws.addEventListener("close", (event) => {
595
+ if (!event.wasClean) {
596
+ self.log("warn", `[ws] Unclean close: ${wsUrl} (code: ${event.code})`, {
597
+ capturedFrom: "websocket",
598
+ url: wsUrl,
599
+ code: event.code,
600
+ reason: event.reason
601
+ });
602
+ }
603
+ });
604
+ return ws;
605
+ };
606
+ window.WebSocket.prototype = OriginalWebSocket.prototype;
607
+ window.WebSocket.CONNECTING = OriginalWebSocket.CONNECTING;
608
+ window.WebSocket.OPEN = OriginalWebSocket.OPEN;
609
+ window.WebSocket.CLOSING = OriginalWebSocket.CLOSING;
610
+ window.WebSocket.CLOSED = OriginalWebSocket.CLOSED;
611
+ }
612
+ // ── Browser Metrics ──────────────────────────────────────
613
+ collectBrowserMetrics() {
614
+ if (typeof window === "undefined") return;
615
+ const perf = performance;
616
+ const memory = perf.memory ? {
617
+ usedJSHeapSize: Math.round(perf.memory.usedJSHeapSize / 1e6),
618
+ totalJSHeapSize: Math.round(perf.memory.totalJSHeapSize / 1e6),
619
+ jsHeapSizeLimit: Math.round(perf.memory.jsHeapSizeLimit / 1e6)
620
+ } : null;
621
+ const nav = performance.getEntriesByType("navigation")[0];
622
+ const domNodeCount = document.querySelectorAll("*").length;
623
+ const resourceEntries = performance.getEntriesByType("resource");
624
+ const failedResources = resourceEntries.filter((e) => e.transferSize === 0 && e.decodedBodySize === 0);
625
+ this.metric({
626
+ memory: memory?.usedJSHeapSize ?? null,
627
+ custom: {
628
+ ...memory || {},
629
+ loadTime: nav ? Math.round(nav.loadEventEnd - nav.startTime) : null,
630
+ domInteractive: nav ? Math.round(nav.domInteractive - nav.startTime) : null,
631
+ domContentLoaded: nav ? Math.round(nav.domContentLoadedEventEnd - nav.startTime) : null,
632
+ domNodeCount,
633
+ resourceCount: resourceEntries.length,
634
+ failedResourceCount: failedResources.length,
635
+ url: location.href,
636
+ sessionId: this.sessionId,
637
+ errorCount: this.errorCount,
638
+ networkFailCount: this.networkFailCount,
639
+ longTaskCount: this.longTaskCount,
640
+ navigationCount: this.navigationCount,
641
+ sessionDurationMs: Math.round(performance.now() - this.pageLoadTime),
642
+ onLine: navigator.onLine
643
+ }
644
+ });
645
+ }
646
+ // ── Internal ─────────────────────────────────────────────
647
+ enqueue(item) {
648
+ item.source = this.config.source;
649
+ item.platform = this.config.platform;
650
+ item.version = this.config.version;
651
+ this.queue.push(item);
652
+ if (this.queue.length >= this.config.maxBatchSize) {
653
+ this.flush();
654
+ }
655
+ }
656
+ async flush() {
657
+ if (this.queue.length === 0) return;
658
+ const batch = this.queue.splice(0, this.config.maxBatchSize);
659
+ const doFetch = this.originalFetch || fetch;
660
+ const promises = batch.map(
661
+ (item) => doFetch(this.config.ingestUrl, {
662
+ method: "POST",
663
+ headers: {
664
+ Authorization: `Bearer ${this.config.apiKey}`,
665
+ "Content-Type": "application/json"
666
+ },
667
+ body: JSON.stringify(item)
668
+ }).catch(() => {
669
+ })
670
+ );
671
+ await Promise.allSettled(promises);
672
+ }
673
+ generateSessionId() {
674
+ return "ses_" + Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
675
+ }
676
+ sanitizeHeaders(headers) {
677
+ if (!headers) return void 0;
678
+ const result = {};
679
+ const h = headers instanceof Headers ? headers : new Headers(headers);
680
+ h.forEach((value, key) => {
681
+ const lower = key.toLowerCase();
682
+ if (lower.includes("auth") || lower.includes("cookie") || lower.includes("token")) {
683
+ result[key] = "[REDACTED]";
684
+ } else {
685
+ result[key] = value;
686
+ }
687
+ });
688
+ return result;
689
+ }
690
+ destroy() {
691
+ if (this.batchTimer) clearInterval(this.batchTimer);
692
+ if (this.metricsTimer) clearInterval(this.metricsTimer);
693
+ if (this.config.captureConsole) {
694
+ for (const [method, original] of Object.entries(this.originalConsole)) {
695
+ if (original) console[method] = original;
696
+ }
697
+ }
698
+ if (this.originalFetch) window.fetch = this.originalFetch;
699
+ if (this.originalXhrOpen) XMLHttpRequest.prototype.open = this.originalXhrOpen;
700
+ if (this.originalXhrSend) XMLHttpRequest.prototype.send = this.originalXhrSend;
701
+ if (this.originalWsConstructor) window.WebSocket = this.originalWsConstructor;
702
+ this.flush();
703
+ }
704
+ };
705
+ // Annotate the CommonJS export names for ESM import in node:
706
+ 0 && (module.exports = {
707
+ DebuggerSDK
708
+ });