diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9765c5a..cd8e16b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
The changes documented here do not include those from the original repository.
+## 5.0.0-OS15
+
+- Feat(Android & iOS): Add setConsent functionality to allow users to set consent for analytics (https://outsystemsrd.atlassian.net/browse/RMET-3677 & https://outsystemsrd.atlassian.net/browse/RMET-3678).
+
## 5.0.0-OS14
- Android | Update dependency to Firebase Analytics Android library (https://outsystemsrd.atlassian.net/browse/RMET-3608).
diff --git a/plugin.xml b/plugin.xml
index d48f963..d97db68 100644
--- a/plugin.xml
+++ b/plugin.xml
@@ -57,6 +57,7 @@ xmlns:android="http://schemas.android.com/apk/res/android"
+
@@ -108,6 +109,7 @@ xmlns:android="http://schemas.android.com/apk/res/android"
+
diff --git a/src/android/com/outsystems/firebase/analytics/FirebaseAnalyticsPlugin.java b/src/android/com/outsystems/firebase/analytics/FirebaseAnalyticsPlugin.java
index 5bf7f58..83e8200 100644
--- a/src/android/com/outsystems/firebase/analytics/FirebaseAnalyticsPlugin.java
+++ b/src/android/com/outsystems/firebase/analytics/FirebaseAnalyticsPlugin.java
@@ -9,14 +9,19 @@
import com.google.firebase.analytics.FirebaseAnalytics;
import com.outsystems.firebase.analytics.OSFANLManager;
+import com.outsystems.firebase.analytics.model.ConsentType;
+import com.outsystems.firebase.analytics.model.ConsentStatus;
import com.outsystems.firebase.analytics.model.OSFANLError;
import com.outsystems.firebase.analytics.model.OSFANLEventOutputModel;
import org.apache.cordova.CallbackContext;
+import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
+import java.util.HashMap;
+import java.util.Map;
public class FirebaseAnalyticsPlugin extends ReflectiveCordovaPlugin {
@@ -103,6 +108,50 @@ private void logECommerceEvent(JSONObject params, CallbackContext callbackContex
}
}
+ @CordovaMethod
+ private void setConsent(String consentSetting, CallbackContext callbackContext) throws JSONException {
+
+ try {
+ JSONArray consentSettings = new JSONArray(consentSetting);
+
+ Map consentMap = new HashMap<>();
+
+ for (int i = 0; i < consentSettings.length(); i++) {
+ JSONObject consentItem = consentSettings.getJSONObject(i);
+ int typeValue = consentItem.getInt("Type");
+ int statusValue = consentItem.getInt("Status");
+
+ FirebaseAnalytics.ConsentType consentType = ConsentType.fromInt(typeValue);
+ FirebaseAnalytics.ConsentStatus consentStatus = ConsentStatus.fromInt(statusValue);
+
+ if (consentType != null) {
+ if (consentStatus != null) {
+ if (consentMap.containsKey(consentType)) {
+ throw OSFANLError.Companion.duplicateItemsIn("ConsentSettings");
+ }
+ consentMap.put(consentType, consentStatus);
+ } else {
+ throw OSFANLError.Companion.invalidType("Consent Status of " + consentType, "GRANTED, or DENIED");
+ }
+ } else {
+ throw OSFANLError.Companion.invalidType("Consent Type", "AD_PERSONALIZATION, AD_STORAGE, AD_USER_DATA, or ANALYTICS_STORAGE");
+ }
+ }
+
+ if (!consentMap.isEmpty()) {
+ this.firebaseAnalytics.setConsent(consentMap);
+ callbackContext.success();
+ } else {
+ throw OSFANLError.Companion.missing("ConsentSettings");
+ }
+ } catch (OSFANLError e) {
+ JSONObject result = new JSONObject();
+ result.put("code", e.getCode());
+ result.put("message", e.getMessage());
+ callbackContext.error(result);
+ }
+ }
+
private static Bundle parse(JSONObject params) throws JSONException {
Bundle bundle = new Bundle();
Iterator it = params.keys();
@@ -126,4 +175,4 @@ private static Bundle parse(JSONObject params) throws JSONException {
return bundle;
}
-}
+}
\ No newline at end of file
diff --git a/src/android/com/outsystems/firebase/analytics/model/OSFANLConsentModels.kt b/src/android/com/outsystems/firebase/analytics/model/OSFANLConsentModels.kt
new file mode 100644
index 0000000..33ad5c0
--- /dev/null
+++ b/src/android/com/outsystems/firebase/analytics/model/OSFANLConsentModels.kt
@@ -0,0 +1,29 @@
+package com.outsystems.firebase.analytics.model
+
+import com.google.firebase.analytics.FirebaseAnalytics
+
+enum class ConsentType(val value: Int, val consentType: FirebaseAnalytics.ConsentType) {
+ AD_PERSONALIZATION(1, FirebaseAnalytics.ConsentType.AD_PERSONALIZATION),
+ AD_STORAGE(2, FirebaseAnalytics.ConsentType.AD_STORAGE),
+ AD_USER_DATA(3, FirebaseAnalytics.ConsentType.AD_USER_DATA),
+ ANALYTICS_STORAGE(4, FirebaseAnalytics.ConsentType.ANALYTICS_STORAGE);
+
+ companion object {
+ private val map = entries.associateBy(ConsentType::value)
+
+ @JvmStatic
+ fun fromInt(value: Int): FirebaseAnalytics.ConsentType? = map[value]?.consentType
+ }
+}
+
+enum class ConsentStatus(val value: Int, val consentStatus: FirebaseAnalytics.ConsentStatus) {
+ GRANTED(1, FirebaseAnalytics.ConsentStatus.GRANTED),
+ DENIED(2, FirebaseAnalytics.ConsentStatus.DENIED);
+
+ companion object {
+ private val map = entries.associateBy(ConsentStatus::value)
+
+ @JvmStatic
+ fun fromInt(value: Int): FirebaseAnalytics.ConsentStatus? = map[value]?.consentStatus
+ }
+}
\ No newline at end of file
diff --git a/src/ios/Common/OSFANLConsentHelper.swift b/src/ios/Common/OSFANLConsentHelper.swift
new file mode 100644
index 0000000..ea845ed
--- /dev/null
+++ b/src/ios/Common/OSFANLConsentHelper.swift
@@ -0,0 +1,96 @@
+import FirebaseAnalytics
+import FirebaseCore
+
+@objc enum ConsentTypeRawValue: Int, CustomStringConvertible, CaseIterable {
+ case adPersonalization = 1
+ case adStorage = 2
+ case adUserData = 3
+ case analyticsStorage = 4
+
+ var description: String {
+ return switch self {
+ case .adPersonalization: "ad_personalization"
+ case .adStorage: "ad_storage"
+ case .adUserData: "ad_user_data"
+ case .analyticsStorage: "analytics_storage"
+ }
+ }
+
+ static func allOptionsString() -> String {
+ let capitalizedDescriptions = allCases.map { $0.description.uppercased() }
+
+ if capitalizedDescriptions.count > 1 {
+ let lastOption = capitalizedDescriptions.last!
+ let allButLast = capitalizedDescriptions.dropLast().joined(separator: ", ")
+ return "\(allButLast), or \(lastOption)"
+ } else {
+ return capitalizedDescriptions.first ?? ""
+ }
+ }
+}
+
+@objc enum ConsentStatusRawValue: Int, CustomStringConvertible, CaseIterable {
+ case granted = 1
+ case denied = 2
+
+ var description: String {
+ return switch self {
+ case .granted: "granted"
+ case .denied: "denied"
+ }
+ }
+
+ static func allOptionsString() -> String {
+ let capitalizedDescriptions = allCases.map { $0.description.uppercased() }
+
+ if capitalizedDescriptions.count > 1 {
+ let lastOption = capitalizedDescriptions.last!
+ let allButLast = capitalizedDescriptions.dropLast().joined(separator: ", ")
+ return "\(allButLast), or \(lastOption)"
+ } else {
+ return capitalizedDescriptions.first ?? ""
+ }
+ }
+}
+
+@objc class OSFANLConsentHelper: NSObject {
+ @objc static func createConsentModel(_ commandArguments: NSArray) throws -> [ConsentType: ConsentStatus] {
+ guard let jsonString = commandArguments[0] as? String,
+ let jsonData = jsonString.data(using: .utf8),
+ let array = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [[String: Any]] else {
+ throw OSFANLError.invalidType("ConsentSettings", type: "JSON")
+ }
+
+ var firebaseConsentDict: [ConsentType: ConsentStatus] = [:]
+
+ for item in array {
+ guard let typeRawValue = item["Type"] as? Int,
+ let statusRawValue = item["Status"] as? Int else {
+ throw OSFANLError.invalidType("JSON passed Consent Type or Status", type: "Integer")
+ }
+
+ guard let consentTypeRawValue = ConsentTypeRawValue(rawValue: typeRawValue) else {
+ throw OSFANLError.invalidType("Consent Type", type: ConsentTypeRawValue.allOptionsString())
+ }
+
+ guard let consentStatusRawValue = ConsentStatusRawValue(rawValue: statusRawValue) else {
+ throw OSFANLError.invalidType("Consent Status", type: ConsentStatusRawValue.allOptionsString())
+ }
+
+ let consentType = ConsentType(rawValue: String(describing: consentTypeRawValue))
+ let consentStatus = ConsentStatus(rawValue: String(describing: consentStatusRawValue))
+
+ if firebaseConsentDict.keys.contains(consentType) {
+ throw OSFANLError.duplicateItemsIn(parameter: "ConsentSettings")
+ } else {
+ firebaseConsentDict[consentType] = consentStatus
+ }
+ }
+
+ if firebaseConsentDict.isEmpty {
+ throw OSFANLError.missing("ConsentSettings")
+ } else {
+ return firebaseConsentDict
+ }
+ }
+}
diff --git a/src/ios/FirebaseAnalyticsPlugin.h b/src/ios/FirebaseAnalyticsPlugin.h
index 9f89b02..da923f0 100644
--- a/src/ios/FirebaseAnalyticsPlugin.h
+++ b/src/ios/FirebaseAnalyticsPlugin.h
@@ -11,5 +11,6 @@
- (void)resetAnalyticsData:(CDVInvokedUrlCommand*)command;
- (void)setDefaultEventParameters:(CDVInvokedUrlCommand*)command;
- (void)requestTrackingAuthorization:(CDVInvokedUrlCommand*)command;
+- (void)setConsent:(CDVInvokedUrlCommand*)command;
@end
diff --git a/src/ios/FirebaseAnalyticsPlugin.m b/src/ios/FirebaseAnalyticsPlugin.m
index 96f2e6f..47c93f2 100644
--- a/src/ios/FirebaseAnalyticsPlugin.m
+++ b/src/ios/FirebaseAnalyticsPlugin.m
@@ -143,6 +143,18 @@ - (void)showTrackingAuthorizationPopup:(CDVInvokedUrlCommand *)command {
[self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}
+- (void)setConsent:(CDVInvokedUrlCommand*)command
+{
+ NSError *error;
+ NSDictionary *consentModel = [OSFANLConsentHelper createConsentModel:command.arguments error:&error];
+ if (error) {
+ [self sendError:error forCallbackId:command.callbackId];
+ } else {
+ [FIRAnalytics setConsent:consentModel];
+ [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK] callbackId:command.callbackId];
+ }
+}
+
typedef void (^showPermissionInformationPopupHandler)(UIAlertAction*);
- (void)showPermissionInformationPopup:
(NSString *)title :
@@ -177,5 +189,4 @@ - (void)sendError:(NSError *)error forCallbackId:(NSString *)callbackId {
[self.commandDelegate sendPluginResult:pluginResult callbackId:callbackId];
}
-
@end
diff --git a/www/FirebaseAnalytics.js b/www/FirebaseAnalytics.js
index d2d980f..bf2734f 100644
--- a/www/FirebaseAnalytics.js
+++ b/www/FirebaseAnalytics.js
@@ -55,5 +55,22 @@ module.exports = {
logECommerceEvent: function(event, eventParameters, items, success, error) {
let args = [{event, eventParameters, items}];
exec(success, error, PLUGIN_NAME, 'logECommerceEvent', args);
+ },
+ /**
+ * setConsent
+ *
+ * @param {string} consentSettings - A JSON string of an object containing consent settings.
+ * @param {function} [success] - Success callback function.
+ * @param {function} [error] - Error callback function.
+ *
+ * @example
+ * const consentSettings = {
+ * AD_STORAGE: 'GRANTED',
+ * ANALYTICS_STORAGE: 'GRANTED',
+ * };
+ * FirebaseAnalytics.setConsent(JSON.stringify(consentSettings));
+ */
+ setConsent: function (consentSettings, success, error) {
+ exec(success, error, PLUGIN_NAME, 'setConsent', [consentSettings]);
}
};