@bbearai/react-native 0.5.0 → 0.5.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 CHANGED
@@ -11393,7 +11393,7 @@ function shouldShowDeprecationWarning() {
11393
11393
  }
11394
11394
  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");
11395
11395
 
11396
- // node_modules/@bbearai/core/dist/index.mjs
11396
+ // ../core/dist/index.mjs
11397
11397
  var MAX_CONSOLE_LOGS = 50;
11398
11398
  var MAX_NETWORK_REQUESTS = 20;
11399
11399
  var MAX_NAVIGATION_HISTORY = 20;
@@ -11653,6 +11653,7 @@ var HOSTED_BUGBEAR_ANON_KEY = getEnvVar("BUGBEAR_ANON_KEY") || getEnvVar("NEXT_P
11653
11653
  var BugBearClient = class {
11654
11654
  constructor(config) {
11655
11655
  this.navigationHistory = [];
11656
+ this.reportSubmitInFlight = false;
11656
11657
  this.config = config;
11657
11658
  this.supabase = createClient(
11658
11659
  config.supabaseUrl || DEFAULT_SUPABASE_URL,
@@ -11713,6 +11714,10 @@ var BugBearClient = class {
11713
11714
  * Submit a report
11714
11715
  */
11715
11716
  async submitReport(report) {
11717
+ if (this.reportSubmitInFlight) {
11718
+ return { success: false, error: "A report is already being submitted" };
11719
+ }
11720
+ this.reportSubmitInFlight = true;
11716
11721
  try {
11717
11722
  const validationError = this.validateReport(report);
11718
11723
  if (validationError) {
@@ -11764,6 +11769,8 @@ var BugBearClient = class {
11764
11769
  } catch (err) {
11765
11770
  const message = err instanceof Error ? err.message : "Unknown error";
11766
11771
  return { success: false, error: message };
11772
+ } finally {
11773
+ this.reportSubmitInFlight = false;
11767
11774
  }
11768
11775
  }
11769
11776
  /**
@@ -11804,6 +11811,14 @@ var BugBearClient = class {
11804
11811
  name,
11805
11812
  description,
11806
11813
  sort_order
11814
+ ),
11815
+ role:project_roles(
11816
+ id,
11817
+ name,
11818
+ slug,
11819
+ color,
11820
+ description,
11821
+ login_hint
11807
11822
  )
11808
11823
  )
11809
11824
  `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
@@ -11841,6 +11856,14 @@ var BugBearClient = class {
11841
11856
  name: item.test_case.group.name,
11842
11857
  description: item.test_case.group.description,
11843
11858
  sortOrder: item.test_case.group.sort_order
11859
+ } : void 0,
11860
+ role: item.test_case.role ? {
11861
+ id: item.test_case.role.id,
11862
+ name: item.test_case.role.name,
11863
+ slug: item.test_case.role.slug,
11864
+ color: item.test_case.role.color,
11865
+ description: item.test_case.role.description,
11866
+ loginHint: item.test_case.role.login_hint
11844
11867
  } : void 0
11845
11868
  }
11846
11869
  }));
@@ -12224,6 +12247,72 @@ var BugBearClient = class {
12224
12247
  return null;
12225
12248
  }
12226
12249
  }
12250
+ /**
12251
+ * Get issue counts for the tester (Open, Done, Reopened)
12252
+ * Used by the widget HomeScreen cards
12253
+ */
12254
+ async getIssueCounts() {
12255
+ try {
12256
+ const testerInfo = await this.getTesterInfo();
12257
+ if (!testerInfo) return { open: 0, done: 0, reopened: 0 };
12258
+ const { data, error } = await this.supabase.rpc("get_tester_issue_counts", {
12259
+ p_project_id: this.config.projectId,
12260
+ p_tester_id: testerInfo.id
12261
+ });
12262
+ if (error) {
12263
+ console.error("BugBear: Failed to fetch issue counts", formatPgError(error));
12264
+ return { open: 0, done: 0, reopened: 0 };
12265
+ }
12266
+ return {
12267
+ open: data?.open ?? 0,
12268
+ done: data?.done ?? 0,
12269
+ reopened: data?.reopened ?? 0
12270
+ };
12271
+ } catch (err) {
12272
+ console.error("BugBear: Error fetching issue counts", err);
12273
+ return { open: 0, done: 0, reopened: 0 };
12274
+ }
12275
+ }
12276
+ /**
12277
+ * Get issues for the tester by category.
12278
+ * Returns enriched data: done issues include verification proof,
12279
+ * reopened issues include original bug context.
12280
+ */
12281
+ async getIssues(category) {
12282
+ try {
12283
+ const testerInfo = await this.getTesterInfo();
12284
+ if (!testerInfo) return [];
12285
+ const { data, error } = await this.supabase.rpc("get_tester_issues", {
12286
+ p_project_id: this.config.projectId,
12287
+ p_tester_id: testerInfo.id,
12288
+ p_category: category
12289
+ });
12290
+ if (error) {
12291
+ console.error("BugBear: Failed to fetch issues", formatPgError(error));
12292
+ return [];
12293
+ }
12294
+ return (data || []).map((row) => ({
12295
+ id: row.id,
12296
+ title: row.title || "Untitled",
12297
+ description: row.description,
12298
+ reportType: row.report_type,
12299
+ severity: row.severity || null,
12300
+ status: row.status,
12301
+ screenshotUrls: row.screenshot_urls || [],
12302
+ route: row.app_context?.currentRoute || void 0,
12303
+ reporterName: row.reporter_name || void 0,
12304
+ createdAt: row.created_at,
12305
+ updatedAt: row.updated_at,
12306
+ verifiedByName: row.verified_by_name || void 0,
12307
+ verifiedAt: row.verified_at || void 0,
12308
+ originalBugId: row.original_bug_id || void 0,
12309
+ originalBugTitle: row.original_bug_title || void 0
12310
+ }));
12311
+ } catch (err) {
12312
+ console.error("BugBear: Error fetching issues", err);
12313
+ return [];
12314
+ }
12315
+ }
12227
12316
  /**
12228
12317
  * Basic email format validation (defense in depth)
12229
12318
  */
@@ -12256,8 +12345,8 @@ var BugBearClient = class {
12256
12345
  return "Maximum 10 screenshots allowed";
12257
12346
  }
12258
12347
  for (const url of report.screenshots) {
12259
- if (typeof url !== "string" || url.length > 2e3) {
12260
- return "Invalid screenshot URL";
12348
+ if (typeof url !== "string" || url.length > 2e3 || !/^https?:\/\//i.test(url)) {
12349
+ return "Invalid screenshot URL (must be an HTTP/HTTPS URL)";
12261
12350
  }
12262
12351
  }
12263
12352
  }
@@ -12646,7 +12735,10 @@ var BugBearClient = class {
12646
12735
  content
12647
12736
  };
12648
12737
  if (attachments && attachments.length > 0) {
12649
- insertData.attachments = attachments;
12738
+ const safeAttachments = attachments.filter((a) => /^https?:\/\//i.test(a.url));
12739
+ if (safeAttachments.length > 0) {
12740
+ insertData.attachments = safeAttachments;
12741
+ }
12650
12742
  }
12651
12743
  const { error } = await this.supabase.from("discussion_messages").insert(insertData);
12652
12744
  if (error) {
@@ -14496,11 +14588,14 @@ function ReportScreen({ nav, prefill }) {
14496
14588
  const [affectedScreen, setAffectedScreen] = (0, import_react10.useState)("");
14497
14589
  const [submitting, setSubmitting] = (0, import_react10.useState)(false);
14498
14590
  const [error, setError] = (0, import_react10.useState)(null);
14591
+ const submittingRef = (0, import_react10.useRef)(false);
14499
14592
  const images = useImageAttachments(uploadImage, 5, "screenshots");
14500
14593
  const isRetestFailure = prefill?.type === "test_fail";
14501
14594
  const isBugType = reportType === "bug" || reportType === "test_fail";
14502
14595
  const handleSubmit = async () => {
14503
14596
  if (!client || !description.trim()) return;
14597
+ if (submittingRef.current) return;
14598
+ submittingRef.current = true;
14504
14599
  setSubmitting(true);
14505
14600
  setError(null);
14506
14601
  try {
@@ -14524,17 +14619,18 @@ function ReportScreen({ nav, prefill }) {
14524
14619
  console.error("BugBear: Report submission failed", result.error);
14525
14620
  setError(result.error || "Failed to submit report. Please try again.");
14526
14621
  setSubmitting(false);
14622
+ submittingRef.current = false;
14527
14623
  return;
14528
14624
  }
14529
14625
  if (prefill?.assignmentId) {
14530
14626
  await refreshAssignments();
14531
14627
  }
14532
- setSubmitting(false);
14533
14628
  nav.replace({ name: "REPORT_SUCCESS" });
14534
14629
  } catch (err) {
14535
14630
  console.error("BugBear: Report submission error", err);
14536
14631
  setError(err instanceof Error ? err.message : "An unexpected error occurred. Please try again.");
14537
14632
  setSubmitting(false);
14633
+ submittingRef.current = false;
14538
14634
  }
14539
14635
  };
14540
14636
  return /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, null, isRetestFailure ? /* @__PURE__ */ import_react10.default.createElement(import_react10.default.Fragment, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.retestBanner }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestIcon }, "\u{1F504}"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, null, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestTitle }, "Bug Still Present"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: styles7.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.section }, /* @__PURE__ */ import_react10.default.createElement(import_react_native9.Text, { style: shared.label }, "Severity"), /* @__PURE__ */ import_react10.default.createElement(import_react_native9.View, { style: styles7.severityRow }, [
package/dist/index.mjs CHANGED
@@ -11360,7 +11360,7 @@ function shouldShowDeprecationWarning() {
11360
11360
  }
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
- // node_modules/@bbearai/core/dist/index.mjs
11363
+ // ../core/dist/index.mjs
11364
11364
  var MAX_CONSOLE_LOGS = 50;
11365
11365
  var MAX_NETWORK_REQUESTS = 20;
11366
11366
  var MAX_NAVIGATION_HISTORY = 20;
@@ -11620,6 +11620,7 @@ var HOSTED_BUGBEAR_ANON_KEY = getEnvVar("BUGBEAR_ANON_KEY") || getEnvVar("NEXT_P
11620
11620
  var BugBearClient = class {
11621
11621
  constructor(config) {
11622
11622
  this.navigationHistory = [];
11623
+ this.reportSubmitInFlight = false;
11623
11624
  this.config = config;
11624
11625
  this.supabase = createClient(
11625
11626
  config.supabaseUrl || DEFAULT_SUPABASE_URL,
@@ -11680,6 +11681,10 @@ var BugBearClient = class {
11680
11681
  * Submit a report
11681
11682
  */
11682
11683
  async submitReport(report) {
11684
+ if (this.reportSubmitInFlight) {
11685
+ return { success: false, error: "A report is already being submitted" };
11686
+ }
11687
+ this.reportSubmitInFlight = true;
11683
11688
  try {
11684
11689
  const validationError = this.validateReport(report);
11685
11690
  if (validationError) {
@@ -11731,6 +11736,8 @@ var BugBearClient = class {
11731
11736
  } catch (err) {
11732
11737
  const message = err instanceof Error ? err.message : "Unknown error";
11733
11738
  return { success: false, error: message };
11739
+ } finally {
11740
+ this.reportSubmitInFlight = false;
11734
11741
  }
11735
11742
  }
11736
11743
  /**
@@ -11771,6 +11778,14 @@ var BugBearClient = class {
11771
11778
  name,
11772
11779
  description,
11773
11780
  sort_order
11781
+ ),
11782
+ role:project_roles(
11783
+ id,
11784
+ name,
11785
+ slug,
11786
+ color,
11787
+ description,
11788
+ login_hint
11774
11789
  )
11775
11790
  )
11776
11791
  `).eq("project_id", this.config.projectId).eq("tester_id", testerInfo.id).in("status", ["pending", "in_progress"]).order("created_at", { ascending: true }).limit(100);
@@ -11808,6 +11823,14 @@ var BugBearClient = class {
11808
11823
  name: item.test_case.group.name,
11809
11824
  description: item.test_case.group.description,
11810
11825
  sortOrder: item.test_case.group.sort_order
11826
+ } : void 0,
11827
+ role: item.test_case.role ? {
11828
+ id: item.test_case.role.id,
11829
+ name: item.test_case.role.name,
11830
+ slug: item.test_case.role.slug,
11831
+ color: item.test_case.role.color,
11832
+ description: item.test_case.role.description,
11833
+ loginHint: item.test_case.role.login_hint
11811
11834
  } : void 0
11812
11835
  }
11813
11836
  }));
@@ -12191,6 +12214,72 @@ var BugBearClient = class {
12191
12214
  return null;
12192
12215
  }
12193
12216
  }
12217
+ /**
12218
+ * Get issue counts for the tester (Open, Done, Reopened)
12219
+ * Used by the widget HomeScreen cards
12220
+ */
12221
+ async getIssueCounts() {
12222
+ try {
12223
+ const testerInfo = await this.getTesterInfo();
12224
+ if (!testerInfo) return { open: 0, done: 0, reopened: 0 };
12225
+ const { data, error } = await this.supabase.rpc("get_tester_issue_counts", {
12226
+ p_project_id: this.config.projectId,
12227
+ p_tester_id: testerInfo.id
12228
+ });
12229
+ if (error) {
12230
+ console.error("BugBear: Failed to fetch issue counts", formatPgError(error));
12231
+ return { open: 0, done: 0, reopened: 0 };
12232
+ }
12233
+ return {
12234
+ open: data?.open ?? 0,
12235
+ done: data?.done ?? 0,
12236
+ reopened: data?.reopened ?? 0
12237
+ };
12238
+ } catch (err) {
12239
+ console.error("BugBear: Error fetching issue counts", err);
12240
+ return { open: 0, done: 0, reopened: 0 };
12241
+ }
12242
+ }
12243
+ /**
12244
+ * Get issues for the tester by category.
12245
+ * Returns enriched data: done issues include verification proof,
12246
+ * reopened issues include original bug context.
12247
+ */
12248
+ async getIssues(category) {
12249
+ try {
12250
+ const testerInfo = await this.getTesterInfo();
12251
+ if (!testerInfo) return [];
12252
+ const { data, error } = await this.supabase.rpc("get_tester_issues", {
12253
+ p_project_id: this.config.projectId,
12254
+ p_tester_id: testerInfo.id,
12255
+ p_category: category
12256
+ });
12257
+ if (error) {
12258
+ console.error("BugBear: Failed to fetch issues", formatPgError(error));
12259
+ return [];
12260
+ }
12261
+ return (data || []).map((row) => ({
12262
+ id: row.id,
12263
+ title: row.title || "Untitled",
12264
+ description: row.description,
12265
+ reportType: row.report_type,
12266
+ severity: row.severity || null,
12267
+ status: row.status,
12268
+ screenshotUrls: row.screenshot_urls || [],
12269
+ route: row.app_context?.currentRoute || void 0,
12270
+ reporterName: row.reporter_name || void 0,
12271
+ createdAt: row.created_at,
12272
+ updatedAt: row.updated_at,
12273
+ verifiedByName: row.verified_by_name || void 0,
12274
+ verifiedAt: row.verified_at || void 0,
12275
+ originalBugId: row.original_bug_id || void 0,
12276
+ originalBugTitle: row.original_bug_title || void 0
12277
+ }));
12278
+ } catch (err) {
12279
+ console.error("BugBear: Error fetching issues", err);
12280
+ return [];
12281
+ }
12282
+ }
12194
12283
  /**
12195
12284
  * Basic email format validation (defense in depth)
12196
12285
  */
@@ -12223,8 +12312,8 @@ var BugBearClient = class {
12223
12312
  return "Maximum 10 screenshots allowed";
12224
12313
  }
12225
12314
  for (const url of report.screenshots) {
12226
- if (typeof url !== "string" || url.length > 2e3) {
12227
- return "Invalid screenshot URL";
12315
+ if (typeof url !== "string" || url.length > 2e3 || !/^https?:\/\//i.test(url)) {
12316
+ return "Invalid screenshot URL (must be an HTTP/HTTPS URL)";
12228
12317
  }
12229
12318
  }
12230
12319
  }
@@ -12613,7 +12702,10 @@ var BugBearClient = class {
12613
12702
  content
12614
12703
  };
12615
12704
  if (attachments && attachments.length > 0) {
12616
- insertData.attachments = attachments;
12705
+ const safeAttachments = attachments.filter((a) => /^https?:\/\//i.test(a.url));
12706
+ if (safeAttachments.length > 0) {
12707
+ insertData.attachments = safeAttachments;
12708
+ }
12617
12709
  }
12618
12710
  const { error } = await this.supabase.from("discussion_messages").insert(insertData);
12619
12711
  if (error) {
@@ -13213,7 +13305,7 @@ function BugBearProvider({ config, children, appVersion, enabled = true }) {
13213
13305
  }
13214
13306
 
13215
13307
  // src/BugBearButton.tsx
13216
- import React14, { useState as useState10, useRef as useRef2 } from "react";
13308
+ import React14, { useState as useState10, useRef as useRef3 } from "react";
13217
13309
  import {
13218
13310
  View as View13,
13219
13311
  Text as Text13,
@@ -14468,7 +14560,7 @@ var styles6 = StyleSheet7.create({
14468
14560
  });
14469
14561
 
14470
14562
  // src/widget/screens/ReportScreen.tsx
14471
- import React8, { useState as useState6 } from "react";
14563
+ import React8, { useState as useState6, useRef as useRef2 } from "react";
14472
14564
  import { View as View7, Text as Text7, TouchableOpacity as TouchableOpacity7, TextInput as TextInput3, StyleSheet as StyleSheet8 } from "react-native";
14473
14565
  function ReportScreen({ nav, prefill }) {
14474
14566
  const { client, getDeviceInfo, uploadImage, refreshAssignments } = useBugBear();
@@ -14478,11 +14570,14 @@ function ReportScreen({ nav, prefill }) {
14478
14570
  const [affectedScreen, setAffectedScreen] = useState6("");
14479
14571
  const [submitting, setSubmitting] = useState6(false);
14480
14572
  const [error, setError] = useState6(null);
14573
+ const submittingRef = useRef2(false);
14481
14574
  const images = useImageAttachments(uploadImage, 5, "screenshots");
14482
14575
  const isRetestFailure = prefill?.type === "test_fail";
14483
14576
  const isBugType = reportType === "bug" || reportType === "test_fail";
14484
14577
  const handleSubmit = async () => {
14485
14578
  if (!client || !description.trim()) return;
14579
+ if (submittingRef.current) return;
14580
+ submittingRef.current = true;
14486
14581
  setSubmitting(true);
14487
14582
  setError(null);
14488
14583
  try {
@@ -14506,17 +14601,18 @@ function ReportScreen({ nav, prefill }) {
14506
14601
  console.error("BugBear: Report submission failed", result.error);
14507
14602
  setError(result.error || "Failed to submit report. Please try again.");
14508
14603
  setSubmitting(false);
14604
+ submittingRef.current = false;
14509
14605
  return;
14510
14606
  }
14511
14607
  if (prefill?.assignmentId) {
14512
14608
  await refreshAssignments();
14513
14609
  }
14514
- setSubmitting(false);
14515
14610
  nav.replace({ name: "REPORT_SUCCESS" });
14516
14611
  } catch (err) {
14517
14612
  console.error("BugBear: Report submission error", err);
14518
14613
  setError(err instanceof Error ? err.message : "An unexpected error occurred. Please try again.");
14519
14614
  setSubmitting(false);
14615
+ submittingRef.current = false;
14520
14616
  }
14521
14617
  };
14522
14618
  return /* @__PURE__ */ React8.createElement(View7, null, isRetestFailure ? /* @__PURE__ */ React8.createElement(React8.Fragment, null, /* @__PURE__ */ React8.createElement(View7, { style: styles7.retestBanner }, /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestIcon }, "\u{1F504}"), /* @__PURE__ */ React8.createElement(View7, null, /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestTitle }, "Bug Still Present"), /* @__PURE__ */ React8.createElement(Text7, { style: styles7.retestSubtitle }, "The fix did not resolve this issue"))), /* @__PURE__ */ React8.createElement(View7, { style: styles7.section }, /* @__PURE__ */ React8.createElement(Text7, { style: shared.label }, "Severity"), /* @__PURE__ */ React8.createElement(View7, { style: styles7.severityRow }, [
@@ -15046,9 +15142,9 @@ function BugBearButton({
15046
15142
  return { x, y };
15047
15143
  };
15048
15144
  const initialPos = getInitialPosition();
15049
- const pan = useRef2(new Animated.ValueXY(initialPos)).current;
15050
- const isDragging = useRef2(false);
15051
- const panResponder = useRef2(
15145
+ const pan = useRef3(new Animated.ValueXY(initialPos)).current;
15146
+ const isDragging = useRef3(false);
15147
+ const panResponder = useRef3(
15052
15148
  PanResponder.create({
15053
15149
  onStartShouldSetPanResponder: () => draggable,
15054
15150
  onMoveShouldSetPanResponder: (_, gs) => draggable && (Math.abs(gs.dx) > 5 || Math.abs(gs.dy) > 5),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bbearai/react-native",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "BugBear React Native components for mobile apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",