mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
821 lines
32 KiB
Objective-C
821 lines
32 KiB
Objective-C
/*
|
|
* Copyright 2018 Google
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#import <TargetConditionals.h>
|
|
#if TARGET_OS_IOS
|
|
|
|
#import "FirebaseDynamicLinks/Sources/Public/FirebaseDynamicLinks/FIRDynamicLinks.h"
|
|
|
|
#import <UIKit/UIKit.h>
|
|
|
|
#ifdef FIRDynamicLinks3P
|
|
#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
|
|
#import "FirebaseDynamicLinks/Sources/FIRDLScionLogging.h"
|
|
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
|
|
#else
|
|
#import "FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h"
|
|
#endif
|
|
|
|
#ifdef FIRDynamicLinks3P
|
|
#import "FirebaseDynamicLinks/Sources/FDLURLComponents/FDLURLComponents+Private.h"
|
|
#endif
|
|
#import "FirebaseDynamicLinks/Sources/FIRDLRetrievalProcessFactory.h"
|
|
#import "FirebaseDynamicLinks/Sources/FIRDLRetrievalProcessProtocols.h"
|
|
#import "FirebaseDynamicLinks/Sources/FIRDLRetrievalProcessResult.h"
|
|
#import "FirebaseDynamicLinks/Sources/FIRDynamicLink+Private.h"
|
|
#import "FirebaseDynamicLinks/Sources/FIRDynamicLinkNetworking.h"
|
|
#import "FirebaseDynamicLinks/Sources/FIRDynamicLinks+FirstParty.h"
|
|
#import "FirebaseDynamicLinks/Sources/FIRDynamicLinks+Private.h"
|
|
#import "FirebaseDynamicLinks/Sources/Logging/FDLLogging.h"
|
|
#import "FirebaseDynamicLinks/Sources/Utilities/FDLUtilities.h"
|
|
|
|
// We should only read the deeplink after install once. We use the following key to store the state
|
|
// in the user defaults.
|
|
NSString *const kFIRDLReadDeepLinkAfterInstallKey =
|
|
@"com.google.appinvite.readDeeplinkAfterInstall";
|
|
|
|
// We should only open url once. We use the following key to store the state in the user defaults.
|
|
static NSString *const kFIRDLOpenURLKey = @"com.google.appinvite.openURL";
|
|
|
|
// Custom domains to be allowed are optionally added as an array to the info.plist.
|
|
static NSString *const kInfoPlistCustomDomainsKey = @"FirebaseDynamicLinksCustomDomains";
|
|
|
|
NS_ASSUME_NONNULL_BEGIN
|
|
|
|
@interface FIRDynamicLinks () <FIRDLRetrievalProcessDelegate>
|
|
|
|
// API Key for API access.
|
|
@property(nonatomic, copy) NSString *APIKey;
|
|
|
|
// Custom URL scheme.
|
|
@property(nonatomic, copy) NSString *URLScheme;
|
|
|
|
// Networking object for Dynamic Links
|
|
@property(nonatomic, readonly) FIRDynamicLinkNetworking *dynamicLinkNetworking;
|
|
|
|
@property(atomic, assign) BOOL retrievingPendingDynamicLink;
|
|
|
|
@end
|
|
|
|
#ifdef FIRDynamicLinks3P
|
|
// Error code from FDL.
|
|
static const NSInteger FIRErrorCodeDurableDeepLinkFailed = -119;
|
|
|
|
@interface FIRDynamicLinks () {
|
|
/// Stored Analytics reference, if it exists.
|
|
id<FIRAnalyticsInterop> _Nullable _analytics;
|
|
}
|
|
@end
|
|
|
|
// DynamicLinks doesn't provide any functionality to other components,
|
|
// so it provides a private, empty protocol that it conforms to and use it for registration.
|
|
|
|
@protocol FIRDynamicLinksInstanceProvider
|
|
@end
|
|
|
|
@interface FIRDynamicLinks () <FIRDynamicLinksInstanceProvider, FIRLibrary>
|
|
|
|
@end
|
|
|
|
#endif
|
|
|
|
@implementation FIRDynamicLinks {
|
|
// User defaults passed.
|
|
NSUserDefaults *_userDefaults;
|
|
|
|
FIRDynamicLinkNetworking *_dynamicLinkNetworking;
|
|
|
|
id<FIRDLRetrievalProcessProtocol> _retrievalProcess;
|
|
}
|
|
|
|
#pragma mark - Object lifecycle
|
|
|
|
#ifdef FIRDynamicLinks3P
|
|
|
|
+ (void)load {
|
|
[FIRApp registerInternalLibrary:self withName:@"fire-dl"];
|
|
}
|
|
|
|
+ (nonnull NSArray<FIRComponent *> *)componentsToRegister {
|
|
FIRComponentCreationBlock creationBlock =
|
|
^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
|
|
// Don't return an instance when it's not the default app.
|
|
if (!container.app.isDefaultApp) {
|
|
// Only configure for the default FIRApp.
|
|
FDLLog(FDLLogLevelInfo, FDLLogIdentifierSetupNonDefaultApp,
|
|
@"Firebase Dynamic Links only "
|
|
"works with the default app.");
|
|
return nil;
|
|
}
|
|
|
|
// Ensure it's cached so it returns the same instance every time dynamicLinks is called.
|
|
*isCacheable = YES;
|
|
id<FIRAnalyticsInterop> analytics = FIR_COMPONENT(FIRAnalyticsInterop, container);
|
|
FIRDynamicLinks *dynamicLinks = [[FIRDynamicLinks alloc] initWithAnalytics:analytics];
|
|
[dynamicLinks configureDynamicLinks:container.app];
|
|
|
|
if ([FIRDynamicLinks isAutomaticRetrievalEnabled]) {
|
|
[dynamicLinks checkForPendingDynamicLink];
|
|
}
|
|
return dynamicLinks;
|
|
};
|
|
FIRComponent *dynamicLinksProvider =
|
|
[FIRComponent componentWithProtocol:@protocol(FIRDynamicLinksInstanceProvider)
|
|
instantiationTiming:FIRInstantiationTimingEagerInDefaultApp
|
|
creationBlock:creationBlock];
|
|
|
|
return @[ dynamicLinksProvider ];
|
|
}
|
|
|
|
- (void)configureDynamicLinks:(FIRApp *)app {
|
|
FIROptions *options = app.options;
|
|
NSError *error;
|
|
NSMutableString *errorDescription;
|
|
NSString *urlScheme;
|
|
|
|
if (options.APIKey.length == 0) {
|
|
errorDescription = [@"API key must not be nil or empty." mutableCopy];
|
|
}
|
|
|
|
if (!errorDescription) {
|
|
// setup FDL if no error detected
|
|
urlScheme = options.deepLinkURLScheme ?: [NSBundle mainBundle].bundleIdentifier;
|
|
[self setUpWithLaunchOptions:nil apiKey:options.APIKey urlScheme:urlScheme userDefaults:nil];
|
|
} else {
|
|
NSString *description =
|
|
[NSString stringWithFormat:@"Configuration failed for service DynamicLinks."];
|
|
NSDictionary *errorDict = @{
|
|
NSLocalizedDescriptionKey : description,
|
|
NSLocalizedFailureReasonErrorKey : errorDescription
|
|
};
|
|
error = [NSError errorWithDomain:kFirebaseDurableDeepLinkErrorDomain
|
|
code:FIRErrorCodeDurableDeepLinkFailed
|
|
userInfo:errorDict];
|
|
}
|
|
if (error) {
|
|
NSString *message = nil;
|
|
if (options.usingOptionsFromDefaultPlist) {
|
|
// Configured using plist file
|
|
message = [NSString
|
|
stringWithFormat:
|
|
@"Firebase Dynamic Links has stopped your project "
|
|
@"because there are missing or incorrect values provided in %@.%@ that may "
|
|
@"prevent your app from behaving as expected:\n\n"
|
|
@"Error: %@\n\n"
|
|
@"Please fix these issues to ensure that Firebase is correctly configured in "
|
|
@"your project.",
|
|
kServiceInfoFileName, kServiceInfoFileType, error.localizedFailureReason];
|
|
} else {
|
|
// Configured manually
|
|
message = [NSString
|
|
stringWithFormat:
|
|
@"Firebase Dynamic Links has stopped your project "
|
|
@"because there are incorrect values provided in Firebase's configuration "
|
|
@"options that may prevent your app from behaving as expected:\n\n"
|
|
@"Error: %@\n\n"
|
|
@"Please fix these issues to ensure that Firebase is correctly configured in "
|
|
@"your project.",
|
|
error.localizedFailureReason];
|
|
}
|
|
[NSException raise:kFirebaseDurableDeepLinkErrorDomain format:@"%@", message];
|
|
}
|
|
[self checkForCustomDomainEntriesInInfoPlist];
|
|
}
|
|
|
|
- (instancetype)initWithAnalytics:(nullable id<FIRAnalyticsInterop>)analytics {
|
|
self = [super init];
|
|
if (self) {
|
|
_analytics = analytics;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
+ (instancetype)dynamicLinks {
|
|
FIRApp *defaultApp = [FIRApp defaultApp]; // Missing configure will be logged here.
|
|
id<FIRDynamicLinksInstanceProvider> instance =
|
|
FIR_COMPONENT(FIRDynamicLinksInstanceProvider, defaultApp.container);
|
|
return (FIRDynamicLinks *)instance;
|
|
}
|
|
|
|
#else
|
|
+ (instancetype)dynamicLinks {
|
|
static FIRDynamicLinks *dynamicLinks;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
dynamicLinks = [[self alloc] init];
|
|
});
|
|
return dynamicLinks;
|
|
}
|
|
#endif
|
|
|
|
#pragma mark - Custom domains
|
|
|
|
- (instancetype)init {
|
|
self = [super init];
|
|
if (self) {
|
|
[self checkForCustomDomainEntriesInInfoPlist];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
// Check for custom domains entry in PLIST file.
|
|
- (void)checkForCustomDomainEntriesInInfoPlist {
|
|
// Check to see if FirebaseDynamicLinksCustomDomains array is present.
|
|
NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary;
|
|
NSArray *customDomains = infoDictionary[kInfoPlistCustomDomainsKey];
|
|
if (customDomains) {
|
|
FIRDLAddToAllowListForCustomDomainsArray(customDomains);
|
|
}
|
|
}
|
|
|
|
#pragma mark - First party interface
|
|
|
|
- (BOOL)setUpWithLaunchOptions:(nullable NSDictionary *)launchOptions
|
|
apiKey:(NSString *)apiKey
|
|
urlScheme:(nullable NSString *)urlScheme
|
|
userDefaults:(nullable NSUserDefaults *)userDefaults {
|
|
if (apiKey == nil) {
|
|
FDLLog(FDLLogLevelError, FDLLogIdentifierSetupNilAPIKey, @"API Key must not be nil.");
|
|
return NO;
|
|
}
|
|
|
|
_APIKey = [apiKey copy];
|
|
_URLScheme = urlScheme.length ? [urlScheme copy] : [NSBundle mainBundle].bundleIdentifier;
|
|
|
|
if (!userDefaults) {
|
|
_userDefaults = [NSUserDefaults standardUserDefaults];
|
|
} else {
|
|
_userDefaults = userDefaults;
|
|
}
|
|
|
|
NSURL *url = launchOptions[UIApplicationLaunchOptionsURLKey];
|
|
if (url) {
|
|
if ([self canParseCustomSchemeURL:url] || [self canParseUniversalLinkURL:url]) {
|
|
// Make sure we don't call |checkForPendingDynamicLink| again if
|
|
// a strong deep link is found.
|
|
[_userDefaults setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];
|
|
}
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (void)checkForPendingDynamicLinkUsingExperimentalRetrievalProcess {
|
|
[self checkForPendingDynamicLink];
|
|
}
|
|
|
|
- (void)checkForPendingDynamicLink {
|
|
// Make sure this method is called only once after the application was installed.
|
|
// kFIRDLOpenURLKey marks checkForPendingDynamic link had been called already so no need to do it
|
|
// again. kFIRDLReadDeepLinkAfterInstallKey marks we have already read a deeplink after the
|
|
// install and so no need to do check for pending dynamic link.
|
|
BOOL appInviteDeepLinkRead = [_userDefaults boolForKey:kFIRDLOpenURLKey] ||
|
|
[_userDefaults boolForKey:kFIRDLReadDeepLinkAfterInstallKey];
|
|
|
|
if (appInviteDeepLinkRead || self.retrievingPendingDynamicLink) {
|
|
NSString *errorDescription =
|
|
appInviteDeepLinkRead ? NSLocalizedString(@"Link was already retrieved", @"Error message")
|
|
: NSLocalizedString(@"Already retrieving link", @"Error message");
|
|
[self handlePendingDynamicLinkRetrievalFailureWithErrorCode:-1
|
|
errorDescription:errorDescription
|
|
underlyingError:nil];
|
|
return;
|
|
}
|
|
|
|
self.retrievingPendingDynamicLink = YES;
|
|
|
|
FIRDLRetrievalProcessFactory *factory =
|
|
[[FIRDLRetrievalProcessFactory alloc] initWithNetworkingService:self.dynamicLinkNetworking
|
|
URLScheme:_URLScheme
|
|
APIKey:_APIKey
|
|
FDLSDKVersion:FIRFirebaseVersion()
|
|
delegate:self];
|
|
_retrievalProcess = [factory automaticRetrievalProcess];
|
|
[_retrievalProcess retrievePendingDynamicLink];
|
|
}
|
|
|
|
// Disable deprecated warning for internal methods.
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
|
|
|
|
+ (instancetype)sharedInstance {
|
|
return [self dynamicLinks];
|
|
}
|
|
|
|
- (BOOL)setUpWithLaunchOptions:(nullable NSDictionary *)launchOptions
|
|
apiKey:(NSString *)apiKey
|
|
clientID:(NSString *)clientID
|
|
urlScheme:(nullable NSString *)urlScheme
|
|
userDefaults:(nullable NSUserDefaults *)userDefaults {
|
|
return [self setUpWithLaunchOptions:launchOptions
|
|
apiKey:apiKey
|
|
urlScheme:urlScheme
|
|
userDefaults:userDefaults];
|
|
}
|
|
|
|
- (void)checkForPendingDeepLink {
|
|
[self checkForPendingDynamicLink];
|
|
}
|
|
|
|
- (nullable FIRDynamicLink *)deepLinkFromCustomSchemeURL:(NSURL *)url {
|
|
return [self dynamicLinkFromCustomSchemeURL:url];
|
|
}
|
|
|
|
- (nullable FIRDynamicLink *)deepLinkFromUniversalLinkURL:(NSURL *)url {
|
|
return [self dynamicLinkFromUniversalLinkURL:url];
|
|
}
|
|
|
|
- (BOOL)shouldHandleDeepLinkFromCustomSchemeURL:(NSURL *)url {
|
|
return [self shouldHandleDynamicLinkFromCustomSchemeURL:url];
|
|
}
|
|
|
|
#pragma clang pop
|
|
|
|
#pragma mark - Public interface
|
|
|
|
- (BOOL)shouldHandleDynamicLinkFromCustomSchemeURL:(NSURL *)url {
|
|
// Return NO if the URL scheme does not match.
|
|
if (![self canParseCustomSchemeURL:url]) {
|
|
return NO;
|
|
}
|
|
|
|
// We can handle "/link" and "/link/dismiss". The latter will return a nil deep link.
|
|
return ([url.path hasPrefix:@"/link"] && [url.host isEqualToString:@"google"]);
|
|
}
|
|
|
|
- (nullable FIRDynamicLink *)dynamicLinkFromCustomSchemeURL:(NSURL *)url {
|
|
// Return nil if the URL scheme does not match.
|
|
if (![self canParseCustomSchemeURL:url]) {
|
|
return nil;
|
|
}
|
|
|
|
if ([url.path isEqualToString:@"/link"] && [url.host isEqualToString:@"google"]) {
|
|
// This URL is a callback url from a device heuristics based match
|
|
// Extract information from query.
|
|
NSString *query = url.query;
|
|
|
|
NSDictionary *parameters = FIRDLDictionaryFromQuery(query);
|
|
|
|
// As long as the deepLink has some parameter, return it.
|
|
if (parameters.count > 0) {
|
|
FIRDynamicLink *dynamicLink =
|
|
[[FIRDynamicLink alloc] initWithParametersDictionary:parameters];
|
|
|
|
#ifdef GIN_SCION_LOGGING
|
|
if (dynamicLink.url) {
|
|
BOOL isFirstOpen = ![_userDefaults boolForKey:kFIRDLReadDeepLinkAfterInstallKey];
|
|
FIRDLLogEvent event = isFirstOpen ? FIRDLLogEventFirstOpen : FIRDLLogEventAppOpen;
|
|
FIRDLLogEventToScion(event, parameters[kFIRDLParameterSource],
|
|
parameters[kFIRDLParameterMedium], parameters[kFIRDLParameterCampaign],
|
|
_analytics);
|
|
}
|
|
#endif
|
|
// Make sure we don't call |checkForPendingDynamicLink| again if we did this already.
|
|
if ([_userDefaults boolForKey:kFIRDLOpenURLKey]) {
|
|
[_userDefaults setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];
|
|
}
|
|
return dynamicLink;
|
|
}
|
|
}
|
|
return nil;
|
|
}
|
|
|
|
- (nullable FIRDynamicLink *)
|
|
dynamicLinkInternalFromUniversalLinkURL:(NSURL *)url
|
|
completion:
|
|
(nullable FIRDynamicLinkUniversalLinkHandler)completion {
|
|
// Make sure the completion is always called on the main queue.
|
|
FIRDynamicLinkUniversalLinkHandler mainQueueCompletion =
|
|
^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
|
|
if (completion) {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(dynamicLink, error);
|
|
});
|
|
}
|
|
};
|
|
|
|
if ([self canParseUniversalLinkURL:url]) {
|
|
if (url.query.length > 0) {
|
|
NSDictionary<NSString *, NSString *> *parameters = FIRDLDictionaryFromQuery(url.query);
|
|
if (parameters[kFIRDLParameterLink]) {
|
|
NSString *urlString = parameters[kFIRDLParameterLink];
|
|
NSURL *deepLinkURL = [NSURL URLWithString:urlString];
|
|
if (deepLinkURL) {
|
|
NSMutableDictionary *paramsDictionary = [[NSMutableDictionary alloc]
|
|
initWithDictionary:@{kFIRDLParameterDeepLinkIdentifier : urlString}];
|
|
|
|
if (parameters[kFIRDLParameterSource] != nil) {
|
|
[paramsDictionary setValue:parameters[kFIRDLParameterSource]
|
|
forKey:kFIRDLParameterSource];
|
|
}
|
|
|
|
if (parameters[kFIRDLParameterMedium] != nil) {
|
|
[paramsDictionary setValue:parameters[kFIRDLParameterMedium]
|
|
forKey:kFIRDLParameterMedium];
|
|
}
|
|
|
|
if (parameters[kFIRDLParameterTerm] != nil) {
|
|
[paramsDictionary setValue:parameters[kFIRDLParameterTerm] forKey:kFIRDLParameterTerm];
|
|
}
|
|
|
|
if (parameters[kFIRDLParameterCampaign] != nil) {
|
|
[paramsDictionary setValue:parameters[kFIRDLParameterCampaign]
|
|
forKey:(kFIRDLParameterCampaign)];
|
|
}
|
|
|
|
if (parameters[kFIRDLParameterContent] != nil) {
|
|
[paramsDictionary setValue:parameters[kFIRDLParameterContent]
|
|
forKey:kFIRDLParameterContent];
|
|
}
|
|
|
|
FIRDynamicLink *dynamicLink =
|
|
[[FIRDynamicLink alloc] initWithParametersDictionary:paramsDictionary];
|
|
dynamicLink.matchType = FIRDLMatchTypeUnique;
|
|
dynamicLink.minimumAppVersion = parameters[kFIRDLParameterMinimumAppVersion];
|
|
|
|
// Call resolveShortLink:completion: to do logging.
|
|
// TODO: Create dedicated logging function to prevent this.
|
|
[self.dynamicLinkNetworking
|
|
resolveShortLink:url
|
|
FDLSDKVersion:FIRFirebaseVersion()
|
|
completion:^(NSURL *_Nullable resolverURL, NSError *_Nullable resolverError) {
|
|
mainQueueCompletion(dynamicLink, resolverError);
|
|
}];
|
|
#ifdef GIN_SCION_LOGGING
|
|
FIRDLLogEventToScion(FIRDLLogEventAppOpen, parameters[kFIRDLParameterSource],
|
|
parameters[kFIRDLParameterMedium],
|
|
parameters[kFIRDLParameterCampaign], _analytics);
|
|
#endif
|
|
return dynamicLink;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
mainQueueCompletion(
|
|
nil, [[NSError alloc] initWithDomain:@"com.firebase.dynamicLinks"
|
|
code:1
|
|
userInfo:@{
|
|
NSLocalizedFailureReasonErrorKey :
|
|
@"Universal link URL could not be parsed by Dynamic Links."
|
|
}]);
|
|
return nil;
|
|
}
|
|
|
|
- (nullable FIRDynamicLink *)dynamicLinkFromUniversalLinkURL:(NSURL *)url {
|
|
return [self dynamicLinkInternalFromUniversalLinkURL:url completion:nil];
|
|
}
|
|
|
|
- (void)dynamicLinkFromUniversalLinkURL:(NSURL *)url
|
|
completion:(FIRDynamicLinkUniversalLinkHandler)completion {
|
|
[self dynamicLinkInternalFromUniversalLinkURL:url completion:completion];
|
|
}
|
|
|
|
- (BOOL)handleUniversalLink:(NSURL *)universalLinkURL
|
|
completion:(FIRDynamicLinkUniversalLinkHandler)completion {
|
|
if ([self matchesShortLinkFormat:universalLinkURL]) {
|
|
__weak __typeof__(self) weakSelf = self;
|
|
[self resolveShortLink:universalLinkURL
|
|
completion:^(NSURL *url, NSError *error) {
|
|
__typeof__(self) strongSelf = weakSelf;
|
|
if (strongSelf) {
|
|
FIRDynamicLink *dynamicLink = [strongSelf dynamicLinkFromCustomSchemeURL:url];
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
completion(dynamicLink, error);
|
|
});
|
|
} else {
|
|
completion(nil, nil);
|
|
}
|
|
}];
|
|
return YES;
|
|
} else {
|
|
[self dynamicLinkFromUniversalLinkURL:universalLinkURL completion:completion];
|
|
BOOL canHandleUniversalLink =
|
|
[self canParseUniversalLinkURL:universalLinkURL] && universalLinkURL.query.length > 0 &&
|
|
FIRDLDictionaryFromQuery(universalLinkURL.query)[kFIRDLParameterLink];
|
|
return canHandleUniversalLink;
|
|
}
|
|
}
|
|
|
|
- (void)resolveShortLink:(NSURL *)url completion:(FIRDynamicLinkResolverHandler)completion {
|
|
[self.dynamicLinkNetworking resolveShortLink:url
|
|
FDLSDKVersion:FIRFirebaseVersion()
|
|
completion:completion];
|
|
}
|
|
|
|
- (BOOL)matchesShortLinkFormat:(NSURL *)url {
|
|
return FIRDLMatchesShortLinkFormat(url);
|
|
}
|
|
|
|
#pragma mark - Private interface
|
|
|
|
+ (BOOL)isAutomaticRetrievalEnabled {
|
|
id retrievalEnabledValue =
|
|
[[NSBundle mainBundle] infoDictionary][@"FirebaseDeepLinkAutomaticRetrievalEnabled"];
|
|
if ([retrievalEnabledValue respondsToSelector:@selector(boolValue)]) {
|
|
return [retrievalEnabledValue boolValue];
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
#pragma mark - Internal methods
|
|
|
|
- (FIRDynamicLinkNetworking *)dynamicLinkNetworking {
|
|
if (!_dynamicLinkNetworking) {
|
|
_dynamicLinkNetworking = [[FIRDynamicLinkNetworking alloc] initWithAPIKey:_APIKey
|
|
URLScheme:_URLScheme];
|
|
}
|
|
return _dynamicLinkNetworking;
|
|
}
|
|
|
|
- (BOOL)canParseCustomSchemeURL:(nullable NSURL *)url {
|
|
if (url.scheme.length) {
|
|
NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
|
|
if ([url.scheme.lowercaseString isEqualToString:_URLScheme.lowercaseString] ||
|
|
[url.scheme.lowercaseString isEqualToString:bundleIdentifier.lowercaseString]) {
|
|
return YES;
|
|
}
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (BOOL)canParseUniversalLinkURL:(nullable NSURL *)url {
|
|
return FIRDLCanParseUniversalLinkURL(url);
|
|
}
|
|
|
|
- (BOOL)handleIncomingCustomSchemeDeepLink:(NSURL *)url {
|
|
return [self canParseCustomSchemeURL:url];
|
|
}
|
|
|
|
- (void)passRetrievedDynamicLinkToApplication:(NSURL *)url {
|
|
id<UIApplicationDelegate> applicationDelegate = [UIApplication sharedApplication].delegate;
|
|
if ([self isOpenUrlMethodPresentInAppDelegate:applicationDelegate]) {
|
|
// pass url directly to application delegate to avoid hop into
|
|
// iOS handling of the universal links
|
|
[applicationDelegate application:[UIApplication sharedApplication] openURL:url options:@{}];
|
|
return;
|
|
}
|
|
|
|
[[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
|
|
}
|
|
|
|
- (BOOL)isOpenUrlMethodPresentInAppDelegate:(id<UIApplicationDelegate>)applicationDelegate {
|
|
return applicationDelegate &&
|
|
[applicationDelegate respondsToSelector:@selector(application:openURL:options:)];
|
|
}
|
|
|
|
- (void)handlePendingDynamicLinkRetrievalFailureWithErrorCode:(NSInteger)errorCode
|
|
errorDescription:(NSString *)errorDescription
|
|
underlyingError:(nullable NSError *)underlyingError {
|
|
self.retrievingPendingDynamicLink = NO;
|
|
}
|
|
|
|
#pragma mark - FIRDLRetrievalProcessDelegate
|
|
|
|
- (void)retrievalProcess:(id<FIRDLRetrievalProcessProtocol>)retrievalProcess
|
|
completedWithResult:(FIRDLRetrievalProcessResult *)result {
|
|
self.retrievingPendingDynamicLink = NO;
|
|
_retrievalProcess = nil;
|
|
|
|
if (![_userDefaults boolForKey:kFIRDLOpenURLKey]) {
|
|
// Once we complete the Pending dynamic link retrieval, regardless of whether the retrieval is
|
|
// success or failure, we don't want to do the retrieval again on next app start.
|
|
// If we try to redo the retrieval again because of some error, the user will experience
|
|
// unwanted deeplinking when they restart the app next time.
|
|
[_userDefaults setBool:YES forKey:kFIRDLOpenURLKey];
|
|
}
|
|
|
|
NSURL *linkToPassToApp = [result URLWithCustomURLScheme:_URLScheme];
|
|
[self passRetrievedDynamicLinkToApplication:linkToPassToApp];
|
|
}
|
|
|
|
#pragma mark - Diagnostics methods
|
|
|
|
static NSString *kSelfDiagnoseOutputHeader =
|
|
@"---- Firebase Dynamic Links diagnostic output start ----\n";
|
|
static NSString *kSelfDiagnoseOutputFooter =
|
|
@"---- Firebase Dynamic Links diagnostic output end ----\n";
|
|
|
|
+ (NSString *)genericDiagnosticInformation {
|
|
NSMutableString *genericDiagnosticInfo = [[NSMutableString alloc] init];
|
|
|
|
[genericDiagnosticInfo
|
|
appendFormat:@"Firebase Dynamic Links framework version %@\n", FIRFirebaseVersion()];
|
|
[genericDiagnosticInfo appendFormat:@"System information: OS %@, OS version %@, model %@\n",
|
|
[UIDevice currentDevice].systemName,
|
|
[UIDevice currentDevice].systemVersion,
|
|
[UIDevice currentDevice].model];
|
|
[genericDiagnosticInfo appendFormat:@"Current date %@\n", [NSDate date]];
|
|
// TODO: bring this diagnostic info back when we shipped non-automatic retrieval
|
|
// [genericDiagnosticInfo appendFormat:@"AutomaticRetrievalEnabled: %@\n",
|
|
// [self isAutomaticRetrievalEnabled] ? @"YES" : @"NO"];
|
|
|
|
// Disable deprecated warning for internal methods.
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
[genericDiagnosticInfo appendFormat:@"Device locale %@ (raw %@), timezone %@\n",
|
|
FIRDLDeviceLocale(), FIRDLDeviceLocaleRaw(),
|
|
FIRDLDeviceTimezone()];
|
|
#pragma clang pop
|
|
|
|
return genericDiagnosticInfo;
|
|
}
|
|
|
|
+ (NSString *)diagnosticAnalyzeEntitlements {
|
|
NSString *embeddedMobileprovisionFilePath = [[[NSBundle mainBundle] bundlePath]
|
|
stringByAppendingPathComponent:@"embedded.mobileprovision"];
|
|
|
|
NSError *error;
|
|
NSMutableData *profileData = [NSMutableData dataWithContentsOfFile:embeddedMobileprovisionFilePath
|
|
options:0
|
|
error:&error];
|
|
|
|
if (!profileData.length || error) {
|
|
return @"\tSKIPPED: Not able to read entitlements (embedded.mobileprovision).\n";
|
|
}
|
|
|
|
// The "embedded.mobileprovision" sometimes contains characters with value 0, which signals the
|
|
// end of a c-string and halts the ASCII parser, or with value > 127, which violates strict 7-bit
|
|
// ASCII. Replace any 0s or invalid characters in the input.
|
|
uint8_t *profileBytes = (uint8_t *)profileData.bytes;
|
|
for (int i = 0; i < profileData.length; i++) {
|
|
uint8_t currentByte = profileBytes[i];
|
|
if (!currentByte || currentByte > 127) {
|
|
profileBytes[i] = '.';
|
|
}
|
|
}
|
|
|
|
NSString *embeddedProfile = [[NSString alloc] initWithBytesNoCopy:profileBytes
|
|
length:profileData.length
|
|
encoding:NSASCIIStringEncoding
|
|
freeWhenDone:NO];
|
|
|
|
if (error || !embeddedProfile.length) {
|
|
return @"\tSKIPPED: Not able to read entitlements (embedded.mobileprovision).\n";
|
|
}
|
|
|
|
NSScanner *scanner = [NSScanner scannerWithString:embeddedProfile];
|
|
NSString *plistContents;
|
|
if ([scanner scanUpToString:@"<plist" intoString:nil]) {
|
|
if ([scanner scanUpToString:@"</plist>" intoString:&plistContents]) {
|
|
plistContents = [plistContents stringByAppendingString:@"</plist>"];
|
|
}
|
|
}
|
|
|
|
if (!plistContents.length) {
|
|
return @"\tWARNING: Not able to read plist entitlements (embedded.mobileprovision).\n";
|
|
}
|
|
|
|
NSData *data = [plistContents dataUsingEncoding:NSUTF8StringEncoding];
|
|
if (!data.length) {
|
|
return @"\tWARNING: Not able to parse entitlements (embedded.mobileprovision).\n";
|
|
}
|
|
|
|
NSError *plistMapError;
|
|
id plistData = [NSPropertyListSerialization propertyListWithData:data
|
|
options:NSPropertyListImmutable
|
|
format:nil
|
|
error:&plistMapError];
|
|
if (plistMapError || ![plistData isKindOfClass:[NSDictionary class]]) {
|
|
return @"\tWARNING: Not able to deserialize entitlements (embedded.mobileprovision).\n";
|
|
}
|
|
NSDictionary *plistMap = (NSDictionary *)plistData;
|
|
|
|
// analyze entitlements and print diagnostic information
|
|
// we can't detect errors, information p[rinted here may hint developer or will help support
|
|
// to identify the issue
|
|
NSMutableString *outputString = [[NSMutableString alloc] init];
|
|
|
|
NSArray *appIdentifierPrefixes = plistMap[@"ApplicationIdentifierPrefix"];
|
|
NSString *teamID = plistMap[@"Entitlements"][@"com.apple.developer.team-identifier"];
|
|
|
|
if (appIdentifierPrefixes.count > 1) {
|
|
// is this possible? anyway, we can handle it
|
|
[outputString
|
|
appendFormat:@"\tAppID Prefixes: %@, Team ID: %@, AppId Prefixes contains to Team ID: %@\n",
|
|
appIdentifierPrefixes, teamID,
|
|
([appIdentifierPrefixes containsObject:teamID] ? @"YES" : @"NO")];
|
|
} else {
|
|
[outputString
|
|
appendFormat:@"\tAppID Prefix: %@, Team ID: %@, AppId Prefix equal to Team ID: %@\n",
|
|
appIdentifierPrefixes[0], teamID,
|
|
([appIdentifierPrefixes[0] isEqualToString:teamID] ? @"YES" : @"NO")];
|
|
}
|
|
|
|
return outputString;
|
|
}
|
|
|
|
+ (NSString *)performDiagnosticsIncludingHeaderFooter:(BOOL)includingHeaderFooter
|
|
detectedErrors:(nullable NSInteger *)detectedErrors {
|
|
NSMutableString *diagnosticString = [[NSMutableString alloc] init];
|
|
if (includingHeaderFooter) {
|
|
[diagnosticString appendString:@"\n"];
|
|
[diagnosticString appendString:kSelfDiagnoseOutputHeader];
|
|
}
|
|
|
|
NSInteger detectedErrorsCnt = 0;
|
|
|
|
[diagnosticString appendString:[self genericDiagnosticInformation]];
|
|
|
|
#if TARGET_IPHONE_SIMULATOR
|
|
// check is Simulator and print WARNING that Universal Links is not supported on Simulator
|
|
[diagnosticString
|
|
appendString:@"WARNING: iOS Simulator does not support Universal Links. Firebase "
|
|
@"Dynamic Links SDK functionality will be limited. Some FDL "
|
|
@"features may be missing or will not work correctly.\n"];
|
|
#endif // TARGET_IPHONE_SIMULATOR
|
|
|
|
id<UIApplicationDelegate> applicationDelegate = [UIApplication sharedApplication].delegate;
|
|
if (![applicationDelegate respondsToSelector:@selector(application:openURL:options:)]) {
|
|
detectedErrorsCnt++;
|
|
[diagnosticString appendFormat:@"ERROR: UIApplication delegate %@ does not implements selector "
|
|
@"%@. FDL depends on this implementation to retrieve pending "
|
|
@"dynamic link.\n",
|
|
applicationDelegate,
|
|
NSStringFromSelector(@selector(application:openURL:options:))];
|
|
}
|
|
|
|
// check that Info.plist has custom URL scheme and the scheme is the same as bundleID or
|
|
// as customURLScheme passed to FDL iOS SDK
|
|
NSString *URLScheme = [FIRDynamicLinks dynamicLinks].URLScheme;
|
|
BOOL URLSchemeFoundInPlist = NO;
|
|
NSArray *URLSchemesFromInfoPlist = [[NSBundle mainBundle] infoDictionary][@"CFBundleURLTypes"];
|
|
for (NSDictionary *schemeDetails in URLSchemesFromInfoPlist) {
|
|
NSArray *arrayOfSchemes = schemeDetails[@"CFBundleURLSchemes"];
|
|
for (NSString *scheme in arrayOfSchemes) {
|
|
if ([scheme isEqualToString:URLScheme]) {
|
|
URLSchemeFoundInPlist = YES;
|
|
break;
|
|
}
|
|
}
|
|
if (URLSchemeFoundInPlist) {
|
|
break;
|
|
}
|
|
}
|
|
if (!URLSchemeFoundInPlist) {
|
|
detectedErrorsCnt++;
|
|
[diagnosticString appendFormat:@"ERROR: Specified custom URL scheme is %@ but Info.plist do "
|
|
@"not contain such scheme in "
|
|
"CFBundleURLTypes key.\n",
|
|
URLScheme];
|
|
} else {
|
|
[diagnosticString appendFormat:@"\tSpecified custom URL scheme is %@ and Info.plist contains "
|
|
@"such scheme in CFBundleURLTypes key.\n",
|
|
URLScheme];
|
|
}
|
|
|
|
#if !TARGET_IPHONE_SIMULATOR
|
|
// analyse information in entitlements file
|
|
NSString *entitlementsAnalysis = [self diagnosticAnalyzeEntitlements];
|
|
if (entitlementsAnalysis.length) {
|
|
[diagnosticString appendString:entitlementsAnalysis];
|
|
}
|
|
#endif // TARGET_IPHONE_SIMULATOR
|
|
|
|
if (includingHeaderFooter) {
|
|
if (detectedErrorsCnt == 0) {
|
|
[diagnosticString
|
|
appendString:@"performDiagnostic completed successfully! No errors found.\n"];
|
|
} else {
|
|
[diagnosticString
|
|
appendFormat:@"performDiagnostic detected %ld ERRORS.\n", (long)detectedErrorsCnt];
|
|
}
|
|
[diagnosticString appendString:kSelfDiagnoseOutputFooter];
|
|
}
|
|
if (detectedErrors) {
|
|
*detectedErrors = detectedErrorsCnt;
|
|
}
|
|
return [diagnosticString copy];
|
|
}
|
|
|
|
+ (void)performDiagnosticsWithCompletion:(void (^_Nullable)(NSString *diagnosticOutput,
|
|
BOOL hasErrors))completionHandler;
|
|
{
|
|
NSInteger detectedErrorsCnt = 0;
|
|
NSString *diagnosticString = [self performDiagnosticsIncludingHeaderFooter:YES
|
|
detectedErrors:&detectedErrorsCnt];
|
|
if (completionHandler) {
|
|
completionHandler(diagnosticString, detectedErrorsCnt > 0);
|
|
} else {
|
|
NSLog(@"%@", diagnosticString);
|
|
}
|
|
}
|
|
|
|
@end
|
|
|
|
NS_ASSUME_NONNULL_END
|
|
|
|
#endif // TARGET_OS_IOS
|