Skip to content

Commit

Permalink
Release version 4.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
hokstuff committed Mar 31, 2023
1 parent 98b3df8 commit 8f935b0
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 87 deletions.
9 changes: 4 additions & 5 deletions BrazeProject/BrazeProject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,10 @@ export const BrazeProject = (): ReactElement => {

Braze.addListener(
Braze.Events.CONTENT_CARDS_UPDATED,
(cardsUpdated: boolean) => {
if (cardsUpdated) {
console.log('Content Cards Updated.');
}
},
(data: Braze.ContentCardsUpdatedEvent) => {
let cards = data.cards;
console.log(`Received ${cards.length} Content Cards with IDs:`, cards.map(card => card.id));
}
);

Braze.addListener(
Expand Down
2 changes: 2 additions & 0 deletions BrazeProject/ios/BrazeProject.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@
baseConfigurationReference = 5B7EB9410499542E8C5724F5 /* Pods-BrazeProject-BrazeProjectTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = 5GLZKGNWQ3;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
Expand Down Expand Up @@ -461,6 +462,7 @@
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
COPY_PHASE_STRIP = NO;
DEVELOPMENT_TEAM = 5GLZKGNWQ3;
INFOPLIST_FILE = BrazeProjectTests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 12.4;
LD_RUNPATH_SEARCH_PATHS = (
Expand Down
12 changes: 6 additions & 6 deletions BrazeProject/ios/BrazeProject/AppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
self.initialProps = @{};

// Setup Braze bridge
id<RCTBridgeDelegate> moduleInitializer = [[BrazeReactBridge alloc] init];
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:moduleInitializer
launchOptions:launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"BrazeProject"
initialProperties:nil];
NSURL *jsCodeLocation =
[[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:self.moduleName
initialProperties:self.initialProps
launchOptions:launchOptions];
self.bridge = rootView.bridge;

// Configure views in the application
Expand Down
26 changes: 13 additions & 13 deletions BrazeProject/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
PODS:
- boost (1.76.0)
- braze-react-native-sdk (2.2.0):
- BrazeKit (~> 5.11.2)
- BrazeLocation (~> 5.11.2)
- BrazeUI (~> 5.11.2)
- braze-react-native-sdk (4.0.0):
- BrazeKit (~> 5.13.0)
- BrazeLocation (~> 5.13.0)
- BrazeUI (~> 5.13.0)
- React-Core
- BrazeKit (5.11.2)
- BrazeLocation (5.11.2):
- BrazeKit (= 5.11.2)
- BrazeUI (5.11.2):
- BrazeKit (= 5.11.2)
- BrazeKit (5.13.0)
- BrazeLocation (5.13.0):
- BrazeKit (= 5.13.0)
- BrazeUI (5.13.0):
- BrazeKit (= 5.13.0)
- CocoaAsyncSocket (7.6.5)
- DoubleConversion (1.1.6)
- FBLazyVector (0.71.2)
Expand Down Expand Up @@ -590,10 +590,10 @@ EXTERNAL SOURCES:

SPEC CHECKSUMS:
boost: 57d2868c099736d80fcd648bf211b4431e51a558
braze-react-native-sdk: f7ab95b1b11273530e8a7e9c456761637fb4be4c
BrazeKit: 5502faeac539dffd14ad403afbf7d26943060d20
BrazeLocation: 3d81f3c36f66939be40ca78ce83a677ff9af1711
BrazeUI: a1d2bcddbb18c015efd75289584a9a9f7341a38e
braze-react-native-sdk: 9832e378fa9940b5830a0b34eaa00c1826a57bd7
BrazeKit: 97f6875bf05920a46a2f0c9d0e452b6b840c5f99
BrazeLocation: 571ee6bf6b2f5c1aba20d9a75e4da190f8db7e5b
BrazeUI: 31dd9f0235149f05d6f7e37f273be7293e000587
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
FBLazyVector: d58428b28fe1f5070fe993495b0e2eaf701d3820
Expand Down
20 changes: 19 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
# 4.0.0

##### Breaking
- The iOS bridge now automatically attaches the default In-App Message UI with the `braze` instance, without needing to call `subscribeToInAppMessage()`. This updates the behavior from `2.0.0` to simplify integration.
- This change doesn't affect integrations using custom UIs for in-app messages.
- Changes the returned value when subscribing to `Braze.Events.CONTENT_CARDS_UPDATED` to be a `Braze.ContentCardsUpdatedEvent` object instead of a boolean.
- `Braze.ContentCardsUpdatedEvent` contains a `cards` property which is an array of the Content Cards in the update.
- Thanks @Minishlink for your contribution!

##### Fixed
- Fixes an issue in the iOS bridge where `getContentCards()` and `getNewsFeedCards()` returned data in a different format than the Android bridge.
- Fixes the behavior when using the recommended iOS integration where the React Bridge delegate had conflicts with other dependencies. The updated sample app code can be found [here](https://github.com/braze-inc/braze-react-native-sdk/blob/master/BrazeProject/ios/BrazeProject/AppDelegate.mm).

##### Added
- Updates the native iOS bridge to [Braze Swift SDK 5.13.0](https://github.com/braze-inc/braze-swift-sdk/blob/main/CHANGELOG.md#5130).
- Improves typescript definitions for `addListener` event types.

# 3.0.0

> Starting with this release, this SDK will use [Semantic Versioning](https://semver.org/).
Expand Down Expand Up @@ -57,7 +74,8 @@
pod 'SDWebImage', :modular_headers => true
```
- Then, follow [these setup instructions](https://braze-inc.github.io/braze-swift-sdk/tutorials/braze/c3-gif-support).
- For sample code to help with the migration, reference our sample app and [`AppDelegate.m`](https://github.com/braze-inc/braze-react-native-sdk/blob/master/public/BrazeProject/ios/BrazeProject/AppDelegate.m) file.
- To use the default In-App Message UI, make sure to call `subscribeToInAppMessage()` or else follow [these instructions](https://braze-inc.github.io/braze-swift-sdk/tutorials/braze/c1-inappmessageui) to add it to your app.
- For sample code to help with the migration, reference our sample app and [`AppDelegate.mm`](https://github.com/braze-inc/braze-react-native-sdk/blob/master/BrazeProject/ios/BrazeProject/AppDelegate.mm) file.
- If you are integrating this SDK with an application that uses only Objective-C, create an empty Swift file to ensure that all the relevant Swift runtime libraries are linked. Reference [this file](https://github.com/braze-inc/braze-react-native-sdk/blob/master/BrazeProject/ios/BrazeProject/empty-file.swift) from our sample app.
- The following methods for News Feed are now no-ops on iOS:
- `Braze.launchNewsFeed()`
Expand Down
13 changes: 6 additions & 7 deletions android/src/main/java/com/braze/reactbridge/BrazeReactBridge.kt
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ class BrazeReactBridge(reactContext: ReactApplicationContext?) : ReactContextBas
}
}
braze.subscribeToContentCardsUpdates(subscriber)
braze.requestContentCardsRefresh(true)
braze.requestContentCardsRefresh(false)
}

@ReactMethod
Expand All @@ -405,12 +405,11 @@ class BrazeReactBridge(reactContext: ReactApplicationContext?) : ReactContextBas
)
}
contentCardsUpdatedSubscriber = IEventSubscriber { event ->
val updated = event.timestampSeconds > contentCardsUpdatedAt
if (updated && reactApplicationContext.hasActiveReactInstance()) {
reactApplicationContext
.getJSModule(RCTDeviceEventEmitter::class.java)
.emit(CONTENT_CARDS_UPDATED_EVENT_NAME, updated)
}
val eventData = Arguments.createMap()
eventData.putArray("cards", mapContentCards(event.allCards))
reactApplicationContext
.getJSModule(RCTDeviceEventEmitter::class.java)
.emit(CONTENT_CARDS_UPDATED_EVENT_NAME, eventData)
updateContentCardsIfNeeded(event)
}

Expand Down
6 changes: 3 additions & 3 deletions braze-react-native-sdk.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ Pod::Spec.new do |s|
s.preserve_paths = 'LICENSE.md', 'README.md', 'package.json', 'index.js', 'iOS/replace-at-import-statements.sh'
s.source_files = 'iOS/**/*.{h,m}'

s.dependency 'BrazeKit', '~> 5.11.2'
s.dependency 'BrazeLocation', '~> 5.11.2'
s.dependency 'BrazeUI', '~> 5.11.2'
s.dependency 'BrazeKit', '~> 5.13.0'
s.dependency 'BrazeLocation', '~> 5.13.0'
s.dependency 'BrazeUI', '~> 5.13.0'

s.dependency 'React-Core'

Expand Down
2 changes: 1 addition & 1 deletion iOS/BrazeReactBridge/BrazeReactBridge/BrazeReactBridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#import <BrazeKit/BrazeKit-Swift.h>

@interface BrazeReactBridge : RCTEventEmitter <RCTBridgeModule, RCTBridgeDelegate>
@interface BrazeReactBridge : RCTEventEmitter <RCTBridgeModule>

/// Intializes the Braze instance based on a configuration. This same instance is also used by the Braze bridge.
/// - Parameters:
Expand Down
147 changes: 110 additions & 37 deletions iOS/BrazeReactBridge/BrazeReactBridge/BrazeReactBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,18 @@ - (void)startObserving {

self.contentCardsSubscription = [braze.contentCards subscribeToUpdates:^(NSArray<BRZContentCardRaw *> * _Nonnull cards) {
RCTLogInfo(@"Received Content Cards array via subscription of length: %lu", [cards count]);
[self sendEventWithName:kContentCardsUpdatedEvent body:nil];
NSMutableDictionary *eventData = [NSMutableDictionary dictionary];
eventData[@"cards"] = RCTFormatContentCards(cards);
[self sendEventWithName:kContentCardsUpdatedEvent body:eventData];
}];
self.newsFeedSubscription = [braze.newsFeed subscribeToUpdates:^(NSArray<BRZNewsFeedCard *> * _Nonnull cards) {
RCTLogInfo(@"Received News Feed cards via subscription of length: %lu", [cards count]);
[self sendEventWithName:kNewsFeedCardsUpdatedEvent body:nil];
}];

// Add default in-app message UI
BrazeInAppMessageUI *inAppMessageUI = [[BrazeInAppMessageUI alloc] init];
braze.inAppMessagePresenter = inAppMessageUI;
}

- (void)stopObserving {
Expand Down Expand Up @@ -466,16 +472,8 @@ - (NSArray *)parseArray:(NSArray *)array {
- (NSArray *)getMappedNewsFeedCards {
NSArray<BRZNewsFeedCard *> *newsFeedCards = braze.newsFeed.cards;
NSMutableArray *mappedNewsFeedCards = [NSMutableArray arrayWithCapacity:[newsFeedCards count]];
[newsFeedCards enumerateObjectsUsingBlock:^(BRZNewsFeedCard *card, NSUInteger idx, BOOL *stop) {
NSError* error = nil;
id cardJSON = [NSJSONSerialization JSONObjectWithData:[card json]
options:NSJSONReadingMutableContainers
error:&error];
if (error) {
RCTLogInfo(@"Unable to parse News Feed Card: %@, error: %@. Skipping.", card, error);
} else {
[mappedNewsFeedCards addObject:cardJSON];
}
[newsFeedCards enumerateObjectsUsingBlock:^(id card, NSUInteger idx, BOOL *stop) {
[mappedNewsFeedCards addObject:RCTFormatNewsFeedCard(card)];
}];
return mappedNewsFeedCards;
}
Expand All @@ -491,6 +489,46 @@ - (nullable BRZNewsFeedCard *)getNewsFeedCardById:(NSString *)idString {
return nil;
}

/// Custom decoding function to match the News Feed Card format expected by the JS layer.
/// - Note: The output intentionally differs from the native `[card json]` method.
static NSDictionary *RCTFormatNewsFeedCard(BRZNewsFeedCard *card) {
NSMutableDictionary *newsFeedCardData = [NSMutableDictionary dictionary];
newsFeedCardData[@"id"] = card.identifier;
newsFeedCardData[@"created"] = @(card.created);
newsFeedCardData[@"expiresAt"] = @(card.expires);
newsFeedCardData[@"viewed"] = @(card.viewed);
newsFeedCardData[@"url"] = RCTNullIfNil(card.url ? card.url.absoluteString : nil);
newsFeedCardData[@"openURLInWebView"] = @(card.useWebView);
newsFeedCardData[@"extras"] = card.extras ? RCTJSONClean(card.extras) : @{};

switch (card.type) {
case BRZNewsFeedCardTypeBanner:
newsFeedCardData[@"image"] = RCTNullIfNil(card.image ? card.image.absoluteString : nil);
newsFeedCardData[@"imageAspectRatio"] = @(card.imageAspectRatio);
newsFeedCardData[@"domain"] = RCTNullIfNil(card.domain);
newsFeedCardData[@"type"] = @"Banner";
case BRZNewsFeedCardTypeCaptionedImage:
newsFeedCardData[@"image"] = RCTNullIfNil(card.image ? card.image.absoluteString : nil);
newsFeedCardData[@"imageAspectRatio"] = @(card.imageAspectRatio);
newsFeedCardData[@"title"] = card.title;
newsFeedCardData[@"cardDescription"] = card.cardDescription;
newsFeedCardData[@"domain"] = RCTNullIfNil(card.domain);
newsFeedCardData[@"type"] = @"Captioned";
case BRZNewsFeedCardTypeClassic:
newsFeedCardData[@"image"] = RCTNullIfNil(card.image ? card.image.absoluteString : nil);
newsFeedCardData[@"cardDescription"] = card.cardDescription;
newsFeedCardData[@"title"] = card.title;
newsFeedCardData[@"domain"] = card.domain;
newsFeedCardData[@"type"] = @"Classic";
case BRZNewsFeedCardTypeTextAnnouncement:
newsFeedCardData[@"title"] = card.title;
newsFeedCardData[@"cardDescription"] = card.cardDescription;
newsFeedCardData[@"domain"] = RCTNullIfNil(card.domain);
newsFeedCardData[@"type"] = @"TextAnnouncement";
}
return newsFeedCardData;
}

#pragma mark - Content Cards

/// Returns the content card for the associated id, or nil if not found.
Expand Down Expand Up @@ -520,23 +558,10 @@ - (nullable BRZContentCardRaw *)getContentCardById:(NSString *)idString {
NSArray<BRZContentCardRaw *> *_Nullable cards,
NSError *_Nullable refreshError) {
if (refreshError) {
RCTLogInfo(@"Error during Content Card refresh: %@", refreshError);
reject(@"Content Card refresh error", refreshError.description, refreshError);
return;
}

NSMutableArray *mappedContentCards = [NSMutableArray arrayWithCapacity:[cards count]];
[cards enumerateObjectsUsingBlock:^(BRZContentCardRaw *card, NSUInteger idx, BOOL *stop) {
NSError* parsingError = nil;
id cardJSON = [NSJSONSerialization JSONObjectWithData:[card json]
options:NSJSONReadingMutableContainers
error:&parsingError];
if (parsingError) {
RCTLogInfo(@"Unable to parse Content Card: %@, error: %@. Skipping.", card, parsingError);
} else {
[mappedContentCards addObject:cardJSON];
}
}];

resolve(mappedContentCards);
resolve(RCTFormatContentCards(cards));
}];
}

Expand Down Expand Up @@ -564,6 +589,62 @@ - (nullable BRZContentCardRaw *)getContentCardById:(NSString *)idString {
}
}

static NSMutableArray *RCTFormatContentCards(NSArray<BRZContentCardRaw *> *cards) {
NSMutableArray *mappedContentCards = [NSMutableArray arrayWithCapacity:[cards count]];
[cards enumerateObjectsUsingBlock:^(id card, NSUInteger idx, BOOL *stop) {
[mappedContentCards addObject:RCTFormatContentCard(card)];
}];
return mappedContentCards;
}

/// Custom decoding function to match the Content Card format expected by the JS layer.
/// - Note: The output intentionally differs from the native `[card json]` method.
static NSDictionary *RCTFormatContentCard(BRZContentCardRaw *card) {
NSMutableDictionary *formattedContentCardData = [NSMutableDictionary dictionary];

formattedContentCardData[@"id"] = card.identifier;
formattedContentCardData[@"created"] = @(card.createdAt);
formattedContentCardData[@"expiresAt"] = @(card.expiresAt);
formattedContentCardData[@"viewed"] = @(card.viewed);
formattedContentCardData[@"clicked"] = @(card.clicked);
formattedContentCardData[@"pinned"] = @(card.pinned);
formattedContentCardData[@"dismissed"] = @(card.removed);
formattedContentCardData[@"dismissible"] = @(card.dismissible);
formattedContentCardData[@"url"] = RCTNullIfNil(card.url ? card.url.absoluteString : nil);
formattedContentCardData[@"openURLInWebView"] = @(card.useWebView);
formattedContentCardData[@"isControl"] = @(NO);

formattedContentCardData[@"extras"] = card.extras ? RCTJSONClean(card.extras) : @{};

switch (card.type) {
case BRZContentCardRawTypeCaptionedImage:
formattedContentCardData[@"image"] = RCTNullIfNil(card.image ? card.image.absoluteString : nil);
formattedContentCardData[@"imageAspectRatio"] = @(card.imageAspectRatio);
formattedContentCardData[@"title"] = card.title;
formattedContentCardData[@"cardDescription"] = card.cardDescription;
formattedContentCardData[@"domain"] = RCTNullIfNil(card.domain);
formattedContentCardData[@"type"] = @"Captioned";
break;
case BRZContentCardRawTypeBanner:
formattedContentCardData[@"image"] = RCTNullIfNil(card.image ? card.image.absoluteString : nil);
formattedContentCardData[@"imageAspectRatio"] = @(card.imageAspectRatio);
formattedContentCardData[@"type"] = @"Banner";
break;
case BRZContentCardRawTypeClassic:
formattedContentCardData[@"image"] = RCTNullIfNil(card.image ? card.image.absoluteString : nil);
formattedContentCardData[@"title"] = card.title;
formattedContentCardData[@"cardDescription"] = card.cardDescription;
formattedContentCardData[@"domain"] = RCTNullIfNil(card.domain);
formattedContentCardData[@"type"] = @"Classic";
break;
case BRZContentCardRawTypeControl:
formattedContentCardData[@"isControl"] = @(YES);
break;
}

return formattedContentCardData;
}

#pragma mark - Other bindings

RCT_EXPORT_METHOD(wipeData) {
Expand Down Expand Up @@ -676,12 +757,10 @@ - (void)braze:(Braze * _Nonnull)braze
RCT_EXPORT_METHOD(subscribeToInAppMessage:(BOOL)useBrazeUI)
{
useBrazeUIForInAppMessages = useBrazeUI;

BrazeInAppMessageUI *inAppMessageUI = [[BrazeInAppMessageUI alloc] init];
inAppMessageUI.delegate = self;
braze.inAppMessagePresenter = inAppMessageUI;
((BrazeInAppMessageUI *)braze.inAppMessagePresenter).delegate = self;
}

/// `BrazeInAppMessageUIDelegate` method
- (enum BRZInAppMessageUIDisplayChoice)inAppMessage:(BrazeInAppMessageUI *)ui
displayChoiceForMessage:(BRZInAppMessageRaw *)message {
NSData *inAppMessageData = [message json];
Expand Down Expand Up @@ -741,12 +820,6 @@ - (BRZInAppMessageRaw *)getInAppMessageFromString:(NSString *)inAppMessageJSONSt
return nil;
}

#pragma mark - RCTBridgeDelegate protocol

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
}

RCT_EXPORT_MODULE();

@end
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@braze/react-native-sdk",
"version": "3.0.0",
"version": "4.0.0",
"description": "Braze SDK for React Native.",
"main": "src/index.js",
"types": "src/index.d.ts",
Expand Down Expand Up @@ -59,4 +59,4 @@
"^.+\\.js$": "<rootDir>/node_modules/react-native/jest/preprocessor.js"
}
}
}
}
2 changes: 1 addition & 1 deletion src/braze.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ export class Braze {
}

/**
* Returns a content cards array
* Returns a content cards array after performing a refresh.
* @returns {Promise<ContentCard[]>}
*/
static getContentCards() {
Expand Down
Loading

0 comments on commit 8f935b0

Please sign in to comment.