Skip to content

Commit 69b6731

Browse files
authored
Merge 7788b81 into 99410e9
2 parents 99410e9 + 7788b81 commit 69b6731

File tree

7 files changed

+231
-28
lines changed

7 files changed

+231
-28
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
- Rename `navigation.processing` span to more expressive `Navigation dispatch to screen A mounted/navigation cancelled` ([#4423](https://github.com/getsentry/sentry-react-native/pull/4423))
1919
- Add RN SDK package to `sdk.packages` for Cocoa ([#4381](https://github.com/getsentry/sentry-react-native/pull/4381))
20+
- Add experimental initialization using `sentry.options.json` and `RNSentrySDK.startWithOptions` method for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
2021

2122
### Internal
2223

packages/core/RNSentryAndroidTester/app/src/androidTest/java/io/sentry/rnsentryandroidtester/RNSentryMapConverterTest.kt

+40
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import android.content.Context
44
import androidx.test.ext.junit.runners.AndroidJUnit4
55
import androidx.test.platform.app.InstrumentationRegistry
66
import com.facebook.react.bridge.Arguments
7+
import com.facebook.react.bridge.JavaOnlyMap
78
import com.facebook.soloader.SoLoader
89
import io.sentry.react.RNSentryMapConverter
10+
import org.json.JSONObject
911
import org.junit.Assert.assertEquals
12+
import org.junit.Assert.assertNotNull
1013
import org.junit.Assert.assertNull
14+
import org.junit.Assert.assertTrue
1115
import org.junit.Before
1216
import org.junit.Test
1317
import org.junit.runner.RunWith
@@ -359,4 +363,40 @@ class MapConverterTest {
359363

360364
assertEquals(actual, expectedMap1)
361365
}
366+
367+
@Test
368+
fun testJsonObjectToReadableMap() {
369+
val json =
370+
JSONObject().apply {
371+
put("stringKey", "stringValue")
372+
put("booleanKey", true)
373+
put("intKey", 123)
374+
}
375+
376+
val result = RNSentryMapConverter.jsonObjectToReadableMap(json)
377+
378+
assertNotNull(result)
379+
assertTrue(result is JavaOnlyMap)
380+
assertEquals("stringValue", result.getString("stringKey"))
381+
assertEquals(true, result.getBoolean("booleanKey"))
382+
assertEquals(123, result.getInt("intKey"))
383+
}
384+
385+
@Test
386+
fun testMapToReadableMap() {
387+
val map =
388+
mapOf(
389+
"stringKey" to "stringValue",
390+
"booleanKey" to true,
391+
"intKey" to 123,
392+
)
393+
394+
val result = RNSentryMapConverter.mapToReadableMap(map)
395+
396+
assertNotNull(result)
397+
assertTrue(result is JavaOnlyMap)
398+
assertEquals("stringValue", result.getString("stringKey"))
399+
assertEquals(true, result.getBoolean("booleanKey"))
400+
assertEquals(123, result.getInt("intKey"))
401+
}
362402
}

packages/core/android/src/main/java/io/sentry/react/RNSentryMapConverter.java

+38
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.sentry.react;
22

33
import com.facebook.react.bridge.Arguments;
4+
import com.facebook.react.bridge.JavaOnlyMap;
45
import com.facebook.react.bridge.ReadableArray;
56
import com.facebook.react.bridge.ReadableMap;
67
import com.facebook.react.bridge.WritableArray;
@@ -10,9 +11,13 @@
1011
import io.sentry.android.core.AndroidLogger;
1112
import java.math.BigDecimal;
1213
import java.math.BigInteger;
14+
import java.util.HashMap;
15+
import java.util.Iterator;
1316
import java.util.List;
1417
import java.util.Map;
1518
import org.jetbrains.annotations.Nullable;
19+
import org.json.JSONException;
20+
import org.json.JSONObject;
1621

1722
public final class RNSentryMapConverter {
1823
public static final String NAME = "RNSentry.MapConverter";
@@ -131,4 +136,37 @@ private static void addValueToWritableMap(WritableMap writableMap, String key, O
131136
logger.log(SentryLevel.ERROR, "Could not convert object" + value);
132137
}
133138
}
139+
140+
public static ReadableMap jsonObjectToReadableMap(JSONObject jsonObject) {
141+
Map<String, Object> map = jsonObjectToMap(jsonObject);
142+
return mapToReadableMap(map);
143+
}
144+
145+
public static ReadableMap mapToReadableMap(Map<String, Object> map) {
146+
// We are not directly using `convertToWritable` since `Arguments.createArray()`
147+
// fails before bridge initialisation
148+
Object[] keysAndValues = new Object[map.size() * 2];
149+
int index = 0;
150+
for (Map.Entry<String, Object> entry : map.entrySet()) {
151+
keysAndValues[index++] = entry.getKey();
152+
keysAndValues[index++] = entry.getValue();
153+
}
154+
return JavaOnlyMap.of(keysAndValues);
155+
}
156+
157+
private static Map<String, Object> jsonObjectToMap(JSONObject jsonObject) {
158+
Map<String, Object> map = new HashMap<>();
159+
Iterator<String> keys = jsonObject.keys();
160+
while (keys.hasNext()) {
161+
String key = keys.next();
162+
Object value = null;
163+
try {
164+
value = jsonObject.get(key);
165+
} catch (JSONException e) {
166+
throw new RuntimeException(e);
167+
}
168+
map.put(key, value);
169+
}
170+
return map;
171+
}
134172
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package io.sentry.react;
2+
3+
import android.content.Context;
4+
import com.facebook.react.bridge.ReadableMap;
5+
import io.sentry.ILogger;
6+
import io.sentry.SentryLevel;
7+
import io.sentry.android.core.AndroidLogger;
8+
import java.io.BufferedReader;
9+
import java.io.InputStream;
10+
import java.io.InputStreamReader;
11+
import java.util.Map;
12+
import org.jetbrains.annotations.NotNull;
13+
import org.json.JSONObject;
14+
15+
public final class RNSentrySDK {
16+
private static final String CONFIGURATION_FILE = "sentry.options.json";
17+
private static final String NAME = "RNSentrySDK";
18+
19+
private static final ILogger logger = new AndroidLogger(NAME);
20+
21+
private RNSentrySDK() {
22+
throw new AssertionError("Utility class should not be instantiated");
23+
}
24+
25+
/**
26+
* Start the Native Android SDK with the provided options
27+
*
28+
* @param context Android Context
29+
* @param options Map with options
30+
*/
31+
public static void init(
32+
@NotNull final Context context, @NotNull final Map<String, Object> options) {
33+
ReadableMap rnOptions = RNSentryMapConverter.mapToReadableMap(options);
34+
RNSentryStart.startWithOptions(context, rnOptions, null, logger);
35+
}
36+
37+
/**
38+
* Start the Native Android SDK with options from `sentry.options.json` configuration file
39+
*
40+
* @param context Android Context
41+
*/
42+
public static void init(@NotNull final Context context) {
43+
try {
44+
JSONObject jsonObject = getOptionsFromConfigurationFile(context);
45+
ReadableMap rnOptions = RNSentryMapConverter.jsonObjectToReadableMap(jsonObject);
46+
RNSentryStart.startWithOptions(context, rnOptions, null, logger);
47+
} catch (Exception e) {
48+
logger.log(
49+
SentryLevel.ERROR, "Failed to start Sentry with options from configuration file.", e);
50+
throw new RuntimeException(e);
51+
}
52+
}
53+
54+
private static JSONObject getOptionsFromConfigurationFile(Context context) {
55+
try (InputStream inputStream = context.getAssets().open(CONFIGURATION_FILE);
56+
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
57+
58+
StringBuilder stringBuilder = new StringBuilder();
59+
String line;
60+
while ((line = reader.readLine()) != null) {
61+
stringBuilder.append(line);
62+
}
63+
String configFileContent = stringBuilder.toString();
64+
return new JSONObject(configFileContent);
65+
66+
} catch (Exception e) {
67+
logger.log(
68+
SentryLevel.ERROR,
69+
"Failed to read configuration file. Please make sure "
70+
+ CONFIGURATION_FILE
71+
+ " exists in the root of your project.",
72+
e);
73+
return null;
74+
}
75+
}
76+
}

packages/core/sentry.gradle

+46-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import org.apache.tools.ant.taskdefs.condition.Os
33
import java.util.regex.Matcher
44
import java.util.regex.Pattern
55

6-
project.ext.shouldSentryAutoUploadNative = { ->
6+
project.ext.shouldSentryAutoUploadNative = { ->
77
return System.getenv('SENTRY_DISABLE_NATIVE_DEBUG_UPLOAD') != 'true'
88
}
99

@@ -17,7 +17,52 @@ project.ext.shouldSentryAutoUpload = { ->
1717

1818
def config = project.hasProperty("sentryCli") ? project.sentryCli : [];
1919

20+
def configFile = "sentry.options.json" // Sentry condiguration file
21+
def androidAssetsDir = new File("$rootDir/app/src/main/assets") // Path to Android assets folder
22+
23+
tasks.register("copySentryJsonConfiguration") {
24+
doLast {
25+
def appRoot = project.rootDir.parentFile ?: project.rootDir
26+
def sentryOptionsFile = new File(appRoot, configFile)
27+
if (sentryOptionsFile.exists()) {
28+
if (!androidAssetsDir.exists()) {
29+
androidAssetsDir.mkdirs()
30+
}
31+
32+
copy {
33+
from sentryOptionsFile
34+
into androidAssetsDir
35+
rename { String fileName -> configFile }
36+
}
37+
logger.lifecycle("Copied ${configFile} to Android assets")
38+
} else {
39+
logger.warn("${configFile} not found in app root (${appRoot})")
40+
}
41+
}
42+
}
43+
44+
tasks.register("cleanupTemporarySentryJsonConfiguration") {
45+
doLast {
46+
def sentryOptionsFile = new File(androidAssetsDir, configFile)
47+
if (sentryOptionsFile.exists()) {
48+
logger.lifecycle("Deleting temporary file: ${sentryOptionsFile.path}")
49+
sentryOptionsFile.delete()
50+
}
51+
}
52+
}
53+
2054
gradle.projectsEvaluated {
55+
// Add a task that copies the sentry.options.json file before the build starts
56+
tasks.named("preBuild").configure {
57+
dependsOn("copySentryJsonConfiguration")
58+
}
59+
// Cleanup sentry.options.json from assets after the build
60+
tasks.matching { task ->
61+
task.name == "build" || task.name.startsWith("assemble") || task.name.startsWith("install")
62+
}.configureEach {
63+
finalizedBy("cleanupTemporarySentryJsonConfiguration")
64+
}
65+
2166
def releases = extractReleasesInfo()
2267

2368
if (config.flavorAware && config.sentryProperties) {

samples/react-native/android/app/src/main/java/io/sentry/reactnative/sample/MainApplication.kt

+10-27
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,7 @@ import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
1111
import com.facebook.react.defaults.DefaultReactNativeHost
1212
import com.facebook.react.soloader.OpenSourceMergedSoMapping
1313
import com.facebook.soloader.SoLoader
14-
import io.sentry.Hint
15-
import io.sentry.SentryEvent
16-
import io.sentry.SentryOptions.BeforeSendCallback
17-
import io.sentry.android.core.SentryAndroid
14+
import io.sentry.react.RNSentrySDK
1815

1916
class MainApplication :
2017
Application(),
@@ -51,28 +48,14 @@ class MainApplication :
5148
}
5249

5350
private fun initializeSentry() {
54-
SentryAndroid.init(this) { options ->
55-
// Only options set here will apply to the Android SDK
56-
// Options from JS are not passed to the Android SDK when initialized manually
57-
options.dsn = "https://[email protected]/5428561"
58-
options.isDebug = true
59-
60-
options.beforeSend =
61-
BeforeSendCallback { event: SentryEvent, hint: Hint? ->
62-
// React native internally throws a JavascriptException
63-
// Since we catch it before that, we don't want to send this one
64-
// because we would send it twice
65-
try {
66-
val ex = event.exceptions!![0]
67-
if (null != ex && ex.type!!.contains("JavascriptException")) {
68-
return@BeforeSendCallback null
69-
}
70-
} catch (ignored: Throwable) {
71-
// We do nothing
72-
}
73-
74-
event
75-
}
76-
}
51+
// RNSentrySDK.init(
52+
// this,
53+
// mapOf(
54+
// "dsn" to "https://[email protected]/5428561",
55+
// "debug" to true,
56+
// ),
57+
// )
58+
59+
RNSentrySDK.init(this)
7760
}
7861
}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"dsn": "https://[email protected]/5428561",
3+
"debug": true,
4+
"environment": "dev",
5+
"enableUserInteractionTracing": true,
6+
"enableAutoSessionTracking": true,
7+
"sessionTrackingIntervalMillis": 30000,
8+
"enableTracing": true,
9+
"tracesSampleRate": 1.0,
10+
"attachStacktrace": true,
11+
"attachScreenshot": true,
12+
"attachViewHierarchy": true,
13+
"enableCaptureFailedRequests": true,
14+
"_release": "[email protected]+1",
15+
"_dist": 1,
16+
"profilesSampleRate": 1.0,
17+
"replaysSessionSampleRate": 1.0,
18+
"replaysOnErrorSampleRate": 1.0,
19+
"spotlight": true
20+
}

0 commit comments

Comments
 (0)