FBSDKCoreKit/FBSDKCoreKit/AppLink/FBSDKAppLinkUtility.m (264 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*/
#if !TARGET_OS_TV
#import "FBSDKAppLinkUtility+Internal.h"
#import <FBSDKCoreKit_Basics/FBSDKCoreKit_Basics.h>
#import "FBSDKAdvertiserIDProviding.h"
#import "FBSDKAppEventDropDetermining.h"
#import "FBSDKAppEventParametersExtracting.h"
#import "FBSDKAppEventsConfigurationProviding.h"
#import "FBSDKAppLinkURL.h"
#import "FBSDKAppLinkURLCreating.h"
#import "FBSDKGraphRequestFactoryProtocol.h"
#import "FBSDKGraphRequestHTTPMethod.h"
#import "FBSDKGraphRequestProtocol.h"
#import "FBSDKSettingsProtocol.h"
#import "FBSDKUserDataPersisting.h"
static NSString *const FBSDKLastDeferredAppLink = @"com.facebook.sdk:lastDeferredAppLink%@";
static NSString *const FBSDKDeferredAppLinkEvent = @"DEFERRED_APP_LINK";
@interface FBSDKAppLinkUtility ()
@property (class, nonatomic) BOOL isConfigured;
@end
@implementation FBSDKAppLinkUtility
static id<FBSDKGraphRequestFactory> _graphRequestFactory;
static id<FBSDKInfoDictionaryProviding> _infoDictionaryProvider;
static id<FBSDKSettings> _settings;
static id<FBSDKAppEventsConfigurationProviding> _appEventsConfigurationProvider;
static id<FBSDKAdvertiserIDProviding> _advertiserIDProvider;
static id<FBSDKAppEventDropDetermining> _appEventsDropDeterminer;
static id<FBSDKAppEventParametersExtracting> _appEventParametersExtractor;
static id<FBSDKAppLinkURLCreating> _appLinkURLFactory;
static id<FBSDKUserIDProviding> _userIDProvider;
static id<FBSDKUserDataPersisting> _userDataStore;
static BOOL _isConfigured = NO;
+ (void)configureWithGraphRequestFactory:(id<FBSDKGraphRequestFactory>)graphRequestFactory
infoDictionaryProvider:(id<FBSDKInfoDictionaryProviding>)infoDictionaryProvider
settings:(id<FBSDKSettings>)settings
appEventsConfigurationProvider:(id<FBSDKAppEventsConfigurationProviding>)appEventsConfigurationProvider
advertiserIDProvider:(id<FBSDKAdvertiserIDProviding>)advertiserIDProvider
appEventsDropDeterminer:(id<FBSDKAppEventDropDetermining>)appEventsDropDeterminer
appEventParametersExtractor:(id<FBSDKAppEventParametersExtracting>)appEventParametersExtractor
appLinkURLFactory:(id<FBSDKAppLinkURLCreating>)appLinkURLFactory
userIDProvider:(nonnull id<FBSDKUserIDProviding>)userIDProvider
userDataStore:(id<FBSDKUserDataPersisting>)userDataStore
{
if (self == FBSDKAppLinkUtility.class) {
self.graphRequestFactory = graphRequestFactory;
self.infoDictionaryProvider = infoDictionaryProvider;
self.settings = settings;
self.appEventsConfigurationProvider = appEventsConfigurationProvider;
self.advertiserIDProvider = advertiserIDProvider;
self.appEventsDropDeterminer = appEventsDropDeterminer;
self.appEventParametersExtractor = appEventParametersExtractor;
self.appLinkURLFactory = appLinkURLFactory;
self.userIDProvider = userIDProvider;
self.userDataStore = userDataStore;
self.isConfigured = YES;
}
}
// MARK: - Properties
+ (id<FBSDKGraphRequestFactory>)graphRequestFactory
{
return _graphRequestFactory;
}
+ (void)setGraphRequestFactory:(id<FBSDKGraphRequestFactory>)graphRequestFactory
{
_graphRequestFactory = graphRequestFactory;
}
+ (id<FBSDKInfoDictionaryProviding>)infoDictionaryProvider
{
return _infoDictionaryProvider;
}
+ (void)setInfoDictionaryProvider:(id<FBSDKInfoDictionaryProviding>)infoDictionaryProvider
{
_infoDictionaryProvider = infoDictionaryProvider;
}
+ (id<FBSDKSettings>)settings
{
return _settings;
}
+ (void)setSettings:(id<FBSDKSettings>)settings
{
_settings = settings;
}
+ (id<FBSDKAppEventsConfigurationProviding>)appEventsConfigurationProvider
{
return _appEventsConfigurationProvider;
}
+ (void)setAppEventsConfigurationProvider:(id<FBSDKAppEventsConfigurationProviding>)appEventsConfigurationProvider
{
_appEventsConfigurationProvider = appEventsConfigurationProvider;
}
+ (id<FBSDKAdvertiserIDProviding>)advertiserIDProvider
{
return _advertiserIDProvider;
}
+ (void)setAdvertiserIDProvider:(id<FBSDKAdvertiserIDProviding>)advertiserIDProvider
{
_advertiserIDProvider = advertiserIDProvider;
}
+ (id<FBSDKAppEventDropDetermining>)appEventsDropDeterminer
{
return _appEventsDropDeterminer;
}
+ (void)setAppEventsDropDeterminer:(id<FBSDKAppEventDropDetermining>)appEventsDropDeterminer
{
_appEventsDropDeterminer = appEventsDropDeterminer;
}
+ (id<FBSDKAppEventParametersExtracting>)appEventParametersExtractor
{
return _appEventParametersExtractor;
}
+ (void)setAppEventParametersExtractor:(id<FBSDKAppEventParametersExtracting>)appEventParametersExtractor
{
_appEventParametersExtractor = appEventParametersExtractor;
}
+ (id<FBSDKAppLinkURLCreating>)appLinkURLFactory
{
return _appLinkURLFactory;
}
+ (void)setAppLinkURLFactory:(id<FBSDKAppLinkURLCreating>)appLinkURLFactory
{
_appLinkURLFactory = appLinkURLFactory;
}
+ (nullable id<FBSDKUserIDProviding>)userIDProvider
{
return _userIDProvider;
}
+ (void)setUserIDProvider:(nullable id<FBSDKUserIDProviding>)userIDProvider
{
_userIDProvider = userIDProvider;
}
+ (nullable id<FBSDKUserDataPersisting>)userDataStore
{
return _userDataStore;
}
+ (void)setUserDataStore:(nullable id<FBSDKUserDataPersisting>)userDataStore
{
_userDataStore = userDataStore;
}
+ (BOOL)isConfigured
{
return _isConfigured;
}
+ (void)setIsConfigured:(BOOL)isConfigured
{
_isConfigured = isConfigured;
}
// MARK: - Public Methods
+ (void)fetchDeferredAppLink:(nullable FBSDKURLBlock)handler
{
[self validateConfiguration];
NSAssert(NSThread.isMainThread, @"FBSDKAppLink fetchDeferredAppLink: must be invoked from main thread.");
[self.appEventsConfigurationProvider loadAppEventsConfigurationWithBlock:^{
if ([self.appEventsDropDeterminer shouldDropAppEvents]) {
if (handler) {
NSError *error = [[NSError alloc] initWithDomain:@"AdvertiserTrackingEnabled must be enabled" code:-1 userInfo:nil];
handler(nil, error);
}
return;
}
if (@available(iOS 14.5, *)) {
NSString *defaultAdvertiserID = @"00000000-0000-0000-0000-000000000000";
BOOL isAdvertiserIDMissingOrDefault = !self.advertiserIDProvider.advertiserID
|| [self.advertiserIDProvider.advertiserID isEqualToString:defaultAdvertiserID];
if (handler && isAdvertiserIDMissingOrDefault) {
NSError *error = [[NSError alloc] initWithDomain:@"ATTrackingManager.AuthorizationStatus must be `authorized` for deferred deep linking to work. Read more at: https://developer.apple.com/documentation/apptrackingtransparency" code:-1 userInfo:nil];
handler(nil, error);
return;
}
}
// Deferred app links are only currently used for engagement ads, thus we consider the app to be an advertising one.
// If this is considered for organic, non-ads scenarios, we'll need to retrieve the FBAppEventsUtility.shouldAccessAdvertisingID
// before we make this call.
NSMutableDictionary<NSString *, NSString *> *deferredAppLinkParameters =
[self.appEventParametersExtractor activityParametersDictionaryForEvent:FBSDKDeferredAppLinkEvent
shouldAccessAdvertisingID:YES
userID:self.userIDProvider.userID
userData:[self.userDataStore getUserData]];
id<FBSDKGraphRequest> deferredAppLinkRequest = [self.graphRequestFactory createGraphRequestWithGraphPath:[NSString stringWithFormat:@"%@/activities", self.settings.appID, nil]
parameters:deferredAppLinkParameters
tokenString:nil
version:nil
HTTPMethod:FBSDKHTTPMethodPOST];
[deferredAppLinkRequest startWithCompletion:^(id<FBSDKGraphRequestConnecting> connection,
id result,
NSError *error) {
NSURL *applinkURL = nil;
if (!error) {
NSString *appLinkString = result[@"applink_url"];
if (appLinkString) {
applinkURL = [NSURL URLWithString:appLinkString];
NSString *createTimeUtc = result[@"click_time"];
if (createTimeUtc) {
// append/translate the create_time_utc so it can be used by clients
NSString *modifiedURLString = [applinkURL.absoluteString
stringByAppendingFormat:@"%@fb_click_time_utc=%@",
(applinkURL.query) ? @"&" : @"?",
createTimeUtc];
applinkURL = [NSURL URLWithString:modifiedURLString];
}
}
}
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(applinkURL, error);
});
}
}];
}];
}
+ (nullable NSString *)appInvitePromotionCodeFromURL:(NSURL *)url
{
[self validateConfiguration];
id<FBSDKAppLinkURL> parsedUrl = [self.appLinkURLFactory createAppLinkURLWithURL:url];
NSDictionary<NSString *, id> *extras = parsedUrl.appLinkExtras;
if (extras) {
NSString *deeplinkContextString = extras[@"deeplink_context"];
// Parse deeplinkContext and extract promo code
if ([deeplinkContextString isKindOfClass:NSString.class] && deeplinkContextString.length > 0) {
NSError *error = nil;
NSDictionary<id, id> *deeplinkContextData = [FBSDKBasicUtility objectForJSONString:deeplinkContextString error:&error];
if (!error && [deeplinkContextData isKindOfClass:[NSDictionary<NSString *, id> class]]) {
return deeplinkContextData[@"promo_code"];
}
}
}
return nil;
}
+ (BOOL)isMatchURLScheme:(NSString *)scheme
{
if (!scheme) {
return NO;
}
[self validateConfiguration];
for (NSDictionary<NSString *, id> *urlType in [self.infoDictionaryProvider objectForInfoDictionaryKey:@"CFBundleURLTypes"]) {
for (NSString *urlScheme in urlType[@"CFBundleURLSchemes"]) {
if ([urlScheme caseInsensitiveCompare:scheme] == NSOrderedSame) {
return YES;
}
}
}
return NO;
}
// MARK: Configuration Validation
+ (void)validateConfiguration
{
#if DEBUG
if (!_isConfigured) {
static NSString *const reason = @"As of v9.0, you must initialize the SDK prior to calling any methods or setting any properties. "
"You can do this by calling `FBSDKApplicationDelegate`'s `application:didFinishLaunchingWithOptions:` method."
"Learn more: https://developers.facebook.com/docs/ios/getting-started"
"If no `UIApplication` is available you can use `FBSDKApplicationDelegate`'s `initializeSDK` method.";
@throw [NSException exceptionWithName:@"InvalidOperationException" reason:reason userInfo:nil];
}
#endif
}
#if DEBUG && FBTEST
+ (void)reset
{
_isConfigured = NO;
_graphRequestFactory = nil;
_infoDictionaryProvider = nil;
_settings = nil;
_appEventsConfigurationProvider = nil;
_advertiserIDProvider = nil;
_appEventsDropDeterminer = nil;
_appEventParametersExtractor = nil;
_appLinkURLFactory = nil;
_userIDProvider = nil;
_userDataStore = nil;
}
#endif
@end
#endif