@bbearai/react-native 0.3.7 → 0.3.8

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.
Files changed (3) hide show
  1. package/dist/index.js +278 -163
  2. package/dist/index.mjs +278 -163
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -11392,6 +11392,239 @@ function shouldShowDeprecationWarning() {
11392
11392
  if (shouldShowDeprecationWarning()) console.warn("\u26A0\uFE0F Node.js 18 and below are deprecated and will no longer be supported in future versions of @supabase/supabase-js. Please upgrade to Node.js 20 or later. For more information, visit: https://github.com/orgs/supabase/discussions/37217");
11393
11393
 
11394
11394
  // ../core/dist/index.mjs
11395
+ var MAX_CONSOLE_LOGS = 50;
11396
+ var MAX_NETWORK_REQUESTS = 20;
11397
+ var MAX_NAVIGATION_HISTORY = 20;
11398
+ var MAX_RESPONSE_BODY_LENGTH = 500;
11399
+ var ContextCaptureManager = class {
11400
+ constructor() {
11401
+ this.consoleLogs = [];
11402
+ this.networkRequests = [];
11403
+ this.navigationHistory = [];
11404
+ this.originalConsole = {};
11405
+ this.isCapturing = false;
11406
+ }
11407
+ /**
11408
+ * Start capturing console logs, network requests, and navigation
11409
+ */
11410
+ startCapture() {
11411
+ if (this.isCapturing) return;
11412
+ this.isCapturing = true;
11413
+ this.captureConsole();
11414
+ this.captureFetch();
11415
+ this.captureNavigation();
11416
+ }
11417
+ /**
11418
+ * Stop capturing and restore original functions
11419
+ */
11420
+ stopCapture() {
11421
+ if (!this.isCapturing) return;
11422
+ this.isCapturing = false;
11423
+ if (this.originalConsole.log) console.log = this.originalConsole.log;
11424
+ if (this.originalConsole.warn) console.warn = this.originalConsole.warn;
11425
+ if (this.originalConsole.error) console.error = this.originalConsole.error;
11426
+ if (this.originalConsole.info) console.info = this.originalConsole.info;
11427
+ if (this.originalFetch && typeof window !== "undefined") {
11428
+ window.fetch = this.originalFetch;
11429
+ }
11430
+ if (typeof window !== "undefined" && typeof history !== "undefined") {
11431
+ if (this.originalPushState) {
11432
+ history.pushState = this.originalPushState;
11433
+ }
11434
+ if (this.originalReplaceState) {
11435
+ history.replaceState = this.originalReplaceState;
11436
+ }
11437
+ if (this.popstateHandler) {
11438
+ window.removeEventListener("popstate", this.popstateHandler);
11439
+ }
11440
+ }
11441
+ }
11442
+ /**
11443
+ * Get captured context for a bug report
11444
+ */
11445
+ getEnhancedContext() {
11446
+ return {
11447
+ consoleLogs: [...this.consoleLogs],
11448
+ networkRequests: [...this.networkRequests],
11449
+ navigationHistory: [...this.navigationHistory],
11450
+ performanceMetrics: this.getPerformanceMetrics(),
11451
+ environment: this.getEnvironmentInfo()
11452
+ };
11453
+ }
11454
+ /**
11455
+ * Get the auto-captured navigation history
11456
+ */
11457
+ getNavigationHistory() {
11458
+ return [...this.navigationHistory];
11459
+ }
11460
+ /**
11461
+ * Get the current route (last entry in navigation history, or window.location)
11462
+ */
11463
+ getCurrentRoute() {
11464
+ if (this.navigationHistory.length > 0) {
11465
+ return this.navigationHistory[this.navigationHistory.length - 1];
11466
+ }
11467
+ if (typeof window !== "undefined") {
11468
+ return window.location.pathname;
11469
+ }
11470
+ return "unknown";
11471
+ }
11472
+ /**
11473
+ * Manually track a navigation event (for React Native or custom routing)
11474
+ */
11475
+ trackNavigation(route) {
11476
+ const last = this.navigationHistory[this.navigationHistory.length - 1];
11477
+ if (route === last) return;
11478
+ this.navigationHistory.push(route);
11479
+ if (this.navigationHistory.length > MAX_NAVIGATION_HISTORY) {
11480
+ this.navigationHistory.shift();
11481
+ }
11482
+ }
11483
+ /**
11484
+ * Clear captured data
11485
+ */
11486
+ clear() {
11487
+ this.consoleLogs = [];
11488
+ this.networkRequests = [];
11489
+ this.navigationHistory = [];
11490
+ }
11491
+ /**
11492
+ * Add a log entry manually (for custom logging)
11493
+ */
11494
+ addLog(level, message, args) {
11495
+ this.consoleLogs.push({
11496
+ level,
11497
+ message,
11498
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11499
+ args
11500
+ });
11501
+ if (this.consoleLogs.length > MAX_CONSOLE_LOGS) {
11502
+ this.consoleLogs = this.consoleLogs.slice(-MAX_CONSOLE_LOGS);
11503
+ }
11504
+ }
11505
+ /**
11506
+ * Add a network request manually
11507
+ */
11508
+ addNetworkRequest(request) {
11509
+ this.networkRequests.push(request);
11510
+ if (this.networkRequests.length > MAX_NETWORK_REQUESTS) {
11511
+ this.networkRequests = this.networkRequests.slice(-MAX_NETWORK_REQUESTS);
11512
+ }
11513
+ }
11514
+ captureConsole() {
11515
+ if (typeof console === "undefined") return;
11516
+ const levels = ["log", "warn", "error", "info"];
11517
+ levels.forEach((level) => {
11518
+ this.originalConsole[level] = console[level];
11519
+ console[level] = (...args) => {
11520
+ this.originalConsole[level]?.apply(console, args);
11521
+ try {
11522
+ const message = args.map((arg) => {
11523
+ if (typeof arg === "string") return arg;
11524
+ try {
11525
+ return JSON.stringify(arg);
11526
+ } catch {
11527
+ return String(arg);
11528
+ }
11529
+ }).join(" ");
11530
+ this.addLog(level, message.slice(0, 500));
11531
+ } catch {
11532
+ }
11533
+ };
11534
+ });
11535
+ }
11536
+ captureFetch() {
11537
+ if (typeof window === "undefined" || typeof fetch === "undefined") return;
11538
+ this.originalFetch = window.fetch;
11539
+ const self2 = this;
11540
+ window.fetch = async function(input, init) {
11541
+ const startTime = Date.now();
11542
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
11543
+ const method = init?.method || "GET";
11544
+ try {
11545
+ const response = await self2.originalFetch.call(window, input, init);
11546
+ const requestEntry = {
11547
+ method,
11548
+ url: url.slice(0, 200),
11549
+ // Limit URL length
11550
+ status: response.status,
11551
+ duration: Date.now() - startTime,
11552
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11553
+ };
11554
+ if (response.status >= 400) {
11555
+ try {
11556
+ const cloned = response.clone();
11557
+ const body = await cloned.text();
11558
+ if (body) {
11559
+ requestEntry.responseBody = body.slice(0, MAX_RESPONSE_BODY_LENGTH);
11560
+ }
11561
+ } catch {
11562
+ }
11563
+ }
11564
+ self2.addNetworkRequest(requestEntry);
11565
+ return response;
11566
+ } catch (error) {
11567
+ self2.addNetworkRequest({
11568
+ method,
11569
+ url: url.slice(0, 200),
11570
+ duration: Date.now() - startTime,
11571
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11572
+ error: error instanceof Error ? error.message : "Unknown error"
11573
+ });
11574
+ throw error;
11575
+ }
11576
+ };
11577
+ }
11578
+ captureNavigation() {
11579
+ if (typeof window === "undefined" || typeof history === "undefined") return;
11580
+ this.trackNavigation(window.location.pathname);
11581
+ const self2 = this;
11582
+ this.originalPushState = history.pushState;
11583
+ history.pushState = function(...args) {
11584
+ self2.originalPushState.apply(history, args);
11585
+ self2.trackNavigation(window.location.pathname);
11586
+ };
11587
+ this.originalReplaceState = history.replaceState;
11588
+ history.replaceState = function(...args) {
11589
+ self2.originalReplaceState.apply(history, args);
11590
+ self2.trackNavigation(window.location.pathname);
11591
+ };
11592
+ this.popstateHandler = () => {
11593
+ self2.trackNavigation(window.location.pathname);
11594
+ };
11595
+ window.addEventListener("popstate", this.popstateHandler);
11596
+ }
11597
+ getPerformanceMetrics() {
11598
+ if (typeof window === "undefined" || typeof performance === "undefined") return void 0;
11599
+ const metrics = {};
11600
+ try {
11601
+ const navigation = performance.getEntriesByType("navigation")[0];
11602
+ if (navigation) {
11603
+ metrics.pageLoadTime = Math.round(navigation.loadEventEnd - navigation.startTime);
11604
+ }
11605
+ } catch {
11606
+ }
11607
+ try {
11608
+ const memory = performance.memory;
11609
+ if (memory) {
11610
+ metrics.memoryUsage = Math.round(memory.usedJSHeapSize / 1024 / 1024);
11611
+ }
11612
+ } catch {
11613
+ }
11614
+ return Object.keys(metrics).length > 0 ? metrics : void 0;
11615
+ }
11616
+ getEnvironmentInfo() {
11617
+ if (typeof window === "undefined" || typeof navigator === "undefined") return void 0;
11618
+ return {
11619
+ language: navigator.language,
11620
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
11621
+ cookiesEnabled: navigator.cookieEnabled,
11622
+ localStorage: typeof localStorage !== "undefined",
11623
+ online: navigator.onLine
11624
+ };
11625
+ }
11626
+ };
11627
+ var contextCapture = new ContextCaptureManager();
11395
11628
  var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
11396
11629
  var getEnvVar = (key) => {
11397
11630
  try {
@@ -11413,22 +11646,40 @@ var BugBearClient = class {
11413
11646
  );
11414
11647
  }
11415
11648
  /**
11416
- * Track navigation for context
11649
+ * Track navigation for context.
11650
+ * Also forwards to contextCapture for auto-tracked navigation.
11417
11651
  */
11418
11652
  trackNavigation(route) {
11419
11653
  this.navigationHistory.push(route);
11420
11654
  if (this.navigationHistory.length > 10) {
11421
11655
  this.navigationHistory.shift();
11422
11656
  }
11657
+ contextCapture.trackNavigation(route);
11423
11658
  }
11424
11659
  /**
11425
- * Get current navigation history
11660
+ * Get current navigation history.
11661
+ * Priority: config callback > manual tracking > auto-captured (pushState/popstate)
11426
11662
  */
11427
11663
  getNavigationHistory() {
11428
11664
  if (this.config.getNavigationHistory) {
11429
11665
  return this.config.getNavigationHistory();
11430
11666
  }
11431
- return [...this.navigationHistory];
11667
+ if (this.navigationHistory.length > 0) {
11668
+ return [...this.navigationHistory];
11669
+ }
11670
+ return contextCapture.getNavigationHistory();
11671
+ }
11672
+ /**
11673
+ * Get current app context.
11674
+ * Uses config.getAppContext() callback if provided, otherwise builds from available data.
11675
+ */
11676
+ getAppContext() {
11677
+ if (this.config.getAppContext) {
11678
+ return this.config.getAppContext();
11679
+ }
11680
+ return {
11681
+ currentRoute: contextCapture.getCurrentRoute()
11682
+ };
11432
11683
  }
11433
11684
  /**
11434
11685
  * Get current user info from host app or BugBear's own auth
@@ -11468,6 +11719,8 @@ var BugBearClient = class {
11468
11719
  project_id: this.config.projectId,
11469
11720
  reporter_id: userInfo.id,
11470
11721
  // User ID from host app (required)
11722
+ reporter_name: testerInfo?.name || userInfo.name || null,
11723
+ reporter_email: userInfo.email || null,
11471
11724
  tester_id: testerInfo?.id || null,
11472
11725
  // Tester record ID (optional)
11473
11726
  report_type: report.type,
@@ -11481,6 +11734,7 @@ var BugBearClient = class {
11481
11734
  app_context: report.appContext,
11482
11735
  device_info: report.deviceInfo || this.getDeviceInfo(),
11483
11736
  navigation_history: this.getNavigationHistory(),
11737
+ enhanced_context: report.enhancedContext || contextCapture.getEnhancedContext(),
11484
11738
  assignment_id: report.assignmentId,
11485
11739
  test_case_id: report.testCaseId
11486
11740
  };
@@ -12714,163 +12968,6 @@ var BugBearClient = class {
12714
12968
  function createBugBear(config) {
12715
12969
  return new BugBearClient(config);
12716
12970
  }
12717
- var MAX_CONSOLE_LOGS = 50;
12718
- var MAX_NETWORK_REQUESTS = 20;
12719
- var ContextCaptureManager = class {
12720
- constructor() {
12721
- this.consoleLogs = [];
12722
- this.networkRequests = [];
12723
- this.originalConsole = {};
12724
- this.isCapturing = false;
12725
- }
12726
- /**
12727
- * Start capturing console logs and network requests
12728
- */
12729
- startCapture() {
12730
- if (this.isCapturing) return;
12731
- this.isCapturing = true;
12732
- this.captureConsole();
12733
- this.captureFetch();
12734
- }
12735
- /**
12736
- * Stop capturing and restore original functions
12737
- */
12738
- stopCapture() {
12739
- if (!this.isCapturing) return;
12740
- this.isCapturing = false;
12741
- if (this.originalConsole.log) console.log = this.originalConsole.log;
12742
- if (this.originalConsole.warn) console.warn = this.originalConsole.warn;
12743
- if (this.originalConsole.error) console.error = this.originalConsole.error;
12744
- if (this.originalConsole.info) console.info = this.originalConsole.info;
12745
- if (this.originalFetch && typeof window !== "undefined") {
12746
- window.fetch = this.originalFetch;
12747
- }
12748
- }
12749
- /**
12750
- * Get captured context for a bug report
12751
- */
12752
- getEnhancedContext() {
12753
- return {
12754
- consoleLogs: [...this.consoleLogs],
12755
- networkRequests: [...this.networkRequests],
12756
- performanceMetrics: this.getPerformanceMetrics(),
12757
- environment: this.getEnvironmentInfo()
12758
- };
12759
- }
12760
- /**
12761
- * Clear captured data
12762
- */
12763
- clear() {
12764
- this.consoleLogs = [];
12765
- this.networkRequests = [];
12766
- }
12767
- /**
12768
- * Add a log entry manually (for custom logging)
12769
- */
12770
- addLog(level, message, args) {
12771
- this.consoleLogs.push({
12772
- level,
12773
- message,
12774
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12775
- args
12776
- });
12777
- if (this.consoleLogs.length > MAX_CONSOLE_LOGS) {
12778
- this.consoleLogs = this.consoleLogs.slice(-MAX_CONSOLE_LOGS);
12779
- }
12780
- }
12781
- /**
12782
- * Add a network request manually
12783
- */
12784
- addNetworkRequest(request) {
12785
- this.networkRequests.push(request);
12786
- if (this.networkRequests.length > MAX_NETWORK_REQUESTS) {
12787
- this.networkRequests = this.networkRequests.slice(-MAX_NETWORK_REQUESTS);
12788
- }
12789
- }
12790
- captureConsole() {
12791
- if (typeof console === "undefined") return;
12792
- const levels = ["log", "warn", "error", "info"];
12793
- levels.forEach((level) => {
12794
- this.originalConsole[level] = console[level];
12795
- console[level] = (...args) => {
12796
- this.originalConsole[level]?.apply(console, args);
12797
- try {
12798
- const message = args.map((arg) => {
12799
- if (typeof arg === "string") return arg;
12800
- try {
12801
- return JSON.stringify(arg);
12802
- } catch {
12803
- return String(arg);
12804
- }
12805
- }).join(" ");
12806
- this.addLog(level, message.slice(0, 500));
12807
- } catch {
12808
- }
12809
- };
12810
- });
12811
- }
12812
- captureFetch() {
12813
- if (typeof window === "undefined" || typeof fetch === "undefined") return;
12814
- this.originalFetch = window.fetch;
12815
- const self2 = this;
12816
- window.fetch = async function(input, init) {
12817
- const startTime = Date.now();
12818
- const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
12819
- const method = init?.method || "GET";
12820
- try {
12821
- const response = await self2.originalFetch.call(window, input, init);
12822
- self2.addNetworkRequest({
12823
- method,
12824
- url: url.slice(0, 200),
12825
- // Limit URL length
12826
- status: response.status,
12827
- duration: Date.now() - startTime,
12828
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
12829
- });
12830
- return response;
12831
- } catch (error) {
12832
- self2.addNetworkRequest({
12833
- method,
12834
- url: url.slice(0, 200),
12835
- duration: Date.now() - startTime,
12836
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12837
- error: error instanceof Error ? error.message : "Unknown error"
12838
- });
12839
- throw error;
12840
- }
12841
- };
12842
- }
12843
- getPerformanceMetrics() {
12844
- if (typeof window === "undefined" || typeof performance === "undefined") return void 0;
12845
- const metrics = {};
12846
- try {
12847
- const navigation = performance.getEntriesByType("navigation")[0];
12848
- if (navigation) {
12849
- metrics.pageLoadTime = Math.round(navigation.loadEventEnd - navigation.startTime);
12850
- }
12851
- } catch {
12852
- }
12853
- try {
12854
- const memory = performance.memory;
12855
- if (memory) {
12856
- metrics.memoryUsage = Math.round(memory.usedJSHeapSize / 1024 / 1024);
12857
- }
12858
- } catch {
12859
- }
12860
- return Object.keys(metrics).length > 0 ? metrics : void 0;
12861
- }
12862
- getEnvironmentInfo() {
12863
- if (typeof window === "undefined" || typeof navigator === "undefined") return void 0;
12864
- return {
12865
- language: navigator.language,
12866
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
12867
- cookiesEnabled: navigator.cookieEnabled,
12868
- localStorage: typeof localStorage !== "undefined",
12869
- online: navigator.onLine
12870
- };
12871
- }
12872
- };
12873
- var contextCapture = new ContextCaptureManager();
12874
12971
 
12875
12972
  // src/BugBearProvider.tsx
12876
12973
  var import_react_native = require("react-native");
@@ -13071,6 +13168,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
13071
13168
  (0, import_react.useEffect)(() => {
13072
13169
  if (enabled && !hasInitialized.current) {
13073
13170
  hasInitialized.current = true;
13171
+ contextCapture.startCapture();
13074
13172
  const newClient = createBugBear(config);
13075
13173
  setClient(newClient);
13076
13174
  initializeBugBear(newClient);
@@ -14296,12 +14394,18 @@ function ReportScreen({ nav, prefill }) {
14296
14394
  const [reportType, setReportType] = (0, import_react10.useState)(prefill?.type || "bug");
14297
14395
  const [severity, setSeverity] = (0, import_react10.useState)("medium");
14298
14396
  const [description, setDescription] = (0, import_react10.useState)("");
14397
+ const [affectedScreen, setAffectedScreen] = (0, import_react10.useState)("");
14299
14398
  const [submitting, setSubmitting] = (0, import_react10.useState)(false);
14300
14399
  const images = useImageAttachments(uploadImage, 5, "screenshots");
14301
14400
  const isBugType = reportType === "bug" || reportType === "test_fail";
14302
14401
  const handleSubmit = async () => {
14303
14402
  if (!client || !description.trim()) return;
14304
14403
  setSubmitting(true);
14404
+ const baseContext = client.getAppContext();
14405
+ const appContext = {
14406
+ ...baseContext,
14407
+ currentRoute: affectedScreen.trim() || baseContext.currentRoute
14408
+ };
14305
14409
  const screenshotUrls = images.getScreenshotUrls();
14306
14410
  await client.submitReport({
14307
14411
  type: reportType,
@@ -14309,7 +14413,7 @@ function ReportScreen({ nav, prefill }) {
14309
14413
  severity: isBugType ? severity : void 0,
14310
14414
  assignmentId: prefill?.assignmentId,
14311
14415
  testCaseId: prefill?.testCaseId,
14312
- appContext: { currentRoute: "unknown" },
14416
+ appContext,
14313
14417
  deviceInfo: getDeviceInfo(),
14314
14418
  screenshots: screenshotUrls.length > 0 ? screenshotUrls : void 0
14315
14419
  });
@@ -14357,7 +14461,16 @@ function ReportScreen({ nav, prefill }) {
14357
14461
  numberOfLines: 4,
14358
14462
  textAlignVertical: "top"
14359
14463
  }
14360
- )), /* @__PURE__ */ import_react10.default.createElement(
14464
+ )), isBugType && /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.section }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.label }, "Which screen?"), /* @__PURE__ */ import_react10.default.createElement(
14465
+ import_react_native9.TextInput,
14466
+ {
14467
+ style: styles7.screenInput,
14468
+ value: affectedScreen,
14469
+ onChangeText: setAffectedScreen,
14470
+ placeholder: "e.g. Reservations, Settings...",
14471
+ placeholderTextColor: colors.textMuted
14472
+ }
14473
+ ), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.screenHint }, "Which screen or area was the bug on? (optional)")), /* @__PURE__ */ import_react10.default.createElement(
14361
14474
  ImagePickerButtons,
14362
14475
  {
14363
14476
  images: images.images,
@@ -14388,7 +14501,9 @@ var styles7 = import_react_native9.StyleSheet.create({
14388
14501
  severityRow: { flexDirection: "row", gap: 8 },
14389
14502
  sevButton: { flex: 1, paddingVertical: 8, borderRadius: 8, alignItems: "center", borderWidth: 1, borderColor: colors.border, backgroundColor: colors.card },
14390
14503
  sevText: { fontSize: 12, fontWeight: "500", color: colors.textSecondary, textTransform: "capitalize" },
14391
- descInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 12, paddingHorizontal: 14, paddingVertical: 12, fontSize: 14, color: colors.textPrimary, minHeight: 100 }
14504
+ descInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 12, paddingHorizontal: 14, paddingVertical: 12, fontSize: 14, color: colors.textPrimary, minHeight: 100 },
14505
+ screenInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 13, color: colors.textPrimary },
14506
+ screenHint: { fontSize: 11, color: colors.textMuted, marginTop: 4 }
14392
14507
  });
