| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- /*
- * Copyright 2018 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import "FirebaseDynamicLinks/Sources/FIRDLDefaultRetrievalProcessV2.h"
- #import <UIKit/UIKit.h>
- #import "FirebaseDynamicLinks/Sources/FIRDLJavaScriptExecutor.h"
- #import "FirebaseDynamicLinks/Sources/FIRDLRetrievalProcessResult+Private.h"
- #import "FirebaseDynamicLinks/Sources/FIRDynamicLink+Private.h"
- #import "FirebaseDynamicLinks/Sources/FIRDynamicLinkNetworking.h"
- #import "FirebaseDynamicLinks/Sources/Utilities/FDLUtilities.h"
- // The maximum number of successful fingerprint api calls.
- const static NSUInteger kMaximumNumberOfSuccessfulFingerprintAPICalls = 2;
- // Reason for this string to ensure that only FDL links, copied to clipboard by AppPreview Page
- // JavaScript code, are recognized and used in copy-unique-match process. If user copied FDL to
- // clipboard by himself, that link must not be used in copy-unique-match process.
- // This constant must be kept in sync with constant in the server version at
- // durabledeeplink/click/ios/click_page.js
- static NSString *expectedCopiedLinkStringSuffix = @"_icp=1";
- NS_ASSUME_NONNULL_BEGIN
- @interface FIRDLDefaultRetrievalProcessV2 () <FIRDLJavaScriptExecutorDelegate>
- @property(atomic, strong) NSMutableArray *requestResults;
- @end
- @implementation FIRDLDefaultRetrievalProcessV2 {
- FIRDynamicLinkNetworking *_networkingService;
- NSString *_clientID;
- NSString *_URLScheme;
- NSString *_APIKey;
- NSString *_FDLSDKVersion;
- NSString *_clipboardContentAtMatchProcessStart;
- FIRDLJavaScriptExecutor *_jsExecutor;
- NSString *_localeFromWebView;
- }
- @synthesize delegate = _delegate;
- #pragma mark - Initialization
- - (instancetype)initWithNetworkingService:(FIRDynamicLinkNetworking *)networkingService
- clientID:(NSString *)clientID
- URLScheme:(NSString *)URLScheme
- APIKey:(NSString *)APIKey
- FDLSDKVersion:(NSString *)FDLSDKVersion
- delegate:(id<FIRDLRetrievalProcessDelegate>)delegate {
- NSParameterAssert(networkingService);
- NSParameterAssert(clientID);
- NSParameterAssert(URLScheme);
- NSParameterAssert(APIKey);
- if (self = [super init]) {
- _networkingService = networkingService;
- _clientID = [clientID copy];
- _URLScheme = [URLScheme copy];
- _APIKey = [APIKey copy];
- _FDLSDKVersion = [FDLSDKVersion copy];
- self.requestResults =
- [[NSMutableArray alloc] initWithCapacity:kMaximumNumberOfSuccessfulFingerprintAPICalls];
- _delegate = delegate;
- }
- return self;
- }
- #pragma mark - FIRDLRetrievalProcessProtocol
- - (void)retrievePendingDynamicLink {
- if (_localeFromWebView) {
- [self retrievePendingDynamicLinkInternal];
- } else {
- [self fetchLocaleFromWebView];
- }
- }
- - (BOOL)isCompleted {
- return self.requestResults.count >= kMaximumNumberOfSuccessfulFingerprintAPICalls;
- }
- #pragma mark - FIRDLJavaScriptExecutorDelegate
- - (void)javaScriptExecutor:(FIRDLJavaScriptExecutor *)executor
- completedExecutionWithResult:(NSString *)result {
- _localeFromWebView = result ?: @"";
- _jsExecutor = nil;
- [self retrievePendingDynamicLinkInternal];
- }
- - (void)javaScriptExecutor:(FIRDLJavaScriptExecutor *)executor failedWithError:(NSError *)error {
- _localeFromWebView = @"";
- _jsExecutor = nil;
- [self retrievePendingDynamicLinkInternal];
- }
- #pragma mark - Internal methods
- - (void)retrievePendingDynamicLinkInternal {
- CGRect mainScreenBounds = [UIScreen mainScreen].bounds;
- NSInteger resolutionWidth = mainScreenBounds.size.width;
- NSInteger resolutionHeight = mainScreenBounds.size.height;
- if ([[[UIDevice currentDevice] model] isEqualToString:@"iPad"] &&
- UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
- // iPhone App running in compatibility mode on iPad
- // screen resolution reported by UIDevice/UIScreen will be wrong
- resolutionWidth = 0;
- resolutionHeight = 0;
- }
- NSURL *uniqueMatchLinkToCheck = [self uniqueMatchLinkToCheck];
- __weak __typeof__(self) weakSelf = self;
- FIRPostInstallAttributionCompletionHandler completionHandler =
- ^(NSDictionary *_Nullable dynamicLinkParameters, NSString *_Nullable matchMessage,
- NSError *_Nullable error) {
- __typeof__(self) strongSelf = weakSelf;
- if (!strongSelf) {
- return;
- }
- if (strongSelf.completed) {
- // we may abort process and return previously found dynamic link before all requests
- // completed
- return;
- }
- FIRDynamicLink *dynamicLink;
- if (dynamicLinkParameters.count) {
- dynamicLink = [[FIRDynamicLink alloc] initWithParametersDictionary:dynamicLinkParameters];
- }
- FIRDLRetrievalProcessResult *result =
- [[FIRDLRetrievalProcessResult alloc] initWithDynamicLink:dynamicLink
- error:error
- message:matchMessage
- matchSource:nil];
- [strongSelf.requestResults addObject:result];
- [strongSelf handleRequestResultsUpdated];
- if (!error) {
- [strongSelf clearUsedUniqueMatchLinkToCheckFromClipboard];
- }
- };
- // Disable deprecated warning for internal methods.
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- // If not unique match, we send request twice, since there are two server calls:
- // one for IPv4, another for IPV6.
- [_networkingService
- retrievePendingDynamicLinkWithIOSVersion:[UIDevice currentDevice].systemVersion
- resolutionHeight:resolutionHeight
- resolutionWidth:resolutionWidth
- locale:FIRDLDeviceLocale()
- localeRaw:FIRDLDeviceLocaleRaw()
- localeFromWebView:_localeFromWebView
- timezone:FIRDLDeviceTimezone()
- modelName:FIRDLDeviceModelName()
- FDLSDKVersion:_FDLSDKVersion
- appInstallationDate:FIRDLAppInstallationDate()
- uniqueMatchVisualStyle:FIRDynamicLinkNetworkingUniqueMatchVisualStyleUnknown
- retrievalProcessType:
- FIRDynamicLinkNetworkingRetrievalProcessTypeImplicitDefault
- uniqueMatchLinkToCheck:uniqueMatchLinkToCheck
- handler:completionHandler];
- #pragma clang pop
- }
- - (NSArray<FIRDLRetrievalProcessResult *> *)foundResultsWithDynamicLinks {
- NSPredicate *predicate =
- [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject,
- NSDictionary<NSString *, id> *_Nullable bindings) {
- if ([evaluatedObject isKindOfClass:[FIRDLRetrievalProcessResult class]]) {
- FIRDLRetrievalProcessResult *result = (FIRDLRetrievalProcessResult *)evaluatedObject;
- return result.dynamicLink.url != nil;
- }
- return NO;
- }];
- return [self.requestResults filteredArrayUsingPredicate:predicate];
- }
- - (NSArray<FIRDLRetrievalProcessResult *> *)resultsWithErrors {
- NSPredicate *predicate =
- [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject,
- NSDictionary<NSString *, id> *_Nullable bindings) {
- if ([evaluatedObject isKindOfClass:[FIRDLRetrievalProcessResult class]]) {
- FIRDLRetrievalProcessResult *result = (FIRDLRetrievalProcessResult *)evaluatedObject;
- return result.error != nil;
- }
- return NO;
- }];
- return [self.requestResults filteredArrayUsingPredicate:predicate];
- }
- - (NSArray<FIRDLRetrievalProcessResult *> *)results {
- NSPredicate *predicate =
- [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject,
- NSDictionary<NSString *, id> *_Nullable bindings) {
- return [evaluatedObject isKindOfClass:[FIRDLRetrievalProcessResult class]];
- }];
- return [self.requestResults filteredArrayUsingPredicate:predicate];
- }
- - (nullable FIRDLRetrievalProcessResult *)resultWithUniqueMatchedDynamicLink {
- // return result with unique-matched dynamic link if found
- NSArray<FIRDLRetrievalProcessResult *> *foundResultsWithDynamicLinks =
- [self foundResultsWithDynamicLinks];
- for (FIRDLRetrievalProcessResult *result in foundResultsWithDynamicLinks) {
- if (result.dynamicLink.matchType == FIRDLMatchTypeUnique) {
- return result;
- }
- }
- return nil;
- }
- - (void)handleRequestResultsUpdated {
- FIRDLRetrievalProcessResult *resultWithUniqueMatchedDynamicLink =
- [self resultWithUniqueMatchedDynamicLink];
- if (resultWithUniqueMatchedDynamicLink) {
- [self markCompleted];
- [self.delegate retrievalProcess:self completedWithResult:resultWithUniqueMatchedDynamicLink];
- } else if (self.completed) {
- NSArray<FIRDLRetrievalProcessResult *> *foundResultsWithDynamicLinks =
- [self foundResultsWithDynamicLinks];
- NSArray<FIRDLRetrievalProcessResult *> *resultsThatEncounteredErrors = [self resultsWithErrors];
- if (foundResultsWithDynamicLinks.count) {
- // return any result if no unique-matched URL is available
- // TODO: Merge match message from all results
- [self.delegate retrievalProcess:self
- completedWithResult:foundResultsWithDynamicLinks.firstObject];
- } else if (resultsThatEncounteredErrors.count > 0) {
- // TODO: Merge match message and errors from all results
- [self.delegate retrievalProcess:self
- completedWithResult:resultsThatEncounteredErrors.firstObject];
- } else {
- // dynamic link not found
- // TODO: Merge match message from all results
- FIRDLRetrievalProcessResult *result = [[self results] firstObject];
- if (!result) {
- // if we did not get any results, construct one
- NSString *message = NSLocalizedString(@"Pending dynamic link not found",
- @"Message when dynamic link was not found");
- result = [[FIRDLRetrievalProcessResult alloc] initWithDynamicLink:nil
- error:nil
- message:message
- matchSource:nil];
- }
- [self.delegate retrievalProcess:self completedWithResult:result];
- }
- }
- }
- - (void)markCompleted {
- while (!self.completed) {
- [self.requestResults addObject:[NSNull null]];
- }
- }
- - (nullable NSURL *)uniqueMatchLinkToCheck {
- _clipboardContentAtMatchProcessStart = nil;
- NSString *pasteboardContents = [UIPasteboard generalPasteboard].string;
- NSInteger linkStringMinimumLength =
- expectedCopiedLinkStringSuffix.length + /* ? or & */ 1 + /* http:// */ 7;
- if ((pasteboardContents.length >= linkStringMinimumLength) &&
- [pasteboardContents hasSuffix:expectedCopiedLinkStringSuffix] &&
- [NSURL URLWithString:pasteboardContents]) {
- // remove custom suffix and preceding '&' or '?' character from string
- NSString *linkStringWithoutSuffix = [pasteboardContents
- substringToIndex:pasteboardContents.length - expectedCopiedLinkStringSuffix.length - 1];
- NSURL *URL = [NSURL URLWithString:linkStringWithoutSuffix];
- if (URL) {
- // check is link matches short link format
- if (FIRDLMatchesShortLinkFormat(URL)) {
- _clipboardContentAtMatchProcessStart = pasteboardContents;
- return URL;
- }
- // check is link matches long link format
- if (FIRDLCanParseUniversalLinkURL(URL)) {
- _clipboardContentAtMatchProcessStart = pasteboardContents;
- return URL;
- }
- }
- }
- return nil;
- }
- - (void)clearUsedUniqueMatchLinkToCheckFromClipboard {
- // See discussion in b/65304652
- // We will clear clipboard after we used the unique match link from the clipboard
- if (_clipboardContentAtMatchProcessStart.length > 0 &&
- [_clipboardContentAtMatchProcessStart isEqualToString:_clipboardContentAtMatchProcessStart]) {
- [UIPasteboard generalPasteboard].string = @"";
- }
- }
- - (void)fetchLocaleFromWebView {
- if (_jsExecutor) {
- return;
- }
- NSString *jsString = @"window.generateFingerprint=function(){try{var "
- @"languageCode=navigator.languages?navigator.languages[0]:navigator."
- @"language;return languageCode;}catch(b){return"
- "}};";
- _jsExecutor = [[FIRDLJavaScriptExecutor alloc] initWithDelegate:self script:jsString];
- }
- @end
- NS_ASSUME_NONNULL_END
|