Skip to content

Commit 1a89392

Browse files
Merge 15a7e6d into a18210a
2 parents a18210a + 15a7e6d commit 1a89392

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+2625
-759
lines changed

CHANGELOG.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,58 @@
88
99
## Unreleased
1010

11+
### Features
12+
13+
- Capture App Start errors and crashes by initializing Sentry from `sentry.options.json` ([#4472](https://github.com/getsentry/sentry-react-native/pull/4472))
14+
15+
Create `sentry.options.json` in the React Native project root and set options the same as you currently have in `Sentry.init` in JS.
16+
17+
```json
18+
{
19+
"dsn": "https://key@example.io/value",
20+
}
21+
```
22+
23+
Initialize Sentry on the native layers by newly provided native methods.
24+
25+
```kotlin
26+
import io.sentry.react.RNSentrySDK
27+
28+
class MainApplication : Application(), ReactApplication {
29+
override fun onCreate() {
30+
super.onCreate()
31+
RNSentrySDK.init(this)
32+
}
33+
}
34+
```
35+
36+
```obj-c
37+
#import <RNSentry/RNSentry.h>
38+
39+
@implementation AppDelegate
40+
- (BOOL)application:(UIApplication *)application
41+
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
42+
{
43+
[RNSentrySDK start];
44+
return [super application:application didFinishLaunchingWithOptions:launchOptions];
45+
}
46+
@end
47+
```
48+
49+
### Changes
50+
51+
- Load `optionsFile` into the JS bundle during Metro bundle process ([#4476](https://github.com/getsentry/sentry-react-native/pull/4476))
52+
- Add experimental version of `startWithConfigureOptions` for Apple platforms ([#4444](https://github.com/getsentry/sentry-react-native/pull/4444))
53+
- Add experimental version of `init` with optional `OptionsConfiguration<SentryAndroidOptions>` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
54+
- Add initialization using `sentry.options.json` for Apple platforms ([#4447](https://github.com/getsentry/sentry-react-native/pull/4447))
55+
- Add initialization using `sentry.options.json` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
56+
- Merge options from file with `Sentry.init` options in JS ([#4510](https://github.com/getsentry/sentry-react-native/pull/4510))
57+
58+
### Internal
59+
60+
- Extract iOS native initialization to standalone structures ([#4442](https://github.com/getsentry/sentry-react-native/pull/4442))
61+
- Extract Android native initialization to standalone structures ([#4445](https://github.com/getsentry/sentry-react-native/pull/4445))
62+
1163
### Dependencies
1264
1365
- Bump JavaScript SDK from v8.53.0 to v8.54.0 ([#4503](https://github.com/getsentry/sentry-react-native/pull/4503))

packages/core/RNSentry.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Pod::Spec.new do |s|
3333
s.preserve_paths = '*.js'
3434

3535
s.source_files = 'ios/**/*.{h,m,mm}'
36-
s.public_header_files = 'ios/RNSentry.h'
36+
s.public_header_files = 'ios/RNSentry.h', 'ios/RNSentrySDK.h'
3737

3838
s.compiler_flags = other_cflags
3939

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"dsn": "invalid-dsn"
3+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
invalid-options
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dsn": "https://abcd@efgh.ingest.sentry.io/123456",
3+
"enableTracing": true,
4+
"tracesSampleRate": 1.0
5+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package io.sentry.react
2+
3+
import androidx.test.ext.junit.runners.AndroidJUnit4
4+
import com.facebook.react.bridge.WritableArray
5+
import com.facebook.react.bridge.WritableMap
6+
import io.sentry.react.RNSentryJsonConverter.convertToWritable
7+
import org.json.JSONArray
8+
import org.json.JSONObject
9+
import org.junit.Assert.assertEquals
10+
import org.junit.Assert.assertNotNull
11+
import org.junit.Assert.assertNull
12+
import org.junit.Test
13+
import org.junit.runner.RunWith
14+
15+
@RunWith(AndroidJUnit4::class)
16+
class RNSentryJsonConverterTest {
17+
@Test
18+
fun testConvertToWritableWithSimpleJsonObject() {
19+
val jsonObject =
20+
JSONObject().apply {
21+
put("floatKey", 12.3f)
22+
put("doubleKey", 12.3)
23+
put("intKey", 123)
24+
put("stringKey", "test")
25+
put("nullKey", JSONObject.NULL)
26+
}
27+
28+
val result: WritableMap? = convertToWritable(jsonObject)
29+
30+
assertNotNull(result)
31+
assertEquals(12.3, result!!.getDouble("floatKey"), 0.0001)
32+
assertEquals(12.3, result.getDouble("doubleKey"), 0.0)
33+
assertEquals(123, result.getInt("intKey"))
34+
assertEquals("test", result.getString("stringKey"))
35+
assertNull(result.getString("nullKey"))
36+
}
37+
38+
@Test
39+
fun testConvertToWritableWithNestedJsonObject() {
40+
val jsonObject =
41+
JSONObject().apply {
42+
put(
43+
"nested",
44+
JSONObject().apply {
45+
put("key", "value")
46+
},
47+
)
48+
}
49+
50+
val result: WritableMap? = convertToWritable(jsonObject)
51+
52+
assertNotNull(result)
53+
val nestedMap = result!!.getMap("nested")
54+
assertNotNull(nestedMap)
55+
assertEquals("value", nestedMap!!.getString("key"))
56+
}
57+
58+
@Test
59+
fun testConvertToWritableWithJsonArray() {
60+
val jsonArray =
61+
JSONArray().apply {
62+
put(1)
63+
put(2.5)
64+
put("string")
65+
put(JSONObject.NULL)
66+
}
67+
68+
val result: WritableArray = convertToWritable(jsonArray)
69+
70+
assertEquals(1, result.getInt(0))
71+
assertEquals(2.5, result.getDouble(1), 0.0)
72+
assertEquals("string", result.getString(2))
73+
assertNull(result.getString(3))
74+
}
75+
76+
@Test
77+
fun testConvertToWritableWithNestedJsonArray() {
78+
val jsonObject =
79+
JSONObject().apply {
80+
put(
81+
"array",
82+
JSONArray().apply {
83+
put(
84+
JSONObject().apply {
85+
put("key1", "value1")
86+
},
87+
)
88+
put(
89+
JSONObject().apply {
90+
put("key2", "value2")
91+
},
92+
)
93+
},
94+
)
95+
}
96+
97+
val result: WritableMap? = convertToWritable(jsonObject)
98+
99+
val array = result?.getArray("array")
100+
assertEquals("value1", array?.getMap(0)?.getString("key1"))
101+
assertEquals("value2", array?.getMap(1)?.getString("key2"))
102+
}
103+
}
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package io.sentry.react
2+
3+
import android.content.Context
4+
import androidx.test.ext.junit.runners.AndroidJUnit4
5+
import androidx.test.platform.app.InstrumentationRegistry
6+
import com.facebook.react.common.JavascriptException
7+
import io.sentry.Hint
8+
import io.sentry.ILogger
9+
import io.sentry.Sentry
10+
import io.sentry.Sentry.OptionsConfiguration
11+
import io.sentry.SentryEvent
12+
import io.sentry.android.core.AndroidLogger
13+
import io.sentry.android.core.SentryAndroidOptions
14+
import io.sentry.protocol.SdkVersion
15+
import org.junit.After
16+
import org.junit.Assert.assertEquals
17+
import org.junit.Assert.assertFalse
18+
import org.junit.Assert.assertNotNull
19+
import org.junit.Assert.assertNull
20+
import org.junit.Assert.assertTrue
21+
import org.junit.Before
22+
import org.junit.Test
23+
import org.junit.runner.RunWith
24+
25+
@RunWith(AndroidJUnit4::class)
26+
class RNSentrySDKTest {
27+
private val logger: ILogger = AndroidLogger(RNSentrySDKTest::class.java.simpleName)
28+
private lateinit var context: Context
29+
30+
companion object {
31+
private const val INITIALISATION_ERROR = "Failed to initialize Sentry's React Native SDK"
32+
private const val VALID_OPTIONS = "sentry.options.json"
33+
private const val INVALID_OPTIONS = "invalid.options.json"
34+
private const val INVALID_JSON = "invalid.options.txt"
35+
private const val MISSING = "non-existing-file"
36+
37+
private val validConfig =
38+
OptionsConfiguration<SentryAndroidOptions> { options ->
39+
options.dsn = "https://abcd@efgh.ingest.sentry.io/123456"
40+
}
41+
private val invalidConfig =
42+
OptionsConfiguration<SentryAndroidOptions> { options ->
43+
options.dsn = "invalid-dsn"
44+
}
45+
private val emptyConfig = OptionsConfiguration<SentryAndroidOptions> {}
46+
}
47+
48+
@Before
49+
fun setUp() {
50+
context = InstrumentationRegistry.getInstrumentation().context
51+
}
52+
53+
@After
54+
fun tearDown() {
55+
Sentry.close()
56+
}
57+
58+
@Test
59+
fun initialisesSuccessfullyWithDefaultValidJsonFile() { // sentry.options.json
60+
RNSentrySDK.init(context)
61+
assertTrue(Sentry.isEnabled())
62+
}
63+
64+
@Test
65+
fun initialisesSuccessfullyWithValidConfigurationAndDefaultValidJsonFile() {
66+
RNSentrySDK.init(context, validConfig)
67+
assertTrue(Sentry.isEnabled())
68+
}
69+
70+
@Test
71+
fun initialisesSuccessfullyWithValidConfigurationAndInvalidJsonFile() {
72+
RNSentrySDK.init(context, validConfig, INVALID_OPTIONS, logger)
73+
assertTrue(Sentry.isEnabled())
74+
}
75+
76+
@Test
77+
fun initialisesSuccessfullyWithValidConfigurationAndMissingJsonFile() {
78+
RNSentrySDK.init(context, validConfig, MISSING, logger)
79+
assertTrue(Sentry.isEnabled())
80+
}
81+
82+
@Test
83+
fun initialisesSuccessfullyWithValidConfigurationAndErrorInParsingJsonFile() {
84+
RNSentrySDK.init(context, validConfig, INVALID_JSON, logger)
85+
assertTrue(Sentry.isEnabled())
86+
}
87+
88+
@Test
89+
fun initialisesSuccessfullyWithNoConfigurationAndValidJsonFile() {
90+
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
91+
assertTrue(Sentry.isEnabled())
92+
}
93+
94+
@Test
95+
fun failsToInitialiseWithNoConfigurationAndInvalidJsonFile() {
96+
try {
97+
RNSentrySDK.init(context, emptyConfig, INVALID_OPTIONS, logger)
98+
} catch (e: Exception) {
99+
assertEquals(INITIALISATION_ERROR, e.message)
100+
}
101+
assertFalse(Sentry.isEnabled())
102+
}
103+
104+
@Test
105+
fun failsToInitialiseWithInvalidConfigAndInvalidJsonFile() {
106+
try {
107+
RNSentrySDK.init(context, invalidConfig, INVALID_OPTIONS, logger)
108+
} catch (e: Exception) {
109+
assertEquals(INITIALISATION_ERROR, e.message)
110+
}
111+
assertFalse(Sentry.isEnabled())
112+
}
113+
114+
@Test
115+
fun failsToInitialiseWithInvalidConfigAndValidJsonFile() {
116+
try {
117+
RNSentrySDK.init(context, invalidConfig, VALID_OPTIONS, logger)
118+
} catch (e: Exception) {
119+
assertEquals(INITIALISATION_ERROR, e.message)
120+
}
121+
assertFalse(Sentry.isEnabled())
122+
}
123+
124+
@Test
125+
fun failsToInitialiseWithInvalidConfigurationAndDefaultValidJsonFile() {
126+
try {
127+
RNSentrySDK.init(context, invalidConfig)
128+
} catch (e: Exception) {
129+
assertEquals(INITIALISATION_ERROR, e.message)
130+
}
131+
assertFalse(Sentry.isEnabled())
132+
}
133+
134+
@Test
135+
fun defaultsAndFinalsAreSetWithValidJsonFile() {
136+
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
137+
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
138+
verifyDefaults(actualOptions)
139+
verifyFinals(actualOptions)
140+
// options file
141+
assert(actualOptions.dsn == "https://abcd@efgh.ingest.sentry.io/123456")
142+
}
143+
144+
@Test
145+
fun defaultsAndFinalsAreSetWithValidConfiguration() {
146+
RNSentrySDK.init(context, validConfig, MISSING, logger)
147+
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
148+
verifyDefaults(actualOptions)
149+
verifyFinals(actualOptions)
150+
// configuration
151+
assert(actualOptions.dsn == "https://abcd@efgh.ingest.sentry.io/123456")
152+
}
153+
154+
@Test
155+
fun defaultsOverrideOptionsJsonFile() {
156+
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
157+
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
158+
assertNull(actualOptions.tracesSampleRate)
159+
assertEquals(false, actualOptions.enableTracing)
160+
}
161+
162+
@Test
163+
fun configurationOverridesDefaultOptions() {
164+
val validConfig =
165+
OptionsConfiguration<SentryAndroidOptions> { options ->
166+
options.dsn = "https://abcd@efgh.ingest.sentry.io/123456"
167+
options.tracesSampleRate = 0.5
168+
options.enableTracing = true
169+
}
170+
RNSentrySDK.init(context, validConfig, MISSING, logger)
171+
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
172+
assertEquals(0.5, actualOptions.tracesSampleRate)
173+
assertEquals(true, actualOptions.enableTracing)
174+
assert(actualOptions.dsn == "https://abcd@efgh.ingest.sentry.io/123456")
175+
}
176+
177+
private fun verifyDefaults(actualOptions: SentryAndroidOptions) {
178+
assertTrue(actualOptions.ignoredExceptionsForType.contains(JavascriptException::class.java))
179+
assertEquals(RNSentryVersion.ANDROID_SDK_NAME, actualOptions.sdkVersion?.name)
180+
assertEquals(
181+
io.sentry.android.core.BuildConfig.VERSION_NAME,
182+
actualOptions.sdkVersion?.version,
183+
)
184+
val pack = actualOptions.sdkVersion?.packages?.first { it.name == RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_NAME }
185+
assertNotNull(pack)
186+
assertEquals(RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION, pack?.version)
187+
assertNull(actualOptions.tracesSampleRate)
188+
assertNull(actualOptions.tracesSampler)
189+
assertEquals(false, actualOptions.enableTracing)
190+
}
191+
192+
private fun verifyFinals(actualOptions: SentryAndroidOptions) {
193+
val event =
194+
SentryEvent().apply { sdk = SdkVersion(RNSentryVersion.ANDROID_SDK_NAME, "1.0") }
195+
val result = actualOptions.beforeSend?.execute(event, Hint())
196+
assertNotNull(result)
197+
assertEquals("android", result?.getTag("event.origin"))
198+
assertEquals("java", result?.getTag("event.environment"))
199+
}
200+
}

0 commit comments

Comments
 (0)