Skip to content

Commit

Permalink
Merge 14fe05d into a18210a
Browse files Browse the repository at this point in the history
  • Loading branch information
krystofwoldrich authored Feb 5, 2025
2 parents a18210a + 14fe05d commit 5625ce7
Show file tree
Hide file tree
Showing 44 changed files with 2,532 additions and 739 deletions.
51 changes: 51 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,57 @@
## Unreleased

### Features

- Capture App Start errors and crashes by initializing Sentry from `sentry.options.json` ([#4472](https://github.com/getsentry/sentry-react-native/pull/4472))

Create `sentry.options.json` in the React Native project root and set options the same as you currently have in `Sentry.init` in JS.

```json
{
"dsn": "https://[email protected]/value",
}
```

Initialize Sentry on the native layers by newly provided native methods.

```kotlin
import io.sentry.react.RNSentrySDK

class MainApplication : Application(), ReactApplication {
override fun onCreate() {
super.onCreate()
RNSentrySDK.init(this)
}
}
```

```obj-c
#import <RNSentry/RNSentry.h>

@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[RNSentrySDK start];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
```
### Changes
- Load `optionsFile` into the JS bundle during Metro bundle process ([#4476](https://github.com/getsentry/sentry-react-native/pull/4476))
- Add experimental version of `startWithConfigureOptions` for Apple platforms ([#4444](https://github.com/getsentry/sentry-react-native/pull/4444))
- Add experimental version of `init` with optional `OptionsConfiguration<SentryAndroidOptions>` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
- Add initialization using `sentry.options.json` for Apple platforms ([#4447](https://github.com/getsentry/sentry-react-native/pull/4447))
- Add initialization using `sentry.options.json` for Android ([#4451](https://github.com/getsentry/sentry-react-native/pull/4451))
### Internal
- Extract iOS native initialization to standalone structures ([#4442](https://github.com/getsentry/sentry-react-native/pull/4442))
- Extract Android native initialization to standalone structures ([#4445](https://github.com/getsentry/sentry-react-native/pull/4445))
### Dependencies
- Bump JavaScript SDK from v8.53.0 to v8.54.0 ([#4503](https://github.com/getsentry/sentry-react-native/pull/4503))
Expand Down
2 changes: 1 addition & 1 deletion packages/core/RNSentry.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Pod::Spec.new do |s|
s.preserve_paths = '*.js'

s.source_files = 'ios/**/*.{h,m,mm}'
s.public_header_files = 'ios/RNSentry.h'
s.public_header_files = 'ios/RNSentry.h', 'ios/RNSentrySDK.h'

s.compiler_flags = other_cflags

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"dsn": "invalid-dsn"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
invalid-options
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dsn": "https://[email protected]/123456",
"enableTracing": true,
"tracesSampleRate": 1.0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package io.sentry.react

import androidx.test.ext.junit.runners.AndroidJUnit4
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import io.sentry.react.RNSentryJsonConverter.convertToWritable
import org.json.JSONArray
import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class RNSentryJsonConverterTest {
@Test
fun testConvertToWritableWithSimpleJsonObject() {
val jsonObject =
JSONObject().apply {
put("floatKey", 12.3f)
put("doubleKey", 12.3)
put("intKey", 123)
put("stringKey", "test")
put("nullKey", JSONObject.NULL)
}

val result: WritableMap? = convertToWritable(jsonObject)

assertNotNull(result)
assertEquals(12.3, result!!.getDouble("floatKey"), 0.0001)
assertEquals(12.3, result.getDouble("doubleKey"), 0.0)
assertEquals(123, result.getInt("intKey"))
assertEquals("test", result.getString("stringKey"))
assertNull(result.getString("nullKey"))
}

@Test
fun testConvertToWritableWithNestedJsonObject() {
val jsonObject =
JSONObject().apply {
put(
"nested",
JSONObject().apply {
put("key", "value")
},
)
}

val result: WritableMap? = convertToWritable(jsonObject)

assertNotNull(result)
val nestedMap = result!!.getMap("nested")
assertNotNull(nestedMap)
assertEquals("value", nestedMap!!.getString("key"))
}

@Test
fun testConvertToWritableWithJsonArray() {
val jsonArray =
JSONArray().apply {
put(1)
put(2.5)
put("string")
put(JSONObject.NULL)
}

val result: WritableArray = convertToWritable(jsonArray)

assertEquals(1, result.getInt(0))
assertEquals(2.5, result.getDouble(1), 0.0)
assertEquals("string", result.getString(2))
assertNull(result.getString(3))
}

@Test
fun testConvertToWritableWithNestedJsonArray() {
val jsonObject =
JSONObject().apply {
put(
"array",
JSONArray().apply {
put(
JSONObject().apply {
put("key1", "value1")
},
)
put(
JSONObject().apply {
put("key2", "value2")
},
)
},
)
}

val result: WritableMap? = convertToWritable(jsonObject)

val array = result?.getArray("array")
assertEquals("value1", array?.getMap(0)?.getString("key1"))
assertEquals("value2", array?.getMap(1)?.getString("key2"))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package io.sentry.react

import android.content.Context
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.facebook.react.common.JavascriptException
import io.sentry.Hint
import io.sentry.ILogger
import io.sentry.Sentry
import io.sentry.Sentry.OptionsConfiguration
import io.sentry.SentryEvent
import io.sentry.android.core.AndroidLogger
import io.sentry.android.core.SentryAndroidOptions
import io.sentry.protocol.SdkVersion
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class RNSentrySDKTest {
private val logger: ILogger = AndroidLogger(RNSentrySDKTest::class.java.simpleName)
private lateinit var context: Context

companion object {
private const val INITIALISATION_ERROR = "Failed to initialize Sentry's React Native SDK"
private const val VALID_OPTIONS = "sentry.options.json"
private const val INVALID_OPTIONS = "invalid.options.json"
private const val INVALID_JSON = "invalid.options.txt"
private const val MISSING = "non-existing-file"

private val validConfig =
OptionsConfiguration<SentryAndroidOptions> { options ->
options.dsn = "https://[email protected]/123456"
}
private val invalidConfig =
OptionsConfiguration<SentryAndroidOptions> { options ->
options.dsn = "invalid-dsn"
}
private val emptyConfig = OptionsConfiguration<SentryAndroidOptions> {}
}

@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().context
}

@After
fun tearDown() {
Sentry.close()
}

@Test
fun initialisesSuccessfullyWithDefaultValidJsonFile() { // sentry.options.json
RNSentrySDK.init(context)
assertTrue(Sentry.isEnabled())
}

@Test
fun initialisesSuccessfullyWithValidConfigurationAndDefaultValidJsonFile() {
RNSentrySDK.init(context, validConfig)
assertTrue(Sentry.isEnabled())
}

@Test
fun initialisesSuccessfullyWithValidConfigurationAndInvalidJsonFile() {
RNSentrySDK.init(context, validConfig, INVALID_OPTIONS, logger)
assertTrue(Sentry.isEnabled())
}

@Test
fun initialisesSuccessfullyWithValidConfigurationAndMissingJsonFile() {
RNSentrySDK.init(context, validConfig, MISSING, logger)
assertTrue(Sentry.isEnabled())
}

@Test
fun initialisesSuccessfullyWithValidConfigurationAndErrorInParsingJsonFile() {
RNSentrySDK.init(context, validConfig, INVALID_JSON, logger)
assertTrue(Sentry.isEnabled())
}

@Test
fun initialisesSuccessfullyWithNoConfigurationAndValidJsonFile() {
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
assertTrue(Sentry.isEnabled())
}

@Test
fun failsToInitialiseWithNoConfigurationAndInvalidJsonFile() {
try {
RNSentrySDK.init(context, emptyConfig, INVALID_OPTIONS, logger)
} catch (e: Exception) {
assertEquals(INITIALISATION_ERROR, e.message)
}
assertFalse(Sentry.isEnabled())
}

@Test
fun failsToInitialiseWithInvalidConfigAndInvalidJsonFile() {
try {
RNSentrySDK.init(context, invalidConfig, INVALID_OPTIONS, logger)
} catch (e: Exception) {
assertEquals(INITIALISATION_ERROR, e.message)
}
assertFalse(Sentry.isEnabled())
}

@Test
fun failsToInitialiseWithInvalidConfigAndValidJsonFile() {
try {
RNSentrySDK.init(context, invalidConfig, VALID_OPTIONS, logger)
} catch (e: Exception) {
assertEquals(INITIALISATION_ERROR, e.message)
}
assertFalse(Sentry.isEnabled())
}

@Test
fun failsToInitialiseWithInvalidConfigurationAndDefaultValidJsonFile() {
try {
RNSentrySDK.init(context, invalidConfig)
} catch (e: Exception) {
assertEquals(INITIALISATION_ERROR, e.message)
}
assertFalse(Sentry.isEnabled())
}

@Test
fun defaultsAndFinalsAreSetWithValidJsonFile() {
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
verifyDefaults(actualOptions)
verifyFinals(actualOptions)
// options file
assert(actualOptions.dsn == "https://[email protected]/123456")
}

@Test
fun defaultsAndFinalsAreSetWithValidConfiguration() {
RNSentrySDK.init(context, validConfig, MISSING, logger)
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
verifyDefaults(actualOptions)
verifyFinals(actualOptions)
// configuration
assert(actualOptions.dsn == "https://[email protected]/123456")
}

@Test
fun defaultsOverrideOptionsJsonFile() {
RNSentrySDK.init(context, emptyConfig, VALID_OPTIONS, logger)
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
assertNull(actualOptions.tracesSampleRate)
assertEquals(false, actualOptions.enableTracing)
}

@Test
fun configurationOverridesDefaultOptions() {
val validConfig =
OptionsConfiguration<SentryAndroidOptions> { options ->
options.dsn = "https://[email protected]/123456"
options.tracesSampleRate = 0.5
options.enableTracing = true
}
RNSentrySDK.init(context, validConfig, MISSING, logger)
val actualOptions = Sentry.getCurrentHub().options as SentryAndroidOptions
assertEquals(0.5, actualOptions.tracesSampleRate)
assertEquals(true, actualOptions.enableTracing)
assert(actualOptions.dsn == "https://[email protected]/123456")
}

private fun verifyDefaults(actualOptions: SentryAndroidOptions) {
assertTrue(actualOptions.ignoredExceptionsForType.contains(JavascriptException::class.java))
assertEquals(RNSentryVersion.ANDROID_SDK_NAME, actualOptions.sdkVersion?.name)
assertEquals(
io.sentry.android.core.BuildConfig.VERSION_NAME,
actualOptions.sdkVersion?.version,
)
val pack = actualOptions.sdkVersion?.packages?.first { it.name == RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_NAME }
assertNotNull(pack)
assertEquals(RNSentryVersion.REACT_NATIVE_SDK_PACKAGE_VERSION, pack?.version)
assertNull(actualOptions.tracesSampleRate)
assertNull(actualOptions.tracesSampler)
assertEquals(false, actualOptions.enableTracing)
}

private fun verifyFinals(actualOptions: SentryAndroidOptions) {
val event =
SentryEvent().apply { sdk = SdkVersion(RNSentryVersion.ANDROID_SDK_NAME, "1.0") }
val result = actualOptions.beforeSend?.execute(event, Hint())
assertNotNull(result)
assertEquals("android", result?.getTag("event.origin"))
assertEquals("java", result?.getTag("event.environment"))
}
}
Loading

0 comments on commit 5625ce7

Please sign in to comment.