| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- /*
- * 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 "DynamicLinks/FIRDynamicLinkNetworking+Private.h"
- #import "DynamicLinks/GINInvocation/GINArgument.h"
- #import "DynamicLinks/GINInvocation/GINInvocation.h"
- #import "DynamicLinks/Utilities/FDLUtilities.h"
- NS_ASSUME_NONNULL_BEGIN
- NSString *const kApiaryRestBaseUrl = @"https://appinvite-pa.googleapis.com/v1";
- static NSString *const kiOSReopenRestBaseUrl = @"https://firebasedynamiclinks.googleapis.com/v1";
- // IPv4 and IPv6 Endpoints.
- static NSString *const kApiaryRestBaseUrlIPV4 = @"https://appinvite-ipv4-pa.googleapis.com/v1";
- static NSString *const kApiaryRestBaseUrlIPV6 = @"https://appinvite-ipv6-pa.googleapis.com/v1";
- // IPv4 and IPv6 Endpoints for default retrieval process V2. (Endpoint version is V1)
- static NSString *const kIosPostInstallAttributionRestBaseUrlIPV4 =
- @"https://firebasedynamiclinks-ipv4.googleapis.com/v1";
- static NSString *const kIosPostInstallAttributionRestBaseUrlIPV6 =
- @"https://firebasedynamiclinks-ipv6.googleapis.com/v1";
- static NSString *const kIosPostInstallAttributionRestBaseUrlUniqueMatch =
- @"https://firebasedynamiclinks.googleapis.com/v1";
- static NSString *const kReasonString = @"reason";
- static NSString *const kiOSInviteReason = @"ios_invite";
- NSString *const kFDLResolvedLinkDeepLinkURLKey = @"deepLink";
- NSString *const kFDLResolvedLinkMinAppVersionKey = @"iosMinAppVersion";
- static NSString *const kFDLAnalyticsDataSourceKey = @"utmSource";
- static NSString *const kFDLAnalyticsDataMediumKey = @"utmMedium";
- static NSString *const kFDLAnalyticsDataCampaignKey = @"utmCampaign";
- static NSString *const kHeaderIosBundleIdentifier = @"X-Ios-Bundle-Identifier";
- typedef NSDictionary *_Nullable (^FIRDLNetworkingParserBlock)(
- NSString *requestURLString,
- NSData *data,
- NSString *_Nullable *_Nonnull matchMessagePtr,
- NSError *_Nullable *_Nullable errorPtr);
- NSString *FIRURLParameterString(NSString *key, NSString *value) {
- if (key.length > 0) {
- return [NSString stringWithFormat:@"?%@=%@", key, value];
- }
- return @"";
- }
- NSString *_Nullable FIRDynamicLinkAPIKeyParameter(NSString *apiKey) {
- return apiKey ? FIRURLParameterString(@"key", apiKey) : nil;
- }
- void FIRMakeHTTPRequest(NSURLRequest *request, FIRNetworkRequestCompletionHandler completion) {
- NSURLSession *session = [NSURLSession sharedSession];
- NSURLSessionDataTask *dataTask =
- [session dataTaskWithRequest:request
- completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
- NSError *_Nullable error) {
- completion(data, error);
- }];
- [dataTask resume];
- }
- NSData *_Nullable FIRDataWithDictionary(NSDictionary *dictionary, NSError **_Nullable error) {
- return [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:error];
- }
- @implementation FIRDynamicLinkNetworking {
- NSString *_APIKey;
- NSString *_clientID;
- NSString *_URLScheme;
- }
- - (instancetype)initWithAPIKey:(NSString *)APIKey
- clientID:(NSString *)clientID
- URLScheme:(NSString *)URLScheme {
- NSParameterAssert(APIKey);
- NSParameterAssert(clientID);
- NSParameterAssert(URLScheme);
- if (self = [super init]) {
- _APIKey = [APIKey copy];
- _clientID = [clientID copy];
- _URLScheme = [URLScheme copy];
- }
- return self;
- }
- #pragma mark - Public interface
- - (void)resolveShortLink:(NSURL *)url
- FDLSDKVersion:(NSString *)FDLSDKVersion
- completion:(FIRDynamicLinkResolverHandler)handler {
- NSParameterAssert(handler);
- if (!url) {
- handler(nil, nil);
- return;
- }
- NSDictionary *requestBody = @{
- @"requestedLink" : url.absoluteString,
- @"bundle_id" : [NSBundle mainBundle].bundleIdentifier,
- @"sdk_version" : FDLSDKVersion
- };
- FIRNetworkRequestCompletionHandler resolveLinkCallback = ^(NSData *data, NSError *error) {
- NSURL *resolvedURL;
- if (!error && data) {
- NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
- if ([result isKindOfClass:[NSDictionary class]]) {
- id invitationIDObject = [result objectForKey:@"invitationId"];
- NSString *invitationIDString;
- if ([invitationIDObject isKindOfClass:[NSDictionary class]]) {
- NSDictionary *invitationIDDictionary = invitationIDObject;
- invitationIDString = invitationIDDictionary[@"id"];
- } else if ([invitationIDObject isKindOfClass:[NSString class]]) {
- invitationIDString = invitationIDObject;
- }
- NSString *deepLinkString = result[kFDLResolvedLinkDeepLinkURLKey];
- NSString *minAppVersion = result[kFDLResolvedLinkMinAppVersionKey];
- NSString *utmSource = result[kFDLAnalyticsDataSourceKey];
- NSString *utmMedium = result[kFDLAnalyticsDataMediumKey];
- NSString *utmCampaign = result[kFDLAnalyticsDataCampaignKey];
- resolvedURL = FIRDLDeepLinkURLWithInviteID(invitationIDString, deepLinkString, utmSource,
- utmMedium, utmCampaign, NO, nil, minAppVersion,
- self->_URLScheme, nil);
- }
- }
- handler(resolvedURL, error);
- };
- NSString *requestURLString =
- [NSString stringWithFormat:@"%@/reopenAttribution%@", kiOSReopenRestBaseUrl,
- FIRDynamicLinkAPIKeyParameter(_APIKey)];
- [self executeOnePlatformRequest:requestBody
- forURL:requestURLString
- completionHandler:resolveLinkCallback];
- }
- - (void)retrievePendingDynamicLinkWithIOSVersion:(NSString *)IOSVersion
- resolutionHeight:(NSInteger)resolutionHeight
- resolutionWidth:(NSInteger)resolutionWidth
- locale:(NSString *)locale
- localeRaw:(NSString *)localeRaw
- localeFromWebView:(NSString *)localeFromWebView
- timezone:(NSString *)timezone
- modelName:(NSString *)modelName
- FDLSDKVersion:(NSString *)FDLSDKVersion
- appInstallationDate:(NSDate *_Nullable)appInstallationDate
- uniqueMatchVisualStyle:
- (FIRDynamicLinkNetworkingUniqueMatchVisualStyle)uniqueMatchVisualStyle
- retrievalProcessType:
- (FIRDynamicLinkNetworkingRetrievalProcessType)retrievalProcessType
- uniqueMatchLinkToCheck:(NSURL *)uniqueMatchLinkToCheck
- handler:
- (FIRPostInstallAttributionCompletionHandler)handler {
- NSParameterAssert(handler);
- NSMutableDictionary *requestBody = [@{
- @"bundleId" : [NSBundle mainBundle].bundleIdentifier,
- @"device" : @{
- @"screenResolutionHeight" : @(resolutionHeight),
- @"screenResolutionWidth" : @(resolutionWidth),
- @"languageCode" : locale,
- @"languageCodeRaw" : localeRaw,
- @"languageCodeFromWebview" : localeFromWebView,
- @"timezone" : timezone,
- @"deviceModelName" : modelName,
- },
- @"iosVersion" : IOSVersion,
- @"sdkVersion" : FDLSDKVersion,
- @"visualStyle" : @(uniqueMatchVisualStyle),
- @"retrievalMethod" : @(retrievalProcessType),
- } mutableCopy];
- if (appInstallationDate) {
- requestBody[@"appInstallationTime"] = @((NSInteger)[appInstallationDate timeIntervalSince1970]);
- }
- if (uniqueMatchLinkToCheck) {
- requestBody[@"uniqueMatchLinkToCheck"] = uniqueMatchLinkToCheck.absoluteString;
- }
- FIRDLNetworkingParserBlock responseParserBlock = ^NSDictionary *_Nullable(
- NSString *requestURLString, NSData *data, NSString **matchMessagePtr, NSError **errorPtr) {
- NSError *serializationError;
- NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data
- options:0
- error:&serializationError];
- if (serializationError) {
- *errorPtr = serializationError;
- return nil;
- }
- NSString *matchMessage = result[@"matchMessage"];
- if (matchMessage.length) {
- *matchMessagePtr = matchMessage;
- }
- // Create the dynamic link parameters
- NSMutableDictionary *dynamicLinkParameters = [[NSMutableDictionary alloc] init];
- dynamicLinkParameters[kFIRDLParameterInviteId] = result[@"invitationId"];
- dynamicLinkParameters[kFIRDLParameterDeepLinkIdentifier] = result[@"deepLink"];
- if (result[@"deepLink"]) {
- dynamicLinkParameters[kFIRDLParameterMatchType] =
- FIRDLMatchTypeStringFromServerString(result[@"attributionConfidence"]);
- }
- dynamicLinkParameters[kFIRDLParameterSource] = result[@"utmSource"];
- dynamicLinkParameters[kFIRDLParameterMedium] = result[@"utmMedium"];
- dynamicLinkParameters[kFIRDLParameterCampaign] = result[@"utmCampaign"];
- dynamicLinkParameters[kFIRDLParameterMinimumAppVersion] = result[@"appMinimumVersion"];
- dynamicLinkParameters[kFIRDLParameterRequestIPVersion] = result[@"requestIpVersion"];
- dynamicLinkParameters[kFIRDLParameterMatchMessage] = matchMessage;
- return [dynamicLinkParameters copy];
- };
- // If uniqueMatch link available send to the unique match endpoint,
- // else send requests to both IPv4 and IPv6 endpoints.
- NSArray *baseURLs =
- uniqueMatchLinkToCheck ? @[ kIosPostInstallAttributionRestBaseUrlUniqueMatch ] : @[
- kIosPostInstallAttributionRestBaseUrlIPV4, kIosPostInstallAttributionRestBaseUrlIPV6
- ];
- for (NSString *baseURL in baseURLs) {
- [self sendRequestWithBaseURLString:baseURL
- requestBody:requestBody
- endpointPath:@"installAttribution"
- parserBlock:responseParserBlock
- completion:handler];
- }
- }
- - (void)convertInvitation:(NSString *)invitationID
- handler:(nullable FIRDynamicLinkNetworkingErrorHandler)handler {
- if (!invitationID) {
- return;
- }
- NSDictionary *requestBody = @{
- @"invitationId" : @{@"id" : invitationID},
- @"containerClientId" : @{
- @"type" : @"IOS",
- @"id" : _clientID,
- }
- };
- FIRNetworkRequestCompletionHandler convertInvitationCallback = ^(NSData *data, NSError *error) {
- if (handler) {
- dispatch_async(dispatch_get_main_queue(), ^{
- handler(error);
- });
- }
- };
- NSString *requestURL = [NSString stringWithFormat:@"%@/convertInvitation%@", kApiaryRestBaseUrl,
- FIRDynamicLinkAPIKeyParameter(_APIKey)];
- [self executeOnePlatformRequest:requestBody
- forURL:requestURL
- completionHandler:convertInvitationCallback];
- }
- #pragma mark - Internal methods
- - (void)sendRequestWithBaseURLString:(NSString *)baseURL
- requestBody:(NSDictionary *)requestBody
- endpointPath:(NSString *)endpointPath
- parserBlock:(FIRDLNetworkingParserBlock)parserBlock
- completion:(FIRPostInstallAttributionCompletionHandler)handler {
- NSParameterAssert(handler);
- NSString *requestURLString = [NSString
- stringWithFormat:@"%@/%@%@", baseURL, endpointPath, FIRDynamicLinkAPIKeyParameter(_APIKey)];
- FIRNetworkRequestCompletionHandler completeInvitationByDeviceCallback = ^(NSData *data,
- NSError *error) {
- if (error || !data) {
- dispatch_async(dispatch_get_main_queue(), ^{
- handler(nil, nil, error);
- });
- return;
- }
- NSString *matchMessage = nil;
- NSError *parsingError = nil;
- NSDictionary *parsedDynamicLinkParameters =
- parserBlock(requestURLString, data, &matchMessage, &parsingError);
- // If request was made with pasteboard contents, verify if we got a unique match. If we got
- // a "none" match, we were unable to get a unique match or deduce using fingerprinting.
- // In this case, resend requests to IPV4 and IPV6 endpoints for fingerprinting. b/79704203
- if (requestBody[@"uniqueMatchLinkToCheck"] && parsedDynamicLinkParameters &&
- (!parsedDynamicLinkParameters[kFIRDLParameterMatchType] ||
- [parsedDynamicLinkParameters[kFIRDLParameterMatchType] isEqualToString:@"none"])) {
- NSMutableDictionary *requestBodyMutable = [requestBody mutableCopy];
- [requestBodyMutable removeObjectForKey:@"uniqueMatchLinkToCheck"];
- NSMutableArray *baseURLs =
- [@[ kIosPostInstallAttributionRestBaseUrlIPV4, kIosPostInstallAttributionRestBaseUrlIPV6 ]
- mutableCopy];
- if (parsedDynamicLinkParameters[kFIRDLParameterRequestIPVersion]) {
- if ([parsedDynamicLinkParameters[kFIRDLParameterRequestIPVersion]
- isEqualToString:@"IP_V4"]) {
- [baseURLs removeObject:kIosPostInstallAttributionRestBaseUrlIPV4];
- } else if ([parsedDynamicLinkParameters[kFIRDLParameterRequestIPVersion]
- isEqualToString:@"IP_V6"]) {
- [baseURLs removeObject:kIosPostInstallAttributionRestBaseUrlIPV6];
- }
- }
- for (NSString *baseURL in baseURLs) {
- [self sendRequestWithBaseURLString:baseURL
- requestBody:requestBodyMutable
- endpointPath:@"installAttribution"
- parserBlock:parserBlock
- completion:handler];
- }
- }
- // We want to return out the result of the unique match check irrespective of success/failure as
- // it is the first fingerprinting request as well.
- dispatch_async(dispatch_get_main_queue(), ^{
- handler(parsedDynamicLinkParameters, matchMessage, parsingError);
- });
- };
- [self executeOnePlatformRequest:requestBody
- forURL:requestURLString
- completionHandler:completeInvitationByDeviceCallback];
- }
- - (void)executeOnePlatformRequest:(NSDictionary *)requestBody
- forURL:(NSString *)requestURLString
- completionHandler:(FIRNetworkRequestCompletionHandler)handler {
- NSURL *requestURL = [NSURL URLWithString:requestURLString];
- NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL];
- // TODO: Verify that HTTPBody and HTTPMethod are iOS 8+ and find an alternative.
- request.HTTPBody = FIRDataWithDictionary(requestBody, nil);
- request.HTTPMethod = @"POST";
- [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
- // Set the iOS bundleID as a request header.
- NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier];
- if (bundleID) {
- [request setValue:bundleID forHTTPHeaderField:kHeaderIosBundleIdentifier];
- }
- FIRMakeHTTPRequest(request, handler);
- }
- @end
- NS_ASSUME_NONNULL_END
|