From 38128f1f35573f7cc8c34a959d90369f4a21a3b4 Mon Sep 17 00:00:00 2001 From: Nickolas Dimitrakas Date: Wed, 10 Sep 2025 19:46:32 -0400 Subject: [PATCH 1/2] fix: Consent Mapping Configuration --- .../MPKitFirebaseAnalytics.m | 159 ++++++++++-------- 1 file changed, 92 insertions(+), 67 deletions(-) diff --git a/mParticle-Google-Analytics-Firebase/MPKitFirebaseAnalytics.m b/mParticle-Google-Analytics-Firebase/MPKitFirebaseAnalytics.m index fcff41e..a33c205 100755 --- a/mParticle-Google-Analytics-Firebase/MPKitFirebaseAnalytics.m +++ b/mParticle-Google-Analytics-Firebase/MPKitFirebaseAnalytics.m @@ -42,10 +42,13 @@ @implementation MPKitFirebaseAnalytics static NSString *const aToZCharacters = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; static NSString *const instanceIdIntegrationKey = @"app_instance_id"; -static NSString *const kMPFIRGA4AdStorageKey = @"ad_storage"; -static NSString *const kMPFIRGA4AdUserDataKey = @"ad_user_data"; -static NSString *const kMPFIRGA4AdPersonalizationKey = @"ad_personalization"; -static NSString *const kMPFIRGA4AnalyticsStorageKey = @"analytics_storage"; +// Consent Mapping Keys +static NSString *const kMPFIRGAAdStorageKey = @"ad_storage"; +static NSString *const kMPFIRGAAdUserDataKey = @"ad_user_data"; +static NSString *const kMPFIRGAAdPersonalizationKey = @"ad_personalization"; +static NSString *const kMPFIRGAAnalyticsStorageKey = @"analytics_storage"; + +// Default Consent Keys (from mParticle UI) static NSString *const kMPFIRGA4DefaultAdStorageKey = @"defaultAdStorageConsentSDK"; static NSString *const kMPFIRGA4DefaultAdUserDataKey = @"defaultAdUserDataConsentSDK"; static NSString *const kMPFIRGA4DefaultAdPersonalizationKey = @"defaultAdPersonalizationConsentSDK"; @@ -359,77 +362,52 @@ - (MPKitExecStatus *)setConsentState:(nullable MPConsentState *)state { } - (void)updateConsent { - FIRConsentStatus adStorageStatus; - FIRConsentStatus adUserDataStatus; - FIRConsentStatus analyticsStorageStatus; - FIRConsentStatus adPersonalizationStatus; - - // Default Consent States - if (self.configuration[kMPFIRGA4DefaultAdStorageKey]) { - if ([self.configuration[kMPFIRGA4DefaultAdStorageKey] isEqual: @"Granted"]) { - adStorageStatus = FIRConsentStatusGranted; - } else if ([self.configuration[kMPFIRGA4DefaultAdStorageKey] isEqual: @"Denied"]) { - adStorageStatus = FIRConsentStatusDenied; - } - } - if (self.configuration[kMPFIRGA4DefaultAdUserDataKey]) { - if ([self.configuration[kMPFIRGA4DefaultAdUserDataKey] isEqual: @"Granted"]) { - adUserDataStatus = FIRConsentStatusGranted; - } else if ([self.configuration[kMPFIRGA4DefaultAdUserDataKey] isEqual: @"Denied"]) { - adUserDataStatus = FIRConsentStatusDenied; - } - } - if (self.configuration[kMPFIRGA4DefaultAnalyticsStorageKey]) { - if ([self.configuration[kMPFIRGA4DefaultAnalyticsStorageKey] isEqual: @"Granted"]) { - analyticsStorageStatus = FIRConsentStatusGranted; - } else if ([self.configuration[kMPFIRGA4DefaultAnalyticsStorageKey] isEqual: @"Denied"]) { - analyticsStorageStatus = FIRConsentStatusDenied; - } - } - if (self.configuration[kMPFIRGA4DefaultAdPersonalizationKey]) { - if ([self.configuration[kMPFIRGA4DefaultAdPersonalizationKey] isEqual: @"Granted"]) { - adPersonalizationStatus = FIRConsentStatusGranted; - } else if ([self.configuration[kMPFIRGA4DefaultAdPersonalizationKey] isEqual: @"Denied"]) { - adPersonalizationStatus = FIRConsentStatusDenied; - } + NSArray *mappings = [self mappingForKey: @"consentMappingSDK"]; + NSDictionary *mappingsConfig; + if (mappings != nil) { + mappingsConfig = [self convertToKeyValuePairs: mappings]; } - + + MParticleUser *currentUser = [[[MParticle sharedInstance] identity] currentUser]; - NSDictionary *userConsentMap = currentUser.consentState.gdprConsentState; + NSDictionary *gdprConsents = currentUser.consentState.gdprConsentState; - // Update from mParticle consent - if (self.configuration[kMPFIRGA4AdStorageKey] && userConsentMap[self.configuration[kMPFIRGA4AdStorageKey]]) { - MPGDPRConsent *consent = userConsentMap[self.configuration[kMPFIRGA4AdStorageKey]]; - adStorageStatus = consent.consented ? FIRConsentStatusGranted : FIRConsentStatusDenied; - } - if (self.configuration[kMPFIRGA4AdUserDataKey] && userConsentMap[self.configuration[kMPFIRGA4AdUserDataKey]]) { - MPGDPRConsent *consent = userConsentMap[self.configuration[kMPFIRGA4AdUserDataKey]]; - adUserDataStatus = consent.consented ? FIRConsentStatusGranted : FIRConsentStatusDenied; - } - if (self.configuration[kMPFIRGA4AnalyticsStorageKey] && userConsentMap[self.configuration[kMPFIRGA4AnalyticsStorageKey]]) { - MPGDPRConsent *consent = userConsentMap[self.configuration[kMPFIRGA4AnalyticsStorageKey]]; - analyticsStorageStatus = consent.consented ? FIRConsentStatusGranted : FIRConsentStatusDenied; - } - if (self.configuration[kMPFIRGA4AdPersonalizationKey] && userConsentMap[self.configuration[kMPFIRGA4AdPersonalizationKey]]) { - MPGDPRConsent *consent = userConsentMap[self.configuration[kMPFIRGA4AdPersonalizationKey]]; - adPersonalizationStatus = consent.consented ? FIRConsentStatusGranted : FIRConsentStatusDenied; - } + NSNumber *adStorage = [self resolvedConsentForMappingKey:kMPFIRGAAdStorageKey + defaultKey:kMPFIRGA4DefaultAdStorageKey + gdprConsents:gdprConsents + mappingsConfig:mappingsConfig]; + + NSNumber *adUserData = [self resolvedConsentForMappingKey:kMPFIRGAAdUserDataKey + defaultKey:kMPFIRGA4DefaultAdUserDataKey + gdprConsents:gdprConsents + mappingsConfig:mappingsConfig]; + + NSNumber *analyticsStorage = [self resolvedConsentForMappingKey:kMPFIRGAAnalyticsStorageKey + defaultKey:kMPFIRGA4DefaultAnalyticsStorageKey + gdprConsents:gdprConsents + mappingsConfig:mappingsConfig]; + + NSNumber *adPersonalization = [self resolvedConsentForMappingKey:kMPFIRGAAdPersonalizationKey + defaultKey:kMPFIRGA4DefaultAdPersonalizationKey + gdprConsents:gdprConsents + mappingsConfig:mappingsConfig]; + + NSMutableDictionary *uploadDict = [NSMutableDictionary dictionary]; - // Construct a dictionary of consents - NSMutableDictionary *uploadDict = [[NSMutableDictionary alloc] init]; - if (adStorageStatus) { - uploadDict[FIRConsentTypeAdStorage] = adStorageStatus; + if (adStorage != nil) { + uploadDict[FIRConsentTypeAdStorage] = adStorage.boolValue ? FIRConsentStatusGranted : FIRConsentStatusDenied; } - if (adUserDataStatus) { - uploadDict[FIRConsentTypeAdUserData] = adUserDataStatus; + if (adUserData != nil) { + uploadDict[FIRConsentTypeAdUserData] = adUserData.boolValue ? FIRConsentStatusGranted : FIRConsentStatusDenied; } - if (analyticsStorageStatus) { - uploadDict[FIRConsentTypeAnalyticsStorage] = analyticsStorageStatus; + if (analyticsStorage != nil) { + uploadDict[FIRConsentTypeAnalyticsStorage] = analyticsStorage.boolValue ? FIRConsentStatusGranted : FIRConsentStatusDenied; } - if (adPersonalizationStatus) { - uploadDict[FIRConsentTypeAdPersonalization] = adPersonalizationStatus; + if (adPersonalization != nil) { + uploadDict[FIRConsentTypeAdPersonalization] = adPersonalization.boolValue ? FIRConsentStatusGranted : FIRConsentStatusDenied; } - + + // Update consent state with FIRAnalytics [FIRAnalytics setConsent:uploadDict]; } @@ -654,4 +632,51 @@ - (void)updateInstanceIDIntegration { } } +#pragma mark - Helpers + +- (NSNumber * _Nullable)resolvedConsentForMappingKey:(NSString *)mappingKey + defaultKey:(NSString *)defaultKey + gdprConsents:(NSDictionary *)gdprConsents + mappingsConfig:(NSDictionary *) mapping { + + // Prefer mParticle Consent if available + NSString *purpose = mapping[mappingKey]; + if (purpose) { + MPGDPRConsent *consent = gdprConsents[purpose]; + if (consent) { + return @(consent.consented); + } + } + + // Fallback to configuration defaults + NSString *value = self->_configuration[defaultKey]; + if ([value isEqualToString:@"Granted"]) { + return @(YES); + } else if ([value isEqualToString:@"Denied"]) { + return @(NO); + } + return nil; +} + +- (NSArray*)mappingForKey:(NSString*)key { + NSString *mappingJson = _configuration[@"consentMappingSDK"]; + if ([mappingJson isKindOfClass:[NSString class]]) { + NSData *jsonData = [mappingJson dataUsingEncoding:NSUTF8StringEncoding]; + return [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil]; + } + return nil; +} + +- (NSDictionary*)convertToKeyValuePairs: (NSArray*) mappings { + NSMutableDictionary *dict = [NSMutableDictionary dictionary]; + for (NSDictionary *entry in mappings) { + NSString *value = entry[@"value"]; + NSString *purpose = [entry[@"map"] lowercaseString]; + if (value && purpose) { + dict[value] = purpose; + } + } + return dict; +} + @end From 7c1fc4394c6c21df55b3339018a56337b9cc09f8 Mon Sep 17 00:00:00 2001 From: Nickolas Dimitrakas Date: Thu, 11 Sep 2025 09:56:12 -0400 Subject: [PATCH 2/2] adjust parameter naming and add error handling --- .../MPKitFirebaseAnalytics.m | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/mParticle-Google-Analytics-Firebase/MPKitFirebaseAnalytics.m b/mParticle-Google-Analytics-Firebase/MPKitFirebaseAnalytics.m index a33c205..8571605 100755 --- a/mParticle-Google-Analytics-Firebase/MPKitFirebaseAnalytics.m +++ b/mParticle-Google-Analytics-Firebase/MPKitFirebaseAnalytics.m @@ -375,22 +375,22 @@ - (void)updateConsent { NSNumber *adStorage = [self resolvedConsentForMappingKey:kMPFIRGAAdStorageKey defaultKey:kMPFIRGA4DefaultAdStorageKey gdprConsents:gdprConsents - mappingsConfig:mappingsConfig]; + mapping:mappingsConfig]; NSNumber *adUserData = [self resolvedConsentForMappingKey:kMPFIRGAAdUserDataKey defaultKey:kMPFIRGA4DefaultAdUserDataKey gdprConsents:gdprConsents - mappingsConfig:mappingsConfig]; + mapping:mappingsConfig]; NSNumber *analyticsStorage = [self resolvedConsentForMappingKey:kMPFIRGAAnalyticsStorageKey defaultKey:kMPFIRGA4DefaultAnalyticsStorageKey gdprConsents:gdprConsents - mappingsConfig:mappingsConfig]; + mapping:mappingsConfig]; NSNumber *adPersonalization = [self resolvedConsentForMappingKey:kMPFIRGAAdPersonalizationKey defaultKey:kMPFIRGA4DefaultAdPersonalizationKey gdprConsents:gdprConsents - mappingsConfig:mappingsConfig]; + mapping:mappingsConfig]; NSMutableDictionary *uploadDict = [NSMutableDictionary dictionary]; @@ -637,7 +637,7 @@ - (void)updateInstanceIDIntegration { - (NSNumber * _Nullable)resolvedConsentForMappingKey:(NSString *)mappingKey defaultKey:(NSString *)defaultKey gdprConsents:(NSDictionary *)gdprConsents - mappingsConfig:(NSDictionary *) mapping { + mapping:(NSDictionary *) mapping { // Prefer mParticle Consent if available NSString *purpose = mapping[mappingKey]; @@ -660,11 +660,20 @@ - (NSNumber * _Nullable)resolvedConsentForMappingKey:(NSString *)mappingKey - (NSArray*)mappingForKey:(NSString*)key { NSString *mappingJson = _configuration[@"consentMappingSDK"]; - if ([mappingJson isKindOfClass:[NSString class]]) { - NSData *jsonData = [mappingJson dataUsingEncoding:NSUTF8StringEncoding]; - return [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:nil]; + if (![mappingJson isKindOfClass:[NSString class]]) { + return nil; } - return nil; + + NSData *jsonData = [mappingJson dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error; + NSArray *result = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error]; + + if (error) { + NSLog(@"Failed to parse consent mapping JSON: %@", error.localizedDescription); + return nil; + } + + return result; } - (NSDictionary*)convertToKeyValuePairs: (NSArray*) mappings {