FIRDynamicLinkNetworking.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  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/FDLDeviceHeuristicsHelper.h"
  22. #import "FirebaseDynamicLinks/Sources/Utilities/FDLUtilities.h"
  23. NS_ASSUME_NONNULL_BEGIN
  24. NSString *const kApiaryRestBaseUrl = @"https://appinvite-pa.googleapis.com/v1";
  25. static NSString *const kiOSReopenRestBaseUrl = @"https://firebasedynamiclinks.googleapis.com/v1";
  26. // Endpoint for default retrieval process V2. (Endpoint version is V1)
  27. static NSString *const kIosPostInstallAttributionRestBaseUrl =
  28. @"https://firebasedynamiclinks.googleapis.com/v1";
  29. static NSString *const kReasonString = @"reason";
  30. static NSString *const kiOSInviteReason = @"ios_invite";
  31. NSString *const kFDLResolvedLinkDeepLinkURLKey = @"deepLink";
  32. NSString *const kFDLResolvedLinkMinAppVersionKey = @"iosMinAppVersion";
  33. static NSString *const kFDLAnalyticsDataSourceKey = @"utmSource";
  34. static NSString *const kFDLAnalyticsDataMediumKey = @"utmMedium";
  35. static NSString *const kFDLAnalyticsDataCampaignKey = @"utmCampaign";
  36. static NSString *const kFDLAnalyticsDataTermKey = @"utmTerm";
  37. static NSString *const kFDLAnalyticsDataContentKey = @"utmContent";
  38. static NSString *const kHeaderIosBundleIdentifier = @"X-Ios-Bundle-Identifier";
  39. static NSString *const kGenericErrorDomain = @"com.firebase.dynamicLinks";
  40. typedef NSDictionary *_Nullable (^FIRDLNetworkingParserBlock)(
  41. NSString *requestURLString,
  42. NSData *data,
  43. NSString *_Nullable *_Nonnull matchMessagePtr,
  44. NSError *_Nullable *_Nullable errorPtr);
  45. NSString *FIRURLParameterString(NSString *key, NSString *value) {
  46. if (key.length > 0) {
  47. return [NSString stringWithFormat:@"?%@=%@", key, value];
  48. }
  49. return @"";
  50. }
  51. NSString *_Nullable FIRDynamicLinkAPIKeyParameter(NSString *apiKey) {
  52. return apiKey ? FIRURLParameterString(@"key", apiKey) : nil;
  53. }
  54. void FIRMakeHTTPRequest(NSURLRequest *request, FIRNetworkRequestCompletionHandler completion) {
  55. NSURLSessionConfiguration *sessionConfig =
  56. [NSURLSessionConfiguration defaultSessionConfiguration];
  57. NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig];
  58. NSURLSessionDataTask *dataTask =
  59. [session dataTaskWithRequest:request
  60. completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
  61. NSError *_Nullable error) {
  62. completion(data, response, error);
  63. }];
  64. [dataTask resume];
  65. }
  66. NSData *_Nullable FIRDataWithDictionary(NSDictionary *dictionary, NSError **_Nullable error) {
  67. return [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:error];
  68. }
  69. @implementation FIRDynamicLinkNetworking {
  70. NSString *_APIKey;
  71. NSString *_URLScheme;
  72. }
  73. - (instancetype)initWithAPIKey:(NSString *)APIKey URLScheme:(NSString *)URLScheme {
  74. NSParameterAssert(APIKey);
  75. NSParameterAssert(URLScheme);
  76. if (self = [super init]) {
  77. _APIKey = [APIKey copy];
  78. _URLScheme = [URLScheme copy];
  79. }
  80. return self;
  81. }
  82. + (nullable NSError *)extractErrorForShortLink:(NSURL *)url
  83. data:(NSData *)data
  84. response:(NSURLResponse *)response
  85. error:(nullable NSError *)error {
  86. if (error) {
  87. return error;
  88. }
  89. NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
  90. NSError *customError = nil;
  91. if (![response isKindOfClass:[NSHTTPURLResponse class]]) {
  92. customError =
  93. [NSError errorWithDomain:kGenericErrorDomain
  94. code:0
  95. userInfo:@{@"message" : @"Response should be of type NSHTTPURLResponse."}];
  96. } else if ((statusCode < 200 || statusCode >= 300) && data) {
  97. NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
  98. if ([result isKindOfClass:[NSDictionary class]] && [result objectForKey:@"error"]) {
  99. id err = [result objectForKey:@"error"];
  100. customError = [NSError errorWithDomain:kGenericErrorDomain code:statusCode userInfo:err];
  101. } else {
  102. customError = [NSError
  103. errorWithDomain:kGenericErrorDomain
  104. code:0
  105. userInfo:@{
  106. @"message" :
  107. [NSString stringWithFormat:@"Failed to resolve link: %@", url.absoluteString]
  108. }];
  109. }
  110. }
  111. return customError;
  112. }
  113. #pragma mark - Public interface
  114. - (void)resolveShortLink:(NSURL *)url
  115. FDLSDKVersion:(NSString *)FDLSDKVersion
  116. completion:(FIRDynamicLinkResolverHandler)handler {
  117. NSParameterAssert(handler);
  118. if (!url) {
  119. handler(nil, nil);
  120. return;
  121. }
  122. NSDictionary *requestBody = @{
  123. @"requestedLink" : url.absoluteString,
  124. @"bundle_id" : [NSBundle mainBundle].bundleIdentifier,
  125. @"sdk_version" : FDLSDKVersion
  126. };
  127. FIRNetworkRequestCompletionHandler resolveLinkCallback =
  128. ^(NSData *data, NSURLResponse *response, NSError *error) {
  129. NSURL *resolvedURL = nil;
  130. NSError *extractedError = [FIRDynamicLinkNetworking extractErrorForShortLink:url
  131. data:data
  132. response:response
  133. error:error];
  134. if (!extractedError && data) {
  135. NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
  136. if ([result isKindOfClass:[NSDictionary class]]) {
  137. id invitationIDObject = [result objectForKey:@"invitationId"];
  138. NSString *invitationIDString;
  139. if ([invitationIDObject isKindOfClass:[NSDictionary class]]) {
  140. NSDictionary *invitationIDDictionary = invitationIDObject;
  141. invitationIDString = invitationIDDictionary[@"id"];
  142. } else if ([invitationIDObject isKindOfClass:[NSString class]]) {
  143. invitationIDString = invitationIDObject;
  144. }
  145. NSString *deepLinkString = result[kFDLResolvedLinkDeepLinkURLKey];
  146. NSString *minAppVersion = result[kFDLResolvedLinkMinAppVersionKey];
  147. NSString *utmSource = result[kFDLAnalyticsDataSourceKey];
  148. NSString *utmMedium = result[kFDLAnalyticsDataMediumKey];
  149. NSString *utmCampaign = result[kFDLAnalyticsDataCampaignKey];
  150. NSString *utmContent = result[kFDLAnalyticsDataContentKey];
  151. NSString *utmTerm = result[kFDLAnalyticsDataTermKey];
  152. resolvedURL = FIRDLDeepLinkURLWithInviteID(
  153. invitationIDString, deepLinkString, utmSource, utmMedium, utmCampaign, utmContent,
  154. utmTerm, NO, nil, minAppVersion, self->_URLScheme, nil);
  155. }
  156. }
  157. handler(resolvedURL, extractedError);
  158. };
  159. NSString *requestURLString =
  160. [NSString stringWithFormat:@"%@/reopenAttribution%@", kiOSReopenRestBaseUrl,
  161. FIRDynamicLinkAPIKeyParameter(_APIKey)];
  162. [self executeOnePlatformRequest:requestBody
  163. forURL:requestURLString
  164. completionHandler:resolveLinkCallback];
  165. }
  166. - (void)retrievePendingDynamicLinkWithIOSVersion:(NSString *)IOSVersion
  167. resolutionHeight:(NSInteger)resolutionHeight
  168. resolutionWidth:(NSInteger)resolutionWidth
  169. locale:(NSString *)locale
  170. localeRaw:(NSString *)localeRaw
  171. localeFromWebView:(NSString *)localeFromWebView
  172. timezone:(NSString *)timezone
  173. modelName:(NSString *)modelName
  174. FDLSDKVersion:(NSString *)FDLSDKVersion
  175. appInstallationDate:(NSDate *_Nullable)appInstallationDate
  176. uniqueMatchVisualStyle:
  177. (FIRDynamicLinkNetworkingUniqueMatchVisualStyle)uniqueMatchVisualStyle
  178. retrievalProcessType:
  179. (FIRDynamicLinkNetworkingRetrievalProcessType)retrievalProcessType
  180. uniqueMatchLinkToCheck:(NSURL *)uniqueMatchLinkToCheck
  181. handler:
  182. (FIRPostInstallAttributionCompletionHandler)handler {
  183. NSParameterAssert(handler);
  184. NSMutableDictionary *requestBody = [@{
  185. @"bundleId" : [NSBundle mainBundle].bundleIdentifier,
  186. @"device" :
  187. [FDLDeviceHeuristicsHelper FDLDeviceInfoDictionaryFromResolutionHeight:resolutionHeight
  188. resolutionWidth:resolutionWidth
  189. locale:locale
  190. localeRaw:localeRaw
  191. localeFromWebview:localeFromWebView
  192. timeZone:timezone
  193. modelName:modelName],
  194. @"iosVersion" : IOSVersion,
  195. @"sdkVersion" : FDLSDKVersion,
  196. @"visualStyle" : @(uniqueMatchVisualStyle),
  197. @"retrievalMethod" : @(retrievalProcessType),
  198. } mutableCopy];
  199. if (appInstallationDate) {
  200. requestBody[@"appInstallationTime"] = @((NSInteger)[appInstallationDate timeIntervalSince1970]);
  201. }
  202. if (uniqueMatchLinkToCheck) {
  203. requestBody[@"uniqueMatchLinkToCheck"] = uniqueMatchLinkToCheck.absoluteString;
  204. }
  205. FIRDLNetworkingParserBlock responseParserBlock = ^NSDictionary *_Nullable(
  206. NSString *requestURLString, NSData *data, NSString **matchMessagePtr, NSError **errorPtr) {
  207. NSError *serializationError;
  208. NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data
  209. options:0
  210. error:&serializationError];
  211. if (serializationError) {
  212. if (errorPtr != nil) {
  213. *errorPtr = serializationError;
  214. }
  215. return nil;
  216. }
  217. NSString *matchMessage = result[@"matchMessage"];
  218. if (matchMessage.length) {
  219. *matchMessagePtr = matchMessage;
  220. }
  221. // Create the dynamic link parameters
  222. NSMutableDictionary *dynamicLinkParameters = [[NSMutableDictionary alloc] init];
  223. dynamicLinkParameters[kFIRDLParameterInviteId] = result[@"invitationId"];
  224. dynamicLinkParameters[kFIRDLParameterDeepLinkIdentifier] = result[@"deepLink"];
  225. if (result[@"deepLink"]) {
  226. dynamicLinkParameters[kFIRDLParameterMatchType] =
  227. FIRDLMatchTypeStringFromServerString(result[@"attributionConfidence"]);
  228. }
  229. dynamicLinkParameters[kFIRDLParameterSource] = result[@"utmSource"];
  230. dynamicLinkParameters[kFIRDLParameterMedium] = result[@"utmMedium"];
  231. dynamicLinkParameters[kFIRDLParameterCampaign] = result[@"utmCampaign"];
  232. dynamicLinkParameters[kFIRDLParameterMinimumAppVersion] = result[@"appMinimumVersion"];
  233. dynamicLinkParameters[kFIRDLParameterRequestIPVersion] = result[@"requestIpVersion"];
  234. dynamicLinkParameters[kFIRDLParameterMatchMessage] = matchMessage;
  235. return [dynamicLinkParameters copy];
  236. };
  237. [self sendRequestWithBaseURLString:kIosPostInstallAttributionRestBaseUrl
  238. requestBody:requestBody
  239. endpointPath:@"installAttribution"
  240. parserBlock:responseParserBlock
  241. completion:handler];
  242. }
  243. - (void)convertInvitation:(NSString *)invitationID
  244. handler:(nullable FIRDynamicLinkNetworkingErrorHandler)handler {
  245. if (!invitationID) {
  246. return;
  247. }
  248. NSDictionary *requestBody = @{
  249. @"invitationId" : @{@"id" : invitationID},
  250. @"containerClientId" : @{
  251. @"type" : @"IOS",
  252. }
  253. };
  254. FIRNetworkRequestCompletionHandler convertInvitationCallback =
  255. ^(NSData *data, NSURLResponse *response, NSError *error) {
  256. if (handler) {
  257. dispatch_async(dispatch_get_main_queue(), ^{
  258. handler(error);
  259. });
  260. }
  261. };
  262. NSString *requestURL = [NSString stringWithFormat:@"%@/convertInvitation%@", kApiaryRestBaseUrl,
  263. FIRDynamicLinkAPIKeyParameter(_APIKey)];
  264. [self executeOnePlatformRequest:requestBody
  265. forURL:requestURL
  266. completionHandler:convertInvitationCallback];
  267. }
  268. #pragma mark - Internal methods
  269. - (void)sendRequestWithBaseURLString:(NSString *)baseURL
  270. requestBody:(NSDictionary *)requestBody
  271. endpointPath:(NSString *)endpointPath
  272. parserBlock:(FIRDLNetworkingParserBlock)parserBlock
  273. completion:(FIRPostInstallAttributionCompletionHandler)handler {
  274. NSParameterAssert(handler);
  275. NSString *requestURLString = [NSString
  276. stringWithFormat:@"%@/%@%@", baseURL, endpointPath, FIRDynamicLinkAPIKeyParameter(_APIKey)];
  277. FIRNetworkRequestCompletionHandler completeInvitationByDeviceCallback =
  278. ^(NSData *data, NSURLResponse *response, NSError *error) {
  279. if (error || !data) {
  280. dispatch_async(dispatch_get_main_queue(), ^{
  281. handler(nil, nil, error);
  282. });
  283. return;
  284. }
  285. NSString *matchMessage = nil;
  286. NSError *parsingError = nil;
  287. NSDictionary *parsedDynamicLinkParameters =
  288. parserBlock(requestURLString, data, &matchMessage, &parsingError);
  289. dispatch_async(dispatch_get_main_queue(), ^{
  290. handler(parsedDynamicLinkParameters, matchMessage, parsingError);
  291. });
  292. };
  293. [self executeOnePlatformRequest:requestBody
  294. forURL:requestURLString
  295. completionHandler:completeInvitationByDeviceCallback];
  296. }
  297. - (void)executeOnePlatformRequest:(NSDictionary *)requestBody
  298. forURL:(NSString *)requestURLString
  299. completionHandler:(FIRNetworkRequestCompletionHandler)handler {
  300. NSURL *requestURL = [NSURL URLWithString:requestURLString];
  301. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:requestURL];
  302. // TODO: Verify that HTTPBody and HTTPMethod are iOS 8+ and find an alternative.
  303. request.HTTPBody = FIRDataWithDictionary(requestBody, nil);
  304. request.HTTPMethod = @"POST";
  305. [request setValue:@"application/json; charset=utf-8" forHTTPHeaderField:@"Content-Type"];
  306. // Set the iOS bundleID as a request header.
  307. NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier];
  308. if (bundleID) {
  309. [request setValue:bundleID forHTTPHeaderField:kHeaderIosBundleIdentifier];
  310. }
  311. FIRMakeHTTPRequest(request, handler);
  312. }
  313. @end
  314. NS_ASSUME_NONNULL_END
  315. #endif // TARGET_OS_IOS