diff --git a/CHANGELOG.md b/CHANGELOG.md index de2c93b5cb..903f110f95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ ### Fixes - Various crashes and issues of Session Replay on Android. See the Android SDK version bump for more details. ([#4529](https://github.com/getsentry/sentry-react-native/pull/4529)) +- `Sentry.setUser(null)` doesn't crash on iOS with RN 0.77.1 ([#4567](https://github.com/getsentry/sentry-react-native/pull/4567)) ### Dependencies diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj index f78b1be0e0..48489ab216 100644 --- a/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTester.xcodeproj/project.pbxproj @@ -8,6 +8,7 @@ /* Begin PBXBuildFile section */ 332D33472CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 332D33462CDBDBB600547D76 /* RNSentryReplayOptionsTests.swift */; }; + 3339C4812D6625570088EB3A /* RNSentryUserTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 3339C4802D6625570088EB3A /* RNSentryUserTests.mm */; }; 336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */; }; 3380C6C42CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3380C6C32CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift */; }; 33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 33958C682BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m */; }; @@ -25,6 +26,8 @@ 332D33482CDBDC7300547D76 /* RNSentry.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RNSentry.h; path = ../ios/RNSentry.h; sourceTree = SOURCE_ROOT; }; 332D33492CDCC8E100547D76 /* RNSentryTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RNSentryTests.h; sourceTree = ""; }; 332D334A2CDCC8EB00547D76 /* RNSentryCocoaTesterTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNSentryCocoaTesterTests-Bridging-Header.h"; sourceTree = ""; }; + 3339C47F2D6625260088EB3A /* RNSentry+Test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RNSentry+Test.h"; sourceTree = ""; }; + 3339C4802D6625570088EB3A /* RNSentryUserTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RNSentryUserTests.mm; sourceTree = ""; }; 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RNSentryReplayBreadcrumbConverterTests.swift; sourceTree = ""; }; 3360843A2C32E3A8008CC412 /* RNSentryReplayBreadcrumbConverter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNSentryReplayBreadcrumbConverter.h; path = ../ios/RNSentryReplayBreadcrumbConverter.h; sourceTree = ""; }; 3360843C2C340C76008CC412 /* RNSentryBreadcrumbTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RNSentryBreadcrumbTests.swift; sourceTree = ""; }; @@ -88,10 +91,12 @@ 3360899029524164007C7730 /* RNSentryCocoaTesterTests */ = { isa = PBXGroup; children = ( + 3339C47F2D6625260088EB3A /* RNSentry+Test.h */, 332D334A2CDCC8EB00547D76 /* RNSentryCocoaTesterTests-Bridging-Header.h */, 332D33492CDCC8E100547D76 /* RNSentryTests.h */, 336084382C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift */, 33F58ACF2977037D008F60EA /* RNSentryTests.mm */, + 3339C4802D6625570088EB3A /* RNSentryUserTests.mm */, 33AFDFEC2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m */, 33AFDFEE2B8D14C200AAB120 /* RNSentryFramesTrackerListenerTests.h */, 33AFDFF02B8D15E500AAB120 /* RNSentryDependencyContainerTests.m */, @@ -243,6 +248,7 @@ 336084392C32E382008CC412 /* RNSentryReplayBreadcrumbConverterTests.swift in Sources */, 33F58AD02977037D008F60EA /* RNSentryTests.mm in Sources */, 33958C692BFCF12600AD1FB6 /* RNSentryOnDrawReporterTests.m in Sources */, + 3339C4812D6625570088EB3A /* RNSentryUserTests.mm in Sources */, 3380C6C42CE25ECA0018B9B6 /* RNSentryReplayPostInitTests.swift in Sources */, 33AFDFED2B8D14B300AAB120 /* RNSentryFramesTrackerListenerTests.m in Sources */, ); diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentry+Test.h b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentry+Test.h new file mode 100644 index 0000000000..51f257cb76 --- /dev/null +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentry+Test.h @@ -0,0 +1,9 @@ +#import + +@interface +RNSentry (RNSentryInternal) + ++ (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys + otherUserKeys:(NSDictionary *)userDataKeys; + +@end diff --git a/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryUserTests.mm b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryUserTests.mm new file mode 100644 index 0000000000..0111e8ddcf --- /dev/null +++ b/packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryUserTests.mm @@ -0,0 +1,110 @@ +#import "RNSentry+Test.h" +#import "RNSentryTests.h" +#import + +@interface RNSentryUserTests : XCTestCase +@end + +@implementation RNSentryUserTests + +- (void)testValidUser +{ + SentryUser *expected = [[SentryUser alloc] init]; + [expected setUserId:@"123"]; + [expected setIpAddress:@"192.168.1.1"]; + [expected setEmail:@"test@example.com"]; + [expected setUsername:@"testuser"]; + [expected setSegment:@"testsegment"]; + [expected setData:@{ + @"foo" : @"bar", + @"baz" : @123, + @"qux" : @[ @"a", @"b", @"c" ], + }]; + + SentryUser *actual = [RNSentry userFrom:@{ + @"id" : @"123", + @"ip_address" : @"192.168.1.1", + @"email" : @"test@example.com", + @"username" : @"testuser", + @"segment" : @"testsegment", + } + otherUserKeys:@{ + @"foo" : @"bar", + @"baz" : @123, + @"qux" : @[ @"a", @"b", @"c" ], + }]; + + XCTAssertTrue([actual isEqualToUser:expected]); +} + +- (void)testNilUser +{ + SentryUser *actual = [RNSentry userFrom:nil otherUserKeys:nil]; + XCTAssertNil(actual); +} + +- (void)testNullUser +{ + SentryUser *actual = [RNSentry userFrom:(NSDictionary *)[NSNull null] otherUserKeys:nil]; + XCTAssertNil(actual); +} + +- (void)testEmptyUser +{ + SentryUser *expected = [[SentryUser alloc] init]; + [expected setData:@{}]; + + SentryUser *actual = [RNSentry userFrom:@{} otherUserKeys:@{}]; + XCTAssertTrue([actual isEqualToUser:expected]); +} + +- (void)testInvalidUser +{ + SentryUser *expected = [[SentryUser alloc] init]; + + SentryUser *actual = [RNSentry userFrom:@{ + @"id" : @123, + @"ip_address" : @ {}, + @"email" : @ {}, + @"username" : @ {}, + @"segment" : @[], + } + otherUserKeys:nil]; + + XCTAssertTrue([actual isEqualToUser:expected]); +} + +- (void)testPartiallyInvalidUser +{ + SentryUser *expected = [[SentryUser alloc] init]; + [expected setUserId:@"123"]; + + SentryUser *actual = [RNSentry userFrom:@{ + @"id" : @"123", + @"ip_address" : @ {}, + @"email" : @ {}, + @"username" : @ {}, + @"segment" : @[], + } + otherUserKeys:nil]; + + XCTAssertTrue([actual isEqualToUser:expected]); +} + +- (void)testNullValuesUser +{ + SentryUser *expected = [[SentryUser alloc] init]; + + SentryUser *actual = [RNSentry userFrom:@{ + @"id" : [NSNull null], + @"ip_address" : [NSNull null], + @"email" : [NSNull null], + @"username" : [NSNull null], + @"segment" : [NSNull null], + } + otherUserKeys:nil]; + + XCTAssertTrue([actual isEqualToUser:expected]); +} + +@end diff --git a/packages/core/ios/RNSentry.mm b/packages/core/ios/RNSentry.mm index 79ff76d0ae..16b7bb72de 100644 --- a/packages/core/ios/RNSentry.mm +++ b/packages/core/ios/RNSentry.mm @@ -641,26 +641,50 @@ - (NSDictionary *)fetchNativeStackFramesBy:(NSArray *)instructionsAd RCT_EXPORT_METHOD(setUser : (NSDictionary *)userKeys otherUserKeys : (NSDictionary *)userDataKeys) { [SentrySDK configureScope:^(SentryScope *_Nonnull scope) { - if (nil == userKeys && nil == userDataKeys) { - [scope setUser:nil]; - } else { - SentryUser *userInstance = [[SentryUser alloc] init]; - - if (nil != userKeys) { - [userInstance setUserId:userKeys[@"id"]]; - [userInstance setIpAddress:userKeys[@"ip_address"]]; - [userInstance setEmail:userKeys[@"email"]]; - [userInstance setUsername:userKeys[@"username"]]; - [userInstance setSegment:userKeys[@"segment"]]; - } + [scope setUser:[RNSentry userFrom:userKeys otherUserKeys:userDataKeys]]; + }]; +} - if (nil != userDataKeys) { - [userInstance setData:userDataKeys]; - } ++ (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys + otherUserKeys:(NSDictionary *)userDataKeys +{ + // 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]; - [scope setUser:userInstance]; + 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]; + } + id segment = [userKeys valueForKey:@"segment"]; + if ([segment isKindOfClass:NSString.class]) { + [userInstance setSegment:segment]; + } + + if ([userDataKeys isKindOfClass:NSDictionary.class]) { + [userInstance setData:userDataKeys]; + } + + return userInstance; + } + + if (![[NSNull null] isEqual:userKeys] && nil != userKeys) { + NSLog(@"[RNSentry] Method setUser received unexpected type of userKeys."); + } + + return nil; } RCT_EXPORT_METHOD(addBreadcrumb : (NSDictionary *)breadcrumb)