From 6e6a716615241791892ebf4df35c2d6d57938ca4 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 8 Jun 2026 10:58:24 +0200 Subject: [PATCH 1/8] refactor(android,ios): Use native SDK deserializers for User and Breadcrumb Replace manual field-by-field map-to-object construction with native SDK deserialization APIs for User and Breadcrumb on both Android and iOS. Co-Authored-By: Claude Opus 4.6 --- .../io/sentry/react/RNSentryBreadcrumb.java | 82 ++++++++----------- .../io/sentry/react/RNSentryModuleImpl.java | 72 ++++++---------- packages/core/ios/RNSentry.mm | 24 ++---- packages/core/ios/RNSentryBreadcrumb.m | 33 ++------ 4 files changed, 72 insertions(+), 139 deletions(-) diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java index 45885adc9c..483fdf7532 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java @@ -1,9 +1,14 @@ package io.sentry.react; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableMapKeySetIterator; +import com.facebook.react.bridge.ReadableType; import io.sentry.Breadcrumb; +import io.sentry.ILogger; +import io.sentry.Sentry; import io.sentry.SentryLevel; -import java.util.Map; +import io.sentry.util.MapObjectReader; +import java.util.HashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -37,58 +42,41 @@ public static String getCurrentScreenFrom(ReadableMap from) { @NotNull public static Breadcrumb fromMap(ReadableMap from) { - final @NotNull Breadcrumb breadcrumb = new Breadcrumb(); - - if (from.hasKey("message")) { - breadcrumb.setMessage(from.getString("message")); - } - - if (from.hasKey("type")) { - breadcrumb.setType(from.getString("type")); - } - - if (from.hasKey("category")) { - breadcrumb.setCategory(from.getString("category")); - } - - if (from.hasKey("origin")) { - breadcrumb.setOrigin(from.getString("origin")); - } else { - breadcrumb.setOrigin("react-native"); - } + final @NotNull ILogger logger = Sentry.getCurrentScopes().getOptions().getLogger(); + try { + final @NotNull MapObjectReader reader = new MapObjectReader(toDeepHashMap(from)); + final @NotNull Breadcrumb breadcrumb = + new Breadcrumb.Deserializer().deserialize(reader, logger); - if (from.hasKey("level")) { - switch (from.getString("level")) { - case "fatal": - breadcrumb.setLevel(SentryLevel.FATAL); - break; - case "warning": - breadcrumb.setLevel(SentryLevel.WARNING); - break; - case "debug": - breadcrumb.setLevel(SentryLevel.DEBUG); - break; - case "error": - breadcrumb.setLevel(SentryLevel.ERROR); - break; - case "info": - default: - breadcrumb.setLevel(SentryLevel.INFO); - break; + if (breadcrumb.getLevel() == null) { + breadcrumb.setLevel(SentryLevel.INFO); } + if (breadcrumb.getOrigin() == null) { + breadcrumb.setOrigin("react-native"); + } + + return breadcrumb; + } catch (Exception e) { + logger.log(SentryLevel.ERROR, "Failed to deserialize breadcrumb from map.", e); + final Breadcrumb fallback = new Breadcrumb(); + fallback.setOrigin("react-native"); + return fallback; } + } - if (from.hasKey("data")) { - final ReadableMap data = from.getMap("data"); - for (final Map.Entry entry : data.toHashMap().entrySet()) { - final Object value = entry.getValue(); - // data is ConcurrentHashMap and can't have null values - if (value != null) { - breadcrumb.setData(entry.getKey(), entry.getValue()); + @NotNull + static HashMap toDeepHashMap(@NotNull ReadableMap from) { + final HashMap map = from.toHashMap(); + final ReadableMapKeySetIterator iterator = from.keySetIterator(); + while (iterator.hasNextKey()) { + final String key = iterator.nextKey(); + if (from.getType(key) == ReadableType.Map) { + final ReadableMap nested = from.getMap(key); + if (nested != null) { + map.put(key, toDeepHashMap(nested)); } } } - - return breadcrumb; + return map; } } diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 0e1053d42b..1070ffa75e 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -53,7 +53,6 @@ import io.sentry.android.core.performance.AppStartMetrics; import io.sentry.profilemeasurements.ProfileMeasurement; import io.sentry.profilemeasurements.ProfileMeasurementValue; -import io.sentry.protocol.Geo; import io.sentry.protocol.SdkVersion; import io.sentry.protocol.SentryId; import io.sentry.protocol.User; @@ -62,6 +61,7 @@ import io.sentry.util.FileUtils; import io.sentry.util.JsonSerializationUtils; import io.sentry.util.LoadClass; +import io.sentry.util.MapObjectReader; import io.sentry.vendor.Base64; import java.io.BufferedInputStream; import java.io.BufferedReader; @@ -575,60 +575,34 @@ public void setUser(final ReadableMap userKeys, final ReadableMap userDataKeys) if (userKeys == null && userDataKeys == null) { scope.setUser(null); } else { - User userInstance = new User(); - - if (userKeys != null) { - if (userKeys.hasKey("email")) { - userInstance.setEmail(userKeys.getString("email")); - } - - if (userKeys.hasKey("id")) { - userInstance.setId(userKeys.getString("id")); - } - - if (userKeys.hasKey("username")) { - userInstance.setUsername(userKeys.getString("username")); - } - - if (userKeys.hasKey("ip_address")) { - userInstance.setIpAddress(userKeys.getString("ip_address")); - } - - if (userKeys.hasKey("geo")) { - ReadableMap geoMap = userKeys.getMap("geo"); - if (geoMap != null) { - Geo geoData = new Geo(); - if (geoMap.hasKey("city")) { - geoData.setCity(geoMap.getString("city")); - } - if (geoMap.hasKey("country_code")) { - geoData.setCountryCode(geoMap.getString("country_code")); + try { + final MapObjectReader reader = + new MapObjectReader( + userKeys != null + ? RNSentryBreadcrumb.toDeepHashMap(userKeys) + : new HashMap<>()); + final User userInstance = new User.Deserializer().deserialize(reader, logger); + + if (userDataKeys != null) { + Map userDataMap = new HashMap<>(); + ReadableMapKeySetIterator it = userDataKeys.keySetIterator(); + while (it.hasNextKey()) { + String key = it.nextKey(); + String value = userDataKeys.getString(key); + + // other is ConcurrentHashMap and can't have null values + if (value != null) { + userDataMap.put(key, value); } - if (geoMap.hasKey("region")) { - geoData.setRegion(geoMap.getString("region")); - } - userInstance.setGeo(geoData); } - } - } - - if (userDataKeys != null) { - Map userDataMap = new HashMap<>(); - ReadableMapKeySetIterator it = userDataKeys.keySetIterator(); - while (it.hasNextKey()) { - String key = it.nextKey(); - String value = userDataKeys.getString(key); - // other is ConcurrentHashMap and can't have null values - if (value != null) { - userDataMap.put(key, value); - } + userInstance.setData(userDataMap); } - userInstance.setData(userDataMap); + scope.setUser(userInstance); + } catch (Exception e) { + logger.log(SentryLevel.ERROR, "Failed to deserialize user from map.", e); } - - scope.setUser(userInstance); } }); } diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index f6754e0e4b..aa1b58577b 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -25,6 +25,7 @@ #import #import #import +#import #import // This guard prevents importing Hermes in JSC apps @@ -654,29 +655,14 @@ + (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys { // we can safely ignore userDataKeys since if original JS user was null userKeys will be null if ([userKeys isKindOfClass:NSDictionary.class]) { - SentryUser *userInstance = [[SentryUser alloc] init]; - - id userId = [userKeys valueForKey:@"id"]; - if ([userId isKindOfClass:NSString.class]) { - [userInstance setUserId:userId]; - } - id ipAddress = [userKeys valueForKey:@"ip_address"]; - if ([ipAddress isKindOfClass:NSString.class]) { - [userInstance setIpAddress:ipAddress]; - } - id email = [userKeys valueForKey:@"email"]; - if ([email isKindOfClass:NSString.class]) { - [userInstance setEmail:email]; - } - id username = [userKeys valueForKey:@"username"]; - if ([username isKindOfClass:NSString.class]) { - [userInstance setUsername:username]; - } + NSMutableDictionary *filteredKeys = [userKeys mutableCopy]; + [filteredKeys removeObjectForKey:@"geo"]; + SentryUser *userInstance = [[SentryUser alloc] initWithDictionary:filteredKeys]; id geo = [userKeys valueForKey:@"geo"]; if ([geo isKindOfClass:NSDictionary.class]) { NSDictionary *geoDict = (NSDictionary *)geo; - SentryGeo *sentryGeo = [SentryGeo alloc]; + SentryGeo *sentryGeo = [[SentryGeo alloc] init]; id city = [geoDict valueForKey:@"city"]; if ([city isKindOfClass:NSString.class]) { diff --git a/packages/core/ios/RNSentryBreadcrumb.m b/packages/core/ios/RNSentryBreadcrumb.m index cc77dce70a..9f9aad0b44 100644 --- a/packages/core/ios/RNSentryBreadcrumb.m +++ b/packages/core/ios/RNSentryBreadcrumb.m @@ -1,37 +1,22 @@ #import "RNSentryBreadcrumb.h" @import Sentry; +#import @implementation RNSentryBreadcrumb + (SentryBreadcrumb *)from:(NSDictionary *)dict { - SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] init]; - - NSString *levelString = dict[@"level"]; - SentryLevel sentryLevel; - if ([levelString isEqualToString:@"fatal"]) { - sentryLevel = kSentryLevelFatal; - } else if ([levelString isEqualToString:@"warning"]) { - sentryLevel = kSentryLevelWarning; - } else if ([levelString isEqualToString:@"error"]) { - sentryLevel = kSentryLevelError; - } else if ([levelString isEqualToString:@"debug"]) { - sentryLevel = kSentryLevelDebug; - } else { - sentryLevel = kSentryLevelInfo; - } + SentryBreadcrumb *crumb = [[SentryBreadcrumb alloc] initWithDictionary:dict]; - [crumb setLevel:sentryLevel]; - [crumb setCategory:dict[@"category"]]; - id origin = dict[@"origin"]; - if (origin != nil) { - [crumb setOrigin:origin]; - } else { + if (crumb.timestamp == nil) { + [crumb setTimestamp:[NSDate date]]; + } + if (dict[@"level"] == nil) { + [crumb setLevel:kSentryLevelInfo]; + } + if (dict[@"origin"] == nil) { [crumb setOrigin:@"react-native"]; } - [crumb setType:dict[@"type"]]; - [crumb setMessage:dict[@"message"]]; - [crumb setData:dict[@"data"]]; return crumb; } From 39a406a5455bfd05159797106a07e195a1351b68 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 8 Jun 2026 11:37:25 +0200 Subject: [PATCH 2/8] fix(android): Use Map interface instead of HashMap to satisfy PMD LooseCoupling rule Co-Authored-By: Claude Opus 4.6 --- .../src/main/java/io/sentry/react/RNSentryBreadcrumb.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java index 483fdf7532..345d881ed6 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java @@ -8,7 +8,7 @@ import io.sentry.Sentry; import io.sentry.SentryLevel; import io.sentry.util.MapObjectReader; -import java.util.HashMap; +import java.util.Map; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -65,8 +65,8 @@ public static Breadcrumb fromMap(ReadableMap from) { } @NotNull - static HashMap toDeepHashMap(@NotNull ReadableMap from) { - final HashMap map = from.toHashMap(); + static Map toDeepHashMap(@NotNull ReadableMap from) { + final Map map = from.toHashMap(); final ReadableMapKeySetIterator iterator = from.keySetIterator(); while (iterator.hasNextKey()) { final String key = iterator.nextKey(); From dacf009866e83b2c9c0243db880e88dc06d01d1e Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 8 Jun 2026 11:46:14 +0200 Subject: [PATCH 3/8] fix(android): Clear stale user from scope on deserialization failure Co-Authored-By: Claude Opus 4.6 --- .../src/main/java/io/sentry/react/RNSentryModuleImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 1070ffa75e..3358190169 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -602,6 +602,7 @@ public void setUser(final ReadableMap userKeys, final ReadableMap userDataKeys) scope.setUser(userInstance); } catch (Exception e) { logger.log(SentryLevel.ERROR, "Failed to deserialize user from map.", e); + scope.setUser(null); } } }); From dbec14528626af409d4dc0c8772a7015fa5ee1b0 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 8 Jun 2026 12:17:25 +0200 Subject: [PATCH 4/8] test(android,ios): Add breadcrumb default level and timestamp tests Co-Authored-By: Claude Opus 4.6 --- .../RNSentryBreadcrumbTest.kt | 23 +++++++++++++++++++ .../RNSentryBreadcrumbTests.swift | 8 +++++++ 2 files changed, 31 insertions(+) diff --git a/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/rnsentryandroidtester/RNSentryBreadcrumbTest.kt b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/rnsentryandroidtester/RNSentryBreadcrumbTest.kt index c85fc9e5f9..53f342227f 100644 --- a/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/rnsentryandroidtester/RNSentryBreadcrumbTest.kt +++ b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/rnsentryandroidtester/RNSentryBreadcrumbTest.kt @@ -4,6 +4,7 @@ import com.facebook.react.bridge.JavaOnlyMap import io.sentry.SentryLevel import io.sentry.react.RNSentryBreadcrumb import junit.framework.TestCase.assertEquals +import junit.framework.TestCase.assertNotNull import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @@ -41,6 +42,28 @@ class RNSentryBreadcrumbTest { assertEquals(testData.toHashMap(), actual.data) } + @Test + fun defaultsToInfoLevelWhenMissing() { + val map = + JavaOnlyMap.of( + "message", + "testMessage", + ) + val actual = RNSentryBreadcrumb.fromMap(map) + assertEquals(SentryLevel.INFO, actual.level) + } + + @Test + fun setsTimestamp() { + val map = + JavaOnlyMap.of( + "message", + "testMessage", + ) + val actual = RNSentryBreadcrumb.fromMap(map) + assertNotNull(actual.timestamp) + } + @Test fun reactNativeForMissingOrigin() { val map = diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryBreadcrumbTests.swift b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryBreadcrumbTests.swift index 0002c11e4f..ad06980a9d 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryBreadcrumbTests.swift +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryBreadcrumbTests.swift @@ -40,6 +40,14 @@ final class RNSentryBreadcrumbTests: XCTestCase { XCTAssertEqual(actualCrumb!.origin, "someOrigin") } + func testSetsTimestamp() { + let actualCrumb = RNSentryBreadcrumb.from([ + "message": "testMessage" + ]) + + XCTAssertNotNil(actualCrumb!.timestamp) + } + func testUsesInfoAsDefaultSentryLevel() { let actualCrumb = RNSentryBreadcrumb.from([ "message": "testMessage" From fddf6f8dc6cd7e68a86fd4036e10d9de52217730 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 8 Jun 2026 12:58:42 +0200 Subject: [PATCH 5/8] fix(ios): Revert SentryGeo init and fix getCurrentScreenFrom guard logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SentryGeo inherits SENTRY_NO_INIT from SentrySerializable, so `init` is unavailable — revert to `[SentryGeo alloc]` as before. Fix pre-existing bug in getCurrentScreenFrom where nil or non-string category values would fall through instead of returning nil. Co-Authored-By: Claude Opus 4.6 --- packages/core/ios/RNSentry.mm | 2 +- packages/core/ios/RNSentryBreadcrumb.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index aa1b58577b..9ca9db259f 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -662,7 +662,7 @@ + (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys id geo = [userKeys valueForKey:@"geo"]; if ([geo isKindOfClass:NSDictionary.class]) { NSDictionary *geoDict = (NSDictionary *)geo; - SentryGeo *sentryGeo = [[SentryGeo alloc] init]; + SentryGeo *sentryGeo = [SentryGeo alloc]; id city = [geoDict valueForKey:@"city"]; if ([city isKindOfClass:NSString.class]) { diff --git a/packages/core/ios/RNSentryBreadcrumb.m b/packages/core/ios/RNSentryBreadcrumb.m index 9f9aad0b44..5fa964cc9f 100644 --- a/packages/core/ios/RNSentryBreadcrumb.m +++ b/packages/core/ios/RNSentryBreadcrumb.m @@ -24,8 +24,8 @@ + (SentryBreadcrumb *)from:(NSDictionary *)dict + (NSString *_Nullable)getCurrentScreenFrom:(NSDictionary *_Nonnull)dict { NSString *_Nullable maybeCategory = [dict valueForKey:@"category"]; - if ([maybeCategory isKindOfClass:[NSString class]] - && ![maybeCategory isEqualToString:@"navigation"]) { + if (![maybeCategory isKindOfClass:[NSString class]] + || ![maybeCategory isEqualToString:@"navigation"]) { return nil; } From 73f4db2f69038fdda01a02ffcab9d79527af79d7 Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 8 Jun 2026 13:14:35 +0200 Subject: [PATCH 6/8] fix(ios): Update user tests for initWithDictionary behavior initWithDictionary: stores non-string values for known fields in the unknown dict rather than silently ignoring them. Update tests to assert individual properties instead of isEqualToUser: which also compares unknown. Co-Authored-By: Claude Opus 4.6 --- .../RNSentryUserTests.m | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryUserTests.m b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryUserTests.m index 9c603940a3..fe91d5f913 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryUserTests.m +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryUserTests.m @@ -50,17 +50,17 @@ - (void)testNullUser - (void)testEmptyUser { - SentryUser *expected = [[SentryUser alloc] init]; - [expected setData:@{ }]; - SentryUser *actual = [RNSentry userFrom:@{ } otherUserKeys:@{ }]; - XCTAssertTrue([actual isEqualToUser:expected]); + XCTAssertNotNil(actual); + XCTAssertNil(actual.userId); + XCTAssertNil(actual.email); + XCTAssertNil(actual.username); + XCTAssertNil(actual.ipAddress); + XCTAssertEqualObjects(actual.data, @{ }); } - (void)testInvalidUser { - SentryUser *expected = [[SentryUser alloc] init]; - SentryUser *actual = [RNSentry userFrom:@{ @"id" : @123, @"ip_address" : @ { }, @@ -69,14 +69,16 @@ - (void)testInvalidUser } otherUserKeys:nil]; - XCTAssertTrue([actual isEqualToUser:expected]); + // initWithDictionary: ignores non-string values for known string fields + XCTAssertNotNil(actual); + XCTAssertNil(actual.userId); + XCTAssertNil(actual.email); + XCTAssertNil(actual.username); + XCTAssertNil(actual.ipAddress); } - (void)testPartiallyInvalidUser { - SentryUser *expected = [[SentryUser alloc] init]; - [expected setUserId:@"123"]; - SentryUser *actual = [RNSentry userFrom:@{ @"id" : @"123", @"ip_address" : @ { }, @@ -85,13 +87,15 @@ - (void)testPartiallyInvalidUser } otherUserKeys:nil]; - XCTAssertTrue([actual isEqualToUser:expected]); + XCTAssertNotNil(actual); + XCTAssertEqualObjects(actual.userId, @"123"); + XCTAssertNil(actual.email); + XCTAssertNil(actual.username); + XCTAssertNil(actual.ipAddress); } - (void)testNullValuesUser { - SentryUser *expected = [[SentryUser alloc] init]; - SentryUser *actual = [RNSentry userFrom:@{ @"id" : [NSNull null], @"ip_address" : [NSNull null], @@ -100,7 +104,12 @@ - (void)testNullValuesUser } otherUserKeys:nil]; - XCTAssertTrue([actual isEqualToUser:expected]); + // initWithDictionary: ignores non-string values for known string fields + XCTAssertNotNil(actual); + XCTAssertNil(actual.userId); + XCTAssertNil(actual.email); + XCTAssertNil(actual.username); + XCTAssertNil(actual.ipAddress); } - (void)testUserWithGeo From 3c1fc9d7d5b0cb027d96ddd3c7465d6edc95f1ff Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Mon, 8 Jun 2026 13:26:13 +0200 Subject: [PATCH 7/8] fix(ios): Declare private initializers inline instead of importing private headers Private headers (SentryBreadcrumb+Private.h, SentryUser+Private.h) are not available in dynamic framework builds. Declare the category methods inline to avoid the header-not-found error. Co-Authored-By: Claude Opus 4.6 --- packages/core/ios/RNSentry.mm | 5 ++++- packages/core/ios/RNSentryBreadcrumb.m | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index 9ca9db259f..c64cc6bb5e 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -25,9 +25,12 @@ #import #import #import -#import #import +@interface SentryUser () +- (instancetype)initWithDictionary:(NSDictionary *)dictionary; +@end + // This guard prevents importing Hermes in JSC apps #if SENTRY_PROFILING_ENABLED # import diff --git a/packages/core/ios/RNSentryBreadcrumb.m b/packages/core/ios/RNSentryBreadcrumb.m index 5fa964cc9f..7ec33cf2a3 100644 --- a/packages/core/ios/RNSentryBreadcrumb.m +++ b/packages/core/ios/RNSentryBreadcrumb.m @@ -1,6 +1,9 @@ #import "RNSentryBreadcrumb.h" @import Sentry; -#import + +@interface SentryBreadcrumb () +- (instancetype _Nonnull)initWithDictionary:(NSDictionary *_Nonnull)dictionary; +@end @implementation RNSentryBreadcrumb From d8dfaa854d01d8cb65b756db2eb1be97a7bf38cc Mon Sep 17 00:00:00 2001 From: Antonis Lilis Date: Tue, 9 Jun 2026 13:34:46 +0200 Subject: [PATCH 8/8] refactor(android): Pass logger to RNSentryBreadcrumb.fromMap instead of fetching from scopes Co-Authored-By: Claude Opus 4.6 --- .../rnsentryandroidtester/RNSentryBreadcrumbTest.kt | 12 ++++++++---- .../java/io/sentry/react/RNSentryBreadcrumb.java | 4 +--- .../java/io/sentry/react/RNSentryModuleImpl.java | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/rnsentryandroidtester/RNSentryBreadcrumbTest.kt b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/rnsentryandroidtester/RNSentryBreadcrumbTest.kt index 53f342227f..46e2f3e069 100644 --- a/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/rnsentryandroidtester/RNSentryBreadcrumbTest.kt +++ b/packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/rnsentryandroidtester/RNSentryBreadcrumbTest.kt @@ -1,6 +1,7 @@ package io.sentry.rnsentryandroidtester import com.facebook.react.bridge.JavaOnlyMap +import io.sentry.ILogger import io.sentry.SentryLevel import io.sentry.react.RNSentryBreadcrumb import junit.framework.TestCase.assertEquals @@ -8,9 +9,12 @@ import junit.framework.TestCase.assertNotNull import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 +import org.mockito.Mockito @RunWith(JUnit4::class) class RNSentryBreadcrumbTest { + private val logger = Mockito.mock(ILogger::class.java) + @Test fun generatesSentryBreadcrumbFromMap() { val testData = @@ -33,7 +37,7 @@ class RNSentryBreadcrumbTest { "data", testData, ) - val actual = RNSentryBreadcrumb.fromMap(map) + val actual = RNSentryBreadcrumb.fromMap(map, logger) assertEquals(SentryLevel.ERROR, actual.level) assertEquals("testCategory", actual.category) assertEquals("testOrigin", actual.origin) @@ -49,7 +53,7 @@ class RNSentryBreadcrumbTest { "message", "testMessage", ) - val actual = RNSentryBreadcrumb.fromMap(map) + val actual = RNSentryBreadcrumb.fromMap(map, logger) assertEquals(SentryLevel.INFO, actual.level) } @@ -60,7 +64,7 @@ class RNSentryBreadcrumbTest { "message", "testMessage", ) - val actual = RNSentryBreadcrumb.fromMap(map) + val actual = RNSentryBreadcrumb.fromMap(map, logger) assertNotNull(actual.timestamp) } @@ -71,7 +75,7 @@ class RNSentryBreadcrumbTest { "message", "testMessage", ) - val actual = RNSentryBreadcrumb.fromMap(map) + val actual = RNSentryBreadcrumb.fromMap(map, logger) assertEquals("testMessage", actual.message) assertEquals("react-native", actual.origin) } diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java index 345d881ed6..487f8c3b7e 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryBreadcrumb.java @@ -5,7 +5,6 @@ import com.facebook.react.bridge.ReadableType; import io.sentry.Breadcrumb; import io.sentry.ILogger; -import io.sentry.Sentry; import io.sentry.SentryLevel; import io.sentry.util.MapObjectReader; import java.util.Map; @@ -41,8 +40,7 @@ public static String getCurrentScreenFrom(ReadableMap from) { } @NotNull - public static Breadcrumb fromMap(ReadableMap from) { - final @NotNull ILogger logger = Sentry.getCurrentScopes().getOptions().getLogger(); + public static Breadcrumb fromMap(ReadableMap from, @NotNull ILogger logger) { try { final @NotNull MapObjectReader reader = new MapObjectReader(toDeepHashMap(from)); final @NotNull Breadcrumb breadcrumb = diff --git a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java index 3358190169..69501ab5d7 100644 --- a/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java +++ b/packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java @@ -611,7 +611,7 @@ public void setUser(final ReadableMap userKeys, final ReadableMap userDataKeys) public void addBreadcrumb(final ReadableMap breadcrumb) { Sentry.configureScope( scope -> { - scope.addBreadcrumb(RNSentryBreadcrumb.fromMap(breadcrumb)); + scope.addBreadcrumb(RNSentryBreadcrumb.fromMap(breadcrumb, logger)); final @Nullable String screen = RNSentryBreadcrumb.getCurrentScreenFrom(breadcrumb); if (screen != null) {