diff --git a/CHANGELOG.md b/CHANGELOG.md index b877cb6..e3fbc7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 3.0.0 + +##### Added +- Added support for the upcoming Braze Feature Flags product with `getFeatureFlag()`, `getAllFeatureFlags()`, `refreshFeatureFlags()`, and `subscribeToFeatureFlagUpdates()`. + +##### Changed +- Updated to [Braze Swift SDK 5.11.0](https://github.com/braze-inc/braze-swift-sdk/releases/tag/5.11.0). +- Removed automatic requests for App Tracking Transparency permissions on iOS. + ## 2.33.0 ##### Breaking diff --git a/package.json b/package.json index 4b1f314..e5339a1 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { "name": "appboy-cordova-sdk", - "version": "2.33.0", + "version": "3.0.0", "main": "www/AppboyPlugin.js" } diff --git a/plugin.xml b/plugin.xml index 4f13d1e..af88866 100644 --- a/plugin.xml +++ b/plugin.xml @@ -1,6 +1,6 @@ + id="cordova-plugin-appboy" version="3.0.0"> Device Braze Cordova SDK MIT @@ -19,6 +19,7 @@ + @@ -52,9 +53,9 @@ - - - + + + diff --git a/sample-project/www/index.html b/sample-project/www/index.html index b8f4e28..8e7c860 100755 --- a/sample-project/www/index.html +++ b/sample-project/www/index.html @@ -72,6 +72,11 @@

Apache Cordova

