getsentry / sentry-react-native

Official Sentry SDK for React Native
https://sentry.io
MIT License
1.58k stars 337 forks source link

Conflict Between javascript-obfuscator and Sentry Settings in Metro Configuration for React Native #3526

Closed zzzkasper-1995 closed 9 months ago

zzzkasper-1995 commented 9 months ago

When integrating Sentry into a React Native project alongside javascript-obfuscator, there appears to be a conflict in the Metro configuration settings. This leads to inaccurate source map line numbers when attempting to trace errors through stack traces.

Environment

React Native Version: 0.70.10 Sentry Version: 5.15.2 obfuscator-io-metro-plugin: 2.1.1 Hermes Enabled: Yes

Steps to Reproduce:

Integrate Sentry according to their documentation by adding settings to metro.config.js. Integrate javascript-obfuscator using obfuscator-io-metro-plugin. Run the project build. Generate an error and observe the source map line numbers in Sentry.

Expected Behavior:

Accurate line number mapping in source maps when using Sentry and javascript-obfuscator together.

Actual Behavior:

The line numbers in the source maps do not correspond accurately, hindering debugging efforts.

Metro Configuration (metro.config.js):

const {getDefaultConfig} = require('metro-config');
const {createSentryMetroSerializer} = require('@sentry/react-native/dist/js/tools/sentryMetroSerializer');
const jsoMetroPlugin = require("obfuscator-io-metro-plugin")(
    {
        compact: true,
    },
    {
        runInDev: false,
        logObfuscatedFiles: false,
    }
);

module.exports = (async () => {
    const {resolver: {sourceExts, assetExts}} = await getDefaultConfig();
    const sentrySerializer = createSentryMetroSerializer();

    return {
        transformer: {
            getTransformOptions: async () => ({
                transform: {
                    experimentalImportSupport: false,
                    inlineRequires: true,
                },
            }),
            babelTransformerPath: require.resolve('react-native-svg-transformer'),

        },
        resolver: {
            assetExts: assetExts.filter((ext) => ext !== 'svg'),
            sourceExts: [...sourceExts, 'svg'],
        },
        ...jsoMetroPlugin,
        serializer: {
            customSerializer: sentrySerializer,
        },
    };})();
krystofwoldrich commented 9 months ago

Hi @zzzkasper-1995, thank you for the message, I see how the two configurations are clashing.

I have enabled source maps in the obfuscator-io-metro-plugin plugin and merged the obfuscator and Sentry serializer configs.

Can you try this?

const { getDefaultConfig } = require("metro-config");
const {
  createSentryMetroSerializer,
} = require("@sentry/react-native/dist/js/tools/sentryMetroSerializer");
const jsoMetroPlugin = require("obfuscator-io-metro-plugin")(
  {
    compact: true,
+    sourceMap: true,
  },
  {
    runInDev: false,
    logObfuscatedFiles: false,
  }
);

module.exports = (async () => {
  const {
    resolver: { sourceExts, assetExts },
  } = await getDefaultConfig();
  const sentrySerializer = createSentryMetroSerializer();

  return {
    transformer: {
      getTransformOptions: async () => ({
        transform: {
          experimentalImportSupport: false,
          inlineRequires: true,
        },
      }),
      babelTransformerPath: require.resolve("react-native-svg-transformer"),
    },
    resolver: {
      assetExts: assetExts.filter((ext) => ext !== "svg"),
      sourceExts: [...sourceExts, "svg"],
    },
    ...jsoMetroPlugin,
    serializer: {
+      ...jsoMetroPlugin.serializer,
      customSerializer: sentrySerializer,
    },
  };
})();

Let us know if this solves the issue for you.

zzzkasper-1995 commented 9 months ago

// metro.config.js

...
serializer: {
    ...jsoMetroPlugin.serializer,
    customSerializer: sentrySerializer,
}
...

"...jsoMetroPlugin.serializer" adds the processModuleFilter field, which sets the filtering settings for modules that should be obfuscated. But in addition to this, the method itself const jsoMetroPlugin = require('obfuscator-io-metro-plugin')(config) underneath triggers the code obfuscation mechanism here

//node_modules/obfuscator-io-metro-plugin/lib/index.js

...
process.on('beforeExit', async function (exitCode) {
    try {
        console.log('info: Obfuscating Code');
        // start obfuscation
        await obfuscateBundle(bundlePath, Array.from(fileNames), config, runConfig);
        if(!runConfig || !runConfig.logObfuscatedFiles){
            await remove(TEMP_FOLDER); // clear temp folder
        }
    } catch(err) {
        console.error(err);
        process.exit(1);
    } finally {
        process.exit(exitCode)
    }
});
...
zzzkasper-1995 commented 9 months ago

Do you think there's any real benefit to obfuscating code when using Hermes, considering that Hermes already complicates reverse engineering by converting JS into bytecode?

krystofwoldrich commented 9 months ago

As long as the Hermes bundle doesn't contain any debug information -> the default behaviour when source maps are enabled. I think the obfuscation of JS doesn't bring any benefits.

krystofwoldrich commented 9 months ago

@zzzkasper-1995 Did the metro config update work for you, can we close this issue?

zzzkasper-1995 commented 9 months ago

As a solution, I switched to https://github.com/vesselsoft/metro-minify-obfuscator, which utilizes the same javascript-obfuscator library under the hood but does not break sourceMaps.

krystofwoldrich commented 9 months ago

@zzzkasper-1995 Thank you for the message. I'll close this issue now.