ErrorBoundary.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import React, { Component } from "react"
  2. import { telemetryClient } from "@src/utils/TelemetryClient"
  3. import { withTranslation, WithTranslation } from "react-i18next"
  4. import { enhanceErrorWithSourceMaps } from "@src/utils/sourceMapUtils"
  5. type ErrorProps = {
  6. children: React.ReactNode
  7. } & WithTranslation
  8. type ErrorState = {
  9. error?: string
  10. componentStack?: string | null
  11. timestamp?: number
  12. }
  13. class ErrorBoundary extends Component<ErrorProps, ErrorState> {
  14. constructor(props: ErrorProps) {
  15. super(props)
  16. this.state = {}
  17. }
  18. static getDerivedStateFromError(error: unknown) {
  19. let errorMessage = ""
  20. if (error instanceof Error) {
  21. errorMessage = error.stack ?? error.message
  22. } else {
  23. errorMessage = `${error}`
  24. }
  25. return {
  26. error: errorMessage,
  27. timestamp: Date.now(),
  28. }
  29. }
  30. async componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
  31. const componentStack = errorInfo.componentStack || ""
  32. const enhancedError = await enhanceErrorWithSourceMaps(error, componentStack)
  33. telemetryClient.capture("error_boundary_caught_error", {
  34. error: enhancedError.message,
  35. stack: enhancedError.sourceMappedStack || enhancedError.stack,
  36. componentStack: enhancedError.sourceMappedComponentStack || componentStack,
  37. timestamp: Date.now(),
  38. errorType: enhancedError.name,
  39. })
  40. this.setState({
  41. error: enhancedError.sourceMappedStack || enhancedError.stack,
  42. componentStack: enhancedError.sourceMappedComponentStack || componentStack,
  43. })
  44. }
  45. render() {
  46. const { t } = this.props
  47. if (!this.state.error) {
  48. return this.props.children
  49. }
  50. const errorDisplay = this.state.error
  51. const componentStackDisplay = this.state.componentStack
  52. const version = process.env.PKG_VERSION || "unknown"
  53. return (
  54. <div>
  55. <h2 className="text-lg font-bold mt-0 mb-2">
  56. {t("errorBoundary.title")} (v{version})
  57. </h2>
  58. <p className="mb-4">
  59. {t("errorBoundary.reportText")}{" "}
  60. <a href="https://github.com/RooCodeInc/Roo-Code/issues" target="_blank" rel="noreferrer">
  61. {t("errorBoundary.githubText")}
  62. </a>
  63. </p>
  64. <p className="mb-2">{t("errorBoundary.copyInstructions")}</p>
  65. <div className="mb-4">
  66. <h3 className="text-md font-bold mb-1">{t("errorBoundary.errorStack")}</h3>
  67. <pre className="p-2 border rounded text-sm overflow-auto">{errorDisplay}</pre>
  68. </div>
  69. {componentStackDisplay && (
  70. <div>
  71. <h3 className="text-md font-bold mb-1">{t("errorBoundary.componentStack")}</h3>
  72. <pre className="p-2 border rounded text-sm overflow-auto">{componentStackDisplay}</pre>
  73. </div>
  74. )}
  75. </div>
  76. )
  77. }
  78. }
  79. export default withTranslation("common")(ErrorBoundary)