FIRDynamicLinks.m 29 KB

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