Skip to content

Commit 7816985

Browse files
authored
feat(analytics): add setConsent implementation (invertase#7629)
1 parent 2ff569d commit 7816985

File tree

17 files changed

+269
-21
lines changed

17 files changed

+269
-21
lines changed

docs/analytics/usage/index.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,19 @@ import { firebase } from '@react-native-firebase/analytics';
182182
await firebase.analytics().setAnalyticsCollectionEnabled(true);
183183
```
184184

185+
To update user's consent (e.g. once you have the users consent), call the `setConsent` method:
186+
187+
```js
188+
import { firebase } from '@react-native-firebase/analytics';
189+
// ...
190+
await firebase.analytics().setConsent({
191+
analytics_storage: true,
192+
ad_storage: true,
193+
ad_user_data: true,
194+
ad_personalization: true,
195+
});
196+
```
197+
185198
## Disable screenview tracking
186199

187200
Analytics automatically tracks some information about screens in your application, such as the class name of the UIViewController or Activity that is currently in focus.

packages/analytics/__tests__/analytics.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,31 @@ describe('Analytics', function () {
9898
);
9999
});
100100

101+
it('throws if consentSettings is not an object', function () {
102+
// @ts-ignore test
103+
expect(() => firebase.analytics().setConsent(1337)).toThrowError(
104+
'The supplied arg must be an object of key/values.',
105+
);
106+
});
107+
it('throws if consentSettings is invalid', function () {
108+
const consentSettings = {
109+
ad_storage: true,
110+
foo: {
111+
bar: 'baz',
112+
},
113+
};
114+
// @ts-ignore test
115+
expect(() => firebase.analytics().setConsent(consentSettings)).toThrowError(
116+
"'consentSettings' value for parameter 'foo' is invalid, expected a boolean.",
117+
);
118+
});
119+
it('throws if one value of consentSettings is a number', function () {
120+
// @ts-ignore test
121+
expect(() => firebase.analytics().setConsent({ ad_storage: 123 })).toThrowError(
122+
"'consentSettings' value for parameter 'ad_storage' is invalid, expected a boolean.",
123+
);
124+
});
125+
101126
it('errors when no parameters are set', function () {
102127
// @ts-ignore test
103128
expect(() => firebase.analytics().logSearch()).toThrowError(

packages/analytics/android/build.gradle

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,11 @@ apply from: file('./../../app/android/firebase-json.gradle')
6565
String collectionDeactivated = 'false'
6666
String adidEnabled = 'true'
6767
String ssaidEnabled = 'true'
68-
String personalizationEnabled = 'true'
6968
String automaticScreenReportingEnabled = 'true'
69+
String analyticsStorageEnabled = 'true'
70+
String adStorageEnabled = 'true'
71+
String adUserDataEnabled = 'true'
72+
String personalizationEnabled = 'true'
7073

7174
// If nothing is defined, data collection defaults to true
7275
String dataCollectionEnabled = 'true'
@@ -96,12 +99,21 @@ if (rootProject.ext && rootProject.ext.firebaseJson) {
9699
if (rnfbConfig.isFlagEnabled('google_analytics_ssaid_collection_enabled', true) == false) {
97100
ssaidEnabled = 'false'
98101
}
99-
if (rnfbConfig.isFlagEnabled('analytics_default_allow_ad_personalization_signals', true) == false) {
100-
personalizationEnabled = 'false'
101-
}
102102
if (rnfbConfig.isFlagEnabled('google_analytics_automatic_screen_reporting_enabled', true) == false) {
103103
automaticScreenReportingEnabled = 'false'
104104
}
105+
if (rnfbConfig.isFlagEnabled('analytics_default_allow_analytics_storage', true) == false) {
106+
analyticsStorageEnabled = 'false'
107+
}
108+
if (rnfbConfig.isFlagEnabled('analytics_default_allow_ad_storage', true) == false) {
109+
adStorageEnabled = 'false'
110+
}
111+
if (rnfbConfig.isFlagEnabled('analytics_default_allow_ad_user_data', true) == false) {
112+
adUserDataEnabled = 'false'
113+
}
114+
if (rnfbConfig.isFlagEnabled('analytics_default_allow_ad_personalization_signals', true) == false) {
115+
personalizationEnabled = 'false'
116+
}
105117
}
106118

107119
android {
@@ -117,8 +129,11 @@ android {
117129
firebaseJsonCollectionDeactivated: collectionDeactivated,
118130
firebaseJsonAdidEnabled: adidEnabled,
119131
firebaseJsonSsaidEnabled: ssaidEnabled,
120-
firebaseJsonPersonalizationEnabled: personalizationEnabled,
121-
firebaseJsonAutomaticScreenReportingEnabled: automaticScreenReportingEnabled
132+
firebaseJsonAutomaticScreenReportingEnabled: automaticScreenReportingEnabled,
133+
firebaseJsonAnalyticsStorageEnabled: analyticsStorageEnabled,
134+
firebaseJsonAdStorageEnabled: adStorageEnabled,
135+
firebaseJsonAdUserDataEnabled: adUserDataEnabled,
136+
firebaseJsonPersonalizationEnabled: personalizationEnabled
122137
]
123138
}
124139

packages/analytics/android/src/main/AndroidManifest.xml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="${firebaseJsonCollectionDeactivated}" />
1212
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="${firebaseJsonAdidEnabled}" />
1313
<meta-data android:name="google_analytics_ssaid_collection_enabled" android:value="${firebaseJsonSsaidEnabled}" />
14-
<meta-data android:name="google_analytics_default_allow_ad_personalization_signals" android:value="${firebaseJsonPersonalizationEnabled}" />
1514
<meta-data android:name="google_analytics_automatic_screen_reporting_enabled" android:value="${firebaseJsonAutomaticScreenReportingEnabled}" />
15+
<meta-data android:name="google_analytics_default_allow_analytics_storage" android:value="${firebaseJsonAnalyticsStorageEnabled}" />
16+
<meta-data android:name="google_analytics_default_allow_ad_storage" android:value="${firebaseJsonAdStorageEnabled}" />
17+
<meta-data android:name="google_analytics_default_allow_ad_user_data" android:value="${firebaseJsonAdUserDataEnabled}" />
18+
<meta-data android:name="google_analytics_default_allow_ad_personalization_signals" android:value="${firebaseJsonPersonalizationEnabled}" />
19+
1620
</application>
1721
</manifest>

packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222
import com.google.android.gms.tasks.Task;
2323
import com.google.android.gms.tasks.Tasks;
2424
import com.google.firebase.analytics.FirebaseAnalytics;
25+
import com.google.firebase.analytics.FirebaseAnalytics.ConsentStatus;
26+
import com.google.firebase.analytics.FirebaseAnalytics.ConsentType;
2527
import io.invertase.firebase.common.UniversalFirebaseModule;
28+
import java.util.EnumMap;
29+
import java.util.Map;
2630
import java.util.Set;
2731

2832
@SuppressWarnings("WeakerAccess")
@@ -109,4 +113,29 @@ Task<Void> setDefaultEventParameters(Bundle parameters) {
109113
return null;
110114
});
111115
}
116+
117+
Task<Void> setConsent(Bundle consentSettings) {
118+
return Tasks.call(
119+
() -> {
120+
boolean analyticsStorage = consentSettings.getBoolean("analytics_storage");
121+
boolean adStorage = consentSettings.getBoolean("ad_storage");
122+
boolean adUserData = consentSettings.getBoolean("ad_user_data");
123+
boolean adPersonalization = consentSettings.getBoolean("ad_personalization");
124+
125+
Map<ConsentType, ConsentStatus> consentMap = new EnumMap<>(ConsentType.class);
126+
consentMap.put(
127+
ConsentType.ANALYTICS_STORAGE,
128+
analyticsStorage ? ConsentStatus.GRANTED : ConsentStatus.DENIED);
129+
consentMap.put(
130+
ConsentType.AD_STORAGE, adStorage ? ConsentStatus.GRANTED : ConsentStatus.DENIED);
131+
consentMap.put(
132+
ConsentType.AD_USER_DATA, adUserData ? ConsentStatus.GRANTED : ConsentStatus.DENIED);
133+
consentMap.put(
134+
ConsentType.AD_PERSONALIZATION,
135+
adPersonalization ? ConsentStatus.GRANTED : ConsentStatus.DENIED);
136+
137+
FirebaseAnalytics.getInstance(getContext()).setConsent(consentMap);
138+
return null;
139+
});
140+
}
112141
}

packages/analytics/android/src/reactnative/java/io/invertase/firebase/analytics/ReactNativeFirebaseAnalyticsModule.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,20 @@ public void setDefaultEventParameters(@Nullable ReadableMap params, Promise prom
178178
});
179179
}
180180

181+
@ReactMethod
182+
public void setConsent(ReadableMap consentSettings, Promise promise) {
183+
module
184+
.setConsent(Arguments.toBundle(consentSettings))
185+
.addOnCompleteListener(
186+
task -> {
187+
if (task.isSuccessful()) {
188+
promise.resolve(task.getResult());
189+
} else {
190+
rejectPromiseWithExceptionMap(promise, task.getException());
191+
}
192+
});
193+
}
194+
181195
private Bundle toBundle(ReadableMap readableMap) {
182196
Bundle bundle = Arguments.toBundle(readableMap);
183197
if (bundle == null) {

packages/analytics/e2e/analytics.e2e.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,22 @@ describe('analytics() modular', function () {
453453
});
454454
});
455455

456+
describe('setConsent()', function () {
457+
it('set ad_storage=true on consentSettings', async function () {
458+
const consentSettings = {
459+
ad_storage: true,
460+
};
461+
await firebase.analytics().setConsent(consentSettings);
462+
});
463+
it('set ad_storage=false and analytics_storage=true on consentSettings', async function () {
464+
const consentSettings = {
465+
ad_storage: false,
466+
analytics_storage: true,
467+
};
468+
await firebase.analytics().setConsent(consentSettings);
469+
});
470+
});
471+
456472
// Test this one near end so all the previous hits are visible in DebugView is that is enabled
457473
describe('resetAnalyticsData()', function () {
458474
it('calls native fn without error', async function () {
@@ -1068,5 +1084,23 @@ describe('analytics() modular', function () {
10681084
await setAnalyticsCollectionEnabled(getAnalytics(), true);
10691085
});
10701086
});
1087+
1088+
describe('setConsent()', function () {
1089+
it('set ad_storage=true on consentSettings', async function () {
1090+
const consentSettings = {
1091+
ad_storage: true,
1092+
};
1093+
const { getAnalytics, setConsent } = analyticsModular;
1094+
await setConsent(getAnalytics(), consentSettings);
1095+
});
1096+
it('set ad_storage=false and analytics_storage=true on consentSettings', async function () {
1097+
const consentSettings = {
1098+
ad_storage: false,
1099+
analytics_storage: true,
1100+
};
1101+
const { getAnalytics, setConsent } = analyticsModular;
1102+
await setConsent(getAnalytics(), consentSettings);
1103+
});
1104+
});
10711105
});
10721106
});

packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,29 @@ - (dispatch_queue_t)methodQueue {
177177
return resolve([NSNull null]);
178178
}
179179

180+
RCT_EXPORT_METHOD(setConsent
181+
: (NSDictionary *)consentSettings resolver
182+
: (RCTPromiseResolveBlock)resolve rejecter
183+
: (RCTPromiseRejectBlock)reject) {
184+
@try {
185+
BOOL analyticsStorage = [consentSettings[@"analytics_storage"] boolValue];
186+
BOOL adStorage = [consentSettings[@"ad_storage"] boolValue];
187+
BOOL adUserData = [consentSettings[@"ad_user_data"] boolValue];
188+
BOOL adPersonalization = [consentSettings[@"ad_personalization"] boolValue];
189+
[FIRAnalytics setConsent:@{
190+
FIRConsentTypeAnalyticsStorage : analyticsStorage ? FIRConsentStatusGranted
191+
: FIRConsentStatusDenied,
192+
FIRConsentTypeAdStorage : adStorage ? FIRConsentStatusGranted : FIRConsentStatusDenied,
193+
FIRConsentTypeAdUserData : adUserData ? FIRConsentStatusGranted : FIRConsentStatusDenied,
194+
FIRConsentTypeAdPersonalization : adPersonalization ? FIRConsentStatusGranted
195+
: FIRConsentStatusDenied,
196+
}];
197+
} @catch (NSException *exception) {
198+
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
199+
}
200+
return resolve([NSNull null]);
201+
}
202+
180203
#pragma mark -
181204
#pragma mark Private methods
182205

packages/analytics/lib/index.d.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -774,15 +774,19 @@ export namespace FirebaseAnalyticsTypes {
774774
*/
775775
export interface ConsentSettings {
776776
/** Enables storage, such as cookies, related to advertising */
777-
ad_storage?: ConsentStatusString;
777+
ad_storage?: boolean;
778+
/** Sets consent for sending user data to Google for online advertising purposes */
779+
ad_user_data?: boolean;
780+
/** Sets consent for personalized advertising */
781+
ad_personalization?: boolean;
778782
/** Enables storage, such as cookies, related to analytics (for example, visit duration) */
779-
analytics_storage?: ConsentStatusString;
783+
analytics_storage?: boolean;
780784
/**
781785
* Enables storage that supports the functionality of the website or app such as language settings
782786
*/
783-
functionality_storage?: ConsentStatusString;
787+
functionality_storage?: boolean;
784788
/** Enables storage related to personalization such as video recommendations */
785-
personalization_storage?: ConsentStatusString;
789+
personalization_storage?: boolean;
786790
/**
787791
* Enables storage related to security such as authentication functionality, fraud prevention,
788792
* and other user protection.
@@ -1727,6 +1731,28 @@ export namespace FirebaseAnalyticsTypes {
17271731
* @param phoneNumber phone number in E.164 format - that is a leading + sign, then up to 15 digits, no dashes or spaces.
17281732
*/
17291733
initiateOnDeviceConversionMeasurementWithPhoneNumber(phoneNumber: string): Promise<void>;
1734+
1735+
/**
1736+
* For Consent Mode!
1737+
*
1738+
* #### Example
1739+
*
1740+
* ```js
1741+
* // Disable consent
1742+
* await firebase.analytics().setConsent({
1743+
* ad_personalization: false,
1744+
* analytics_storage: false,
1745+
* ad_storage: false,
1746+
* ad_user_data: false,
1747+
* });
1748+
* ```
1749+
*
1750+
* Sets the applicable end user consent state (e.g., for device identifiers) for this app on this device.
1751+
* Use the consent map to specify individual consent type values.
1752+
* Settings are persisted across app sessions.
1753+
* @param consentSettings Consent status settings for each consent type.
1754+
*/
1755+
setConsent(consentSettings: ConsentSettings): Promise<void>;
17301756
}
17311757

17321758
/**

packages/analytics/lib/index.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export {
8787
setDefaultEventParameters,
8888
initiateOnDeviceConversionMeasurementWithEmailAddress,
8989
initiateOnDeviceConversionMeasurementWithPhoneNumber,
90+
setConsent,
9091
} from './modular/index';
9192

9293
const ReservedEventNames = [
@@ -261,6 +262,26 @@ class FirebaseAnalyticsModule extends FirebaseModule {
261262
return this.native.resetAnalyticsData();
262263
}
263264

265+
setConsent(consentSettings) {
266+
if (!isObject(consentSettings)) {
267+
throw new Error(
268+
'firebase.analytics().setConsent(*): The supplied arg must be an object of key/values.',
269+
);
270+
}
271+
272+
const entries = Object.entries(consentSettings);
273+
for (let i = 0; i < entries.length; i++) {
274+
const [key, value] = entries[i];
275+
if (!isBoolean(value)) {
276+
throw new Error(
277+
`firebase.analytics().setConsent(*) 'consentSettings' value for parameter '${key}' is invalid, expected a boolean.`,
278+
);
279+
}
280+
}
281+
282+
return this.native.setConsent(consentSettings);
283+
}
284+
264285
/** -------------------
265286
* EVENTS
266287
* -------------------- */

0 commit comments

Comments
 (0)