diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cd616a599..bf9c39a749 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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)) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift index c437b83049..86673ef206 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryStartTests.swift @@ -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] = [ { diff --git a/packages/core/ios/RNSentryStart.m b/packages/core/ios/RNSentryStart.m index 6763d2e072..c2485cf990 100644 --- a/packages/core/ios/RNSentryStart.m +++ b/packages/core/ios/RNSentryStart.m @@ -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;