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