14393
14508
 
14394
14509
  // src/widget/screens/ReportSuccessScreen.tsx
package/dist/index.mjs CHANGED
@@ -11361,6 +11361,239 @@ function shouldShowDeprecationWarning() {
11361
11361
  if (shouldShowDeprecationWarning()) console.warn("\u26A0\uFE0F Node.js 18 and below are deprecated and will no longer be supported in future versions of @supabase/supabase-js. Please upgrade to Node.js 20 or later. For more information, visit: https://github.com/orgs/supabase/discussions/37217");
11362
11362
 
11363
11363
  // ../core/dist/index.mjs
11364
+ var MAX_CONSOLE_LOGS = 50;
11365
+ var MAX_NETWORK_REQUESTS = 20;
11366
+ var MAX_NAVIGATION_HISTORY = 20;
11367
+ var MAX_RESPONSE_BODY_LENGTH = 500;
11368
+ var ContextCaptureManager = class {
11369
+ constructor() {
11370
+ this.consoleLogs = [];
11371
+ this.networkRequests = [];
11372
+ this.navigationHistory = [];
11373
+ this.originalConsole = {};
11374
+ this.isCapturing = false;
11375
+ }
11376
+ /**
11377
+ * Start capturing console logs, network requests, and navigation
11378
+ */
11379
+ startCapture() {
11380
+ if (this.isCapturing) return;
11381
+ this.isCapturing = true;
11382
+ this.captureConsole();
11383
+ this.captureFetch();
11384
+ this.captureNavigation();
11385
+ }
11386
+ /**
11387
+ * Stop capturing and restore original functions
11388
+ */
11389
+ stopCapture() {
11390
+ if (!this.isCapturing) return;
11391
+ this.isCapturing = false;
11392
+ if (this.originalConsole.log) console.log = this.originalConsole.log;
11393
+ if (this.originalConsole.warn) console.warn = this.originalConsole.warn;
11394
+ if (this.originalConsole.error) console.error = this.originalConsole.error;
11395
+ if (this.originalConsole.info) console.info = this.originalConsole.info;
11396
+ if (this.originalFetch && typeof window !== "undefined") {
11397
+ window.fetch = this.originalFetch;
11398
+ }
11399
+ if (typeof window !== "undefined" && typeof history !== "undefined") {
11400
+ if (this.originalPushState) {
11401
+ history.pushState = this.originalPushState;
11402
+ }
11403
+ if (this.originalReplaceState) {
11404
+ history.replaceState = this.originalReplaceState;
11405
+ }
11406
+ if (this.popstateHandler) {
11407
+ window.removeEventListener("popstate", this.popstateHandler);
11408
+ }
11409
+ }
11410
+ }
11411
+ /**
11412
+ * Get captured context for a bug report
11413
+ */
11414
+ getEnhancedContext() {
11415
+ return {
11416
+ consoleLogs: [...this.consoleLogs],
11417
+ networkRequests: [...this.networkRequests],
11418
+ navigationHistory: [...this.navigationHistory],
11419
+ performanceMetrics: this.getPerformanceMetrics(),
11420
+ environment: this.getEnvironmentInfo()
11421
+ };
11422
+ }
11423
+ /**
11424
+ * Get the auto-captured navigation history
11425
+ */
11426
+ getNavigationHistory() {
11427
+ return [...this.navigationHistory];
11428
+ }
11429
+ /**
11430
+ * Get the current route (last entry in navigation history, or window.location)
11431
+ */
11432
+ getCurrentRoute() {
11433
+ if (this.navigationHistory.length > 0) {
11434
+ return this.navigationHistory[this.navigationHistory.length - 1];
11435
+ }
11436
+ if (typeof window !== "undefined") {
11437
+ return window.location.pathname;
11438
+ }
11439
+ return "unknown";
11440
+ }
11441
+ /**
11442
+ * Manually track a navigation event (for React Native or custom routing)
11443
+ */
11444
+ trackNavigation(route) {
11445
+ const last = this.navigationHistory[this.navigationHistory.length - 1];
11446
+ if (route === last) return;
11447
+ this.navigationHistory.push(route);
11448
+ if (this.navigationHistory.length > MAX_NAVIGATION_HISTORY) {
11449
+ this.navigationHistory.shift();
11450
+ }
11451
+ }
11452
+ /**
11453
+ * Clear captured data
11454
+ */
11455
+ clear() {
11456
+ this.consoleLogs = [];
11457
+ this.networkRequests = [];
11458
+ this.navigationHistory = [];
11459
+ }
11460
+ /**
11461
+ * Add a log entry manually (for custom logging)
11462
+ */
11463
+ addLog(level, message, args) {
11464
+ this.consoleLogs.push({
11465
+ level,
11466
+ message,
11467
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11468
+ args
11469
+ });
11470
+ if (this.consoleLogs.length > MAX_CONSOLE_LOGS) {
11471
+ this.consoleLogs = this.consoleLogs.slice(-MAX_CONSOLE_LOGS);
11472
+ }
11473
+ }
11474
+ /**
11475
+ * Add a network request manually
11476
+ */
11477
+ addNetworkRequest(request) {
11478
+ this.networkRequests.push(request);
11479
+ if (this.networkRequests.length > MAX_NETWORK_REQUESTS) {
11480
+ this.networkRequests = this.networkRequests.slice(-MAX_NETWORK_REQUESTS);
11481
+ }
11482
+ }
11483
+ captureConsole() {
11484
+ if (typeof console === "undefined") return;
11485
+ const levels = ["log", "warn", "error", "info"];
11486
+ levels.forEach((level) => {
11487
+ this.originalConsole[level] = console[level];
11488
+ console[level] = (...args) => {
11489
+ this.originalConsole[level]?.apply(console, args);
11490
+ try {
11491
+ const message = args.map((arg) => {
11492
+ if (typeof arg === "string") return arg;
11493
+ try {
11494
+ return JSON.stringify(arg);
11495
+ } catch {
11496
+ return String(arg);
11497
+ }
11498
+ }).join(" ");
11499
+ this.addLog(level, message.slice(0, 500));
11500
+ } catch {
11501
+ }
11502
+ };
11503
+ });
11504
+ }
11505
+ captureFetch() {
11506
+ if (typeof window === "undefined" || typeof fetch === "undefined") return;
11507
+ this.originalFetch = window.fetch;
11508
+ const self2 = this;
11509
+ window.fetch = async function(input, init) {
11510
+ const startTime = Date.now();
11511
+ const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
11512
+ const method = init?.method || "GET";
11513
+ try {
11514
+ const response = await self2.originalFetch.call(window, input, init);
11515
+ const requestEntry = {
11516
+ method,
11517
+ url: url.slice(0, 200),
11518
+ // Limit URL length
11519
+ status: response.status,
11520
+ duration: Date.now() - startTime,
11521
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
11522
+ };
11523
+ if (response.status >= 400) {
11524
+ try {
11525
+ const cloned = response.clone();
11526
+ const body = await cloned.text();
11527
+ if (body) {
11528
+ requestEntry.responseBody = body.slice(0, MAX_RESPONSE_BODY_LENGTH);
11529
+ }
11530
+ } catch {
11531
+ }
11532
+ }
11533
+ self2.addNetworkRequest(requestEntry);
11534
+ return response;
11535
+ } catch (error) {
11536
+ self2.addNetworkRequest({
11537
+ method,
11538
+ url: url.slice(0, 200),
11539
+ duration: Date.now() - startTime,
11540
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
11541
+ error: error instanceof Error ? error.message : "Unknown error"
11542
+ });
11543
+ throw error;
11544
+ }
11545
+ };
11546
+ }
11547
+ captureNavigation() {
11548
+ if (typeof window === "undefined" || typeof history === "undefined") return;
11549
+ this.trackNavigation(window.location.pathname);
11550
+ const self2 = this;
11551
+ this.originalPushState = history.pushState;
11552
+ history.pushState = function(...args) {
11553
+ self2.originalPushState.apply(history, args);
11554
+ self2.trackNavigation(window.location.pathname);
11555
+ };
11556
+ this.originalReplaceState = history.replaceState;
11557
+ history.replaceState = function(...args) {
11558
+ self2.originalReplaceState.apply(history, args);
11559
+ self2.trackNavigation(window.location.pathname);
11560
+ };
11561
+ this.popstateHandler = () => {
11562
+ self2.trackNavigation(window.location.pathname);
11563
+ };
11564
+ window.addEventListener("popstate", this.popstateHandler);
11565
+ }
11566
+ getPerformanceMetrics() {
11567
+ if (typeof window === "undefined" || typeof performance === "undefined") return void 0;
11568
+ const metrics = {};
11569
+ try {
11570
+ const navigation = performance.getEntriesByType("navigation")[0];
11571
+ if (navigation) {
11572
+ metrics.pageLoadTime = Math.round(navigation.loadEventEnd - navigation.startTime);
11573
+ }
11574
+ } catch {
11575
+ }
11576
+ try {
11577
+ const memory = performance.memory;
11578
+ if (memory) {
11579
+ metrics.memoryUsage = Math.round(memory.usedJSHeapSize / 1024 / 1024);
11580
+ }
11581
+ } catch {
11582
+ }
11583
+ return Object.keys(metrics).length > 0 ? metrics : void 0;
11584
+ }
11585
+ getEnvironmentInfo() {
11586
+ if (typeof window === "undefined" || typeof navigator === "undefined") return void 0;
11587
+ return {
11588
+ language: navigator.language,
11589
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
11590
+ cookiesEnabled: navigator.cookieEnabled,
11591
+ localStorage: typeof localStorage !== "undefined",
11592
+ online: navigator.onLine
11593
+ };
11594
+ }
11595
+ };
11596
+ var contextCapture = new ContextCaptureManager();
11364
11597
  var DEFAULT_SUPABASE_URL = "https://kyxgzjnqgvapvlnvqawz.supabase.co";
11365
11598
  var getEnvVar = (key) => {
11366
11599
  try {
@@ -11382,22 +11615,40 @@ var BugBearClient = class {
11382
11615
  );
11383
11616
  }
11384
11617
  /**
11385
- * Track navigation for context
11618
+ * Track navigation for context.
11619
+ * Also forwards to contextCapture for auto-tracked navigation.
11386
11620
  */
11387
11621
  trackNavigation(route) {
11388
11622
  this.navigationHistory.push(route);
11389
11623
  if (this.navigationHistory.length > 10) {
11390
11624
  this.navigationHistory.shift();
11391
11625
  }
11626
+ contextCapture.trackNavigation(route);
11392
11627
  }
11393
11628
  /**
11394
- * Get current navigation history
11629
+ * Get current navigation history.
11630
+ * Priority: config callback > manual tracking > auto-captured (pushState/popstate)
11395
11631
  */
11396
11632
  getNavigationHistory() {
11397
11633
  if (this.config.getNavigationHistory) {
11398
11634
  return this.config.getNavigationHistory();
11399
11635
  }
11400
- return [...this.navigationHistory];
11636
+ if (this.navigationHistory.length > 0) {
11637
+ return [...this.navigationHistory];
11638
+ }
11639
+ return contextCapture.getNavigationHistory();
11640
+ }
11641
+ /**
11642
+ * Get current app context.
11643
+ * Uses config.getAppContext() callback if provided, otherwise builds from available data.
11644
+ */
11645
+ getAppContext() {
11646
+ if (this.config.getAppContext) {
11647
+ return this.config.getAppContext();
11648
+ }
11649
+ return {
11650
+ currentRoute: contextCapture.getCurrentRoute()
11651
+ };
11401
11652
  }
11402
11653
  /**
11403
11654
  * Get current user info from host app or BugBear's own auth
@@ -11437,6 +11688,8 @@ var BugBearClient = class {
11437
11688
  project_id: this.config.projectId,
11438
11689
  reporter_id: userInfo.id,
11439
11690
  // User ID from host app (required)
11691
+ reporter_name: testerInfo?.name || userInfo.name || null,
11692
+ reporter_email: userInfo.email || null,
11440
11693
  tester_id: testerInfo?.id || null,
11441
11694
  // Tester record ID (optional)
11442
11695
  report_type: report.type,
@@ -11450,6 +11703,7 @@ var BugBearClient = class {
11450
11703
  app_context: report.appContext,
11451
11704
  device_info: report.deviceInfo || this.getDeviceInfo(),
11452
11705
  navigation_history: this.getNavigationHistory(),
11706
+ enhanced_context: report.enhancedContext || contextCapture.getEnhancedContext(),
11453
11707
  assignment_id: report.assignmentId,
11454
11708
  test_case_id: report.testCaseId
11455
11709
  };
@@ -12683,163 +12937,6 @@ var BugBearClient = class {
12683
12937
  function createBugBear(config) {
12684
12938
  return new BugBearClient(config);
12685
12939
  }
12686
- var MAX_CONSOLE_LOGS = 50;
12687
- var MAX_NETWORK_REQUESTS = 20;
12688
- var ContextCaptureManager = class {
12689
- constructor() {
12690
- this.consoleLogs = [];
12691
- this.networkRequests = [];
12692
- this.originalConsole = {};
12693
- this.isCapturing = false;
12694
- }
12695
- /**
12696
- * Start capturing console logs and network requests
12697
- */
12698
- startCapture() {
12699
- if (this.isCapturing) return;
12700
- this.isCapturing = true;
12701
- this.captureConsole();
12702
- this.captureFetch();
12703
- }
12704
- /**
12705
- * Stop capturing and restore original functions
12706
- */
12707
- stopCapture() {
12708
- if (!this.isCapturing) return;
12709
- this.isCapturing = false;
12710
- if (this.originalConsole.log) console.log = this.originalConsole.log;
12711
- if (this.originalConsole.warn) console.warn = this.originalConsole.warn;
12712
- if (this.originalConsole.error) console.error = this.originalConsole.error;
12713
- if (this.originalConsole.info) console.info = this.originalConsole.info;
12714
- if (this.originalFetch && typeof window !== "undefined") {
12715
- window.fetch = this.originalFetch;
12716
- }
12717
- }
12718
- /**
12719
- * Get captured context for a bug report
12720
- */
12721
- getEnhancedContext() {
12722
- return {
12723
- consoleLogs: [...this.consoleLogs],
12724
- networkRequests: [...this.networkRequests],
12725
- performanceMetrics: this.getPerformanceMetrics(),
12726
- environment: this.getEnvironmentInfo()
12727
- };
12728
- }
12729
- /**
12730
- * Clear captured data
12731
- */
12732
- clear() {
12733
- this.consoleLogs = [];
12734
- this.networkRequests = [];
12735
- }
12736
- /**
12737
- * Add a log entry manually (for custom logging)
12738
- */
12739
- addLog(level, message, args) {
12740
- this.consoleLogs.push({
12741
- level,
12742
- message,
12743
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12744
- args
12745
- });
12746
- if (this.consoleLogs.length > MAX_CONSOLE_LOGS) {
12747
- this.consoleLogs = this.consoleLogs.slice(-MAX_CONSOLE_LOGS);
12748
- }
12749
- }
12750
- /**
12751
- * Add a network request manually
12752
- */
12753
- addNetworkRequest(request) {
12754
- this.networkRequests.push(request);
12755
- if (this.networkRequests.length > MAX_NETWORK_REQUESTS) {
12756
- this.networkRequests = this.networkRequests.slice(-MAX_NETWORK_REQUESTS);
12757
- }
12758
- }
12759
- captureConsole() {
12760
- if (typeof console === "undefined") return;
12761
- const levels = ["log", "warn", "error", "info"];
12762
- levels.forEach((level) => {
12763
- this.originalConsole[level] = console[level];
12764
- console[level] = (...args) => {
12765
- this.originalConsole[level]?.apply(console, args);
12766
- try {
12767
- const message = args.map((arg) => {
12768
- if (typeof arg === "string") return arg;
12769
- try {
12770
- return JSON.stringify(arg);
12771
- } catch {
12772
- return String(arg);
12773
- }
12774
- }).join(" ");
12775
- this.addLog(level, message.slice(0, 500));
12776
- } catch {
12777
- }
12778
- };
12779
- });
12780
- }
12781
- captureFetch() {
12782
- if (typeof window === "undefined" || typeof fetch === "undefined") return;
12783
- this.originalFetch = window.fetch;
12784
- const self2 = this;
12785
- window.fetch = async function(input, init) {
12786
- const startTime = Date.now();
12787
- const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
12788
- const method = init?.method || "GET";
12789
- try {
12790
- const response = await self2.originalFetch.call(window, input, init);
12791
- self2.addNetworkRequest({
12792
- method,
12793
- url: url.slice(0, 200),
12794
- // Limit URL length
12795
- status: response.status,
12796
- duration: Date.now() - startTime,
12797
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
12798
- });
12799
- return response;
12800
- } catch (error) {
12801
- self2.addNetworkRequest({
12802
- method,
12803
- url: url.slice(0, 200),
12804
- duration: Date.now() - startTime,
12805
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12806
- error: error instanceof Error ? error.message : "Unknown error"
12807
- });
12808
- throw error;
12809
- }
12810
- };
12811
- }
12812
- getPerformanceMetrics() {
12813
- if (typeof window === "undefined" || typeof performance === "undefined") return void 0;
12814
- const metrics = {};
12815
- try {
12816
- const navigation = performance.getEntriesByType("navigation")[0];
12817
- if (navigation) {
12818
- metrics.pageLoadTime = Math.round(navigation.loadEventEnd - navigation.startTime);
12819
- }
12820
- } catch {
12821
- }
12822
- try {
12823
- const memory = performance.memory;
12824
- if (memory) {
12825
- metrics.memoryUsage = Math.round(memory.usedJSHeapSize / 1024 / 1024);
12826
- }
12827
- } catch {
12828
- }
12829
- return Object.keys(metrics).length > 0 ? metrics : void 0;
12830
- }
12831
- getEnvironmentInfo() {
12832
- if (typeof window === "undefined" || typeof navigator === "undefined") return void 0;
12833
- return {
12834
- language: navigator.language,
12835
- timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
12836
- cookiesEnabled: navigator.cookieEnabled,
12837
- localStorage: typeof localStorage !== "undefined",
12838
- online: navigator.onLine
12839
- };
12840
- }
12841
- };
12842
- var contextCapture = new ContextCaptureManager();
12843
12940
 
12844
12941
  // src/BugBearProvider.tsx
12845
12942
  import { Platform, Dimensions } from "react-native";
@@ -13040,6 +13137,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
13040
13137
  useEffect(() => {
13041
13138
  if (enabled && !hasInitialized.current) {
13042
13139
  hasInitialized.current = true;
13140
+ contextCapture.startCapture();
13043
13141
  const newClient = createBugBear(config);
13044
13142
  setClient(newClient);
13045
13143
  initializeBugBear(newClient);
@@ -14279,12 +14377,18 @@ function ReportScreen({ nav, prefill }) {
14279
14377
  const [reportType, setReportType] = useState6(prefill?.type || "bug");
14280
14378
  const [severity, setSeverity] = useState6("medium");
14281
14379
  const [description, setDescription] = useState6("");
14380
+ const [affectedScreen, setAffectedScreen] = useState6("");
14282
14381
  const [submitting, setSubmitting] = useState6(false);
14283
14382
  const images = useImageAttachments(uploadImage, 5, "screenshots");
14284
14383
  const isBugType = reportType === "bug" || reportType === "test_fail";
14285
14384
  const handleSubmit = async () => {
14286
14385
  if (!client || !description.trim()) return;
14287
14386
  setSubmitting(true);
14387
+ const baseContext = client.getAppContext();
14388
+ const appContext = {
14389
+ ...baseContext,
14390
+ currentRoute: affectedScreen.trim() || baseContext.currentRoute
14391
+ };
14288
14392
  const screenshotUrls = images.getScreenshotUrls();
14289
14393
  await client.submitReport({
14290
14394
  type: reportType,
@@ -14292,7 +14396,7 @@ function ReportScreen({ nav, prefill }) {
14292
14396
  severity: isBugType ? severity : void 0,
14293
14397
  assignmentId: prefill?.assignmentId,
14294
14398
  testCaseId: prefill?.testCaseId,
14295
- appContext: { currentRoute: "unknown" },
14399
+ appContext,
14296
14400
  deviceInfo: getDeviceInfo(),
14297
14401
  screenshots: screenshotUrls.length > 0 ? screenshotUrls : void 0
14298
14402
  });
@@ -14340,7 +14444,16 @@ function ReportScreen({ nav, prefill }) {
14340
14444
  numberOfLines: 4,
14341
14445
  textAlignVertical: "top"
14342
14446
  }
14343
- )), /* @__PURE__ */ React8.createElement(
14447
+ )), isBugType && /* @__PURE__ */ React8.createElement(View7, { style: styles7.section }, /* @__PURE__ */ React8.createElement(Text7, { style: shared.label }, "Which screen?"), /* @__PURE__ */ React8.createElement(
14448
+ TextInput3,
14449
+ {
14450
+ style: styles7.screenInput,
14451
+ value: affectedScreen,
14452
+ onChangeText: setAffectedScreen,
14453
+ placeholder: "e.g. Reservations, Settings...",
14454
+ placeholderTextColor: colors.textMuted
14455
+ }
14456
+ ), /* @__PURE__ */ React8.createElement(Text7, { style: styles7.screenHint }, "Which screen or area was the bug on? (optional)")), /* @__PURE__ */ React8.createElement(
14344
14457
  ImagePickerButtons,
14345
14458
  {
14346
14459
  images: images.images,
@@ -14371,7 +14484,9 @@ var styles7 = StyleSheet8.create({
14371
14484
  severityRow: { flexDirection: "row", gap: 8 },
14372
14485
  sevButton: { flex: 1, paddingVertical: 8, borderRadius: 8, alignItems: "center", borderWidth: 1, borderColor: colors.border, backgroundColor: colors.card },
14373
14486
  sevText: { fontSize: 12, fontWeight: "500", color: colors.textSecondary, textTransform: "capitalize" },
14374
- descInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 12, paddingHorizontal: 14, paddingVertical: 12, fontSize: 14, color: colors.textPrimary, minHeight: 100 }
14487
+ descInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 12, paddingHorizontal: 14, paddingVertical: 12, fontSize: 14, color: colors.textPrimary, minHeight: 100 },
14488
+ screenInput: { backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border, borderRadius: 8, paddingHorizontal: 12, paddingVertical: 8, fontSize: 13, color: colors.textPrimary },
14489
+ screenHint: { fontSize: 11, color: colors.textMuted, marginTop: 4 }
14375
14490
  });
14376
14491
 
14377
14492
  // src/widget/screens/ReportSuccessScreen.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/react-native",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "BugBear React Native components for mobile apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",