diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Branch-TestBed/Branch-TestBed.xcodeproj/project.pbxproj b/Branch-TestBed/Branch-TestBed.xcodeproj/project.pbxproj index 23e32a6c7..bfd8bb661 100644 --- a/Branch-TestBed/Branch-TestBed.xcodeproj/project.pbxproj +++ b/Branch-TestBed/Branch-TestBed.xcodeproj/project.pbxproj @@ -188,7 +188,6 @@ 5F644C492B7AA811000DCD78 /* BNCEventUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F644BB82B7AA811000DCD78 /* BNCEventUtils.m */; }; 5F67F48E228F535500067429 /* BNCEncodingUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F67F48D228F535500067429 /* BNCEncodingUtilsTests.m */; }; 5F6D86D92BB5E9650068B536 /* BNCClassSerializationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F6D86D82BB5E9650068B536 /* BNCClassSerializationTests.m */; }; - 5F83B9ED2433BAAA0054A022 /* BNCServerInterface.Test.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D16837E2098C901008819E3 /* BNCServerInterface.Test.m */; }; 5F86501A2B76DA3200364BDE /* NSMutableDictionaryBranchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F8650192B76DA3200364BDE /* NSMutableDictionaryBranchTests.m */; }; 5F892EC5236116CD0023AEC1 /* NSErrorBranchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5F892EC4236116CC0023AEC1 /* NSErrorBranchTests.m */; }; 5F8B7B4021B5F5CD009CE0A6 /* libBranch.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 466B58381B17773000A69EDE /* libBranch.a */; }; @@ -235,6 +234,8 @@ C1614D56285BC8A00098946B /* LinkPresentation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C1614D55285BC8A00098946B /* LinkPresentation.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; C17DAF7B2AC20C2000B16B1A /* BranchClassTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C17DAF7A2AC20C2000B16B1A /* BranchClassTests.m */; }; C1CC888229BAAFC000BDD2B5 /* BNCReferringURLUtilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = C1CC888129BAAFC000BDD2B5 /* BNCReferringURLUtilityTests.m */; }; + E56394312CC7AC9F00E18E65 /* BranchFileLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = E563942F2CC7AC9500E18E65 /* BranchFileLogger.m */; }; + E56394332CC7ACB600E18E65 /* BranchFileLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = E56394322CC7ACAA00E18E65 /* BranchFileLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; E72489D228E40D0200DCD8FD /* PasteControlViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = E72489D128E40D0200DCD8FD /* PasteControlViewController.m */; }; E7A728BD2AA9A112009343B7 /* BNCAPIServerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = E7A728BC2AA9A112009343B7 /* BNCAPIServerTest.m */; }; F1CF14111F4CC79F00BB2694 /* CoreSpotlight.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 67F270881BA9FCFF002546A7 /* CoreSpotlight.framework */; settings = {ATTRIBUTES = (Required, ); }; }; @@ -539,6 +540,8 @@ C1614D55285BC8A00098946B /* LinkPresentation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LinkPresentation.framework; path = System/Library/Frameworks/LinkPresentation.framework; sourceTree = SDKROOT; }; C17DAF7A2AC20C2000B16B1A /* BranchClassTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BranchClassTests.m; sourceTree = ""; }; C1CC888129BAAFC000BDD2B5 /* BNCReferringURLUtilityTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BNCReferringURLUtilityTests.m; sourceTree = ""; }; + E563942F2CC7AC9500E18E65 /* BranchFileLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BranchFileLogger.m; sourceTree = ""; }; + E56394322CC7ACAA00E18E65 /* BranchFileLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BranchFileLogger.h; sourceTree = ""; }; E72489D028E40D0200DCD8FD /* PasteControlViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PasteControlViewController.h; sourceTree = ""; }; E72489D128E40D0200DCD8FD /* PasteControlViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PasteControlViewController.m; sourceTree = ""; }; E7A728BC2AA9A112009343B7 /* BNCAPIServerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BNCAPIServerTest.m; sourceTree = ""; }; @@ -676,6 +679,7 @@ 5F644B252B7AA810000DCD78 /* BranchSDK */ = { isa = PBXGroup; children = ( + E563942F2CC7AC9500E18E65 /* BranchFileLogger.m */, 5F644BB02B7AA811000DCD78 /* BNCAppGroupsData.m */, 5F644BA82B7AA811000DCD78 /* BNCAppleReceipt.m */, 5F644B2B2B7AA810000DCD78 /* BNCApplication.m */, @@ -755,6 +759,7 @@ 5F644B522B7AA810000DCD78 /* Public */ = { isa = PBXGroup; children = ( + E56394322CC7ACAA00E18E65 /* BranchFileLogger.h */, 5F644B5E2B7AA810000DCD78 /* BNCCallbacks.h */, 5F644B5A2B7AA810000DCD78 /* BNCCurrency.h */, 5F644B612B7AA810000DCD78 /* BNCInitSessionResponse.h */, @@ -986,6 +991,7 @@ 5F644BFE2B7AA811000DCD78 /* BNCServerInterface.h in Headers */, 5F644BFF2B7AA811000DCD78 /* BNCProductCategory.h in Headers */, 5F644C1C2B7AA811000DCD78 /* NSString+Branch.h in Headers */, + E56394332CC7ACB600E18E65 /* BranchFileLogger.h in Headers */, 5F644C132B7AA811000DCD78 /* NSMutableDictionary+Branch.h in Headers */, 5F644C122B7AA811000DCD78 /* BNCApplication.h in Headers */, 5F5FDA122B7DE22A00F14A43 /* BranchLogger.h in Headers */, @@ -1317,6 +1323,7 @@ 5F644BD72B7AA811000DCD78 /* BNCNetworkService.m in Sources */, 5F644C402B7AA811000DCD78 /* UIViewController+Branch.m in Sources */, 5F644BC12B7AA811000DCD78 /* BNCServerRequestQueue.m in Sources */, + E56394312CC7AC9F00E18E65 /* BranchFileLogger.m in Sources */, 5F644C452B7AA811000DCD78 /* BranchOpenRequest.m in Sources */, 5F644C312B7AA811000DCD78 /* BNCLinkData.m in Sources */, 5F644BD22B7AA811000DCD78 /* BranchContentDiscoveryManifest.m in Sources */, diff --git a/BranchSDK.xcodeproj/project.pbxproj b/BranchSDK.xcodeproj/project.pbxproj index 7219bec79..02f1cb075 100644 --- a/BranchSDK.xcodeproj/project.pbxproj +++ b/BranchSDK.xcodeproj/project.pbxproj @@ -489,6 +489,11 @@ 5FCDD5B22B7AC89100EAF29F /* BranchSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FF2AFDF28E7C22100393216 /* BranchSDK.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5FCDD5B32B7AC89200EAF29F /* BranchSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FF2AFDF28E7C22100393216 /* BranchSDK.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5FCDD5B42B7AC89200EAF29F /* BranchSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = 5FF2AFDF28E7C22100393216 /* BranchSDK.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E52E5B062CC79E4E00F553EE /* BranchFileLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = E52E5B052CC79E4E00F553EE /* BranchFileLogger.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E52E5B072CC79E4E00F553EE /* BranchFileLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = E52E5B052CC79E4E00F553EE /* BranchFileLogger.h */; }; + E52E5B0A2CC79E5C00F553EE /* BranchFileLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = E52E5B092CC79E5C00F553EE /* BranchFileLogger.m */; }; + E52E5B0B2CC79E5C00F553EE /* BranchFileLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = E52E5B092CC79E5C00F553EE /* BranchFileLogger.m */; }; + E563942E2CC7A8E600E18E65 /* BranchFileLogger.h in Headers */ = {isa = PBXBuildFile; fileRef = E52E5B052CC79E4E00F553EE /* BranchFileLogger.h */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -698,6 +703,8 @@ 5FF2AFDC28E7BF8A00393216 /* build_xcframework.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = build_xcframework.sh; sourceTree = ""; }; 5FF2AFDE28E7C22100393216 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 5FF2AFDF28E7C22100393216 /* BranchSDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BranchSDK.h; sourceTree = ""; }; + E52E5B052CC79E4E00F553EE /* BranchFileLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BranchFileLogger.h; sourceTree = ""; }; + E52E5B092CC79E5C00F553EE /* BranchFileLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BranchFileLogger.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -811,6 +818,7 @@ 5FCDD36B2B7AC6A100EAF29F /* BranchSDK */ = { isa = PBXGroup; children = ( + E52E5B092CC79E5C00F553EE /* BranchFileLogger.m */, 5FCDD3F62B7AC6A100EAF29F /* BNCAppGroupsData.m */, 5FCDD3EE2B7AC6A100EAF29F /* BNCAppleReceipt.m */, 5FCDD3712B7AC6A100EAF29F /* BNCApplication.m */, @@ -890,6 +898,7 @@ 5FCDD3982B7AC6A100EAF29F /* Public */ = { isa = PBXGroup; children = ( + E52E5B052CC79E4E00F553EE /* BranchFileLogger.h */, 5FCDD3A42B7AC6A100EAF29F /* BNCCallbacks.h */, 5FCDD3A02B7AC6A100EAF29F /* BNCCurrency.h */, 5FCDD3A72B7AC6A100EAF29F /* BNCInitSessionResponse.h */, @@ -1029,6 +1038,7 @@ 5FCDD4A12B7AC6A200EAF29F /* BNCServerResponse.h in Headers */, 5FCDD4C22B7AC6A200EAF29F /* BranchDeepLinkingController.h in Headers */, 5FCDD4B92B7AC6A200EAF29F /* BranchPasteControl.h in Headers */, + E52E5B062CC79E4E00F553EE /* BranchFileLogger.h in Headers */, 5FCDD5072B7AC6A300EAF29F /* BNCUrlQueryParameter.h in Headers */, 5FCDD5462B7AC6A300EAF29F /* BNCConfig.h in Headers */, 5FCDD4E62B7AC6A200EAF29F /* UIViewController+Branch.h in Headers */, @@ -1110,6 +1120,7 @@ 5FCDD4C32B7AC6A200EAF29F /* BranchDeepLinkingController.h in Headers */, 5FCDD4BA2B7AC6A200EAF29F /* BranchPasteControl.h in Headers */, 5FCDD49F2B7AC6A200EAF29F /* Branch.h in Headers */, + E52E5B072CC79E4E00F553EE /* BranchFileLogger.h in Headers */, 5FCDD5082B7AC6A300EAF29F /* BNCUrlQueryParameter.h in Headers */, 5FCDD5472B7AC6A300EAF29F /* BNCConfig.h in Headers */, 5F5FDA1B2B7DE31E00F14A43 /* BranchLogger.h in Headers */, @@ -1190,6 +1201,7 @@ 5FCDD4A02B7AC6A200EAF29F /* Branch.h in Headers */, 5FCDD5092B7AC6A300EAF29F /* BNCUrlQueryParameter.h in Headers */, 5FCDD5482B7AC6A300EAF29F /* BNCConfig.h in Headers */, + E563942E2CC7A8E600E18E65 /* BranchFileLogger.h in Headers */, 5FCDD4E82B7AC6A200EAF29F /* UIViewController+Branch.h in Headers */, 5FCDD54B2B7AC6A300EAF29F /* BranchConstants.h in Headers */, 5F5FDA1C2B7DE31E00F14A43 /* BranchLogger.h in Headers */, @@ -1608,6 +1620,7 @@ 5FCDD4592B7AC6A100EAF29F /* BNCNetworkService.m in Sources */, 5FCDD5942B7AC6A400EAF29F /* UIViewController+Branch.m in Sources */, 5FCDD4172B7AC6A100EAF29F /* BNCServerRequestQueue.m in Sources */, + E52E5B0A2CC79E5C00F553EE /* BranchFileLogger.m in Sources */, 5FCDD5A32B7AC6A400EAF29F /* BranchOpenRequest.m in Sources */, 5FCDD5672B7AC6A300EAF29F /* BNCLinkData.m in Sources */, 5FCDD44A2B7AC6A100EAF29F /* BranchContentDiscoveryManifest.m in Sources */, @@ -1712,6 +1725,7 @@ 5FCDD45A2B7AC6A100EAF29F /* BNCNetworkService.m in Sources */, 5FCDD5952B7AC6A400EAF29F /* UIViewController+Branch.m in Sources */, 5FCDD4182B7AC6A100EAF29F /* BNCServerRequestQueue.m in Sources */, + E52E5B0B2CC79E5C00F553EE /* BranchFileLogger.m in Sources */, 5FCDD5A42B7AC6A400EAF29F /* BranchOpenRequest.m in Sources */, 5FCDD5682B7AC6A400EAF29F /* BNCLinkData.m in Sources */, 5FCDD44B2B7AC6A100EAF29F /* BranchContentDiscoveryManifest.m in Sources */, diff --git a/Sources/BranchSDK/BNCSystemObserver.m b/Sources/BranchSDK/BNCSystemObserver.m index e171a7271..6c5d8a2ec 100644 --- a/Sources/BranchSDK/BNCSystemObserver.m +++ b/Sources/BranchSDK/BNCSystemObserver.m @@ -165,6 +165,17 @@ + (BOOL)compareUriSchemes : (NSString *) serverUriScheme { return false; } ++ (BOOL)compareLinkDomain:(NSString *)serverLinkDomain { + NSArray *linkDomains = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"branch_universal_link_domains"]; + + for (NSString *domain in linkDomains) { + if ([domain isEqualToString:serverLinkDomain]) { + return true; + } + } + return false; +} + + (NSString *)bundleIdentifier { return [[NSBundle mainBundle] bundleIdentifier]; } diff --git a/Sources/BranchSDK/Branch+Validator.m b/Sources/BranchSDK/Branch+Validator.m index 8a387a803..a56ab5410 100644 --- a/Sources/BranchSDK/Branch+Validator.m +++ b/Sources/BranchSDK/Branch+Validator.m @@ -13,6 +13,11 @@ #import "BNCEncodingUtils.h" #import "BNCServerAPI.h" #import "UIViewController+Branch.h" +#import "BNCConfig.h" +#import "Branch.h" +#if !TARGET_OS_TV +#import "BranchFileLogger.h" +#endif void BNCForceBranchValidatorCategoryToLoad(void) { // Empty body but forces loader to load the category. @@ -30,6 +35,62 @@ static inline void BNCAfterSecondsPerformBlockOnMainThread(NSTimeInterval second dispatch_after(BNCDispatchTimeFromSeconds(seconds), dispatch_get_main_queue(), block); } +typedef NS_ENUM(NSUInteger, BranchValidationError) { + BranchLinkDomainError, // for link domain or alternate domain mismatch + BranchURISchemeError, // for uri scheme mismatch + BranchAppIDError, // for bundle ID or app prefix mismatch + BranchATTError // for idfa missing error +}; + +NSString *BranchValidationErrorDescription(BranchValidationError error) { + switch (error) { + case BranchLinkDomainError: + return @"Check the link domain and alternate domain values in your info.plist file under the key 'branch_universal_link_domains'. The values should match with the ones on the Branch dashboard.\n\n"; + case BranchURISchemeError: + return @"The URI scheme in your info.plist file shoudl match with the URI scheme value for iOS on the Branch dashboard.\n\n"; + case BranchAppIDError: + return @"Check your bundle ID and Apple App Prefix from the Apple Developer website and ensure it matches with the values you have added on the Branch dashboard.\n\n"; + case BranchATTError: + return @"The ATT prompt ensures that the Branch SDK can access the IDFA when the user permits it. Add the ATT prompt in your app for IDFA access.\n\n"; + } + return @"Unknown"; +} + +NSString *BranchValidationErrorReferenceDescription(BranchValidationError error) { + switch (error) { + case BranchLinkDomainError: + return @"Link Domain Reference"; + case BranchURISchemeError: + return @"URI Scheme Reference"; + case BranchAppIDError: + return @"App Prefix/Bundle ID Reference"; + case BranchATTError: + return @"ATT Prompt Reference"; + } + return @"Unknown"; +} + +NSURL *BranchValidationErrorReference(BranchValidationError error) { + NSString *urlString; + + switch (error) { + case BranchLinkDomainError: + urlString = @"https://help.branch.io/developers-hub/docs/ios-basic-integration#4-configure-infoplist"; + break; + case BranchURISchemeError: + urlString = @"https://help.branch.io/developers-hub/docs/ios-basic-integration#4-configure-infoplist"; + break; + case BranchAppIDError: + urlString = @"https://help.branch.io/developers-hub/docs/ios-basic-integration#1-configure-branch-dashboard"; + break; + case BranchATTError: + urlString = @"https://help.branch.io/developers-hub/docs/ios-advanced-features#include-apples-attrackingmanager"; + break; + } + + return [NSURL URLWithString:urlString]; +} + #pragma mark - Branch (Validator) @implementation Branch (Validator) @@ -62,20 +123,23 @@ - (void) validateIntegrationWithServerResponse:(BNCServerResponse*)response { NSString*serverUriScheme = BNCStringFromWireFormat(response.data[@"ios_uri_scheme"]) ?: @""; NSString*serverBundleID = BNCStringFromWireFormat(response.data[@"ios_bundle_id"]) ?: @""; NSString*serverTeamID = BNCStringFromWireFormat(response.data[@"ios_team_id"]) ?: @""; + NSString*defaultDomain = BNCStringFromWireFormat(response.data[@"default_short_url_domain"]) ?: @""; + NSString*alternateDomain = BNCStringFromWireFormat(response.data[@"alternate_short_url_domain"]) ?: @""; + NSString*attOptInStatus = [BNCSystemObserver attOptedInStatus]; // Verify: NSLog(@"** Initiating Branch integration verification **"); NSLog(@"-------------------------------------------------"); NSLog(@"------ Checking for URI scheme correctness ------"); - NSString *clientUriScheme = [NSString stringWithFormat:@"%@%@", [BNCSystemObserver defaultURIScheme], @"://"]; NSString *uriScheme = [BNCSystemObserver compareUriSchemes:serverUriScheme] ? passString : errorString; + bool doUriSchemesMatch = [BNCSystemObserver compareUriSchemes:serverUriScheme]; NSLog(@"-------------------------------------------------"); NSLog(@"-- Checking for bundle identifier correctness ---"); NSString *clientBundleIdentifier = [[NSBundle mainBundle] bundleIdentifier] ?: @""; - bool doUriSchemesMatch = [serverBundleID isEqualToString:clientBundleIdentifier]; - NSString *bundleIdentifier = doUriSchemesMatch ? passString : errorString; + bool doBundleIDsMatch = [serverBundleID isEqualToString:clientBundleIdentifier]; + NSString *bundleIdentifier = doBundleIDsMatch ? passString : errorString; NSString *bundleIdentifierMessage = [NSString stringWithFormat:@"%@: Dashboard Link Settings page '%@' compared to client side '%@'", bundleIdentifier, serverBundleID, clientBundleIdentifier]; @@ -107,9 +171,37 @@ - (void) validateIntegrationWithServerResponse:(BNCServerResponse*)response { BOOL testsFailed = NO; NSString *kPassMark = @"✅\t"; NSString *kFailMark = @"❌\t"; + NSString *kWarningMark = @"⚠️\t"; // Build an alert string: - NSString *alertString = @"\n"; + NSString *alertString = @""; + NSMutableArray *errors = [[NSMutableArray alloc] init]; + alertString = [alertString stringByAppendingFormat:@"\nBranch SDK Version: %@\n", BNC_SDK_VERSION]; + if ([Branch useTestBranchKey]) { + alertString = [alertString stringByAppendingFormat:@"The SDK is using the test key\n\n"]; + } else { + alertString = [alertString stringByAppendingFormat:@"The SDK is using the live key\n\n"]; + } + if ([BNCSystemObserver compareLinkDomain:defaultDomain]) { + alertString = [alertString stringByAppendingFormat:@"%@Default Link Domain matches:\n\t'%@'\n", kPassMark, defaultDomain]; + } else { + testsFailed = YES; + alertString = [alertString stringByAppendingFormat:@"%@Default Link Domain mismatch:\n\t'%@'\n", kFailMark, defaultDomain]; + if (![errors containsObject:@(BranchLinkDomainError)]) { + [errors addObject:@(BranchLinkDomainError)]; + } + } + + if ([BNCSystemObserver compareLinkDomain:alternateDomain]) { + alertString = [alertString stringByAppendingFormat:@"%@Alternate Link Domain matches:\n\t'%@'\n", kPassMark, alternateDomain]; + } else { + testsFailed = YES; + alertString = [alertString stringByAppendingFormat:@"%@Alternate Link Domain mismatch:\n\t'%@'\n", kFailMark, alternateDomain]; + if (![errors containsObject:@(BranchLinkDomainError)]) { + [errors addObject:@(BranchLinkDomainError)]; + } + } + if (serverUriScheme.length && doUriSchemesMatch) { alertString = [alertString stringByAppendingFormat:@"%@URI Scheme matches:\n\t'%@'\n", kPassMark, serverUriScheme]; @@ -117,6 +209,9 @@ - (void) validateIntegrationWithServerResponse:(BNCServerResponse*)response { testsFailed = YES; alertString = [alertString stringByAppendingFormat:@"%@URI Scheme mismatch:\n\t'%@'\n", kFailMark, serverUriScheme]; + if (![errors containsObject:@(BranchURISchemeError)]) { + [errors addObject:@(BranchURISchemeError)]; + } } if ([serverBundleID isEqualToString:clientBundleIdentifier]) { @@ -126,6 +221,9 @@ - (void) validateIntegrationWithServerResponse:(BNCServerResponse*)response { testsFailed = YES; alertString = [alertString stringByAppendingFormat:@"%@App Bundle ID mismatch:\n\t'%@'\n", kFailMark, serverBundleID]; + if (![errors containsObject:@(BranchAppIDError)]) { + [errors addObject:@(BranchAppIDError)]; + } } if ([serverTeamID isEqualToString:clientTeamId]) { @@ -135,10 +233,22 @@ - (void) validateIntegrationWithServerResponse:(BNCServerResponse*)response { testsFailed = YES; alertString = [alertString stringByAppendingFormat:@"%@Team ID mismatch:\n\t'%@'\n", kFailMark, serverTeamID]; + if (![errors containsObject:@(BranchAppIDError)]) { + [errors addObject:@(BranchAppIDError)]; + } } - + + if ([attOptInStatus isEqualToString:@"authorized"]) { + alertString = [alertString stringByAppendingFormat:@"%@IDFA is accessible\n", kPassMark]; + } else { + alertString = [alertString stringByAppendingFormat:@"%@IDFA is not accessible\n", kWarningMark]; + if (![errors containsObject:@(BranchATTError)]) { + [errors addObject:@(BranchATTError)]; + } + } + if (testsFailed) { - alertString = [alertString stringByAppendingString:@"\nFailed!\nCheck the log for details."]; + alertString = [alertString stringByAppendingString:@"\nFailed!"]; } else { alertString = [alertString stringByAppendingString:@"\nPassed!"]; } @@ -154,19 +264,31 @@ - (void) validateIntegrationWithServerResponse:(BNCServerResponse*)response { BNCPerformBlockOnMainThreadAsync(^{ UIAlertController *alertController = - [UIAlertController alertControllerWithTitle:@"Branch Integration" + [UIAlertController alertControllerWithTitle:@"Branch Integration Validator" message:alertString preferredStyle:UIAlertControllerStyleAlert]; if (testsFailed) { [alertController - addAction:[UIAlertAction actionWithTitle:@"Bummer" + addAction:[UIAlertAction actionWithTitle:@"What should I change?" + style:UIAlertActionStyleDefault + handler:^ (UIAlertAction *action) { [self showSolutionsForErrors:(errors)]; }]]; + [alertController + addAction:[UIAlertAction actionWithTitle:@"Export Logs" + style:UIAlertActionStyleDefault + handler:^ (UIAlertAction *action) { [self showExportedLogs]; }]]; + [alertController + addAction:[UIAlertAction actionWithTitle:@"Done" style:UIAlertActionStyleDefault handler:nil]]; } else { [alertController - addAction:[UIAlertAction actionWithTitle:@"Next Step" + addAction:[UIAlertAction actionWithTitle:@"Done" + style:UIAlertActionStyleDefault + handler:nil]]; + [alertController + addAction:[UIAlertAction actionWithTitle:@"Export Logs" style:UIAlertActionStyleDefault - handler:^ (UIAlertAction *action) { [self showNextStep]; }]]; + handler:^ (UIAlertAction *action) { [self showExportedLogs]; }]]; } [alertController setValue:styledAlertString forKey:@"attributedMessage"]; [[UIViewController bnc_currentViewController] @@ -176,6 +298,57 @@ - (void) validateIntegrationWithServerResponse:(BNCServerResponse*)response { }); } +- (void) showSolutionsForErrors:(NSArray*) errors { + NSString *message = @""; + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"What should I change?" message: @"" preferredStyle: UIAlertControllerStyleAlert]; + for (NSNumber *errorNumber in errors) { + BranchValidationError error = (BranchValidationError)[errorNumber integerValue]; + + message = [message stringByAppendingString:BranchValidationErrorDescription(error)]; + + [alertController + addAction:[UIAlertAction actionWithTitle:BranchValidationErrorReferenceDescription(error) + style:UIAlertActionStyleDefault + handler:^ (UIAlertAction *action) { + Class applicationClass = NSClassFromString(@"UIApplication"); + id sharedApplication = [applicationClass performSelector:@selector(sharedApplication)]; + if ([sharedApplication respondsToSelector:@selector(openURL:)]) + [sharedApplication performSelector:@selector(openURL:) withObject:BranchValidationErrorReference(error)]; + }]]; + } + + alertController.message = message; + [alertController + addAction:[UIAlertAction actionWithTitle:@"Done" + style:UIAlertActionStyleDefault + handler:nil]]; + + [[UIViewController bnc_currentViewController] + presentViewController:alertController + animated:YES + completion:nil]; +} + +- (void) showExportedLogs { + #if !TARGET_OS_TV + if ([[BranchFileLogger sharedInstance] isLogFilePopulated]) { + UIViewController *currentVC = [UIViewController bnc_currentViewController]; + [[BranchFileLogger sharedInstance] shareLogFileFromViewController:currentVC]; + } else { + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"No log file available" message: @"Ensure that logging is enabled and you are running the app in debug mode to export logs." preferredStyle: UIAlertControllerStyleAlert]; + [alertController + addAction:[UIAlertAction actionWithTitle:@"Done" + style:UIAlertActionStyleDefault + handler:nil]]; + [[UIViewController bnc_currentViewController] + presentViewController:alertController + animated:YES + completion:nil]; + } + #endif +} + +//MARK: Not in use until development of Integration Validator Phase 2 Changes - (void) showNextStep { NSString *message = @"\nGreat! Remove the 'validateSDKIntegration' line in your app.\n\n" diff --git a/Sources/BranchSDK/Branch.m b/Sources/BranchSDK/Branch.m index 9b4d4f87e..bdfc59cc7 100644 --- a/Sources/BranchSDK/Branch.m +++ b/Sources/BranchSDK/Branch.m @@ -2107,6 +2107,8 @@ - (void)handleInitSuccessAndCallCallback:(BOOL)callCallback sceneIdentifier:(NSS id sharedApplication = [applicationClass performSelector:@selector(sharedApplication)]; if ([sharedApplication respondsToSelector:@selector(openURL:)]) [sharedApplication performSelector:@selector(openURL:) withObject:comp.URL]; + } else if ([latestReferringParams[@"validate_integration"] isEqualToString:@"true"]) { + [self validateSDKIntegration]; } if (callCallback) { diff --git a/Sources/BranchSDK/BranchFileLogger.m b/Sources/BranchSDK/BranchFileLogger.m new file mode 100644 index 000000000..10aceb5ce --- /dev/null +++ b/Sources/BranchSDK/BranchFileLogger.m @@ -0,0 +1,114 @@ +// +// BranchFileLogger.m +// +// +// Created by Sharath Sriram on 15/10/24. +// +#if !TARGET_OS_TV + +#import "BranchFileLogger.h" + +@interface BranchFileLogger () + +@property (nonatomic, strong) NSString *logFilePath; + +@end + +@implementation BranchFileLogger + ++ (instancetype)sharedInstance { + static BranchFileLogger *sharedInstance = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + sharedInstance = [[self alloc] init]; + }); + return sharedInstance; +} + +- (instancetype)init { + if (self = [super init]) { + _logFilePath = [self getLogFilePath]; + [self clearLogs]; // Clear logs at the start of each app session + } + return self; +} + +// Get the log file path in the app’s documents directory +- (NSString *)getLogFilePath { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths firstObject]; + return [documentsDirectory stringByAppendingPathComponent:@"app_log.txt"]; +} + +// Append a message to the log file +- (void)logMessage:(NSString *)message { + NSString *timestampedMessage = [NSString stringWithFormat:@"%@: %@\n", [self currentTimestamp], message]; + + NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.logFilePath]; + if (!fileHandle) { + [[NSFileManager defaultManager] createFileAtPath:self.logFilePath contents:nil attributes:nil]; + fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.logFilePath]; + } + + [fileHandle seekToEndOfFile]; + NSData *data = [timestampedMessage dataUsingEncoding:NSUTF8StringEncoding]; + [fileHandle writeData:data]; + [fileHandle closeFile]; +} + +// Helper: Get the current timestamp +- (NSString *)currentTimestamp { + NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; + [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"]; + return [formatter stringFromDate:[NSDate date]]; +} + +// Clear the log file (called at app start) +- (void)clearLogs { + [[NSFileManager defaultManager] removeItemAtPath:self.logFilePath error:nil]; + [[NSFileManager defaultManager] createFileAtPath:self.logFilePath contents:nil attributes:nil]; +} + +- (BOOL)isLogFilePopulated { + NSString *logFilePath = [self getLogFilePath]; + + // Check if the file exists + if (![[NSFileManager defaultManager] fileExistsAtPath:logFilePath]) { + return NO; + } + + // Check if the file is non-empty + NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:logFilePath error:nil]; + unsigned long long fileSize = [attributes fileSize]; + + return fileSize > 0; // Return YES if file is populated, NO otherwise +} + +- (void)shareLogFileFromViewController:(UIViewController *)viewController { + NSString *logFilePath = [self getLogFilePath]; + + // Check if the log file exists + if (![[NSFileManager defaultManager] fileExistsAtPath:logFilePath]) { + NSLog(@"No log file found to share."); + return; + } + + // Create a URL from the log file path + NSURL *logFileURL = [NSURL fileURLWithPath:logFilePath]; + + // Create an activity view controller with the log file + UIActivityViewController *activityVC = + [[UIActivityViewController alloc] initWithActivityItems:@[logFileURL] + applicationActivities:nil]; + + // Exclude certain activities if necessary (optional) + activityVC.excludedActivityTypes = @[UIActivityTypePostToFacebook, + UIActivityTypePostToTwitter]; + + // Present the share sheet + [viewController presentViewController:activityVC animated:YES completion:nil]; +} + +@end + +#endif diff --git a/Sources/BranchSDK/BranchLogger.m b/Sources/BranchSDK/BranchLogger.m index d64a65568..0ad29fabf 100644 --- a/Sources/BranchSDK/BranchLogger.m +++ b/Sources/BranchSDK/BranchLogger.m @@ -7,6 +7,9 @@ // #import "BranchLogger.h" +#if !TARGET_OS_TV +#import "BranchFileLogger.h" +#endif #import @implementation BranchLogger @@ -81,6 +84,11 @@ - (void)logMessage:(NSString *)message withLevel:(BranchLogLevel)level error:(NS if (self.logCallback) { self.logCallback(formattedMessage, level, error); } + #if !TARGET_OS_TV + #ifdef DEBUG + [[BranchFileLogger sharedInstance] logMessage:formattedMessage]; + #endif + #endif } - (NSString *)callingClass { diff --git a/Sources/BranchSDK/Private/BNCSystemObserver.h b/Sources/BranchSDK/Private/BNCSystemObserver.h index 9b8119027..dd5d663a5 100644 --- a/Sources/BranchSDK/Private/BNCSystemObserver.h +++ b/Sources/BranchSDK/Private/BNCSystemObserver.h @@ -34,5 +34,6 @@ + (NSString *)environment; + (BOOL)isAppClip; + (BOOL)compareUriSchemes:(NSString *) serverUriScheme; ++ (BOOL)compareLinkDomain:(NSString*) serverLinkDomain; @end diff --git a/Sources/BranchSDK/Public/BranchFileLogger.h b/Sources/BranchSDK/Public/BranchFileLogger.h new file mode 100644 index 000000000..6ded601d7 --- /dev/null +++ b/Sources/BranchSDK/Public/BranchFileLogger.h @@ -0,0 +1,24 @@ +// +// BranchFileLogger.h +// +// +// Created by Sharath Sriram on 15/10/24. +// + +#if !TARGET_OS_TV + +#import +#import + +@interface BranchFileLogger : NSObject + ++ (instancetype)sharedInstance; +- (void)logMessage:(NSString *)message; +- (NSString *)getLogFilePath; +- (void)clearLogs; +- (BOOL)isLogFilePopulated; +- (void)shareLogFileFromViewController:(UIViewController *)viewController; + +@end + +#endif