Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 64 additions & 15 deletions Sources/BranchSDK/Branch.m
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,10 @@ + (void)setAnonID:(NSString *)anonID {
}

- (void)setConsumerProtectionAttributionLevel:(BranchAttributionLevel)level {
[self setConsumerProtectionAttributionLevel:level resetSession:YES];
}

- (void)setConsumerProtectionAttributionLevel:(BranchAttributionLevel)level resetSession:(BOOL)resetSession {
self.preferenceHelper.attributionLevel = level;

[[BranchLogger shared] logVerbose:[NSString stringWithFormat:@"Setting Consumer Protection Attribution Level to %@", level] error:nil];
Expand Down Expand Up @@ -720,8 +724,10 @@ - (void)setConsumerProtectionAttributionLevel:(BranchAttributionLevel)level {
// Set the flag:
[BNCPreferenceHelper sharedInstance].trackingDisabled = NO;

// Initialize a Branch session:
[[Branch getInstance] initUserSessionAndCallCallback:NO sceneIdentifier:nil urlString:nil reset:true];
if (resetSession) {
// Initialize a Branch session:
[[Branch getInstance] initUserSessionAndCallCallback:NO sceneIdentifier:nil urlString:nil reset:true];
}
}
}

Expand Down Expand Up @@ -1220,7 +1226,18 @@ - (void)logoutWithCallback:(callbackWithStatus)callback {
}

- (void)sendServerRequest:(BNCServerRequest*)request {
[self initSafetyCheck];
@synchronized (self) {
if (self.initializationStatus == BNCInitStatusUninitialized) {
NSError *error = [NSError branchErrorWithCode:BNCInitError];
[[BranchLogger shared] logWarning:@"Branch SDK is not initialized, cannot send this request. Please intialize session before calling this API." error:error];
if ([[BNCCallbackMap shared] containsRequest:request]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[BNCCallbackMap shared] callCompletionForRequest:request withSuccessStatus:NO error:error];
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Concurrency / Thread Safety

Issue: Checking containsRequest: on the current thread (which might be a background thread) creates a potential race condition. The request could be removed from the map between the check and the asynchronous execution. Additionally, if BNCCallbackMap is not thread-safe, accessing it from a background thread is risky.

Fix: Move the containsRequest: check inside the dispatch_async block. This ensures the check and execution happen atomically on the main thread and aligns with the pattern used in other hunks.

Impact: Improves thread safety and prevents potential race conditions.

Suggested change
if ([[BNCCallbackMap shared] containsRequest:request]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[BNCCallbackMap shared] callCompletionForRequest:request withSuccessStatus:NO error:error];
});
}
dispatch_async(dispatch_get_main_queue(), ^{
if ([[BNCCallbackMap shared] containsRequest:request]) {
[[BNCCallbackMap shared] callCompletionForRequest:request withSuccessStatus:NO error:error];
}
});

return;
}
}
dispatch_async(self.isolationQueue, ^(){
[self.requestQueue enqueue:request];
[self processNextQueueItem];
Expand Down Expand Up @@ -1298,7 +1315,16 @@ - (BranchLinkProperties *)getLatestReferringBranchLinkProperties {
#pragma mark - Query methods

- (void)lastAttributedTouchDataWithAttributionWindow:(NSInteger)window completion:(void(^) (BranchLastAttributedTouchData * _Nullable latd, NSError * _Nullable error))completion {
[self initSafetyCheck];
@synchronized (self) {
if (self.initializationStatus == BNCInitStatusUninitialized) {
NSError *error = [NSError branchErrorWithCode:BNCInitError];
[[BranchLogger shared] logWarning:@"Branch SDK is not initialized, cannot request LATD. Please intialize session before calling this API." error:error];
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) { completion(nil, error); }
});
return;
}
}
dispatch_async(self.isolationQueue, ^(){
[BranchLastAttributedTouchData requestLastTouchAttributedData:self.serverInterface key:self.class.branchKey attributionWindow:window completion:completion];
});
Expand Down Expand Up @@ -1419,7 +1445,16 @@ - (void)getShortUrlWithParams:(NSDictionary *)params andTags:(NSArray *)tags and
}

