From bf7c762121edab595d7bd6cdadbf3fdaf8fed01f Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Tue, 14 Jan 2025 19:22:34 +0200 Subject: [PATCH 1/5] Extract Android SDK Init --- .../io/sentry/react/RNSentryModuleImpl.java | 284 +--------------- .../java/io/sentry/react/RNSentryStart.java | 306 ++++++++++++++++++ 2 files changed, 310 insertions(+), 280 deletions(-) create mode 100644 packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 486ae72c4..cb8b99d9e 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -20,13 +20,11 @@ import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; -import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.bridge.WritableNativeMap; -import com.facebook.react.common.JavascriptException; import com.facebook.react.modules.core.DeviceEventManagerModule; import io.sentry.Breadcrumb; import io.sentry.HubAdapter; @@ -34,25 +32,16 @@ import io.sentry.IScope; import io.sentry.ISentryExecutorService; import io.sentry.ISerializer; -import io.sentry.Integration; import io.sentry.Sentry; import io.sentry.SentryDate; import io.sentry.SentryDateProvider; -import io.sentry.SentryEvent; import io.sentry.SentryExecutorService; import io.sentry.SentryLevel; import io.sentry.SentryOptions; -import io.sentry.SentryReplayOptions; -import io.sentry.UncaughtExceptionHandlerIntegration; import io.sentry.android.core.AndroidLogger; import io.sentry.android.core.AndroidProfiler; -import io.sentry.android.core.AnrIntegration; -import io.sentry.android.core.BuildConfig; import io.sentry.android.core.BuildInfoProvider; -import io.sentry.android.core.CurrentActivityHolder; import io.sentry.android.core.InternalSentrySdk; -import io.sentry.android.core.NdkIntegration; -import io.sentry.android.core.SentryAndroid; import io.sentry.android.core.SentryAndroidDateProvider; import io.sentry.android.core.SentryAndroidOptions; import io.sentry.android.core.ViewHierarchyEventProcessor; @@ -61,11 +50,8 @@ import io.sentry.android.core.performance.AppStartMetrics; import io.sentry.protocol.SdkVersion; import io.sentry.protocol.SentryId; -import io.sentry.protocol.SentryPackage; import io.sentry.protocol.User; import io.sentry.protocol.ViewHierarchy; -import io.sentry.react.replay.RNSentryReplayMask; -import io.sentry.react.replay.RNSentryReplayUnmask; import io.sentry.util.DebugMetaPropertiesApplier; import io.sentry.util.FileUtils; import io.sentry.util.JsonSerializationUtils; @@ -77,8 +63,6 @@ import java.io.FileReader; import java.io.IOException; import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Iterator; @@ -133,11 +117,14 @@ public class RNSentryModuleImpl { private final @NotNull SentryDateProvider dateProvider; + private final @NotNull RNSentryStart sdk; + public RNSentryModuleImpl(ReactApplicationContext reactApplicationContext) { packageInfo = getPackageInfo(reactApplicationContext); this.reactApplicationContext = reactApplicationContext; this.emitNewFrameEvent = createEmitNewFrameEvent(); this.dateProvider = new SentryAndroidDateProvider(); + this.sdk = new RNSentryStart(); } private ReactApplicationContext getReactApplicationContext() { @@ -178,216 +165,11 @@ public void initNativeReactNavigationNewFrameTracking(Promise promise) { } public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { - SentryAndroid.init( - this.getReactApplicationContext(), - options -> getSentryAndroidOptions(options, rnOptions, logger)); + sdk.startWithOptions(this.getReactApplicationContext(), rnOptions, getCurrentActivity(), logger); promise.resolve(true); } - protected void getSentryAndroidOptions( - @NotNull SentryAndroidOptions options, @NotNull ReadableMap rnOptions, ILogger logger) { - @Nullable SdkVersion sdkVersion = options.getSdkVersion(); - if (sdkVersion == null) { - sdkVersion = new SdkVersion(RNSentryVersion.ANDROID_SDK_NAME, BuildConfig.VERSION_NAME); - } else { - sdkVersion.setName(RNSentryVersion.ANDROID_SDK_NAME); - } - sdkVersion.addPackage( - RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_NAME, - RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION); - - options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion()); - options.setNativeSdkName(RNSentryVersion.NATIVE_SDK_NAME); - options.setSdkVersion(sdkVersion); - - if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) { - options.setDebug(true); - } - if (rnOptions.hasKey("dsn") && rnOptions.getString("dsn") != null) { - String dsn = rnOptions.getString("dsn"); - logger.log(SentryLevel.INFO, String.format("Starting with DSN: '%s'", dsn)); - options.setDsn(dsn); - } else { - // SentryAndroid needs an empty string fallback for the dsn. - options.setDsn(""); - } - if (rnOptions.hasKey("sampleRate")) { - options.setSampleRate(rnOptions.getDouble("sampleRate")); - } - if (rnOptions.hasKey("sendClientReports")) { - options.setSendClientReports(rnOptions.getBoolean("sendClientReports")); - } - if (rnOptions.hasKey("maxBreadcrumbs")) { - options.setMaxBreadcrumbs(rnOptions.getInt("maxBreadcrumbs")); - } - if (rnOptions.hasKey("maxCacheItems")) { - options.setMaxCacheItems(rnOptions.getInt("maxCacheItems")); - } - if (rnOptions.hasKey("environment") && rnOptions.getString("environment") != null) { - options.setEnvironment(rnOptions.getString("environment")); - } - if (rnOptions.hasKey("release") && rnOptions.getString("release") != null) { - options.setRelease(rnOptions.getString("release")); - } - if (rnOptions.hasKey("dist") && rnOptions.getString("dist") != null) { - options.setDist(rnOptions.getString("dist")); - } - if (rnOptions.hasKey("enableAutoSessionTracking")) { - options.setEnableAutoSessionTracking(rnOptions.getBoolean("enableAutoSessionTracking")); - } - if (rnOptions.hasKey("sessionTrackingIntervalMillis")) { - options.setSessionTrackingIntervalMillis(rnOptions.getInt("sessionTrackingIntervalMillis")); - } - if (rnOptions.hasKey("shutdownTimeout")) { - options.setShutdownTimeoutMillis(rnOptions.getInt("shutdownTimeout")); - } - if (rnOptions.hasKey("enableNdkScopeSync")) { - options.setEnableScopeSync(rnOptions.getBoolean("enableNdkScopeSync")); - } - if (rnOptions.hasKey("attachStacktrace")) { - options.setAttachStacktrace(rnOptions.getBoolean("attachStacktrace")); - } - if (rnOptions.hasKey("attachThreads")) { - // JS use top level stacktrace and android attaches Threads which hides them so - // by default we hide. - options.setAttachThreads(rnOptions.getBoolean("attachThreads")); - } - if (rnOptions.hasKey("attachScreenshot")) { - options.setAttachScreenshot(rnOptions.getBoolean("attachScreenshot")); - } - if (rnOptions.hasKey("attachViewHierarchy")) { - options.setAttachViewHierarchy(rnOptions.getBoolean("attachViewHierarchy")); - } - if (rnOptions.hasKey("sendDefaultPii")) { - options.setSendDefaultPii(rnOptions.getBoolean("sendDefaultPii")); - } - if (rnOptions.hasKey("maxQueueSize")) { - options.setMaxQueueSize(rnOptions.getInt("maxQueueSize")); - } - if (rnOptions.hasKey("enableNdk")) { - options.setEnableNdk(rnOptions.getBoolean("enableNdk")); - } - if (rnOptions.hasKey("spotlight")) { - if (rnOptions.getType("spotlight") == ReadableType.Boolean) { - options.setEnableSpotlight(rnOptions.getBoolean("spotlight")); - options.setSpotlightConnectionUrl(rnOptions.getString("defaultSidecarUrl")); - } else if (rnOptions.getType("spotlight") == ReadableType.String) { - options.setEnableSpotlight(true); - options.setSpotlightConnectionUrl(rnOptions.getString("spotlight")); - } - } - - SentryReplayOptions replayOptions = getReplayOptions(rnOptions); - options.setSessionReplay(replayOptions); - if (isReplayEnabled(replayOptions)) { - options.getReplayController().setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter()); - } - - // Exclude Dev Server and Sentry Dsn request from Breadcrumbs - String dsn = getURLFromDSN(rnOptions.getString("dsn")); - String devServerUrl = rnOptions.getString("devServerUrl"); - options.setBeforeBreadcrumb( - (breadcrumb, hint) -> { - Object urlObject = breadcrumb.getData("url"); - String url = urlObject instanceof String ? (String) urlObject : ""; - if ("http".equals(breadcrumb.getType()) - && ((dsn != null && url.startsWith(dsn)) - || (devServerUrl != null && url.startsWith(devServerUrl)))) { - return null; - } - return breadcrumb; - }); - - // React native internally throws a JavascriptException. - // we want to ignore it on the native side to avoid sending it twice. - options.addIgnoredExceptionForType(JavascriptException.class); - - options.setBeforeSend( - (event, hint) -> { - setEventOriginTag(event); - addPackages(event, options.getSdkVersion()); - - return event; - }); - - if (rnOptions.hasKey("enableNativeCrashHandling") - && !rnOptions.getBoolean("enableNativeCrashHandling")) { - final List integrations = options.getIntegrations(); - for (final Integration integration : integrations) { - if (integration instanceof UncaughtExceptionHandlerIntegration - || integration instanceof AnrIntegration - || integration instanceof NdkIntegration) { - integrations.remove(integration); - } - } - } - logger.log( - SentryLevel.INFO, String.format("Native Integrations '%s'", options.getIntegrations())); - - final CurrentActivityHolder currentActivityHolder = CurrentActivityHolder.getInstance(); - final Activity currentActivity = getCurrentActivity(); - if (currentActivity != null) { - currentActivityHolder.setActivity(currentActivity); - } - } - - private boolean isReplayEnabled(SentryReplayOptions replayOptions) { - return replayOptions.getSessionSampleRate() != null - || replayOptions.getOnErrorSampleRate() != null; - } - - private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) { - final SdkVersion replaySdkVersion = - new SdkVersion( - RNSentryVersion.REACT_NATIVE_SDK_NAME, - RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION); - @NotNull - final SentryReplayOptions androidReplayOptions = - new SentryReplayOptions(false, replaySdkVersion); - - if (!(rnOptions.hasKey("replaysSessionSampleRate") - || rnOptions.hasKey("replaysOnErrorSampleRate"))) { - return androidReplayOptions; - } - - androidReplayOptions.setSessionSampleRate( - rnOptions.hasKey("replaysSessionSampleRate") - ? rnOptions.getDouble("replaysSessionSampleRate") - : null); - androidReplayOptions.setOnErrorSampleRate( - rnOptions.hasKey("replaysOnErrorSampleRate") - ? rnOptions.getDouble("replaysOnErrorSampleRate") - : null); - - if (!rnOptions.hasKey("mobileReplayOptions")) { - return androidReplayOptions; - } - @Nullable final ReadableMap rnMobileReplayOptions = rnOptions.getMap("mobileReplayOptions"); - if (rnMobileReplayOptions == null) { - return androidReplayOptions; - } - - androidReplayOptions.setMaskAllText( - !rnMobileReplayOptions.hasKey("maskAllText") - || rnMobileReplayOptions.getBoolean("maskAllText")); - androidReplayOptions.setMaskAllImages( - !rnMobileReplayOptions.hasKey("maskAllImages") - || rnMobileReplayOptions.getBoolean("maskAllImages")); - - final boolean redactVectors = - !rnMobileReplayOptions.hasKey("maskAllVectors") - || rnMobileReplayOptions.getBoolean("maskAllVectors"); - if (redactVectors) { - androidReplayOptions.addMaskViewClass("com.horcrux.svg.SvgView"); // react-native-svg - } - - androidReplayOptions.setMaskViewContainerClass(RNSentryReplayMask.class.getName()); - androidReplayOptions.setUnmaskViewContainerClass(RNSentryReplayUnmask.class.getName()); - - return androidReplayOptions; - } - public void crash() { throw new RuntimeException("TEST - Sentry Client Crash (only works in release mode)"); } @@ -974,51 +756,6 @@ public void crashedLastRun(Promise promise) { promise.resolve(Sentry.isCrashedLastRun()); } - private void setEventOriginTag(SentryEvent event) { - // We hardcode native-java as only java events are processed by the Android SDK. - SdkVersion sdk = event.getSdk(); - if (sdk != null) { - switch (sdk.getName()) { - case RNSentryVersion.NATIVE_SDK_NAME: - setEventEnvironmentTag(event, "native"); - break; - case RNSentryVersion.ANDROID_SDK_NAME: - setEventEnvironmentTag(event, "java"); - break; - default: - break; - } - } - } - - private void setEventEnvironmentTag(SentryEvent event, String environment) { - event.setTag("event.origin", "android"); - event.setTag("event.environment", environment); - } - - private void addPackages(SentryEvent event, SdkVersion sdk) { - SdkVersion eventSdk = event.getSdk(); - if (eventSdk != null - && "sentry.javascript.react-native".equals(eventSdk.getName()) - && sdk != null) { - List sentryPackages = sdk.getPackages(); - if (sentryPackages != null) { - for (SentryPackage sentryPackage : sentryPackages) { - eventSdk.addPackage(sentryPackage.getName(), sentryPackage.getVersion()); - } - } - - List integrations = sdk.getIntegrations(); - if (integrations != null) { - for (String integration : integrations) { - eventSdk.addIntegration(integration); - } - } - - event.setSdk(eventSdk); - } - } - private boolean checkAndroidXAvailability() { try { Class.forName("androidx.core.app.FrameMetricsAggregator"); @@ -1032,17 +769,4 @@ private boolean checkAndroidXAvailability() { private boolean isFrameMetricsAggregatorAvailable() { return androidXAvailable && frameMetricsAggregator != null; } - - public static @Nullable String getURLFromDSN(@Nullable String dsn) { - if (dsn == null) { - return null; - } - URI uri = null; - try { - uri = new URI(dsn); - } catch (URISyntaxException e) { - return null; - } - return uri.getScheme() + "://" + uri.getHost(); - } } diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java new file mode 100644 index 000000000..4f04c2949 --- /dev/null +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java @@ -0,0 +1,306 @@ +package io.sentry.react; + +import android.app.Activity; +import android.content.Context; + +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableType; +import com.facebook.react.common.JavascriptException; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import io.sentry.ILogger; +import io.sentry.Integration; +import io.sentry.SentryEvent; +import io.sentry.SentryLevel; +import io.sentry.SentryReplayOptions; +import io.sentry.UncaughtExceptionHandlerIntegration; +import io.sentry.android.core.AnrIntegration; +import io.sentry.android.core.BuildConfig; +import io.sentry.android.core.CurrentActivityHolder; +import io.sentry.android.core.NdkIntegration; +import io.sentry.android.core.SentryAndroid; +import io.sentry.android.core.SentryAndroidOptions; +import io.sentry.protocol.SdkVersion; +import io.sentry.protocol.SentryPackage; +import io.sentry.react.replay.RNSentryReplayMask; +import io.sentry.react.replay.RNSentryReplayUnmask; + +public class RNSentryStart { + + public void startWithOptions(@NotNull final Context context, + @NotNull final ReadableMap rnOptions, + @Nullable Activity currentActivity, + @NotNull ILogger logger) { + SentryAndroid.init(context, options -> getSentryAndroidOptions(options, rnOptions, currentActivity, logger)); + } + + protected void getSentryAndroidOptions( + @NotNull SentryAndroidOptions options, @NotNull ReadableMap rnOptions, @Nullable Activity currentActivity, ILogger logger) { + @Nullable SdkVersion sdkVersion = options.getSdkVersion(); + if (sdkVersion == null) { + sdkVersion = new SdkVersion(RNSentryVersion.ANDROID_SDK_NAME, BuildConfig.VERSION_NAME); + } else { + sdkVersion.setName(RNSentryVersion.ANDROID_SDK_NAME); + } + sdkVersion.addPackage( + RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_NAME, + RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION); + + options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion()); + options.setNativeSdkName(RNSentryVersion.NATIVE_SDK_NAME); + options.setSdkVersion(sdkVersion); + + if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) { + options.setDebug(true); + } + if (rnOptions.hasKey("dsn") && rnOptions.getString("dsn") != null) { + String dsn = rnOptions.getString("dsn"); + logger.log(SentryLevel.INFO, String.format("Starting with DSN: '%s'", dsn)); + options.setDsn(dsn); + } else { + // SentryAndroid needs an empty string fallback for the dsn. + options.setDsn(""); + } + if (rnOptions.hasKey("sampleRate")) { + options.setSampleRate(rnOptions.getDouble("sampleRate")); + } + if (rnOptions.hasKey("sendClientReports")) { + options.setSendClientReports(rnOptions.getBoolean("sendClientReports")); + } + if (rnOptions.hasKey("maxBreadcrumbs")) { + options.setMaxBreadcrumbs(rnOptions.getInt("maxBreadcrumbs")); + } + if (rnOptions.hasKey("maxCacheItems")) { + options.setMaxCacheItems(rnOptions.getInt("maxCacheItems")); + } + if (rnOptions.hasKey("environment") && rnOptions.getString("environment") != null) { + options.setEnvironment(rnOptions.getString("environment")); + } + if (rnOptions.hasKey("release") && rnOptions.getString("release") != null) { + options.setRelease(rnOptions.getString("release")); + } + if (rnOptions.hasKey("dist") && rnOptions.getString("dist") != null) { + options.setDist(rnOptions.getString("dist")); + } + if (rnOptions.hasKey("enableAutoSessionTracking")) { + options.setEnableAutoSessionTracking(rnOptions.getBoolean("enableAutoSessionTracking")); + } + if (rnOptions.hasKey("sessionTrackingIntervalMillis")) { + options.setSessionTrackingIntervalMillis(rnOptions.getInt("sessionTrackingIntervalMillis")); + } + if (rnOptions.hasKey("shutdownTimeout")) { + options.setShutdownTimeoutMillis(rnOptions.getInt("shutdownTimeout")); + } + if (rnOptions.hasKey("enableNdkScopeSync")) { + options.setEnableScopeSync(rnOptions.getBoolean("enableNdkScopeSync")); + } + if (rnOptions.hasKey("attachStacktrace")) { + options.setAttachStacktrace(rnOptions.getBoolean("attachStacktrace")); + } + if (rnOptions.hasKey("attachThreads")) { + // JS use top level stacktrace and android attaches Threads which hides them so + // by default we hide. + options.setAttachThreads(rnOptions.getBoolean("attachThreads")); + } + if (rnOptions.hasKey("attachScreenshot")) { + options.setAttachScreenshot(rnOptions.getBoolean("attachScreenshot")); + } + if (rnOptions.hasKey("attachViewHierarchy")) { + options.setAttachViewHierarchy(rnOptions.getBoolean("attachViewHierarchy")); + } + if (rnOptions.hasKey("sendDefaultPii")) { + options.setSendDefaultPii(rnOptions.getBoolean("sendDefaultPii")); + } + if (rnOptions.hasKey("maxQueueSize")) { + options.setMaxQueueSize(rnOptions.getInt("maxQueueSize")); + } + if (rnOptions.hasKey("enableNdk")) { + options.setEnableNdk(rnOptions.getBoolean("enableNdk")); + } + if (rnOptions.hasKey("spotlight")) { + if (rnOptions.getType("spotlight") == ReadableType.Boolean) { + options.setEnableSpotlight(rnOptions.getBoolean("spotlight")); + options.setSpotlightConnectionUrl(rnOptions.getString("defaultSidecarUrl")); + } else if (rnOptions.getType("spotlight") == ReadableType.String) { + options.setEnableSpotlight(true); + options.setSpotlightConnectionUrl(rnOptions.getString("spotlight")); + } + } + + SentryReplayOptions replayOptions = getReplayOptions(rnOptions); + options.setSessionReplay(replayOptions); + if (isReplayEnabled(replayOptions)) { + options.getReplayController().setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter()); + } + + // Exclude Dev Server and Sentry Dsn request from Breadcrumbs + String dsn = getURLFromDSN(rnOptions.getString("dsn")); + String devServerUrl = rnOptions.getString("devServerUrl"); + options.setBeforeBreadcrumb( + (breadcrumb, hint) -> { + Object urlObject = breadcrumb.getData("url"); + String url = urlObject instanceof String ? (String) urlObject : ""; + if ("http".equals(breadcrumb.getType()) + && ((dsn != null && url.startsWith(dsn)) + || (devServerUrl != null && url.startsWith(devServerUrl)))) { + return null; + } + return breadcrumb; + }); + + // React native internally throws a JavascriptException. + // we want to ignore it on the native side to avoid sending it twice. + options.addIgnoredExceptionForType(JavascriptException.class); + + options.setBeforeSend( + (event, hint) -> { + setEventOriginTag(event); + addPackages(event, options.getSdkVersion()); + + return event; + }); + + if (rnOptions.hasKey("enableNativeCrashHandling") + && !rnOptions.getBoolean("enableNativeCrashHandling")) { + final List integrations = options.getIntegrations(); + for (final Integration integration : integrations) { + if (integration instanceof UncaughtExceptionHandlerIntegration + || integration instanceof AnrIntegration + || integration instanceof NdkIntegration) { + integrations.remove(integration); + } + } + } + logger.log( + SentryLevel.INFO, String.format("Native Integrations '%s'", options.getIntegrations())); + + setCurrentActivity(currentActivity); + } + + private void setCurrentActivity(Activity currentActivity) { + final CurrentActivityHolder currentActivityHolder = CurrentActivityHolder.getInstance(); + if (currentActivity != null) { + currentActivityHolder.setActivity(currentActivity); + } + } + + private boolean isReplayEnabled(SentryReplayOptions replayOptions) { + return replayOptions.getSessionSampleRate() != null + || replayOptions.getOnErrorSampleRate() != null; + } + + private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) { + final SdkVersion replaySdkVersion = + new SdkVersion( + RNSentryVersion.REACT_NATIVE_SDK_NAME, + RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION); + @NotNull + final SentryReplayOptions androidReplayOptions = + new SentryReplayOptions(false, replaySdkVersion); + + if (!(rnOptions.hasKey("replaysSessionSampleRate") + || rnOptions.hasKey("replaysOnErrorSampleRate"))) { + return androidReplayOptions; + } + + androidReplayOptions.setSessionSampleRate( + rnOptions.hasKey("replaysSessionSampleRate") + ? rnOptions.getDouble("replaysSessionSampleRate") + : null); + androidReplayOptions.setOnErrorSampleRate( + rnOptions.hasKey("replaysOnErrorSampleRate") + ? rnOptions.getDouble("replaysOnErrorSampleRate") + : null); + + if (!rnOptions.hasKey("mobileReplayOptions")) { + return androidReplayOptions; + } + @Nullable final ReadableMap rnMobileReplayOptions = rnOptions.getMap("mobileReplayOptions"); + if (rnMobileReplayOptions == null) { + return androidReplayOptions; + } + + androidReplayOptions.setMaskAllText( + !rnMobileReplayOptions.hasKey("maskAllText") + || rnMobileReplayOptions.getBoolean("maskAllText")); + androidReplayOptions.setMaskAllImages( + !rnMobileReplayOptions.hasKey("maskAllImages") + || rnMobileReplayOptions.getBoolean("maskAllImages")); + + final boolean redactVectors = + !rnMobileReplayOptions.hasKey("maskAllVectors") + || rnMobileReplayOptions.getBoolean("maskAllVectors"); + if (redactVectors) { + androidReplayOptions.addMaskViewClass("com.horcrux.svg.SvgView"); // react-native-svg + } + + androidReplayOptions.setMaskViewContainerClass(RNSentryReplayMask.class.getName()); + androidReplayOptions.setUnmaskViewContainerClass(RNSentryReplayUnmask.class.getName()); + + return androidReplayOptions; + } + + private void setEventOriginTag(SentryEvent event) { + // We hardcode native-java as only java events are processed by the Android SDK. + SdkVersion sdk = event.getSdk(); + if (sdk != null) { + switch (sdk.getName()) { + case RNSentryVersion.NATIVE_SDK_NAME: + setEventEnvironmentTag(event, "native"); + break; + case RNSentryVersion.ANDROID_SDK_NAME: + setEventEnvironmentTag(event, "java"); + break; + default: + break; + } + } + } + + private void setEventEnvironmentTag(SentryEvent event, String environment) { + event.setTag("event.origin", "android"); + event.setTag("event.environment", environment); + } + + private void addPackages(SentryEvent event, SdkVersion sdk) { + SdkVersion eventSdk = event.getSdk(); + if (eventSdk != null + && "sentry.javascript.react-native".equals(eventSdk.getName()) + && sdk != null) { + List sentryPackages = sdk.getPackages(); + if (sentryPackages != null) { + for (SentryPackage sentryPackage : sentryPackages) { + eventSdk.addPackage(sentryPackage.getName(), sentryPackage.getVersion()); + } + } + + List integrations = sdk.getIntegrations(); + if (integrations != null) { + for (String integration : integrations) { + eventSdk.addIntegration(integration); + } + } + + event.setSdk(eventSdk); + } + } + + private static @Nullable String getURLFromDSN(@Nullable String dsn) { + if (dsn == null) { + return null; + } + URI uri = null; + try { + uri = new URI(dsn); + } catch (URISyntaxException e) { + return null; + } + return uri.getScheme() + "://" + uri.getHost(); + } +} From 897d91805ad07759ba32cfa08a05aca44e1cc288 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Tue, 14 Jan 2025 19:22:44 +0200 Subject: [PATCH 2/5] Update tests --- .../io/sentry/react/RNSentryModuleImplTest.kt | 166 --------------- .../java/io/sentry/react/RNSentryStartTest.kt | 193 ++++++++++++++++++ 2 files changed, 193 insertions(+), 166 deletions(-) create mode 100644 packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryStartTest.kt diff --git a/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryModuleImplTest.kt b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryModuleImplTest.kt index adffbf78a..34af996a7 100644 --- a/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryModuleImplTest.kt +++ b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryModuleImplTest.kt @@ -3,20 +3,13 @@ package io.sentry.react import android.content.pm.PackageInfo import android.content.pm.PackageManager import com.facebook.react.bridge.Arguments -import com.facebook.react.bridge.JavaOnlyMap import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.WritableMap -import com.facebook.react.common.JavascriptException -import io.sentry.Breadcrumb import io.sentry.ILogger import io.sentry.SentryLevel -import io.sentry.android.core.SentryAndroidOptions import org.junit.After import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertNull -import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -103,163 +96,4 @@ class RNSentryModuleImplTest { val capturedMap = writableMapCaptor.value assertEquals(false, capturedMap.getBoolean("has_fetched")) } - - @Test - fun `when the spotlight option is enabled, the spotlight SentryAndroidOption is set to true and the default url is used`() { - val options = - JavaOnlyMap.of( - "spotlight", - true, - "defaultSidecarUrl", - "http://localhost:8969/teststream", - ) - val actualOptions = SentryAndroidOptions() - module.getSentryAndroidOptions(actualOptions, options, logger) - assert(actualOptions.isEnableSpotlight) - assertEquals("http://localhost:8969/teststream", actualOptions.spotlightConnectionUrl) - } - - @Test - fun `when the spotlight url is passed, the spotlight is enabled for the given url`() { - val options = JavaOnlyMap.of("spotlight", "http://localhost:8969/teststream") - val actualOptions = SentryAndroidOptions() - module.getSentryAndroidOptions(actualOptions, options, logger) - assert(actualOptions.isEnableSpotlight) - assertEquals("http://localhost:8969/teststream", actualOptions.spotlightConnectionUrl) - } - - @Test - fun `when the spotlight option is disabled, the spotlight SentryAndroidOption is set to false`() { - val options = JavaOnlyMap.of("spotlight", false) - val actualOptions = SentryAndroidOptions() - module.getSentryAndroidOptions(actualOptions, options, logger) - assertFalse(actualOptions.isEnableSpotlight) - } - - @Test - fun `the JavascriptException is added to the ignoredExceptionsForType list on initialisation`() { - val actualOptions = SentryAndroidOptions() - module.getSentryAndroidOptions(actualOptions, JavaOnlyMap.of(), logger) - assertTrue(actualOptions.ignoredExceptionsForType.contains(JavascriptException::class.java)) - } - - @Test - fun `beforeBreadcrumb callback filters out Sentry DSN requests breadcrumbs`() { - val options = SentryAndroidOptions() - val rnOptions = - JavaOnlyMap.of( - "dsn", - "https://abc@def.ingest.sentry.io/1234567", - "devServerUrl", - "http://localhost:8081", - ) - module.getSentryAndroidOptions(options, rnOptions, logger) - - val breadcrumb = - Breadcrumb().apply { - type = "http" - setData("url", "https://def.ingest.sentry.io/1234567") - } - - val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) - - assertNull("Breadcrumb should be filtered out", result) - } - - @Test - fun `beforeBreadcrumb callback filters out dev server breadcrumbs`() { - val mockDevServerUrl = "http://localhost:8081" - val options = SentryAndroidOptions() - val rnOptions = - JavaOnlyMap.of( - "dsn", - "https://abc@def.ingest.sentry.io/1234567", - "devServerUrl", - mockDevServerUrl, - ) - module.getSentryAndroidOptions(options, rnOptions, logger) - - val breadcrumb = - Breadcrumb().apply { - type = "http" - setData("url", mockDevServerUrl) - } - - val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) - - assertNull("Breadcrumb should be filtered out", result) - } - - @Test - fun `beforeBreadcrumb callback does not filter out non dev server or dsn breadcrumbs`() { - val options = SentryAndroidOptions() - val rnOptions = - JavaOnlyMap.of( - "dsn", - "https://abc@def.ingest.sentry.io/1234567", - "devServerUrl", - "http://localhost:8081", - ) - module.getSentryAndroidOptions(options, rnOptions, logger) - - val breadcrumb = - Breadcrumb().apply { - type = "http" - setData("url", "http://testurl.com/service") - } - - val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) - - assertEquals(breadcrumb, result) - } - - @Test - fun `the breadcrumb is not filtered out when the dev server url and dsn are not passed`() { - val options = SentryAndroidOptions() - module.getSentryAndroidOptions(options, JavaOnlyMap(), logger) - - val breadcrumb = - Breadcrumb().apply { - type = "http" - setData("url", "http://testurl.com/service") - } - - val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) - - assertEquals(breadcrumb, result) - } - - @Test - fun `the breadcrumb is not filtered out when the dev server url is not passed and the dsn does not match`() { - val options = SentryAndroidOptions() - val rnOptions = JavaOnlyMap.of("dsn", "https://abc@def.ingest.sentry.io/1234567") - module.getSentryAndroidOptions(options, rnOptions, logger) - - val breadcrumb = - Breadcrumb().apply { - type = "http" - setData("url", "http://testurl.com/service") - } - - val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) - - assertEquals(breadcrumb, result) - } - - @Test - fun `the breadcrumb is not filtered out when the dev server url does not match and the dsn is not passed`() { - val options = SentryAndroidOptions() - val rnOptions = JavaOnlyMap.of("devServerUrl", "http://localhost:8081") - module.getSentryAndroidOptions(options, rnOptions, logger) - - val breadcrumb = - Breadcrumb().apply { - type = "http" - setData("url", "http://testurl.com/service") - } - - val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) - - assertEquals(breadcrumb, result) - } } diff --git a/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryStartTest.kt b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryStartTest.kt new file mode 100644 index 000000000..e49e49f34 --- /dev/null +++ b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryStartTest.kt @@ -0,0 +1,193 @@ +package io.sentry.react + +import android.app.Activity +import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.common.JavascriptException +import io.sentry.Breadcrumb +import io.sentry.ILogger +import io.sentry.android.core.SentryAndroidOptions +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations + +@RunWith(JUnit4::class) +class RNSentryStartTest { + private lateinit var module: RNSentryStart + private lateinit var logger: ILogger + + private lateinit var activity: Activity + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + logger = mock(ILogger::class.java) + activity = mock(Activity::class.java) + module = RNSentryStart() + } + + @Test + fun `when the spotlight option is enabled, the spotlight SentryAndroidOption is set to true and the default url is used`() { + val options = + JavaOnlyMap.of( + "spotlight", + true, + "defaultSidecarUrl", + "http://localhost:8969/teststream", + ) + val actualOptions = SentryAndroidOptions() + module.getSentryAndroidOptions(actualOptions, options, activity, logger) + assert(actualOptions.isEnableSpotlight) + assertEquals("http://localhost:8969/teststream", actualOptions.spotlightConnectionUrl) + } + + @Test + fun `when the spotlight url is passed, the spotlight is enabled for the given url`() { + val options = JavaOnlyMap.of("spotlight", "http://localhost:8969/teststream") + val actualOptions = SentryAndroidOptions() + module.getSentryAndroidOptions(actualOptions, options, activity, logger) + assert(actualOptions.isEnableSpotlight) + assertEquals("http://localhost:8969/teststream", actualOptions.spotlightConnectionUrl) + } + + @Test + fun `when the spotlight option is disabled, the spotlight SentryAndroidOption is set to false`() { + val options = JavaOnlyMap.of("spotlight", false) + val actualOptions = SentryAndroidOptions() + module.getSentryAndroidOptions(actualOptions, options, activity, logger) + assertFalse(actualOptions.isEnableSpotlight) + } + + @Test + fun `the JavascriptException is added to the ignoredExceptionsForType list on initialisation`() { + val actualOptions = SentryAndroidOptions() + module.getSentryAndroidOptions(actualOptions, JavaOnlyMap.of(), activity, logger) + assertTrue(actualOptions.ignoredExceptionsForType.contains(JavascriptException::class.java)) + } + + @Test + fun `beforeBreadcrumb callback filters out Sentry DSN requests breadcrumbs`() { + val options = SentryAndroidOptions() + val rnOptions = + JavaOnlyMap.of( + "dsn", + "https://abc@def.ingest.sentry.io/1234567", + "devServerUrl", + "http://localhost:8081", + ) + module.getSentryAndroidOptions(options, rnOptions, activity, logger) + + val breadcrumb = + Breadcrumb().apply { + type = "http" + setData("url", "https://def.ingest.sentry.io/1234567") + } + + val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) + + assertNull("Breadcrumb should be filtered out", result) + } + + @Test + fun `beforeBreadcrumb callback filters out dev server breadcrumbs`() { + val mockDevServerUrl = "http://localhost:8081" + val options = SentryAndroidOptions() + val rnOptions = + JavaOnlyMap.of( + "dsn", + "https://abc@def.ingest.sentry.io/1234567", + "devServerUrl", + mockDevServerUrl, + ) + module.getSentryAndroidOptions(options, rnOptions, activity, logger) + + val breadcrumb = + Breadcrumb().apply { + type = "http" + setData("url", mockDevServerUrl) + } + + val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) + + assertNull("Breadcrumb should be filtered out", result) + } + + @Test + fun `beforeBreadcrumb callback does not filter out non dev server or dsn breadcrumbs`() { + val options = SentryAndroidOptions() + val rnOptions = + JavaOnlyMap.of( + "dsn", + "https://abc@def.ingest.sentry.io/1234567", + "devServerUrl", + "http://localhost:8081", + ) + module.getSentryAndroidOptions(options, rnOptions, activity, logger) + + val breadcrumb = + Breadcrumb().apply { + type = "http" + setData("url", "http://testurl.com/service") + } + + val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) + + assertEquals(breadcrumb, result) + } + + @Test + fun `the breadcrumb is not filtered out when the dev server url and dsn are not passed`() { + val options = SentryAndroidOptions() + module.getSentryAndroidOptions(options, JavaOnlyMap(), activity, logger) + + val breadcrumb = + Breadcrumb().apply { + type = "http" + setData("url", "http://testurl.com/service") + } + + val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) + + assertEquals(breadcrumb, result) + } + + @Test + fun `the breadcrumb is not filtered out when the dev server url is not passed and the dsn does not match`() { + val options = SentryAndroidOptions() + val rnOptions = JavaOnlyMap.of("dsn", "https://abc@def.ingest.sentry.io/1234567") + module.getSentryAndroidOptions(options, rnOptions, activity, logger) + + val breadcrumb = + Breadcrumb().apply { + type = "http" + setData("url", "http://testurl.com/service") + } + + val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) + + assertEquals(breadcrumb, result) + } + + @Test + fun `the breadcrumb is not filtered out when the dev server url does not match and the dsn is not passed`() { + val options = SentryAndroidOptions() + val rnOptions = JavaOnlyMap.of("devServerUrl", "http://localhost:8081") + module.getSentryAndroidOptions(options, rnOptions, activity, logger) + + val breadcrumb = + Breadcrumb().apply { + type = "http" + setData("url", "http://testurl.com/service") + } + + val result = options.beforeBreadcrumb?.execute(breadcrumb, mock()) + + assertEquals(breadcrumb, result) + } +} From e06e6bbe07df52cfe31c1c995dcbdfa7d36e4c4f Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Tue, 14 Jan 2025 19:25:39 +0200 Subject: [PATCH 3/5] Adds changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dd716d64..68f1a9f5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,10 @@ - [changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md#8480) - [diff](https://github.com/getsentry/sentry-javascript/compare/8.47.0...8.48.0) +### Internal + +- Extract Android native initialization to standalone structures ([#4445](https://github.com/getsentry/sentry-react-native/pull/4445)) + ## 6.5.0 ### Features From 903fc5542ebe74edfe2c1d22b0ac6067342c0c8a Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Tue, 14 Jan 2025 19:31:56 +0200 Subject: [PATCH 4/5] Fix lint issues --- .../io/sentry/react/RNSentryModuleImpl.java | 3 +- .../java/io/sentry/react/RNSentryStart.java | 513 +++++++++--------- 2 files changed, 259 insertions(+), 257 deletions(-) diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index cb8b99d9e..6f655e3c2 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -165,7 +165,8 @@ public void initNativeReactNavigationNewFrameTracking(Promise promise) { } public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { - sdk.startWithOptions(this.getReactApplicationContext(), rnOptions, getCurrentActivity(), logger); + sdk.startWithOptions( + this.getReactApplicationContext(), rnOptions, getCurrentActivity(), logger); promise.resolve(true); } diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java index 4f04c2949..e83950247 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryStart.java @@ -2,18 +2,9 @@ import android.app.Activity; import android.content.Context; - import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableType; import com.facebook.react.common.JavascriptException; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.List; - import io.sentry.ILogger; import io.sentry.Integration; import io.sentry.SentryEvent; @@ -30,277 +21,287 @@ import io.sentry.protocol.SentryPackage; import io.sentry.react.replay.RNSentryReplayMask; import io.sentry.react.replay.RNSentryReplayUnmask; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class RNSentryStart { - public void startWithOptions(@NotNull final Context context, - @NotNull final ReadableMap rnOptions, - @Nullable Activity currentActivity, - @NotNull ILogger logger) { - SentryAndroid.init(context, options -> getSentryAndroidOptions(options, rnOptions, currentActivity, logger)); + public void startWithOptions( + @NotNull final Context context, + @NotNull final ReadableMap rnOptions, + @Nullable Activity currentActivity, + @NotNull ILogger logger) { + SentryAndroid.init( + context, options -> getSentryAndroidOptions(options, rnOptions, currentActivity, logger)); + } + + protected void getSentryAndroidOptions( + @NotNull SentryAndroidOptions options, + @NotNull ReadableMap rnOptions, + @Nullable Activity currentActivity, + ILogger logger) { + @Nullable SdkVersion sdkVersion = options.getSdkVersion(); + if (sdkVersion == null) { + sdkVersion = new SdkVersion(RNSentryVersion.ANDROID_SDK_NAME, BuildConfig.VERSION_NAME); + } else { + sdkVersion.setName(RNSentryVersion.ANDROID_SDK_NAME); } + sdkVersion.addPackage( + RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_NAME, + RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION); - protected void getSentryAndroidOptions( - @NotNull SentryAndroidOptions options, @NotNull ReadableMap rnOptions, @Nullable Activity currentActivity, ILogger logger) { - @Nullable SdkVersion sdkVersion = options.getSdkVersion(); - if (sdkVersion == null) { - sdkVersion = new SdkVersion(RNSentryVersion.ANDROID_SDK_NAME, BuildConfig.VERSION_NAME); - } else { - sdkVersion.setName(RNSentryVersion.ANDROID_SDK_NAME); - } - sdkVersion.addPackage( - RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_NAME, - RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION); - - options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion()); - options.setNativeSdkName(RNSentryVersion.NATIVE_SDK_NAME); - options.setSdkVersion(sdkVersion); - - if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) { - options.setDebug(true); - } - if (rnOptions.hasKey("dsn") && rnOptions.getString("dsn") != null) { - String dsn = rnOptions.getString("dsn"); - logger.log(SentryLevel.INFO, String.format("Starting with DSN: '%s'", dsn)); - options.setDsn(dsn); - } else { - // SentryAndroid needs an empty string fallback for the dsn. - options.setDsn(""); - } - if (rnOptions.hasKey("sampleRate")) { - options.setSampleRate(rnOptions.getDouble("sampleRate")); - } - if (rnOptions.hasKey("sendClientReports")) { - options.setSendClientReports(rnOptions.getBoolean("sendClientReports")); - } - if (rnOptions.hasKey("maxBreadcrumbs")) { - options.setMaxBreadcrumbs(rnOptions.getInt("maxBreadcrumbs")); - } - if (rnOptions.hasKey("maxCacheItems")) { - options.setMaxCacheItems(rnOptions.getInt("maxCacheItems")); - } - if (rnOptions.hasKey("environment") && rnOptions.getString("environment") != null) { - options.setEnvironment(rnOptions.getString("environment")); - } - if (rnOptions.hasKey("release") && rnOptions.getString("release") != null) { - options.setRelease(rnOptions.getString("release")); - } - if (rnOptions.hasKey("dist") && rnOptions.getString("dist") != null) { - options.setDist(rnOptions.getString("dist")); - } - if (rnOptions.hasKey("enableAutoSessionTracking")) { - options.setEnableAutoSessionTracking(rnOptions.getBoolean("enableAutoSessionTracking")); - } - if (rnOptions.hasKey("sessionTrackingIntervalMillis")) { - options.setSessionTrackingIntervalMillis(rnOptions.getInt("sessionTrackingIntervalMillis")); - } - if (rnOptions.hasKey("shutdownTimeout")) { - options.setShutdownTimeoutMillis(rnOptions.getInt("shutdownTimeout")); - } - if (rnOptions.hasKey("enableNdkScopeSync")) { - options.setEnableScopeSync(rnOptions.getBoolean("enableNdkScopeSync")); - } - if (rnOptions.hasKey("attachStacktrace")) { - options.setAttachStacktrace(rnOptions.getBoolean("attachStacktrace")); - } - if (rnOptions.hasKey("attachThreads")) { - // JS use top level stacktrace and android attaches Threads which hides them so - // by default we hide. - options.setAttachThreads(rnOptions.getBoolean("attachThreads")); - } - if (rnOptions.hasKey("attachScreenshot")) { - options.setAttachScreenshot(rnOptions.getBoolean("attachScreenshot")); - } - if (rnOptions.hasKey("attachViewHierarchy")) { - options.setAttachViewHierarchy(rnOptions.getBoolean("attachViewHierarchy")); - } - if (rnOptions.hasKey("sendDefaultPii")) { - options.setSendDefaultPii(rnOptions.getBoolean("sendDefaultPii")); - } - if (rnOptions.hasKey("maxQueueSize")) { - options.setMaxQueueSize(rnOptions.getInt("maxQueueSize")); - } - if (rnOptions.hasKey("enableNdk")) { - options.setEnableNdk(rnOptions.getBoolean("enableNdk")); - } - if (rnOptions.hasKey("spotlight")) { - if (rnOptions.getType("spotlight") == ReadableType.Boolean) { - options.setEnableSpotlight(rnOptions.getBoolean("spotlight")); - options.setSpotlightConnectionUrl(rnOptions.getString("defaultSidecarUrl")); - } else if (rnOptions.getType("spotlight") == ReadableType.String) { - options.setEnableSpotlight(true); - options.setSpotlightConnectionUrl(rnOptions.getString("spotlight")); - } - } - - SentryReplayOptions replayOptions = getReplayOptions(rnOptions); - options.setSessionReplay(replayOptions); - if (isReplayEnabled(replayOptions)) { - options.getReplayController().setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter()); - } - - // Exclude Dev Server and Sentry Dsn request from Breadcrumbs - String dsn = getURLFromDSN(rnOptions.getString("dsn")); - String devServerUrl = rnOptions.getString("devServerUrl"); - options.setBeforeBreadcrumb( - (breadcrumb, hint) -> { - Object urlObject = breadcrumb.getData("url"); - String url = urlObject instanceof String ? (String) urlObject : ""; - if ("http".equals(breadcrumb.getType()) - && ((dsn != null && url.startsWith(dsn)) - || (devServerUrl != null && url.startsWith(devServerUrl)))) { - return null; - } - return breadcrumb; - }); - - // React native internally throws a JavascriptException. - // we want to ignore it on the native side to avoid sending it twice. - options.addIgnoredExceptionForType(JavascriptException.class); - - options.setBeforeSend( - (event, hint) -> { - setEventOriginTag(event); - addPackages(event, options.getSdkVersion()); - - return event; - }); + options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion()); + options.setNativeSdkName(RNSentryVersion.NATIVE_SDK_NAME); + options.setSdkVersion(sdkVersion); - if (rnOptions.hasKey("enableNativeCrashHandling") - && !rnOptions.getBoolean("enableNativeCrashHandling")) { - final List integrations = options.getIntegrations(); - for (final Integration integration : integrations) { - if (integration instanceof UncaughtExceptionHandlerIntegration - || integration instanceof AnrIntegration - || integration instanceof NdkIntegration) { - integrations.remove(integration); - } - } - } - logger.log( - SentryLevel.INFO, String.format("Native Integrations '%s'", options.getIntegrations())); - - setCurrentActivity(currentActivity); + if (rnOptions.hasKey("debug") && rnOptions.getBoolean("debug")) { + options.setDebug(true); } - - private void setCurrentActivity(Activity currentActivity) { - final CurrentActivityHolder currentActivityHolder = CurrentActivityHolder.getInstance(); - if (currentActivity != null) { - currentActivityHolder.setActivity(currentActivity); - } + if (rnOptions.hasKey("dsn") && rnOptions.getString("dsn") != null) { + String dsn = rnOptions.getString("dsn"); + logger.log(SentryLevel.INFO, String.format("Starting with DSN: '%s'", dsn)); + options.setDsn(dsn); + } else { + // SentryAndroid needs an empty string fallback for the dsn. + options.setDsn(""); } - - private boolean isReplayEnabled(SentryReplayOptions replayOptions) { - return replayOptions.getSessionSampleRate() != null - || replayOptions.getOnErrorSampleRate() != null; + if (rnOptions.hasKey("sampleRate")) { + options.setSampleRate(rnOptions.getDouble("sampleRate")); + } + if (rnOptions.hasKey("sendClientReports")) { + options.setSendClientReports(rnOptions.getBoolean("sendClientReports")); + } + if (rnOptions.hasKey("maxBreadcrumbs")) { + options.setMaxBreadcrumbs(rnOptions.getInt("maxBreadcrumbs")); + } + if (rnOptions.hasKey("maxCacheItems")) { + options.setMaxCacheItems(rnOptions.getInt("maxCacheItems")); + } + if (rnOptions.hasKey("environment") && rnOptions.getString("environment") != null) { + options.setEnvironment(rnOptions.getString("environment")); + } + if (rnOptions.hasKey("release") && rnOptions.getString("release") != null) { + options.setRelease(rnOptions.getString("release")); + } + if (rnOptions.hasKey("dist") && rnOptions.getString("dist") != null) { + options.setDist(rnOptions.getString("dist")); + } + if (rnOptions.hasKey("enableAutoSessionTracking")) { + options.setEnableAutoSessionTracking(rnOptions.getBoolean("enableAutoSessionTracking")); + } + if (rnOptions.hasKey("sessionTrackingIntervalMillis")) { + options.setSessionTrackingIntervalMillis(rnOptions.getInt("sessionTrackingIntervalMillis")); + } + if (rnOptions.hasKey("shutdownTimeout")) { + options.setShutdownTimeoutMillis(rnOptions.getInt("shutdownTimeout")); + } + if (rnOptions.hasKey("enableNdkScopeSync")) { + options.setEnableScopeSync(rnOptions.getBoolean("enableNdkScopeSync")); + } + if (rnOptions.hasKey("attachStacktrace")) { + options.setAttachStacktrace(rnOptions.getBoolean("attachStacktrace")); + } + if (rnOptions.hasKey("attachThreads")) { + // JS use top level stacktrace and android attaches Threads which hides them so + // by default we hide. + options.setAttachThreads(rnOptions.getBoolean("attachThreads")); + } + if (rnOptions.hasKey("attachScreenshot")) { + options.setAttachScreenshot(rnOptions.getBoolean("attachScreenshot")); + } + if (rnOptions.hasKey("attachViewHierarchy")) { + options.setAttachViewHierarchy(rnOptions.getBoolean("attachViewHierarchy")); + } + if (rnOptions.hasKey("sendDefaultPii")) { + options.setSendDefaultPii(rnOptions.getBoolean("sendDefaultPii")); + } + if (rnOptions.hasKey("maxQueueSize")) { + options.setMaxQueueSize(rnOptions.getInt("maxQueueSize")); + } + if (rnOptions.hasKey("enableNdk")) { + options.setEnableNdk(rnOptions.getBoolean("enableNdk")); + } + if (rnOptions.hasKey("spotlight")) { + if (rnOptions.getType("spotlight") == ReadableType.Boolean) { + options.setEnableSpotlight(rnOptions.getBoolean("spotlight")); + options.setSpotlightConnectionUrl(rnOptions.getString("defaultSidecarUrl")); + } else if (rnOptions.getType("spotlight") == ReadableType.String) { + options.setEnableSpotlight(true); + options.setSpotlightConnectionUrl(rnOptions.getString("spotlight")); + } } - private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) { - final SdkVersion replaySdkVersion = - new SdkVersion( - RNSentryVersion.REACT_NATIVE_SDK_NAME, - RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION); - @NotNull - final SentryReplayOptions androidReplayOptions = - new SentryReplayOptions(false, replaySdkVersion); - - if (!(rnOptions.hasKey("replaysSessionSampleRate") - || rnOptions.hasKey("replaysOnErrorSampleRate"))) { - return androidReplayOptions; - } - - androidReplayOptions.setSessionSampleRate( - rnOptions.hasKey("replaysSessionSampleRate") - ? rnOptions.getDouble("replaysSessionSampleRate") - : null); - androidReplayOptions.setOnErrorSampleRate( - rnOptions.hasKey("replaysOnErrorSampleRate") - ? rnOptions.getDouble("replaysOnErrorSampleRate") - : null); - - if (!rnOptions.hasKey("mobileReplayOptions")) { - return androidReplayOptions; - } - @Nullable final ReadableMap rnMobileReplayOptions = rnOptions.getMap("mobileReplayOptions"); - if (rnMobileReplayOptions == null) { - return androidReplayOptions; - } + SentryReplayOptions replayOptions = getReplayOptions(rnOptions); + options.setSessionReplay(replayOptions); + if (isReplayEnabled(replayOptions)) { + options.getReplayController().setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter()); + } - androidReplayOptions.setMaskAllText( - !rnMobileReplayOptions.hasKey("maskAllText") - || rnMobileReplayOptions.getBoolean("maskAllText")); - androidReplayOptions.setMaskAllImages( - !rnMobileReplayOptions.hasKey("maskAllImages") - || rnMobileReplayOptions.getBoolean("maskAllImages")); + // Exclude Dev Server and Sentry Dsn request from Breadcrumbs + String dsn = getURLFromDSN(rnOptions.getString("dsn")); + String devServerUrl = rnOptions.getString("devServerUrl"); + options.setBeforeBreadcrumb( + (breadcrumb, hint) -> { + Object urlObject = breadcrumb.getData("url"); + String url = urlObject instanceof String ? (String) urlObject : ""; + if ("http".equals(breadcrumb.getType()) + && ((dsn != null && url.startsWith(dsn)) + || (devServerUrl != null && url.startsWith(devServerUrl)))) { + return null; + } + return breadcrumb; + }); + + // React native internally throws a JavascriptException. + // we want to ignore it on the native side to avoid sending it twice. + options.addIgnoredExceptionForType(JavascriptException.class); + + options.setBeforeSend( + (event, hint) -> { + setEventOriginTag(event); + addPackages(event, options.getSdkVersion()); + + return event; + }); + + if (rnOptions.hasKey("enableNativeCrashHandling") + && !rnOptions.getBoolean("enableNativeCrashHandling")) { + final List integrations = options.getIntegrations(); + for (final Integration integration : integrations) { + if (integration instanceof UncaughtExceptionHandlerIntegration + || integration instanceof AnrIntegration + || integration instanceof NdkIntegration) { + integrations.remove(integration); + } + } + } + logger.log( + SentryLevel.INFO, String.format("Native Integrations '%s'", options.getIntegrations())); - final boolean redactVectors = - !rnMobileReplayOptions.hasKey("maskAllVectors") - || rnMobileReplayOptions.getBoolean("maskAllVectors"); - if (redactVectors) { - androidReplayOptions.addMaskViewClass("com.horcrux.svg.SvgView"); // react-native-svg - } + setCurrentActivity(currentActivity); + } - androidReplayOptions.setMaskViewContainerClass(RNSentryReplayMask.class.getName()); - androidReplayOptions.setUnmaskViewContainerClass(RNSentryReplayUnmask.class.getName()); + private void setCurrentActivity(Activity currentActivity) { + final CurrentActivityHolder currentActivityHolder = CurrentActivityHolder.getInstance(); + if (currentActivity != null) { + currentActivityHolder.setActivity(currentActivity); + } + } + + private boolean isReplayEnabled(SentryReplayOptions replayOptions) { + return replayOptions.getSessionSampleRate() != null + || replayOptions.getOnErrorSampleRate() != null; + } + + private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) { + final SdkVersion replaySdkVersion = + new SdkVersion( + RNSentryVersion.REACT_NATIVE_SDK_NAME, + RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION); + @NotNull + final SentryReplayOptions androidReplayOptions = + new SentryReplayOptions(false, replaySdkVersion); + + if (!(rnOptions.hasKey("replaysSessionSampleRate") + || rnOptions.hasKey("replaysOnErrorSampleRate"))) { + return androidReplayOptions; + } - return androidReplayOptions; + androidReplayOptions.setSessionSampleRate( + rnOptions.hasKey("replaysSessionSampleRate") + ? rnOptions.getDouble("replaysSessionSampleRate") + : null); + androidReplayOptions.setOnErrorSampleRate( + rnOptions.hasKey("replaysOnErrorSampleRate") + ? rnOptions.getDouble("replaysOnErrorSampleRate") + : null); + + if (!rnOptions.hasKey("mobileReplayOptions")) { + return androidReplayOptions; + } + @Nullable final ReadableMap rnMobileReplayOptions = rnOptions.getMap("mobileReplayOptions"); + if (rnMobileReplayOptions == null) { + return androidReplayOptions; } - private void setEventOriginTag(SentryEvent event) { - // We hardcode native-java as only java events are processed by the Android SDK. - SdkVersion sdk = event.getSdk(); - if (sdk != null) { - switch (sdk.getName()) { - case RNSentryVersion.NATIVE_SDK_NAME: - setEventEnvironmentTag(event, "native"); - break; - case RNSentryVersion.ANDROID_SDK_NAME: - setEventEnvironmentTag(event, "java"); - break; - default: - break; - } - } + androidReplayOptions.setMaskAllText( + !rnMobileReplayOptions.hasKey("maskAllText") + || rnMobileReplayOptions.getBoolean("maskAllText")); + androidReplayOptions.setMaskAllImages( + !rnMobileReplayOptions.hasKey("maskAllImages") + || rnMobileReplayOptions.getBoolean("maskAllImages")); + + final boolean redactVectors = + !rnMobileReplayOptions.hasKey("maskAllVectors") + || rnMobileReplayOptions.getBoolean("maskAllVectors"); + if (redactVectors) { + androidReplayOptions.addMaskViewClass("com.horcrux.svg.SvgView"); // react-native-svg } - private void setEventEnvironmentTag(SentryEvent event, String environment) { - event.setTag("event.origin", "android"); - event.setTag("event.environment", environment); + androidReplayOptions.setMaskViewContainerClass(RNSentryReplayMask.class.getName()); + androidReplayOptions.setUnmaskViewContainerClass(RNSentryReplayUnmask.class.getName()); + + return androidReplayOptions; + } + + private void setEventOriginTag(SentryEvent event) { + // We hardcode native-java as only java events are processed by the Android SDK. + SdkVersion sdk = event.getSdk(); + if (sdk != null) { + switch (sdk.getName()) { + case RNSentryVersion.NATIVE_SDK_NAME: + setEventEnvironmentTag(event, "native"); + break; + case RNSentryVersion.ANDROID_SDK_NAME: + setEventEnvironmentTag(event, "java"); + break; + default: + break; + } } + } - private void addPackages(SentryEvent event, SdkVersion sdk) { - SdkVersion eventSdk = event.getSdk(); - if (eventSdk != null - && "sentry.javascript.react-native".equals(eventSdk.getName()) - && sdk != null) { - List sentryPackages = sdk.getPackages(); - if (sentryPackages != null) { - for (SentryPackage sentryPackage : sentryPackages) { - eventSdk.addPackage(sentryPackage.getName(), sentryPackage.getVersion()); - } - } + private void setEventEnvironmentTag(SentryEvent event, String environment) { + event.setTag("event.origin", "android"); + event.setTag("event.environment", environment); + } - List integrations = sdk.getIntegrations(); - if (integrations != null) { - for (String integration : integrations) { - eventSdk.addIntegration(integration); - } - } + private void addPackages(SentryEvent event, SdkVersion sdk) { + SdkVersion eventSdk = event.getSdk(); + if (eventSdk != null + && "sentry.javascript.react-native".equals(eventSdk.getName()) + && sdk != null) { + List sentryPackages = sdk.getPackages(); + if (sentryPackages != null) { + for (SentryPackage sentryPackage : sentryPackages) { + eventSdk.addPackage(sentryPackage.getName(), sentryPackage.getVersion()); + } + } - event.setSdk(eventSdk); + List integrations = sdk.getIntegrations(); + if (integrations != null) { + for (String integration : integrations) { + eventSdk.addIntegration(integration); } + } + + event.setSdk(eventSdk); } + } - private static @Nullable String getURLFromDSN(@Nullable String dsn) { - if (dsn == null) { - return null; - } - URI uri = null; - try { - uri = new URI(dsn); - } catch (URISyntaxException e) { - return null; - } - return uri.getScheme() + "://" + uri.getHost(); + private static @Nullable String getURLFromDSN(@Nullable String dsn) { + if (dsn == null) { + return null; + } + URI uri = null; + try { + uri = new URI(dsn); + } catch (URISyntaxException e) { + return null; } + return uri.getScheme() + "://" + uri.getHost(); + } } From 739ad307b9022fa97f5b95b0cf102f9c34cfec3c Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Wed, 15 Jan 2025 15:33:49 +0200 Subject: [PATCH 5/5] Rename RNSentryStart instance for clarity --- .../src/main/java/io/sentry/react/RNSentryModuleImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 6f655e3c2..92acbe461 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -117,14 +117,14 @@ public class RNSentryModuleImpl { private final @NotNull SentryDateProvider dateProvider; - private final @NotNull RNSentryStart sdk; + private final @NotNull RNSentryStart startSdk; public RNSentryModuleImpl(ReactApplicationContext reactApplicationContext) { packageInfo = getPackageInfo(reactApplicationContext); this.reactApplicationContext = reactApplicationContext; this.emitNewFrameEvent = createEmitNewFrameEvent(); this.dateProvider = new SentryAndroidDateProvider(); - this.sdk = new RNSentryStart(); + this.startSdk = new RNSentryStart(); } private ReactApplicationContext getReactApplicationContext() { @@ -165,7 +165,7 @@ public void initNativeReactNavigationNewFrameTracking(Promise promise) { } public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { - sdk.startWithOptions( + startSdk.startWithOptions( this.getReactApplicationContext(), rnOptions, getCurrentActivity(), logger); promise.resolve(true);