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