Files
firebase-ios-sdk/FirebaseDynamicLinks/Sources/FIRDynamicLinkNetworking.m
T

363 lines
15 KiB
Objective-C
Raw Normal View History

2018-09-19 12:08:51 -07:00
/*
* 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.
*/
2020-08-11 07:44:06 -07:00
#import <TargetConditionals.h>
#if TARGET_OS_IOS
#import "FirebaseDynamicLinks/Sources/FIRDynamicLinkNetworking+Private.h"
2018-09-19 12:08:51 -07:00
#import "FirebaseDynamicLinks/Sources/GINInvocation/GINArgument.h"
#import "FirebaseDynamicLinks/Sources/GINInvocation/GINInvocation.h"
#import "FirebaseDynamicLinks/Sources/Utilities/FDLUtilities.h"
2018-09-19 12:08:51 -07:00
NS_ASSUME_NONNULL_BEGIN
NSString *const kApiaryRestBaseUrl = @"https://appinvite-pa.googleapis.com/v1";
static NSString *const kiOSReopenRestBaseUrl = @"https://firebasedynamiclinks.googleapis.com/v1";
2018-09-19 12:08:51 -07:00
2020-09-14 15:21:43 -07:00
// Endpoint for default retrieval process V2. (Endpoint version is V1)
static NSString *const kIosPostInstallAttributionRestBaseUrl =
2018-09-19 12:08:51 -07:00
@"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";
2018-09-19 12:08:51 -07:00
static NSString *const kHeaderIosBundleIdentifier = @"X-Ios-Bundle-Identifier";
static NSString *const kGenericErrorDomain = @"com.firebase.dynamicLinks";
2018-09-19 12:08:51 -07:00
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) {
2020-07-03 02:42:33 +08:00
NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
2018-09-19 12:08:51 -07:00
NSURLSessionDataTask *dataTask =
[session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
NSError *_Nullable error) {
completion(data, response, error);
2018-09-19 12:08:51 -07:00
}];
[dataTask resume];
}
NSData *_Nullable FIRDataWithDictionary(NSDictionary *dictionary, NSError **_Nullable error) {
return [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:error];
}
@implementation FIRDynamicLinkNetworking {
NSString *_APIKey;
NSString *_URLScheme;
}
- (instancetype)initWithAPIKey:(NSString *)APIKey URLScheme:(NSString *)URLScheme {
2018-09-19 12:08:51 -07:00
NSParameterAssert(APIKey);
NSParameterAssert(URLScheme);
if (self = [super init]) {
_APIKey = [APIKey copy];
_URLScheme = [URLScheme copy];
}
return self;
}
+ (nullable NSError *)extractErrorForShortLink:(NSURL *)url
data:(NSData *)data
response:(NSURLResponse *)response
error:(nullable NSError *)error {
if (error) {
return error;
}
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
NSError *customError = nil;
if (![response isKindOfClass:[NSHTTPURLResponse class]]) {
customError =
[NSError errorWithDomain:kGenericErrorDomain
code:0
userInfo:@{@"message" : @"Response should be of type NSHTTPURLResponse."}];
} else if ((statusCode < 200 || statusCode >= 300) && data) {
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if ([result isKindOfClass:[NSDictionary class]] && [result objectForKey:@"error"]) {
id err = [result objectForKey:@"error"];
customError = [NSError errorWithDomain:kGenericErrorDomain code:statusCode userInfo:err];
} else {
customError = [NSError
errorWithDomain:kGenericErrorDomain
code:0
userInfo:@{
@"message" :
[NSString stringWithFormat:@"Failed to resolve link: %@", url.absoluteString]
}];
}
}
return customError;
}
2018-09-19 12:08:51 -07:00
#pragma mark - Public interface
- (void)resolveShortLink:(NSURL *)url
FDLSDKVersion:(NSString *)FDLSDKVersion
completion:(FIRDynamicLinkResolverHandler)handler {
2018-09-19 12:08:51 -07:00
NSParameterAssert(handler);
if (!url) {
handler(nil, nil);
return;
}
NSDictionary *requestBody = @{
@"requestedLink" : url.absoluteString,
@"bundle_id" : [NSBundle mainBundle].bundleIdentifier,
@"sdk_version" : FDLSDKVersion
};
2018-09-19 12:08:51 -07:00
FIRNetworkRequestCompletionHandler resolveLinkCallback =
^(NSData *data, NSURLResponse *response, NSError *error) {
NSURL *resolvedURL = nil;
NSError *extractedError = [FIRDynamicLinkNetworking extractErrorForShortLink:url
data:data
response:response
error:error];
2018-09-19 12:08:51 -07:00
if (!extractedError && data) {
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
if ([result isKindOfClass:[NSDictionary class]]) {
id invitationIDObject = [result objectForKey:@"invitationId"];
2018-09-19 12:08:51 -07:00
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);
}
2018-09-19 12:08:51 -07:00
}
handler(resolvedURL, extractedError);
};
2018-09-19 12:08:51 -07:00
NSString *requestURLString =
[NSString stringWithFormat:@"%@/reopenAttribution%@", kiOSReopenRestBaseUrl,
FIRDynamicLinkAPIKeyParameter(_APIKey)];
2018-09-19 12:08:51 -07:00
[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];
2018-09-19 12:08:51 -07:00
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];
};
2020-09-14 15:21:43 -07:00
[self sendRequestWithBaseURLString:kIosPostInstallAttributionRestBaseUrl
requestBody:requestBody
endpointPath:@"installAttribution"
parserBlock:responseParserBlock
completion:handler];
2018-09-19 12:08:51 -07:00
}
- (void)convertInvitation:(NSString *)invitationID
handler:(nullable FIRDynamicLinkNetworkingErrorHandler)handler {
if (!invitationID) {
return;
}
NSDictionary *requestBody = @{
@"invitationId" : @{@"id" : invitationID},
@"containerClientId" : @{
@"type" : @"IOS",
}
};
FIRNetworkRequestCompletionHandler convertInvitationCallback =
^(NSData *data, NSURLResponse *response, NSError *error) {
if (handler) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(error);
});
}
};
2018-09-19 12:08:51 -07:00
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)];
2020-09-14 15:21:43 -07:00
FIRNetworkRequestCompletionHandler completeInvitationByDeviceCallback =
^(NSData *data, NSURLResponse *response, NSError *error) {
2020-09-14 15:21:43 -07:00
if (error || !data) {
dispatch_async(dispatch_get_main_queue(), ^{
handler(nil, nil, error);
});
return;
2018-09-19 12:08:51 -07:00
}
2020-09-14 15:21:43 -07:00
NSString *matchMessage = nil;
NSError *parsingError = nil;
NSDictionary *parsedDynamicLinkParameters =
parserBlock(requestURLString, data, &matchMessage, &parsingError);
dispatch_async(dispatch_get_main_queue(), ^{
handler(parsedDynamicLinkParameters, matchMessage, parsingError);
});
};
2018-09-19 12:08:51 -07:00
[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
2020-08-11 07:44:06 -07:00
#endif // TARGET_OS_IOS