FIRIAMBookKeeper.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  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 "FIRIAMBookKeeper.h"
  19. NSString *const FIRIAM_UserDefaultsKeyForImpressions = @"firebase-iam-message-impressions";
  20. NSString *const FIRIAM_UserDefaultsKeyForLastImpressionTimestamp =
  21. @"firebase-iam-last-impression-timestamp";
  22. NSString *FIRIAM_UserDefaultsKeyForLastFetchTimestamp = @"firebase-iam-last-fetch-timestamp";
  23. // The two keys used to map FIRIAMImpressionRecord object to a NSDictionary object for
  24. // persistence.
  25. NSString *const FIRIAM_ImpressionDictKeyForID = @"message_id";
  26. NSString *const FIRIAM_ImpressionDictKeyForTimestamp = @"impression_time";
  27. static NSString *const kUserDefaultsKeyForFetchWaitTime = @"firebase-iam-fetch-wait-time";
  28. // 24 hours
  29. static NSTimeInterval kDefaultFetchWaitTimeInSeconds = 24 * 60 * 60;
  30. // 3 days
  31. static NSTimeInterval kMaxFetchWaitTimeInSeconds = 3 * 24 * 60 * 60;
  32. @interface FIRIAMBookKeeperViaUserDefaults ()
  33. @property(nonatomic) double lastDisplayTime;
  34. @property(nonatomic) double lastFetchTime;
  35. @property(nonatomic) double nextFetchWaitTime;
  36. @property(nonatomic, nonnull) NSUserDefaults *defaults;
  37. @end
  38. @interface FIRIAMImpressionRecord ()
  39. - (instancetype)initWithStorageDictionary:(NSDictionary *)dict;
  40. @end
  41. @implementation FIRIAMImpressionRecord
  42. - (instancetype)initWithMessageID:(NSString *)messageID
  43. impressionTimeInSeconds:(long)impressionTime {
  44. if (self = [super init]) {
  45. _messageID = messageID;
  46. _impressionTimeInSeconds = impressionTime;
  47. }
  48. return self;
  49. }
  50. - (instancetype)initWithStorageDictionary:(NSDictionary *)dict {
  51. id timestamp = dict[FIRIAM_ImpressionDictKeyForTimestamp];
  52. id messageID = dict[FIRIAM_ImpressionDictKeyForID];
  53. if (![timestamp isKindOfClass:[NSNumber class]] || ![messageID isKindOfClass:[NSString class]]) {
  54. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM270003",
  55. @"Incorrect data in the dictionary object for creating a FIRIAMImpressionRecord"
  56. " object");
  57. return nil;
  58. } else {
  59. return [self initWithMessageID:messageID
  60. impressionTimeInSeconds:((NSNumber *)timestamp).longValue];
  61. }
  62. }
  63. - (NSString *)description {
  64. return [NSString stringWithFormat:@"%@ impressed at %ld in seconds", self.messageID,
  65. self.impressionTimeInSeconds];
  66. }
  67. @end
  68. @implementation FIRIAMBookKeeperViaUserDefaults
  69. - (instancetype)initWithUserDefaults:(NSUserDefaults *)userDefaults {
  70. if (self = [super init]) {
  71. _defaults = userDefaults;
  72. // ok if it returns 0 due to the entry being absent
  73. _lastDisplayTime = [_defaults doubleForKey:FIRIAM_UserDefaultsKeyForLastImpressionTimestamp];
  74. _lastFetchTime = [_defaults doubleForKey:FIRIAM_UserDefaultsKeyForLastFetchTimestamp];
  75. id fetchWaitTimeEntry = [_defaults objectForKey:kUserDefaultsKeyForFetchWaitTime];
  76. if (![fetchWaitTimeEntry isKindOfClass:NSNumber.class]) {
  77. // This corresponds to the case there is no wait time entry is set in user defaults yet
  78. _nextFetchWaitTime = kDefaultFetchWaitTimeInSeconds;
  79. } else {
  80. _nextFetchWaitTime = ((NSNumber *)fetchWaitTimeEntry).doubleValue;
  81. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM270009",
  82. @"Next fetch wait time loaded from user defaults is %lf", _nextFetchWaitTime);
  83. }
  84. }
  85. return self;
  86. }
  87. // A helper function for reading and verifying the stored array data for impressions
  88. // in UserDefaults. It returns nil if it does not exist or fail to pass the data type
  89. // checking.
  90. - (NSArray *)fetchImpressionArrayFromStorage {
  91. id impressionsData = [self.defaults objectForKey:FIRIAM_UserDefaultsKeyForImpressions];
  92. if (impressionsData && ![impressionsData isKindOfClass:[NSArray class]]) {
  93. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM270007",
  94. @"Found non-array data from impression userdefaults storage with key %@",
  95. FIRIAM_UserDefaultsKeyForImpressions);
  96. return nil;
  97. }
  98. return (NSArray *)impressionsData;
  99. }
  100. - (void)recordNewImpressionForMessage:(NSString *)messageID
  101. withStartTimestampInSeconds:(double)timestamp {
  102. @synchronized(self) {
  103. NSArray *oldImpressions = [self fetchImpressionArrayFromStorage];
  104. // oldImpressions could be nil at the first time
  105. NSMutableArray *newImpressions =
  106. oldImpressions ? [oldImpressions mutableCopy] : [[NSMutableArray alloc] init];
  107. // Two cases
  108. // If a prior impression exists for that messageID, update its impression timestamp
  109. // If a prior impression for that messageID does not exist, add a new entry for the
  110. // messageID.
  111. NSDictionary *newImpressionEntry = @{
  112. FIRIAM_ImpressionDictKeyForID : messageID,
  113. FIRIAM_ImpressionDictKeyForTimestamp : [NSNumber numberWithDouble:timestamp]
  114. };
  115. BOOL oldImpressionRecordFound = NO;
  116. for (int i = 0; i < newImpressions.count; i++) {
  117. if ([newImpressions[i] isKindOfClass:[NSDictionary class]]) {
  118. NSDictionary *currentItem = (NSDictionary *)newImpressions[i];
  119. if ([messageID isEqualToString:currentItem[FIRIAM_ImpressionDictKeyForID]]) {
  120. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM270001",
  121. @"Updating timestamp of existing impression record to be %f for "
  122. "message %@",
  123. timestamp, messageID);
  124. [newImpressions replaceObjectAtIndex:i withObject:newImpressionEntry];
  125. oldImpressionRecordFound = YES;
  126. break;
  127. }
  128. }
  129. }
  130. if (!oldImpressionRecordFound) {
  131. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM270002",
  132. @"Insert the first impression record for message %@ with timestamp in seconds "
  133. "as %f",
  134. messageID, timestamp);
  135. [newImpressions addObject:newImpressionEntry];
  136. }
  137. [self.defaults setObject:newImpressions forKey:FIRIAM_UserDefaultsKeyForImpressions];
  138. [self.defaults setDouble:timestamp forKey:FIRIAM_UserDefaultsKeyForLastImpressionTimestamp];
  139. self.lastDisplayTime = timestamp;
  140. }
  141. }
  142. - (void)clearImpressionsWithMessageList:(NSArray<NSString *> *)messageList {
  143. @synchronized(self) {
  144. NSArray *existingImpressions = [self fetchImpressionArrayFromStorage];
  145. NSSet<NSString *> *messageIDSet = [NSSet setWithArray:messageList];
  146. NSPredicate *notInMessageListPredicate =
  147. [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
  148. if (![evaluatedObject isKindOfClass:[NSDictionary class]]) {
  149. return NO; // unexpected item. Throw it away
  150. }
  151. NSDictionary *impression = (NSDictionary *)evaluatedObject;
  152. return impression[FIRIAM_ImpressionDictKeyForID] &&
  153. ![messageIDSet containsObject:impression[FIRIAM_ImpressionDictKeyForID]];
  154. }];
  155. NSArray<NSDictionary *> *updatedImpressions =
  156. [existingImpressions filteredArrayUsingPredicate:notInMessageListPredicate];
  157. if (existingImpressions.count != updatedImpressions.count) {
  158. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM270004",
  159. @"Updating the impression records after purging %d items based on the "
  160. "server fetch response",
  161. (int)(existingImpressions.count - updatedImpressions.count));
  162. [self.defaults setObject:updatedImpressions forKey:FIRIAM_UserDefaultsKeyForImpressions];
  163. } else {
  164. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM270005",
  165. @"No impression records update due to no change after applying the server "
  166. "message list");
  167. }
  168. }
  169. }
  170. - (NSArray<FIRIAMImpressionRecord *> *)getImpressions {
  171. NSArray<NSDictionary *> *impressionsFromStorage = [self fetchImpressionArrayFromStorage];
  172. NSMutableArray<FIRIAMImpressionRecord *> *resultArray = [[NSMutableArray alloc] init];
  173. for (NSDictionary *next in impressionsFromStorage) {
  174. FIRIAMImpressionRecord *nextImpression =
  175. [[FIRIAMImpressionRecord alloc] initWithStorageDictionary:next];
  176. [resultArray addObject:nextImpression];
  177. }
  178. return resultArray;
  179. }
  180. - (NSArray<NSString *> *)getMessageIDsFromImpressions {
  181. NSArray<NSDictionary *> *impressionsFromStorage = [self fetchImpressionArrayFromStorage];
  182. NSMutableArray<NSString *> *resultArray = [[NSMutableArray alloc] init];
  183. for (NSDictionary *next in impressionsFromStorage) {
  184. [resultArray addObject:next[FIRIAM_ImpressionDictKeyForID]];
  185. }
  186. return resultArray;
  187. }
  188. - (void)recordNewFetchWithFetchCount:(NSInteger)fetchedMsgCount
  189. withTimestampInSeconds:(double)fetchTimestamp
  190. nextFetchWaitTime:(nullable NSNumber *)nextFetchWaitTime;
  191. {
  192. [self.defaults setDouble:fetchTimestamp forKey:FIRIAM_UserDefaultsKeyForLastFetchTimestamp];
  193. self.lastFetchTime = fetchTimestamp;
  194. if (nextFetchWaitTime != nil) {
  195. if (nextFetchWaitTime.doubleValue > kMaxFetchWaitTimeInSeconds) {
  196. FIRLogInfo(kFIRLoggerInAppMessaging, @"I-IAM270006",
  197. @"next fetch wait time %lf is too large. Ignore it.",
  198. nextFetchWaitTime.doubleValue);
  199. } else {
  200. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM270008",
  201. @"Setting next fetch wait time as %lf from fetch response.",
  202. nextFetchWaitTime.doubleValue);
  203. self.nextFetchWaitTime = nextFetchWaitTime.doubleValue;
  204. [self.defaults setObject:nextFetchWaitTime forKey:kUserDefaultsKeyForFetchWaitTime];
  205. }
  206. }
  207. }
  208. - (void)cleanupImpressions {
  209. [self.defaults setObject:@[] forKey:FIRIAM_UserDefaultsKeyForImpressions];
  210. }
  211. - (void)cleanupFetchRecords {
  212. [self.defaults setDouble:0 forKey:FIRIAM_UserDefaultsKeyForLastFetchTimestamp];
  213. self.lastFetchTime = 0;
  214. }
  215. @end