FIRDynamicLinks.m 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824
  1. /*
  2. * Copyright 2018 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <TargetConditionals.h>
  17. #if TARGET_OS_IOS
  18. #import "FirebaseDynamicLinks/Sources/Public/FirebaseDynamicLinks/FIRDynamicLinks.h"
  19. #import <UIKit/UIKit.h>
  20. #ifdef FIRDynamicLinks3P
  21. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  22. #import "FirebaseDynamicLinks/Sources/FIRDLScionLogging.h"
  23. #import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
  24. #else
  25. #import "FirebaseCore/Sources/Public/FirebaseCore/FIRVersion.h"
  26. #endif
  27. #ifdef FIRDynamicLinks3P
  28. #import "FirebaseDynamicLinks/Sources/FDLURLComponents/FDLURLComponents+Private.h"
  29. #endif
  30. #import "FirebaseDynamicLinks/Sources/FIRDLRetrievalProcessFactory.h"
  31. #import "FirebaseDynamicLinks/Sources/FIRDLRetrievalProcessProtocols.h"
  32. #import "FirebaseDynamicLinks/Sources/FIRDLRetrievalProcessResult.h"
  33. #import "FirebaseDynamicLinks/Sources/FIRDynamicLink+Private.h"
  34. #import "FirebaseDynamicLinks/Sources/FIRDynamicLinkNetworking.h"
  35. #import "FirebaseDynamicLinks/Sources/FIRDynamicLinks+FirstParty.h"
  36. #import "FirebaseDynamicLinks/Sources/FIRDynamicLinks+Private.h"
  37. #import "FirebaseDynamicLinks/Sources/Logging/FDLLogging.h"
  38. #import "FirebaseDynamicLinks/Sources/Utilities/FDLUtilities.h"
  39. // We should only read the deeplink after install once. We use the following key to store the state
  40. // in the user defaults.
  41. NSString *const kFIRDLReadDeepLinkAfterInstallKey =
  42. @"com.google.appinvite.readDeeplinkAfterInstall";
  43. // We should only open url once. We use the following key to store the state in the user defaults.
  44. static NSString *const kFIRDLOpenURLKey = @"com.google.appinvite.openURL";
  45. // Custom domains to be allowed are optionally added as an array to the info.plist.
  46. static NSString *const kInfoPlistCustomDomainsKey = @"FirebaseDynamicLinksCustomDomains";
  47. NS_ASSUME_NONNULL_BEGIN
  48. @interface FIRDynamicLinks () <FIRDLRetrievalProcessDelegate>
  49. // API Key for API access.
  50. @property(nonatomic, copy) NSString *APIKey;
  51. // Custom URL scheme.
  52. @property(nonatomic, copy) NSString *URLScheme;
  53. // Networking object for Dynamic Links
  54. @property(nonatomic, readonly) FIRDynamicLinkNetworking *dynamicLinkNetworking;
  55. @property(atomic, assign) BOOL retrievingPendingDynamicLink;
  56. @end
  57. #ifdef FIRDynamicLinks3P
  58. // Error code from FDL.
  59. static const NSInteger FIRErrorCodeDurableDeepLinkFailed = -119;
  60. @interface FIRDynamicLinks () {
  61. /// Stored Analytics reference, if it exists.
  62. id<FIRAnalyticsInterop> _Nullable _analytics;
  63. }
  64. @end
  65. // DynamicLinks doesn't provide any functionality to other components,
  66. // so it provides a private, empty protocol that it conforms to and use it for registration.
  67. @protocol FIRDynamicLinksInstanceProvider
  68. @end
  69. @interface FIRDynamicLinks () <FIRDynamicLinksInstanceProvider, FIRLibrary>
  70. @end
  71. #endif
  72. @implementation FIRDynamicLinks {
  73. // User defaults passed.
  74. NSUserDefaults *_userDefaults;
  75. FIRDynamicLinkNetworking *_dynamicLinkNetworking;
  76. id<FIRDLRetrievalProcessProtocol> _retrievalProcess;
  77. }
  78. #pragma mark - Object lifecycle
  79. #ifdef FIRDynamicLinks3P
  80. + (void)load {
  81. [FIRApp registerInternalLibrary:self withName:@"fire-dl"];
  82. }
  83. + (nonnull NSArray<FIRComponent *> *)componentsToRegister {
  84. // Product requirement is enforced by CocoaPod. Not technical requirement for analytics.
  85. FIRDependency *analyticsDep = [FIRDependency dependencyWithProtocol:@protocol(FIRAnalyticsInterop)
  86. isRequired:NO];
  87. FIRComponentCreationBlock creationBlock =
  88. ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
  89. // Don't return an instance when it's not the default app.
  90. if (!container.app.isDefaultApp) {
  91. // Only configure for the default FIRApp.
  92. FDLLog(FDLLogLevelInfo, FDLLogIdentifierSetupNonDefaultApp,
  93. @"Firebase Dynamic Links only "
  94. "works with the default app.");
  95. return nil;
  96. }
  97. // Ensure it's cached so it returns the same instance every time dynamicLinks is called.
  98. *isCacheable = YES;
  99. id<FIRAnalyticsInterop> analytics = FIR_COMPONENT(FIRAnalyticsInterop, container);
  100. FIRDynamicLinks *dynamicLinks = [[FIRDynamicLinks alloc] initWithAnalytics:analytics];
  101. [dynamicLinks configureDynamicLinks:container.app];
  102. if ([FIRDynamicLinks isAutomaticRetrievalEnabled]) {
  103. [dynamicLinks checkForPendingDynamicLink];
  104. }
  105. return dynamicLinks;
  106. };
  107. FIRComponent *dynamicLinksProvider =
  108. [FIRComponent componentWithProtocol:@protocol(FIRDynamicLinksInstanceProvider)
  109. instantiationTiming:FIRInstantiationTimingEagerInDefaultApp
  110. dependencies:@[ analyticsDep ]
  111. creationBlock:creationBlock];
  112. return @[ dynamicLinksProvider ];
  113. }
  114. - (void)configureDynamicLinks:(FIRApp *)app {
  115. FIROptions *options = app.options;
  116. NSError *error;
  117. NSMutableString *errorDescription;
  118. NSString *urlScheme;
  119. if (options.APIKey.length == 0) {
  120. errorDescription = [@"API key must not be nil or empty." mutableCopy];
  121. }
  122. if (!errorDescription) {
  123. // setup FDL if no error detected
  124. urlScheme = options.deepLinkURLScheme ?: [NSBundle mainBundle].bundleIdentifier;
  125. [self setUpWithLaunchOptions:nil apiKey:options.APIKey urlScheme:urlScheme userDefaults:nil];
  126. } else {
  127. NSString *description =
  128. [NSString stringWithFormat:@"Configuration failed for service DynamicLinks."];
  129. NSDictionary *errorDict = @{
  130. NSLocalizedDescriptionKey : description,
  131. NSLocalizedFailureReasonErrorKey : errorDescription
  132. };
  133. error = [NSError errorWithDomain:kFirebaseDurableDeepLinkErrorDomain
  134. code:FIRErrorCodeDurableDeepLinkFailed
  135. userInfo:errorDict];
  136. }
  137. if (error) {
  138. NSString *message = nil;
  139. if (options.usingOptionsFromDefaultPlist) {
  140. // Configured using plist file
  141. message = [NSString
  142. stringWithFormat:
  143. @"Firebase Dynamic Links has stopped your project "
  144. @"because there are missing or incorrect values provided in %@.%@ that may "
  145. @"prevent your app from behaving as expected:\n\n"
  146. @"Error: %@\n\n"
  147. @"Please fix these issues to ensure that Firebase is correctly configured in "
  148. @"your project.",
  149. kServiceInfoFileName, kServiceInfoFileType, error.localizedFailureReason];
  150. } else {
  151. // Configured manually
  152. message = [NSString
  153. stringWithFormat:
  154. @"Firebase Dynamic Links has stopped your project "
  155. @"because there are incorrect values provided in Firebase's configuration "
  156. @"options that may prevent your app from behaving as expected:\n\n"
  157. @"Error: %@\n\n"
  158. @"Please fix these issues to ensure that Firebase is correctly configured in "
  159. @"your project.",
  160. error.localizedFailureReason];
  161. }
  162. [NSException raise:kFirebaseDurableDeepLinkErrorDomain format:@"%@", message];
  163. }
  164. [self checkForCustomDomainEntriesInInfoPlist];
  165. }
  166. - (instancetype)initWithAnalytics:(nullable id<FIRAnalyticsInterop>)analytics {
  167. self = [super init];
  168. if (self) {
  169. _analytics = analytics;
  170. }
  171. return self;
  172. }
  173. + (instancetype)dynamicLinks {
  174. FIRApp *defaultApp = [FIRApp defaultApp]; // Missing configure will be logged here.
  175. id<FIRDynamicLinksInstanceProvider> instance =
  176. FIR_COMPONENT(FIRDynamicLinksInstanceProvider, defaultApp.container);
  177. return (FIRDynamicLinks *)instance;
  178. }
  179. #else
  180. + (instancetype)dynamicLinks {
  181. static FIRDynamicLinks *dynamicLinks;
  182. static dispatch_once_t onceToken;
  183. dispatch_once(&onceToken, ^{
  184. dynamicLinks = [[self alloc] init];
  185. });
  186. return dynamicLinks;
  187. }
  188. #endif
  189. #pragma mark - Custom domains
  190. - (instancetype)init {
  191. self = [super init];
  192. if (self) {
  193. [self checkForCustomDomainEntriesInInfoPlist];
  194. }
  195. return self;
  196. }
  197. // Check for custom domains entry in PLIST file.
  198. - (void)checkForCustomDomainEntriesInInfoPlist {
  199. // Check to see if FirebaseDynamicLinksCustomDomains array is present.
  200. NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary;
  201. NSArray *customDomains = infoDictionary[kInfoPlistCustomDomainsKey];
  202. if (customDomains) {
  203. FIRDLAddToAllowListForCustomDomainsArray(customDomains);
  204. }
  205. }
  206. #pragma mark - First party interface
  207. - (BOOL)setUpWithLaunchOptions:(nullable NSDictionary *)launchOptions
  208. apiKey:(NSString *)apiKey
  209. urlScheme:(nullable NSString *)urlScheme
  210. userDefaults:(nullable NSUserDefaults *)userDefaults {
  211. if (apiKey == nil) {
  212. FDLLog(FDLLogLevelError, FDLLogIdentifierSetupNilAPIKey, @"API Key must not be nil.");
  213. return NO;
  214. }
  215. _APIKey = [apiKey copy];
  216. _URLScheme = urlScheme.length ? [urlScheme copy] : [NSBundle mainBundle].bundleIdentifier;
  217. if (!userDefaults) {
  218. _userDefaults = [NSUserDefaults standardUserDefaults];
  219. } else {
  220. _userDefaults = userDefaults;
  221. }
  222. NSURL *url = launchOptions[UIApplicationLaunchOptionsURLKey];
  223. if (url) {
  224. if ([self canParseCustomSchemeURL:url] || [self canParseUniversalLinkURL:url]) {
  225. // Make sure we don't call |checkForPendingDynamicLink| again if
  226. // a strong deep link is found.
  227. [_userDefaults setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];
  228. }
  229. }
  230. return YES;
  231. }
  232. - (void)checkForPendingDynamicLinkUsingExperimentalRetrievalProcess {
  233. [self checkForPendingDynamicLink];
  234. }
  235. - (void)checkForPendingDynamicLink {
  236. // Make sure this method is called only once after the application was installed.
  237. // kFIRDLOpenURLKey marks checkForPendingDynamic link had been called already so no need to do it
  238. // again. kFIRDLReadDeepLinkAfterInstallKey marks we have already read a deeplink after the
  239. // install and so no need to do check for pending dynamic link.
  240. BOOL appInviteDeepLinkRead = [_userDefaults boolForKey:kFIRDLOpenURLKey] ||
  241. [_userDefaults boolForKey:kFIRDLReadDeepLinkAfterInstallKey];
  242. if (appInviteDeepLinkRead || self.retrievingPendingDynamicLink) {
  243. NSString *errorDescription =
  244. appInviteDeepLinkRead ? NSLocalizedString(@"Link was already retrieved", @"Error message")
  245. : NSLocalizedString(@"Already retrieving link", @"Error message");
  246. [self handlePendingDynamicLinkRetrievalFailureWithErrorCode:-1
  247. errorDescription:errorDescription
  248. underlyingError:nil];
  249. return;
  250. }
  251. self.retrievingPendingDynamicLink = YES;
  252. FIRDLRetrievalProcessFactory *factory =
  253. [[FIRDLRetrievalProcessFactory alloc] initWithNetworkingService:self.dynamicLinkNetworking
  254. URLScheme:_URLScheme
  255. APIKey:_APIKey
  256. FDLSDKVersion:FIRFirebaseVersion()
  257. delegate:self];
  258. _retrievalProcess = [factory automaticRetrievalProcess];
  259. [_retrievalProcess retrievePendingDynamicLink];
  260. }
  261. // Disable deprecated warning for internal methods.
  262. #pragma clang diagnostic push
  263. #pragma clang diagnostic ignored "-Wdeprecated-implementations"
  264. + (instancetype)sharedInstance {
  265. return [self dynamicLinks];
  266. }
  267. - (BOOL)setUpWithLaunchOptions:(nullable NSDictionary *)launchOptions
  268. apiKey:(NSString *)apiKey
  269. clientID:(NSString *)clientID
  270. urlScheme:(nullable NSString *)urlScheme
  271. userDefaults:(nullable NSUserDefaults *)userDefaults {
  272. return [self setUpWithLaunchOptions:launchOptions
  273. apiKey:apiKey
  274. urlScheme:urlScheme
  275. userDefaults:userDefaults];
  276. }
  277. - (void)checkForPendingDeepLink {
  278. [self checkForPendingDynamicLink];
  279. }
  280. - (nullable FIRDynamicLink *)deepLinkFromCustomSchemeURL:(NSURL *)url {
  281. return [self dynamicLinkFromCustomSchemeURL:url];
  282. }
  283. - (nullable FIRDynamicLink *)deepLinkFromUniversalLinkURL:(NSURL *)url {
  284. return [self dynamicLinkFromUniversalLinkURL:url];
  285. }
  286. - (BOOL)shouldHandleDeepLinkFromCustomSchemeURL:(NSURL *)url {
  287. return [self shouldHandleDynamicLinkFromCustomSchemeURL:url];
  288. }
  289. #pragma clang pop
  290. #pragma mark - Public interface
  291. - (BOOL)shouldHandleDynamicLinkFromCustomSchemeURL:(NSURL *)url {
  292. // Return NO if the URL scheme does not match.
  293. if (![self canParseCustomSchemeURL:url]) {
  294. return NO;
  295. }
  296. // We can handle "/link" and "/link/dismiss". The latter will return a nil deep link.
  297. return ([url.path hasPrefix:@"/link"] && [url.host isEqualToString:@"google"]);
  298. }
  299. - (nullable FIRDynamicLink *)dynamicLinkFromCustomSchemeURL:(NSURL *)url {
  300. // Return nil if the URL scheme does not match.
  301. if (![self canParseCustomSchemeURL:url]) {
  302. return nil;
  303. }
  304. if ([url.path isEqualToString:@"/link"] && [url.host isEqualToString:@"google"]) {
  305. // This URL is a callback url from a device heuristics based match
  306. // Extract information from query.
  307. NSString *query = url.query;
  308. NSDictionary *parameters = FIRDLDictionaryFromQuery(query);
  309. // As long as the deepLink has some parameter, return it.
  310. if (parameters.count > 0) {
  311. FIRDynamicLink *dynamicLink =
  312. [[FIRDynamicLink alloc] initWithParametersDictionary:parameters];
  313. #ifdef GIN_SCION_LOGGING
  314. if (dynamicLink.url) {
  315. BOOL isFirstOpen = ![_userDefaults boolForKey:kFIRDLReadDeepLinkAfterInstallKey];
  316. FIRDLLogEvent event = isFirstOpen ? FIRDLLogEventFirstOpen : FIRDLLogEventAppOpen;
  317. FIRDLLogEventToScion(event, parameters[kFIRDLParameterSource],
  318. parameters[kFIRDLParameterMedium], parameters[kFIRDLParameterCampaign],
  319. _analytics);
  320. }
  321. #endif
  322. // Make sure we don't call |checkForPendingDynamicLink| again if we did this already.
  323. if ([_userDefaults boolForKey:kFIRDLOpenURLKey]) {
  324. [_userDefaults setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];
  325. }
  326. return dynamicLink;
  327. }
  328. }
  329. return nil;
  330. }
  331. - (nullable FIRDynamicLink *)
  332. dynamicLinkInternalFromUniversalLinkURL:(NSURL *)url
  333. completion:
  334. (nullable FIRDynamicLinkUniversalLinkHandler)completion {
  335. // Make sure the completion is always called on the main queue.
  336. FIRDynamicLinkUniversalLinkHandler mainQueueCompletion =
  337. ^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
  338. if (completion) {
  339. dispatch_async(dispatch_get_main_queue(), ^{
  340. completion(dynamicLink, error);
  341. });
  342. }
  343. };
  344. if ([self canParseUniversalLinkURL:url]) {
  345. if (url.query.length > 0) {
  346. NSDictionary<NSString *, NSString *> *parameters = FIRDLDictionaryFromQuery(url.query);
  347. if (parameters[kFIRDLParameterLink]) {
  348. NSString *urlString = parameters[kFIRDLParameterLink];
  349. NSURL *deepLinkURL = [NSURL URLWithString:urlString];
  350. if (deepLinkURL) {
  351. NSMutableDictionary *paramsDictionary = [[NSMutableDictionary alloc]
  352. initWithDictionary:@{kFIRDLParameterDeepLinkIdentifier : urlString}];
  353. if (parameters[kFIRDLParameterSource] != nil) {
  354. [paramsDictionary setValue:parameters[kFIRDLParameterSource]
  355. forKey:kFIRDLParameterSource];
  356. }
  357. if (parameters[kFIRDLParameterMedium] != nil) {
  358. [paramsDictionary setValue:parameters[kFIRDLParameterMedium]
  359. forKey:kFIRDLParameterMedium];
  360. }
  361. if (parameters[kFIRDLParameterTerm] != nil) {
  362. [paramsDictionary setValue:parameters[kFIRDLParameterTerm] forKey:kFIRDLParameterTerm];
  363. }
  364. if (parameters[kFIRDLParameterCampaign] != nil) {
  365. [paramsDictionary setValue:parameters[kFIRDLParameterCampaign]
  366. forKey:(kFIRDLParameterCampaign)];
  367. }
  368. if (parameters[kFIRDLParameterContent] != nil) {
  369. [paramsDictionary setValue:parameters[kFIRDLParameterContent]
  370. forKey:kFIRDLParameterContent];
  371. }
  372. FIRDynamicLink *dynamicLink =
  373. [[FIRDynamicLink alloc] initWithParametersDictionary:paramsDictionary];
  374. dynamicLink.matchType = FIRDLMatchTypeUnique;
  375. dynamicLink.minimumAppVersion = parameters[kFIRDLParameterMinimumAppVersion];
  376. // Call resolveShortLink:completion: to do logging.
  377. // TODO: Create dedicated logging function to prevent this.
  378. [self.dynamicLinkNetworking
  379. resolveShortLink:url
  380. FDLSDKVersion:FIRFirebaseVersion()
  381. completion:^(NSURL *_Nullable resolverURL, NSError *_Nullable resolverError) {
  382. mainQueueCompletion(dynamicLink, resolverError);
  383. }];
  384. #ifdef GIN_SCION_LOGGING
  385. FIRDLLogEventToScion(FIRDLLogEventAppOpen, parameters[kFIRDLParameterSource],
  386. parameters[kFIRDLParameterMedium],
  387. parameters[kFIRDLParameterCampaign], _analytics);
  388. #endif
  389. return dynamicLink;
  390. }
  391. }
  392. }
  393. }
  394. mainQueueCompletion(
  395. nil, [[NSError alloc] initWithDomain:@"com.firebase.dynamicLinks"
  396. code:1
  397. userInfo:@{
  398. NSLocalizedFailureReasonErrorKey :
  399. @"Universal link URL could not be parsed by Dynamic Links."
  400. }]);
  401. return nil;
  402. }
  403. - (nullable FIRDynamicLink *)dynamicLinkFromUniversalLinkURL:(NSURL *)url {
  404. return [self dynamicLinkInternalFromUniversalLinkURL:url completion:nil];
  405. }
  406. - (void)dynamicLinkFromUniversalLinkURL:(NSURL *)url
  407. completion:(FIRDynamicLinkUniversalLinkHandler)completion {
  408. [self dynamicLinkInternalFromUniversalLinkURL:url completion:completion];
  409. }
  410. - (BOOL)handleUniversalLink:(NSURL *)universalLinkURL
  411. completion:(FIRDynamicLinkUniversalLinkHandler)completion {
  412. if ([self matchesShortLinkFormat:universalLinkURL]) {
  413. __weak __typeof__(self) weakSelf = self;
  414. [self resolveShortLink:universalLinkURL
  415. completion:^(NSURL *url, NSError *error) {
  416. __typeof__(self) strongSelf = weakSelf;
  417. if (strongSelf) {
  418. FIRDynamicLink *dynamicLink = [strongSelf dynamicLinkFromCustomSchemeURL:url];
  419. dispatch_async(dispatch_get_main_queue(), ^{
  420. completion(dynamicLink, error);
  421. });
  422. } else {
  423. completion(nil, nil);
  424. }
  425. }];
  426. return YES;
  427. } else {
  428. [self dynamicLinkFromUniversalLinkURL:universalLinkURL completion:completion];
  429. BOOL canHandleUniversalLink =
  430. [self canParseUniversalLinkURL:universalLinkURL] && universalLinkURL.query.length > 0 &&
  431. FIRDLDictionaryFromQuery(universalLinkURL.query)[kFIRDLParameterLink];
  432. return canHandleUniversalLink;
  433. }
  434. }
  435. - (void)resolveShortLink:(NSURL *)url completion:(FIRDynamicLinkResolverHandler)completion {
  436. [self.dynamicLinkNetworking resolveShortLink:url
  437. FDLSDKVersion:FIRFirebaseVersion()
  438. completion:completion];
  439. }
  440. - (BOOL)matchesShortLinkFormat:(NSURL *)url {
  441. return FIRDLMatchesShortLinkFormat(url);
  442. }
  443. #pragma mark - Private interface
  444. + (BOOL)isAutomaticRetrievalEnabled {
  445. id retrievalEnabledValue =
  446. [[NSBundle mainBundle] infoDictionary][@"FirebaseDeepLinkAutomaticRetrievalEnabled"];
  447. if ([retrievalEnabledValue respondsToSelector:@selector(boolValue)]) {
  448. return [retrievalEnabledValue boolValue];
  449. }
  450. return YES;
  451. }
  452. #pragma mark - Internal methods
  453. - (FIRDynamicLinkNetworking *)dynamicLinkNetworking {
  454. if (!_dynamicLinkNetworking) {
  455. _dynamicLinkNetworking = [[FIRDynamicLinkNetworking alloc] initWithAPIKey:_APIKey
  456. URLScheme:_URLScheme];
  457. }
  458. return _dynamicLinkNetworking;
  459. }
  460. - (BOOL)canParseCustomSchemeURL:(nullable NSURL *)url {
  461. if (url.scheme.length) {
  462. NSString *bundleIdentifier = [NSBundle mainBundle].bundleIdentifier;
  463. if ([url.scheme.lowercaseString isEqualToString:_URLScheme.lowercaseString] ||
  464. [url.scheme.lowercaseString isEqualToString:bundleIdentifier.lowercaseString]) {
  465. return YES;
  466. }
  467. }
  468. return NO;
  469. }
  470. - (BOOL)canParseUniversalLinkURL:(nullable NSURL *)url {
  471. return FIRDLCanParseUniversalLinkURL(url);
  472. }
  473. - (BOOL)handleIncomingCustomSchemeDeepLink:(NSURL *)url {
  474. return [self canParseCustomSchemeURL:url];
  475. }
  476. - (void)passRetrievedDynamicLinkToApplication:(NSURL *)url {
  477. id<UIApplicationDelegate> applicationDelegate = [UIApplication sharedApplication].delegate;
  478. if ([self isOpenUrlMethodPresentInAppDelegate:applicationDelegate]) {
  479. // pass url directly to application delegate to avoid hop into
  480. // iOS handling of the universal links
  481. [applicationDelegate application:[UIApplication sharedApplication] openURL:url options:@{}];
  482. return;
  483. }
  484. [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
  485. }
  486. - (BOOL)isOpenUrlMethodPresentInAppDelegate:(id<UIApplicationDelegate>)applicationDelegate {
  487. return applicationDelegate &&
  488. [applicationDelegate respondsToSelector:@selector(application:openURL:options:)];
  489. }
  490. - (void)handlePendingDynamicLinkRetrievalFailureWithErrorCode:(NSInteger)errorCode
  491. errorDescription:(NSString *)errorDescription
  492. underlyingError:(nullable NSError *)underlyingError {
  493. self.retrievingPendingDynamicLink = NO;
  494. }
  495. #pragma mark - FIRDLRetrievalProcessDelegate
  496. - (void)retrievalProcess:(id<FIRDLRetrievalProcessProtocol>)retrievalProcess
  497. completedWithResult:(FIRDLRetrievalProcessResult *)result {
  498. self.retrievingPendingDynamicLink = NO;
  499. _retrievalProcess = nil;
  500. if (![_userDefaults boolForKey:kFIRDLOpenURLKey]) {
  501. // Once we complete the Pending dynamic link retrieval, regardless of whether the retrieval is
  502. // success or failure, we don't want to do the retrieval again on next app start.
  503. // If we try to redo the retrieval again because of some error, the user will experience
  504. // unwanted deeplinking when they restart the app next time.
  505. [_userDefaults setBool:YES forKey:kFIRDLOpenURLKey];
  506. }
  507. NSURL *linkToPassToApp = [result URLWithCustomURLScheme:_URLScheme];
  508. [self passRetrievedDynamicLinkToApplication:linkToPassToApp];
  509. }
  510. #pragma mark - Diagnostics methods
  511. static NSString *kSelfDiagnoseOutputHeader =
  512. @"---- Firebase Dynamic Links diagnostic output start ----\n";
  513. static NSString *kSelfDiagnoseOutputFooter =
  514. @"---- Firebase Dynamic Links diagnostic output end ----\n";
  515. + (NSString *)genericDiagnosticInformation {
  516. NSMutableString *genericDiagnosticInfo = [[NSMutableString alloc] init];
  517. [genericDiagnosticInfo
  518. appendFormat:@"Firebase Dynamic Links framework version %@\n", FIRFirebaseVersion()];
  519. [genericDiagnosticInfo appendFormat:@"System information: OS %@, OS version %@, model %@\n",
  520. [UIDevice currentDevice].systemName,
  521. [UIDevice currentDevice].systemVersion,
  522. [UIDevice currentDevice].model];
  523. [genericDiagnosticInfo appendFormat:@"Current date %@\n", [NSDate date]];
  524. // TODO: bring this diagnostic info back when we shipped non-automatic retrieval
  525. // [genericDiagnosticInfo appendFormat:@"AutomaticRetrievalEnabled: %@\n",
  526. // [self isAutomaticRetrievalEnabled] ? @"YES" : @"NO"];
  527. // Disable deprecated warning for internal methods.
  528. #pragma clang diagnostic push
  529. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  530. [genericDiagnosticInfo appendFormat:@"Device locale %@ (raw %@), timezone %@\n",
  531. FIRDLDeviceLocale(), FIRDLDeviceLocaleRaw(),
  532. FIRDLDeviceTimezone()];
  533. #pragma clang pop
  534. return genericDiagnosticInfo;
  535. }
  536. + (NSString *)diagnosticAnalyzeEntitlements {
  537. NSString *embeddedMobileprovisionFilePath = [[[NSBundle mainBundle] bundlePath]
  538. stringByAppendingPathComponent:@"embedded.mobileprovision"];
  539. NSError *error;
  540. NSMutableData *profileData = [NSMutableData dataWithContentsOfFile:embeddedMobileprovisionFilePath
  541. options:0
  542. error:&error];
  543. if (!profileData.length || error) {
  544. return @"\tSKIPPED: Not able to read entitlements (embedded.mobileprovision).\n";
  545. }
  546. // The "embedded.mobileprovision" sometimes contains characters with value 0, which signals the
  547. // end of a c-string and halts the ASCII parser, or with value > 127, which violates strict 7-bit
  548. // ASCII. Replace any 0s or invalid characters in the input.
  549. uint8_t *profileBytes = (uint8_t *)profileData.bytes;
  550. for (int i = 0; i < profileData.length; i++) {
  551. uint8_t currentByte = profileBytes[i];
  552. if (!currentByte || currentByte > 127) {
  553. profileBytes[i] = '.';
  554. }
  555. }
  556. NSString *embeddedProfile = [[NSString alloc] initWithBytesNoCopy:profileBytes
  557. length:profileData.length
  558. encoding:NSASCIIStringEncoding
  559. freeWhenDone:NO];
  560. if (error || !embeddedProfile.length) {
  561. return @"\tSKIPPED: Not able to read entitlements (embedded.mobileprovision).\n";
  562. }
  563. NSScanner *scanner = [NSScanner scannerWithString:embeddedProfile];
  564. NSString *plistContents;
  565. if ([scanner scanUpToString:@"<plist" intoString:nil]) {
  566. if ([scanner scanUpToString:@"</plist>" intoString:&plistContents]) {
  567. plistContents = [plistContents stringByAppendingString:@"</plist>"];
  568. }
  569. }
  570. if (!plistContents.length) {
  571. return @"\tWARNING: Not able to read plist entitlements (embedded.mobileprovision).\n";
  572. }
  573. NSData *data = [plistContents dataUsingEncoding:NSUTF8StringEncoding];
  574. if (!data.length) {
  575. return @"\tWARNING: Not able to parse entitlements (embedded.mobileprovision).\n";
  576. }
  577. NSError *plistMapError;
  578. id plistData = [NSPropertyListSerialization propertyListWithData:data
  579. options:NSPropertyListImmutable
  580. format:nil
  581. error:&plistMapError];
  582. if (plistMapError || ![plistData isKindOfClass:[NSDictionary class]]) {
  583. return @"\tWARNING: Not able to deserialize entitlements (embedded.mobileprovision).\n";
  584. }
  585. NSDictionary *plistMap = (NSDictionary *)plistData;
  586. // analyze entitlements and print diagnostic information
  587. // we can't detect erorrs, information p[rinted here may hint developer or will help support
  588. // to identify the issue
  589. NSMutableString *outputString = [[NSMutableString alloc] init];
  590. NSArray *appIdentifierPrefixes = plistMap[@"ApplicationIdentifierPrefix"];
  591. NSString *teamID = plistMap[@"Entitlements"][@"com.apple.developer.team-identifier"];
  592. if (appIdentifierPrefixes.count > 1) {
  593. // is this possible? anyway, we can handle it
  594. [outputString
  595. appendFormat:@"\tAppID Prefixes: %@, Team ID: %@, AppId Prefixes contains to Team ID: %@\n",
  596. appIdentifierPrefixes, teamID,
  597. ([appIdentifierPrefixes containsObject:teamID] ? @"YES" : @"NO")];
  598. } else {
  599. [outputString
  600. appendFormat:@"\tAppID Prefix: %@, Team ID: %@, AppId Prefix equal to Team ID: %@\n",
  601. appIdentifierPrefixes[0], teamID,
  602. ([appIdentifierPrefixes[0] isEqualToString:teamID] ? @"YES" : @"NO")];
  603. }
  604. return outputString;
  605. }
  606. + (NSString *)performDiagnosticsIncludingHeaderFooter:(BOOL)includingHeaderFooter
  607. detectedErrors:(nullable NSInteger *)detectedErrors {
  608. NSMutableString *diagnosticString = [[NSMutableString alloc] init];
  609. if (includingHeaderFooter) {
  610. [diagnosticString appendString:@"\n"];
  611. [diagnosticString appendString:kSelfDiagnoseOutputHeader];
  612. }
  613. NSInteger detectedErrorsCnt = 0;
  614. [diagnosticString appendString:[self genericDiagnosticInformation]];
  615. #if TARGET_IPHONE_SIMULATOR
  616. // check is Simulator and print WARNING that Universal Links is not supported on Simulator
  617. [diagnosticString
  618. appendString:@"WARNING: iOS Simulator does not support Universal Links. Firebase "
  619. @"Dynamic Links SDK functionality will be limited. Some FDL "
  620. @"features may be missing or will not work correctly.\n"];
  621. #endif // TARGET_IPHONE_SIMULATOR
  622. id<UIApplicationDelegate> applicationDelegate = [UIApplication sharedApplication].delegate;
  623. if (![applicationDelegate respondsToSelector:@selector(application:openURL:options:)]) {
  624. detectedErrorsCnt++;
  625. [diagnosticString appendFormat:@"ERROR: UIApplication delegate %@ does not implements selector "
  626. @"%@. FDL depends on this implementation to retrieve pending "
  627. @"dynamic link.\n",
  628. applicationDelegate,
  629. NSStringFromSelector(@selector(application:openURL:options:))];
  630. }
  631. // check that Info.plist has custom URL scheme and the scheme is the same as bundleID or
  632. // as customURLScheme passed to FDL iOS SDK
  633. NSString *URLScheme = [FIRDynamicLinks dynamicLinks].URLScheme;
  634. BOOL URLSchemeFoundInPlist = NO;
  635. NSArray *URLSchemesFromInfoPlist = [[NSBundle mainBundle] infoDictionary][@"CFBundleURLTypes"];
  636. for (NSDictionary *schemeDetails in URLSchemesFromInfoPlist) {
  637. NSArray *arrayOfSchemes = schemeDetails[@"CFBundleURLSchemes"];
  638. for (NSString *scheme in arrayOfSchemes) {
  639. if ([scheme isEqualToString:URLScheme]) {
  640. URLSchemeFoundInPlist = YES;
  641. break;
  642. }
  643. }
  644. if (URLSchemeFoundInPlist) {
  645. break;
  646. }
  647. }
  648. if (!URLSchemeFoundInPlist) {
  649. detectedErrorsCnt++;
  650. [diagnosticString appendFormat:@"ERROR: Specified custom URL scheme is %@ but Info.plist do "
  651. @"not contain such scheme in "
  652. "CFBundleURLTypes key.\n",
  653. URLScheme];
  654. } else {
  655. [diagnosticString appendFormat:@"\tSpecified custom URL scheme is %@ and Info.plist contains "
  656. @"such scheme in CFBundleURLTypes key.\n",
  657. URLScheme];
  658. }
  659. #if !TARGET_IPHONE_SIMULATOR
  660. // analyse information in entitlements file
  661. NSString *entitlementsAnalysis = [self diagnosticAnalyzeEntitlements];
  662. if (entitlementsAnalysis.length) {
  663. [diagnosticString appendString:entitlementsAnalysis];
  664. }
  665. #endif // TARGET_IPHONE_SIMULATOR
  666. if (includingHeaderFooter) {
  667. if (detectedErrorsCnt == 0) {
  668. [diagnosticString
  669. appendString:@"performDiagnostic completed successfully! No errors found.\n"];
  670. } else {
  671. [diagnosticString
  672. appendFormat:@"performDiagnostic detected %ld ERRORS.\n", (long)detectedErrorsCnt];
  673. }
  674. [diagnosticString appendString:kSelfDiagnoseOutputFooter];
  675. }
  676. if (detectedErrors) {
  677. *detectedErrors = detectedErrorsCnt;
  678. }
  679. return [diagnosticString copy];
  680. }
  681. + (void)performDiagnosticsWithCompletion:(void (^_Nullable)(NSString *diagnosticOutput,
  682. BOOL hasErrors))completionHandler;
  683. {
  684. NSInteger detectedErrorsCnt = 0;
  685. NSString *diagnosticString = [self performDiagnosticsIncludingHeaderFooter:YES
  686. detectedErrors:&detectedErrorsCnt];
  687. if (completionHandler) {
  688. completionHandler(diagnosticString, detectedErrorsCnt > 0);
  689. } else {
  690. NSLog(@"%@", diagnosticString);
  691. }
  692. }
  693. @end
  694. NS_ASSUME_NONNULL_END
  695. #endif // TARGET_OS_IOS