472 lines
19 KiB
Objective-C
472 lines
19 KiB
Objective-C
#import "RNBranch.h"
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTEventDispatcher.h>
|
|
#import <React/RCTLog.h>
|
|
#import "BranchLinkProperties+RNBranch.h"
|
|
#import "BranchUniversalObject+RNBranch.h"
|
|
#import "RNBranchAgingDictionary.h"
|
|
#import "RNBranchEventEmitter.h"
|
|
|
|
NSString * const RNBranchLinkOpenedNotification = @"RNBranchLinkOpenedNotification";
|
|
NSString * const RNBranchLinkOpenedNotificationErrorKey = @"error";
|
|
NSString * const RNBranchLinkOpenedNotificationParamsKey = @"params";
|
|
NSString * const RNBranchLinkOpenedNotificationUriKey = @"uri";
|
|
NSString * const RNBranchLinkOpenedNotificationBranchUniversalObjectKey = @"branch_universal_object";
|
|
NSString * const RNBranchLinkOpenedNotificationLinkPropertiesKey = @"link_properties";
|
|
|
|
static NSDictionary *initSessionWithLaunchOptionsResult;
|
|
static NSURL *sourceUrl;
|
|
static Branch *branchInstance;
|
|
|
|
static NSString * const IdentFieldName = @"ident";
|
|
|
|
// These are only really exposed to the JS layer, so keep them internal for now.
|
|
static NSString * const RNBranchErrorDomain = @"RNBranchErrorDomain";
|
|
static NSInteger const RNBranchUniversalObjectNotFoundError = 1;
|
|
|
|
#pragma mark - Private RNBranch declarations
|
|
|
|
@interface RNBranch()
|
|
@property (nonatomic, readonly) UIViewController *currentViewController;
|
|
@property (nonatomic) RNBranchAgingDictionary<NSString *, BranchUniversalObject *> *universalObjectMap;
|
|
@end
|
|
|
|
#pragma mark - RNBranch implementation
|
|
|
|
@implementation RNBranch
|
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
RCT_EXPORT_MODULE();
|
|
|
|
- (NSDictionary<NSString *, NSString *> *)constantsToExport {
|
|
return @{
|
|
// RN events transmitted to JS by event emitter
|
|
@"INIT_SESSION_SUCCESS": kRNBranchInitSessionSuccess,
|
|
@"INIT_SESSION_ERROR": kRNBranchInitSessionError,
|
|
|
|
// constants for use with userCompletedAction
|
|
@"ADD_TO_CART_EVENT": BNCAddToCartEvent,
|
|
@"ADD_TO_WISHLIST_EVENT": BNCAddToWishlistEvent,
|
|
@"PURCHASED_EVENT": BNCPurchasedEvent,
|
|
@"PURCHASE_INITIATED_EVENT": BNCPurchaseInitiatedEvent,
|
|
@"REGISTER_VIEW_EVENT": BNCRegisterViewEvent,
|
|
@"SHARE_COMPLETED_EVENT": BNCShareCompletedEvent,
|
|
@"SHARE_INITIATED_EVENT": BNCShareInitiatedEvent
|
|
};
|
|
}
|
|
|
|
#pragma mark - Class methods
|
|
|
|
+ (void)useTestInstance {
|
|
branchInstance = [Branch getTestInstance];
|
|
}
|
|
|
|
//Called by AppDelegate.m -- stores initSession result in static variables and raises initSessionFinished event that's captured by the RNBranch instance to emit it to React Native
|
|
+ (void)initSessionWithLaunchOptions:(NSDictionary *)launchOptions isReferrable:(BOOL)isReferrable {
|
|
sourceUrl = launchOptions[UIApplicationLaunchOptionsURLKey];
|
|
|
|
if (!branchInstance) {
|
|
branchInstance = [Branch getInstance];
|
|
}
|
|
[branchInstance initSessionWithLaunchOptions:launchOptions isReferrable:isReferrable andRegisterDeepLinkHandler:^(NSDictionary *params, NSError *error) {
|
|
NSMutableDictionary *result = [NSMutableDictionary dictionary];
|
|
if (error) result[RNBranchLinkOpenedNotificationErrorKey] = error;
|
|
if (params) {
|
|
result[RNBranchLinkOpenedNotificationParamsKey] = params;
|
|
|
|
if (params[@"~id"]) {
|
|
BranchUniversalObject *branchUniversalObject = [BranchUniversalObject getBranchUniversalObjectFromDictionary:params];
|
|
if (branchUniversalObject) result[RNBranchLinkOpenedNotificationBranchUniversalObjectKey] = branchUniversalObject;
|
|
|
|
BranchLinkProperties *linkProperties = [BranchLinkProperties getBranchLinkPropertiesFromDictionary:params];
|
|
if (linkProperties) result[RNBranchLinkOpenedNotificationLinkPropertiesKey] = linkProperties;
|
|
}
|
|
}
|
|
if (sourceUrl) result[RNBranchLinkOpenedNotificationUriKey] = sourceUrl;
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RNBranchLinkOpenedNotification object:nil userInfo:result];
|
|
}];
|
|
}
|
|
|
|
+ (BOOL)handleDeepLink:(NSURL *)url {
|
|
sourceUrl = url;
|
|
BOOL handled = [branchInstance handleDeepLink:url];
|
|
return handled;
|
|
}
|
|
|
|
+ (BOOL)continueUserActivity:(NSUserActivity *)userActivity {
|
|
sourceUrl = userActivity.webpageURL;
|
|
return [branchInstance continueUserActivity:userActivity];
|
|
}
|
|
|
|
#pragma mark - Object lifecycle
|
|
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
|
|
if (self) {
|
|
_universalObjectMap = [RNBranchAgingDictionary dictionaryWithTtl:3600.0];
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onInitSessionFinished:) name:RNBranchLinkOpenedNotification object:nil];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void) dealloc {
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self];
|
|
}
|
|
|
|
#pragma mark - Utility methods
|
|
|
|
- (UIViewController *)currentViewController
|
|
{
|
|
UIViewController *current = [UIApplication sharedApplication].keyWindow.rootViewController;
|
|
while (current.presentedViewController && ![current.presentedViewController isKindOfClass:UIAlertController.class]) {
|
|
current = current.presentedViewController;
|
|
}
|
|
return current;
|
|
}
|
|
|
|
- (void) onInitSessionFinished:(NSNotification*) notification {
|
|
NSURL *uri = notification.userInfo[RNBranchLinkOpenedNotificationUriKey];
|
|
NSError *error = notification.userInfo[RNBranchLinkOpenedNotificationErrorKey];
|
|
NSDictionary *params = notification.userInfo[RNBranchLinkOpenedNotificationParamsKey];
|
|
|
|
initSessionWithLaunchOptionsResult = @{
|
|
RNBranchLinkOpenedNotificationErrorKey: error.localizedDescription ?: NSNull.null,
|
|
RNBranchLinkOpenedNotificationParamsKey: params[@"~id"] ? params : NSNull.null,
|
|
RNBranchLinkOpenedNotificationUriKey: uri.absoluteString ?: NSNull.null
|
|
};
|
|
|
|
// If there is an error, fire error event
|
|
if (error) {
|
|
[RNBranchEventEmitter initSessionDidEncounterErrorWithPayload:initSessionWithLaunchOptionsResult];
|
|
}
|
|
|
|
// otherwise notify the session is finished
|
|
else {
|
|
[RNBranchEventEmitter initSessionDidSucceedWithPayload:initSessionWithLaunchOptionsResult];
|
|
}
|
|
}
|
|
|
|
- (BranchLinkProperties*) createLinkProperties:(NSDictionary *)linkPropertiesMap withControlParams:(NSDictionary *)controlParamsMap
|
|
{
|
|
BranchLinkProperties *linkProperties = [[BranchLinkProperties alloc] initWithMap:linkPropertiesMap];
|
|
|
|
linkProperties.controlParams = controlParamsMap;
|
|
return linkProperties;
|
|
}
|
|
|
|
- (BranchUniversalObject *)findUniversalObjectWithIdent:(NSString *)ident rejecter:(RCTPromiseRejectBlock)reject
|
|
{
|
|
BranchUniversalObject *universalObject = self.universalObjectMap[ident];
|
|
|
|
if (!universalObject) {
|
|
NSString *errorMessage = [NSString stringWithFormat:@"BranchUniversalObject for ident %@ not found.", ident];
|
|
|
|
NSError *error = [NSError errorWithDomain:RNBranchErrorDomain
|
|
code:RNBranchUniversalObjectNotFoundError
|
|
userInfo:@{IdentFieldName : ident,
|
|
NSLocalizedDescriptionKey: errorMessage
|
|
}];
|
|
|
|
reject(@"RNBranch::Error::BUONotFound", errorMessage, error);
|
|
}
|
|
|
|
return universalObject;
|
|
}
|
|
|
|
#pragma mark - Methods exported to React Native
|
|
|
|
#pragma mark redeemInitSessionResult
|
|
RCT_EXPORT_METHOD(
|
|
redeemInitSessionResult:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(__unused RCTPromiseRejectBlock)reject
|
|
) {
|
|
resolve(initSessionWithLaunchOptionsResult ? initSessionWithLaunchOptionsResult : [NSNull null]);
|
|
}
|
|
|
|
#pragma mark setDebug
|
|
RCT_EXPORT_METHOD(
|
|
setDebug
|
|
) {
|
|
[branchInstance setDebug];
|
|
}
|
|
|
|
#pragma mark getLatestReferringParams
|
|
RCT_EXPORT_METHOD(
|
|
getLatestReferringParams:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(__unused RCTPromiseRejectBlock)reject
|
|
) {
|
|
resolve([branchInstance getLatestReferringParams]);
|
|
}
|
|
|
|
#pragma mark getFirstReferringParams
|
|
RCT_EXPORT_METHOD(
|
|
getFirstReferringParams:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(__unused RCTPromiseRejectBlock)reject
|
|
) {
|
|
resolve([branchInstance getFirstReferringParams]);
|
|
}
|
|
|
|
#pragma mark setIdentity
|
|
RCT_EXPORT_METHOD(
|
|
setIdentity:(NSString *)identity
|
|
) {
|
|
[branchInstance setIdentity:identity];
|
|
}
|
|
|
|
#pragma mark logout
|
|
RCT_EXPORT_METHOD(
|
|
logout
|
|
) {
|
|
[branchInstance logout];
|
|
}
|
|
|
|
#pragma mark userCompletedAction
|
|
RCT_EXPORT_METHOD(
|
|
userCompletedAction:(NSString *)event withState:(NSDictionary *)appState
|
|
) {
|
|
[branchInstance userCompletedAction:event withState:appState];
|
|
}
|
|
|
|
#pragma mark userCompletedActionOnUniversalObject
|
|
RCT_EXPORT_METHOD(
|
|
userCompletedActionOnUniversalObject:(NSString *)identifier
|
|
event:(NSString *)event
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject
|
|
) {
|
|
BranchUniversalObject *branchUniversalObject = [self findUniversalObjectWithIdent:identifier rejecter:reject];
|
|
if (!branchUniversalObject) return;
|
|
|
|
[branchUniversalObject userCompletedAction:event];
|
|
resolve(NSNull.null);
|
|
}
|
|
|
|
#pragma mark userCompletedActionOnUniversalObject
|
|
RCT_EXPORT_METHOD(
|
|
userCompletedActionOnUniversalObject:(NSString *)identifier
|
|
event:(NSString *)event
|
|
state:(NSDictionary *)state
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject
|
|
) {
|
|
BranchUniversalObject *branchUniversalObject = [self findUniversalObjectWithIdent:identifier rejecter:reject];
|
|
if (!branchUniversalObject) return;
|
|
|
|
[branchUniversalObject userCompletedAction:event withState:state];
|
|
resolve(NSNull.null);
|
|
}
|
|
|
|
#pragma mark showShareSheet
|
|
RCT_EXPORT_METHOD(
|
|
showShareSheet:(NSString *)identifier
|
|
withShareOptions:(NSDictionary *)shareOptionsMap
|
|
withLinkProperties:(NSDictionary *)linkPropertiesMap
|
|
withControlParams:(NSDictionary *)controlParamsMap
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject
|
|
) {
|
|
BranchUniversalObject *branchUniversalObject = [self findUniversalObjectWithIdent:identifier rejecter:reject];
|
|
if (!branchUniversalObject) return;
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
NSMutableDictionary *mutableControlParams = controlParamsMap.mutableCopy;
|
|
if (shareOptionsMap && shareOptionsMap[@"emailSubject"]) {
|
|
mutableControlParams[@"$email_subject"] = shareOptionsMap[@"emailSubject"];
|
|
}
|
|
|
|
BranchLinkProperties *linkProperties = [self createLinkProperties:linkPropertiesMap withControlParams:mutableControlParams];
|
|
|
|
[branchUniversalObject showShareSheetWithLinkProperties:linkProperties
|
|
andShareText:shareOptionsMap[@"messageBody"]
|
|
fromViewController:self.currentViewController
|
|
completionWithError:^(NSString * _Nullable activityType, BOOL completed, NSError * _Nullable activityError){
|
|
if (activityError) {
|
|
NSString *errorCodeString = [NSString stringWithFormat:@"%ld", (long)activityError.code];
|
|
reject(errorCodeString, activityError.localizedDescription, activityError);
|
|
return;
|
|
}
|
|
|
|
NSDictionary *result = @{
|
|
@"channel" : activityType ?: [NSNull null],
|
|
@"completed" : @(completed),
|
|
@"error" : [NSNull null]
|
|
};
|
|
|
|
resolve(result);
|
|
}];
|
|
});
|
|
}
|
|
|
|
#pragma mark registerView
|
|
RCT_EXPORT_METHOD(
|
|
registerView:(NSString *)identifier
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject
|
|
) {
|
|
BranchUniversalObject *branchUniversalObject = [self findUniversalObjectWithIdent:identifier rejecter:reject];
|
|
if (!branchUniversalObject) return;
|
|
|
|
[branchUniversalObject registerViewWithCallback:^(NSDictionary *params, NSError *error) {
|
|
if (!error) {
|
|
resolve([NSNull null]);
|
|
} else {
|
|
reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error);
|
|
}
|
|
}];
|
|
}
|
|
|
|
#pragma mark generateShortUrl
|
|
RCT_EXPORT_METHOD(
|
|
generateShortUrl:(NSString *)identifier
|
|
withLinkProperties:(NSDictionary *)linkPropertiesMap
|
|
withControlParams:(NSDictionary *)controlParamsMap
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject
|
|
) {
|
|
BranchUniversalObject *branchUniversalObject = [self findUniversalObjectWithIdent:identifier rejecter:reject];
|
|
if (!branchUniversalObject) return;
|
|
|
|
BranchLinkProperties *linkProperties = [self createLinkProperties:linkPropertiesMap withControlParams:controlParamsMap];
|
|
|
|
[branchUniversalObject getShortUrlWithLinkProperties:linkProperties andCallback:^(NSString *url, NSError *error) {
|
|
if (!error) {
|
|
RCTLogInfo(@"RNBranch Success");
|
|
resolve(@{ @"url": url });
|
|
} else {
|
|
reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error);
|
|
}
|
|
}];
|
|
}
|
|
|
|
#pragma mark listOnSpotlight
|
|
RCT_EXPORT_METHOD(
|
|
listOnSpotlight:(NSString *)identifier
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject
|
|
) {
|
|
BranchUniversalObject *branchUniversalObject = [self findUniversalObjectWithIdent:identifier rejecter:reject];
|
|
if (!branchUniversalObject) return;
|
|
|
|
[branchUniversalObject listOnSpotlightWithCallback:^(NSString *string, NSError *error) {
|
|
if (!error) {
|
|
NSDictionary *data = @{@"result":string};
|
|
resolve(data);
|
|
} else {
|
|
reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error);
|
|
}
|
|
}];
|
|
}
|
|
|
|
// @TODO can this be removed? legacy, short url should be created from BranchUniversalObject
|
|
#pragma mark getShortUrl
|
|
RCT_EXPORT_METHOD(
|
|
getShortUrl:(NSDictionary *)linkPropertiesMap
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject
|
|
) {
|
|
NSString *feature = linkPropertiesMap[@"feature"];
|
|
NSString *channel = linkPropertiesMap[@"channel"];
|
|
NSString *stage = linkPropertiesMap[@"stage"];
|
|
NSArray *tags = linkPropertiesMap[@"tags"];
|
|
|
|
[branchInstance getShortURLWithParams:linkPropertiesMap
|
|
andTags:tags
|
|
andChannel:channel
|
|
andFeature:feature
|
|
andStage:stage
|
|
andCallback:^(NSString *url, NSError *error) {
|
|
if (error) {
|
|
RCTLogError(@"RNBranch::Error: %@", error.localizedDescription);
|
|
reject(@"RNBranch::Error", @"getShortURLWithParams", error);
|
|
}
|
|
resolve(url);
|
|
}];
|
|
}
|
|
|
|
#pragma mark loadRewards
|
|
RCT_EXPORT_METHOD(
|
|
loadRewards:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject
|
|
) {
|
|
[branchInstance loadRewardsWithCallback:^(BOOL changed, NSError *error) {
|
|
if(!error) {
|
|
int credits = (int)[branchInstance getCredits];
|
|
resolve(@{@"credits": @(credits)});
|
|
} else {
|
|
RCTLogError(@"Load Rewards Error: %@", error.localizedDescription);
|
|
reject(@"RNBranch::Error::loadRewardsWithCallback", @"loadRewardsWithCallback", error);
|
|
}
|
|
}];
|
|
}
|
|
|
|
#pragma mark redeemRewards
|
|
RCT_EXPORT_METHOD(
|
|
redeemRewards:(NSInteger)amount
|
|
inBucket:(NSString *)bucket
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject
|
|
) {
|
|
if (bucket) {
|
|
[branchInstance redeemRewards:amount forBucket:bucket callback:^(BOOL changed, NSError *error) {
|
|
if (!error) {
|
|
resolve(@{@"changed": @(changed)});
|
|
} else {
|
|
RCTLogError(@"Redeem Rewards Error: %@", error.localizedDescription);
|
|
reject(@"RNBranch::Error::redeemRewards", error.localizedDescription, error);
|
|
}
|
|
}];
|
|
} else {
|
|
[branchInstance redeemRewards:amount callback:^(BOOL changed, NSError *error) {
|
|
if (!error) {
|
|
resolve(@{@"changed": @(changed)});
|
|
} else {
|
|
RCTLogError(@"Redeem Rewards Error: %@", error.localizedDescription);
|
|
reject(@"RNBranch::Error::redeemRewards", error.localizedDescription, error);
|
|
}
|
|
}];
|
|
}
|
|
}
|
|
|
|
#pragma mark getCreditHistory
|
|
RCT_EXPORT_METHOD(
|
|
getCreditHistory:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(RCTPromiseRejectBlock)reject
|
|
) {
|
|
[branchInstance getCreditHistoryWithCallback:^(NSArray *list, NSError *error) {
|
|
if (!error) {
|
|
resolve(list);
|
|
} else {
|
|
RCTLogError(@"Credit History Error: %@", error.localizedDescription);
|
|
reject(@"RNBranch::Error::getCreditHistory", error.localizedDescription, error);
|
|
}
|
|
}];
|
|
}
|
|
|
|
#pragma mark createUniversalObject
|
|
RCT_EXPORT_METHOD(
|
|
createUniversalObject:(NSDictionary *)universalObjectProperties
|
|
resolver:(RCTPromiseResolveBlock)resolve
|
|
rejecter:(__unused RCTPromiseRejectBlock)reject
|
|
) {
|
|
BranchUniversalObject *universalObject = [[BranchUniversalObject alloc] initWithMap:universalObjectProperties];
|
|
NSString *identifier = [NSUUID UUID].UUIDString;
|
|
self.universalObjectMap[identifier] = universalObject;
|
|
NSDictionary *response = @{IdentFieldName: identifier};
|
|
|
|
resolve(response);
|
|
}
|
|
|
|
#pragma mark releaseUniversalObject
|
|
RCT_EXPORT_METHOD(
|
|
releaseUniversalObject:(NSString *)identifier
|
|
) {
|
|
[self.universalObjectMap removeObjectForKey:identifier];
|
|
}
|
|
|
|
@end
|