FIRIAMFetchFlow.m 12 KB

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