Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(analytics): add setConsent implementation #7629

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/analytics/usage/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,19 @@ import { firebase } from '@react-native-firebase/analytics';
await firebase.analytics().setAnalyticsCollectionEnabled(true);
```

To update user's consent (e.g. once you have the users consent), call the `setConsent` method:

```js
import { firebase } from '@react-native-firebase/analytics';
// ...
await firebase.analytics().setConsent({
analytics_storage: true,
ad_storage: true,
ad_user_data: true,
ad_personalization: true,
});
```

## Disable screenview tracking

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.
Expand Down
25 changes: 25 additions & 0 deletions packages/analytics/__tests__/analytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,31 @@ describe('Analytics', function () {
);
});

it('throws if consentSettings is not an object', function () {
mikehardy marked this conversation as resolved.
Show resolved Hide resolved
// @ts-ignore test
expect(() => firebase.analytics().setConsent(1337)).toThrowError(
'The supplied arg must be an object of key/values.',
);
});
it('throws if consentSettings is invalid', function () {
const consentSettings = {
ad_storage: true,
foo: {
bar: 'baz',
},
};
// @ts-ignore test
expect(() => firebase.analytics().setConsent(consentSettings)).toThrowError(
"'consentSettings' value for parameter 'foo' is invalid, expected a boolean.",
);
});
it('throws if one value of consentSettings is a number', function () {
// @ts-ignore test
expect(() => firebase.analytics().setConsent({ ad_storage: 123 })).toThrowError(
"'consentSettings' value for parameter 'ad_storage' is invalid, expected a boolean.",
);
});

it('errors when no parameters are set', function () {
// @ts-ignore test
expect(() => firebase.analytics().logSearch()).toThrowError(
Expand Down
27 changes: 21 additions & 6 deletions packages/analytics/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,11 @@ apply from: file('./../../app/android/firebase-json.gradle')
String collectionDeactivated = 'false'
String adidEnabled = 'true'
String ssaidEnabled = 'true'
String personalizationEnabled = 'true'
String automaticScreenReportingEnabled = 'true'
String analyticsStorageEnabled = 'true'
String adStorageEnabled = 'true'
String adUserDataEnabled = 'true'
String personalizationEnabled = 'true'

// If nothing is defined, data collection defaults to true
String dataCollectionEnabled = 'true'
Expand Down Expand Up @@ -96,12 +99,21 @@ if (rootProject.ext && rootProject.ext.firebaseJson) {
if (rnfbConfig.isFlagEnabled('google_analytics_ssaid_collection_enabled', true) == false) {
ssaidEnabled = 'false'
}
if (rnfbConfig.isFlagEnabled('analytics_default_allow_ad_personalization_signals', true) == false) {
personalizationEnabled = 'false'
}
if (rnfbConfig.isFlagEnabled('google_analytics_automatic_screen_reporting_enabled', true) == false) {
automaticScreenReportingEnabled = 'false'
}
if (rnfbConfig.isFlagEnabled('analytics_default_allow_analytics_storage', true) == false) {
analyticsStorageEnabled = 'false'
}
if (rnfbConfig.isFlagEnabled('analytics_default_allow_ad_storage', true) == false) {
adStorageEnabled = 'false'
}
if (rnfbConfig.isFlagEnabled('analytics_default_allow_ad_user_data', true) == false) {
adUserDataEnabled = 'false'
}
if (rnfbConfig.isFlagEnabled('analytics_default_allow_ad_personalization_signals', true) == false) {
personalizationEnabled = 'false'
}
}

android {
Expand All @@ -117,8 +129,11 @@ android {
firebaseJsonCollectionDeactivated: collectionDeactivated,
firebaseJsonAdidEnabled: adidEnabled,
firebaseJsonSsaidEnabled: ssaidEnabled,
firebaseJsonPersonalizationEnabled: personalizationEnabled,
firebaseJsonAutomaticScreenReportingEnabled: automaticScreenReportingEnabled
firebaseJsonAutomaticScreenReportingEnabled: automaticScreenReportingEnabled,
firebaseJsonAnalyticsStorageEnabled: analyticsStorageEnabled,
firebaseJsonAdStorageEnabled: adStorageEnabled,
firebaseJsonAdUserDataEnabled: adUserDataEnabled,
firebaseJsonPersonalizationEnabled: personalizationEnabled
]
}

Expand Down
6 changes: 5 additions & 1 deletion packages/analytics/android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@
<meta-data android:name="firebase_analytics_collection_deactivated" android:value="${firebaseJsonCollectionDeactivated}" />
<meta-data android:name="google_analytics_adid_collection_enabled" android:value="${firebaseJsonAdidEnabled}" />
<meta-data android:name="google_analytics_ssaid_collection_enabled" android:value="${firebaseJsonSsaidEnabled}" />
<meta-data android:name="google_analytics_default_allow_ad_personalization_signals" android:value="${firebaseJsonPersonalizationEnabled}" />
<meta-data android:name="google_analytics_automatic_screen_reporting_enabled" android:value="${firebaseJsonAutomaticScreenReportingEnabled}" />
<meta-data android:name="google_analytics_default_allow_analytics_storage" android:value="${firebaseJsonAnalyticsStorageEnabled}" />
<meta-data android:name="google_analytics_default_allow_ad_storage" android:value="${firebaseJsonAdStorageEnabled}" />
<meta-data android:name="google_analytics_default_allow_ad_user_data" android:value="${firebaseJsonAdUserDataEnabled}" />
<meta-data android:name="google_analytics_default_allow_ad_personalization_signals" android:value="${firebaseJsonPersonalizationEnabled}" />

</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.google.firebase.analytics.FirebaseAnalytics.ConsentStatus;
import com.google.firebase.analytics.FirebaseAnalytics.ConsentType;
import io.invertase.firebase.common.UniversalFirebaseModule;
import java.util.EnumMap;
import java.util.Map;
import java.util.Set;

@SuppressWarnings("WeakerAccess")
Expand Down Expand Up @@ -109,4 +113,29 @@
return null;
});
}

Task<Void> setConsent(Bundle consentSettings) {
return Tasks.call(

Check warning on line 118 in packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L118 was not covered by tests
() -> {
boolean analyticsStorage = consentSettings.getBoolean("analytics_storage");
boolean adStorage = consentSettings.getBoolean("ad_storage");
boolean adUserData = consentSettings.getBoolean("ad_user_data");
boolean adPersonalization = consentSettings.getBoolean("ad_personalization");

Check warning on line 123 in packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java

View check run for this annotation

Codecov / codecov/patch

packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java#L120-L123

Added lines #L120 - L123 were not covered by tests

Map<ConsentType, ConsentStatus> consentMap = new EnumMap<>(ConsentType.class);
consentMap.put(

Check warning on line 126 in packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java

View check run for this annotation

Codecov / codecov/patch

packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java#L125-L126

Added lines #L125 - L126 were not covered by tests
ConsentType.ANALYTICS_STORAGE,
analyticsStorage ? ConsentStatus.GRANTED : ConsentStatus.DENIED);
consentMap.put(

Check warning on line 129 in packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L129 was not covered by tests
ConsentType.AD_STORAGE, adStorage ? ConsentStatus.GRANTED : ConsentStatus.DENIED);
consentMap.put(

Check warning on line 131 in packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L131 was not covered by tests
ConsentType.AD_USER_DATA, adUserData ? ConsentStatus.GRANTED : ConsentStatus.DENIED);
consentMap.put(

Check warning on line 133 in packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L133 was not covered by tests
ConsentType.AD_PERSONALIZATION,
adPersonalization ? ConsentStatus.GRANTED : ConsentStatus.DENIED);

FirebaseAnalytics.getInstance(getContext()).setConsent(consentMap);
return null;

Check warning on line 138 in packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java

View check run for this annotation

Codecov / codecov/patch

packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java#L137-L138

Added lines #L137 - L138 were not covered by tests
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,20 @@
});
}

@ReactMethod
public void setConsent(ReadableMap consentSettings, Promise promise) {
module
.setConsent(Arguments.toBundle(consentSettings))
.addOnCompleteListener(

Check warning on line 185 in packages/analytics/android/src/reactnative/java/io/invertase/firebase/analytics/ReactNativeFirebaseAnalyticsModule.java

View check run for this annotation

Codecov / codecov/patch

packages/analytics/android/src/reactnative/java/io/invertase/firebase/analytics/ReactNativeFirebaseAnalyticsModule.java#L183-L185

Added lines #L183 - L185 were not covered by tests
task -> {
if (task.isSuccessful()) {
promise.resolve(task.getResult());

Check warning on line 188 in packages/analytics/android/src/reactnative/java/io/invertase/firebase/analytics/ReactNativeFirebaseAnalyticsModule.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L188 was not covered by tests
} else {
rejectPromiseWithExceptionMap(promise, task.getException());

Check warning on line 190 in packages/analytics/android/src/reactnative/java/io/invertase/firebase/analytics/ReactNativeFirebaseAnalyticsModule.java

View check run for this annotation

Codecov / codecov/patch

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

Added line #L190 was not covered by tests
}
});
}

Check warning on line 193 in packages/analytics/android/src/reactnative/java/io/invertase/firebase/analytics/ReactNativeFirebaseAnalyticsModule.java

View check run for this annotation

Codecov / codecov/patch

packages/analytics/android/src/reactnative/java/io/invertase/firebase/analytics/ReactNativeFirebaseAnalyticsModule.java#L192-L193

Added lines #L192 - L193 were not covered by tests

private Bundle toBundle(ReadableMap readableMap) {
Bundle bundle = Arguments.toBundle(readableMap);
if (bundle == null) {
Expand Down
34 changes: 34 additions & 0 deletions packages/analytics/e2e/analytics.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,22 @@ describe('analytics() modular', function () {
});
});

describe('setConsent()', function () {
it('set ad_storage=true on consentSettings', async function () {
const consentSettings = {
ad_storage: true,
};
await firebase.analytics().setConsent(consentSettings);
});
it('set ad_storage=false and analytics_storage=true on consentSettings', async function () {
const consentSettings = {
ad_storage: false,
analytics_storage: true,
};
await firebase.analytics().setConsent(consentSettings);
});
});

// Test this one near end so all the previous hits are visible in DebugView is that is enabled
describe('resetAnalyticsData()', function () {
it('calls native fn without error', async function () {
Expand Down Expand Up @@ -1068,5 +1084,23 @@ describe('analytics() modular', function () {
await setAnalyticsCollectionEnabled(getAnalytics(), true);
});
});

describe('setConsent()', function () {
it('set ad_storage=true on consentSettings', async function () {
const consentSettings = {
ad_storage: true,
};
const { getAnalytics, setConsent } = analyticsModular;
await setConsent(getAnalytics(), consentSettings);
});
it('set ad_storage=false and analytics_storage=true on consentSettings', async function () {
const consentSettings = {
ad_storage: false,
analytics_storage: true,
};
const { getAnalytics, setConsent } = analyticsModular;
await setConsent(getAnalytics(), consentSettings);
});
});
});
});
23 changes: 23 additions & 0 deletions packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,29 @@ - (dispatch_queue_t)methodQueue {
return resolve([NSNull null]);
}

RCT_EXPORT_METHOD(setConsent
: (NSDictionary *)consentSettings resolver
: (RCTPromiseResolveBlock)resolve rejecter
: (RCTPromiseRejectBlock)reject) {
@try {
BOOL analyticsStorage = [consentSettings[@"analytics_storage"] boolValue];
BOOL adStorage = [consentSettings[@"ad_storage"] boolValue];
BOOL adUserData = [consentSettings[@"ad_user_data"] boolValue];
BOOL adPersonalization = [consentSettings[@"ad_personalization"] boolValue];
[FIRAnalytics setConsent:@{
FIRConsentTypeAnalyticsStorage : analyticsStorage ? FIRConsentStatusGranted
: FIRConsentStatusDenied,
FIRConsentTypeAdStorage : adStorage ? FIRConsentStatusGranted : FIRConsentStatusDenied,
FIRConsentTypeAdUserData : adUserData ? FIRConsentStatusGranted : FIRConsentStatusDenied,
FIRConsentTypeAdPersonalization : adPersonalization ? FIRConsentStatusGranted
: FIRConsentStatusDenied,
}];
} @catch (NSException *exception) {
return [RNFBSharedUtils rejectPromiseWithExceptionDict:reject exception:exception];
}
return resolve([NSNull null]);
}

#pragma mark -
#pragma mark Private methods

Expand Down
34 changes: 30 additions & 4 deletions packages/analytics/lib/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -774,15 +774,19 @@ export namespace FirebaseAnalyticsTypes {
*/
export interface ConsentSettings {
/** Enables storage, such as cookies, related to advertising */
ad_storage?: ConsentStatusString;
ad_storage?: boolean;
/** Sets consent for sending user data to Google for online advertising purposes */
ad_user_data?: boolean;
/** Sets consent for personalized advertising */
ad_personalization?: boolean;
/** Enables storage, such as cookies, related to analytics (for example, visit duration) */
analytics_storage?: ConsentStatusString;
analytics_storage?: boolean;
/**
* Enables storage that supports the functionality of the website or app such as language settings
*/
functionality_storage?: ConsentStatusString;
functionality_storage?: boolean;
/** Enables storage related to personalization such as video recommendations */
personalization_storage?: ConsentStatusString;
personalization_storage?: boolean;
/**
* Enables storage related to security such as authentication functionality, fraud prevention,
* and other user protection.
Expand Down Expand Up @@ -1727,6 +1731,28 @@ export namespace FirebaseAnalyticsTypes {
* @param phoneNumber phone number in E.164 format - that is a leading + sign, then up to 15 digits, no dashes or spaces.
*/
initiateOnDeviceConversionMeasurementWithPhoneNumber(phoneNumber: string): Promise<void>;

/**
* For Consent Mode!
*
* #### Example
*
* ```js
* // Disable consent
* await firebase.analytics().setConsent({
* ad_personalization: false,
* analytics_storage: false,
* ad_storage: false,
* ad_user_data: false,
* });
* ```
*
* Sets the applicable end user consent state (e.g., for device identifiers) for this app on this device.
* Use the consent map to specify individual consent type values.
* Settings are persisted across app sessions.
* @param consentSettings Consent status settings for each consent type.
*/
setConsent(consentSettings: ConsentSettings): Promise<void>;
}

/**
Expand Down
21 changes: 21 additions & 0 deletions packages/analytics/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export {
setDefaultEventParameters,
initiateOnDeviceConversionMeasurementWithEmailAddress,
initiateOnDeviceConversionMeasurementWithPhoneNumber,
setConsent,
} from './modular/index';

const ReservedEventNames = [
Expand Down Expand Up @@ -261,6 +262,26 @@ class FirebaseAnalyticsModule extends FirebaseModule {
return this.native.resetAnalyticsData();
}

setConsent(consentSettings) {
if (!isObject(consentSettings)) {
throw new Error(
'firebase.analytics().setConsent(*): The supplied arg must be an object of key/values.',
);
}

const entries = Object.entries(consentSettings);
for (let i = 0; i < entries.length; i++) {
const [key, value] = entries[i];
if (!isBoolean(value)) {
throw new Error(
`firebase.analytics().setConsent(*) 'consentSettings' value for parameter '${key}' is invalid, expected a boolean.`,
);
}
}

return this.native.setConsent(consentSettings);
}

/** -------------------
* EVENTS
* -------------------- */
Expand Down
8 changes: 4 additions & 4 deletions packages/analytics/lib/modular/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1189,16 +1189,16 @@ export function initiateOnDeviceConversionMeasurementWithPhoneNumber(
export function isSupported(): Promise<boolean>;

/**
* Sets the applicable end user consent state for this web app across all gtag
* references once Firebase Analytics is initialized. Web only.
* Sets the applicable end user consent state for this app.
* references once Firebase Analytics is initialized.
* @param analytics Analytics instance.
* @param consentSettings See {@link analytics.ConsentSettings}.
* @returns {void}
* @returns {Promise<void>}
*/
export function setConsent(
analytics: Analytics,
consentSettings: FirebaseAnalyticsTypes.ConsentSettings,
): void;
): Promise<void>;

/**
* Configures Firebase Analytics to use custom gtag or dataLayer names.
Expand Down
10 changes: 5 additions & 5 deletions packages/analytics/lib/modular/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -648,15 +648,15 @@
}

/**
* Sets the applicable end user consent state for this web app across all gtag
* references once Firebase Analytics is initialized. Web only.
* Sets the applicable end user consent state for this app.
* references once Firebase Analytics is initialized.
* @param analytics Analytics instance.
* @param consentSettings See {@link analytics.ConsentSettings}.
* @returns {void}
* @returns {Promise<void>}
*/
// eslint-disable-next-line
export function setConsent(consentSettings) {
// Returns nothing until Web implemented.
export function setConsent(analytics, consentSettings) {

Check warning on line 658 in packages/analytics/lib/modular/index.js

View check run for this annotation

Codecov / codecov/patch

packages/analytics/lib/modular/index.js#L658

Added line #L658 was not covered by tests
return analytics.setConsent(consentSettings);
}

/**
Expand Down
Loading
Loading