Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

### Fixes

- Fix duplicate JS error reporting on iOS New Architecture when the native SDK is initialized early via `sentry.options.json` ("Capture App Start Errors"). The `ExceptionsManager.reportException` C++ wrapper filter is now applied in both init paths, matching the JS-init dedup added in `7.9.0` ([#6116](https://github.com/getsentry/sentry-react-native/issues/6116))
- Fix the issue with uploading iOS Debug Symbols in EAS Build when using pnpm ([#6076](https://github.com/getsentry/sentry-react-native/issues/6076))
- Improve frame delay collection performance by using sentry-java `getFramesDelay` API ([#6074](https://github.com/getsentry/sentry-react-native/pull/6074))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,52 @@ final class RNSentryStartTests: XCTestCase {
}
}

func testStartIgnoresJsErrorCppExceptionWrapper() throws {
// Reproduces getsentry/sentry-react-native#6116:
// When the native SDK is started early via sentry.options.json ("Capture App Start
// Errors"), New Architecture wraps unhandled JS errors in a C++ exception that the
// native crash handler captures. Without dedup in updateWithReactFinals, both the JS
// event and the C++ wrapper are sent. Cover both init paths to keep them aligned.
let testCases: [() throws -> Void] = [
{
RNSentrySDK.start { options in
options.dsn = "https://abcd@efgh.ingest.sentry.io/123456"
}
},
{
try self.startFromRN(options: [
"dsn": "https://abcd@efgh.ingest.sentry.io/123456"
])
}
]

for startMethod in testCases {
try startMethod()

let actualOptions = PrivateSentrySDKOnly.options

let cppWrapperEvent = Event()
cppWrapperEvent.exceptions = [
Exception(
value: "N8facebook3jsi7JSErrorE: ExceptionsManager.reportException raised an exception: Unhandled JS Exception: Error: Test error",
type: "C++ Exception"
)
]
XCTAssertNil(actualOptions.beforeSend!(cppWrapperEvent),
"Event with ExceptionsManager.reportException in value should be dropped")

let legitimateCppEvent = Event()
legitimateCppEvent.exceptions = [
Exception(
value: "std::runtime_error: Some other C++ error occurred",
type: "C++ Exception"
)
]
XCTAssertNotNil(actualOptions.beforeSend!(legitimateCppEvent),
"Legitimate C++ exception without ExceptionsManager.reportException should not be dropped")
}
}

func testStartSetsNativeEventOrigin() throws {
let testCases: [() throws -> Void] = [
{
Expand Down
13 changes: 13 additions & 0 deletions packages/core/ios/RNSentryStart.m
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,19 @@ + (void)updateWithReactFinals:(SentryOptions *)options
return nil;
}

// With New Architecture, React Native wraps JS errors in C++ exceptions.
// These exceptions are caught by the native crash handler and should be filtered out
// since the JS error is already reported by the JS error handler.
// The key indicator is "ExceptionsManager.reportException" in the exception value,
// which is React Native's mechanism for reporting JS errors to the native layer.
for (SentryException *exception in event.exceptions) {
if (nil != exception.value &&
[exception.value rangeOfString:@"ExceptionsManager.reportException"].location
!= NSNotFound) {
return nil;
}
}

[self setEventOriginTag:event];
if (userBeforeSend == nil) {
return event;
Expand Down
Loading