+ + + + +
diff --git a/sample-project/www/js/index.js b/sample-project/www/js/index.js index 3a97bf2..c11a656 100755 --- a/sample-project/www/js/index.js +++ b/sample-project/www/js/index.js @@ -34,6 +34,10 @@ bindEvents: function() { // function, we must explicitly call 'app.receivedEvent(...);' onDeviceReady: function() { app.receivedEvent('deviceready'); + document.getElementById("getFeatureFlagBtn").addEventListener("click", getFeatureFlag); + document.getElementById("getAllFeatureFlagsBtn").addEventListener("click", getAllFeatureFlags); + document.getElementById("refreshFeatureFlagsBtn").addEventListener("click", refreshFeatureFlags); + document.getElementById("subscribeToFeatureFlagsBtn").addEventListener("click", subscribeToFeatureFlags) document.getElementById("changeUserBtn").addEventListener("click", changeUser); document.getElementById("logCustomEventBtn").addEventListener("click", logCustomEvent); document.getElementById("logPurchaseBtn").addEventListener("click", logPurchase); @@ -92,6 +96,24 @@ function changeUser() { showTextBubble("Change user called"); } +function getFeatureFlag() { + AppboyPlugin.getFeatureFlag(document.getElementById("featureFlagInputId").value, customPluginSuccessJsonCallback("FeatureFlag: "), customPluginErrorCallback); +} + +function getAllFeatureFlags() { + AppboyPlugin.getAllFeatureFlags(customPluginSuccessArrayCallback("GetAllFeatureFlags: "), customPluginErrorCallback); +} + +function refreshFeatureFlags() { + AppboyPlugin.refreshFeatureFlags(); + showTextBubble("Refresh feature flags"); +} + +function subscribeToFeatureFlags() { + AppboyPlugin.subscribeToFeatureFlagUpdates(featureFlagsUpdated); + showTextBubble("Subscribed to Feature Flags"); +} + function logCustomEvent() { var properties = {}; properties["One"] = "That's the Way of the World"; @@ -309,6 +331,13 @@ function customPluginSuccessCallback(bubbleMessage) { return function(callbackResult) { showTextBubble(bubbleMessage + " " + callbackResult) }; } +/** +* Serves as the success callback for the Appboy Plugin. Displays a text bubble with a message when called. +**/ +function customPluginSuccessJsonCallback(bubbleMessage) { + return function(callbackResult) { showTextBubble(bubbleMessage + " JSON: " + JSON.stringify(callbackResult)) }; +} + /** * Serves as the success callback for the Appboy Plugin. Displays a text bubble with a message when called. **/ @@ -322,6 +351,14 @@ function customPluginSuccessArrayCallback(bubbleMessage) { }; } +function featureFlagsUpdated(featureFlags) { + var numElements = featureFlags.length; + showTextBubble("Feature Flags Updated: " + numElements + " objects") + for (var i = 0; i < numElements; i++) { + console.log(" Feature Flag - " + JSON.stringify(featureFlags[i])); + } +} + /** * Serves as the error callback for the Appboy Plugin. Displays a text bubble with a message when called. **/ diff --git a/src/android/BrazePlugin.kt b/src/android/BrazePlugin.kt index 9cbe11f..5298ba8 100644 --- a/src/android/BrazePlugin.kt +++ b/src/android/BrazePlugin.kt @@ -13,9 +13,11 @@ import com.braze.configuration.BrazeConfig import com.braze.cordova.ContentCardUtils.getCardById import com.braze.cordova.ContentCardUtils.mapContentCards import com.braze.cordova.CordovaInAppMessageViewWrapper.CordovaInAppMessageViewWrapperFactory +import com.braze.cordova.FeatureFlagUtils.mapFeatureFlags import com.braze.enums.BrazeSdkMetadata import com.braze.events.ContentCardsUpdatedEvent import com.braze.events.IEventSubscriber +import com.braze.events.FeatureFlagsUpdatedEvent import com.braze.models.outgoing.BrazeProperties import com.braze.support.BrazeLogger.Priority.* import com.braze.support.BrazeLogger.brazelog @@ -27,6 +29,7 @@ import com.braze.ui.inappmessage.BrazeInAppMessageManager import org.apache.cordova.CallbackContext import org.apache.cordova.CordovaPlugin import org.apache.cordova.CordovaPreferences +import org.apache.cordova.PluginResult; import org.json.JSONArray import org.json.JSONException import org.json.JSONObject @@ -277,6 +280,32 @@ open class BrazePlugin : CordovaPlugin() { cordova.activity.startActivity(intent) return true } + "getFeatureFlag" -> { + callbackContext.success(Braze.getInstance(applicationContext).getFeatureFlag(args.getString(0)).forJsonPut()) + return true + } + "getAllFeatureFlags" -> { + callbackContext.success( + mapFeatureFlags( + Braze.getInstance(applicationContext).getAllFeatureFlags() + ) + ) + return true + } + "refreshFeatureFlags" -> { + runOnBraze { it.refreshFeatureFlags() } + return true + } + "subscribeToFeatureFlagUpdates" -> { + runOnBraze { + it.subscribeToFeatureFlagsUpdates() { event: FeatureFlagsUpdatedEvent -> + val result = PluginResult(PluginResult.Status.OK, mapFeatureFlags(event.featureFlags)) + result.setKeepCallback(true) + callbackContext.sendPluginResult(result) + } + } + return true + } GET_NEWS_FEED_METHOD, GET_CARD_COUNT_FOR_CATEGORIES_METHOD, GET_UNREAD_CARD_COUNT_FOR_CATEGORIES_METHOD -> return handleNewsFeedGetters(action, args, callbackContext) diff --git a/src/android/FeatureFlagUtils.kt b/src/android/FeatureFlagUtils.kt new file mode 100644 index 0000000..748c4b0 --- /dev/null +++ b/src/android/FeatureFlagUtils.kt @@ -0,0 +1,22 @@ +package com.braze.cordova + +import com.braze.models.FeatureFlag +import com.braze.support.BrazeLogger.Priority.E +import com.braze.support.BrazeLogger.brazelog +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject + +object FeatureFlagUtils { + fun mapFeatureFlags(ffList: List): JSONArray { + val featureFlags = JSONArray() + for (ff in ffList) { + try { + featureFlags.put(ff.forJsonPut()) + } catch (e: JSONException) { + brazelog(E, e) { "Failed to map FeatureFlag to JSON. $ff" } + } + } + return featureFlags + } +} diff --git a/src/android/build-extras.gradle b/src/android/build-extras.gradle index 4bc9773..81cdc61 100644 --- a/src/android/build-extras.gradle +++ b/src/android/build-extras.gradle @@ -5,7 +5,7 @@ repositories { } dependencies { - implementation 'com.braze:android-sdk-ui:24.1.0' + implementation 'com.braze:android-sdk-ui:24.2.0' implementation 'com.google.firebase:firebase-messaging:23.0.0' } diff --git a/src/ios/BrazePlugin.h b/src/ios/BrazePlugin.h index a128a46..aa76d59 100644 --- a/src/ios/BrazePlugin.h +++ b/src/ios/BrazePlugin.h @@ -6,58 +6,65 @@ @property Braze *braze; @property id idfaDelegate; +@property NSMutableArray *subscriptions; /*-------Braze-------*/ -- (void) changeUser:(CDVInvokedUrlCommand *)command; -- (void) logCustomEvent:(CDVInvokedUrlCommand *)command; -- (void) logPurchase:(CDVInvokedUrlCommand *)command; -- (void) disableSdk:(CDVInvokedUrlCommand *)command; -- (void) enableSdk:(CDVInvokedUrlCommand *)command; -- (void) wipeData:(CDVInvokedUrlCommand *)command; -- (void) requestImmediateDataFlush:(CDVInvokedUrlCommand *)command; -- (void) getDeviceId:(CDVInvokedUrlCommand *)command; +- (void)changeUser:(CDVInvokedUrlCommand *)command; +- (void)logCustomEvent:(CDVInvokedUrlCommand *)command; +- (void)logPurchase:(CDVInvokedUrlCommand *)command; +- (void)disableSdk:(CDVInvokedUrlCommand *)command; +- (void)enableSdk:(CDVInvokedUrlCommand *)command; +- (void)wipeData:(CDVInvokedUrlCommand *)command; +- (void)requestImmediateDataFlush:(CDVInvokedUrlCommand *)command; +- (void)getDeviceId:(CDVInvokedUrlCommand *)command; /*-------Braze.User-------*/ -- (void) setFirstName:(CDVInvokedUrlCommand *)command; -- (void) setLastName:(CDVInvokedUrlCommand *)command; -- (void) setEmail:(CDVInvokedUrlCommand *)command; -- (void) setGender:(CDVInvokedUrlCommand *)command; -- (void) setDateOfBirth:(CDVInvokedUrlCommand *)command; -- (void) setCountry:(CDVInvokedUrlCommand *)command; -- (void) setHomeCity:(CDVInvokedUrlCommand *)command; -- (void) setPhoneNumber:(CDVInvokedUrlCommand *)command; -- (void) setLanguage:(CDVInvokedUrlCommand *)command; - -- (void) setPushNotificationSubscriptionType:(CDVInvokedUrlCommand *)command; -- (void) setEmailNotificationSubscriptionType:(CDVInvokedUrlCommand *)command; - -- (void) setBoolCustomUserAttribute:(CDVInvokedUrlCommand *)command; -- (void) setStringCustomUserAttribute:(CDVInvokedUrlCommand *)command; -- (void) setDoubleCustomUserAttribute:(CDVInvokedUrlCommand *)command; -- (void) setDateCustomUserAttribute:(CDVInvokedUrlCommand *)command; -- (void) setIntCustomUserAttribute:(CDVInvokedUrlCommand *)command; -- (void) setCustomUserAttributeArray:(CDVInvokedUrlCommand *)command; -- (void) unsetCustomUserAttribute:(CDVInvokedUrlCommand *)command; -- (void) incrementCustomUserAttribute:(CDVInvokedUrlCommand *)command; -- (void) addToCustomAttributeArray:(CDVInvokedUrlCommand *)command; -- (void) removeFromCustomAttributeArray:(CDVInvokedUrlCommand *)command; -- (void) addAlias:(CDVInvokedUrlCommand *)command; +- (void)setFirstName:(CDVInvokedUrlCommand *)command; +- (void)setLastName:(CDVInvokedUrlCommand *)command; +- (void)setEmail:(CDVInvokedUrlCommand *)command; +- (void)setGender:(CDVInvokedUrlCommand *)command; +- (void)setDateOfBirth:(CDVInvokedUrlCommand *)command; +- (void)setCountry:(CDVInvokedUrlCommand *)command; +- (void)setHomeCity:(CDVInvokedUrlCommand *)command; +- (void)setPhoneNumber:(CDVInvokedUrlCommand *)command; +- (void)setLanguage:(CDVInvokedUrlCommand *)command; + +- (void)setPushNotificationSubscriptionType:(CDVInvokedUrlCommand *)command; +- (void)setEmailNotificationSubscriptionType:(CDVInvokedUrlCommand *)command; + +- (void)setBoolCustomUserAttribute:(CDVInvokedUrlCommand *)command; +- (void)setStringCustomUserAttribute:(CDVInvokedUrlCommand *)command; +- (void)setDoubleCustomUserAttribute:(CDVInvokedUrlCommand *)command; +- (void)setDateCustomUserAttribute:(CDVInvokedUrlCommand *)command; +- (void)setIntCustomUserAttribute:(CDVInvokedUrlCommand *)command; +- (void)setCustomUserAttributeArray:(CDVInvokedUrlCommand *)command; +- (void)unsetCustomUserAttribute:(CDVInvokedUrlCommand *)command; +- (void)incrementCustomUserAttribute:(CDVInvokedUrlCommand *)command; +- (void)addToCustomAttributeArray:(CDVInvokedUrlCommand *)command; +- (void)removeFromCustomAttributeArray:(CDVInvokedUrlCommand *)command; +- (void)addAlias:(CDVInvokedUrlCommand *)command; /*-------BrazeUI-------*/ -- (void) launchNewsFeed:(CDVInvokedUrlCommand *)command; -- (void) launchContentCards:(CDVInvokedUrlCommand *)command; +- (void)launchNewsFeed:(CDVInvokedUrlCommand *)command; +- (void)launchContentCards:(CDVInvokedUrlCommand *)command; /*-------News Feed-------*/ -- (void) getCardCountForCategories:(CDVInvokedUrlCommand *)command; -- (void) getUnreadCardCountForCategories:(CDVInvokedUrlCommand *)command; -- (void) getNewsFeed:(CDVInvokedUrlCommand *)command; +- (void)getCardCountForCategories:(CDVInvokedUrlCommand *)command; +- (void)getUnreadCardCountForCategories:(CDVInvokedUrlCommand *)command; +- (void)getNewsFeed:(CDVInvokedUrlCommand *)command; /*-------Content Cards-------*/ -- (void) requestContentCardsRefresh:(CDVInvokedUrlCommand *)command; -- (void) getContentCardsFromServer:(CDVInvokedUrlCommand *)command; -- (void) getContentCardsFromCache:(CDVInvokedUrlCommand *)command; -- (void) logContentCardClicked:(CDVInvokedUrlCommand *)command; -- (void) logContentCardImpression:(CDVInvokedUrlCommand *)command; -- (void) logContentCardDismissed:(CDVInvokedUrlCommand *)command; +- (void)requestContentCardsRefresh:(CDVInvokedUrlCommand *)command; +- (void)getContentCardsFromServer:(CDVInvokedUrlCommand *)command; +- (void)getContentCardsFromCache:(CDVInvokedUrlCommand *)command; +- (void)logContentCardClicked:(CDVInvokedUrlCommand *)command; +- (void)logContentCardImpression:(CDVInvokedUrlCommand *)command; +- (void)logContentCardDismissed:(CDVInvokedUrlCommand *)command; + +/*-------Feature Flags-------*/ +- (void)getFeatureFlag:(CDVInvokedUrlCommand *)command; +- (void)getAllFeatureFlags:(CDVInvokedUrlCommand *)command; +- (void)refreshFeatureFlags:(CDVInvokedUrlCommand *)command; +- (void)subscribeToFeatureFlagUpdates:(CDVInvokedUrlCommand *)command; @end diff --git a/src/ios/BrazePlugin.m b/src/ios/BrazePlugin.m index 3d7b47c..daaa0d7 100644 --- a/src/ios/BrazePlugin.m +++ b/src/ios/BrazePlugin.m @@ -5,8 +5,6 @@ @import BrazeLocation; @import BrazeUI; @import UserNotifications; -@import AppTrackingTransparency; -@import AdSupport; @interface BrazePlugin() @property NSString *APIKey; @@ -35,11 +33,6 @@ - (void)pluginInitialize { self.sessionTimeout = settings[@"com.appboy.ios_session_timeout"]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didFinishLaunchingListener:) name:UIApplicationDidFinishLaunchingNotification object:nil]; - - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(requestAppTrackingTransparencyAuthorization) - name:UIApplicationDidBecomeActiveNotification - object:nil]; if (![self.disableAutomaticPushHandling isEqualToString:@"YES"]) { [AppDelegate swizzleHostAppDelegate]; @@ -61,6 +54,7 @@ - (void)didFinishLaunchingListener:(NSNotification *)notification { [configuration.api addSDKMetadata:@[[BRZSDKMetadata cordova]]]; self.braze = [[Braze alloc] initWithConfiguration:configuration]; self.braze.inAppMessagePresenter = [[BrazeInAppMessageUI alloc] init]; + self.subscriptions = [NSMutableArray array]; // Set the IDFA delegate for the plugin if ([self.enableIDFACollection isEqualToString:@"YES"]) { @@ -94,14 +88,6 @@ - (void)didFinishLaunchingListener:(NSNotification *)notification { } } -- (void)requestAppTrackingTransparencyAuthorization { - if (@available(iOS 14, *)) { - [ATTrackingManager requestTrackingAuthorizationWithCompletionHandler:^(ATTrackingManagerAuthorizationStatus status) { - NSLog(@"Got result from App Track Transparency popup %ld", (long)status); - }]; - } -} - /*-------Braze-------*/ - (void)changeUser:(CDVInvokedUrlCommand *)command { NSString *userId = [command argumentAtIndex:0 withDefault:nil]; @@ -606,6 +592,58 @@ + (NSString *)getJsonFromExtras:(NSDictionary *)extras { } } +/*-------Feature Flags-------*/ +- (void)getFeatureFlag:(CDVInvokedUrlCommand *)command { + NSString *featureFlagId = [command argumentAtIndex:0 withDefault:nil]; + BRZFeatureFlag *featureFlag = [self.braze.featureFlags featureFlagWithId:featureFlagId]; + + [self sendCordovaSuccessPluginResultWithDictionary:[BrazePlugin formattedFeatureFlag:featureFlag] andCommand:command]; +} + +- (void)getAllFeatureFlags:(CDVInvokedUrlCommand *)command { + [self sendCordovaSuccessPluginResultWithArray:[BrazePlugin formattedFeatureFlagsMap:self.braze.featureFlags.featureFlags] + andCommand:command]; +} + +- (void)refreshFeatureFlags:(CDVInvokedUrlCommand *)command { + [self.braze.featureFlags requestRefreshWithCompletion:^(NSArray * flags, NSError * error) { + if (error) { + NSLog(@"%@", error.debugDescription); + } else { + NSLog(@"Got Feature Flags from server callback"); + [self sendCordovaSuccessPluginResultWithArray:[BrazePlugin formattedFeatureFlagsMap:self.braze.featureFlags.featureFlags] + andCommand:command]; + } + }]; +} + +- (void)subscribeToFeatureFlagUpdates:(CDVInvokedUrlCommand *)command { + [self.subscriptions addObject:[self.braze.featureFlags subscribeToUpdates:^(NSArray * featureFlags) { + NSArray *mappedFlags = [BrazePlugin formattedFeatureFlagsMap:featureFlags]; + CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsArray:mappedFlags]; + [pluginResult setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; + }]]; +} + ++ (NSDictionary *)formattedFeatureFlag:(BRZFeatureFlag *)featureFlag { + NSMutableDictionary *formattedFlag = [NSMutableDictionary dictionary]; + formattedFlag[@"id"] = featureFlag.identifier; + formattedFlag[@"enabled"] = @(featureFlag.enabled); + formattedFlag[@"properties"] = featureFlag.properties; + + return formattedFlag; +} + ++ (NSArray *)formattedFeatureFlagsMap:(NSArray *)featureFlags { + NSMutableArray *mappedFlags = [NSMutableArray array]; + for (BRZFeatureFlag *flag in featureFlags) { + [mappedFlags addObject:[BrazePlugin formattedFeatureFlag:flag]]; + } + + return mappedFlags; +} + /*-------Cordova Helper Methods-------*/ - (void)sendCordovaErrorPluginResultWithString:(NSString *)resultMessage andCommand:(CDVInvokedUrlCommand *)command { CDVPluginResult *pluginResult = nil; @@ -631,4 +669,10 @@ - (void)sendCordovaSuccessPluginResultWithArray:(NSArray *)resultMessage andComm [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; } +- (void)sendCordovaSuccessPluginResultWithDictionary:(NSDictionary *)resultMessage andCommand:(CDVInvokedUrlCommand *)command { + CDVPluginResult *pluginResult = nil; + pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:resultMessage]; + [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId]; +} + @end diff --git a/www/AppboyPlugin.js b/www/AppboyPlugin.js index c5b2948..c3adceb 100644 --- a/www/AppboyPlugin.js +++ b/www/AppboyPlugin.js @@ -436,6 +436,43 @@ AppboyPlugin.prototype.getDeviceId = function (successCallback, errorCallback) { cordova.exec(successCallback, errorCallback, "AppboyPlugin", "getDeviceId"); } +/** + * Requests a specific Feature Flags. This will pull the data from a local cache and does + * not force a refresh. + * + * @param id The ID of the Feature Flag to retrieve. + * @return [FeatureFlag] of the requested ID. If the Feature Flag does not exist, a [FeatureFlag] + * will be returned with enabled set to `false` and empty properties. + */ +AppboyPlugin.prototype.getFeatureFlag = function (id, successCallback, errorCallback) { + cordova.exec(successCallback, errorCallback, "AppboyPlugin", "getFeatureFlag", [id]); +} + +/** + * Retrieves the offline/cached list of Feature Flags from offline storage. + * + * @return The list of cached Feature Flags. Note that this does not request a + * fresh list of Feature Flags from Braze. If the SDK is disabled or the + * cached list of feature flags cannot be retrieved, returns empty list. + */ +AppboyPlugin.prototype.getAllFeatureFlags = function (successCallback, errorCallback) { + cordova.exec(successCallback, errorCallback, "AppboyPlugin", "getAllFeatureFlags"); +} + +/** + * Requests a refresh of Feature Flags from the Braze server. + */ +AppboyPlugin.prototype.refreshFeatureFlags = function (successCallback, errorCallback) { + cordova.exec(successCallback, errorCallback, "AppboyPlugin", "refreshFeatureFlags"); +} + +/** + * Subscribes to Feature Flags events. The subscriber callback will be called when Feature Flags are updated. + */ +AppboyPlugin.prototype.subscribeToFeatureFlagUpdates = function (successCallback) { + cordova.exec(successCallback, null, "AppboyPlugin", "subscribeToFeatureFlagUpdates"); +} + /** * @return Starts SDK session tracking if previously disabled. Only used for Android. */