FIRIAMFetchFlow.m 13 KB

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