From 9f646ae6223ca4151fdb4febf9400cb5d7809933 Mon Sep 17 00:00:00 2001 From: Marian Iordache Date: Fri, 16 Feb 2024 11:10:27 +0200 Subject: [PATCH 1/3] feat(analytics): add setConsent implementation --- docs/analytics/usage/index.md | 13 +++++++ .../analytics/__tests__/analytics.test.ts | 25 ++++++++++++++ packages/analytics/android/build.gradle | 20 ++++++++--- .../android/src/main/AndroidManifest.xml | 6 +++- .../UniversalFirebaseAnalyticsModule.java | 27 +++++++++++++++ .../ReactNativeFirebaseAnalyticsModule.java | 14 ++++++++ .../ios/RNFBAnalytics/RNFBAnalyticsModule.m | 21 ++++++++++++ packages/analytics/lib/index.d.ts | 34 ++++++++++++++++--- packages/analytics/lib/index.js | 21 ++++++++++++ packages/analytics/lib/modular/index.d.ts | 6 ++-- packages/analytics/lib/modular/index.js | 4 +-- packages/analytics/type-test.ts | 5 +++ packages/app/firebase-schema.json | 12 +++++++ packages/app/ios_config.sh | 24 +++++++++++++ tests/firebase.json | 3 ++ tests/ios/testing.xcodeproj/project.pbxproj | 2 +- 16 files changed, 222 insertions(+), 15 deletions(-) diff --git a/docs/analytics/usage/index.md b/docs/analytics/usage/index.md index 008a227b78..3b500ed456 100644 --- a/docs/analytics/usage/index.md +++ b/docs/analytics/usage/index.md @@ -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. diff --git a/packages/analytics/__tests__/analytics.test.ts b/packages/analytics/__tests__/analytics.test.ts index dabc0e263f..c6ff8f1259 100644 --- a/packages/analytics/__tests__/analytics.test.ts +++ b/packages/analytics/__tests__/analytics.test.ts @@ -98,6 +98,31 @@ describe('Analytics', function () { ); }); + it('throws if consentSettings is not an object', function () { + // @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( diff --git a/packages/analytics/android/build.gradle b/packages/analytics/android/build.gradle index 34f07a6ca8..f3e2f4dcfd 100644 --- a/packages/analytics/android/build.gradle +++ b/packages/analytics/android/build.gradle @@ -96,12 +96,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 { @@ -117,8 +126,11 @@ android { firebaseJsonCollectionDeactivated: collectionDeactivated, firebaseJsonAdidEnabled: adidEnabled, firebaseJsonSsaidEnabled: ssaidEnabled, + firebaseJsonAutomaticScreenReportingEnabled: automaticScreenReportingEnabled, + firebaseJsonAnalyticsStorageEnabled: analyticsStorageEnabled, + firebaseJsonAdStorageEnabled: adStorageEnabled, + firebaseJsonAdUserDataEnabled: adUserDataEnabled, firebaseJsonPersonalizationEnabled: personalizationEnabled, - firebaseJsonAutomaticScreenReportingEnabled: automaticScreenReportingEnabled ] } diff --git a/packages/analytics/android/src/main/AndroidManifest.xml b/packages/analytics/android/src/main/AndroidManifest.xml index de844a870e..d3c21c6bb6 100644 --- a/packages/analytics/android/src/main/AndroidManifest.xml +++ b/packages/analytics/android/src/main/AndroidManifest.xml @@ -11,7 +11,11 @@ - + + + + + diff --git a/packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java b/packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java index 424497e4ce..f31e349f5f 100644 --- a/packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java +++ b/packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java @@ -22,8 +22,12 @@ 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.Set; +import java.util.EnumMap; +import java.util.Map; @SuppressWarnings("WeakerAccess") public class UniversalFirebaseAnalyticsModule extends UniversalFirebaseModule { @@ -109,4 +113,27 @@ Task setDefaultEventParameters(Bundle parameters) { return null; }); } + + Task setConsent(Bundle consentSettings) { + return Tasks.call( + () -> { + 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"); + + Map consentMap = new EnumMap<>(ConsentType.class); + consentMap.put(ConsentType.ANALYTICS_STORAGE, + analyticsStorage ? ConsentStatus.GRANTED : ConsentStatus.DENIED); + consentMap.put(ConsentType.AD_STORAGE, + adStorage ? ConsentStatus.GRANTED : ConsentStatus.DENIED); + consentMap.put(ConsentType.AD_USER_DATA, + adUserData ? ConsentStatus.GRANTED : ConsentStatus.DENIED); + consentMap.put(ConsentType.AD_PERSONALIZATION, + adPersonalization ? ConsentStatus.GRANTED : ConsentStatus.DENIED); + + FirebaseAnalytics.getInstance(getContext()).setConsent(consentMap); + return null; + }); + } } diff --git a/packages/analytics/android/src/reactnative/java/io/invertase/firebase/analytics/ReactNativeFirebaseAnalyticsModule.java b/packages/analytics/android/src/reactnative/java/io/invertase/firebase/analytics/ReactNativeFirebaseAnalyticsModule.java index f82f3972f3..8e9397a237 100644 --- a/packages/analytics/android/src/reactnative/java/io/invertase/firebase/analytics/ReactNativeFirebaseAnalyticsModule.java +++ b/packages/analytics/android/src/reactnative/java/io/invertase/firebase/analytics/ReactNativeFirebaseAnalyticsModule.java @@ -178,6 +178,20 @@ public void setDefaultEventParameters(@Nullable ReadableMap params, Promise prom }); } + @ReactMethod + public void setConsent(ReadableMap consentSettings, Promise promise) { + module + .setConsent(Arguments.toBundle(consentSettings)) + .addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + promise.resolve(task.getResult()); + } else { + rejectPromiseWithExceptionMap(promise, task.getException()); + } + }); + } + private Bundle toBundle(ReadableMap readableMap) { Bundle bundle = Arguments.toBundle(readableMap); if (bundle == null) { diff --git a/packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m b/packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m index 4b2e8af126..c4ec6f9e81 100644 --- a/packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m +++ b/packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m @@ -177,6 +177,27 @@ - (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 diff --git a/packages/analytics/lib/index.d.ts b/packages/analytics/lib/index.d.ts index 31d806284f..84f015aed1 100644 --- a/packages/analytics/lib/index.d.ts +++ b/packages/analytics/lib/index.d.ts @@ -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. @@ -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; + + /** + * 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; } /** diff --git a/packages/analytics/lib/index.js b/packages/analytics/lib/index.js index 99af9ce990..e35ce907f1 100644 --- a/packages/analytics/lib/index.js +++ b/packages/analytics/lib/index.js @@ -87,6 +87,7 @@ export { setDefaultEventParameters, initiateOnDeviceConversionMeasurementWithEmailAddress, initiateOnDeviceConversionMeasurementWithPhoneNumber, + setConsent, } from './modular/index'; const ReservedEventNames = [ @@ -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 * -------------------- */ diff --git a/packages/analytics/lib/modular/index.d.ts b/packages/analytics/lib/modular/index.d.ts index 7664e0a84d..f2f5850269 100644 --- a/packages/analytics/lib/modular/index.d.ts +++ b/packages/analytics/lib/modular/index.d.ts @@ -1189,8 +1189,8 @@ export function initiateOnDeviceConversionMeasurementWithPhoneNumber( export function isSupported(): Promise; /** - * 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} @@ -1198,7 +1198,7 @@ export function isSupported(): Promise; export function setConsent( analytics: Analytics, consentSettings: FirebaseAnalyticsTypes.ConsentSettings, -): void; +): Promise; /** * Configures Firebase Analytics to use custom gtag or dataLayer names. diff --git a/packages/analytics/lib/modular/index.js b/packages/analytics/lib/modular/index.js index 32fc565702..3459572482 100644 --- a/packages/analytics/lib/modular/index.js +++ b/packages/analytics/lib/modular/index.js @@ -652,11 +652,11 @@ export function isSupported() { * references once Firebase Analytics is initialized. Web only. * @param analytics Analytics instance. * @param consentSettings See {@link analytics.ConsentSettings}. - * @returns {void} + * @returns {Promise} */ // eslint-disable-next-line export function setConsent(consentSettings) { - // Returns nothing until Web implemented. + return analytics.setConsent(consentSettings); } /** diff --git a/packages/analytics/type-test.ts b/packages/analytics/type-test.ts index 042f5867e0..b391d1e8b2 100644 --- a/packages/analytics/type-test.ts +++ b/packages/analytics/type-test.ts @@ -30,6 +30,9 @@ firebase .analytics() .setUserProperties({ foo: 'bar' }) .then(); +firebase + .analytics() + .setConsent({ ad_storage: true }) console.log(firebase.analytics().logAddPaymentInfo); console.log(firebase.analytics().logAddToCart); @@ -72,6 +75,7 @@ console.log(firebase.analytics().setUserId); console.log(firebase.analytics().setUserProperties); console.log(firebase.analytics().logViewSearchResults); console.log(firebase.analytics().setUserProperty); +console.log(firebase.analytics().setConsent); console.log(analytics().logAddPaymentInfo); console.log(analytics().logAddToCart); @@ -114,3 +118,4 @@ console.log(analytics().setUserId); console.log(analytics().setUserProperties); console.log(analytics().logViewSearchResults); console.log(analytics().setUserProperty); +console.log(analytics().setConsent); diff --git a/packages/app/firebase-schema.json b/packages/app/firebase-schema.json index b4791acc15..d5e7af3ab4 100644 --- a/packages/app/firebase-schema.json +++ b/packages/app/firebase-schema.json @@ -33,6 +33,18 @@ "description": "For your convenience, on iOS the SDK automatically registers your app with Apple for ad network attribution with SKAdNetwork.\nDefaults to true, include this key as false to disable.", "type": "boolean" }, + "analytics_default_allow_analytics_storage": { + "description": "Enables storage (such as app identifiers) related to analytics, e.g. visit duration.", + "type": "boolean" + }, + "analytics_default_allow_ad_storage": { + "description": "Enables storage (such as device identifiers) related to advertising.", + "type": "boolean" + }, + "analytics_default_allow_ad_user_data": { + "description": "Sets consent for sending user data to Google for advertising purposes.", + "type": "boolean" + }, "analytics_default_allow_ad_personalization_signals": { "description": "Configure whether a user's Analytics data may be used for personalized advertising in other products.\n If set, may be overridden at runtime by calling setUserProperty on the key 'allow_personalized_ads'", "type": "boolean" diff --git a/packages/app/ios_config.sh b/packages/app/ios_config.sh index 13fbec7d9f..d86c33f1cf 100755 --- a/packages/app/ios_config.sh +++ b/packages/app/ios_config.sh @@ -119,6 +119,30 @@ if [[ ${_SEARCH_RESULT} ]]; then _PLIST_ENTRY_VALUES+=("$(jsonBoolToYesNo "$_ANALYTICS_IDFV_COLLECTION")") fi + # config.analytics_default_allow_analytics_storage + _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue "$_JSON_OUTPUT_RAW" "analytics_default_allow_analytics_storage") + if [[ $_ANALYTICS_STORAGE ]]; then + _PLIST_ENTRY_KEYS+=("GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE") + _PLIST_ENTRY_TYPES+=("bool") + _PLIST_ENTRY_VALUES+=("$(jsonBoolToYesNo "$_ANALYTICS_STORAGE")") + fi + + # config.analytics_default_allow_ad_storage + _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue "$_JSON_OUTPUT_RAW" "analytics_default_allow_ad_storage") + if [[ $_ANALYTICS_AD_STORAGE ]]; then + _PLIST_ENTRY_KEYS+=("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE") + _PLIST_ENTRY_TYPES+=("bool") + _PLIST_ENTRY_VALUES+=("$(jsonBoolToYesNo "$_ANALYTICS_AD_STORAGE")") + fi + + # config.analytics_default_allow_ad_user_data + _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue "$_JSON_OUTPUT_RAW" "analytics_default_allow_ad_user_data") + if [[ $_ANALYTICS_AD_USER_DATA ]]; then + _PLIST_ENTRY_KEYS+=("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA") + _PLIST_ENTRY_TYPES+=("bool") + _PLIST_ENTRY_VALUES+=("$(jsonBoolToYesNo "$_ANALYTICS_AD_USER_DATA")") + fi + # config.analytics_default_allow_ad_personalization_signals _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue "$_JSON_OUTPUT_RAW" "analytics_default_allow_ad_personalization_signals") if [[ $_ANALYTICS_PERSONALIZATION ]]; then diff --git a/tests/firebase.json b/tests/firebase.json index ad4309d086..6043d1ee1a 100644 --- a/tests/firebase.json +++ b/tests/firebase.json @@ -27,6 +27,9 @@ "google_analytics_ssaid_collection_enabled": true, "google_analytics_automatic_screen_reporting_enabled": true, "google_analytics_registration_with_ad_network_enabled": true, + "analytics_default_allow_analytics_storage": true, + "analytics_default_allow_ad_storage": true, + "analytics_default_allow_ad_user_data": true, "analytics_default_allow_ad_personalization_signals": true, "perf_auto_collection_enabled": false, diff --git a/tests/ios/testing.xcodeproj/project.pbxproj b/tests/ios/testing.xcodeproj/project.pbxproj index 7e25ee321a..4fe6b6f608 100644 --- a/tests/ios/testing.xcodeproj/project.pbxproj +++ b/tests/ios/testing.xcodeproj/project.pbxproj @@ -354,7 +354,7 @@ name = "[CP-User] [RNFB] Core Configuration"; runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; + shellScript = "#!/usr/bin/env bash\n#\n# Copyright (c) 2016-present Invertase Limited & Contributors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this library except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\nset -e\n\n_MAX_LOOKUPS=2;\n_SEARCH_RESULT=''\n_RN_ROOT_EXISTS=''\n_CURRENT_LOOKUPS=1\n_JSON_ROOT=\"'react-native'\"\n_JSON_FILE_NAME='firebase.json'\n_JSON_OUTPUT_BASE64='e30=' # { }\n_CURRENT_SEARCH_DIR=${PROJECT_DIR}\n_PLIST_BUDDY=/usr/libexec/PlistBuddy\n_TARGET_PLIST=\"${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}\"\n_DSYM_PLIST=\"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist\"\n\n# plist arrays\n_PLIST_ENTRY_KEYS=()\n_PLIST_ENTRY_TYPES=()\n_PLIST_ENTRY_VALUES=()\n\nfunction setPlistValue {\n echo \"info: setting plist entry '$1' of type '$2' in file '$4'\"\n ${_PLIST_BUDDY} -c \"Add :$1 $2 '$3'\" $4 || echo \"info: '$1' already exists\"\n}\n\nfunction getFirebaseJsonKeyValue () {\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$1'); puts output[$_JSON_ROOT]['$2']\"\n else\n echo \"\"\n fi;\n}\n\nfunction jsonBoolToYesNo () {\n if [[ $1 == \"false\" ]]; then\n echo \"NO\"\n elif [[ $1 == \"true\" ]]; then\n echo \"YES\"\n else echo \"NO\"\n fi\n}\n\necho \"info: -> RNFB build script started\"\necho \"info: 1) Locating ${_JSON_FILE_NAME} file:\"\n\nif [[ -z ${_CURRENT_SEARCH_DIR} ]]; then\n _CURRENT_SEARCH_DIR=$(pwd)\nfi;\n\nwhile true; do\n _CURRENT_SEARCH_DIR=$(dirname \"$_CURRENT_SEARCH_DIR\")\n if [[ \"$_CURRENT_SEARCH_DIR\" == \"/\" ]] || [[ ${_CURRENT_LOOKUPS} -gt ${_MAX_LOOKUPS} ]]; then break; fi;\n echo \"info: ($_CURRENT_LOOKUPS of $_MAX_LOOKUPS) Searching in '$_CURRENT_SEARCH_DIR' for a ${_JSON_FILE_NAME} file.\"\n _SEARCH_RESULT=$(find \"$_CURRENT_SEARCH_DIR\" -maxdepth 2 -name ${_JSON_FILE_NAME} -print | /usr/bin/head -n 1)\n if [[ ${_SEARCH_RESULT} ]]; then\n echo \"info: ${_JSON_FILE_NAME} found at $_SEARCH_RESULT\"\n break;\n fi;\n _CURRENT_LOOKUPS=$((_CURRENT_LOOKUPS+1))\ndone\n\nif [[ ${_SEARCH_RESULT} ]]; then\n _JSON_OUTPUT_RAW=$(cat \"${_SEARCH_RESULT}\")\n _RN_ROOT_EXISTS=$(ruby -Ku -e \"require 'rubygems';require 'json'; output=JSON.parse('$_JSON_OUTPUT_RAW'); puts output[$_JSON_ROOT]\" || echo '')\n\n if [[ ${_RN_ROOT_EXISTS} ]]; then\n if ! python3 --version >/dev/null 2>&1; then echo \"python3 not found, firebase.json file processing error.\" && exit 1; fi\n _JSON_OUTPUT_BASE64=$(python3 -c 'import json,sys,base64;print(base64.b64encode(bytes(json.dumps(json.loads(open('\"'${_SEARCH_RESULT}'\"', '\"'rb'\"').read())['${_JSON_ROOT}']), '\"'utf-8'\"')).decode())' || echo \"e30=\")\n fi\n\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n\n # config.app_data_collection_default_enabled\n _APP_DATA_COLLECTION_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_data_collection_default_enabled\")\n if [[ $_APP_DATA_COLLECTION_ENABLED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseDataCollectionDefaultEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_DATA_COLLECTION_ENABLED\")\")\n fi\n\n # config.analytics_auto_collection_enabled\n _ANALYTICS_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_auto_collection_enabled\")\n if [[ $_ANALYTICS_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_COLLECTION\")\")\n fi\n\n # config.analytics_collection_deactivated\n _ANALYTICS_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_collection_deactivated\")\n if [[ $_ANALYTICS_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"FIREBASE_ANALYTICS_COLLECTION_DEACTIVATED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_DEACTIVATED\")\")\n fi\n\n # config.analytics_idfv_collection_enabled\n _ANALYTICS_IDFV_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_idfv_collection_enabled\")\n if [[ $_ANALYTICS_IDFV_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_IDFV_COLLECTION_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_IDFV_COLLECTION\")\")\n fi\n\n # config.analytics_default_allow_analytics_storage\n _ANALYTICS_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_analytics_storage\")\n if [[ $_ANALYTICS_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_storage\n _ANALYTICS_AD_STORAGE=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_storage\")\n if [[ $_ANALYTICS_AD_STORAGE ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_STORAGE\")\")\n fi\n\n # config.analytics_default_allow_ad_user_data\n _ANALYTICS_AD_USER_DATA=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_user_data\")\n if [[ $_ANALYTICS_AD_USER_DATA ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AD_USER_DATA\")\")\n fi\n\n # config.analytics_default_allow_ad_personalization_signals\n _ANALYTICS_PERSONALIZATION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"analytics_default_allow_ad_personalization_signals\")\n if [[ $_ANALYTICS_PERSONALIZATION ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_PERSONALIZATION\")\")\n fi\n\n # config.analytics_registration_with_ad_network_enabled\n _ANALYTICS_REGISTRATION_WITH_AD_NETWORK=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_registration_with_ad_network_enabled\")\n if [[ $_ANALYTICS_REGISTRATION_WITH_AD_NETWORK ]]; then\n _PLIST_ENTRY_KEYS+=(\"GOOGLE_ANALYTICS_REGISTRATION_WITH_AD_NETWORK_ENABLED\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_REGISTRATION_WITH_AD_NETWORK\")\")\n fi\n\n # config.google_analytics_automatic_screen_reporting_enabled\n _ANALYTICS_AUTO_SCREEN_REPORTING=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"google_analytics_automatic_screen_reporting_enabled\")\n if [[ $_ANALYTICS_AUTO_SCREEN_REPORTING ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAutomaticScreenReportingEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_ANALYTICS_AUTO_SCREEN_REPORTING\")\")\n fi\n\n # config.perf_auto_collection_enabled\n _PERF_AUTO_COLLECTION=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_auto_collection_enabled\")\n if [[ $_PERF_AUTO_COLLECTION ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_enabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_AUTO_COLLECTION\")\")\n fi\n\n # config.perf_collection_deactivated\n _PERF_DEACTIVATED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"perf_collection_deactivated\")\n if [[ $_PERF_DEACTIVATED ]]; then\n _PLIST_ENTRY_KEYS+=(\"firebase_performance_collection_deactivated\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_PERF_DEACTIVATED\")\")\n fi\n\n # config.messaging_auto_init_enabled\n _MESSAGING_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"messaging_auto_init_enabled\")\n if [[ $_MESSAGING_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseMessagingAutoInitEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_MESSAGING_AUTO_INIT\")\")\n fi\n\n # config.in_app_messaging_auto_colllection_enabled\n _FIAM_AUTO_INIT=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"in_app_messaging_auto_collection_enabled\")\n if [[ $_FIAM_AUTO_INIT ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseInAppMessagingAutomaticDataCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_FIAM_AUTO_INIT\")\")\n fi\n\n # config.app_check_token_auto_refresh\n _APP_CHECK_TOKEN_AUTO_REFRESH=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"app_check_token_auto_refresh\")\n if [[ $_APP_CHECK_TOKEN_AUTO_REFRESH ]]; then\n _PLIST_ENTRY_KEYS+=(\"FirebaseAppCheckTokenAutoRefreshEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"$(jsonBoolToYesNo \"$_APP_CHECK_TOKEN_AUTO_REFRESH\")\")\n fi\n\n # config.crashlytics_disable_auto_disabler - undocumented for now - mainly for debugging, document if becomes useful\n _CRASHLYTICS_AUTO_DISABLE_ENABLED=$(getFirebaseJsonKeyValue \"$_JSON_OUTPUT_RAW\" \"crashlytics_disable_auto_disabler\")\n if [[ $_CRASHLYTICS_AUTO_DISABLE_ENABLED == \"true\" ]]; then\n echo \"Disabled Crashlytics auto disabler.\" # do nothing\n else\n _PLIST_ENTRY_KEYS+=(\"FirebaseCrashlyticsCollectionEnabled\")\n _PLIST_ENTRY_TYPES+=(\"bool\")\n _PLIST_ENTRY_VALUES+=(\"NO\")\n fi\nelse\n _PLIST_ENTRY_KEYS+=(\"firebase_json_raw\")\n _PLIST_ENTRY_TYPES+=(\"string\")\n _PLIST_ENTRY_VALUES+=(\"$_JSON_OUTPUT_BASE64\")\n echo \"warning: A firebase.json file was not found, whilst this file is optional it is recommended to include it to configure firebase services in React Native Firebase.\"\nfi;\n\necho \"info: 2) Injecting Info.plist entries: \"\n\n# Log out the keys we're adding\nfor i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n echo \" -> $i) ${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\"\ndone\n\nfor plist in \"${_TARGET_PLIST}\" \"${_DSYM_PLIST}\" ; do\n if [[ -f \"${plist}\" ]]; then\n\n # paths with spaces break the call to setPlistValue. temporarily modify\n # the shell internal field separator variable (IFS), which normally\n # includes spaces, to consist only of line breaks\n oldifs=$IFS\n IFS=\"\n\"\n\n for i in \"${!_PLIST_ENTRY_KEYS[@]}\"; do\n setPlistValue \"${_PLIST_ENTRY_KEYS[$i]}\" \"${_PLIST_ENTRY_TYPES[$i]}\" \"${_PLIST_ENTRY_VALUES[$i]}\" \"${plist}\"\n done\n\n # restore the original internal field separator value\n IFS=$oldifs\n else\n echo \"warning: A Info.plist build output file was not found (${plist})\"\n fi\ndone\n\necho \"info: <- RNFB build script finished\"\n"; }; /* End PBXShellScriptBuildPhase section */ From 117ade3e02651dbf37d2293eeb9e859158e22eec Mon Sep 17 00:00:00 2001 From: Marian Iordache Date: Mon, 19 Feb 2024 12:31:04 +0200 Subject: [PATCH 2/3] feat(analytics): add e2e tests and lint files --- docs/analytics/usage/index.md | 2 +- .../UniversalFirebaseAnalyticsModule.java | 42 ++++++++++--------- packages/analytics/e2e/analytics.e2e.js | 34 +++++++++++++++ .../ios/RNFBAnalytics/RNFBAnalyticsModule.m | 12 +++--- packages/analytics/lib/modular/index.d.ts | 2 +- packages/analytics/lib/modular/index.js | 6 +-- 6 files changed, 68 insertions(+), 30 deletions(-) diff --git a/docs/analytics/usage/index.md b/docs/analytics/usage/index.md index 3b500ed456..e8a9638db6 100644 --- a/docs/analytics/usage/index.md +++ b/docs/analytics/usage/index.md @@ -191,7 +191,7 @@ await firebase.analytics().setConsent({ analytics_storage: true, ad_storage: true, ad_user_data: true, - ad_personalization: true + ad_personalization: true, }); ``` diff --git a/packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java b/packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java index f31e349f5f..c6bf0cd016 100644 --- a/packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java +++ b/packages/analytics/android/src/main/java/io/invertase/firebase/analytics/UniversalFirebaseAnalyticsModule.java @@ -25,9 +25,9 @@ import com.google.firebase.analytics.FirebaseAnalytics.ConsentStatus; import com.google.firebase.analytics.FirebaseAnalytics.ConsentType; import io.invertase.firebase.common.UniversalFirebaseModule; -import java.util.Set; import java.util.EnumMap; import java.util.Map; +import java.util.Set; @SuppressWarnings("WeakerAccess") public class UniversalFirebaseAnalyticsModule extends UniversalFirebaseModule { @@ -116,24 +116,26 @@ Task setDefaultEventParameters(Bundle parameters) { Task setConsent(Bundle consentSettings) { return Tasks.call( - () -> { - 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"); - - Map consentMap = new EnumMap<>(ConsentType.class); - consentMap.put(ConsentType.ANALYTICS_STORAGE, - analyticsStorage ? ConsentStatus.GRANTED : ConsentStatus.DENIED); - consentMap.put(ConsentType.AD_STORAGE, - adStorage ? ConsentStatus.GRANTED : ConsentStatus.DENIED); - consentMap.put(ConsentType.AD_USER_DATA, - adUserData ? ConsentStatus.GRANTED : ConsentStatus.DENIED); - consentMap.put(ConsentType.AD_PERSONALIZATION, - adPersonalization ? ConsentStatus.GRANTED : ConsentStatus.DENIED); - - FirebaseAnalytics.getInstance(getContext()).setConsent(consentMap); - return null; - }); + () -> { + 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"); + + Map consentMap = new EnumMap<>(ConsentType.class); + consentMap.put( + ConsentType.ANALYTICS_STORAGE, + analyticsStorage ? ConsentStatus.GRANTED : ConsentStatus.DENIED); + consentMap.put( + ConsentType.AD_STORAGE, adStorage ? ConsentStatus.GRANTED : ConsentStatus.DENIED); + consentMap.put( + ConsentType.AD_USER_DATA, adUserData ? ConsentStatus.GRANTED : ConsentStatus.DENIED); + consentMap.put( + ConsentType.AD_PERSONALIZATION, + adPersonalization ? ConsentStatus.GRANTED : ConsentStatus.DENIED); + + FirebaseAnalytics.getInstance(getContext()).setConsent(consentMap); + return null; + }); } } diff --git a/packages/analytics/e2e/analytics.e2e.js b/packages/analytics/e2e/analytics.e2e.js index 0dcafdb22f..45bb109f08 100644 --- a/packages/analytics/e2e/analytics.e2e.js +++ b/packages/analytics/e2e/analytics.e2e.js @@ -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 () { @@ -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); + }); + }); }); }); diff --git a/packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m b/packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m index c4ec6f9e81..61b7dfa1b7 100644 --- a/packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m +++ b/packages/analytics/ios/RNFBAnalytics/RNFBAnalyticsModule.m @@ -187,11 +187,13 @@ - (dispatch_queue_t)methodQueue { 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, - }]; + 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]; } diff --git a/packages/analytics/lib/modular/index.d.ts b/packages/analytics/lib/modular/index.d.ts index f2f5850269..36d1d7cc08 100644 --- a/packages/analytics/lib/modular/index.d.ts +++ b/packages/analytics/lib/modular/index.d.ts @@ -1193,7 +1193,7 @@ export function isSupported(): Promise; * references once Firebase Analytics is initialized. * @param analytics Analytics instance. * @param consentSettings See {@link analytics.ConsentSettings}. - * @returns {void} + * @returns {Promise} */ export function setConsent( analytics: Analytics, diff --git a/packages/analytics/lib/modular/index.js b/packages/analytics/lib/modular/index.js index 3459572482..1b96573ff8 100644 --- a/packages/analytics/lib/modular/index.js +++ b/packages/analytics/lib/modular/index.js @@ -648,14 +648,14 @@ export function isSupported() { } /** - * 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 {Promise} */ // eslint-disable-next-line -export function setConsent(consentSettings) { +export function setConsent(analytics, consentSettings) { return analytics.setConsent(consentSettings); } From eab6574468cd373cec0a8e59dc5ffa99d3efb195 Mon Sep 17 00:00:00 2001 From: Marian Iordache Date: Mon, 19 Feb 2024 18:35:56 +0200 Subject: [PATCH 3/3] feat(analytics): define consent mode variables in .gradle --- packages/analytics/android/build.gradle | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/analytics/android/build.gradle b/packages/analytics/android/build.gradle index f3e2f4dcfd..e9edff10bb 100644 --- a/packages/analytics/android/build.gradle +++ b/packages/analytics/android/build.gradle @@ -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' @@ -130,7 +133,7 @@ android { firebaseJsonAnalyticsStorageEnabled: analyticsStorageEnabled, firebaseJsonAdStorageEnabled: adStorageEnabled, firebaseJsonAdUserDataEnabled: adUserDataEnabled, - firebaseJsonPersonalizationEnabled: personalizationEnabled, + firebaseJsonPersonalizationEnabled: personalizationEnabled ] }