mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-09 22:36:01 +05:45
* Move FDL sources to new location. * Add FDL unit tests. * Move test app. * Fix source path. * Move unit tests path. * Rename source to sources. * Change relative path. * Update podspec. * Fix header search path. * Fix relative path for scripts. * Fix tests.
322 lines
13 KiB
Objective-C
322 lines
13 KiB
Objective-C
/*
|
|
* 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
|