PorterErrorBoundary.tsx 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. import React from "react";
  2. import * as Sentry from "@sentry/react";
  3. import { type Context, type Primitive } from "@sentry/types";
  4. import { ErrorBoundary } from "react-error-boundary";
  5. import StackTrace from "stacktrace-js";
  6. import UnexpectedErrorPage from "components/UnexpectedErrorPage";
  7. import { stackFramesToString } from "./stack_trace_utils";
  8. export type PorterErrorBoundaryProps<OnResetProps = {}> = {
  9. // Component or useful name to describe where the error boundary was setted
  10. errorBoundaryLocation: string;
  11. // Used in case the boundary shouldn't refresh but instead do other action
  12. onReset?: (props: OnResetProps) => unknown;
  13. // Add more tags to sentry errors
  14. tags?: Record<string, Primitive>;
  15. // Add more context for sentry errors
  16. context?: Record<string, Context>;
  17. };
  18. const PorterErrorBoundary: React.FC<PorterErrorBoundaryProps> = ({
  19. errorBoundaryLocation,
  20. onReset,
  21. children,
  22. tags,
  23. context,
  24. }) => {
  25. const handleError = (err: Error) => {
  26. StackTrace.fromError(err).then((stackframes) => {
  27. const stackFramesStringify = stackFramesToString(stackframes);
  28. // Preserve the old stack just in case
  29. const originalStack = err.stack;
  30. // Update the error stack with the StackTrace stack (this helps for minified environments)
  31. err.stack = stackFramesStringify;
  32. if (import.meta.env.ENABLE_SENTRY) {
  33. Sentry.captureException(err, (scope) => {
  34. scope.setTags({
  35. error_boundary_location: errorBoundaryLocation,
  36. error_message: err?.message,
  37. ...(tags || {}),
  38. });
  39. scope.setContext("Original stack", {
  40. originalStack,
  41. });
  42. if (typeof context === "object") {
  43. Object.entries(context).forEach(([contextName, contextContent]) => {
  44. scope.setContext(contextName, contextContent);
  45. });
  46. }
  47. return scope;
  48. });
  49. }
  50. window?.analytics?.track("React Error", {
  51. location: errorBoundaryLocation,
  52. error: stackFramesStringify,
  53. componentStack: err.stack,
  54. url: window.location.toString(),
  55. });
  56. });
  57. };
  58. const handleOnReset = (props: unknown) => {
  59. typeof onReset === "function" ? onReset(props) : window.location.reload();
  60. };
  61. return (
  62. <ErrorBoundary
  63. onError={handleError}
  64. FallbackComponent={UnexpectedErrorPage}
  65. onReset={handleOnReset}
  66. >
  67. {children}
  68. </ErrorBoundary>
  69. );
  70. };
  71. export default PorterErrorBoundary;