GT2/GT2-iOS/node_modules/react-native-branch/ios/RNBranch.m

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