mirror of
https://github.com/firebase/firebase-ios-sdk
synced 2026-11-10 22:45:59 +05:45
* updates changelog * updates code to fix missing utm parameters * fixes missing utm_term and utm_content * fixes unit tests * fixes changelog * fixes clang-format issues * updates changelog Co-authored-by: Cristian Avalos <ceavalos@google.com>
370 lines
15 KiB
Objective-C
370 lines
15 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 <TargetConditionals.h>
|
|
#if TARGET_OS_IOS
|
|
|
|
#import "FirebaseDynamicLinks/Sources/FIRDynamicLinkNetworking+Private.h"
|
|
|
|
#import "FirebaseDynamicLinks/Sources/GINInvocation/GINArgument.h"
|
|
#import "FirebaseDynamicLinks/Sources/GINInvocation/GINInvocation.h"
|
|
#import "FirebaseDynamicLinks/Sources/Utilities/FDLDeviceHeuristicsHelper.h"
|
|
#import "FirebaseDynamicLinks/Sources/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";
|
|
|
|
// Endpoint for default retrieval process V2. (Endpoint version is V1)
|
|
static NSString *const kIosPostInstallAttributionRestBaseUrl =
|
|
@"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 kFDLAnalyticsDataTermKey = @"utmTerm";
|
|
static NSString *const kFDLAnalyticsDataContentKey = @"utmContent";
|
|
static NSString *const kHeaderIosBundleIdentifier = @"X-Ios-Bundle-Identifier";
|
|
static NSString *const kGenericErrorDomain = @"com.firebase.dynamicLinks";
|
|
|
|
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) {
|
|
NSURLSessionConfiguration *sessionConfig =
|
|
[NSURLSessionConfiguration defaultSessionConfiguration];
|
|
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
|
|
NSURLSessionDataTask *dataTask =
|
|
[session dataTaskWithRequest:request
|
|
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
|
|
NSError *_Nullable error) {
|
|
completion(data, response, error);
|
|
}];
|
|
[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 {
|
|
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;
|
|
}
|
|
|
|
#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, NSURLResponse *response, NSError *error) {
|
|
NSURL *resolvedURL = nil;
|
|
NSError *extractedError = [FIRDynamicLinkNetworking extractErrorForShortLink:url
|
|
data:data
|
|
response:response
|
|
error:error];
|
|
|
|
if (!extractedError && 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];
|
|
NSString *utmContent = result[kFDLAnalyticsDataContentKey];
|
|
NSString *utmTerm = result[kFDLAnalyticsDataTermKey];
|
|
resolvedURL = FIRDLDeepLinkURLWithInviteID(
|
|
invitationIDString, deepLinkString, utmSource, utmMedium, utmCampaign, utmContent,
|
|
utmTerm, NO, nil, minAppVersion, self->_URLScheme, nil);
|
|
}
|
|
}
|
|
handler(resolvedURL, extractedError);
|
|
};
|
|
|
|
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" :
|
|
[FDLDeviceHeuristicsHelper FDLDeviceInfoDictionaryFromResolutionHeight:resolutionHeight
|
|
resolutionWidth:resolutionWidth
|
|
locale:locale
|
|
localeRaw:localeRaw
|
|
localeFromWebview:localeFromWebView
|
|
timeZone:timezone
|
|
modelName: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) {
|
|
if (errorPtr != nil) {
|
|
*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];
|
|
};
|
|
|
|
[self sendRequestWithBaseURLString:kIosPostInstallAttributionRestBaseUrl
|
|
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",
|
|
}
|
|
};
|
|
|
|
FIRNetworkRequestCompletionHandler convertInvitationCallback =
|
|
^(NSData *data, NSURLResponse *response, 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, NSURLResponse *response, 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);
|
|
|
|
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
|
|
|
|
#endif // TARGET_OS_IOS
|