Sentry ReactJS: how to set up a tunnel to bypass Ad Blocker?

1.4k Views Asked by At

I set up Sentry cloud in our React application but it's blocked by Ad Blockers (when turning the Ad blocker off, it works).

Is there someone who successfully set up a tunnel in a React application?

  1. I played around with CORS but it didn't work
  2. Playing around with the tunnel property in Sentry.init from the nextjs example in https://github.com/getsentry/examples/blob/master/tunneling/nextjs/pages/api/tunnel.js is throwing a /tunnel 404 (Not Found) console error in react app although I added a route to this path into my App which contains the handle function from nextjs example.
...
Sentry.init({
  dsn: 'https://[email protected]/mine',
  integrations: [new BrowserTracing()],
  environment,
  tunnel: '/tunnel',
  tracesSampleRate,
});
...

where I tried it directly via <Route path='/tunnel' component={(req, res) => handle(req, res)} /> and also by using a component <Route path='/tunnel' component={Tunnel} /> with

function Tunnel(props) {
  let location = useLocation();

  useEffect(() => {
    if(location.pathname === '/tunnel') {
      handle(props.req, props.res);
    }
  }, [location.pathname]);

  return null;
}

  1. I even tried Webpack Plugin
plugins: [
      new SentryWebpackPlugin({
        include: '.',
        ignore: ['node_modules'],
        org: 'my_org',
        project: 'app',
        authToken:
          'myToken',
      }),
    ],

but it also is being getting blocked

--- Update --- At least for local development and testing, it's possible to adjust the webpack config.

const bodyParser = require('body-parser')

const sentryHost = '@o<orgId>.ingest.sentry.io';

// Set knownProjectIds to an array with your Sentry project IDs which you
// want to accept through this proxy.

const knownProjectIds = ['12345'];
app.use(bodyParser.text());

app?.post('/tunnel', async (req, res) => {
try {
  const envelope = req.body;
  const pieces = envelope.split('\n');
  const header = JSON.parse(pieces[0]);
  // DSNs are of the form `https://<key>@o<orgId>.ingest.sentry.io/<projectId>`

  const { host, pathname } = new URL(header.dsn);
  // Remove leading slash
  const projectId = pathname.substring(1);

  if (host !== sentryHost) {
    throw new Error(`invalid host: ${host}`);
  }

  if (!knownProjectIds.includes(projectId)) {
     throw new Error(`invalid project id: $.        {projectId}`);
   }
   const sentryIngestURL = `https://${sentryHost}/api/${projectId}/envelope/`;
    const sentryResponse = await fetch(sentryIngestURL, {
              method: 'POST',
              body: envelope,
            });
            sentryResponse.headers.forEach(([key, value]) => res.setHeader(key, value));
            res.status(sentryResponse.status).send(sentryResponse.body);
          } catch (e) {
            captureException(e);
            return res.status(400).json({ status: 'invalid request' });
          }
          res.send("POST res sent from webpack dev server")
        })

but only for local testing. In production, I guess we would use a proxy.

1

There are 1 best solutions below

0
Asif vora On

Dealing with Ad-Blockers

When you are using sentry CDN, ad-blocking or script-blocking extensions may prevent sentry SDK from being fetched and initialized properly. Because of this, any call to the SDKs API will fail and may cause your application to behave unexpectedly.

Additionally, even when the SDK is downloaded and initialized correctly, Sentry endpoints that need to receive captured data may be blocked as well. This prevents any error reports, session health, or performance data from being delivered, making it effectively unavailable in sentry.io.

Furthermore, some browsers, like Brave, have built-in ad-blockers that may block requests sent to our endpoint. Even if users deactivate your domain from blocking, Brave might continue to block requests made by service workers.

Using the tunnel Option

A tunnel is an HTTP endpoint that acts as a proxy between Sentry and your application. Because you control this server, there is no risk of any requests sent to it being blocked. When the endpoint lives under the same origin (although it does not have to in order for the tunnel to work), the browser will not treat any requests to the endpoint as a third-party request. As a result, these requests will have different security measures applied which, by default, don't trigger ad-blockers. A quick summary of the flow can be found below.

enter image description here

You can setup a tunnel route using a backend node server or choose your preferred backend language.

NodeJS - server.js

import axios from 'axios';
import express from 'express';
import bodyParser from 'body-parser';

const app = express();
const port = 3005;

app.use(bodyParser.text({ type: ['text/*', '*/json'], limit: '50mb' }))

app.get('/', (req, res) => {
    res.send('Hello World!');
});

app.post('/tunnel', async (req, res) => {
    try {
        const envelope = req.body;

        const pieces = envelope.split('\n');

        const header = JSON.parse(pieces[0]);

        const { host, pathname, username } = new URL(header.dsn);

        const projectId = pathname.slice(1);

        const url = `https://${host}/api/${projectId}/envelope/?sentry_key=${username}`;

        const options = {
            'headers': {
                'Content-Type': 'application/x-sentry-envelope'
            }
        };

        const response = await axios.post(url, envelope, options);

        res.status(201).json({ message: "Success", data: response?.data })
    } catch (e) {
        const error = e?.response || e?.message;
        res.status(400).json({ message: 'invalid request', error: error });
    }
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
});

You can find the full NodeJS server code here

After setup, the backend server makes some changes on the front-end react app.

You need to setup a proxy on the frontend app. If you're using CRA you just follow these stpes.

For more details https://create-react-app.dev/docs/proxying-api-requests-in-development/

First, install http-proxy-middleware using npm or Yarn:

$ npm install http-proxy-middleware --save
$ # or
$ yarn add http-proxy-middleware

Next, create src/setupProxy.js and place the following contents in it:

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  // ...
};

You can now register proxies as you wish! Here's an example using the above http-proxy-middleware:

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/tunnel',
    createProxyMiddleware({
      target: 'http://localhost:5000', // Update based your server URL
      changeOrigin: true,
    })
  );
};

After done with the proxy setup make some changes where you initialize the sentry configuration

Sentry.init({
        dsn:"your sentry DSN URL", // Example: https://[email protected]/mine

        // https://docs.sentry.io/platforms/javascript/troubleshooting/
        tunnel: '/tunnel',

        environment: 'production',

        integrations: [
            // https://docs.sentry.io/platforms/javascript/guides/react/session-replay/configuration/?original_referrer=https%3A%2F%2Fwww.google.com%2F
            new Sentry.Replay({
                // necessary since Replays currently don't work with tunneling
                useCompression: false,
            }),

            // https://docs.sentry.io/platforms/javascript/guides/nextjs/performance/instrumentation/automatic-instrumentation/
            new Sentry.BrowserTracing(),

            // defaults to ['log', 'info', 'warn', 'error', 'debug', 'assert']
            new CaptureConsole({ levels: ['error', 'warn'] }),
        ],

        attachStacktrace: true,

        // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
        tracesSampleRate: 1.0,

        // Capture Replay for 10% of all sessions, plus for 100% of sessions with an error
        replaysSessionSampleRate: 0.1,
        replaysOnErrorSampleRate: 1.0,
    });

Once finished with the setup just restart your react app. You will be able to catch errors by tunneling. Go to the sentry dashboard and check it.