diff --git a/package.json b/package.json index 7ce20b4e7..469dd8853 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,12 @@ "clean": "lerna run clean", "circularDepCheck": "lerna run circularDepCheck", "test": "lerna run test", - "fix": "lerna run fix", - "lint": "lerna run lint", + "fix": "run-s fix:lerna fix:android", + "fix:lerna": "lerna run fix", + "fix:android": "./scripts/google-java-format.sh fix", + "lint": "run-s lint:lerna lint:android", + "lint:lerna": "lerna run lint", + "lint:android": "./scripts/google-java-format.sh lint", "run-ios": "cd samples/react-native && yarn react-native run-ios", "run-android": "cd samples/react-native && yarn react-native run-android", "set-version-samples": "lerna run set-version" @@ -16,6 +20,7 @@ "devDependencies": { "@sentry/cli": "2.37.0", "downlevel-dts": "^0.11.0", + "google-java-format": "^1.4.0", "lerna": "^8.1.8", "npm-run-all2": "^6.2.2", "prettier": "^2.0.5", diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java index 3fcfffcf8..a8041fe82 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java @@ -1,87 +1,84 @@ package io.sentry.react; import com.facebook.react.bridge.ReadableMap; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; - import io.sentry.Breadcrumb; import io.sentry.SentryLevel; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class RNSentryBreadcrumb { - @Nullable - public static String getCurrentScreenFrom(ReadableMap from) { - final @Nullable String maybeCategory = from.hasKey("category") ? from.getString("category") : null; - if (maybeCategory == null || !maybeCategory.equals("navigation")) { - return null; - } - - final @Nullable ReadableMap maybeData = from.hasKey("data") ? from.getMap("data") : null; - if (maybeData == null) { - return null; - } + @Nullable + public static String getCurrentScreenFrom(ReadableMap from) { + final @Nullable String maybeCategory = + from.hasKey("category") ? from.getString("category") : null; + if (maybeCategory == null || !maybeCategory.equals("navigation")) { + return null; + } - try { - // getString might throw if cast to string fails (data.to is not enforced by TS to be a string) - return maybeData.hasKey("to") ? maybeData.getString("to") : null; - } catch (Throwable exception) { - return null; - } + final @Nullable ReadableMap maybeData = from.hasKey("data") ? from.getMap("data") : null; + if (maybeData == null) { + return null; } - @NotNull - public static Breadcrumb fromMap(ReadableMap from) { - final @NotNull Breadcrumb breadcrumb = new Breadcrumb(); + try { + // getString might throw if cast to string fails (data.to is not enforced by TS to be a + // string) + return maybeData.hasKey("to") ? maybeData.getString("to") : null; + } catch (Throwable exception) { + return null; + } + } - if (from.hasKey("message")) { - breadcrumb.setMessage(from.getString("message")); - } + @NotNull + public static Breadcrumb fromMap(ReadableMap from) { + final @NotNull Breadcrumb breadcrumb = new Breadcrumb(); - if (from.hasKey("type")) { - breadcrumb.setType(from.getString("type")); - } + if (from.hasKey("message")) { + breadcrumb.setMessage(from.getString("message")); + } - if (from.hasKey("category")) { - breadcrumb.setCategory(from.getString("category")); - } + if (from.hasKey("type")) { + breadcrumb.setType(from.getString("type")); + } - if (from.hasKey("level")) { - switch (from.getString("level")) { - case "fatal": - breadcrumb.setLevel(SentryLevel.FATAL); - break; - case "warning": - breadcrumb.setLevel(SentryLevel.WARNING); - break; - case "debug": - breadcrumb.setLevel(SentryLevel.DEBUG); - break; - case "error": - breadcrumb.setLevel(SentryLevel.ERROR); - break; - case "info": - default: - breadcrumb.setLevel(SentryLevel.INFO); - break; - } - } + if (from.hasKey("category")) { + breadcrumb.setCategory(from.getString("category")); + } + if (from.hasKey("level")) { + switch (from.getString("level")) { + case "fatal": + breadcrumb.setLevel(SentryLevel.FATAL); + break; + case "warning": + breadcrumb.setLevel(SentryLevel.WARNING); + break; + case "debug": + breadcrumb.setLevel(SentryLevel.DEBUG); + break; + case "error": + breadcrumb.setLevel(SentryLevel.ERROR); + break; + case "info": + default: + breadcrumb.setLevel(SentryLevel.INFO); + break; + } + } - if (from.hasKey("data")) { - final ReadableMap data = from.getMap("data"); - for (final Map.Entry entry : data.toHashMap().entrySet()) { - final Object value = entry.getValue(); - // data is ConcurrentHashMap and can't have null values - if (value != null) { - breadcrumb.setData(entry.getKey(), entry.getValue()); - } - } + if (from.hasKey("data")) { + final ReadableMap data = from.getMap("data"); + for (final Map.Entry entry : data.toHashMap().entrySet()) { + final Object value = entry.getValue(); + // data is ConcurrentHashMap and can't have null values + if (value != null) { + breadcrumb.setData(entry.getKey(), entry.getValue()); } - - return breadcrumb; + } } + return breadcrumb; + } } diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryMapConverter.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryMapConverter.java index d7dec9177..6bfbbf5c7 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryMapConverter.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryMapConverter.java @@ -5,130 +5,126 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableMap; - -import org.jetbrains.annotations.Nullable; - +import io.sentry.ILogger; +import io.sentry.SentryLevel; +import io.sentry.android.core.AndroidLogger; import java.math.BigDecimal; import java.math.BigInteger; import java.util.List; import java.util.Map; - -import io.sentry.ILogger; -import io.sentry.SentryLevel; -import io.sentry.android.core.AndroidLogger; +import org.jetbrains.annotations.Nullable; public class RNSentryMapConverter { - public static final String NAME = "RNSentry.MapConverter"; + public static final String NAME = "RNSentry.MapConverter"; - private static final ILogger logger = new AndroidLogger(NAME); + private static final ILogger logger = new AndroidLogger(NAME); - public static Object convertToWritable(@Nullable Object serialized) { - if (serialized instanceof List) { - WritableArray writable = Arguments.createArray(); - for (Object item : (List) serialized) { - addValueToWritableArray(writable, convertToWritable(item)); - } - return writable; - } else if (serialized instanceof Map) { - WritableMap writable = Arguments.createMap(); - for (Map.Entry entry : ((Map) serialized).entrySet()) { - Object key = entry.getKey(); - Object value = entry.getValue(); + public static Object convertToWritable(@Nullable Object serialized) { + if (serialized instanceof List) { + WritableArray writable = Arguments.createArray(); + for (Object item : (List) serialized) { + addValueToWritableArray(writable, convertToWritable(item)); + } + return writable; + } else if (serialized instanceof Map) { + WritableMap writable = Arguments.createMap(); + for (Map.Entry entry : ((Map) serialized).entrySet()) { + Object key = entry.getKey(); + Object value = entry.getValue(); - if (key instanceof String) { - addValueToWritableMap(writable, (String) key, convertToWritable(value)); - } else { - logger.log(SentryLevel.ERROR, "Only String keys are supported in Map.", key); - } - } - return writable; - } else if (serialized instanceof Byte) { - return Integer.valueOf((Byte) serialized); - } else if (serialized instanceof Short) { - return Integer.valueOf((Short) serialized); - } else if (serialized instanceof Float) { - return Double.valueOf((Float) serialized); - } else if (serialized instanceof Long) { - return Double.valueOf((Long) serialized); - } else if (serialized instanceof BigInteger) { - return ((BigInteger) serialized).doubleValue(); - } else if (serialized instanceof BigDecimal) { - return ((BigDecimal) serialized).doubleValue(); - } else if (serialized instanceof Integer - || serialized instanceof Double - || serialized instanceof Boolean - || serialized == null - || serialized instanceof String) { - return serialized; + if (key instanceof String) { + addValueToWritableMap(writable, (String) key, convertToWritable(value)); } else { - logger.log(SentryLevel.ERROR, "Supplied serialized value could not be converted." + serialized); - return null; + logger.log(SentryLevel.ERROR, "Only String keys are supported in Map.", key); } + } + return writable; + } else if (serialized instanceof Byte) { + return Integer.valueOf((Byte) serialized); + } else if (serialized instanceof Short) { + return Integer.valueOf((Short) serialized); + } else if (serialized instanceof Float) { + return Double.valueOf((Float) serialized); + } else if (serialized instanceof Long) { + return Double.valueOf((Long) serialized); + } else if (serialized instanceof BigInteger) { + return ((BigInteger) serialized).doubleValue(); + } else if (serialized instanceof BigDecimal) { + return ((BigDecimal) serialized).doubleValue(); + } else if (serialized instanceof Integer + || serialized instanceof Double + || serialized instanceof Boolean + || serialized == null + || serialized instanceof String) { + return serialized; + } else { + logger.log( + SentryLevel.ERROR, "Supplied serialized value could not be converted." + serialized); + return null; } + } - private static void addValueToWritableArray(WritableArray writableArray, Object value) { - if (value == null) { - writableArray.pushNull(); - } else if (value instanceof Boolean) { - writableArray.pushBoolean((Boolean) value); - } else if (value instanceof Double) { - writableArray.pushDouble((Double) value); - } else if (value instanceof Float) { - writableArray.pushDouble(((Float) value).doubleValue()); - } else if (value instanceof Integer) { - writableArray.pushInt((Integer) value); - } else if (value instanceof Short) { - writableArray.pushInt(((Short) value).intValue()); - } else if (value instanceof Byte) { - writableArray.pushInt(((Byte) value).intValue()); - } else if (value instanceof Long) { - writableArray.pushDouble(((Long) value).doubleValue()); - } else if (value instanceof BigInteger) { - writableArray.pushDouble(((BigInteger) value).doubleValue()); - } else if (value instanceof BigDecimal) { - writableArray.pushDouble(((BigDecimal) value).doubleValue()); - } else if (value instanceof String) { - writableArray.pushString((String) value); - } else if (value instanceof ReadableMap) { - writableArray.pushMap((ReadableMap) value); - } else if (value instanceof ReadableArray) { - writableArray.pushArray((ReadableArray) value); - } else { - logger.log(SentryLevel.ERROR, - "Could not convert object: " + value); - } + private static void addValueToWritableArray(WritableArray writableArray, Object value) { + if (value == null) { + writableArray.pushNull(); + } else if (value instanceof Boolean) { + writableArray.pushBoolean((Boolean) value); + } else if (value instanceof Double) { + writableArray.pushDouble((Double) value); + } else if (value instanceof Float) { + writableArray.pushDouble(((Float) value).doubleValue()); + } else if (value instanceof Integer) { + writableArray.pushInt((Integer) value); + } else if (value instanceof Short) { + writableArray.pushInt(((Short) value).intValue()); + } else if (value instanceof Byte) { + writableArray.pushInt(((Byte) value).intValue()); + } else if (value instanceof Long) { + writableArray.pushDouble(((Long) value).doubleValue()); + } else if (value instanceof BigInteger) { + writableArray.pushDouble(((BigInteger) value).doubleValue()); + } else if (value instanceof BigDecimal) { + writableArray.pushDouble(((BigDecimal) value).doubleValue()); + } else if (value instanceof String) { + writableArray.pushString((String) value); + } else if (value instanceof ReadableMap) { + writableArray.pushMap((ReadableMap) value); + } else if (value instanceof ReadableArray) { + writableArray.pushArray((ReadableArray) value); + } else { + logger.log(SentryLevel.ERROR, "Could not convert object: " + value); } + } - private static void addValueToWritableMap(WritableMap writableMap, String key, Object value) { - if (value == null) { - writableMap.putNull(key); - } else if (value instanceof Boolean) { - writableMap.putBoolean(key, (Boolean) value); - } else if (value instanceof Double) { - writableMap.putDouble(key, (Double) value); - } else if (value instanceof Float) { - writableMap.putDouble(key, ((Float) value).doubleValue()); - } else if (value instanceof Integer) { - writableMap.putInt(key, (Integer) value); - } else if (value instanceof Short) { - writableMap.putInt(key, ((Short) value).intValue()); - } else if (value instanceof Byte) { - writableMap.putInt(key, ((Byte) value).intValue()); - } else if (value instanceof Long) { - writableMap.putDouble(key, ((Long) value).doubleValue()); - } else if (value instanceof BigInteger) { - writableMap.putDouble(key, ((BigInteger) value).doubleValue()); - } else if (value instanceof BigDecimal) { - writableMap.putDouble(key, ((BigDecimal) value).doubleValue()); - } else if (value instanceof String) { - writableMap.putString(key, (String) value); - } else if (value instanceof ReadableArray) { - writableMap.putArray(key, (ReadableArray) value); - } else if (value instanceof ReadableMap) { - writableMap.putMap(key, (ReadableMap) value); - } else { - logger.log(SentryLevel.ERROR, - "Could not convert object" + value); - } + private static void addValueToWritableMap(WritableMap writableMap, String key, Object value) { + if (value == null) { + writableMap.putNull(key); + } else if (value instanceof Boolean) { + writableMap.putBoolean(key, (Boolean) value); + } else if (value instanceof Double) { + writableMap.putDouble(key, (Double) value); + } else if (value instanceof Float) { + writableMap.putDouble(key, ((Float) value).doubleValue()); + } else if (value instanceof Integer) { + writableMap.putInt(key, (Integer) value); + } else if (value instanceof Short) { + writableMap.putInt(key, ((Short) value).intValue()); + } else if (value instanceof Byte) { + writableMap.putInt(key, ((Byte) value).intValue()); + } else if (value instanceof Long) { + writableMap.putDouble(key, ((Long) value).doubleValue()); + } else if (value instanceof BigInteger) { + writableMap.putDouble(key, ((BigInteger) value).doubleValue()); + } else if (value instanceof BigDecimal) { + writableMap.putDouble(key, ((BigDecimal) value).doubleValue()); + } else if (value instanceof String) { + writableMap.putString(key, (String) value); + } else if (value instanceof ReadableArray) { + writableMap.putArray(key, (ReadableArray) value); + } else if (value instanceof ReadableMap) { + writableMap.putMap(key, (ReadableMap) value); + } else { + logger.log(SentryLevel.ERROR, "Could not convert object" + value); } + } } 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 ecb41f166..16014c5de 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 @@ -1,9 +1,9 @@ package io.sentry.react; -import static java.util.concurrent.TimeUnit.SECONDS; import static io.sentry.android.core.internal.util.ScreenshotUtils.takeScreenshot; import static io.sentry.vendor.Base64.NO_PADDING; import static io.sentry.vendor.Base64.NO_WRAP; +import static java.util.concurrent.TimeUnit.SECONDS; import android.app.Activity; import android.content.Context; @@ -11,11 +11,9 @@ import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.util.SparseIntArray; - import androidx.core.app.FrameMetricsAggregator; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; - import com.facebook.hermes.instrumentation.HermesSamplingProfiler; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.Promise; @@ -28,32 +26,10 @@ import com.facebook.react.bridge.WritableNativeArray; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.modules.core.DeviceEventManagerModule; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.concurrent.CountDownLatch; - -import io.sentry.Breadcrumb; -import io.sentry.DateUtils; import io.sentry.HubAdapter; import io.sentry.ILogger; -import io.sentry.ISentryExecutorService; import io.sentry.IScope; +import io.sentry.ISentryExecutorService; import io.sentry.ISerializer; import io.sentry.Integration; import io.sentry.Sentry; @@ -87,856 +63,917 @@ import io.sentry.protocol.User; import io.sentry.protocol.ViewHierarchy; import io.sentry.util.DebugMetaPropertiesApplier; +import io.sentry.util.FileUtils; import io.sentry.util.JsonSerializationUtils; import io.sentry.vendor.Base64; -import io.sentry.util.FileUtils; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.CountDownLatch; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class RNSentryModuleImpl { - public static final String NAME = "RNSentry"; - - private static final String NATIVE_SDK_NAME = "sentry.native.android.react-native"; - private static final String ANDROID_SDK_NAME = "sentry.java.android.react-native"; - private static final ILogger logger = new AndroidLogger(NAME); - private static final BuildInfoProvider buildInfo = new BuildInfoProvider(logger); - private static final String modulesPath = "modules.json"; - private static final Charset UTF_8 = Charset.forName("UTF-8"); - - private final ReactApplicationContext reactApplicationContext; - private final PackageInfo packageInfo; - private FrameMetricsAggregator frameMetricsAggregator = null; - private boolean androidXAvailable; - - private static boolean hasFetchedAppStart; - - // 700ms to constitute frozen frames. - private static final int FROZEN_FRAME_THRESHOLD = 700; - // 16ms (slower than 60fps) to constitute slow frames. - private static final int SLOW_FRAME_THRESHOLD = 16; - - private static final int SCREENSHOT_TIMEOUT_SECONDS = 2; - - /** - * Profiling traces rate. 101 hz means 101 traces in 1 second. Defaults to 101 to avoid possible - * lockstep sampling. More on - * https://stackoverflow.com/questions/45470758/what-is-lockstep-sampling - */ - private int profilingTracesHz = 101; - - private AndroidProfiler androidProfiler = null; - - private boolean isProguardDebugMetaLoaded = false; - private @Nullable String proguardUuid = null; - private String cacheDirPath = null; - private ISentryExecutorService executorService = null; - - private final @NotNull Runnable emitNewFrameEvent; - - /** Max trace file size in bytes. */ - private long maxTraceFileSize = 5 * 1024 * 1024; - - public RNSentryModuleImpl(ReactApplicationContext reactApplicationContext) { - packageInfo = getPackageInfo(reactApplicationContext); - this.reactApplicationContext = reactApplicationContext; - this.emitNewFrameEvent = createEmitNewFrameEvent(); - } - - private ReactApplicationContext getReactApplicationContext() { - return this.reactApplicationContext; - } - - private @Nullable Activity getCurrentActivity() { - return this.reactApplicationContext.getCurrentActivity(); - } - - private @NotNull Runnable createEmitNewFrameEvent() { - final @NotNull SentryDateProvider dateProvider = new SentryAndroidDateProvider(); - - return () -> { - final SentryDate endDate = dateProvider.now(); - WritableMap event = Arguments.createMap(); - event.putDouble("newFrameTimestampInSeconds", endDate.nanoTimestamp() / 1e9); - getReactApplicationContext() - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit("rn_sentry_new_frame", event); - }; - } - - private void initFragmentInitialFrameTracking() { - final RNSentryReactFragmentLifecycleTracer fragmentLifecycleTracer = - new RNSentryReactFragmentLifecycleTracer(buildInfo, emitNewFrameEvent, logger); - - final @Nullable FragmentActivity fragmentActivity = (FragmentActivity) getCurrentActivity(); - if (fragmentActivity != null) { - final @Nullable FragmentManager supportFragmentManager = fragmentActivity.getSupportFragmentManager(); - if (supportFragmentManager != null) { - supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleTracer, true); - } - } - } - - public void initNativeReactNavigationNewFrameTracking(Promise promise) { - this.initFragmentInitialFrameTracking(); - } - - public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { - SentryAndroid.init(this.getReactApplicationContext(), options -> { - @Nullable SdkVersion sdkVersion = options.getSdkVersion(); - if (sdkVersion == null) { - sdkVersion = new SdkVersion(ANDROID_SDK_NAME, BuildConfig.VERSION_NAME); - } else { - sdkVersion.setName(ANDROID_SDK_NAME); - } - - options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion()); - options.setNativeSdkName(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("_experiments")) { - options.getExperimental().setSessionReplay(getReplayOptions(rnOptions)); - options.getReplayController().setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter()); - } - options.setBeforeSend((event, hint) -> { + public static final String NAME = "RNSentry"; + + private static final String NATIVE_SDK_NAME = "sentry.native.android.react-native"; + private static final String ANDROID_SDK_NAME = "sentry.java.android.react-native"; + private static final ILogger logger = new AndroidLogger(NAME); + private static final BuildInfoProvider buildInfo = new BuildInfoProvider(logger); + private static final String modulesPath = "modules.json"; + private static final Charset UTF_8 = Charset.forName("UTF-8"); + + private final ReactApplicationContext reactApplicationContext; + private final PackageInfo packageInfo; + private FrameMetricsAggregator frameMetricsAggregator = null; + private boolean androidXAvailable; + + private static boolean hasFetchedAppStart; + + // 700ms to constitute frozen frames. + private static final int FROZEN_FRAME_THRESHOLD = 700; + // 16ms (slower than 60fps) to constitute slow frames. + private static final int SLOW_FRAME_THRESHOLD = 16; + + private static final int SCREENSHOT_TIMEOUT_SECONDS = 2; + + /** + * Profiling traces rate. 101 hz means 101 traces in 1 second. Defaults to 101 to avoid possible + * lockstep sampling. More on + * https://stackoverflow.com/questions/45470758/what-is-lockstep-sampling + */ + private int profilingTracesHz = 101; + + private AndroidProfiler androidProfiler = null; + + private boolean isProguardDebugMetaLoaded = false; + private @Nullable String proguardUuid = null; + private String cacheDirPath = null; + private ISentryExecutorService executorService = null; + + private final @NotNull Runnable emitNewFrameEvent; + + /** Max trace file size in bytes. */ + private long maxTraceFileSize = 5 * 1024 * 1024; + + public RNSentryModuleImpl(ReactApplicationContext reactApplicationContext) { + packageInfo = getPackageInfo(reactApplicationContext); + this.reactApplicationContext = reactApplicationContext; + this.emitNewFrameEvent = createEmitNewFrameEvent(); + } + + private ReactApplicationContext getReactApplicationContext() { + return this.reactApplicationContext; + } + + private @Nullable Activity getCurrentActivity() { + return this.reactApplicationContext.getCurrentActivity(); + } + + private @NotNull Runnable createEmitNewFrameEvent() { + final @NotNull SentryDateProvider dateProvider = new SentryAndroidDateProvider(); + + return () -> { + final SentryDate endDate = dateProvider.now(); + WritableMap event = Arguments.createMap(); + event.putDouble("newFrameTimestampInSeconds", endDate.nanoTimestamp() / 1e9); + getReactApplicationContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit("rn_sentry_new_frame", event); + }; + } + + private void initFragmentInitialFrameTracking() { + final RNSentryReactFragmentLifecycleTracer fragmentLifecycleTracer = + new RNSentryReactFragmentLifecycleTracer(buildInfo, emitNewFrameEvent, logger); + + final @Nullable FragmentActivity fragmentActivity = (FragmentActivity) getCurrentActivity(); + if (fragmentActivity != null) { + final @Nullable FragmentManager supportFragmentManager = + fragmentActivity.getSupportFragmentManager(); + if (supportFragmentManager != null) { + supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleTracer, true); + } + } + } + + public void initNativeReactNavigationNewFrameTracking(Promise promise) { + this.initFragmentInitialFrameTracking(); + } + + public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { + SentryAndroid.init( + this.getReactApplicationContext(), + options -> { + @Nullable SdkVersion sdkVersion = options.getSdkVersion(); + if (sdkVersion == null) { + sdkVersion = new SdkVersion(ANDROID_SDK_NAME, BuildConfig.VERSION_NAME); + } else { + sdkVersion.setName(ANDROID_SDK_NAME); + } + + options.setSentryClientName(sdkVersion.getName() + "/" + sdkVersion.getVersion()); + options.setNativeSdkName(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("_experiments")) { + options.getExperimental().setSessionReplay(getReplayOptions(rnOptions)); + options + .getReplayController() + .setBreadcrumbConverter(new RNSentryReplayBreadcrumbConverter()); + } + options.setBeforeSend( + (event, hint) -> { // React native internally throws a JavascriptException // Since we catch it before that, we don't want to send this one // because we would send it twice try { - SentryException ex = event.getExceptions().get(0); - if (null != ex && ex.getType().contains("JavascriptException")) { - return null; - } + SentryException ex = event.getExceptions().get(0); + if (null != ex && ex.getType().contains("JavascriptException")) { + return null; + } } catch (Throwable ignored) { - // We do nothing + // We do nothing } 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); - } + }); + + 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); + } }); - promise.resolve(true); - } - - private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) { - @NotNull final SentryReplayOptions androidReplayOptions = new SentryReplayOptions(false); - - @Nullable final ReadableMap rnExperimentsOptions = rnOptions.getMap("_experiments"); - if (rnExperimentsOptions == null) { - return androidReplayOptions; - } - - if (!(rnExperimentsOptions.hasKey("replaysSessionSampleRate") || rnExperimentsOptions.hasKey("replaysOnErrorSampleRate"))) { - return androidReplayOptions; - } - - androidReplayOptions.setSessionSampleRate(rnExperimentsOptions.hasKey("replaysSessionSampleRate") - ? rnExperimentsOptions.getDouble("replaysSessionSampleRate") : null); - androidReplayOptions.setOnErrorSampleRate(rnExperimentsOptions.hasKey("replaysOnErrorSampleRate") - ? rnExperimentsOptions.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 - } - - return androidReplayOptions; - } - - public void crash() { - throw new RuntimeException("TEST - Sentry Client Crash (only works in release mode)"); - } - - public void addListener(String _eventType) { - // Is must be defined otherwise the generated interface from TS won't be fulfilled - logger.log(SentryLevel.ERROR, "addListener of NativeEventEmitter can't be used on Android!"); - } - - public void removeListeners(double _id) { - // Is must be defined otherwise the generated interface from TS won't be fulfilled - logger.log(SentryLevel.ERROR, "removeListeners of NativeEventEmitter can't be used on Android!"); - } - - public void fetchModules(Promise promise) { - final AssetManager assets = this.getReactApplicationContext().getResources().getAssets(); - try (final InputStream stream = - new BufferedInputStream(assets.open(RNSentryModuleImpl.modulesPath))) { - int size = stream.available(); - byte[] buffer = new byte[size]; - stream.read(buffer); - stream.close(); - String modulesJson = new String(buffer, RNSentryModuleImpl.UTF_8); - promise.resolve(modulesJson); - } catch (FileNotFoundException e) { - promise.resolve(null); - } catch (Throwable e) { - logger.log(SentryLevel.WARNING, "Fetching JS Modules failed."); - promise.resolve(null); - } - } - - public void fetchNativeRelease(Promise promise) { - WritableMap release = Arguments.createMap(); - release.putString("id", packageInfo.packageName); - release.putString("version", packageInfo.versionName); - release.putString("build", String.valueOf(packageInfo.versionCode)); - promise.resolve(release); - } - - public void fetchNativeAppStart(Promise promise) { - fetchNativeAppStart(promise, InternalSentrySdk.getAppStartMeasurement(), logger, AppStartMetrics.getInstance().isAppLaunchedInForeground()); - } - - protected void fetchNativeAppStart(Promise promise, final Map appStartMeasurement, ILogger logger, boolean isAppLaunchedInForeground) { - if (!isAppLaunchedInForeground) { - logger.log(SentryLevel.WARNING, "Invalid app start data: app not launched in foreground."); - promise.resolve(null); - return; - } - - WritableMap mutableMeasurement = (WritableMap) RNSentryMapConverter.convertToWritable(appStartMeasurement); - mutableMeasurement.putBoolean("has_fetched", hasFetchedAppStart); - - // This is always set to true, as we would only allow an app start fetch to only - // happen once in the case of a JS bundle reload, we do not want it to be - // instrumented again. - hasFetchedAppStart = true; - - promise.resolve(mutableMeasurement); - } - - /** - * Returns frames metrics at the current point in time. - */ - public void fetchNativeFrames(Promise promise) { - if (!isFrameMetricsAggregatorAvailable()) { - promise.resolve(null); - } else { - try { - int totalFrames = 0; - int slowFrames = 0; - int frozenFrames = 0; - - final SparseIntArray[] framesRates = frameMetricsAggregator.getMetrics(); - - if (framesRates != null) { - final SparseIntArray totalIndexArray = framesRates[FrameMetricsAggregator.TOTAL_INDEX]; - if (totalIndexArray != null) { - for (int i = 0; i < totalIndexArray.size(); i++) { - int frameTime = totalIndexArray.keyAt(i); - int numFrames = totalIndexArray.valueAt(i); - totalFrames += numFrames; - // hard coded values, its also in the official android docs and frame metrics - // API - if (frameTime > FROZEN_FRAME_THRESHOLD) { - // frozen frames, threshold is 700ms - frozenFrames += numFrames; - } else if (frameTime > SLOW_FRAME_THRESHOLD) { - // slow frames, above 16ms, 60 frames/second - slowFrames += numFrames; - } - } - } - } - - WritableMap map = Arguments.createMap(); - map.putInt("totalFrames", totalFrames); - map.putInt("slowFrames", slowFrames); - map.putInt("frozenFrames", frozenFrames); - - promise.resolve(map); - } catch (Throwable ignored) { - logger.log(SentryLevel.WARNING, "Error fetching native frames."); - promise.resolve(null); - } - } - } - - public void captureReplay(boolean isHardCrash, Promise promise) { - Sentry.getCurrentHub().getOptions().getReplayController().captureReplay(isHardCrash); - promise.resolve(getCurrentReplayId()); - } - - public @Nullable String getCurrentReplayId() { - final @Nullable IScope scope = InternalSentrySdk.getCurrentScope(); - if (scope == null) { - return null; - } - - final @NotNull SentryId id = scope.getReplayId(); - if (id == SentryId.EMPTY_ID) { - return null; - } - return id.toString(); - } - - public void captureEnvelope(String rawBytes, ReadableMap options, Promise promise) { - byte[] bytes = Base64.decode(rawBytes, Base64.DEFAULT); - - try { - InternalSentrySdk.captureEnvelope(bytes, !options.hasKey("hardCrashed") || !options.getBoolean("hardCrashed")); - } catch (Throwable e) { - logger.log(SentryLevel.ERROR, "Error while capturing envelope"); - promise.resolve(false); - } - promise.resolve(true); - } - - public void captureScreenshot(Promise promise) { - - final Activity activity = getCurrentActivity(); - if (activity == null) { - logger.log(SentryLevel.WARNING, "CurrentActivity is null, can't capture screenshot."); - promise.resolve(null); - return; - } - - final byte[] raw = takeScreenshotOnUiThread(activity); - - if (raw == null) { - logger.log(SentryLevel.WARNING, "Screenshot is null, screen was not captured."); - promise.resolve(null); - return; - } - - final WritableNativeArray data = new WritableNativeArray(); - for (final byte b : raw) { - data.pushInt(b); - } - final WritableMap screenshot = new WritableNativeMap(); - screenshot.putString("contentType", "image/png"); - screenshot.putArray("data", data); - screenshot.putString("filename", "screenshot.png"); - - final WritableArray screenshotsArray = new WritableNativeArray(); - screenshotsArray.pushMap(screenshot); - promise.resolve(screenshotsArray); - } - - private static byte[] takeScreenshotOnUiThread(Activity activity) { - CountDownLatch doneSignal = new CountDownLatch(1); - final byte[][] bytesWrapper = {{}}; // wrapper to be able to set the value in the runnable - final Runnable runTakeScreenshot = () -> { - bytesWrapper[0] = takeScreenshot(activity, logger, buildInfo); - doneSignal.countDown(); + promise.resolve(true); + } + + private SentryReplayOptions getReplayOptions(@NotNull ReadableMap rnOptions) { + @NotNull final SentryReplayOptions androidReplayOptions = new SentryReplayOptions(false); + + @Nullable final ReadableMap rnExperimentsOptions = rnOptions.getMap("_experiments"); + if (rnExperimentsOptions == null) { + return androidReplayOptions; + } + + if (!(rnExperimentsOptions.hasKey("replaysSessionSampleRate") + || rnExperimentsOptions.hasKey("replaysOnErrorSampleRate"))) { + return androidReplayOptions; + } + + androidReplayOptions.setSessionSampleRate( + rnExperimentsOptions.hasKey("replaysSessionSampleRate") + ? rnExperimentsOptions.getDouble("replaysSessionSampleRate") + : null); + androidReplayOptions.setOnErrorSampleRate( + rnExperimentsOptions.hasKey("replaysOnErrorSampleRate") + ? rnExperimentsOptions.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 + } + + return androidReplayOptions; + } + + public void crash() { + throw new RuntimeException("TEST - Sentry Client Crash (only works in release mode)"); + } + + public void addListener(String _eventType) { + // Is must be defined otherwise the generated interface from TS won't be fulfilled + logger.log(SentryLevel.ERROR, "addListener of NativeEventEmitter can't be used on Android!"); + } + + public void removeListeners(double _id) { + // Is must be defined otherwise the generated interface from TS won't be fulfilled + logger.log( + SentryLevel.ERROR, "removeListeners of NativeEventEmitter can't be used on Android!"); + } + + public void fetchModules(Promise promise) { + final AssetManager assets = this.getReactApplicationContext().getResources().getAssets(); + try (final InputStream stream = + new BufferedInputStream(assets.open(RNSentryModuleImpl.modulesPath))) { + int size = stream.available(); + byte[] buffer = new byte[size]; + stream.read(buffer); + stream.close(); + String modulesJson = new String(buffer, RNSentryModuleImpl.UTF_8); + promise.resolve(modulesJson); + } catch (FileNotFoundException e) { + promise.resolve(null); + } catch (Throwable e) { + logger.log(SentryLevel.WARNING, "Fetching JS Modules failed."); + promise.resolve(null); + } + } + + public void fetchNativeRelease(Promise promise) { + WritableMap release = Arguments.createMap(); + release.putString("id", packageInfo.packageName); + release.putString("version", packageInfo.versionName); + release.putString("build", String.valueOf(packageInfo.versionCode)); + promise.resolve(release); + } + + public void fetchNativeAppStart(Promise promise) { + fetchNativeAppStart( + promise, + InternalSentrySdk.getAppStartMeasurement(), + logger, + AppStartMetrics.getInstance().isAppLaunchedInForeground()); + } + + protected void fetchNativeAppStart( + Promise promise, + final Map appStartMeasurement, + ILogger logger, + boolean isAppLaunchedInForeground) { + if (!isAppLaunchedInForeground) { + logger.log(SentryLevel.WARNING, "Invalid app start data: app not launched in foreground."); + promise.resolve(null); + return; + } + + WritableMap mutableMeasurement = + (WritableMap) RNSentryMapConverter.convertToWritable(appStartMeasurement); + mutableMeasurement.putBoolean("has_fetched", hasFetchedAppStart); + + // This is always set to true, as we would only allow an app start fetch to only + // happen once in the case of a JS bundle reload, we do not want it to be + // instrumented again. + hasFetchedAppStart = true; + + promise.resolve(mutableMeasurement); + } + + /** Returns frames metrics at the current point in time. */ + public void fetchNativeFrames(Promise promise) { + if (!isFrameMetricsAggregatorAvailable()) { + promise.resolve(null); + } else { + try { + int totalFrames = 0; + int slowFrames = 0; + int frozenFrames = 0; + + final SparseIntArray[] framesRates = frameMetricsAggregator.getMetrics(); + + if (framesRates != null) { + final SparseIntArray totalIndexArray = framesRates[FrameMetricsAggregator.TOTAL_INDEX]; + if (totalIndexArray != null) { + for (int i = 0; i < totalIndexArray.size(); i++) { + int frameTime = totalIndexArray.keyAt(i); + int numFrames = totalIndexArray.valueAt(i); + totalFrames += numFrames; + // hard coded values, its also in the official android docs and frame metrics + // API + if (frameTime > FROZEN_FRAME_THRESHOLD) { + // frozen frames, threshold is 700ms + frozenFrames += numFrames; + } else if (frameTime > SLOW_FRAME_THRESHOLD) { + // slow frames, above 16ms, 60 frames/second + slowFrames += numFrames; + } + } + } + } + + WritableMap map = Arguments.createMap(); + map.putInt("totalFrames", totalFrames); + map.putInt("slowFrames", slowFrames); + map.putInt("frozenFrames", frozenFrames); + + promise.resolve(map); + } catch (Throwable ignored) { + logger.log(SentryLevel.WARNING, "Error fetching native frames."); + promise.resolve(null); + } + } + } + + public void captureReplay(boolean isHardCrash, Promise promise) { + Sentry.getCurrentHub().getOptions().getReplayController().captureReplay(isHardCrash); + promise.resolve(getCurrentReplayId()); + } + + public @Nullable String getCurrentReplayId() { + final @Nullable IScope scope = InternalSentrySdk.getCurrentScope(); + if (scope == null) { + return null; + } + + final @NotNull SentryId id = scope.getReplayId(); + if (id == SentryId.EMPTY_ID) { + return null; + } + return id.toString(); + } + + public void captureEnvelope(String rawBytes, ReadableMap options, Promise promise) { + byte[] bytes = Base64.decode(rawBytes, Base64.DEFAULT); + + try { + InternalSentrySdk.captureEnvelope( + bytes, !options.hasKey("hardCrashed") || !options.getBoolean("hardCrashed")); + } catch (Throwable e) { + logger.log(SentryLevel.ERROR, "Error while capturing envelope"); + promise.resolve(false); + } + promise.resolve(true); + } + + public void captureScreenshot(Promise promise) { + + final Activity activity = getCurrentActivity(); + if (activity == null) { + logger.log(SentryLevel.WARNING, "CurrentActivity is null, can't capture screenshot."); + promise.resolve(null); + return; + } + + final byte[] raw = takeScreenshotOnUiThread(activity); + + if (raw == null) { + logger.log(SentryLevel.WARNING, "Screenshot is null, screen was not captured."); + promise.resolve(null); + return; + } + + final WritableNativeArray data = new WritableNativeArray(); + for (final byte b : raw) { + data.pushInt(b); + } + final WritableMap screenshot = new WritableNativeMap(); + screenshot.putString("contentType", "image/png"); + screenshot.putArray("data", data); + screenshot.putString("filename", "screenshot.png"); + + final WritableArray screenshotsArray = new WritableNativeArray(); + screenshotsArray.pushMap(screenshot); + promise.resolve(screenshotsArray); + } + + private static byte[] takeScreenshotOnUiThread(Activity activity) { + CountDownLatch doneSignal = new CountDownLatch(1); + final byte[][] bytesWrapper = {{}}; // wrapper to be able to set the value in the runnable + final Runnable runTakeScreenshot = + () -> { + bytesWrapper[0] = takeScreenshot(activity, logger, buildInfo); + doneSignal.countDown(); }; - if (UiThreadUtil.isOnUiThread()) { - runTakeScreenshot.run(); - } else { - UiThreadUtil.runOnUiThread(runTakeScreenshot); - } - - try { - doneSignal.await(SCREENSHOT_TIMEOUT_SECONDS, SECONDS); - } catch (InterruptedException e) { - logger.log(SentryLevel.ERROR, "Screenshot process was interrupted."); - return null; - } - - return bytesWrapper[0]; - } - - public void fetchViewHierarchy(Promise promise) { - final @Nullable Activity activity = getCurrentActivity(); - final @Nullable ViewHierarchy viewHierarchy = ViewHierarchyEventProcessor.snapshotViewHierarchy(activity, logger); - if (viewHierarchy == null) { - logger.log(SentryLevel.ERROR, "Could not get ViewHierarchy."); - promise.resolve(null); - return; - } - - ISerializer serializer = HubAdapter.getInstance().getOptions().getSerializer(); - final @Nullable byte[] bytes = JsonSerializationUtils.bytesFrom(serializer, logger, viewHierarchy); - if (bytes == null) { - logger.log(SentryLevel.ERROR, "Could not serialize ViewHierarchy."); - promise.resolve(null); - return; - } - if (bytes.length < 1) { - logger.log(SentryLevel.ERROR, "Got empty bytes array after serializing ViewHierarchy."); - promise.resolve(null); - return; - } - - final WritableNativeArray data = new WritableNativeArray(); - for (final byte b : bytes) { - data.pushInt(b); - } - promise.resolve(data); - } - - private static PackageInfo getPackageInfo(Context ctx) { - try { - return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); - } catch (PackageManager.NameNotFoundException e) { - logger.log(SentryLevel.WARNING, "Error getting package info."); - return null; - } - } - - public void setUser(final ReadableMap userKeys, final ReadableMap userDataKeys) { - Sentry.configureScope(scope -> { - if (userKeys == null && userDataKeys == null) { - scope.setUser(null); - } else { - User userInstance = new User(); - - if (userKeys != null) { - if (userKeys.hasKey("email")) { - userInstance.setEmail(userKeys.getString("email")); - } - - if (userKeys.hasKey("id")) { - userInstance.setId(userKeys.getString("id")); - } - - if (userKeys.hasKey("username")) { - userInstance.setUsername(userKeys.getString("username")); - } - - if (userKeys.hasKey("ip_address")) { - userInstance.setIpAddress(userKeys.getString("ip_address")); - } - - if (userKeys.hasKey("segment")) { - userInstance.setSegment(userKeys.getString("segment")); - } - } - - if (userDataKeys != null) { - HashMap userDataMap = new HashMap<>(); - ReadableMapKeySetIterator it = userDataKeys.keySetIterator(); - while (it.hasNextKey()) { - String key = it.nextKey(); - String value = userDataKeys.getString(key); - - // other is ConcurrentHashMap and can't have null values - if (value != null) { - userDataMap.put(key, value); - } - } - - userInstance.setData(userDataMap); + if (UiThreadUtil.isOnUiThread()) { + runTakeScreenshot.run(); + } else { + UiThreadUtil.runOnUiThread(runTakeScreenshot); + } + + try { + doneSignal.await(SCREENSHOT_TIMEOUT_SECONDS, SECONDS); + } catch (InterruptedException e) { + logger.log(SentryLevel.ERROR, "Screenshot process was interrupted."); + return null; + } + + return bytesWrapper[0]; + } + + public void fetchViewHierarchy(Promise promise) { + final @Nullable Activity activity = getCurrentActivity(); + final @Nullable ViewHierarchy viewHierarchy = + ViewHierarchyEventProcessor.snapshotViewHierarchy(activity, logger); + if (viewHierarchy == null) { + logger.log(SentryLevel.ERROR, "Could not get ViewHierarchy."); + promise.resolve(null); + return; + } + + ISerializer serializer = HubAdapter.getInstance().getOptions().getSerializer(); + final @Nullable byte[] bytes = + JsonSerializationUtils.bytesFrom(serializer, logger, viewHierarchy); + if (bytes == null) { + logger.log(SentryLevel.ERROR, "Could not serialize ViewHierarchy."); + promise.resolve(null); + return; + } + if (bytes.length < 1) { + logger.log(SentryLevel.ERROR, "Got empty bytes array after serializing ViewHierarchy."); + promise.resolve(null); + return; + } + + final WritableNativeArray data = new WritableNativeArray(); + for (final byte b : bytes) { + data.pushInt(b); + } + promise.resolve(data); + } + + private static PackageInfo getPackageInfo(Context ctx) { + try { + return ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0); + } catch (PackageManager.NameNotFoundException e) { + logger.log(SentryLevel.WARNING, "Error getting package info."); + return null; + } + } + + public void setUser(final ReadableMap userKeys, final ReadableMap userDataKeys) { + Sentry.configureScope( + scope -> { + if (userKeys == null && userDataKeys == null) { + scope.setUser(null); + } else { + User userInstance = new User(); + + if (userKeys != null) { + if (userKeys.hasKey("email")) { + userInstance.setEmail(userKeys.getString("email")); + } + + if (userKeys.hasKey("id")) { + userInstance.setId(userKeys.getString("id")); + } + + if (userKeys.hasKey("username")) { + userInstance.setUsername(userKeys.getString("username")); + } + + if (userKeys.hasKey("ip_address")) { + userInstance.setIpAddress(userKeys.getString("ip_address")); + } + + if (userKeys.hasKey("segment")) { + userInstance.setSegment(userKeys.getString("segment")); + } + } + + if (userDataKeys != null) { + HashMap userDataMap = new HashMap<>(); + ReadableMapKeySetIterator it = userDataKeys.keySetIterator(); + while (it.hasNextKey()) { + String key = it.nextKey(); + String value = userDataKeys.getString(key); + + // other is ConcurrentHashMap and can't have null values + if (value != null) { + userDataMap.put(key, value); } + } - scope.setUser(userInstance); + userInstance.setData(userDataMap); } + + scope.setUser(userInstance); + } }); - } + } - public void addBreadcrumb(final ReadableMap breadcrumb) { - Sentry.configureScope(scope -> { - scope.addBreadcrumb(RNSentryBreadcrumb.fromMap(breadcrumb)); + public void addBreadcrumb(final ReadableMap breadcrumb) { + Sentry.configureScope( + scope -> { + scope.addBreadcrumb(RNSentryBreadcrumb.fromMap(breadcrumb)); - final @Nullable String screen = RNSentryBreadcrumb.getCurrentScreenFrom(breadcrumb); - if (screen != null) { - scope.setScreen(screen); - } + final @Nullable String screen = RNSentryBreadcrumb.getCurrentScreenFrom(breadcrumb); + if (screen != null) { + scope.setScreen(screen); + } }); - } + } - public void clearBreadcrumbs() { - Sentry.configureScope(scope -> { - scope.clearBreadcrumbs(); + public void clearBreadcrumbs() { + Sentry.configureScope( + scope -> { + scope.clearBreadcrumbs(); }); - } + } - public void setExtra(String key, String extra) { - if (key == null || extra == null) { - logger.log(SentryLevel.ERROR, "RNSentry.setExtra called with null key or value, can't change extra."); - return; - } + public void setExtra(String key, String extra) { + if (key == null || extra == null) { + logger.log( + SentryLevel.ERROR, + "RNSentry.setExtra called with null key or value, can't change extra."); + return; + } - Sentry.configureScope(scope -> { - scope.setExtra(key, extra); + Sentry.configureScope( + scope -> { + scope.setExtra(key, extra); }); + } + + public void setContext(final String key, final ReadableMap context) { + if (key == null) { + logger.log( + SentryLevel.ERROR, "RNSentry.setContext called with null key, can't change context."); + return; } - public void setContext(final String key, final ReadableMap context) { - if (key == null) { - logger.log(SentryLevel.ERROR, "RNSentry.setContext called with null key, can't change context."); + Sentry.configureScope( + scope -> { + if (context == null) { + scope.removeContexts(key); return; - } + } - Sentry.configureScope(scope -> { - if (context == null) { - scope.removeContexts(key); - return; - } - - final HashMap contextHashMap = context.toHashMap(); - scope.setContexts(key, contextHashMap); + final HashMap contextHashMap = context.toHashMap(); + scope.setContexts(key, contextHashMap); }); - } + } - public void setTag(String key, String value) { - Sentry.configureScope(scope -> { - scope.setTag(key, value); + public void setTag(String key, String value) { + Sentry.configureScope( + scope -> { + scope.setTag(key, value); }); - } - - public void closeNativeSdk(Promise promise) { - Sentry.close(); - - disableNativeFramesTracking(); - - promise.resolve(true); - } + } - public void enableNativeFramesTracking() { - androidXAvailable = checkAndroidXAvailability(); + public void closeNativeSdk(Promise promise) { + Sentry.close(); - if (androidXAvailable) { - frameMetricsAggregator = new FrameMetricsAggregator(); - final Activity currentActivity = getCurrentActivity(); + disableNativeFramesTracking(); - if (frameMetricsAggregator != null && currentActivity != null) { - try { - frameMetricsAggregator.add(currentActivity); + promise.resolve(true); + } - logger.log(SentryLevel.INFO, "FrameMetricsAggregator installed."); - } catch (Throwable ignored) { - // throws ConcurrentModification when calling addOnFrameMetricsAvailableListener - // this is a best effort since we can't reproduce it - logger.log(SentryLevel.ERROR, "Error adding Activity to frameMetricsAggregator."); - } - } else { - logger.log(SentryLevel.INFO, "currentActivity isn't available."); - } - } else { - logger.log(SentryLevel.WARNING, "androidx.core' isn't available as a dependency."); - } - } + public void enableNativeFramesTracking() { + androidXAvailable = checkAndroidXAvailability(); - public void disableNativeFramesTracking() { - if (isFrameMetricsAggregatorAvailable()) { - frameMetricsAggregator.stop(); - frameMetricsAggregator = null; - } - } - - private String getProfilingTracesDirPath() { - if (cacheDirPath == null) { - cacheDirPath = new File(getReactApplicationContext().getCacheDir(), "sentry/react").getAbsolutePath(); - } - File profilingTraceDir = new File(cacheDirPath, "profiling_trace"); - profilingTraceDir.mkdirs(); - return profilingTraceDir.getAbsolutePath(); - } - - private void initializeAndroidProfiler() { - if (executorService == null) { - executorService = new SentryExecutorService(); - } - final String tracesFilesDirPath = getProfilingTracesDirPath(); - - androidProfiler = new AndroidProfiler( - tracesFilesDirPath, - (int) SECONDS.toMicros(1) / profilingTracesHz, - new SentryFrameMetricsCollector(reactApplicationContext, logger, buildInfo), - executorService, - logger, - buildInfo - ); - } - - public WritableMap startProfiling(boolean platformProfilers) { - final WritableMap result = new WritableNativeMap(); - if (androidProfiler == null && platformProfilers) { - initializeAndroidProfiler(); - } + if (androidXAvailable) { + frameMetricsAggregator = new FrameMetricsAggregator(); + final Activity currentActivity = getCurrentActivity(); + if (frameMetricsAggregator != null && currentActivity != null) { try { - HermesSamplingProfiler.enable(); - if (androidProfiler != null) { - androidProfiler.start(); - } - - result.putBoolean("started", true); - } catch (Throwable e) { - result.putBoolean("started", false); - result.putString("error", e.toString()); - } - return result; - } - - public WritableMap stopProfiling() { - final boolean isDebug = HubAdapter.getInstance().getOptions().isDebug(); - final WritableMap result = new WritableNativeMap(); - File output = null; + frameMetricsAggregator.add(currentActivity); + + logger.log(SentryLevel.INFO, "FrameMetricsAggregator installed."); + } catch (Throwable ignored) { + // throws ConcurrentModification when calling addOnFrameMetricsAvailableListener + // this is a best effort since we can't reproduce it + logger.log(SentryLevel.ERROR, "Error adding Activity to frameMetricsAggregator."); + } + } else { + logger.log(SentryLevel.INFO, "currentActivity isn't available."); + } + } else { + logger.log(SentryLevel.WARNING, "androidx.core' isn't available as a dependency."); + } + } + + public void disableNativeFramesTracking() { + if (isFrameMetricsAggregatorAvailable()) { + frameMetricsAggregator.stop(); + frameMetricsAggregator = null; + } + } + + private String getProfilingTracesDirPath() { + if (cacheDirPath == null) { + cacheDirPath = + new File(getReactApplicationContext().getCacheDir(), "sentry/react").getAbsolutePath(); + } + File profilingTraceDir = new File(cacheDirPath, "profiling_trace"); + profilingTraceDir.mkdirs(); + return profilingTraceDir.getAbsolutePath(); + } + + private void initializeAndroidProfiler() { + if (executorService == null) { + executorService = new SentryExecutorService(); + } + final String tracesFilesDirPath = getProfilingTracesDirPath(); + + androidProfiler = + new AndroidProfiler( + tracesFilesDirPath, + (int) SECONDS.toMicros(1) / profilingTracesHz, + new SentryFrameMetricsCollector(reactApplicationContext, logger, buildInfo), + executorService, + logger, + buildInfo); + } + + public WritableMap startProfiling(boolean platformProfilers) { + final WritableMap result = new WritableNativeMap(); + if (androidProfiler == null && platformProfilers) { + initializeAndroidProfiler(); + } + + try { + HermesSamplingProfiler.enable(); + if (androidProfiler != null) { + androidProfiler.start(); + } + + result.putBoolean("started", true); + } catch (Throwable e) { + result.putBoolean("started", false); + result.putString("error", e.toString()); + } + return result; + } + + public WritableMap stopProfiling() { + final boolean isDebug = HubAdapter.getInstance().getOptions().isDebug(); + final WritableMap result = new WritableNativeMap(); + File output = null; + try { + AndroidProfiler.ProfileEndData end = null; + if (androidProfiler != null) { + end = androidProfiler.endAndCollect(false, null); + } + HermesSamplingProfiler.disable(); + + output = + File.createTempFile( + "sampling-profiler-trace", ".cpuprofile", reactApplicationContext.getCacheDir()); + if (isDebug) { + logger.log(SentryLevel.INFO, "Profile saved to: " + output.getAbsolutePath()); + } + + HermesSamplingProfiler.dumpSampledTraceToFile(output.getPath()); + result.putString("profile", readStringFromFile(output)); + + if (end != null) { + WritableMap androidProfile = new WritableNativeMap(); + byte[] androidProfileBytes = + FileUtils.readBytesFromFile(end.traceFile.getPath(), maxTraceFileSize); + String base64AndroidProfile = + Base64.encodeToString(androidProfileBytes, NO_WRAP | NO_PADDING); + + androidProfile.putString("sampled_profile", base64AndroidProfile); + androidProfile.putInt("android_api_level", buildInfo.getSdkInfoVersion()); + androidProfile.putString("build_id", getProguardUuid()); + result.putMap("androidProfile", androidProfile); + } + } catch (Throwable e) { + result.putString("error", e.toString()); + } finally { + if (output != null) { try { - AndroidProfiler.ProfileEndData end = null; - if (androidProfiler != null) { - end = androidProfiler.endAndCollect(false, null); - } - HermesSamplingProfiler.disable(); - - output = File.createTempFile( - "sampling-profiler-trace", ".cpuprofile", reactApplicationContext.getCacheDir()); - if (isDebug) { - logger.log(SentryLevel.INFO, "Profile saved to: " + output.getAbsolutePath()); - } - - HermesSamplingProfiler.dumpSampledTraceToFile(output.getPath()); - result.putString("profile", readStringFromFile(output)); - - if (end != null) { - WritableMap androidProfile = new WritableNativeMap(); - byte[] androidProfileBytes = FileUtils.readBytesFromFile(end.traceFile.getPath(), maxTraceFileSize); - String base64AndroidProfile = Base64.encodeToString(androidProfileBytes, NO_WRAP | NO_PADDING); - - androidProfile.putString("sampled_profile", base64AndroidProfile); - androidProfile.putInt("android_api_level", buildInfo.getSdkInfoVersion()); - androidProfile.putString("build_id", getProguardUuid()); - result.putMap("androidProfile", androidProfile); - } + final boolean wasProfileSuccessfullyDeleted = output.delete(); + if (!wasProfileSuccessfullyDeleted) { + logger.log(SentryLevel.WARNING, "Profile not deleted from:" + output.getAbsolutePath()); + } } catch (Throwable e) { - result.putString("error", e.toString()); - } finally { - if (output != null) { - try { - final boolean wasProfileSuccessfullyDeleted = output.delete(); - if (!wasProfileSuccessfullyDeleted) { - logger.log(SentryLevel.WARNING, "Profile not deleted from:" + output.getAbsolutePath()); - } - } catch (Throwable e) { - logger.log(SentryLevel.WARNING, "Profile not deleted from:" + output.getAbsolutePath()); - } - } - } - return result; - } - - private @Nullable String getProguardUuid() { - if (isProguardDebugMetaLoaded) { - return proguardUuid; - } - isProguardDebugMetaLoaded = true; - final @Nullable List debugMetaList = (new AssetsDebugMetaLoader(this.getReactApplicationContext(), - logger)).loadDebugMeta(); - if (debugMetaList == null) { - return null; - } - - for (Properties debugMeta : debugMetaList) { - proguardUuid = DebugMetaPropertiesApplier.getProguardUuid(debugMeta); - if (proguardUuid != null) { - logger.log(SentryLevel.INFO, "Proguard uuid found: " + proguardUuid); - return proguardUuid; - } - } - - logger.log(SentryLevel.WARNING, "No proguard uuid found in debug meta properties file!"); - return null; - } - - private String readStringFromFile(File path) throws IOException { - try (final BufferedReader br = new BufferedReader(new FileReader(path));) { - - final StringBuilder text = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - text.append(line); - text.append('\n'); - } - return text.toString(); - } - } - - public void fetchNativeDeviceContexts(Promise promise) { - final @NotNull SentryOptions options = HubAdapter.getInstance().getOptions(); - if (!(options instanceof SentryAndroidOptions)) { - promise.resolve(null); - return; - } - - final @Nullable Context context = this.getReactApplicationContext().getApplicationContext(); - if (context == null) { - promise.resolve(null); - return; - } - - final @Nullable IScope currentScope = InternalSentrySdk.getCurrentScope(); - final @NotNull Map serialized = InternalSentrySdk.serializeScope( - context, - (SentryAndroidOptions) options, - currentScope); - final @Nullable Object deviceContext = RNSentryMapConverter.convertToWritable(serialized); - promise.resolve(deviceContext); - } - - public void fetchNativeSdkInfo(Promise promise) { - final @Nullable SdkVersion sdkVersion = HubAdapter.getInstance().getOptions().getSdkVersion(); - if (sdkVersion == null) { - promise.resolve(null); - } else { - final WritableMap sdkInfo = new WritableNativeMap(); - sdkInfo.putString("name", sdkVersion.getName()); - sdkInfo.putString("version", sdkVersion.getVersion()); - promise.resolve(sdkInfo); - } - } - - public String fetchNativePackageName() { - return packageInfo.packageName; - } - - public void crashedLastRun(Promise promise) { - promise.resolve(Sentry.isCrashedLastRun()); - } - - private void setEventOriginTag(SentryEvent event) { - SdkVersion sdk = event.getSdk(); - if (sdk != null) { - switch (sdk.getName()) { - // If the event is from capacitor js, it gets set there and we do not handle it - // here. - case NATIVE_SDK_NAME: - setEventEnvironmentTag(event, "native"); - break; - case 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 && eventSdk.getName().equals("sentry.javascript.react-native") && 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"); - return true; - } catch (ClassNotFoundException ignored) { - // androidx.core isn't available. - return false; - } - } - - private boolean isFrameMetricsAggregatorAvailable() { - return androidXAvailable && frameMetricsAggregator != null; - } + logger.log(SentryLevel.WARNING, "Profile not deleted from:" + output.getAbsolutePath()); + } + } + } + return result; + } + + private @Nullable String getProguardUuid() { + if (isProguardDebugMetaLoaded) { + return proguardUuid; + } + isProguardDebugMetaLoaded = true; + final @Nullable List debugMetaList = + (new AssetsDebugMetaLoader(this.getReactApplicationContext(), logger)).loadDebugMeta(); + if (debugMetaList == null) { + return null; + } + + for (Properties debugMeta : debugMetaList) { + proguardUuid = DebugMetaPropertiesApplier.getProguardUuid(debugMeta); + if (proguardUuid != null) { + logger.log(SentryLevel.INFO, "Proguard uuid found: " + proguardUuid); + return proguardUuid; + } + } + + logger.log(SentryLevel.WARNING, "No proguard uuid found in debug meta properties file!"); + return null; + } + + private String readStringFromFile(File path) throws IOException { + try (final BufferedReader br = new BufferedReader(new FileReader(path)); ) { + + final StringBuilder text = new StringBuilder(); + String line; + while ((line = br.readLine()) != null) { + text.append(line); + text.append('\n'); + } + return text.toString(); + } + } + + public void fetchNativeDeviceContexts(Promise promise) { + final @NotNull SentryOptions options = HubAdapter.getInstance().getOptions(); + if (!(options instanceof SentryAndroidOptions)) { + promise.resolve(null); + return; + } + + final @Nullable Context context = this.getReactApplicationContext().getApplicationContext(); + if (context == null) { + promise.resolve(null); + return; + } + + final @Nullable IScope currentScope = InternalSentrySdk.getCurrentScope(); + final @NotNull Map serialized = + InternalSentrySdk.serializeScope(context, (SentryAndroidOptions) options, currentScope); + final @Nullable Object deviceContext = RNSentryMapConverter.convertToWritable(serialized); + promise.resolve(deviceContext); + } + + public void fetchNativeSdkInfo(Promise promise) { + final @Nullable SdkVersion sdkVersion = HubAdapter.getInstance().getOptions().getSdkVersion(); + if (sdkVersion == null) { + promise.resolve(null); + } else { + final WritableMap sdkInfo = new WritableNativeMap(); + sdkInfo.putString("name", sdkVersion.getName()); + sdkInfo.putString("version", sdkVersion.getVersion()); + promise.resolve(sdkInfo); + } + } + + public String fetchNativePackageName() { + return packageInfo.packageName; + } + + 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 NATIVE_SDK_NAME: + setEventEnvironmentTag(event, "native"); + break; + case 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 + && eventSdk.getName().equals("sentry.javascript.react-native") + && 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"); + return true; + } catch (ClassNotFoundException ignored) { + // androidx.core isn't available. + return false; + } + } + + private boolean isFrameMetricsAggregatorAvailable() { + return androidXAvailable && frameMetricsAggregator != null; + } } diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryOnDrawReporterManager.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryOnDrawReporterManager.java index a33f4fc1c..a412ab51b 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryOnDrawReporterManager.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryOnDrawReporterManager.java @@ -3,7 +3,6 @@ import android.app.Activity; import android.content.Context; import android.view.View; - import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.WritableMap; @@ -12,12 +11,6 @@ import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.events.RCTEventEmitter; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Map; - import io.sentry.ILogger; import io.sentry.SentryDate; import io.sentry.SentryDateProvider; @@ -26,131 +19,145 @@ import io.sentry.android.core.BuildInfoProvider; import io.sentry.android.core.SentryAndroidDateProvider; import io.sentry.android.core.internal.util.FirstDrawDoneListener; +import java.util.Map; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public class RNSentryOnDrawReporterManager extends SimpleViewManager { - - public static final String REACT_CLASS = "RNSentryOnDrawReporter"; - private final @NotNull ReactApplicationContext mCallerContext; - - public RNSentryOnDrawReporterManager(ReactApplicationContext reactContext) { - mCallerContext = reactContext; +public class RNSentryOnDrawReporterManager + extends SimpleViewManager { + + public static final String REACT_CLASS = "RNSentryOnDrawReporter"; + private final @NotNull ReactApplicationContext mCallerContext; + + public RNSentryOnDrawReporterManager(ReactApplicationContext reactContext) { + mCallerContext = reactContext; + } + + @NotNull + @Override + public String getName() { + return REACT_CLASS; + } + + @NotNull + @Override + protected RNSentryOnDrawReporterView createViewInstance( + @NotNull ThemedReactContext themedReactContext) { + return new RNSentryOnDrawReporterView( + mCallerContext, new BuildInfoProvider(new AndroidLogger())); + } + + @ReactProp(name = "initialDisplay", defaultBoolean = false) + public void setInitialDisplay(RNSentryOnDrawReporterView view, boolean initialDisplay) { + view.setInitialDisplay(initialDisplay); + } + + @ReactProp(name = "fullDisplay", defaultBoolean = false) + public void setFullDisplay(RNSentryOnDrawReporterView view, boolean fullDisplay) { + view.setFullDisplay(fullDisplay); + } + + public Map getExportedCustomBubblingEventTypeConstants() { + return MapBuilder.builder() + .put( + "onDrawNextFrameView", + MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onDrawNextFrame"))) + .build(); + } + + public static class RNSentryOnDrawReporterView extends View { + + private static final ILogger logger = new AndroidLogger("RNSentryOnDrawReporterView"); + + private final @Nullable ReactApplicationContext reactContext; + private final @NotNull SentryDateProvider dateProvider = new SentryAndroidDateProvider(); + private final @Nullable Runnable emitInitialDisplayEvent; + private final @Nullable Runnable emitFullDisplayEvent; + private final @Nullable BuildInfoProvider buildInfo; + + public RNSentryOnDrawReporterView(@NotNull Context context) { + super(context); + reactContext = null; + buildInfo = null; + emitInitialDisplayEvent = null; + emitFullDisplayEvent = null; } - @NotNull - @Override - public String getName() { - return REACT_CLASS; + public RNSentryOnDrawReporterView( + @NotNull ReactApplicationContext context, @NotNull BuildInfoProvider buildInfoProvider) { + super(context); + reactContext = context; + buildInfo = buildInfoProvider; + emitInitialDisplayEvent = () -> emitDisplayEvent("initialDisplay"); + emitFullDisplayEvent = () -> emitDisplayEvent("fullDisplay"); } - @NotNull - @Override - protected RNSentryOnDrawReporterView createViewInstance(@NotNull ThemedReactContext themedReactContext) { - return new RNSentryOnDrawReporterView(mCallerContext, new BuildInfoProvider(new AndroidLogger())); - } + public void setFullDisplay(boolean fullDisplay) { + if (!fullDisplay) { + return; + } - @ReactProp(name = "initialDisplay", defaultBoolean = false) - public void setInitialDisplay(RNSentryOnDrawReporterView view, boolean initialDisplay) { - view.setInitialDisplay(initialDisplay); + logger.log(SentryLevel.DEBUG, "[TimeToDisplay] Register full display event emitter."); + registerForNextDraw(emitFullDisplayEvent); } - @ReactProp(name = "fullDisplay", defaultBoolean = false) - public void setFullDisplay(RNSentryOnDrawReporterView view, boolean fullDisplay) { - view.setFullDisplay(fullDisplay); + public void setInitialDisplay(boolean initialDisplay) { + if (!initialDisplay) { + return; + } + + logger.log(SentryLevel.DEBUG, "[TimeToDisplay] Register initial display event emitter."); + registerForNextDraw(emitInitialDisplayEvent); } - public Map getExportedCustomBubblingEventTypeConstants() { - return MapBuilder.builder().put( - "onDrawNextFrameView", - MapBuilder.of( - "phasedRegistrationNames", - MapBuilder.of("bubbled", "onDrawNextFrame") - ) - ).build(); + private void registerForNextDraw(@Nullable Runnable emitter) { + if (emitter == null) { + logger.log( + SentryLevel.ERROR, + "[TimeToDisplay] Won't emit next frame drawn event, emitter is null."); + return; + } + if (buildInfo == null) { + logger.log( + SentryLevel.ERROR, + "[TimeToDisplay] Won't emit next frame drawn event, buildInfo is null."); + return; + } + if (reactContext == null) { + logger.log( + SentryLevel.ERROR, + "[TimeToDisplay] Won't emit next frame drawn event, reactContext is null."); + return; + } + + @Nullable Activity activity = reactContext.getCurrentActivity(); + if (activity == null) { + logger.log( + SentryLevel.ERROR, + "[TimeToDisplay] Won't emit next frame drawn event, reactContext is null."); + return; + } + + FirstDrawDoneListener.registerForNextDraw(activity, emitter, buildInfo); } - public static class RNSentryOnDrawReporterView extends View { - - private static final ILogger logger = new AndroidLogger("RNSentryOnDrawReporterView"); - - private final @Nullable ReactApplicationContext reactContext; - private final @NotNull SentryDateProvider dateProvider = new SentryAndroidDateProvider(); - private final @Nullable Runnable emitInitialDisplayEvent; - private final @Nullable Runnable emitFullDisplayEvent; - private final @Nullable BuildInfoProvider buildInfo; - - - public RNSentryOnDrawReporterView(@NotNull Context context) { - super(context); - reactContext = null; - buildInfo = null; - emitInitialDisplayEvent = null; - emitFullDisplayEvent = null; - } - - public RNSentryOnDrawReporterView(@NotNull ReactApplicationContext context, @NotNull BuildInfoProvider buildInfoProvider) { - super(context); - reactContext = context; - buildInfo = buildInfoProvider; - emitInitialDisplayEvent = () -> emitDisplayEvent("initialDisplay"); - emitFullDisplayEvent = () -> emitDisplayEvent("fullDisplay"); - } - - public void setFullDisplay(boolean fullDisplay) { - if (!fullDisplay) { - return; - } - - logger.log(SentryLevel.DEBUG, "[TimeToDisplay] Register full display event emitter."); - registerForNextDraw(emitFullDisplayEvent); - } - - public void setInitialDisplay(boolean initialDisplay) { - if (!initialDisplay) { - return; - } - - logger.log(SentryLevel.DEBUG, "[TimeToDisplay] Register initial display event emitter."); - registerForNextDraw(emitInitialDisplayEvent); - } - - private void registerForNextDraw(@Nullable Runnable emitter) { - if (emitter == null) { - logger.log(SentryLevel.ERROR, "[TimeToDisplay] Won't emit next frame drawn event, emitter is null."); - return; - } - if (buildInfo == null) { - logger.log(SentryLevel.ERROR, "[TimeToDisplay] Won't emit next frame drawn event, buildInfo is null."); - return; - } - if (reactContext == null) { - logger.log(SentryLevel.ERROR, "[TimeToDisplay] Won't emit next frame drawn event, reactContext is null."); - return; - } - - @Nullable Activity activity = reactContext.getCurrentActivity(); - if (activity == null) { - logger.log(SentryLevel.ERROR, "[TimeToDisplay] Won't emit next frame drawn event, reactContext is null."); - return; - } - - FirstDrawDoneListener - .registerForNextDraw(activity, emitter, buildInfo); - } - - private void emitDisplayEvent(String type) { - final SentryDate endDate = dateProvider.now(); - - WritableMap event = Arguments.createMap(); - event.putString("type", type); - event.putDouble("newFrameTimestampInSeconds", endDate.nanoTimestamp() / 1e9); - - if (reactContext == null) { - logger.log(SentryLevel.ERROR, "[TimeToDisplay] Recorded next frame draw but can't emit the event, reactContext is null."); - return; - } - reactContext - .getJSModule(RCTEventEmitter.class) - .receiveEvent(getId(), "onDrawNextFrameView", event); - } + private void emitDisplayEvent(String type) { + final SentryDate endDate = dateProvider.now(); + + WritableMap event = Arguments.createMap(); + event.putString("type", type); + event.putDouble("newFrameTimestampInSeconds", endDate.nanoTimestamp() / 1e9); + + if (reactContext == null) { + logger.log( + SentryLevel.ERROR, + "[TimeToDisplay] Recorded next frame draw but can't emit the event, reactContext is" + + " null."); + return; + } + reactContext + .getJSModule(RCTEventEmitter.class) + .receiveEvent(getId(), "onDrawNextFrameView", event); } + } } diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryPackage.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryPackage.java index 8cce37e29..c039a1c00 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryPackage.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryPackage.java @@ -2,18 +2,16 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import com.facebook.react.TurboReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.module.model.ReactModuleInfo; import com.facebook.react.module.model.ReactModuleInfoProvider; -import com.facebook.react.TurboReactPackage; import com.facebook.react.uimanager.ViewManager; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; public class RNSentryPackage extends TurboReactPackage { @@ -42,18 +40,14 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { true, // hasConstants false, // isCxxModule isTurboModule // isTurboModule - )); + )); return moduleInfos; }; } @NonNull @Override - public List createViewManagers( - ReactApplicationContext reactContext) { - return Arrays.asList( - new RNSentryOnDrawReporterManager(reactContext) - ); + public List createViewManagers(ReactApplicationContext reactContext) { + return Arrays.asList(new RNSentryOnDrawReporterManager(reactContext)); } - } diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryReactFragmentLifecycleTracer.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryReactFragmentLifecycleTracer.java index d5780d674..4f76fceb4 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryReactFragmentLifecycleTracer.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryReactFragmentLifecycleTracer.java @@ -1,99 +1,105 @@ package io.sentry.react; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager.FragmentLifecycleCallbacks; - -import android.os.Bundle; -import android.view.View; -import android.view.ViewGroup; - import com.facebook.react.bridge.ReactContext; import com.facebook.react.uimanager.UIManagerHelper; import com.facebook.react.uimanager.events.Event; import com.facebook.react.uimanager.events.EventDispatcher; import com.facebook.react.uimanager.events.EventDispatcherListener; - -import org.jetbrains.annotations.Nullable; -import org.jetbrains.annotations.NotNull; - import io.sentry.ILogger; import io.sentry.SentryLevel; import io.sentry.android.core.BuildInfoProvider; import io.sentry.android.core.internal.util.FirstDrawDoneListener; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class RNSentryReactFragmentLifecycleTracer extends FragmentLifecycleCallbacks { - private @NotNull final BuildInfoProvider buildInfoProvider; - private @NotNull final Runnable emitNewFrameEvent; - private @NotNull final ILogger logger; - - public RNSentryReactFragmentLifecycleTracer( - @NotNull BuildInfoProvider buildInfoProvider, - @NotNull Runnable emitNewFrameEvent, - @NotNull ILogger logger) { - this.buildInfoProvider = buildInfoProvider; - this.emitNewFrameEvent = emitNewFrameEvent; - this.logger = logger; + private @NotNull final BuildInfoProvider buildInfoProvider; + private @NotNull final Runnable emitNewFrameEvent; + private @NotNull final ILogger logger; + + public RNSentryReactFragmentLifecycleTracer( + @NotNull BuildInfoProvider buildInfoProvider, + @NotNull Runnable emitNewFrameEvent, + @NotNull ILogger logger) { + this.buildInfoProvider = buildInfoProvider; + this.emitNewFrameEvent = emitNewFrameEvent; + this.logger = logger; + } + + @Override + public void onFragmentViewCreated( + @NotNull FragmentManager fm, + @NotNull Fragment f, + @NotNull View v, + @Nullable Bundle savedInstanceState) { + if (!("com.swmansion.rnscreens.ScreenStackFragment".equals(f.getClass().getCanonicalName()))) { + logger.log( + SentryLevel.DEBUG, + "Fragment is not a ScreenStackFragment, won't listen for the first draw."); + return; } - @Override - public void onFragmentViewCreated( - @NotNull FragmentManager fm, - @NotNull Fragment f, - @NotNull View v, - @Nullable Bundle savedInstanceState) { - if (!("com.swmansion.rnscreens.ScreenStackFragment".equals(f.getClass().getCanonicalName()))) { - logger.log(SentryLevel.DEBUG, "Fragment is not a ScreenStackFragment, won't listen for the first draw."); - return; - } - - if (!(v instanceof ViewGroup)) { - logger.log(SentryLevel.WARNING, "Fragment view is not a ViewGroup, won't listen for the first draw."); - return; - } + if (!(v instanceof ViewGroup)) { + logger.log( + SentryLevel.WARNING, + "Fragment view is not a ViewGroup, won't listen for the first draw."); + return; + } - final ViewGroup viewGroup = (ViewGroup) v; - if (viewGroup.getChildCount() == 0) { - logger.log(SentryLevel.WARNING, "Fragment view has no children, won't listen for the first draw."); - return; - } + final ViewGroup viewGroup = (ViewGroup) v; + if (viewGroup.getChildCount() == 0) { + logger.log( + SentryLevel.WARNING, "Fragment view has no children, won't listen for the first draw."); + return; + } - final @Nullable View screen = viewGroup.getChildAt(0); - if (screen == null || !(screen.getContext() instanceof ReactContext)) { - logger.log(SentryLevel.WARNING, "Fragment view has no ReactContext, won't listen for the first draw."); - return; - } + final @Nullable View screen = viewGroup.getChildAt(0); + if (screen == null || !(screen.getContext() instanceof ReactContext)) { + logger.log( + SentryLevel.WARNING, + "Fragment view has no ReactContext, won't listen for the first draw."); + return; + } - final int screenId = screen.getId(); - if (screenId == View.NO_ID) { - logger.log(SentryLevel.WARNING, "Screen has no id, won't listen for the first draw."); - return; - } + final int screenId = screen.getId(); + if (screenId == View.NO_ID) { + logger.log(SentryLevel.WARNING, "Screen has no id, won't listen for the first draw."); + return; + } - final @Nullable EventDispatcher eventDispatcher = getEventDispatcherForReactTag(screen, screenId); - if (eventDispatcher == null) { - logger.log(SentryLevel.WARNING, "Screen has no event dispatcher, won't listen for the first draw."); - return; - } + final @Nullable EventDispatcher eventDispatcher = + getEventDispatcherForReactTag(screen, screenId); + if (eventDispatcher == null) { + logger.log( + SentryLevel.WARNING, "Screen has no event dispatcher, won't listen for the first draw."); + return; + } - final @NotNull Runnable emitNewFrameEvent = this.emitNewFrameEvent; - eventDispatcher.addListener(new EventDispatcherListener() { - @Override - public void onEventDispatch(Event event) { - if ("com.swmansion.rnscreens.events.ScreenAppearEvent".equals(event.getClass().getCanonicalName())) { - eventDispatcher.removeListener(this); - FirstDrawDoneListener - .registerForNextDraw(v, emitNewFrameEvent, buildInfoProvider); - } + final @NotNull Runnable emitNewFrameEvent = this.emitNewFrameEvent; + eventDispatcher.addListener( + new EventDispatcherListener() { + @Override + public void onEventDispatch(Event event) { + if ("com.swmansion.rnscreens.events.ScreenAppearEvent" + .equals(event.getClass().getCanonicalName())) { + eventDispatcher.removeListener(this); + FirstDrawDoneListener.registerForNextDraw(v, emitNewFrameEvent, buildInfoProvider); } + } }); - } + } - private static @Nullable EventDispatcher getEventDispatcherForReactTag(@NonNull View screen, int screenId) { - return UIManagerHelper.getEventDispatcherForReactTag( - UIManagerHelper.getReactContext(screen), - screenId); - } + private static @Nullable EventDispatcher getEventDispatcherForReactTag( + @NonNull View screen, int screenId) { + return UIManagerHelper.getEventDispatcherForReactTag( + UIManagerHelper.getReactContext(screen), screenId); + } } diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java index 4d6457a15..b6629a815 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryReplayBreadcrumbConverter.java @@ -2,21 +2,18 @@ import io.sentry.Breadcrumb; import io.sentry.android.replay.DefaultReplayBreadcrumbConverter; -import io.sentry.rrweb.RRWebEvent; import io.sentry.rrweb.RRWebBreadcrumbEvent; +import io.sentry.rrweb.RRWebEvent; import io.sentry.rrweb.RRWebSpanEvent; - import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; -import java.util.List; -import java.util.Map; - public final class RNSentryReplayBreadcrumbConverter extends DefaultReplayBreadcrumbConverter { - public RNSentryReplayBreadcrumbConverter() { - } + public RNSentryReplayBreadcrumbConverter() {} @Override public @Nullable RRWebEvent convert(final @NotNull Breadcrumb breadcrumb) { @@ -25,8 +22,8 @@ public RNSentryReplayBreadcrumbConverter() { } // Do not add Sentry Event breadcrumbs to replay - if (breadcrumb.getCategory().equals("sentry.event") || - breadcrumb.getCategory().equals("sentry.transaction")) { + if (breadcrumb.getCategory().equals("sentry.event") + || breadcrumb.getCategory().equals("sentry.transaction")) { return null; } if (breadcrumb.getCategory().equals("http")) { @@ -49,7 +46,8 @@ public RNSentryReplayBreadcrumbConverter() { // ignore native navigation breadcrumbs if (nativeBreadcrumb instanceof RRWebBreadcrumbEvent) { final RRWebBreadcrumbEvent rrWebBreadcrumb = (RRWebBreadcrumbEvent) nativeBreadcrumb; - if (rrWebBreadcrumb.getCategory() != null && rrWebBreadcrumb.getCategory().equals("navigation")) { + if (rrWebBreadcrumb.getCategory() != null + && rrWebBreadcrumb.getCategory().equals("navigation")) { return null; } } @@ -71,8 +69,8 @@ public RNSentryReplayBreadcrumbConverter() { rrWebBreadcrumb.setCategory("ui.tap"); - rrWebBreadcrumb.setMessage(RNSentryReplayBreadcrumbConverter - .getTouchPathMessage(breadcrumb.getData("path"))); + rrWebBreadcrumb.setMessage( + RNSentryReplayBreadcrumbConverter.getTouchPathMessage(breadcrumb.getData("path"))); setRRWebEventDefaultsFrom(rrWebBreadcrumb, breadcrumb); return rrWebBreadcrumb; @@ -93,7 +91,7 @@ public RNSentryReplayBreadcrumbConverter() { for (int i = Math.min(3, path.size() - 1); i >= 0; i--) { final @Nullable Object maybeItem = path.get(i); if (!(maybeItem instanceof Map)) { - return null; + return null; } final @NotNull Map item = (Map) maybeItem; @@ -115,19 +113,11 @@ public RNSentryReplayBreadcrumbConverter() { boolean hasElement = maybeElement instanceof String; boolean hasFile = maybeFile instanceof String; if (hasElement && hasFile) { - message.append('(') - .append(maybeElement) - .append(", ") - .append(maybeFile) - .append(')'); + message.append('(').append(maybeElement).append(", ").append(maybeFile).append(')'); } else if (hasElement) { - message.append('(') - .append(maybeElement) - .append(')'); + message.append('(').append(maybeElement).append(')'); } else if (hasFile) { - message.append('(') - .append(maybeFile) - .append(')'); + message.append('(').append(maybeFile).append(')'); } if (i > 0) { @@ -140,12 +130,16 @@ public RNSentryReplayBreadcrumbConverter() { @TestOnly public @Nullable RRWebEvent convertNetworkBreadcrumb(final @NotNull Breadcrumb breadcrumb) { - final Double startTimestamp = breadcrumb.getData("start_timestamp") instanceof Number - ? (Double) breadcrumb.getData("start_timestamp") : null; - final Double endTimestamp = breadcrumb.getData("end_timestamp") instanceof Number - ? (Double) breadcrumb.getData("end_timestamp") : null; - final String url = breadcrumb.getData("url") instanceof String - ? (String) breadcrumb.getData("url") : null; + final Double startTimestamp = + breadcrumb.getData("start_timestamp") instanceof Number + ? (Double) breadcrumb.getData("start_timestamp") + : null; + final Double endTimestamp = + breadcrumb.getData("end_timestamp") instanceof Number + ? (Double) breadcrumb.getData("end_timestamp") + : null; + final String url = + breadcrumb.getData("url") instanceof String ? (String) breadcrumb.getData("url") : null; if (startTimestamp == null || endTimestamp == null || url == null) { return null; @@ -177,7 +171,8 @@ public RNSentryReplayBreadcrumbConverter() { return rrWebSpanEvent; } - private void setRRWebEventDefaultsFrom(final @NotNull RRWebBreadcrumbEvent rrWebBreadcrumb, final @NotNull Breadcrumb breadcrumb) { + private void setRRWebEventDefaultsFrom( + final @NotNull RRWebBreadcrumbEvent rrWebBreadcrumb, final @NotNull Breadcrumb breadcrumb) { rrWebBreadcrumb.setLevel(breadcrumb.getLevel()); rrWebBreadcrumb.setData(breadcrumb.getData()); rrWebBreadcrumb.setTimestamp(breadcrumb.getTimestamp().getTime()); diff --git a/packages/core/android/src/newarch/java/io/sentry/react/RNSentryModule.java b/packages/core/android/src/newarch/java/io/sentry/react/RNSentryModule.java index fd5b902e5..78f1911da 100644 --- a/packages/core/android/src/newarch/java/io/sentry/react/RNSentryModule.java +++ b/packages/core/android/src/newarch/java/io/sentry/react/RNSentryModule.java @@ -1,176 +1,175 @@ package io.sentry.react; import androidx.annotation.NonNull; - +import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; -import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.WritableMap; public class RNSentryModule extends NativeRNSentrySpec { - private final RNSentryModuleImpl impl; - - RNSentryModule(ReactApplicationContext reactContext) { - super(reactContext); - this.impl = new RNSentryModuleImpl(reactContext); - } - - @Override - @NonNull - public String getName() { - return RNSentryModuleImpl.NAME; - } - - @Override - public void addListener(String eventType) { - this.impl.addListener(eventType); - } - - @Override - public void removeListeners(double id) { - this.impl.removeListeners(id); - } - - @Override - public void initNativeReactNavigationNewFrameTracking(Promise promise) { - this.impl.initNativeReactNavigationNewFrameTracking(promise); - } - - @Override - public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { - this.impl.initNativeSdk(rnOptions, promise); - } - - @Override - public void crash() { - this.impl.crash(); - } - - @Override - public void fetchModules(Promise promise) { - this.impl.fetchModules(promise); - } - - @Override - public void fetchNativeRelease(Promise promise) { - this.impl.fetchNativeRelease(promise); - } - - @Override - public void fetchNativeAppStart(Promise promise) { - this.impl.fetchNativeAppStart(promise); - } - - @Override - public void fetchNativeFrames(Promise promise) { - this.impl.fetchNativeFrames(promise); - } - - @Override - public void captureEnvelope(String rawBytes, ReadableMap options, Promise promise) { - this.impl.captureEnvelope(rawBytes, options, promise); - } - - @Override - public void captureScreenshot(Promise promise) { - this.impl.captureScreenshot(promise); - } - - @Override - public void fetchViewHierarchy(Promise promise){ - this.impl.fetchViewHierarchy(promise); - } - - @Override - public void setUser(final ReadableMap user, final ReadableMap otherUserKeys) { - this.impl.setUser(user, otherUserKeys); - } - - @Override - public void addBreadcrumb(final ReadableMap breadcrumb) { - this.impl.addBreadcrumb(breadcrumb); - } - - @Override - public void clearBreadcrumbs() { - this.impl.clearBreadcrumbs(); - } - - @Override - public void setExtra(String key, String extra) { - this.impl.setExtra(key, extra); - } - - @Override - public void setContext(final String key, final ReadableMap context) { - this.impl.setContext(key, context); - } - - @Override - public void setTag(String key, String value) { - this.impl.setTag(key, value); - } - - @Override - public void closeNativeSdk(Promise promise) { - this.impl.closeNativeSdk(promise); - } - - @Override - public void enableNativeFramesTracking() { - this.impl.enableNativeFramesTracking(); - } - - @Override - public void disableNativeFramesTracking() { - this.impl.disableNativeFramesTracking(); - } - - @Override - public void fetchNativeDeviceContexts(Promise promise) { - this.impl.fetchNativeDeviceContexts(promise); - } - - @Override - public void fetchNativeSdkInfo(Promise promise) { - this.impl.fetchNativeSdkInfo(promise); - } - - @Override - public WritableMap startProfiling(boolean platformProfilers) { - return this.impl.startProfiling(platformProfilers); - } - - @Override - public WritableMap stopProfiling() { - return this.impl.stopProfiling(); - } - - @Override - public String fetchNativePackageName() { - return this.impl.fetchNativePackageName(); - } - - @Override - public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) { - // Not used on Android - return null; - } - - @Override - public void captureReplay(boolean isHardCrash, Promise promise) { - this.impl.captureReplay(isHardCrash, promise); - } - - @Override - public String getCurrentReplayId() { - return this.impl.getCurrentReplayId(); - } - - @Override - public void crashedLastRun(Promise promise) { - this.impl.crashedLastRun(promise); - } + private final RNSentryModuleImpl impl; + + RNSentryModule(ReactApplicationContext reactContext) { + super(reactContext); + this.impl = new RNSentryModuleImpl(reactContext); + } + + @Override + @NonNull + public String getName() { + return RNSentryModuleImpl.NAME; + } + + @Override + public void addListener(String eventType) { + this.impl.addListener(eventType); + } + + @Override + public void removeListeners(double id) { + this.impl.removeListeners(id); + } + + @Override + public void initNativeReactNavigationNewFrameTracking(Promise promise) { + this.impl.initNativeReactNavigationNewFrameTracking(promise); + } + + @Override + public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { + this.impl.initNativeSdk(rnOptions, promise); + } + + @Override + public void crash() { + this.impl.crash(); + } + + @Override + public void fetchModules(Promise promise) { + this.impl.fetchModules(promise); + } + + @Override + public void fetchNativeRelease(Promise promise) { + this.impl.fetchNativeRelease(promise); + } + + @Override + public void fetchNativeAppStart(Promise promise) { + this.impl.fetchNativeAppStart(promise); + } + + @Override + public void fetchNativeFrames(Promise promise) { + this.impl.fetchNativeFrames(promise); + } + + @Override + public void captureEnvelope(String rawBytes, ReadableMap options, Promise promise) { + this.impl.captureEnvelope(rawBytes, options, promise); + } + + @Override + public void captureScreenshot(Promise promise) { + this.impl.captureScreenshot(promise); + } + + @Override + public void fetchViewHierarchy(Promise promise) { + this.impl.fetchViewHierarchy(promise); + } + + @Override + public void setUser(final ReadableMap user, final ReadableMap otherUserKeys) { + this.impl.setUser(user, otherUserKeys); + } + + @Override + public void addBreadcrumb(final ReadableMap breadcrumb) { + this.impl.addBreadcrumb(breadcrumb); + } + + @Override + public void clearBreadcrumbs() { + this.impl.clearBreadcrumbs(); + } + + @Override + public void setExtra(String key, String extra) { + this.impl.setExtra(key, extra); + } + + @Override + public void setContext(final String key, final ReadableMap context) { + this.impl.setContext(key, context); + } + + @Override + public void setTag(String key, String value) { + this.impl.setTag(key, value); + } + + @Override + public void closeNativeSdk(Promise promise) { + this.impl.closeNativeSdk(promise); + } + + @Override + public void enableNativeFramesTracking() { + this.impl.enableNativeFramesTracking(); + } + + @Override + public void disableNativeFramesTracking() { + this.impl.disableNativeFramesTracking(); + } + + @Override + public void fetchNativeDeviceContexts(Promise promise) { + this.impl.fetchNativeDeviceContexts(promise); + } + + @Override + public void fetchNativeSdkInfo(Promise promise) { + this.impl.fetchNativeSdkInfo(promise); + } + + @Override + public WritableMap startProfiling(boolean platformProfilers) { + return this.impl.startProfiling(platformProfilers); + } + + @Override + public WritableMap stopProfiling() { + return this.impl.stopProfiling(); + } + + @Override + public String fetchNativePackageName() { + return this.impl.fetchNativePackageName(); + } + + @Override + public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) { + // Not used on Android + return null; + } + + @Override + public void captureReplay(boolean isHardCrash, Promise promise) { + this.impl.captureReplay(isHardCrash, promise); + } + + @Override + public String getCurrentReplayId() { + return this.impl.getCurrentReplayId(); + } + + @Override + public void crashedLastRun(Promise promise) { + this.impl.crashedLastRun(promise); + } } diff --git a/packages/core/android/src/oldarch/java/io/sentry/react/RNSentryModule.java b/packages/core/android/src/oldarch/java/io/sentry/react/RNSentryModule.java index 6b135ecb9..c77d4218d 100644 --- a/packages/core/android/src/oldarch/java/io/sentry/react/RNSentryModule.java +++ b/packages/core/android/src/oldarch/java/io/sentry/react/RNSentryModule.java @@ -1,176 +1,175 @@ package io.sentry.react; -import com.facebook.react.bridge.ReadableArray; -import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; public class RNSentryModule extends ReactContextBaseJavaModule { - private final RNSentryModuleImpl impl; - - RNSentryModule(ReactApplicationContext reactContext) { - super(reactContext); - this.impl = new RNSentryModuleImpl(reactContext); - } - - @Override - public String getName() { - return RNSentryModuleImpl.NAME; - } - - @ReactMethod - public void addListener(String eventType) { - this.impl.addListener(eventType); - } - - @ReactMethod - public void removeListeners(double id) { - this.impl.removeListeners(id); - } - - @ReactMethod - public void initNativeReactNavigationNewFrameTracking(Promise promise) { - this.impl.initNativeReactNavigationNewFrameTracking(promise); - } - - @ReactMethod - public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { - this.impl.initNativeSdk(rnOptions, promise); - } - - @ReactMethod - public void crash() { - this.impl.crash(); - } - - @ReactMethod - public void fetchModules(Promise promise) { - this.impl.fetchModules(promise); - } - - @ReactMethod - public void fetchNativeRelease(Promise promise) { - this.impl.fetchNativeRelease(promise); - } - - @ReactMethod - public void fetchNativeAppStart(Promise promise) { - this.impl.fetchNativeAppStart(promise); - } - - @ReactMethod - public void fetchNativeFrames(Promise promise) { - this.impl.fetchNativeFrames(promise); - } - - @ReactMethod - public void captureEnvelope(String rawBytes, ReadableMap options, Promise promise) { - this.impl.captureEnvelope(rawBytes, options, promise); - } - - @ReactMethod - public void captureScreenshot(Promise promise) { - this.impl.captureScreenshot(promise); - } - - @ReactMethod - public void fetchViewHierarchy(Promise promise){ - this.impl.fetchViewHierarchy(promise); - } - - @ReactMethod - public void setUser(final ReadableMap user, final ReadableMap otherUserKeys) { - this.impl.setUser(user, otherUserKeys); - } - - @ReactMethod - public void addBreadcrumb(final ReadableMap breadcrumb) { - this.impl.addBreadcrumb(breadcrumb); - } - - @ReactMethod - public void clearBreadcrumbs() { - this.impl.clearBreadcrumbs(); - } - - @ReactMethod - public void setExtra(String key, String extra) { - this.impl.setExtra(key, extra); - } - - @ReactMethod - public void setContext(final String key, final ReadableMap context) { - this.impl.setContext(key, context); - } - - @ReactMethod - public void setTag(String key, String value) { - this.impl.setTag(key, value); - } - - @ReactMethod - public void closeNativeSdk(Promise promise) { - this.impl.closeNativeSdk(promise); - } - - @ReactMethod - public void enableNativeFramesTracking() { - this.impl.enableNativeFramesTracking(); - } - - @ReactMethod - public void disableNativeFramesTracking() { - this.impl.disableNativeFramesTracking(); - } - - @ReactMethod - public void fetchNativeDeviceContexts(Promise promise) { - this.impl.fetchNativeDeviceContexts(promise); - } - - @ReactMethod - public void fetchNativeSdkInfo(Promise promise) { - this.impl.fetchNativeSdkInfo(promise); - } - - @ReactMethod(isBlockingSynchronousMethod = true) - public WritableMap startProfiling(boolean platformProfilers) { - return this.impl.startProfiling(platformProfilers); - } - - @ReactMethod(isBlockingSynchronousMethod = true) - public WritableMap stopProfiling() { - return this.impl.stopProfiling(); - } - - @ReactMethod(isBlockingSynchronousMethod = true) - public String fetchNativePackageName() { - return this.impl.fetchNativePackageName(); - } - - @ReactMethod(isBlockingSynchronousMethod = true) - public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) { - // Not used on Android - return null; - } - - @ReactMethod - public void captureReplay(boolean isHardCrash, Promise promise) { - this.impl.captureReplay(isHardCrash, promise); - } - - @ReactMethod(isBlockingSynchronousMethod = true) - public String getCurrentReplayId() { - return this.impl.getCurrentReplayId(); - } - - @ReactMethod - public void crashedLastRun(Promise promise) { - this.impl.crashedLastRun(promise); - } + private final RNSentryModuleImpl impl; + + RNSentryModule(ReactApplicationContext reactContext) { + super(reactContext); + this.impl = new RNSentryModuleImpl(reactContext); + } + + @Override + public String getName() { + return RNSentryModuleImpl.NAME; + } + + @ReactMethod + public void addListener(String eventType) { + this.impl.addListener(eventType); + } + + @ReactMethod + public void removeListeners(double id) { + this.impl.removeListeners(id); + } + + @ReactMethod + public void initNativeReactNavigationNewFrameTracking(Promise promise) { + this.impl.initNativeReactNavigationNewFrameTracking(promise); + } + + @ReactMethod + public void initNativeSdk(final ReadableMap rnOptions, Promise promise) { + this.impl.initNativeSdk(rnOptions, promise); + } + + @ReactMethod + public void crash() { + this.impl.crash(); + } + + @ReactMethod + public void fetchModules(Promise promise) { + this.impl.fetchModules(promise); + } + + @ReactMethod + public void fetchNativeRelease(Promise promise) { + this.impl.fetchNativeRelease(promise); + } + + @ReactMethod + public void fetchNativeAppStart(Promise promise) { + this.impl.fetchNativeAppStart(promise); + } + + @ReactMethod + public void fetchNativeFrames(Promise promise) { + this.impl.fetchNativeFrames(promise); + } + + @ReactMethod + public void captureEnvelope(String rawBytes, ReadableMap options, Promise promise) { + this.impl.captureEnvelope(rawBytes, options, promise); + } + + @ReactMethod + public void captureScreenshot(Promise promise) { + this.impl.captureScreenshot(promise); + } + + @ReactMethod + public void fetchViewHierarchy(Promise promise) { + this.impl.fetchViewHierarchy(promise); + } + + @ReactMethod + public void setUser(final ReadableMap user, final ReadableMap otherUserKeys) { + this.impl.setUser(user, otherUserKeys); + } + + @ReactMethod + public void addBreadcrumb(final ReadableMap breadcrumb) { + this.impl.addBreadcrumb(breadcrumb); + } + + @ReactMethod + public void clearBreadcrumbs() { + this.impl.clearBreadcrumbs(); + } + + @ReactMethod + public void setExtra(String key, String extra) { + this.impl.setExtra(key, extra); + } + + @ReactMethod + public void setContext(final String key, final ReadableMap context) { + this.impl.setContext(key, context); + } + + @ReactMethod + public void setTag(String key, String value) { + this.impl.setTag(key, value); + } + + @ReactMethod + public void closeNativeSdk(Promise promise) { + this.impl.closeNativeSdk(promise); + } + + @ReactMethod + public void enableNativeFramesTracking() { + this.impl.enableNativeFramesTracking(); + } + + @ReactMethod + public void disableNativeFramesTracking() { + this.impl.disableNativeFramesTracking(); + } + + @ReactMethod + public void fetchNativeDeviceContexts(Promise promise) { + this.impl.fetchNativeDeviceContexts(promise); + } + + @ReactMethod + public void fetchNativeSdkInfo(Promise promise) { + this.impl.fetchNativeSdkInfo(promise); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public WritableMap startProfiling(boolean platformProfilers) { + return this.impl.startProfiling(platformProfilers); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public WritableMap stopProfiling() { + return this.impl.stopProfiling(); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public String fetchNativePackageName() { + return this.impl.fetchNativePackageName(); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public WritableMap fetchNativeStackFramesBy(ReadableArray instructionsAddr) { + // Not used on Android + return null; + } + + @ReactMethod + public void captureReplay(boolean isHardCrash, Promise promise) { + this.impl.captureReplay(isHardCrash, promise); + } + + @ReactMethod(isBlockingSynchronousMethod = true) + public String getCurrentReplayId() { + return this.impl.getCurrentReplayId(); + } + + @ReactMethod + public void crashedLastRun(Promise promise) { + this.impl.crashedLastRun(promise); + } } diff --git a/samples/react-native/android/app/src/main/java/io/sentry/reactnative/sample/AssetsModule.java b/samples/react-native/android/app/src/main/java/io/sentry/reactnative/sample/AssetsModule.java index 57a732c2e..f5cba7960 100644 --- a/samples/react-native/android/app/src/main/java/io/sentry/reactnative/sample/AssetsModule.java +++ b/samples/react-native/android/app/src/main/java/io/sentry/reactnative/sample/AssetsModule.java @@ -6,36 +6,35 @@ import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableNativeArray; - import java.io.InputStream; public class AssetsModule extends ReactContextBaseJavaModule { - AssetsModule(ReactApplicationContext context) { - super(context); - } + AssetsModule(ReactApplicationContext context) { + super(context); + } - @Override - public String getName() { - return "AssetsModule"; - } + @Override + public String getName() { + return "AssetsModule"; + } - @ReactMethod - public void getExampleAssetData(Promise promise) { - try { - InputStream stream = this.getReactApplicationContext().getResources().getAssets() - .open("logo_mini.png"); - int size = stream.available(); - byte[] buffer = new byte[size]; - stream.read(buffer); - stream.close(); - WritableArray array = new WritableNativeArray(); - for (int i = 0; i < size; i++) { - array.pushInt(buffer[i]); - } - promise.resolve(array); - } catch (Exception e) { - promise.reject(e); - } + @ReactMethod + public void getExampleAssetData(Promise promise) { + try { + InputStream stream = + this.getReactApplicationContext().getResources().getAssets().open("logo_mini.png"); + int size = stream.available(); + byte[] buffer = new byte[size]; + stream.read(buffer); + stream.close(); + WritableArray array = new WritableNativeArray(); + for (int i = 0; i < size; i++) { + array.pushInt(buffer[i]); + } + promise.resolve(array); + } catch (Exception e) { + promise.reject(e); } + } } diff --git a/samples/react-native/android/app/src/main/java/io/sentry/reactnative/sample/SamplePackage.java b/samples/react-native/android/app/src/main/java/io/sentry/reactnative/sample/SamplePackage.java index e98a2d614..84964536c 100644 --- a/samples/react-native/android/app/src/main/java/io/sentry/reactnative/sample/SamplePackage.java +++ b/samples/react-native/android/app/src/main/java/io/sentry/reactnative/sample/SamplePackage.java @@ -1,77 +1,81 @@ package io.sentry.reactnative.sample; + import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.uimanager.ViewManager; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import io.sentry.ILogger; import io.sentry.SentryLevel; import io.sentry.android.core.AndroidLogger; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; public class SamplePackage implements ReactPackage { - private static final ILogger logger = new AndroidLogger("SamplePackage"); - - static { - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - System.loadLibrary("appmodules"); - } - } - - public native void crash(); + private static final ILogger logger = new AndroidLogger("SamplePackage"); - @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.emptyList(); + static { + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + System.loadLibrary("appmodules"); } - - @Override - public List createNativeModules( - ReactApplicationContext reactContext) { - List modules = new ArrayList<>(); - - modules.add(new AssetsModule(reactContext)); - - modules.add(new ReactContextBaseJavaModule() { - @Override public String getName() { - return "CppModule"; - } - - @ReactMethod public void crashCpp() { - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { - crash(); - } else { - logger.log(SentryLevel.WARNING, "Enable RNNA to try this."); - } + } + + public native void crash(); + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @Override + public List createNativeModules(ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + + modules.add(new AssetsModule(reactContext)); + + modules.add( + new ReactContextBaseJavaModule() { + @Override + public String getName() { + return "CppModule"; + } + + @ReactMethod + public void crashCpp() { + if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + crash(); + } else { + logger.log(SentryLevel.WARNING, "Enable RNNA to try this."); } + } }); - modules.add(new ReactContextBaseJavaModule() { - @Override public String getName() { - return "CrashModule"; - } - - @ReactMethod public void crashOrUndefined() { - this.crashNow(); - } - - @ReactMethod public int crashOrNumber() { - this.crashNow(); - return 42; - } - - private void crashNow() { - throw new RuntimeException("CrashModule.crashNow()"); - } + modules.add( + new ReactContextBaseJavaModule() { + @Override + public String getName() { + return "CrashModule"; + } + + @ReactMethod + public void crashOrUndefined() { + this.crashNow(); + } + + @ReactMethod + public int crashOrNumber() { + this.crashNow(); + return 42; + } + + private void crashNow() { + throw new RuntimeException("CrashModule.crashNow()"); + } }); - return modules; - } - + return modules; + } } diff --git a/scripts/google-java-format.sh b/scripts/google-java-format.sh new file mode 100755 index 000000000..fa277a34a --- /dev/null +++ b/scripts/google-java-format.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +# Array of glob patterns +glob_patterns=( + "samples/react-native/android/app/src/**/*.java" + "packages/core/android/**/*.java" + "performance-tests/TestAppPlain/android/app/src/**/*.java" + "performance-tests/TestAppSentry/android/app/src/**/*.java" +) + +# Check if an argument is provided +if [ $# -eq 0 ]; then + echo "Usage: $0 " + exit 1 +fi + +# Set the mode based on the first argument +mode=$1 + +# Base command +base_cmd="npx google-java-format" + +# Add --replace flag if mode is 'fix' +if [ "$mode" = "fix" ]; then + base_cmd+=" --replace" +elif [ "$mode" = "lint" ]; then + base_cmd+=" --set-exit-if-changed" + if [ "$CI" = "true" ]; then + echo "Running in CI mode, replacing files." + base_cmd+=" --replace" + fi +else + echo "Invalid mode. Use 'fix' or 'lint'." + exit 1 +fi + +# Variable to track if any command failed +any_failed=0 + +# Loop through the glob patterns and execute the command +for pattern in "${glob_patterns[@]}"; do + cmd="$base_cmd --glob='$pattern'" + echo "Executing: $cmd" + eval $cmd >> /dev/null + + # Check the exit status of the command + if [ $? -ne 0 ]; then + echo "Command failed: $cmd" + any_failed=1 + fi +done + +if [ "$CI" = "true" ]; then + # Print git patch for currently changed tracked files + echo "Printing git patch for currently changed tracked files:" + git diff --patch --exit-code || true +fi + +# Exit with code 1 if any command failed +if [ $any_failed -eq 1 ]; then + echo "One or more commands failed." + exit 1 +fi + +echo "All commands completed successfully." +exit 0 diff --git a/yarn.lock b/yarn.lock index 9f3fd9e7b..05c5e867a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15182,6 +15182,19 @@ __metadata: languageName: node linkType: hard +"google-java-format@npm:^1.4.0": + version: 1.4.0 + resolution: "google-java-format@npm:1.4.0" + dependencies: + async: ^3.2.4 + glob: ^8.1.0 + resolve: ^1.22.8 + bin: + google-java-format: index.js + checksum: 66043dce2152d1195c3d01bdea5533ce0d2786c1f66bb96a553d7da9935970aeb63d9536f2f48af1a1b90be42e1aeeeaabb2f86bbe50622f45bf92a19d0fa286 + languageName: node + linkType: hard + "gopd@npm:^1.0.1": version: 1.0.1 resolution: "gopd@npm:1.0.1" @@ -23865,7 +23878,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.10.1#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.18.1#~builtin, resolve@patch:resolve@npm%3A^1.1.6#~builtin, resolve@patch:resolve@npm%3A^1.10.0#~builtin, resolve@patch:resolve@npm%3A^1.14.2#~builtin, resolve@patch:resolve@npm%3A^1.20.0#~builtin, resolve@patch:resolve@npm%3A^1.21.0#~builtin, resolve@patch:resolve@npm%3A^1.22.2#~builtin, resolve@patch:resolve@npm%3A^1.22.4#~builtin, resolve@patch:resolve@npm%3A^1.22.8#~builtin": +"resolve@patch:resolve@^1.10.1#~builtin, resolve@patch:resolve@^1.12.0#~builtin, resolve@patch:resolve@^1.18.1#~builtin, resolve@patch:resolve@^1.22.8#~builtin, resolve@patch:resolve@npm%3A^1.1.6#~builtin, resolve@patch:resolve@npm%3A^1.10.0#~builtin, resolve@patch:resolve@npm%3A^1.14.2#~builtin, resolve@patch:resolve@npm%3A^1.20.0#~builtin, resolve@patch:resolve@npm%3A^1.21.0#~builtin, resolve@patch:resolve@npm%3A^1.22.2#~builtin, resolve@patch:resolve@npm%3A^1.22.4#~builtin, resolve@patch:resolve@npm%3A^1.22.8#~builtin": version: 1.22.8 resolution: "resolve@patch:resolve@npm%3A1.22.8#~builtin::version=1.22.8&hash=c3c19d" dependencies: @@ -24515,6 +24528,7 @@ __metadata: dependencies: "@sentry/cli": 2.37.0 downlevel-dts: ^0.11.0 + google-java-format: ^1.4.0 lerna: ^8.1.8 npm-run-all2: ^6.2.2 prettier: ^2.0.5