FIRDynamicLinks.m 30 KB

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