- (void)getSpotlightUrlWithParams:(NSDictionary *)params callback:(callbackWithParams)callback {
[self initSafetyCheck];
@synchronized (self) {
if (self.initializationStatus == BNCInitStatusUninitialized) {
NSError *error = [NSError branchErrorWithCode:BNCInitError];
[[BranchLogger shared] logWarning:@"Branch SDK is not initialized, cannot create Spotlight URL. Please intialize session before calling this API." error:error];
dispatch_async(dispatch_get_main_queue(), ^{
if (callback) { callback(nil, error); }
});
return;
}
}
dispatch_async(self.isolationQueue, ^(){
BranchSpotlightUrlRequest *req = [[BranchSpotlightUrlRequest alloc] initWithParams:params callback:callback];
[self.requestQueue enqueue:req];
Expand Down Expand Up @@ -1683,7 +1718,18 @@ - (void)generateShortUrl:(NSArray *)tags
andCampaign:campaign andParams:(NSDictionary *)params
andCallback:(callbackWithUrl)callback {

[self initSafetyCheck];
@synchronized (self) {
if (self.initializationStatus == BNCInitStatusUninitialized) {
NSError *error = [NSError branchErrorWithCode:BNCInitError];
[[BranchLogger shared] logWarning:@"Branch SDK is not initialized, cannot generate short URL. Please intialize session before calling this API." error:error];
if (callback) {
dispatch_async(dispatch_get_main_queue(), ^{
callback(nil, error);
});
}
return;
}
}
dispatch_async(self.isolationQueue, ^(){
BNCLinkData *linkData = [self prepareLinkDataFor:tags
andAlias:alias
Expand Down Expand Up @@ -1891,7 +1937,18 @@ - (BNCLinkData *)prepareLinkDataFor:(NSArray *)tags
#pragma mark - BranchUniversalObject methods

- (void)registerViewWithParams:(NSDictionary *)params andCallback:(callbackWithParams)callback {
[self initSafetyCheck];
@synchronized (self) {
if (self.initializationStatus == BNCInitStatusUninitialized) {
NSError *error = [NSError branchErrorWithCode:BNCInitError];
[[BranchLogger shared] logWarning:@"Branch SDK is not initialized, cannot register view. Please intialize session before calling this API." error:error];
if (callback) {
dispatch_async(dispatch_get_main_queue(), ^{
callback(nil, error);
});
}
return;
}
}
dispatch_async(self.isolationQueue, ^(){
BranchUniversalObject *buo = [[BranchUniversalObject alloc] init];
buo.contentMetadata.customMetadata = (id) params;
Expand Down Expand Up @@ -2151,14 +2208,6 @@ - (void)notifyNativeToInit {
self.cachedInitBlock = nil;
}

// SDK-631 Workaround to maintain existing error handling behavior.
// Some methods require init before they are called. Instead of returning an error, we try to fix the situation by calling init ourselves.
- (void)initSafetyCheck {
if (self.initializationStatus == BNCInitStatusUninitialized) {
[[BranchLogger shared] logDebug:@"Branch avoided an error by preemptively initializing." error:nil];
[self initUserSessionAndCallCallback:NO sceneIdentifier:nil urlString:nil reset:NO];
}
}

- (void)initUserSessionAndCallCallback:(BOOL)callCallback sceneIdentifier:(NSString *)sceneIdentifier urlString:(NSString *)urlString reset:(BOOL)reset {

Expand Down
9 changes: 9 additions & 0 deletions Sources/BranchSDK/Public/Branch.h
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,15 @@ extern BranchAttributionLevel const BranchAttributionLevelNone;
*/
- (void)setConsumerProtectionAttributionLevel:(BranchAttributionLevel)level;

/**
Sets the consumer protection attribution level with an option to reset the session.

@param level The desired consumer protection attribution level, represented by the BranchAttributionLevel enum (Full, Reduced, Minimal, None).
@param resetSession If YES, a new session will be initialized when transitioning from BranchAttributionLevelNone to other higher levels.
If NO, the session will not be re-initialized automatically when transitioning from BranchAttributionLevelNone to other higher levels.
*/
- (void)setConsumerProtectionAttributionLevel:(BranchAttributionLevel)level resetSession:(BOOL)resetSession;


#pragma mark - Session Item methods

Expand Down
10 changes: 4 additions & 6 deletions Sources/BranchSDK/Public/BranchEvent.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,20 +97,18 @@ typedef NS_ENUM(NSInteger, BranchEventAdType) {
/**
Logs the event on the Branch server.
This version will callback on success/failure.

This method should only be invoked after initSession.
If it is invoked before, then we will silently initialize the SDK before the callback has been set, in order to carry out this method's required task.
As a result, you may experience issues where the initSession callback does not fire. Again, the solution to this issue is to only invoke this method after you have invoked initSession.
If invoked before initSession, the event will be dropped and a BNCInitError will be returned.
*/
- (void)logEventWithCompletion:(void (^_Nullable)(BOOL success, NSError * _Nullable error))completion;

/**
Logs the event on the Branch server.
This version automatically caches and retries as necessary.

This method should only be invoked after initSession.
If it is invoked before, then we will silently initialize the SDK before the callback has been set, in order to carry out this method's required task.
As a result, you may experience issues where the initSession callback does not fire. Again, the solution to this issue is to only invoke this method after you have invoked initSession.
If invoked before initSession, the event will be dropped.
*/
- (void)logEvent;

Expand Down