diff --git a/FirebaseRemoteConfig/Sources/RCNConfigSettings.m b/FirebaseRemoteConfig/Sources/RCNConfigSettings.m index 09af88f32dc..45b672ae4cf 100644 --- a/FirebaseRemoteConfig/Sources/RCNConfigSettings.m +++ b/FirebaseRemoteConfig/Sources/RCNConfigSettings.m @@ -28,6 +28,7 @@ static NSString *const kRCNGroupPrefix = @"frc.group."; static NSString *const kRCNUserDefaultsKeyNamelastETag = @"lastETag"; static NSString *const kRCNUserDefaultsKeyNameLastSuccessfulFetchTime = @"lastSuccessfulFetchTime"; +static NSString *const kRCNAnalyticsFirstOpenTimePropertyName = @"_fot"; static const int kRCNExponentialBackoffMinimumInterval = 60 * 2; // 2 mins. static const int kRCNExponentialBackoffMaximumInterval = 60 * 60 * 4; // 4 hours. @@ -359,16 +360,31 @@ - (NSString *)nextRequestWithUserProperties:(NSDictionary *)userProperties { if (userProperties && userProperties.count > 0) { NSError *error; - NSData *jsonData = [NSJSONSerialization dataWithJSONObject:userProperties - options:0 - error:&error]; - if (!error) { - ret = [ret - stringByAppendingString:[NSString - stringWithFormat:@", analytics_user_properties:%@", - [[NSString alloc] - initWithData:jsonData - encoding:NSUTF8StringEncoding]]]; + + // Extract first open time from user properties and send as a separate field + NSNumber *firstOpenTime = userProperties[kRCNAnalyticsFirstOpenTimePropertyName]; + NSMutableDictionary *remainingUserProperties = [userProperties mutableCopy]; + if (firstOpenTime != nil) { + NSDate *date = [NSDate dateWithTimeIntervalSince1970:([firstOpenTime longValue] / 1000)]; + NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init]; + NSString *firstOpenTimeISOString = [formatter stringFromDate:date]; + ret = [ret stringByAppendingString:[NSString stringWithFormat:@", first_open_time:'%@'", + firstOpenTimeISOString]]; + + [remainingUserProperties removeObjectForKey:kRCNAnalyticsFirstOpenTimePropertyName]; + } + if (remainingUserProperties.count > 0) { + NSData *jsonData = [NSJSONSerialization dataWithJSONObject:remainingUserProperties + options:0 + error:&error]; + if (!error) { + ret = [ret + stringByAppendingString:[NSString + stringWithFormat:@", analytics_user_properties:%@", + [[NSString alloc] + initWithData:jsonData + encoding:NSUTF8StringEncoding]]]; + } } } ret = [ret stringByAppendingString:@"}"]; diff --git a/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m b/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m index 5fc426b1e42..cb3c869d70d 100644 --- a/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m +++ b/FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m @@ -79,6 +79,10 @@ @interface RCNUserDefaultsManager (Test) + (NSUserDefaults *)sharedUserDefaultsForBundleIdentifier:(NSString *)bundleIdentifier; @end +@interface RCNConfigSettings (Test) +- (NSString *)nextRequestWithUserProperties:(NSDictionary *)userProperties; +@end + typedef NS_ENUM(NSInteger, RCNTestRCInstance) { RCNTestRCInstanceDefault, RCNTestRCInstanceSecondNamespace, @@ -1415,6 +1419,30 @@ - (void)testSetFetchTimeoutConfigSetting { } } +- (void)testFetchRequestWithUserPropertiesOnly { + NSDictionary *userProperties = @{@"user_key" : @"user_value"}; + NSString *req = [_settings nextRequestWithUserProperties:userProperties]; + + XCTAssertTrue([req containsString:@"analytics_user_properties:{\"user_key\":\"user_value\"}"]); + XCTAssertFalse([req containsString:@"first_open_time"]); +} + +- (void)testFetchRequestWithFirstOpenTimeAndUserProperties { + NSDictionary *userProperties = @{@"_fot" : @1649116800000, @"user_key" : @"user_value"}; + NSString *req = [_settings nextRequestWithUserProperties:userProperties]; + + XCTAssertTrue([req containsString:@"first_open_time:'2022-04-05T00:00:00Z'"]); + XCTAssertTrue([req containsString:@"analytics_user_properties:{\"user_key\":\"user_value\"}"]); +} + +- (void)testFetchRequestFirstOpenTimeOnly { + NSDictionary *userProperties = @{@"_fot" : @1650315600000}; + NSString *req = [_settings nextRequestWithUserProperties:userProperties]; + + XCTAssertTrue([req containsString:@"first_open_time:'2022-04-18T21:00:00Z'"]); + XCTAssertFalse([req containsString:@"analytics_user_properties"]); +} + #pragma mark - Public Factory Methods - (void)testConfigureConfigWithValidInput {