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