@bbearai/core 0.1.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/dist/index.js ADDED
@@ -0,0 +1,705 @@
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
+ BugBearClient: () => BugBearClient,
24
+ captureError: () => captureError,
25
+ contextCapture: () => contextCapture,
26
+ createBugBear: () => createBugBear
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/client.ts
31
+ var import_supabase_js = require("@supabase/supabase-js");
32
+ var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
33
+ var getEnvVar = (key) => {
34
+ try {
35
+ if (typeof process !== "undefined" && process.env) {
36
+ return process.env[key];
37
+ }
38
+ } catch {
39
+ }
40
+ return void 0;
41
+ };
42
+ var HOSTED_BUGBEAR_ANON_KEY = getEnvVar("BUGBEAR_ANON_KEY") || getEnvVar("NEXT_PUBLIC_BUGBEAR_ANON_KEY") || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imt5eGd6am5xZ3ZhcHZsbnZxYXd6Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjkyNjgwNDIsImV4cCI6MjA4NDg0NDA0Mn0.NUkAlCHLFjeRoisbmNUVoGb4R6uQ8xs5LAEIX1BWTwU";
43
+ var BugBearClient = class {
44
+ constructor(config) {
45
+ this.navigationHistory = [];
46
+ this.config = config;
47
+ this.supabase = (0, import_supabase_js.createClient)(
48
+ config.supabaseUrl || DEFAULT_SUPABASE_URL,
49
+ config.supabaseAnonKey || HOSTED_BUGBEAR_ANON_KEY
50
+ );
51
+ }
52
+ /**
53
+ * Track navigation for context
54
+ */
55
+ trackNavigation(route) {
56
+ this.navigationHistory.push(route);
57
+ if (this.navigationHistory.length > 10) {
58
+ this.navigationHistory.shift();
59
+ }
60
+ }
61
+ /**
62
+ * Get current navigation history
63
+ */
64
+ getNavigationHistory() {
65
+ if (this.config.getNavigationHistory) {
66
+ return this.config.getNavigationHistory();
67
+ }
68
+ return [...this.navigationHistory];
69
+ }
70
+ /**
71
+ * Get current user info from host app or BugBear's own auth
72
+ */
73
+ async getCurrentUserInfo() {
74
+ if (this.config.getCurrentUser) {
75
+ return await this.config.getCurrentUser();
76
+ }
77
+ const { data: { user } } = await this.supabase.auth.getUser();
78
+ if (!user || !user.email) return null;
79
+ return {
80
+ id: user.id,
81
+ email: user.email
82
+ };
83
+ }
84
+ /**
85
+ * Submit a report
86
+ */
87
+ async submitReport(report) {
88
+ try {
89
+ const userInfo = await this.getCurrentUserInfo();
90
+ if (!userInfo) {
91
+ console.error("BugBear: No user info available, cannot submit report");
92
+ return { success: false, error: "User not authenticated" };
93
+ }
94
+ const testerInfo = await this.getTesterInfo();
95
+ const fullReport = {
96
+ project_id: this.config.projectId,
97
+ reporter_id: userInfo.id,
98
+ // User ID from host app (required)
99
+ tester_id: testerInfo?.id || null,
100
+ // Tester record ID (optional)
101
+ report_type: report.type,
102
+ title: report.title || this.generateTitle(report),
103
+ description: report.description,
104
+ severity: report.severity,
105
+ failed_at_step: report.failedAtStep,
106
+ voice_audio_url: report.voiceAudioUrl,
107
+ voice_transcript: report.voiceTranscript,
108
+ screenshot_urls: report.screenshots || [],
109
+ app_context: report.appContext,
110
+ device_info: report.deviceInfo || this.getDeviceInfo(),
111
+ navigation_history: this.getNavigationHistory(),
112
+ assignment_id: report.assignmentId,
113
+ test_case_id: report.testCaseId
114
+ };
115
+ const { data, error } = await this.supabase.from("reports").insert(fullReport).select("id").single();
116
+ if (error) {
117
+ console.error("BugBear: Failed to submit report", error.message);
118
+ return { success: false, error: error.message };
119
+ }
120
+ if (this.config.onReportSubmitted) {
121
+ this.config.onReportSubmitted(report);
122
+ }
123
+ return { success: true, reportId: data.id };
124
+ } catch (err) {
125
+ const message = err instanceof Error ? err.message : "Unknown error";
126
+ return { success: false, error: message };
127
+ }
128
+ }
129
+ /**
130
+ * Get assigned tests for current user
131
+ * First looks up the tester by email, then fetches their assignments
132
+ */
133
+ async getAssignedTests() {
134
+ try {
135
+ const testerInfo = await this.getTesterInfo();
136
+ if (!testerInfo) return [];
137
+ const { data, error } = await this.supabase.from("test_assignments").select(`
138
+ id,
139
+ status,
140
+ test_case:test_cases(
141
+ id,
142
+ title,
143
+ test_key,
144
+ description,
145
+ steps,
146
+ expected_result,
147
+ priority,
148
+ target_route,
149
+ track:qa_tracks(
150
+ id,
151
+ name,
152
+ icon,
153
+ color,
154
+ test_template,
155
+ rubric_mode,
156
+ description
157
+ )
158
+ )
159
+ `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true });
160
+ if (error) {
161
+ console.error("BugBear: Failed to fetch assignments", error);
162
+ return [];
163
+ }
164
+ return (data || []).map((item) => ({
165
+ id: item.id,
166
+ status: item.status,
167
+ testCase: {
168
+ id: item.test_case.id,
169
+ title: item.test_case.title,
170
+ testKey: item.test_case.test_key,
171
+ description: item.test_case.description,
172
+ steps: item.test_case.steps,
173
+ expectedResult: item.test_case.expected_result,
174
+ priority: item.test_case.priority,
175
+ targetRoute: item.test_case.target_route,
176
+ track: item.test_case.track ? {
177
+ id: item.test_case.track.id,
178
+ name: item.test_case.track.name,
179
+ icon: item.test_case.track.icon,
180
+ color: item.test_case.track.color,
181
+ testTemplate: item.test_case.track.test_template,
182
+ rubricMode: item.test_case.track.rubric_mode || "pass_fail",
183
+ description: item.test_case.track.description
184
+ } : void 0
185
+ }
186
+ }));
187
+ } catch (err) {
188
+ console.error("BugBear: Error fetching assignments", err);
189
+ return [];
190
+ }
191
+ }
192
+ /**
193
+ * Get current tester info
194
+ * Looks up tester by email from the host app's authenticated user
195
+ */
196
+ async getTesterInfo() {
197
+ try {
198
+ const userInfo = await this.getCurrentUserInfo();
199
+ if (!userInfo) return null;
200
+ const { data, error } = await this.supabase.from("testers").select("*").eq("project_id", this.config.projectId).eq("email", userInfo.email).eq("status", "active").single();
201
+ if (error || !data) return null;
202
+ return {
203
+ id: data.id,
204
+ name: data.name,
205
+ email: data.email,
206
+ assignedTests: data.assigned_count || 0,
207
+ completedTests: data.completed_count || 0
208
+ };
209
+ } catch (err) {
210
+ console.error("BugBear: getTesterInfo error", err);
211
+ return null;
212
+ }
213
+ }
214
+ /**
215
+ * Check if current user is a tester for this project
216
+ */
217
+ async isTester() {
218
+ const info = await this.getTesterInfo();
219
+ return info !== null;
220
+ }
221
+ /**
222
+ * Check if QA mode is enabled for this project
223
+ * This is a master switch that admins can toggle in the dashboard
224
+ */
225
+ async isQAEnabled() {
226
+ try {
227
+ const { data, error } = await this.supabase.from("projects").select("is_qa_enabled").eq("id", this.config.projectId).single();
228
+ if (error) {
229
+ if (error.code !== "PGRST116") {
230
+ console.warn("BugBear: Could not check QA status", error.message || error.code || "Unknown error");
231
+ }
232
+ return true;
233
+ }
234
+ return data?.is_qa_enabled ?? true;
235
+ } catch (err) {
236
+ return true;
237
+ }
238
+ }
239
+ /**
240
+ * Check if the widget should be visible
241
+ * (QA mode enabled AND current user is a tester)
242
+ */
243
+ async shouldShowWidget() {
244
+ const [qaEnabled, isTester] = await Promise.all([
245
+ this.isQAEnabled(),
246
+ this.isTester()
247
+ ]);
248
+ return qaEnabled && isTester;
249
+ }
250
+ /**
251
+ * Upload a screenshot
252
+ */
253
+ async uploadScreenshot(file, filename) {
254
+ try {
255
+ const name = filename || `screenshot-${Date.now()}.png`;
256
+ const path = `${this.config.projectId}/${name}`;
257
+ const { error } = await this.supabase.storage.from("screenshots").upload(path, file, {
258
+ contentType: "image/png",
259
+ upsert: false
260
+ });
261
+ if (error) {
262
+ console.error("BugBear: Failed to upload screenshot", error);
263
+ return null;
264
+ }
265
+ const { data: { publicUrl } } = this.supabase.storage.from("screenshots").getPublicUrl(path);
266
+ return publicUrl;
267
+ } catch (err) {
268
+ console.error("BugBear: Error uploading screenshot", err);
269
+ return null;
270
+ }
271
+ }
272
+ /**
273
+ * Generate a title from the report content
274
+ */
275
+ generateTitle(report) {
276
+ const prefix = report.type === "bug" ? "\u{1F41B} Bug:" : report.type === "feedback" ? "\u{1F4A1} Feedback:" : report.type === "suggestion" ? "\u2728 Suggestion:" : report.type === "test_fail" ? "\u274C Test Failed:" : "\u2705 Test Passed:";
277
+ const desc = report.description || "";
278
+ const shortDesc = desc.length > 50 ? desc.slice(0, 50) + "..." : desc;
279
+ return `${prefix} ${shortDesc}`;
280
+ }
281
+ /**
282
+ * Get device info (override in platform-specific implementations)
283
+ */
284
+ getDeviceInfo() {
285
+ if (typeof window !== "undefined") {
286
+ return {
287
+ platform: "web",
288
+ userAgent: navigator.userAgent,
289
+ screenSize: {
290
+ width: window.screen.width,
291
+ height: window.screen.height
292
+ }
293
+ };
294
+ }
295
+ return { platform: "web" };
296
+ }
297
+ /**
298
+ * Create a fix request for Claude Code to pick up
299
+ * Called from the dashboard when user clicks "Send to Claude Code"
300
+ */
301
+ async createFixRequest(request) {
302
+ try {
303
+ const userInfo = await this.getCurrentUserInfo();
304
+ const fixRequest = {
305
+ project_id: this.config.projectId,
306
+ title: request.title,
307
+ description: request.description || null,
308
+ prompt: request.prompt,
309
+ file_path: request.filePath || null,
310
+ report_id: request.reportId || null,
311
+ status: "pending",
312
+ created_by: userInfo?.id || null
313
+ };
314
+ const { data, error } = await this.supabase.from("fix_requests").insert(fixRequest).select("id").single();
315
+ if (error) {
316
+ console.error("BugBear: Failed to create fix request", error.message);
317
+ return { success: false, error: error.message };
318
+ }
319
+ return { success: true, fixRequestId: data.id };
320
+ } catch (err) {
321
+ const message = err instanceof Error ? err.message : "Unknown error";
322
+ return { success: false, error: message };
323
+ }
324
+ }
325
+ /**
326
+ * Get pending fix requests for this project
327
+ * Useful for dashboard to show queue status
328
+ */
329
+ async getFixRequests(options) {
330
+ try {
331
+ let query = this.supabase.from("fix_requests").select("*").eq("project_id", this.config.projectId).order("created_at", { ascending: false }).limit(options?.limit || 20);
332
+ if (options?.status) {
333
+ query = query.eq("status", options.status);
334
+ }
335
+ const { data, error } = await query;
336
+ if (error) {
337
+ console.error("BugBear: Failed to fetch fix requests", error);
338
+ return [];
339
+ }
340
+ return (data || []).map((fr) => ({
341
+ id: fr.id,
342
+ title: fr.title,
343
+ description: fr.description,
344
+ status: fr.status,
345
+ claimedBy: fr.claimed_by,
346
+ claimedAt: fr.claimed_at,
347
+ completedAt: fr.completed_at,
348
+ createdAt: fr.created_at
349
+ }));
350
+ } catch (err) {
351
+ console.error("BugBear: Error fetching fix requests", err);
352
+ return [];
353
+ }
354
+ }
355
+ /**
356
+ * Get threads visible to the current tester
357
+ * Includes threads where audience is 'all' or tester is in audience_tester_ids
358
+ */
359
+ async getThreadsForTester() {
360
+ try {
361
+ const testerInfo = await this.getTesterInfo();
362
+ if (!testerInfo) return [];
363
+ const { data: threads, error } = await this.supabase.from("discussion_threads").select(`
364
+ id,
365
+ subject,
366
+ thread_type,
367
+ priority,
368
+ is_pinned,
369
+ is_resolved,
370
+ last_message_at,
371
+ created_at
372
+ `).eq("project_id", this.config.projectId).or(`audience.eq.all,audience_tester_ids.cs.{${testerInfo.id}}`).order("is_pinned", { ascending: false }).order("last_message_at", { ascending: false });
373
+ if (error) {
374
+ console.error("BugBear: Failed to fetch threads", error);
375
+ return [];
376
+ }
377
+ if (!threads || threads.length === 0) return [];
378
+ const threadIds = threads.map((t) => t.id);
379
+ const { data: readStatuses } = await this.supabase.from("discussion_read_status").select("thread_id, last_read_at, last_read_message_id").eq("tester_id", testerInfo.id).in("thread_id", threadIds);
380
+ const readStatusMap = new Map(
381
+ (readStatuses || []).map((rs) => [rs.thread_id, rs])
382
+ );
383
+ const { data: lastMessages } = await this.supabase.from("discussion_messages").select(`
384
+ id,
385
+ thread_id,
386
+ sender_type,
387
+ sender_name,
388
+ content,
389
+ created_at,
390
+ attachments
391
+ `).in("thread_id", threadIds).order("created_at", { ascending: false });
392
+ const lastMessageMap = /* @__PURE__ */ new Map();
393
+ for (const msg of lastMessages || []) {
394
+ if (!lastMessageMap.has(msg.thread_id)) {
395
+ lastMessageMap.set(msg.thread_id, msg);
396
+ }
397
+ }
398
+ const unreadCounts = await Promise.all(
399
+ threads.map(async (thread) => {
400
+ const readStatus = readStatusMap.get(thread.id);
401
+ const lastReadAt = readStatus?.last_read_at || "1970-01-01T00:00:00Z";
402
+ const { count, error: countError } = await this.supabase.from("discussion_messages").select("*", { count: "exact", head: true }).eq("thread_id", thread.id).gt("created_at", lastReadAt);
403
+ return { threadId: thread.id, count: countError ? 0 : count || 0 };
404
+ })
405
+ );
406
+ const unreadCountMap = new Map(
407
+ unreadCounts.map((uc) => [uc.threadId, uc.count])
408
+ );
409
+ return threads.map((thread) => {
410
+ const lastMsg = lastMessageMap.get(thread.id);
411
+ return {
412
+ id: thread.id,
413
+ subject: thread.subject,
414
+ threadType: thread.thread_type,
415
+ priority: thread.priority,
416
+ isPinned: thread.is_pinned,
417
+ isResolved: thread.is_resolved,
418
+ lastMessageAt: thread.last_message_at,
419
+ createdAt: thread.created_at,
420
+ unreadCount: unreadCountMap.get(thread.id) || 0,
421
+ lastMessage: lastMsg ? {
422
+ id: lastMsg.id,
423
+ threadId: lastMsg.thread_id,
424
+ senderType: lastMsg.sender_type,
425
+ senderName: lastMsg.sender_name,
426
+ content: lastMsg.content,
427
+ createdAt: lastMsg.created_at,
428
+ attachments: lastMsg.attachments || []
429
+ } : void 0
430
+ };
431
+ });
432
+ } catch (err) {
433
+ console.error("BugBear: Error fetching threads", err);
434
+ return [];
435
+ }
436
+ }
437
+ /**
438
+ * Get all messages in a thread
439
+ */
440
+ async getThreadMessages(threadId) {
441
+ try {
442
+ const { data, error } = await this.supabase.from("discussion_messages").select(`
443
+ id,
444
+ thread_id,
445
+ sender_type,
446
+ sender_name,
447
+ content,
448
+ created_at,
449
+ attachments
450
+ `).eq("thread_id", threadId).order("created_at", { ascending: true });
451
+ if (error) {
452
+ console.error("BugBear: Failed to fetch messages", error);
453
+ return [];
454
+ }
455
+ return (data || []).map((msg) => ({
456
+ id: msg.id,
457
+ threadId: msg.thread_id,
458
+ senderType: msg.sender_type,
459
+ senderName: msg.sender_name,
460
+ content: msg.content,
461
+ createdAt: msg.created_at,
462
+ attachments: msg.attachments || []
463
+ }));
464
+ } catch (err) {
465
+ console.error("BugBear: Error fetching messages", err);
466
+ return [];
467
+ }
468
+ }
469
+ /**
470
+ * Send a message to a thread
471
+ */
472
+ async sendMessage(threadId, content) {
473
+ try {
474
+ const testerInfo = await this.getTesterInfo();
475
+ if (!testerInfo) {
476
+ console.error("BugBear: No tester info, cannot send message");
477
+ return false;
478
+ }
479
+ const { error } = await this.supabase.from("discussion_messages").insert({
480
+ thread_id: threadId,
481
+ sender_type: "tester",
482
+ sender_id: testerInfo.id,
483
+ sender_name: testerInfo.name,
484
+ content
485
+ });
486
+ if (error) {
487
+ console.error("BugBear: Failed to send message", error);
488
+ return false;
489
+ }
490
+ await this.supabase.from("discussion_threads").update({ last_message_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", threadId);
491
+ return true;
492
+ } catch (err) {
493
+ console.error("BugBear: Error sending message", err);
494
+ return false;
495
+ }
496
+ }
497
+ /**
498
+ * Mark a thread as read
499
+ */
500
+ async markThreadAsRead(threadId) {
501
+ try {
502
+ const testerInfo = await this.getTesterInfo();
503
+ if (!testerInfo) return;
504
+ const { data: latestMsg } = await this.supabase.from("discussion_messages").select("id").eq("thread_id", threadId).order("created_at", { ascending: false }).limit(1).single();
505
+ await this.supabase.from("discussion_read_status").upsert({
506
+ thread_id: threadId,
507
+ tester_id: testerInfo.id,
508
+ last_read_at: (/* @__PURE__ */ new Date()).toISOString(),
509
+ last_read_message_id: latestMsg?.id || null
510
+ }, {
511
+ onConflict: "thread_id,tester_id"
512
+ });
513
+ } catch (err) {
514
+ console.error("BugBear: Error marking thread as read", err);
515
+ }
516
+ }
517
+ /**
518
+ * Get total unread message count across all threads
519
+ */
520
+ async getUnreadCount() {
521
+ try {
522
+ const threads = await this.getThreadsForTester();
523
+ return threads.reduce((sum, thread) => sum + thread.unreadCount, 0);
524
+ } catch (err) {
525
+ console.error("BugBear: Error getting unread count", err);
526
+ return 0;
527
+ }
528
+ }
529
+ };
530
+ function createBugBear(config) {
531
+ return new BugBearClient(config);
532
+ }
533
+
534
+ // src/capture.ts
535
+ var MAX_CONSOLE_LOGS = 50;
536
+ var MAX_NETWORK_REQUESTS = 20;
537
+ var ContextCaptureManager = class {
538
+ constructor() {
539
+ this.consoleLogs = [];
540
+ this.networkRequests = [];
541
+ this.originalConsole = {};
542
+ this.isCapturing = false;
543
+ }
544
+ /**
545
+ * Start capturing console logs and network requests
546
+ */
547
+ startCapture() {
548
+ if (this.isCapturing) return;
549
+ this.isCapturing = true;
550
+ this.captureConsole();
551
+ this.captureFetch();
552
+ }
553
+ /**
554
+ * Stop capturing and restore original functions
555
+ */
556
+ stopCapture() {
557
+ if (!this.isCapturing) return;
558
+ this.isCapturing = false;
559
+ if (this.originalConsole.log) console.log = this.originalConsole.log;
560
+ if (this.originalConsole.warn) console.warn = this.originalConsole.warn;
561
+ if (this.originalConsole.error) console.error = this.originalConsole.error;
562
+ if (this.originalConsole.info) console.info = this.originalConsole.info;
563
+ if (this.originalFetch && typeof window !== "undefined") {
564
+ window.fetch = this.originalFetch;
565
+ }
566
+ }
567
+ /**
568
+ * Get captured context for a bug report
569
+ */
570
+ getEnhancedContext() {
571
+ return {
572
+ consoleLogs: [...this.consoleLogs],
573
+ networkRequests: [...this.networkRequests],
574
+ performanceMetrics: this.getPerformanceMetrics(),
575
+ environment: this.getEnvironmentInfo()
576
+ };
577
+ }
578
+ /**
579
+ * Clear captured data
580
+ */
581
+ clear() {
582
+ this.consoleLogs = [];
583
+ this.networkRequests = [];
584
+ }
585
+ /**
586
+ * Add a log entry manually (for custom logging)
587
+ */
588
+ addLog(level, message, args) {
589
+ this.consoleLogs.push({
590
+ level,
591
+ message,
592
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
593
+ args
594
+ });
595
+ if (this.consoleLogs.length > MAX_CONSOLE_LOGS) {
596
+ this.consoleLogs = this.consoleLogs.slice(-MAX_CONSOLE_LOGS);
597
+ }
598
+ }
599
+ /**
600
+ * Add a network request manually
601
+ */
602
+ addNetworkRequest(request) {
603
+ this.networkRequests.push(request);
604
+ if (this.networkRequests.length > MAX_NETWORK_REQUESTS) {
605
+ this.networkRequests = this.networkRequests.slice(-MAX_NETWORK_REQUESTS);
606
+ }
607
+ }
608
+ captureConsole() {
609
+ if (typeof console === "undefined") return;
610
+ const levels = ["log", "warn", "error", "info"];
611
+ levels.forEach((level) => {
612
+ this.originalConsole[level] = console[level];
613
+ console[level] = (...args) => {
614
+ this.originalConsole[level]?.apply(console, args);
615
+ try {
616
+ const message = args.map((arg) => {
617
+ if (typeof arg === "string") return arg;
618
+ try {
619
+ return JSON.stringify(arg);
620
+ } catch {
621
+ return String(arg);
622
+ }
623
+ }).join(" ");
624
+ this.addLog(level, message.slice(0, 500));
625
+ } catch {
626
+ }
627
+ };
628
+ });
629
+ }
630
+ captureFetch() {
631
+ if (typeof window === "undefined" || typeof fetch === "undefined") return;
632
+ this.originalFetch = window.fetch;
633
+ const self = this;
634
+ window.fetch = async function(input, init) {
635
+ const startTime = Date.now();
636
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
637
+ const method = init?.method || "GET";
638
+ try {
639
+ const response = await self.originalFetch.call(window, input, init);
640
+ self.addNetworkRequest({
641
+ method,
642
+ url: url.slice(0, 200),
643
+ // Limit URL length
644
+ status: response.status,
645
+ duration: Date.now() - startTime,
646
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
647
+ });
648
+ return response;
649
+ } catch (error) {
650
+ self.addNetworkRequest({
651
+ method,
652
+ url: url.slice(0, 200),
653
+ duration: Date.now() - startTime,
654
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
655
+ error: error instanceof Error ? error.message : "Unknown error"
656
+ });
657
+ throw error;
658
+ }
659
+ };
660
+ }
661
+ getPerformanceMetrics() {
662
+ if (typeof window === "undefined" || typeof performance === "undefined") return void 0;
663
+ const metrics = {};
664
+ try {
665
+ const navigation = performance.getEntriesByType("navigation")[0];
666
+ if (navigation) {
667
+ metrics.pageLoadTime = Math.round(navigation.loadEventEnd - navigation.startTime);
668
+ }
669
+ } catch {
670
+ }
671
+ try {
672
+ const memory = performance.memory;
673
+ if (memory) {
674
+ metrics.memoryUsage = Math.round(memory.usedJSHeapSize / 1024 / 1024);
675
+ }
676
+ } catch {
677
+ }
678
+ return Object.keys(metrics).length > 0 ? metrics : void 0;
679
+ }
680
+ getEnvironmentInfo() {
681
+ if (typeof window === "undefined" || typeof navigator === "undefined") return void 0;
682
+ return {
683
+ language: navigator.language,
684
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
685
+ cookiesEnabled: navigator.cookieEnabled,
686
+ localStorage: typeof localStorage !== "undefined",
687
+ online: navigator.onLine
688
+ };
689
+ }
690
+ };
691
+ var contextCapture = new ContextCaptureManager();
692
+ function captureError(error, errorInfo) {
693
+ return {
694
+ errorMessage: error.message,
695
+ errorStack: error.stack,
696
+ componentStack: errorInfo?.componentStack
697
+ };
698
+ }
699
+ // Annotate the CommonJS export names for ESM import in node:
700
+ 0 && (module.exports = {
701
+ BugBearClient,
702
+ captureError,
703
+ contextCapture,
704
+ createBugBear
705
+ });