Skip to content

Commit 9c74a1b

Browse files
fix: Set user geo to the native layer. (#5302)
* set geo from android (OK) ios (WIP) * project cleanup/changelog * lint * use other method to set geo on ios * lint * remove init * fix header * test fix? * removal of init * remove another init * missing [ * missing ;
1 parent fa7bb7e commit 9c74a1b

File tree

11 files changed

+396
-3
lines changed

11 files changed

+396
-3
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929
});
3030
```
3131

32+
### Fix
33+
34+
- Sync `user.geo` from `SetUser` to the native layer ([#5302](https://github.com/getsentry/sentry-react-native/pull/5302))
35+
3236
### Dependencies
3337

3438
- Bump Bundler Plugins from v4.4.0 to v4.6.0 ([#5283](https://github.com/getsentry/sentry-react-native/pull/5283), [#5314](https://github.com/getsentry/sentry-react-native/pull/5314))

packages/core/RNSentryAndroidTester/app/src/test/java/io/sentry/react/RNSentryModuleImplTest.kt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,4 +272,90 @@ class RNSentryModuleImplTest {
272272
val regex = Regex(options.ignoredErrors!![0].filterString)
273273
assertTrue(regex.matches("Error*WithStar"))
274274
}
275+
276+
@Test
277+
fun `setUser with geo data creates user with correct geo properties`() {
278+
val userKeys =
279+
JavaOnlyMap.of(
280+
"id",
281+
"123",
282+
"email",
283+
"test@example.com",
284+
"username",
285+
"testuser",
286+
"geo",
287+
JavaOnlyMap.of(
288+
"city",
289+
"San Francisco",
290+
"country_code",
291+
"US",
292+
"region",
293+
"California",
294+
),
295+
)
296+
val userDataKeys = JavaOnlyMap.of("customField", "customValue")
297+
298+
module.setUser(userKeys, userDataKeys)
299+
}
300+
301+
@Test
302+
fun `setUser with partial geo data creates user with available geo properties`() {
303+
val userKeys =
304+
JavaOnlyMap.of(
305+
"id",
306+
"123",
307+
"geo",
308+
JavaOnlyMap.of(
309+
"city",
310+
"New York",
311+
"country_code",
312+
"US",
313+
),
314+
)
315+
val userDataKeys = JavaOnlyMap.of()
316+
317+
module.setUser(userKeys, userDataKeys)
318+
}
319+
320+
@Test
321+
fun `setUser with empty geo data handles empty geo object`() {
322+
val userKeys =
323+
JavaOnlyMap.of(
324+
"id",
325+
"123",
326+
"geo",
327+
JavaOnlyMap.of(),
328+
)
329+
val userDataKeys = JavaOnlyMap.of()
330+
331+
module.setUser(userKeys, userDataKeys)
332+
}
333+
334+
@Test
335+
fun `setUser with null geo data handles null geo gracefully`() {
336+
val userKeys =
337+
JavaOnlyMap.of(
338+
"id",
339+
"123",
340+
"geo",
341+
null,
342+
)
343+
val userDataKeys = JavaOnlyMap.of()
344+
345+
module.setUser(userKeys, userDataKeys)
346+
}
347+
348+
@Test
349+
fun `setUser with invalid geo data handles non-map geo gracefully`() {
350+
val userKeys =
351+
JavaOnlyMap.of(
352+
"id",
353+
"123",
354+
"geo",
355+
"invalid_geo_data",
356+
)
357+
val userDataKeys = JavaOnlyMap.of()
358+
359+
module.setUser(userKeys, userDataKeys)
360+
}
275361
}

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@
22
#import <RNSentry/RNSentry.h>
33

44
@class SentryOptions;
5+
@class SentryUser;
56

67
@interface SentrySDKInternal (PrivateTests)
78

89
+ (nullable SentryOptions *)options;
910
@end
11+
12+
@interface RNSentry (PrivateTests)
13+
14+
+ (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys
15+
otherUserKeys:(NSDictionary *)userDataKeys;
16+
17+
@end

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryTests.m

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,4 +857,90 @@ - (void)testCreateOptionsWithDictionaryEnableSessionReplayInUnreliableEnvironmen
857857
@"enableSessionReplayInUnreliableEnvironment should be disabled");
858858
}
859859

860+
- (void)testCreateUserWithGeoDataCreatesSentryGeoObject
861+
{
862+
NSDictionary *userKeys = @{
863+
@"id" : @"123",
864+
@"email" : @"test@example.com",
865+
@"username" : @"testuser",
866+
@"geo" :
867+
@ { @"city" : @"San Francisco", @"country_code" : @"US", @"region" : @"California" }
868+
};
869+
870+
NSDictionary *userDataKeys = @{ @"customField" : @"customValue" };
871+
872+
SentryUser *user = [RNSentry userFrom:userKeys otherUserKeys:userDataKeys];
873+
874+
XCTAssertNotNil(user, @"User should not be nil");
875+
XCTAssertEqual(user.userId, @"123", @"User ID should match");
876+
XCTAssertEqual(user.email, @"test@example.com", @"Email should match");
877+
XCTAssertEqual(user.username, @"testuser", @"Username should match");
878+
879+
// Test that geo data is properly converted to SentryGeo object
880+
XCTAssertNotNil(user.geo, @"Geo should not be nil");
881+
XCTAssertTrue([user.geo isKindOfClass:[SentryGeo class]], @"Geo should be SentryGeo object");
882+
XCTAssertEqual(user.geo.city, @"San Francisco", @"City should match");
883+
XCTAssertEqual(user.geo.countryCode, @"US", @"Country code should match");
884+
XCTAssertEqual(user.geo.region, @"California", @"Region should match");
885+
886+
// Test that custom data is preserved
887+
XCTAssertNotNil(user.data, @"User data should not be nil");
888+
XCTAssertEqual(user.data[@"customField"], @"customValue", @"Custom field should be preserved");
889+
}
890+
891+
- (void)testCreateUserWithPartialGeoDataCreatesSentryGeoObject
892+
{
893+
NSDictionary *userKeys =
894+
@{ @"id" : @"456", @"geo" : @ { @"city" : @"New York", @"country_code" : @"US" } };
895+
896+
NSDictionary *userDataKeys = @{};
897+
898+
SentryUser *user = [RNSentry userFrom:userKeys otherUserKeys:userDataKeys];
899+
900+
XCTAssertNotNil(user, @"User should not be nil");
901+
XCTAssertEqual(user.userId, @"456", @"User ID should match");
902+
903+
// Test that partial geo data is properly converted to SentryGeo object
904+
XCTAssertNotNil(user.geo, @"Geo should not be nil");
905+
XCTAssertTrue([user.geo isKindOfClass:[SentryGeo class]], @"Geo should be SentryGeo object");
906+
XCTAssertEqual(user.geo.city, @"New York", @"City should match");
907+
XCTAssertEqual(user.geo.countryCode, @"US", @"Country code should match");
908+
XCTAssertNil(user.geo.region, @"Region should be nil when not provided");
909+
}
910+
911+
- (void)testCreateUserWithEmptyGeoDataCreatesSentryGeoObject
912+
{
913+
NSDictionary *userKeys = @{ @"id" : @"789", @"geo" : @ {} };
914+
915+
NSDictionary *userDataKeys = @{};
916+
917+
SentryUser *user = [RNSentry userFrom:userKeys otherUserKeys:userDataKeys];
918+
919+
XCTAssertNotNil(user, @"User should not be nil");
920+
XCTAssertEqual(user.userId, @"789", @"User ID should match");
921+
922+
// Test that empty geo data is properly converted to SentryGeo object
923+
XCTAssertNotNil(user.geo, @"Geo should not be nil");
924+
XCTAssertTrue([user.geo isKindOfClass:[SentryGeo class]], @"Geo should be SentryGeo object");
925+
XCTAssertNil(user.geo.city, @"City should be nil when not provided");
926+
XCTAssertNil(user.geo.countryCode, @"Country code should be nil when not provided");
927+
XCTAssertNil(user.geo.region, @"Region should be nil when not provided");
928+
}
929+
930+
- (void)testCreateUserWithoutGeoDataDoesNotCreateGeoObject
931+
{
932+
NSDictionary *userKeys = @{ @"id" : @"999", @"email" : @"test@example.com" };
933+
934+
NSDictionary *userDataKeys = @{};
935+
936+
SentryUser *user = [RNSentry userFrom:userKeys otherUserKeys:userDataKeys];
937+
938+
XCTAssertNotNil(user, @"User should not be nil");
939+
XCTAssertEqual(user.userId, @"999", @"User ID should match");
940+
XCTAssertEqual(user.email, @"test@example.com", @"Email should match");
941+
942+
// Test that no geo object is created when geo data is not provided
943+
XCTAssertNil(user.geo, @"Geo should be nil when not provided");
944+
}
945+
860946
@end

packages/core/RNSentryCocoaTester/RNSentryCocoaTesterTests/RNSentryUserTests.m

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#import "RNSentry+Test.h"
22
#import "RNSentryTests.h"
3+
#import <Sentry/SentryGeo.h>
34
#import <XCTest/XCTest.h>
45

56
@interface RNSentryUserTests : XCTestCase
@@ -102,4 +103,73 @@ - (void)testNullValuesUser
102103
XCTAssertTrue([actual isEqualToUser:expected]);
103104
}
104105

106+
- (void)testUserWithGeo
107+
{
108+
SentryUser *expected = [SentryUser alloc];
109+
[expected setUserId:@"123"];
110+
[expected setEmail:@"test@example.com"];
111+
[expected setUsername:@"testuser"];
112+
113+
SentryGeo *expectedGeo = [SentryGeo alloc];
114+
[expectedGeo setCity:@"San Francisco"];
115+
[expectedGeo setCountryCode:@"US"];
116+
[expectedGeo setRegion:@"California"];
117+
[expected setGeo:expectedGeo];
118+
119+
SentryUser *actual = [RNSentry userFrom:@{
120+
@"id" : @"123",
121+
@"email" : @"test@example.com",
122+
@"username" : @"testuser",
123+
@"geo" :
124+
@ { @"city" : @"San Francisco", @"country_code" : @"US", @"region" : @"California" }
125+
}
126+
otherUserKeys:nil];
127+
128+
XCTAssertTrue([actual isEqualToUser:expected]);
129+
}
130+
131+
- (void)testUserWithPartialGeo
132+
{
133+
SentryUser *expected = [[SentryUser alloc] init];
134+
[expected setUserId:@"123"];
135+
136+
SentryGeo *expectedGeo = [SentryGeo alloc];
137+
[expectedGeo setCity:@"New York"];
138+
[expectedGeo setCountryCode:@"US"];
139+
[expected setGeo:expectedGeo];
140+
141+
SentryUser *actual = [RNSentry userFrom:@{
142+
@"id" : @"123",
143+
@"geo" : @ { @"city" : @"New York", @"country_code" : @"US" }
144+
}
145+
otherUserKeys:nil];
146+
147+
XCTAssertTrue([actual isEqualToUser:expected]);
148+
}
149+
150+
- (void)testUserWithEmptyGeo
151+
{
152+
SentryUser *expected = [[SentryUser alloc] init];
153+
[expected setUserId:@"123"];
154+
155+
// Empty geo dictionary creates an empty SentryGeo object
156+
SentryGeo *expectedGeo = [SentryGeo alloc];
157+
[expected setGeo:expectedGeo];
158+
159+
SentryUser *actual = [RNSentry userFrom:@{ @"id" : @"123", @"geo" : @ {} } otherUserKeys:nil];
160+
161+
XCTAssertTrue([actual isEqualToUser:expected]);
162+
}
163+
164+
- (void)testUserWithInvalidGeo
165+
{
166+
SentryUser *expected = [[SentryUser alloc] init];
167+
[expected setUserId:@"123"];
168+
169+
SentryUser *actual = [RNSentry userFrom:@{ @"id" : @"123", @"geo" : @"invalid_geo_data" }
170+
otherUserKeys:nil];
171+
172+
XCTAssertTrue([actual isEqualToUser:expected]);
173+
}
174+
105175
@end
0 Bytes
Binary file not shown.

packages/core/android/src/main/java/io/sentry/react/RNSentryModuleImpl.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
import io.sentry.android.core.internal.debugmeta.AssetsDebugMetaLoader;
6464
import io.sentry.android.core.internal.util.SentryFrameMetricsCollector;
6565
import io.sentry.android.core.performance.AppStartMetrics;
66+
import io.sentry.protocol.Geo;
6667
import io.sentry.protocol.SdkVersion;
6768
import io.sentry.protocol.SentryId;
6869
import io.sentry.protocol.SentryPackage;
@@ -739,6 +740,23 @@ public void setUser(final ReadableMap userKeys, final ReadableMap userDataKeys)
739740
if (userKeys.hasKey("ip_address")) {
740741
userInstance.setIpAddress(userKeys.getString("ip_address"));
741742
}
743+
744+
if (userKeys.hasKey("geo")) {
745+
ReadableMap geoMap = userKeys.getMap("geo");
746+
if (geoMap != null) {
747+
Geo geoData = new Geo();
748+
if (geoMap.hasKey("city")) {
749+
geoData.setCity(geoMap.getString("city"));
750+
}
751+
if (geoMap.hasKey("country_code")) {
752+
geoData.setCountryCode(geoMap.getString("country_code"));
753+
}
754+
if (geoMap.hasKey("region")) {
755+
geoData.setRegion(geoMap.getString("region"));
756+
}
757+
userInstance.setGeo(geoData);
758+
}
759+
}
742760
}
743761

744762
if (userDataKeys != null) {

packages/core/ios/RNSentry.mm

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#import <Sentry/SentryEvent.h>
2626
#import <Sentry/SentryException.h>
2727
#import <Sentry/SentryFormatter.h>
28+
#import <Sentry/SentryGeo.h>
2829
#import <Sentry/SentryOptions.h>
2930
#import <Sentry/SentryOptionsInternal.h>
3031
#import <Sentry/SentryScreenFrames.h>
@@ -722,6 +723,29 @@ + (SentryUser *_Nullable)userFrom:(NSDictionary *)userKeys
722723
[userInstance setUsername:username];
723724
}
724725

726+
id geo = [userKeys valueForKey:@"geo"];
727+
if ([geo isKindOfClass:NSDictionary.class]) {
728+
NSDictionary *geoDict = (NSDictionary *)geo;
729+
SentryGeo *sentryGeo = [SentryGeo alloc];
730+
731+
id city = [geoDict valueForKey:@"city"];
732+
if ([city isKindOfClass:NSString.class]) {
733+
[sentryGeo setCity:city];
734+
}
735+
736+
id countryCode = [geoDict valueForKey:@"country_code"];
737+
if ([countryCode isKindOfClass:NSString.class]) {
738+
[sentryGeo setCountryCode:countryCode];
739+
}
740+
741+
id region = [geoDict valueForKey:@"region"];
742+
if ([region isKindOfClass:NSString.class]) {
743+
[sentryGeo setRegion:region];
744+
}
745+
746+
[userInstance setGeo:sentryGeo];
747+
}
748+
725749
if ([userDataKeys isKindOfClass:NSDictionary.class]) {
726750
[userInstance setData:userDataKeys];
727751
}

packages/core/src/js/wrapper.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,13 +394,13 @@ export const NATIVE: SentryNativeWrapper = {
394394
let userKeys = null;
395395
let userDataKeys = null;
396396
if (user) {
397-
const { id, ip_address, email, username, ...otherKeys } = user;
398-
// TODO: Update native impl to use geo
399-
const requiredUser: Omit<RequiredKeysUser, 'geo'> = {
397+
const { id, ip_address, email, username, geo, ...otherKeys } = user;
398+
const requiredUser: RequiredKeysUser = {
400399
id,
401400
ip_address,
402401
email,
403402
username,
403+
geo,
404404
};
405405
userKeys = this._serializeObject(requiredUser);
406406
userDataKeys = this._serializeObject(otherKeys);

0 commit comments

Comments
 (0)