diff --git a/Branch-TestBed/Branch-SDK-Tests/BranchClassTests.m b/Branch-TestBed/Branch-SDK-Tests/BranchClassTests.m index 9d337a1..d1e7713 100644 --- a/Branch-TestBed/Branch-SDK-Tests/BranchClassTests.m +++ b/Branch-TestBed/Branch-SDK-Tests/BranchClassTests.m @@ -241,4 +241,25 @@ - (void)testSetDMAParamsForEEA { [[BNCPreferenceHelper sharedInstance] writeObjectToDefaults:@"bnc_dma_ad_user_data" value:nil]; } +- (void)testSetConsumerProtectionAttributionLevel { + // Set to Reduced and check + Branch *branch = [Branch getInstance]; + [branch setConsumerProtectionAttributionLevel:BranchAttributionLevelReduced]; + XCTAssertEqual([BNCPreferenceHelper sharedInstance].attributionLevel, BranchAttributionLevelReduced); + + // Set to Minimal and check + [branch setConsumerProtectionAttributionLevel:BranchAttributionLevelMinimal]; + XCTAssertEqual([BNCPreferenceHelper sharedInstance].attributionLevel, BranchAttributionLevelMinimal); + + // Set to None and check + [branch setConsumerProtectionAttributionLevel:BranchAttributionLevelNone]; + XCTAssertEqual([BNCPreferenceHelper sharedInstance].attributionLevel, BranchAttributionLevelNone); + + // Set to Full and check + [branch setConsumerProtectionAttributionLevel:BranchAttributionLevelFull]; + XCTAssertEqual([BNCPreferenceHelper sharedInstance].attributionLevel, BranchAttributionLevelFull); + +} + + @end diff --git a/Branch-TestBed/Branch-TestBed/AppDelegate.m b/Branch-TestBed/Branch-TestBed/AppDelegate.m index a8c160c..aedf355 100644 --- a/Branch-TestBed/Branch-TestBed/AppDelegate.m +++ b/Branch-TestBed/Branch-TestBed/AppDelegate.m @@ -21,6 +21,8 @@ @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [self setBranchLogFile]; + appDelegate = self; /* @@ -32,20 +34,28 @@ - (BOOL)application:(UIApplication *)application // Branch.useTestBranchKey = YES; // Make sure to comment this line out for production apps!!! Branch *branch = [Branch getInstance]; + // Change the Branch base API URL //[Branch setAPIUrl:@"https://api3.branch.io"]; // test pre init support //[self testDispatchToIsolationQueue:branch] - [branch enableLoggingAtLevel:BranchLogLevelVerbose withCallback:^(NSString * _Nonnull message, BranchLogLevel logLevel, NSError * _Nullable error) { + + + [Branch enableLoggingAtLevel:BranchLogLevelVerbose withCallback:^(NSString * _Nonnull message, BranchLogLevel logLevel, NSError * _Nullable error) { // Handle the log message and error here. For example, printing to the console: if (error) { NSLog(@"[BranchLog] Level: %lu, Message: %@, Error: %@", (unsigned long)logLevel, message, error.localizedDescription); } else { NSLog(@"[BranchLog] Level: %lu, Message: %@", (unsigned long)logLevel, message); } + + NSString *logEntry = error ? [NSString stringWithFormat:@"Level: %lu, Message: %@, Error: %@", (unsigned long)logLevel, message, error.localizedDescription] + : [NSString stringWithFormat:@"Level: %lu, Message: %@", (unsigned long)logLevel, message]; + APPLogHookFunction([NSDate date], logLevel, logEntry); }]; + // Comment out in production. Un-comment to test your Branch SDK Integration: //[branch validateSDKIntegration]; @@ -58,14 +68,16 @@ - (BOOL)application:(UIApplication *)application * Required: Initialize Branch, passing a deep link handler block: */ - [self setLogFile:@"OpenNInstall"]; + //[self setLogFile:@"OpenNInstall"]; [branch setIdentity:@"Bobby Branch"]; + //[[Branch getInstance] setConsumerProtectionAttributionLevel:BranchAttributionLevelReduced]; + [branch initSessionWithLaunchOptions:launchOptions andRegisterDeepLinkHandlerUsingBranchUniversalObject: ^ (BranchUniversalObject * _Nullable universalObject, BranchLinkProperties * _Nullable linkProperties, NSError * _Nullable error) { - [self setLogFile:nil]; + //[self setLogFile:nil]; [self handleDeepLinkObject:universalObject linkProperties:linkProperties error:error]; }]; @@ -134,7 +146,7 @@ - (void) handleDeepLinkObject:(BranchUniversalObject*)object [storyboard instantiateViewControllerWithIdentifier:@"LogOutputViewController"]; [navigationController pushViewController:logOutputViewController animated:YES]; NSString *logOutput = - [NSString stringWithFormat:@"Successfully Deeplinked:\n\n%@\nSession Details:\n\n%@", + [NSString stringWithFormat:@"Successfully Deeplinked!\n\nCustom Metadata Deeplink Text: %@\n\nSession Details:\n\n%@", deeplinkText, [[[Branch getInstance] getLatestReferringParams] description]]; logOutputViewController.logOutput = logOutput; } @@ -175,6 +187,19 @@ - (BOOL)application:(UIApplication *)application return YES; } +- (void)setBranchLogFile { + NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; + NSString *logFilePath = [documentsDirectory stringByAppendingPathComponent:@"branchlogs.txt"]; + + // If the log file already exists, remove it to start fresh + if ([[NSFileManager defaultManager] fileExistsAtPath:logFilePath]) { + [[NSFileManager defaultManager] removeItemAtPath:logFilePath error:nil]; + } + + self.logFileName = logFilePath; +} + + #pragma mark - Push Notifications (Optional) /* // Helper method @@ -213,10 +238,11 @@ -(void)application:(UIApplication *)application // hook Function for SDK - Its for taking control of Logging messages. void APPLogHookFunction(NSDate*_Nonnull timestamp, BranchLogLevel level, NSString*_Nullable message) { - [appDelegate processLogMessage:message]; + NSString *formattedMessage = [NSString stringWithFormat:@"%@ [%lu] %@", timestamp, (unsigned long)level, message]; + [appDelegate processLogMessage:formattedMessage]; } -// Writes message to log File. +// Writes message to Log File. - (void) processLogMessage:(NSString *)message { if (!self.logFileName) @@ -228,13 +254,12 @@ - (void) processLogMessage:(NSString *)message { [fileHandle seekToEndOfFile]; [fileHandle writeData:[message dataUsingEncoding:NSUTF8StringEncoding]]; [fileHandle closeFile]; - } else { // Create file if it doesnt exist + } else { [message writeToFile:self.logFileName atomically:NO encoding:NSStringEncodingConversionAllowLossy error:nil]; } - NSLog(@"%@", message); // Log mmessages to console - remove if required. } } diff --git a/Branch-TestBed/Branch-TestBed/LogOutputViewController.m b/Branch-TestBed/Branch-TestBed/LogOutputViewController.m index aee1033..76a88af 100644 --- a/Branch-TestBed/Branch-TestBed/LogOutputViewController.m +++ b/Branch-TestBed/Branch-TestBed/LogOutputViewController.m @@ -17,6 +17,12 @@ @implementation LogOutputViewController - (void)viewDidLoad { [super viewDidLoad]; self.logOutputTextView.text = _logOutput; + + UIBarButtonItem *clearButton = [[UIBarButtonItem alloc] initWithTitle:@"Clear Logs" + style:UIBarButtonItemStylePlain + target:self + action:@selector(clearLogs)]; + self.navigationItem.rightBarButtonItem = clearButton; } - (void)didReceiveMemoryWarning { @@ -24,4 +30,23 @@ - (void)didReceiveMemoryWarning { // Dispose of any resources that can be recreated. } +- (void)clearLogs { + NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]; + NSString *logFilePath = [documentsDirectory stringByAppendingPathComponent:@"branchlogs.txt"]; + + NSError *error = nil; + [[NSFileManager defaultManager] removeItemAtPath:logFilePath error:&error]; + + if (error) { + NSLog(@"Error clearing log file: %@", error.localizedDescription); + } else { + self.logOutputTextView.text = @"Logs cleared."; + NSLog(@"Log file cleared successfully."); + + [self.navigationController popViewControllerAnimated:YES]; + + } +} + + @end diff --git a/Branch-TestBed/Branch-TestBed/Main.storyboard b/Branch-TestBed/Branch-TestBed/Main.storyboard index 21f8155..e9ca1da 100644 --- a/Branch-TestBed/Branch-TestBed/Main.storyboard +++ b/Branch-TestBed/Branch-TestBed/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -21,7 +21,7 @@ - + - + @@ -139,7 +139,7 @@ - + @@ -535,7 +535,7 @@ - + + + + + + + + + @@ -832,7 +861,7 @@ - + @@ -848,10 +877,11 @@ + - + @@ -859,25 +889,25 @@ - - - - + + + + - + - + - + - + diff --git a/Branch-TestBed/Branch-TestBed/ViewController.m b/Branch-TestBed/Branch-TestBed/ViewController.m index da170e6..994b689 100644 --- a/Branch-TestBed/Branch-TestBed/ViewController.m +++ b/Branch-TestBed/Branch-TestBed/ViewController.m @@ -156,6 +156,41 @@ - (IBAction)goToPasteControlPressed:(id)sender { sender:self]; } +- (IBAction)changeConsumerProtectionAttributionLevel:(id)sender { + Branch *branch = [Branch getInstance]; + UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:@"Select Consumer Protection Attribution Level" message:nil preferredStyle:UIAlertControllerStyleActionSheet]; + + UIAlertAction *fullAction = [UIAlertAction actionWithTitle:@"Full" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [branch setConsumerProtectionAttributionLevel:BranchAttributionLevelFull]; + [self showAlert:@"Consumer Protection Attribution Level set to Full" withDescription:@""]; + }]; + + UIAlertAction *privacyOnlyAction = [UIAlertAction actionWithTitle:@"Reduced" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [branch setConsumerProtectionAttributionLevel:BranchAttributionLevelReduced]; + [self showAlert:@"Consumer Protection Attribution Level set to Reduced" withDescription:@""]; + }]; + + UIAlertAction *attributionOnlyAction = [UIAlertAction actionWithTitle:@"Minimal" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [branch setConsumerProtectionAttributionLevel:BranchAttributionLevelMinimal]; + [self showAlert:@"Consumer Protection Attribution Level set to Minimal" withDescription:@""]; + }]; + + UIAlertAction *noAttributionAction = [UIAlertAction actionWithTitle:@"None" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { + [branch setConsumerProtectionAttributionLevel:BranchAttributionLevelNone]; + [self showAlert:@"Consumer Protection Attribution Level set to None" withDescription:@""]; + }]; + + UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]; + + [actionSheet addAction:fullAction]; + [actionSheet addAction:privacyOnlyAction]; + [actionSheet addAction:attributionOnlyAction]; + [actionSheet addAction:noAttributionAction]; + [actionSheet addAction:cancelAction]; + + [self presentViewController:actionSheet animated:YES completion:nil]; +} + -(IBAction)showVersionAlert:(id)sender { NSString *versionString = [NSString stringWithFormat:@"Branch SDK v%@\nBundle Version %@\niOS %@", BNC_SDK_VERSION, @@ -793,10 +828,11 @@ - (IBAction)loadLogs:(id)sender { [storyboard instantiateViewControllerWithIdentifier:@"LogOutputViewController"]; [navigationController pushViewController:logOutputViewController animated:YES]; - NSString *logFileContents = [NSString stringWithContentsOfFile:appDelegate.PrevCommandLogFileName encoding:NSUTF8StringEncoding error:nil]; + //NSString *logFileContents = [NSString stringWithContentsOfFile:appDelegate.PrevCommandLogFileName encoding:NSUTF8StringEncoding error:nil]; + NSString *logFileContents = [NSString stringWithContentsOfFile:appDelegate.logFileName encoding:NSUTF8StringEncoding error:nil]; + NSLog(@"Got log file (%@) contents: %@", appDelegate.logFileName, logFileContents); logOutputViewController.logOutput = [NSString stringWithFormat:@"%@", logFileContents]; - } - (IBAction)disableTracking:(id)sender { diff --git a/BranchSDK.podspec b/BranchSDK.podspec index e939549..57959bc 100644 --- a/BranchSDK.podspec +++ b/BranchSDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "BranchSDK" - s.version = "3.6.5" + s.version = "3.7.0" s.summary = "Create an HTTP URL for any piece of content in your app" s.description = <<-DESC - Want the highest possible conversions on your sharing feature? diff --git a/BranchSDK.xcodeproj/project.pbxproj b/BranchSDK.xcodeproj/project.pbxproj index 4472d52..41d7948 100644 --- a/BranchSDK.xcodeproj/project.pbxproj +++ b/BranchSDK.xcodeproj/project.pbxproj @@ -1974,7 +1974,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 3.6.5; + MARKETING_VERSION = 3.7.0; OTHER_LDFLAGS = ( "-weak_framework", LinkPresentation, @@ -2009,7 +2009,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 3.6.5; + MARKETING_VERSION = 3.7.0; OTHER_LDFLAGS = ( "-weak_framework", LinkPresentation, @@ -2215,7 +2215,7 @@ "@loader_path/Frameworks", ); MACH_O_TYPE = staticlib; - MARKETING_VERSION = 3.6.5; + MARKETING_VERSION = 3.7.0; OTHER_LDFLAGS = ( "-weak_framework", LinkPresentation, @@ -2254,7 +2254,7 @@ "@loader_path/Frameworks", ); MACH_O_TYPE = staticlib; - MARKETING_VERSION = 3.6.5; + MARKETING_VERSION = 3.7.0; OTHER_LDFLAGS = ( "-weak_framework", LinkPresentation, @@ -2291,7 +2291,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 3.6.5; + MARKETING_VERSION = 3.7.0; OTHER_LDFLAGS = ( "-weak_framework", LinkPresentation, @@ -2326,7 +2326,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 3.6.5; + MARKETING_VERSION = 3.7.0; OTHER_LDFLAGS = ( "-weak_framework", LinkPresentation, diff --git a/ChangeLog.md b/ChangeLog.md index 8996557..2835d28 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,9 @@ Branch iOS SDK Change Log +v.3.7.0 +- Added `setConsumerProtectionAttributionLevel` for controlling attribution data collection +- Deprecated `setTrackingDisabled` in favor of `setConsumerProtectionAttributionLevel(NONE)` + v.3.6.5 - Removed on-disk caching and replay of server request objects. diff --git a/DeepLinkDemo/DeepLinkDemo.xcodeproj/project.pbxproj b/DeepLinkDemo/DeepLinkDemo.xcodeproj/project.pbxproj index 5820cf5..6117713 100644 --- a/DeepLinkDemo/DeepLinkDemo.xcodeproj/project.pbxproj +++ b/DeepLinkDemo/DeepLinkDemo.xcodeproj/project.pbxproj @@ -73,7 +73,6 @@ B7B7DC292859974E00D45FC5 /* TextViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewController.swift; sourceTree = ""; }; C06885D21868B25319262FC1 /* Pods-DeepLinkDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-DeepLinkDemo.debug.xcconfig"; path = "Target Support Files/Pods-DeepLinkDemo/Pods-DeepLinkDemo.debug.xcconfig"; sourceTree = ""; }; DDBB5A837B0C008CA5BEC1EF /* Pods_DeepLinkDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_DeepLinkDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - E70E80C12A0E22C1008007B6 /* BranchSDK.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = BranchSDK.xcframework; path = Framework/BranchSDK.xcframework; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -101,7 +100,6 @@ 29D53CAAE9E9EF2F96296086 /* Frameworks */ = { isa = PBXGroup; children = ( - E70E80C12A0E22C1008007B6 /* BranchSDK.xcframework */, DDBB5A837B0C008CA5BEC1EF /* Pods_DeepLinkDemo.framework */, ); name = Frameworks; diff --git a/DeepLinkDemo/DeepLinkDemo/Base.lproj/Main.storyboard b/DeepLinkDemo/DeepLinkDemo/Base.lproj/Main.storyboard index 0e79934..7c58445 100644 --- a/DeepLinkDemo/DeepLinkDemo/Base.lproj/Main.storyboard +++ b/DeepLinkDemo/DeepLinkDemo/Base.lproj/Main.storyboard @@ -1,9 +1,9 @@ - + - + @@ -882,20 +882,20 @@ - + - - + + - + @@ -1126,6 +1142,7 @@ + @@ -1134,6 +1151,7 @@ + @@ -1142,13 +1160,14 @@ + - + @@ -1158,6 +1177,7 @@ + @@ -1168,7 +1188,7 @@ - + @@ -1182,7 +1202,7 @@ - + @@ -1193,6 +1213,7 @@ + @@ -2649,16 +2670,16 @@ - + - + - + diff --git a/DeepLinkDemo/DeepLinkDemo/Controllers/HomeViewController.swift b/DeepLinkDemo/DeepLinkDemo/Controllers/HomeViewController.swift index ba4fade..b4880dc 100644 --- a/DeepLinkDemo/DeepLinkDemo/Controllers/HomeViewController.swift +++ b/DeepLinkDemo/DeepLinkDemo/Controllers/HomeViewController.swift @@ -21,6 +21,7 @@ class HomeViewController: UITableViewController { @IBOutlet weak var btnReadLog: UIButton! @IBOutlet weak var btnSetDMAParams: UIButton! @IBOutlet weak var btnSendV2Event: UIButton! + @IBOutlet weak var btnSetAttributionLevel: UIButton! @IBOutlet weak var switchControl: UISwitch! @@ -47,6 +48,7 @@ class HomeViewController: UITableViewController { btnLoadWebView.layer.cornerRadius = 8.0 btnSetDMAParams.layer.cornerRadius = 8.0 btnSendV2Event.layer.cornerRadius = 8.0 + btnSetAttributionLevel.layer.cornerRadius = 8.0 NotificationCenter.default.addObserver(self, selector: #selector(self.methodOfReceivedNotification(notification:)), name: Notification.Name("NotificationIdentifier"), object: nil) @@ -131,7 +133,9 @@ class HomeViewController: UITableViewController { self.setDMAParamsWrapper() } else if textValue == "sendV2Event" { self.sendV2EventWrapper() - } + } else if textValue == "setAttributionLevel" { + self.setAttributionLevelWrapper() + } } } @@ -159,10 +163,9 @@ class HomeViewController: UITableViewController { } func enableBranchLogging(callback: @escaping BranchLogCallback){ - BranchLogger.shared().loggingEnabled = true - BranchLogger.shared().logLevelThreshold = .verbose - BranchLogger.shared().logCallback = callback + Branch.enableLogging(at: .verbose, withCallback: callback) } + func initBranch(){ if branchSDKInitialized { return @@ -195,6 +198,68 @@ class HomeViewController: UITableViewController { event.logEvent() } + func setAttributionLevelWrapper() { + self.logData = "Error: Missing testData.\n" + + let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil) + let vc = storyBoard.instantiateViewController(withIdentifier: "TextViewController") as? TextViewController + vc?.isSetAttributionLevel = true + + do { + let argCount = ProcessInfo.processInfo.arguments.count + if argCount >= 2 { + + for i in (1 ..< argCount) { + let data = ProcessInfo.processInfo.arguments[i].data(using: .utf8)! + + if let jsonObject = try JSONSerialization.jsonObject(with: data, options : .allowFragments) as? [String:AnyObject] + { + if jsonObject["consumer_protection_attribution_level"] != nil { + let attribution_level = jsonObject["consumer_protection_attribution_level"] as! String + self.logData = "" + self.enableBranchLogging(){(msg:String,msg2:BranchLogLevel,msg3:Error?)->() in + if (msg.contains("BranchSDK")){ + self.logData = self.logData + msg + "\n" + } + vc?.updateText(msg: self.logData) + } + if(self.branchSDKInitialized){ + Branch.getInstance().resetUserSession() + } + + switch attribution_level { + case "0": + Branch.getInstance().setConsumerProtectionAttributionLevel(.full) + case "1": + Branch.getInstance().setConsumerProtectionAttributionLevel(.reduced) + case "2": + Branch.getInstance().setConsumerProtectionAttributionLevel(.minimal) + case "3": + Branch.getInstance().setConsumerProtectionAttributionLevel(.none) + default: + Branch.getInstance().setConsumerProtectionAttributionLevel(.full) + } + + AppDelegate.shared.getBranchData(AppDelegate.shared.launchOption) + self.branchSDKInitialized = true + } else { + self.logData = "Missing params from JSON Object: \n" + jsonObject.description + } + } else { + self.logData = "Bad JSON : \n" + ProcessInfo.processInfo.arguments[i] + } + } + + + } + } catch let error as NSError { + print(error) + self.logData += error.localizedDescription + } + vc?.updateText(msg: self.logData) + self.navigationController?.pushViewController(vc!, animated: true) + } + func setDMAParamsWrapper() { self.logData = "Error: Missing testData.\n" @@ -367,6 +432,10 @@ class HomeViewController: UITableViewController { @IBAction func sendV2Event(){ reachabilityCheck(textValue: "sendV2Event") } + + @IBAction func setAttributionLevel(){ + reachabilityCheck(textValue: "setAttributionLevel") + } } extension UITextField { diff --git a/DeepLinkDemo/DeepLinkDemo/Controllers/TextViewController.swift b/DeepLinkDemo/DeepLinkDemo/Controllers/TextViewController.swift index 375a290..b55b15c 100644 --- a/DeepLinkDemo/DeepLinkDemo/Controllers/TextViewController.swift +++ b/DeepLinkDemo/DeepLinkDemo/Controllers/TextViewController.swift @@ -23,7 +23,8 @@ class TextViewController: UIViewController { var isTrackUser = false var isSetDMAParams = false var isSendV2Event = false - + var isSetAttributionLevel = false + var url = "" var responseStatus = "" var dictData = [String:Any]() @@ -68,6 +69,8 @@ class TextViewController: UIViewController { self.navigationController?.popToRootViewController(animated: true) } else if self.isSendV2Event == true { self.navigationController?.popToRootViewController(animated: true) + } else if self.isSetAttributionLevel == true { + self.navigationController?.popToRootViewController(animated: true) } else { launchReadVC() diff --git a/DeepLinkDemo/Podfile.lock b/DeepLinkDemo/Podfile.lock index 41cac45..e5b283e 100644 --- a/DeepLinkDemo/Podfile.lock +++ b/DeepLinkDemo/Podfile.lock @@ -1,16 +1,23 @@ PODS: - - IQKeyboardManager (6.5.15) + - BranchSDK (3.4.4) + - IQKeyboardManager (6.5.19) DEPENDENCIES: + - BranchSDK (from `./../`) - IQKeyboardManager SPEC REPOS: trunk: - IQKeyboardManager +EXTERNAL SOURCES: + BranchSDK: + :path: "./../" + SPEC CHECKSUMS: - IQKeyboardManager: 22ffab9bd300ad493485a390a095f5db0c841776 + BranchSDK: 28bec34fb99c53ab8e86b7bf69cc55a4505fe0b3 + IQKeyboardManager: c8665b3396bd0b79402b4c573eac345a31c7d485 -PODFILE CHECKSUM: 5d77f506f7fd530dd480a25e957b270a906b4207 +PODFILE CHECKSUM: 8c4b32f5c4a14defd62bc8a2d1e49cee9e7c2173 -COCOAPODS: 1.12.1 +COCOAPODS: 1.15.2 diff --git a/Sources/BranchSDK/BNCConfig.m b/Sources/BranchSDK/BNCConfig.m index 47cd61f..a6252e6 100644 --- a/Sources/BranchSDK/BNCConfig.m +++ b/Sources/BranchSDK/BNCConfig.m @@ -8,7 +8,7 @@ #include "BNCConfig.h" -NSString * const BNC_SDK_VERSION = @"3.6.5"; +NSString * const BNC_SDK_VERSION = @"3.7.0"; NSString * const BNC_LINK_URL = @"https://bnc.lt"; NSString * const BNC_CDN_URL = @"https://cdn.branch.io"; diff --git a/Sources/BranchSDK/BNCPreferenceHelper.m b/Sources/BranchSDK/BNCPreferenceHelper.m index 66b9464..1acf209 100644 --- a/Sources/BranchSDK/BNCPreferenceHelper.m +++ b/Sources/BranchSDK/BNCPreferenceHelper.m @@ -64,6 +64,8 @@ static NSString * const BRANCH_PREFS_KEY_DMA_AD_PERSONALIZATION = @"bnc_dma_ad_personalization"; static NSString * const BRANCH_PREFS_KEY_DMA_AD_USER_DATA = @"bnc_dma_ad_user_data"; +static NSString * const BRANCH_PREFS_KEY_ATTRIBUTION_LEVEL = @"bnc_attribution_level"; + NSURL* /* _Nonnull */ BNCURLForBranchDirectory_Unthreaded(void); @@ -119,7 +121,8 @@ @implementation BNCPreferenceHelper patternListURL = _patternListURL, eeaRegion = _eeaRegion, adPersonalizationConsent = _adPersonalizationConsent, - adUserDataUsageConsent = _adUserDataUsageConsent; + adUserDataUsageConsent = _adUserDataUsageConsent, + attributionLevel = _attributionLevel; + (BNCPreferenceHelper *)sharedInstance { static BNCPreferenceHelper *preferenceHelper = nil; @@ -627,7 +630,6 @@ - (void) setDropURLOpen:(BOOL)value { } } - - (BOOL) trackingDisabled { @synchronized(self) { NSNumber *b = (id) [self readObjectFromDefaults:@"trackingDisabled"]; @@ -823,6 +825,21 @@ - (void) setAdUserDataUsageConsent:(BOOL)hasConsent { } } +- (BOOL) attributionLevelInitialized { + @synchronized(self) { + if([self readObjectFromDefaults:BRANCH_PREFS_KEY_ATTRIBUTION_LEVEL]) + return YES; + return NO; + } +} + +- (BranchAttributionLevel)attributionLevel { + return [self readStringFromDefaults:BRANCH_PREFS_KEY_ATTRIBUTION_LEVEL]; +} + +- (void)setAttributionLevel:(BranchAttributionLevel)level { + [self writeObjectToDefaults:BRANCH_PREFS_KEY_ATTRIBUTION_LEVEL value:level]; +} - (void) clearTrackingInformation { @synchronized(self) { diff --git a/Sources/BranchSDK/BNCRequestFactory.m b/Sources/BranchSDK/BNCRequestFactory.m index 12ad738..6601527 100644 --- a/Sources/BranchSDK/BNCRequestFactory.m +++ b/Sources/BranchSDK/BNCRequestFactory.m @@ -127,6 +127,8 @@ - (NSDictionary *)dataForInstallWithURLString:(NSString *)urlString { // Add DMA Compliance Params for Google [self addDMAConsentParamsToJSON:json]; + [self addConsumerProtectionAttributionLevel:json]; + return json; } @@ -181,6 +183,8 @@ - (NSDictionary *)dataForOpenWithURLString:(NSString *)urlString { // Add DMA Compliance Params for Google [self addDMAConsentParamsToJSON:json]; + [self addConsumerProtectionAttributionLevel:json]; + return json; } @@ -359,7 +363,6 @@ - (void)addDMAConsentParamsToJSON:(NSMutableDictionary *)json { } } - - (void)addLocalURLToInstallJSON:(NSMutableDictionary *)json { if ([BNCPasteboard sharedInstance].checkOnInstall) { NSURL *pasteboardURL = nil; @@ -496,9 +499,12 @@ - (void)addInstrumentationToJSON:(NSMutableDictionary *)json { // BNCReferringURLUtility requires the endpoint string to determine which query params are applied - (void)addReferringURLsToJSON:(NSMutableDictionary *)json forEndpoint:(NSString *)endpoint { // Not a singleton, but BNCReferringURLUtility does pull from storage - BNCReferringURLUtility *utility = [BNCReferringURLUtility new]; - NSDictionary *urlQueryParams = [utility referringURLQueryParamsForEndpoint:endpoint]; - [json bnc_safeAddEntriesFromDictionary:urlQueryParams]; + if ([[self.preferenceHelper attributionLevel] isEqualToString:BranchAttributionLevelFull] || + ![self.preferenceHelper attributionLevelInitialized]) { + BNCReferringURLUtility *utility = [BNCReferringURLUtility new]; + NSDictionary *urlQueryParams = [utility referringURLQueryParamsForEndpoint:endpoint]; + [json bnc_safeAddEntriesFromDictionary:urlQueryParams]; + } } // install and open @@ -506,6 +512,13 @@ - (void)addDeveloperUserIDToJSON:(NSMutableDictionary *)json { [json bnc_safeSetObject:self.preferenceHelper.userIdentity forKey:@"identity"]; } +- (void)addConsumerProtectionAttributionLevel:(NSMutableDictionary *)json { + if ([self.preferenceHelper attributionLevelInitialized]) { + BranchAttributionLevel attributionLevel = [self.preferenceHelper attributionLevel]; + [self safeSetValue:attributionLevel forKey:BRANCH_REQUEST_KEY_CPP_LEVEL onDict:json]; + } +} + // event - (void)addV2DictionaryToJSON:(NSMutableDictionary *)json { NSDictionary *tmp = [self v2dictionary]; @@ -518,18 +531,28 @@ - (NSDictionary *)v2dictionary { NSMutableDictionary *dictionary = [NSMutableDictionary new]; @synchronized (self.deviceInfo) { [self.deviceInfo checkAdvertisingIdentifier]; - + BOOL disableAdNetworkCallouts = self.preferenceHelper.disableAdNetworkCallouts; if (disableAdNetworkCallouts) { dictionary[@"disable_ad_network_callouts"] = [NSNumber numberWithBool:disableAdNetworkCallouts]; } - + if (self.preferenceHelper.isDebug) { dictionary[@"unidentified_device"] = @(YES); } else { - [dictionary bnc_safeSetObject:self.deviceInfo.vendorId forKey:@"idfv"]; - [dictionary bnc_safeSetObject:self.deviceInfo.advertiserId forKey:@"idfa"]; + BranchAttributionLevel attributionLevel = [self.preferenceHelper attributionLevel]; + + if ([attributionLevel isEqualToString:BranchAttributionLevelFull] || + ![self.preferenceHelper attributionLevelInitialized]) { + [dictionary bnc_safeSetObject:self.deviceInfo.advertiserId forKey:@"idfa"]; + } + + if (![attributionLevel isEqualToString:BranchAttributionLevelNone] || + ![self.preferenceHelper attributionLevelInitialized]) { + [dictionary bnc_safeSetObject:self.deviceInfo.vendorId forKey:@"idfv"]; + } } + [dictionary bnc_safeSetObject:self.deviceInfo.anonId forKey:@"anon_id"]; [dictionary bnc_safeSetObject:self.deviceInfo.localIPAddress forKey:@"local_ip"]; @@ -569,6 +592,8 @@ - (NSDictionary *)v2dictionary { // Add DMA Compliance Params for Google [self addDMAConsentParamsToJSON:dictionary]; + [self addConsumerProtectionAttributionLevel:dictionary]; + return dictionary; } @@ -584,20 +609,28 @@ - (void)updateDeviceInfoToMutableDictionary:(NSMutableDictionary *)dict { if (![self isTrackingDisabled]) { [self.deviceInfo checkAdvertisingIdentifier]; - // hardware id information. idfa, idfv or random - NSString *hardwareId = [self.deviceInfo.hardwareId copy]; - NSString *hardwareIdType = [self.deviceInfo.hardwareIdType copy]; - NSNumber *isRealHardwareId = @(self.deviceInfo.isRealHardwareId); - if (hardwareId != nil && hardwareIdType != nil && isRealHardwareId != nil) { - dict[BRANCH_REQUEST_KEY_HARDWARE_ID] = hardwareId; - dict[BRANCH_REQUEST_KEY_HARDWARE_ID_TYPE] = hardwareIdType; - dict[BRANCH_REQUEST_KEY_IS_HARDWARE_ID_REAL] = isRealHardwareId; + // Only include hardware ID fields for Full Attribution Level + if (([[self.preferenceHelper attributionLevel] isEqualToString:BranchAttributionLevelFull]) + || [self.preferenceHelper attributionLevelInitialized] == false) { + + // hardware id information. idfa, idfv or random + NSString *hardwareId = [self.deviceInfo.hardwareId copy]; + NSString *hardwareIdType = [self.deviceInfo.hardwareIdType copy]; + NSNumber *isRealHardwareId = @(self.deviceInfo.isRealHardwareId); + + if (hardwareId != nil && hardwareIdType != nil && isRealHardwareId != nil) { + dict[BRANCH_REQUEST_KEY_HARDWARE_ID] = hardwareId; + dict[BRANCH_REQUEST_KEY_HARDWARE_ID_TYPE] = hardwareIdType; + dict[BRANCH_REQUEST_KEY_IS_HARDWARE_ID_REAL] = isRealHardwareId; + } } - - // idfv is duplicated in the hardware id field when idfa is unavailable - [self safeSetValue:self.deviceInfo.vendorId forKey:BRANCH_REQUEST_KEY_IOS_VENDOR_ID onDict:dict]; - // idfa is only in the hardware id field - // [self safeSetValue:deviceInfo.advertiserId forKey:@"idfa" onDict:dict]; + + // Only include hardware ID fields for attribution levels greater than None + if ([self.preferenceHelper attributionLevel] != BranchAttributionLevelNone) { + // idfv is duplicated in the hardware id field when idfa is unavailable + [self safeSetValue:self.deviceInfo.vendorId forKey:BRANCH_REQUEST_KEY_IOS_VENDOR_ID onDict:dict]; + } + [self safeSetValue:self.deviceInfo.anonId forKey:@"anon_id" onDict:dict]; [self safeSetValue:[self.deviceInfo localIPAddress] forKey:@"local_ip" onDict:dict]; diff --git a/Sources/BranchSDK/BNCSKAdNetwork.m b/Sources/BranchSDK/BNCSKAdNetwork.m index f5d05be..da95238 100644 --- a/Sources/BranchSDK/BNCSKAdNetwork.m +++ b/Sources/BranchSDK/BNCSKAdNetwork.m @@ -11,6 +11,7 @@ #import "BNCPreferenceHelper.h" #import "BranchConstants.h" #import "BranchLogger.h" +#import "Branch.h" @interface BNCSKAdNetwork() @@ -72,6 +73,11 @@ - (BOOL)shouldAttemptSKAdNetworkCallout { - (void)registerAppForAdNetworkAttribution { if (@available(iOS 14.0, macCatalyst 14.0, *)) { + if (![self isSKANAllowedForAttributionLevel]) { + [[BranchLogger shared] logDebug:@"SKAN registerAppForAdNetworkAttribution skipped due to BranchAttributionLevel" error:nil]; + return; + } + if ([self shouldAttemptSKAdNetworkCallout] && [self.skAdNetworkClass respondsToSelector:self.skAdNetworkRegisterAppForAdNetworkAttribution]) { [[BranchLogger shared] logDebug:@"Calling registerAppForAdNetworkAttribution" error:nil]; // Equivalent call [SKAdNetwork registerAppForAdNetworkAttribution]; @@ -82,6 +88,11 @@ - (void)registerAppForAdNetworkAttribution { - (void)updateConversionValue:(NSInteger)conversionValue { if (@available(iOS 14.0, macCatalyst 14.0, *)) { + if (![self isSKANAllowedForAttributionLevel]) { + [[BranchLogger shared] logDebug:@"SKAN updateConversionValue skipped due to BranchAttributionLevel" error:nil]; + return; + } + if ([self shouldAttemptSKAdNetworkCallout] && [self.skAdNetworkClass respondsToSelector:self.skAdNetworkUpdateConversionValue]) { [[BranchLogger shared] logDebug:[NSString stringWithFormat:@"Calling updateConversionValue:%ld", (long)conversionValue] error:nil]; @@ -93,6 +104,11 @@ - (void)updateConversionValue:(NSInteger)conversionValue { - (void)updatePostbackConversionValue:(NSInteger)conversionValue completionHandler:(void (^)(NSError *error))completion { if (@available(iOS 15.4, macCatalyst 15.4, *)) { + if (![self isSKANAllowedForAttributionLevel]) { + [[BranchLogger shared] logDebug:@"SKAN updatePostbackConversionValue skipped due to BranchAttributionLevel" error:nil]; + return; + } + if ([self shouldAttemptSKAdNetworkCallout] && [self.skAdNetworkClass respondsToSelector:self.skAdNetworkUpdatePostbackConversionValue]) { [[BranchLogger shared] logDebug:[NSString stringWithFormat:@"Calling updatePostbackConversionValue:%ld completionHandler:completion", (long)conversionValue] error:nil]; @@ -108,6 +124,11 @@ - (void)updatePostbackConversionValue:(NSInteger)fineValue lockWindow:(BOOL)lockWindow completionHandler:(void (^)(NSError *error))completion { if (@available(iOS 16.1, macCatalyst 16.1, *)) { + if (![self isSKANAllowedForAttributionLevel]) { + [[BranchLogger shared] logDebug:@"SKAN updatePostbackConversionValue skipped due to BranchAttributionLevel" error:nil]; + return; + } + if ([self shouldAttemptSKAdNetworkCallout] && [self.skAdNetworkClass respondsToSelector:self.skAdNetworkUpdatePostbackConversionValueCoarseValueLockWindow]) { [[BranchLogger shared] logDebug:[NSString stringWithFormat:@"Calling updatePostbackConversionValue:%ld coarseValue:%@ lockWindow:%d completionHandler:completion", (long)fineValue, coarseValue, lockWindow] error:nil]; // Equivalent call [SKAdNetwork updatePostbackConversionValue:coarseValue:lockWindow:completionHandler:]; @@ -197,4 +218,10 @@ - (BOOL) shouldCallPostbackForDataResponse:(NSDictionary *) dataResponseDictiona return shouldCallUpdatePostback; } +- (BOOL)isSKANAllowedForAttributionLevel { + BranchAttributionLevel level = [[BNCPreferenceHelper sharedInstance] attributionLevel]; + return !([level isEqualToString:BranchAttributionLevelMinimal] || + [level isEqualToString:BranchAttributionLevelNone]); +} + @end diff --git a/Sources/BranchSDK/BNCSystemObserver.m b/Sources/BranchSDK/BNCSystemObserver.m index e171a72..dbb6abe 100644 --- a/Sources/BranchSDK/BNCSystemObserver.m +++ b/Sources/BranchSDK/BNCSystemObserver.m @@ -84,6 +84,8 @@ + (NSString *)advertiserIdentifier { if ([uid isEqualToString:@"00000000-0000-0000-0000-000000000000"]) { [[BranchLogger shared] logVerbose:[NSString stringWithFormat:@"IDFA is all 0's. Probably running on a simulator or an App Clip."] error:nil]; uid = nil; + } else { + [[BranchLogger shared] logDebug:[NSString stringWithFormat:@"IDFA found: %@", uid] error:nil]; } } return uid; diff --git a/Sources/BranchSDK/Branch.m b/Sources/BranchSDK/Branch.m index 25433f7..f1178f4 100644 --- a/Sources/BranchSDK/Branch.m +++ b/Sources/BranchSDK/Branch.m @@ -85,6 +85,12 @@ NSString * const BNCSpotlightFeature = @"spotlight"; +BranchAttributionLevel const BranchAttributionLevelFull = @"FULL"; +BranchAttributionLevel const BranchAttributionLevelReduced = @"REDUCED"; +BranchAttributionLevel const BranchAttributionLevelMinimal = @"MINIMAL"; +BranchAttributionLevel const BranchAttributionLevelNone = @"NONE"; + + #ifndef CSSearchableItemActivityIdentifier #define CSSearchableItemActivityIdentifier @"kCSSearchableItemActivityIdentifier" #endif @@ -544,6 +550,44 @@ + (void) setDMAParamsForEEA:(BOOL)eeaRegion AdPersonalizationConsent:(BOOL)adPer [BNCPreferenceHelper sharedInstance].adUserDataUsageConsent = adUserDataUsageConsent; } +- (void)setConsumerProtectionAttributionLevel:(BranchAttributionLevel)level { + self.preferenceHelper.attributionLevel = level; + + [[BranchLogger shared] logVerbose:[NSString stringWithFormat:@"Setting Consumer Protection Attribution Level to %@", level] error:nil]; + + //Set tracking to disabled if consumer protection attribution level is changed to BranchAttributionLevelNone. Otherwise, keep tracking enabled. + if (level == BranchAttributionLevelNone) { + if ([Branch trackingDisabled] == false) { + //Disable Tracking + [[BranchLogger shared] logVerbose:@"Disabling attribution events due to Consumer Protection Attribution Level being BranchAttributionLevelNone." error:nil]; + + // Clear partner parameters + [[BNCPartnerParameters shared] clearAllParameters]; + + // Set the flag (which also clears the settings): + [BNCPreferenceHelper sharedInstance].trackingDisabled = YES; + Branch *branch = Branch.getInstance; + [branch clearNetworkQueue]; + branch.initializationStatus = BNCInitStatusUninitialized; + [branch.linkCache clear]; + // Release the lock in case it's locked: + [BranchOpenRequest releaseOpenResponseLock]; + } + } else { + if ([Branch trackingDisabled]) { + //Enable Tracking + [[BranchLogger shared] logVerbose:[NSString stringWithFormat:@"Enabling attribution events due to Consumer Protection Attribution Level being %@.", level] error:nil]; + + // Set the flag: + [BNCPreferenceHelper sharedInstance].trackingDisabled = NO; + + // Initialize a Branch session: + [[Branch getInstance] initUserSessionAndCallCallback:NO sceneIdentifier:nil urlString:nil reset:true]; + } + } + +} + #pragma mark - InitSession Permutation methods - (void)initSessionWithLaunchOptions:(NSDictionary *)options { diff --git a/Sources/BranchSDK/BranchConstants.m b/Sources/BranchSDK/BranchConstants.m index 290e930..15b0660 100644 --- a/Sources/BranchSDK/BranchConstants.m +++ b/Sources/BranchSDK/BranchConstants.m @@ -166,6 +166,7 @@ NSString * const BRANCH_REQUEST_KEY_DMA_AD_PEROSALIZATION = @"dma_ad_personalization"; NSString * const BRANCH_REQUEST_KEY_DMA_AD_USER_DATA = @"dma_ad_user_data"; +NSString * const BRANCH_REQUEST_KEY_CPP_LEVEL = @"cpp_level"; + NSString * const BRANCH_REQUEST_KEY_REQUEST_UUID = @"branch_sdk_request_unique_id"; NSString * const BRANCH_REQUEST_KEY_REQUEST_CREATION_TIME_STAMP = @"branch_sdk_request_timestamp"; - diff --git a/Sources/BranchSDK/Private/BranchConstants.h b/Sources/BranchSDK/Private/BranchConstants.h index a366ee1..50a82e7 100644 --- a/Sources/BranchSDK/Private/BranchConstants.h +++ b/Sources/BranchSDK/Private/BranchConstants.h @@ -168,5 +168,7 @@ extern NSString * const BRANCH_REQUEST_KEY_DMA_EEA; extern NSString * const BRANCH_REQUEST_KEY_DMA_AD_PEROSALIZATION; extern NSString * const BRANCH_REQUEST_KEY_DMA_AD_USER_DATA; +extern NSString * const BRANCH_REQUEST_KEY_CPP_LEVEL; + extern NSString * const BRANCH_REQUEST_KEY_REQUEST_UUID; -extern NSString * const BRANCH_REQUEST_KEY_REQUEST_CREATION_TIME_STAMP; +extern NSString * const BRANCH_REQUEST_KEY_REQUEST_CREATION_TIME_STAMP; \ No newline at end of file diff --git a/Sources/BranchSDK/Public/BNCPreferenceHelper.h b/Sources/BranchSDK/Public/BNCPreferenceHelper.h index 8c0fba2..464d550 100644 --- a/Sources/BranchSDK/Public/BNCPreferenceHelper.h +++ b/Sources/BranchSDK/Public/BNCPreferenceHelper.h @@ -76,6 +76,9 @@ NSURL* /* _Nonnull */ BNCURLForBranchDirectory(void); @property (assign, nonatomic) BOOL adPersonalizationConsent; @property (assign, nonatomic) BOOL adUserDataUsageConsent; +@property (nonatomic, assign) NSString *attributionLevel; + + - (void) clearTrackingInformation; + (BNCPreferenceHelper *)sharedInstance; @@ -100,5 +103,6 @@ NSURL* /* _Nonnull */ BNCURLForBranchDirectory(void); - (void) synchronize; // Flushes preference queue to persistence. + (void) clearAll; - (BOOL) eeaRegionInitialized; +- (BOOL) attributionLevelInitialized; @end diff --git a/Sources/BranchSDK/Public/Branch.h b/Sources/BranchSDK/Public/Branch.h index 78b3f03..3f381ef 100644 --- a/Sources/BranchSDK/Public/Branch.h +++ b/Sources/BranchSDK/Public/Branch.h @@ -785,7 +785,7 @@ Sets a custom base URL for all calls to the Branch API. @param disabled If set to `true` then tracking will be disabled. @warning This will prevent most of the Branch SDK functionality. */ -+ (void) setTrackingDisabled:(BOOL)disabled; ++ (void)setTrackingDisabled:(BOOL)disabled __attribute__((deprecated("This method has been deprecated. Use `setConsumerProtectionAttributionLevel:` with `BranchAttributionLevelNone` instead."))); ///Returns the current tracking state. + (BOOL) trackingDisabled; @@ -812,6 +812,64 @@ Sets a custom base URL for all calls to the Branch API. */ + (void) setDMAParamsForEEA:(BOOL) eeaRegion AdPersonalizationConsent:(BOOL) adPersonalizationConsent AdUserDataUsageConsent:(BOOL) adUserDataUsageConsent; + +/** + * Enumeration representing different levels of consumer protection attribution levels + */ +typedef NSString * BranchAttributionLevel NS_STRING_ENUM; + +/** + * Full: + * - Advertising Ids + * - Device Ids + * - Local IP + * - Persisted Non-Aggregate Ids + * - Persisted Aggregate Ids + * - Ads Postbacks / Webhooks + * - Data Integrations Webhooks + * - SAN Callouts + * - Privacy Frameworks + * - Deep Linking + */ +extern BranchAttributionLevel const BranchAttributionLevelFull; + +/** + * Reduced: + * - Device Ids + * - Local IP + * - Data Integrations Webhooks + * - Privacy Frameworks + * - Deep Linking + */ +extern BranchAttributionLevel const BranchAttributionLevelReduced; + +/** + * Minimal: + * - Device Ids + * - Local IP + * - Data Integrations Webhooks + * - Deep Linking + */ +extern BranchAttributionLevel const BranchAttributionLevelMinimal; + +/** + * None: + * - Only Deterministic Deep Linking + * - Disables all other Branch requests + */ +extern BranchAttributionLevel const BranchAttributionLevelNone; + + +/** + Sets the consumer protection attribution level. + + @param level The desired consumer protection attribution level, represented by the BranchAttributionLevel enum (Full, Reduced, Minimal, None). + @discussion This method allows you to control the amount and type of data collected and transmitted by Branch. + Adjusting the consumer protection attribution level can help you comply with privacy regulations and meet your data collection needs. + */ +- (void)setConsumerProtectionAttributionLevel:(BranchAttributionLevel)level; + + #pragma mark - Session Item methods ///-------------------- diff --git a/scripts/version.sh b/scripts/version.sh index f83d509..2f54c58 100755 --- a/scripts/version.sh +++ b/scripts/version.sh @@ -30,7 +30,7 @@ Options: USAGE } -version=3.6.5 +version=3.7.0 prev_version="$version" if (( $# == 0 )); then