Table Of Contents

Into

Sentry is a powerful tool for monitoring and debugging errors in your applications. Integrating it with a Chrome extension can help you capture and diagnose issues effectively. This guide walks you through setting up Sentry for a Chrome extension with a React-based frontend.

1. Install Sentry

To start, install the Sentry browser package:

npm install --save @sentry/browser

2. Disable Filters in Project Settings

If Sentry doesn’t capture errors from your Chrome extension, it might be due to inbound filters. To ensure all errors are recorded:

  1. Navigate to Projects -> MY_PROJECT -> Project Settings -> Inbound Filters in your Sentry dashboard.
  2. Disable any active filters that might block errors from Chrome extensions.

3. Capture Errors in React

To capture React errors without initializing Sentry.init() globally, you can manually configure a Sentry client and use it with an ErrorBoundary component. This approach prevents global state conflicts in shared environments, such as browser extensions.

3.1 Configure the Sentry Client

Create a custom Sentry client:

import {
  BrowserClient,
  defaultStackParser,
  getDefaultIntegrations,
  makeFetchTransport,
  Scope,
  type EventHint,
} from "@sentry/browser";
import type { ErrorInfo } from "react";

// Configure client without global state
const integrations = getDefaultIntegrations({}).filter((integration) => {
  return !["BrowserApiErrors", "Breadcrumbs", "GlobalHandlers"].includes(
    integration.name,
  );
});

const client = new BrowserClient({
  dsn: "YOUR_DSN_URL",
  transport: makeFetchTransport,
  stackParser: defaultStackParser,
  integrations: integrations,
  enabled: import.meta.env.PROD, // Enable only in production
});

const scope = new Scope();
scope.setClient(client);

// Function to capture exceptions
export const captureException = (error: Error, errorInfo: ErrorInfo) => {
  if (import.meta.env.DEV) {
    console.group("Error captured in development:");
    console.error("Error:", error);
    console.info("Component Stack:", errorInfo.componentStack);
    console.groupEnd();
    return;
  }

  scope.addEventProcessor((event) => {
    return { ...event, extra: { ...event.extra, errorInfo } };
  });

  const hint: EventHint = {
    data: { react: errorInfo },
  };

  client.captureException(error, hint);
};

3.2 Create an ErrorBoundary Component

Use an ErrorBoundary to handle errors gracefully and send them to Sentry:

import React from "react";
import { captureException } from "@/utils/sentryCapturer";

interface Props {
  children: React.ReactNode;
  fallback?: React.ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

export class ErrorBoundary extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    captureException(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || <ErrorFallback error={this.state.error} />;
    }
    return this.props.children;
  }
}

export const ErrorFallback = ({ error }: { error?: Error }) => (
  <div className="error-container">
    <h2>Something went wrong</h2>
    <p>An error occurred while loading the component. We're working on fixing it.</p>
    {error && <pre>{error.message}</pre>}
    <button onClick={() => window.location.reload()}>Refresh Page</button>
  </div>
);

3.3 Use ErrorBoundary in Your Application

Wrap components prone to errors with the ErrorBoundary:

import React from "react";
import { captureException } from "@/utils/sentryCapturer.ts";

interface Props {
  children: React.ReactNode;
  fallback?: React.ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

export class ErrorBoundary extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    captureException(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback || <ErrorFallback error={this.state.error} />;
    }
    return this.props.children;
  }
}

export const ErrorFallback = ({ error }: { error?: Error }) => (
  <div className="min-h-[200px] p-6 rounded-lg bg-gradient-to-br from-red-50 to-red-100 border border-red-200 shadow-sm">
    <div className="flex flex-col items-center text-center space-y-4">
      <div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center">
        <svg className="w-8 h-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
        </svg>
      </div>

      <h2 className="text-xl font-semibold text-red-700">
        Something went wrong
      </h2>

      <p className="text-xs text-red-600 max-w-md">
        An error occurred while loading the component.
      </p>
      <p className="text-xs text-red-600 max-w-md">
        We&apos;re already working on fixing it.
      </p>

      {error && (
        <div className="mt-4 p-3 bg-white/50 rounded-md backdrop-blur-sm w-full">
          <p className="text-xs font-mono text-red-800 break-all">
            {error.message}
          </p>
        </div>
      )}

      <button
        onClick={() => window.location.reload()}
        className="mt-4 px-4 py-2 bg-red-500 hover:bg-red-600 active:bg-red-700 text-white rounded-lg
        transition-all duration-200 transform hover:scale-105 active:scale-100
        shadow-md hover:shadow-lg active:shadow-sm"
      >
        Refresh Page
      </button>
    </div>
  </div>
);

// Test component that triggers an error
///
// Usage:
//
//     <ErrorBoundary fallback={<ErrorFallback />}>
//       <BuggyCounter />
//     </ErrorBoundary>
//
export class BuggyCounter extends React.Component<Record<string, unknown>, { counter: number }> {
  state = { counter: 0 };

  handleClick = () => {
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
  };

  render() {
    if (this.state.counter === 5) {
      throw new Error('Error simulation!');
    }
    return (
      <button
        onClick={this.handleClick}
        className="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600
        transition-all duration-200 transform hover:scale-105 active:scale-100
        shadow-md hover:shadow-lg mb-4"
      >
        Clicks: {this.state.counter}
      </button>
    );
  }
}

Test ErrorBoundary out:

import React from "react";
import ReactDOM from "react-dom";
import { ErrorBoundary, ErrorFallback, BuggyCounter } from "./ErrorBoundary";

ReactDOM.render(
  <React.StrictMode>
    <ErrorBoundary fallback={<ErrorFallback />}>
      <BuggyCounter />
    </ErrorBoundary>
  </React.StrictMode>,
  document.getElementById("root")
);

4. Manual Error Reporting

To capture errors programmatically, use the captureException function:

import { captureException } from "./sentryClient";

function ExampleComponent() {
  const handleClick = () => {
    try {
      throw new Error("Example error!");
    } catch (error) {
      captureException(error);
    }
  };

  return <button onClick={handleClick}>Simulate Error</button>;
}

Benefits of This Approach

  • No Global State Pollution: Ensures Sentry doesn’t interfere with other parts of your app.
  • Fine-Grained Control: Precisely manage what errors and contexts are sent to Sentry.
  • Browser Extension Compatibility: Works seamlessly in shared environments like Chrome extensions.

By following this guide, you’ll have a robust error monitoring solution for your Chrome extension that integrates seamlessly with React and Sentry.