FIRIAMFetchFlow.m 13 KB

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