quick-bug-reporter-react 1.0.5 → 1.1.1

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.cjs CHANGED
@@ -555,6 +555,31 @@ var ScreenshotCapturer = class {
555
555
 
556
556
  // src/core/types.ts
557
557
  var DEFAULT_MAX_RECORDING_MS = 2 * 60 * 1e3;
558
+ function formatConsoleLogs(logs) {
559
+ if (logs.length === 0) {
560
+ return "No console output captured.";
561
+ }
562
+ return logs.map((entry) => {
563
+ const tag = entry.level.toUpperCase().padEnd(5);
564
+ const args = entry.args.join(" ");
565
+ return `[${entry.timestamp}] ${tag} ${args}`;
566
+ }).join("\n");
567
+ }
568
+ function formatJsErrors(errors) {
569
+ if (errors.length === 0) {
570
+ return "No JavaScript errors captured.";
571
+ }
572
+ return errors.map((entry) => {
573
+ const lines = [`[${entry.timestamp}] ${entry.type}: ${entry.message}`];
574
+ if (entry.source) {
575
+ lines.push(` at ${entry.source}${entry.lineno ? `:${entry.lineno}` : ""}${entry.colno ? `:${entry.colno}` : ""}`);
576
+ }
577
+ if (entry.stack) {
578
+ lines.push(entry.stack.split("\n").map((l) => ` ${l}`).join("\n"));
579
+ }
580
+ return lines.join("\n");
581
+ }).join("\n\n");
582
+ }
558
583
  function formatNetworkLogs(logs) {
559
584
  if (logs.length === 0) {
560
585
  return "No network requests captured.";
@@ -859,6 +884,8 @@ var BugReporter = class {
859
884
  videoBlob: artifacts.videoBlob,
860
885
  screenshotBlob: options.screenshotBlob ?? artifacts.screenshotBlob,
861
886
  networkLogs: artifacts.networkLogs,
887
+ consoleLogs: options.consoleLogs ?? [],
888
+ jsErrors: options.jsErrors ?? [],
862
889
  captureMode: artifacts.captureMode,
863
890
  pageUrl: typeof window !== "undefined" ? window.location.href : "",
864
891
  userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "",
@@ -898,6 +925,124 @@ var BugReporter = class {
898
925
  }
899
926
  };
900
927
 
928
+ // src/core/ConsoleCapture.ts
929
+ var MAX_CONSOLE_ENTRIES = 200;
930
+ var MAX_ERROR_ENTRIES = 50;
931
+ var MAX_ARG_LENGTH = 1e3;
932
+ function serializeArg(arg) {
933
+ if (typeof arg === "string") {
934
+ return arg.length > MAX_ARG_LENGTH ? arg.slice(0, MAX_ARG_LENGTH) + "\u2026" : arg;
935
+ }
936
+ try {
937
+ const json = JSON.stringify(arg);
938
+ return json.length > MAX_ARG_LENGTH ? json.slice(0, MAX_ARG_LENGTH) + "\u2026" : json;
939
+ } catch {
940
+ return String(arg);
941
+ }
942
+ }
943
+ var ConsoleCapture = class {
944
+ constructor() {
945
+ this.consoleLogs = [];
946
+ this.errors = [];
947
+ this.originals = {};
948
+ this.errorHandler = null;
949
+ this.rejectionHandler = null;
950
+ this.active = false;
951
+ }
952
+ start() {
953
+ if (this.active || typeof window === "undefined") {
954
+ return;
955
+ }
956
+ this.active = true;
957
+ this.consoleLogs = [];
958
+ this.errors = [];
959
+ const levels = ["log", "info", "warn", "error"];
960
+ for (const level of levels) {
961
+ const original = console[level];
962
+ this.originals[level] = original;
963
+ const capture = this;
964
+ console[level] = (...args) => {
965
+ try {
966
+ capture.consoleLogs.push({
967
+ level,
968
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
969
+ args: args.map(serializeArg)
970
+ });
971
+ if (capture.consoleLogs.length > MAX_CONSOLE_ENTRIES) {
972
+ capture.consoleLogs.shift();
973
+ }
974
+ } catch {
975
+ }
976
+ original.apply(console, args);
977
+ };
978
+ }
979
+ this.errorHandler = (event) => {
980
+ try {
981
+ this.errors.push({
982
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
983
+ message: event.message || "Unknown error",
984
+ source: event.filename || void 0,
985
+ lineno: event.lineno || void 0,
986
+ colno: event.colno || void 0,
987
+ stack: event.error?.stack || void 0,
988
+ type: "error"
989
+ });
990
+ if (this.errors.length > MAX_ERROR_ENTRIES) {
991
+ this.errors.shift();
992
+ }
993
+ } catch {
994
+ }
995
+ };
996
+ window.addEventListener("error", this.errorHandler);
997
+ this.rejectionHandler = (event) => {
998
+ try {
999
+ const reason = event.reason;
1000
+ this.errors.push({
1001
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1002
+ message: reason instanceof Error ? reason.message : String(reason),
1003
+ stack: reason instanceof Error ? reason.stack || void 0 : void 0,
1004
+ type: "unhandledrejection"
1005
+ });
1006
+ if (this.errors.length > MAX_ERROR_ENTRIES) {
1007
+ this.errors.shift();
1008
+ }
1009
+ } catch {
1010
+ }
1011
+ };
1012
+ window.addEventListener("unhandledrejection", this.rejectionHandler);
1013
+ }
1014
+ snapshot() {
1015
+ return {
1016
+ consoleLogs: [...this.consoleLogs],
1017
+ jsErrors: [...this.errors]
1018
+ };
1019
+ }
1020
+ stop() {
1021
+ if (!this.active) {
1022
+ return;
1023
+ }
1024
+ for (const [level, original] of Object.entries(this.originals)) {
1025
+ if (original) {
1026
+ console[level] = original;
1027
+ }
1028
+ }
1029
+ this.originals = {};
1030
+ if (this.errorHandler) {
1031
+ window.removeEventListener("error", this.errorHandler);
1032
+ this.errorHandler = null;
1033
+ }
1034
+ if (this.rejectionHandler) {
1035
+ window.removeEventListener("unhandledrejection", this.rejectionHandler);
1036
+ this.rejectionHandler = null;
1037
+ }
1038
+ this.active = false;
1039
+ }
1040
+ clear() {
1041
+ this.consoleLogs = [];
1042
+ this.errors = [];
1043
+ }
1044
+ };
1045
+
901
1046
  // src/integrations/linear.ts
902
1047
  var DEFAULT_GRAPHQL_ENDPOINT = "https://api.linear.app/graphql";
903
1048
  var noop = () => {
@@ -979,12 +1124,22 @@ var LinearIntegration = class {
979
1124
  const description = buildCleanDescription(payload, { screenshotUrl, recordingUrl });
980
1125
  const issue = await this.createIssue(payload.title, description);
981
1126
  progress("Attaching logs\u2026");
1127
+ const comments = [];
982
1128
  const logsComment = "### Network Logs\n```text\n" + formatNetworkLogs(payload.networkLogs) + "\n```";
1129
+ comments.push(this.addComment(issue.id, logsComment));
1130
+ if (payload.jsErrors.length > 0 || payload.consoleLogs.length > 0) {
1131
+ const parts = [];
1132
+ if (payload.jsErrors.length > 0) {
1133
+ parts.push("### JavaScript Errors\n```text\n" + formatJsErrors(payload.jsErrors) + "\n```");
1134
+ }
1135
+ if (payload.consoleLogs.length > 0) {
1136
+ parts.push("### Console Output\n```text\n" + formatConsoleLogs(payload.consoleLogs) + "\n```");
1137
+ }
1138
+ comments.push(this.addComment(issue.id, parts.join("\n\n")));
1139
+ }
983
1140
  const metadataComment = "### Client Metadata\n```json\n" + JSON.stringify(payload.metadata, null, 2) + "\n```";
984
- await Promise.all([
985
- this.addComment(issue.id, logsComment),
986
- this.addComment(issue.id, metadataComment)
987
- ]);
1141
+ comments.push(this.addComment(issue.id, metadataComment));
1142
+ await Promise.all(comments);
988
1143
  progress("Done!");
989
1144
  return {
990
1145
  provider: this.provider,
@@ -1317,6 +1472,17 @@ var JiraIntegration = class {
1317
1472
  }
1318
1473
  const logsBlob = new Blob([formatNetworkLogs(payload.networkLogs)], { type: "text/plain" });
1319
1474
  uploads.push(this.uploadAttachment(issue.key, logsBlob, "network-logs.txt", "text/plain"));
1475
+ if (payload.consoleLogs.length > 0 || payload.jsErrors.length > 0) {
1476
+ const consoleParts = [];
1477
+ if (payload.jsErrors.length > 0) {
1478
+ consoleParts.push("=== JavaScript Errors ===\n" + formatJsErrors(payload.jsErrors));
1479
+ }
1480
+ if (payload.consoleLogs.length > 0) {
1481
+ consoleParts.push("=== Console Output ===\n" + formatConsoleLogs(payload.consoleLogs));
1482
+ }
1483
+ const consoleBlob = new Blob([consoleParts.join("\n\n")], { type: "text/plain" });
1484
+ uploads.push(this.uploadAttachment(issue.key, consoleBlob, "console-logs.txt", "text/plain"));
1485
+ }
1320
1486
  const metadataBlob = new Blob([JSON.stringify(payload.metadata, null, 2)], { type: "application/json" });
1321
1487
  uploads.push(this.uploadAttachment(issue.key, metadataBlob, "client-metadata.json", "application/json"));
1322
1488
  await Promise.all(uploads);
@@ -1617,6 +1783,16 @@ function BugReporterProvider({
1617
1783
  imageHeight: 0
1618
1784
  });
1619
1785
  const reporterRef = react.useRef(null);
1786
+ const consoleCaptureRef = react.useRef(null);
1787
+ react.useEffect(() => {
1788
+ const capture = new ConsoleCapture();
1789
+ capture.start();
1790
+ consoleCaptureRef.current = capture;
1791
+ return () => {
1792
+ capture.stop();
1793
+ consoleCaptureRef.current = null;
1794
+ };
1795
+ }, []);
1620
1796
  const availableProviders = react.useMemo(() => {
1621
1797
  return ["linear", "jira"].filter((provider) => Boolean(integrations[provider]));
1622
1798
  }, [integrations]);
@@ -1740,6 +1916,18 @@ function BugReporterProvider({
1740
1916
  window.clearInterval(interval);
1741
1917
  };
1742
1918
  }, [isRecording]);
1919
+ react.useEffect(() => {
1920
+ if (!isRecording) {
1921
+ return;
1922
+ }
1923
+ const onBeforeUnload = (event) => {
1924
+ event.preventDefault();
1925
+ };
1926
+ window.addEventListener("beforeunload", onBeforeUnload);
1927
+ return () => {
1928
+ window.removeEventListener("beforeunload", onBeforeUnload);
1929
+ };
1930
+ }, [isRecording]);
1743
1931
  react.useEffect(() => {
1744
1932
  return () => {
1745
1933
  setScreenshotPreviewUrl((current) => {
@@ -1963,10 +2151,16 @@ function BugReporterProvider({
1963
2151
  highlights: screenshotAnnotation.highlights
1964
2152
  } : void 0
1965
2153
  };
2154
+ const { consoleLogs, jsErrors } = consoleCaptureRef.current?.snapshot() ?? {
2155
+ consoleLogs: [],
2156
+ jsErrors: []
2157
+ };
1966
2158
  try {
1967
2159
  const result = await reporter.submit(title, description, {
1968
2160
  screenshotBlob: screenshotBlobForSubmit,
1969
2161
  metadata,
2162
+ consoleLogs,
2163
+ jsErrors,
1970
2164
  onProgress: setSubmissionProgress
1971
2165
  });
1972
2166
  setSuccess(`Submitted to ${getProviderLabel(result.provider)} (${result.issueKey}).`);
@@ -2189,7 +2383,8 @@ function FloatingBugButton() {
2189
2383
  "div",
2190
2384
  {
2191
2385
  ref: containerRef,
2192
- className: "fixed bottom-4 right-4 z-[1100] flex flex-col items-end",
2386
+ style: { position: "fixed", bottom: "1rem", right: "1rem", zIndex: 1100 },
2387
+ className: "flex flex-col items-end",
2193
2388
  "data-bug-reporter-ui": "true",
2194
2389
  children: isRecording ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
2195
2390
  /* @__PURE__ */ jsxRuntime.jsxs(Button, { className: "h-11 gap-2 rounded-full px-4 shadow-lg shadow-black/20", type: "button", variant: "destructive", onClick: () => void handleStopRecording(), children: [
@@ -2312,6 +2507,7 @@ function DialogOverlay({
2312
2507
  radixUi.Dialog.Overlay,
2313
2508
  {
2314
2509
  "data-slot": "dialog-overlay",
2510
+ "data-bug-reporter-ui": "true",
2315
2511
  className: cn("data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/60 duration-100 supports-backdrop-filter:backdrop-blur-sm fixed inset-0 isolate z-50", className),
2316
2512
  ...props
2317
2513
  }
@@ -2329,8 +2525,16 @@ function DialogContent({
2329
2525
  radixUi.Dialog.Content,
2330
2526
  {
2331
2527
  "data-slot": "dialog-content",
2528
+ "data-bug-reporter-ui": "true",
2529
+ style: {
2530
+ position: "fixed",
2531
+ top: "50%",
2532
+ left: "50%",
2533
+ transform: "translate(-50%, -50%)",
2534
+ translate: "none"
2535
+ },
2332
2536
  className: cn(
2333
- "bg-white text-gray-900 data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-gray-900/5 grid max-w-[calc(100%-2rem)] gap-6 rounded-4xl p-6 text-sm ring-1 shadow-xl duration-100 sm:max-w-md fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none",
2537
+ "bg-white text-gray-900 data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-gray-900/5 grid max-w-[calc(100%-2rem)] gap-6 rounded-4xl p-6 text-sm ring-1 shadow-xl duration-100 sm:max-w-md z-50 w-full outline-none",
2334
2538
  className
2335
2539
  ),
2336
2540
  ...props,
@@ -3030,6 +3234,7 @@ exports.BugReporter = BugReporter;
3030
3234
  exports.BugReporterModal = BugReporterModal;
3031
3235
  exports.BugReporterProvider = BugReporterProvider;
3032
3236
  exports.BugSession = BugSession;
3237
+ exports.ConsoleCapture = ConsoleCapture;
3033
3238
  exports.DEFAULT_MAX_RECORDING_MS = DEFAULT_MAX_RECORDING_MS;
3034
3239
  exports.FloatingBugButton = FloatingBugButton;
3035
3240
  exports.JiraIntegration = JiraIntegration;
@@ -3038,6 +3243,8 @@ exports.NetworkLogger = NetworkLogger;
3038
3243
  exports.ScreenRecorder = ScreenRecorder;
3039
3244
  exports.ScreenshotCapturer = ScreenshotCapturer;
3040
3245
  exports.collectClientEnvironmentMetadata = collectClientEnvironmentMetadata;
3246
+ exports.formatConsoleLogs = formatConsoleLogs;
3247
+ exports.formatJsErrors = formatJsErrors;
3041
3248
  exports.formatNetworkLogs = formatNetworkLogs;
3042
3249
  exports.toErrorMessage = toErrorMessage;
3043
3250
  exports.useBugReporter = useBugReporter;