| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801 |
- /*
- * 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 {
- // Product requirement is enforced by CocoaPod. Not technical requirement for analytics.
- FIRDependency *analyticsDep = [FIRDependency dependencyWithProtocol:@protocol(FIRAnalyticsInterop)
- isRequired:NO];
- 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];
- // Check for pending Dynamic Link automatically if enabled, otherwise we expect the developer to
- // call strong match FDL API to retrieve a pending link.
- if ([FIRDynamicLinks isAutomaticRetrievalEnabled]) {
- [dynamicLinks checkForPendingDynamicLink];
- }
- return dynamicLinks;
- };
- FIRComponent *dynamicLinksProvider =
- [FIRComponent componentWithProtocol:@protocol(FIRDynamicLinksInstanceProvider)
- instantiationTiming:FIRInstantiationTimingEagerInDefaultApp
- dependencies:@[ analyticsDep ]
- 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 fingerprint 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 *parameters = FIRDLDictionaryFromQuery(url.query);
- if (parameters[kFIRDLParameterLink]) {
- FIRDynamicLink *dynamicLink = [[FIRDynamicLink alloc] init];
- NSString *urlString = parameters[kFIRDLParameterLink];
- NSURL *deepLinkURL = [NSURL URLWithString:urlString];
- if (deepLinkURL) {
- dynamicLink.url = deepLinkURL;
- 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;
- // TODO (b/38035270) inform caller why we failed, for App developer it is hard to debug
- // stuff like this without having source code access
- }
- #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";
- // TODO (b/38397557) Add link to the "Debug FDL" documentation when docs is published
- 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 erorrs, 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
|