FIRDynamicLinkNetworking.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. /*
  2. * Copyright 2018 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <TargetConditionals.h>
  17. #if TARGET_OS_IOS
  18. #import "FirebaseDynamicLinks/Sources/FIRDynamicLinkNetworking+Private.h"
  19. #import "FirebaseDynamicLinks/Sources/GINInvocation/GINArgument.h"
  20. #import "FirebaseDynamicLinks/Sources/GINInvocation/GINInvocation.h"
  21. #import "FirebaseDynamicLinks/Sources/Utilities/FDLUtilities.h"
  22. NS_ASSUME_NONNULL_BEGIN
  23. NSString *const kApiaryRestBaseUrl = @"https://appinvite-pa.googleapis.com/v1";
  24. static NSString *const kiOSReopenRestBaseUrl = @"https://firebasedynamiclinks.googleapis.com/v1";
  25. // Endpoint for default retrieval process V2. (Endpoint version is V1)
  26. static NSString *const kIosPostInstallAttributionRestBaseUrl =
  27. @"https://firebasedynamiclinks.googleapis.com/v1";
  28. static NSString *const kReasonString = @"reason";
  29. static NSString *const kiOSInviteReason = @"ios_invite";
  30. NSString *const kFDLResolvedLinkDeepLinkURLKey = @"deepLink";
  31. NSString *const kFDLResolvedLinkMinAppVersionKey = @"iosMinAppVersion";
  32. static NSString *const kFDLAnalyticsDataSourceKey = @"utmSource";
  33. static NSString *const kFDLAnalyticsDataMediumKey = @"utmMedium";
  34. static NSString *const kFDLAnalyticsDataCampaignKey = @"utmCampaign";
  35. static NSString *const kHeaderIosBundleIdentifier = @"X-Ios-Bundle-Identifier";
  36. typedef NSDictionary *_Nullable (^FIRDLNetworkingParserBlock)(
  37. NSString *requestURLString,
  38. NSData *data,
  39. NSString *_Nullable *_Nonnull matchMessagePtr,
  40. NSError *_Nullable *_Nullable errorPtr);
  41. NSString *FIRURLParameterString(NSString *key, NSString *value) {
  42. if (key.length > 0) {
  43. return [NSString stringWithFormat:@"?%@=%@", key, value];
  44. }
  45. return @"";
  46. }
  47. NSString *_Nullable FIRDynamicLinkAPIKeyParameter(NSString *apiKey) {
  48. return apiKey ? FIRURLParameterString(@"key", apiKey) : nil;
  49. }
  50. void FIRMakeHTTPRequest(NSURLRequest *request, FIRNetworkRequestCompletionHandler completion) {
  51. NSURLSessionConfiguration *sessionConfig =
  52. [NSURLSessionConfiguration defaultSessionConfiguration];
  53. NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
  54. NSURLSessionDataTask *dataTask =
  55. [session dataTaskWithRequest:request
  56. completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
  57. NSError *_Nullable error) {
  58. completion(data, error);
  59. }];
  60. [dataTask resume];
  61. }
  62. NSData *_Nullable FIRDataWithDictionary(NSDictionary *dictionary, NSError **_Nullable error) {
  63. return [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:error];
  64. }
  65. @implementation FIRDynamicLinkNetworking {
  66. NSString *_APIKey;
  67. NSString *_URLScheme;
  68. }
  69. - (instancetype)initWithAPIKey:(NSString *)APIKey URLScheme:(NSString *)URLScheme {
  70. NSParameterAssert(APIKey);
  71. NSParameterAssert(URLScheme);
  72. if (self = [super init]) {
  73. _APIKey = [APIKey copy];
  74. _URLScheme = [URLScheme copy];
  75. }
  76. return self;
  77. }
  78. #pragma mark - Public interface
  79. - (void)resolveShortLink:(NSURL *)url
  80. FDLSDKVersion:(NSString *)FDLSDKVersion
  81. completion:(FIRDynamicLinkResolverHandler)handler {
  82. NSParameterAssert(handler);
  83. if (!url) {
  84. handler(nil, nil);
  85. return;
  86. }
  87. NSDictionary *requestBody = @{
  88. @"requestedLink" : url.absoluteString,
  89. @"bundle_id" : [NSBundle mainBundle].bundleIdentifier,
  90. @"sdk_version" : FDLSDKVersion
  91. };
  92. FIRNetworkRequestCompletionHandler resolveLinkCallback = ^(NSData *data, NSError *error) {
  93. NSURL *resolvedURL;
  94. if (!error && data) {
  95. NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
  96. if ([result isKindOfClass:[NSDictionary class]]) {
  97. id invitationIDObject = [result objectForKey:@"invitationId"];
  98. NSString *invitationIDString;
  99. if ([invitationIDObject isKindOfClass:[NSDictionary class]]) {
  100. NSDictionary *invitationIDDictionary = invitationIDObject;
  101. invitationIDString = invitationIDDictionary[@"id"];
  102. } else if ([invitationIDObject isKindOfClass:[NSString class]]) {
  103. invitationIDString = invitationIDObject;
  104. }
  105. NSString *deepLinkString = result[kFDLResolvedLinkDeepLinkURLKey];
  106. NSString *minAppVersion = result[kFDLResolvedLinkMinAppVersionKey];
  107. NSString *utmSource = result[kFDLAnalyticsDataSourceKey];
  108. NSString *utmMedium = result[kFDLAnalyticsDataMediumKey];
  109. NSString *utmCampaign = result[kFDLAnalyticsDataCampaignKey];
  110. resolvedURL = FIRDLDeepLinkURLWithInviteID(invitationIDString, deepLinkString, utmSource,
  111. utmMedium, utmCampaign, NO, nil, minAppVersion,
  112. self->_URLScheme, nil);
  113. }
  114. }
  115. handler(resolvedURL, error);
  116. };
  117. NSString *requestURLString =
  118. [NSString stringWithFormat:@"%@/reopenAttribution%@", kiOSReopenRestBaseUrl,
  119. FIRDynamicLinkAPIKeyParameter(_APIKey)];
  120. [self executeOnePlatformRequest:requestBody
  121. forURL:requestURLString
  122. completionHandler:resolveLinkCallback];
  123. }
  124. - (void)retrievePendingDynamicLinkWithIOSVersion:(NSString *)IOSVersion
  125. resolutionHeight:(NSInteger)resolutionHeight
  126. resolutionWidth:(NSInteger)resolutionWidth
  127. locale:(NSString *)locale
  128. localeRaw:(NSString *)localeRaw
  129. localeFromWebView:(NSString *)localeFromWebView
  130. timezone:(NSString *)timezone
  131. modelName:(NSString *)modelName
  132. FDLSDKVersion:(NSString *)FDLSDKVersion
  133. appInstallationDate:(NSDate *_Nullable)appInstallationDate
  134. uniqueMatchVisualStyle:
  135. (FIRDynamicLinkNetworkingUniqueMatchVisualStyle)uniqueMatchVisualStyle
  136. retrievalProcessType:
  137. (FIRDynamicLinkNetworkingRetrievalProcessType)retrievalProcessType
  138. uniqueMatchLinkToCheck:(NSURL *)uniqueMatchLinkToCheck
  139. handler:
  140. (FIRPostInstallAttributionCompletionHandler)handler {
  141. NSParameterAssert(handler);
  142. NSMutableDictionary *requestBody = [@{
  143. @"bundleId" : [NSBundle mainBundle].bundleIdentifier,
  144. @"device" : @{
  145. @"screenResolutionHeight" : @(resolutionHeight),
  146. @"screenResolutionWidth" : @(resolutionWidth),
  147. @"languageCode" : locale,
  148. @"languageCodeRaw" : localeRaw,
  149. @"languageCodeFromWebview" : localeFromWebView,
  150. @"timezone" : timezone,
  151. @"deviceModelName" : modelName,
  152. },
  153. @"iosVersion" : IOSVersion,
  154. @"sdkVersion" : FDLSDKVersion,
  155. @"visualStyle" : @(uniqueMatchVisualStyle),
  156. @"retrievalMethod" : @(retrievalProcessType),
  157. } mutableCopy];
  158. if (appInstallationDate) {
  159. requestBody[@"appInstallationTime"] = @((NSInteger)[appInstallationDate timeIntervalSince1970]);
  160. }
  161. if (uniqueMatchLinkToCheck) {
  162. requestBody[@"uniqueMatchLinkToCheck"] = uniqueMatchLinkToCheck.absoluteString;
  163. }
  164. FIRDLNetworkingParserBlock responseParserBlock = ^NSDictionary *_Nullable(
  165. NSString *requestURLString, NSData *data, NSString **matchMessagePtr, NSError **errorPtr) {
  166. NSError *serializationError;
  167. NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data
  168. options:0
  169. error:&serializationError];
  170. if (serializationError) {
  171. *errorPtr = serializationError;
  172. return nil;
  173. }
  174. NSString *matchMessage = result[@"matchMessage"];
  175. if (matchMessage.length) {
  176. *matchMessagePtr = matchMessage;
  177. }
  178. // Create the dynamic link parameters
  179. NSMutableDictionary *dynamicLinkParameters = [[NSMutableDictionary alloc] init];
  180. dynamicLinkParameters[kFIRDLParameterInviteId] = result[@"invitationId"];
  181. dynamicLinkParameters[kFIRDLParameterDeepLinkIdentifier] = result[@"deepLink"];
  182. if (result[@"deepLink"]) {
  183. dynamicLinkParameters[kFIRDLParameterMatchType] =
  184. FIRDLMatchTypeStringFromServerString(result[@"attributionConfidence"]);
  185. }
  186. dynamicLinkParameters[kFIRDLParameterSource] = result[@"utmSource"];
  187. dynamicLinkParameters[kFIRDLParameterMedium] = result[@"utmMedium"];
  188. dynamicLinkParameters[kFIRDLParameterCampaign] = result[@"utmCampaign"];
  189. dynamicLinkParameters[kFIRDLParameterMinimumAppVersion] = result[@"appMinimumVersion"];
  190. dynamicLinkParameters[kFIRDLParameterRequestIPVersion] = result[@"requestIpVersion"];
  191. dynamicLinkParameters[kFIRDLParameterMatchMessage] = matchMessage;
  192. return [dynamicLinkParameters copy];
  193. };
  194. [self sendRequestWithBaseURLString:kIosPostInstallAttributionRestBaseUrl
  195. requestBody:requestBody
  196. endpointPath:@"installAttribution"
  197. parserBlock:responseParserBlock
  198. completion:handler];
  199. }
  200. - (void)convertInvitation:(NSString *)invitationID
  201. handler:(nullable FIRDynamicLinkNetworkingErrorHandler)handler {
  202. if (!invitationID) {
  203. return;
  204. }
  205. NSDictionary *requestBody = @{
  206. @"invitationId" : @{@"id" : invitationID},
  207. @"containerClientId" : @{
  208. @"type" : @"IOS",
  209. }
  210. };
  211. FIRNetworkRequestCompletionHandler convertInvitationCallback = ^(NSData *data, NSError *error) {
  212. if (handler) {
  213. dispatch_async(dispatch_get_main_queue(), ^{
  214. handler(error);
  215. });
  216. }
  217. };
  218. NSString *requestURL = [NSString stringWithFormat:@"%@/convertInvitation%@", kApiaryRestBaseUrl,
  219. FIRDynamicLinkAPIKeyParameter(_APIKey)];
  220. [self executeOnePlatformRequest:requestBody
  221. forURL:requestURL
  222. completionHandler:convertInvitationCallback];
  223. }
  224. #pragma mark - Internal methods
  225. - (void)sendRequestWithBaseURLString:(NSString *)baseURL
  226. requestBody:(NSDictionary *)requestBody
  227. endpointPath:(NSString *)endpointPath
  228. parserBlock:(FIRDLNetworkingParserBlock)parserBlock
  229. completion:(FIRPostInstallAttributionCompletionHandler)handler {
  230. NSParameterAssert(handler);
  231. NSString *requestURLString = [NSString
  232. stringWithFormat:@"%@/%@%@", baseURL, endpointPath, FIRDynamicLinkAPIKeyParameter(_APIKey)];
  233. FIRNetworkRequestCompletionHandler completeInvitationByDeviceCallback =
  234. ^(NSData *data, NSError *error) {
  235. if (error || !data) {
  236. dispatch_async(dispatch_get_main_queue(), ^{
  237. handler(nil, nil, error);
  238. });
  239. return;
  240. }
  241. NSString *matchMessage = nil;
  242. NSError *parsingError = nil;
  243. NSDictionary *parsedDynamicLinkParameters =
  244. parserBlock(requestURLString, data, &matchMessage, &parsingError);
  245. dispatch_async(dispatch_get_main_queue(), ^{
  246. handler(parsedDynamicLinkParameters, matchMessage, parsingError);
  247. });
  248. };
  249. [self executeOnePlatformRequest:requestBody
  250. forURL:requestURLString
  251. completionHandler:completeInvitationByDeviceCallback];
  252. }
  253. - (void)executeOnePlatformRequest:(NSDictionary *)requestBody
  254. forURL:(NSString *)requestURLString
  255. completionHandler:(FIRNetworkRequestCompletionHandler)handler {
  256. NSURL *requestURL = [NSURL URLWithString:requestURLString];
  257. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL];
  258. // TODO: Verify that HTTPBody and HTTPMethod are iOS 8+ and find an alternative.
  259. request.HTTPBody = FIRDataWithDictionary(requestBody, nil);
  260. request.HTTPMethod = @"POST";
  261. [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
  262. // Set the iOS bundleID as a request header.
  263. NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier];
  264. if (bundleID) {
  265. [request setValue:bundleID forHTTPHeaderField:kHeaderIosBundleIdentifier];
  266. }
  267. FIRMakeHTTPRequest(request, handler);
  268. }
  269. @end
  270. NS_ASSUME_NONNULL_END
  271. #endif // TARGET_OS_IOS