FIRIAMFetchFlow.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. /*
  2. * Copyright 2017 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 || TARGET_OS_TV
  18. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  19. #import "FirebaseInAppMessaging/Sources/FIRCore+InAppMessaging.h"
  20. #import "FirebaseInAppMessaging/Sources/Private/Analytics/FIRIAMClearcutLogger.h"
  21. #import "FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMFetchFlow.h"
  22. #import "FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMRuntimeManager.h"
  23. @implementation FIRIAMFetchSetting
  24. @end
  25. // the notification message to say that the fetch flow is done
  26. NSString *const kFIRIAMFetchIsDoneNotification = @"FIRIAMFetchIsDoneNotification";
  27. @interface FIRIAMFetchFlow ()
  28. @property(nonatomic) id<FIRIAMTimeFetcher> timeFetcher;
  29. @property(nonatomic) NSTimeInterval lastFetchTime;
  30. @property(nonatomic, nonnull, readonly) FIRIAMFetchSetting *setting;
  31. @property(nonatomic, nonnull, readonly) FIRIAMMessageClientCache *messageCache;
  32. @property(nonatomic) id<FIRIAMMessageFetcher> messageFetcher;
  33. @property(nonatomic, nonnull, readonly) id<FIRIAMBookKeeper> fetchBookKeeper;
  34. @property(nonatomic, nonnull, readonly) FIRIAMActivityLogger *activityLogger;
  35. @property(nonatomic, nonnull, readonly) id<FIRIAMAnalyticsEventLogger> analyticsEventLogger;
  36. @property(nonatomic, nonnull, readonly) FIRIAMSDKModeManager *sdkModeManager;
  37. @property(nonatomic, nonnull, readonly) FIRIAMDisplayExecutor *displayExecutor;
  38. @end
  39. @implementation FIRIAMFetchFlow
  40. - (instancetype)initWithSetting:(FIRIAMFetchSetting *)setting
  41. messageCache:(FIRIAMMessageClientCache *)cache
  42. messageFetcher:(id<FIRIAMMessageFetcher>)messageFetcher
  43. timeFetcher:(id<FIRIAMTimeFetcher>)timeFetcher
  44. bookKeeper:(id<FIRIAMBookKeeper>)fetchBookKeeper
  45. activityLogger:(FIRIAMActivityLogger *)activityLogger
  46. analyticsEventLogger:(id<FIRIAMAnalyticsEventLogger>)analyticsEventLogger
  47. FIRIAMSDKModeManager:(FIRIAMSDKModeManager *)sdkModeManager
  48. displayExecutor:(FIRIAMDisplayExecutor *)displayExecutor {
  49. if (self = [super init]) {
  50. _timeFetcher = timeFetcher;
  51. _lastFetchTime = [fetchBookKeeper lastFetchTime];
  52. _setting = setting;
  53. _messageCache = cache;
  54. _messageFetcher = messageFetcher;
  55. _fetchBookKeeper = fetchBookKeeper;
  56. _activityLogger = activityLogger;
  57. _analyticsEventLogger = analyticsEventLogger;
  58. _sdkModeManager = sdkModeManager;
  59. _displayExecutor = displayExecutor;
  60. }
  61. return self;
  62. }
  63. - (FIRIAMAnalyticsLogEventType)fetchErrorToLogEventType:(NSError *)error {
  64. if ([error.domain isEqual:NSURLErrorDomain]) {
  65. if (error.code == NSURLErrorNotConnectedToInternet) {
  66. return FIRIAMAnalyticsEventFetchAPINetworkError;
  67. } else {
  68. // error.code could be a non 2xx status code
  69. if (error.code > 0) {
  70. if (error.code >= 400 && error.code < 500) {
  71. return FIRIAMAnalyticsEventFetchAPIClientError;
  72. } else {
  73. if (error.code >= 500 && error.code < 600) {
  74. return FIRIAMAnalyticsEventFetchAPIServerError;
  75. }
  76. }
  77. }
  78. }
  79. }
  80. return FIRIAMAnalyticsLogEventUnknown;
  81. }
  82. - (void)sendFetchIsDoneNotification {
  83. [[NSNotificationCenter defaultCenter] postNotificationName:kFIRIAMFetchIsDoneNotification
  84. object:self];
  85. }
  86. - (void)handleSuccessullyFetchedMessages:(NSArray<FIRIAMMessageDefinition *> *)messagesInResponse
  87. withFetchWaitTime:(NSNumber *_Nullable)fetchWaitTime
  88. requestImpressions:(NSArray<FIRIAMImpressionRecord *> *)requestImpressions {
  89. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700004", @"%lu messages were fetched successfully.",
  90. (unsigned long)messagesInResponse.count);
  91. for (FIRIAMMessageDefinition *next in messagesInResponse) {
  92. if (next.isTestMessage && self.sdkModeManager.currentMode != FIRIAMSDKModeTesting) {
  93. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700006",
  94. @"Seeing test message in fetch response. Turn "
  95. "the current instance into a testing instance.");
  96. [self.sdkModeManager becomeTestingInstance];
  97. }
  98. }
  99. NSArray<NSString *> *responseMessageIDs =
  100. [messagesInResponse valueForKeyPath:@"renderData.messageID"];
  101. NSArray<NSString *> *impressionMessageIDs = [requestImpressions valueForKey:@"messageID"];
  102. // We are going to clear impression records for those IDs that are in both impressionMessageIDs
  103. // and responseMessageIDs. This is to avoid incorrectly clearing impressions records that come
  104. // in between the sending the request and receiving the response for the fetch operation.
  105. // So we are computing intersection between responseMessageIDs and impressionMessageIDs and use
  106. // that for impression log clearing.
  107. NSMutableSet *idIntersection = [NSMutableSet setWithArray:responseMessageIDs];
  108. [idIntersection intersectSet:[NSSet setWithArray:impressionMessageIDs]];
  109. [self.fetchBookKeeper clearImpressionsWithMessageList:[idIntersection allObjects]];
  110. [self.messageCache setMessageData:messagesInResponse];
  111. [self.sdkModeManager registerOneMoreFetch];
  112. [self.fetchBookKeeper recordNewFetchWithFetchCount:messagesInResponse.count
  113. withTimestampInSeconds:[self.timeFetcher currentTimestampInSeconds]
  114. nextFetchWaitTime:fetchWaitTime];
  115. }
  116. - (void)checkAndFetchForInitialAppLaunch:(BOOL)forInitialAppLaunch {
  117. NSTimeInterval intervalFromLastFetchInSeconds =
  118. [self.timeFetcher currentTimestampInSeconds] - self.fetchBookKeeper.lastFetchTime;
  119. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700005",
  120. @"Interval from last time fetch is %lf seconds", intervalFromLastFetchInSeconds);
  121. BOOL fetchIsAllowedNow = NO;
  122. if (intervalFromLastFetchInSeconds >= self.fetchBookKeeper.nextFetchWaitTime) {
  123. // it's enough wait time interval from last fetch.
  124. fetchIsAllowedNow = YES;
  125. } else {
  126. FIRIAMSDKMode sdkMode = [self.sdkModeManager currentMode];
  127. if (sdkMode == FIRIAMSDKModeNewlyInstalled || sdkMode == FIRIAMSDKModeTesting) {
  128. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700007",
  129. @"OK to fetch due to current SDK mode being %@",
  130. FIRIAMDescriptonStringForSDKMode(sdkMode));
  131. fetchIsAllowedNow = YES;
  132. } else {
  133. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700008",
  134. @"Interval from last time fetch is %lf seconds, smaller than fetch wait time %lf",
  135. intervalFromLastFetchInSeconds, self.fetchBookKeeper.nextFetchWaitTime);
  136. }
  137. }
  138. if (fetchIsAllowedNow) {
  139. // we are allowed to fetch in-app message from time interval wise
  140. FIRIAMActivityRecord *record =
  141. [[FIRIAMActivityRecord alloc] initWithActivityType:FIRIAMActivityTypeCheckForFetch
  142. isSuccessful:YES
  143. withDetail:@"OK to do a fetch"
  144. timestamp:nil];
  145. [self.activityLogger addLogRecord:record];
  146. NSArray<FIRIAMImpressionRecord *> *impressions = [self.fetchBookKeeper getImpressions];
  147. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700001", @"Go ahead to fetch messages");
  148. NSTimeInterval fetchStartTime = [[NSDate date] timeIntervalSince1970];
  149. [self.messageFetcher
  150. fetchMessagesWithImpressionList:impressions
  151. withCompletion:^(NSArray<FIRIAMMessageDefinition *> *_Nullable messages,
  152. NSNumber *_Nullable nextFetchWaitTime,
  153. NSInteger discardedMessageCount,
  154. NSError *_Nullable error) {
  155. if (error) {
  156. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM700002",
  157. @"Error happened during message fetching %@", error);
  158. FIRIAMAnalyticsLogEventType eventType =
  159. [self fetchErrorToLogEventType:error];
  160. [self.analyticsEventLogger logAnalyticsEventForType:eventType
  161. forCampaignID:@"all"
  162. withCampaignName:@"all"
  163. eventTimeInMs:nil
  164. completion:^(BOOL success){
  165. // nothing to do
  166. }];
  167. FIRIAMActivityRecord *record = [[FIRIAMActivityRecord alloc]
  168. initWithActivityType:FIRIAMActivityTypeFetchMessage
  169. isSuccessful:NO
  170. withDetail:error.description
  171. timestamp:nil];
  172. [self.activityLogger addLogRecord:record];
  173. } else {
  174. double fetchOperationLatencyInMills =
  175. ([[NSDate date] timeIntervalSince1970] - fetchStartTime) * 1000;
  176. NSString *impressionListString =
  177. [impressions componentsJoinedByString:@","];
  178. NSString *activityLogDetail = @"";
  179. if (discardedMessageCount > 0) {
  180. activityLogDetail = [NSString
  181. stringWithFormat:
  182. @"%lu messages fetched with impression list as [%@]"
  183. " and %lu messages are discarded due to data being "
  184. "invalid. It took"
  185. " %lf milliseconds",
  186. (unsigned long)messages.count, impressionListString,
  187. (unsigned long)discardedMessageCount,
  188. fetchOperationLatencyInMills];
  189. } else {
  190. activityLogDetail = [NSString
  191. stringWithFormat:
  192. @"%lu messages fetched with impression list as [%@]. It took"
  193. " %lf milliseconds",
  194. (unsigned long)messages.count, impressionListString,
  195. fetchOperationLatencyInMills];
  196. }
  197. FIRIAMActivityRecord *record = [[FIRIAMActivityRecord alloc]
  198. initWithActivityType:FIRIAMActivityTypeFetchMessage
  199. isSuccessful:YES
  200. withDetail:activityLogDetail
  201. timestamp:nil];
  202. [self.activityLogger addLogRecord:record];
  203. // Now handle the fetched messages.
  204. [self handleSuccessullyFetchedMessages:messages
  205. withFetchWaitTime:nextFetchWaitTime
  206. requestImpressions:impressions];
  207. if (forInitialAppLaunch) {
  208. [self checkForAppLaunchMessage];
  209. }
  210. }
  211. // Send this regardless whether fetch is successful or not.
  212. [self sendFetchIsDoneNotification];
  213. }];
  214. } else {
  215. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM700003",
  216. @"Only %lf seconds from last fetch time. No action.",
  217. intervalFromLastFetchInSeconds);
  218. // for no fetch case, we still send out the notification so that and display flow can continue
  219. // from here.
  220. [self sendFetchIsDoneNotification];
  221. FIRIAMActivityRecord *record =
  222. [[FIRIAMActivityRecord alloc] initWithActivityType:FIRIAMActivityTypeCheckForFetch
  223. isSuccessful:NO
  224. withDetail:@"Abort due to check time interval "
  225. "not reached yet"
  226. timestamp:nil];
  227. [self.activityLogger addLogRecord:record];
  228. }
  229. }
  230. - (void)checkForAppLaunchMessage {
  231. [self.displayExecutor checkAndDisplayNextAppLaunchMessage];
  232. }
  233. @end
  234. #endif // TARGET_OS_IOS || TARGET_OS_TV