AutorÃa | Ultima modificación | Ver Log |
/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <TargetConditionals.h>
#if TARGET_OS_IOS || TARGET_OS_TV
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
#import "FirebaseInAppMessaging/Sources/Analytics/FIRIAMAnalyticsEventLoggerImpl.h"
#import "FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutHttpRequestSender.h"
#import "FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutLogStorage.h"
#import "FirebaseInAppMessaging/Sources/FIRCore+InAppMessaging.h"
#import "FirebaseInAppMessaging/Sources/FIRInAppMessagingPrivate.h"
#import "FirebaseInAppMessaging/Sources/Flows/FIRIAMDisplayCheckOnFetchDoneNotificationFlow.h"
#import "FirebaseInAppMessaging/Sources/Private/Analytics/FIRIAMClearcutLogger.h"
#import "FirebaseInAppMessaging/Sources/Private/Analytics/FIRIAMClearcutUploader.h"
#import "FirebaseInAppMessaging/Sources/Private/Analytics/FIRIAMClientInfoFetcher.h"
#import "FirebaseInAppMessaging/Sources/Private/Data/FIRIAMFetchResponseParser.h"
#import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMActivityLogger.h"
#import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMBookKeeper.h"
#import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMDisplayCheckOnAnalyticEventsFlow.h"
#import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMDisplayCheckOnAppForegroundFlow.h"
#import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMDisplayExecutor.h"
#import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMFetchOnAppForegroundFlow.h"
#import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMessageClientCache.h"
#import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMsgFetcherUsingRestful.h"
#import "FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMRuntimeManager.h"
#import "FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMSDKModeManager.h"
#import "FirebaseInAppMessaging/Sources/Public/FirebaseInAppMessaging/FIRInAppMessaging.h"
// A enum indicating 3 different possiblities of a setting about auto data collection.
typedef NS_ENUM(NSInteger, FIRIAMAutoDataCollectionSetting) {
// This indicates that the config is not explicitly set.
FIRIAMAutoDataCollectionSettingNone = 0,
// This indicates that the setting explicitly enables the auto data collection.
FIRIAMAutoDataCollectionSettingEnabled = 1,
// This indicates that the setting explicitly disables the auto data collection.
FIRIAMAutoDataCollectionSettingDisabled = 2,
};
@interface FIRIAMRuntimeManager () <FIRIAMTestingModeListener>
@property(nonatomic, nonnull) FIRIAMMsgFetcherUsingRestful *restfulFetcher;
@property(nonatomic, nonnull) FIRIAMDisplayCheckOnAppForegroundFlow *displayOnAppForegroundFlow;
@property(nonatomic, nonnull) FIRIAMDisplayCheckOnFetchDoneNotificationFlow *displayOnFetchDoneFlow;
@property(nonatomic, nonnull)
FIRIAMDisplayCheckOnAnalyticEventsFlow *displayOnFIRAnalyticEventsFlow;
@property(nonatomic, nonnull) FIRIAMFetchOnAppForegroundFlow *fetchOnAppForegroundFlow;
@property(nonatomic, nonnull) FIRIAMClientInfoFetcher *clientInfoFetcher;
@property(nonatomic, nonnull) FIRIAMFetchResponseParser *responseParser;
@end
static NSString *const _userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting =
@"firebase-iam-sdk-auto-data-collection";
@implementation FIRIAMRuntimeManager {
// since we allow the SDK feature to be disabled/enabled at runtime, we need a field to track
// its state on this
BOOL _running;
}
+ (FIRIAMRuntimeManager *)getSDKRuntimeInstance {
static FIRIAMRuntimeManager *managerInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
managerInstance = [[FIRIAMRuntimeManager alloc] init];
});
return managerInstance;
}
// For protocol FIRIAMTestingModeListener.
- (void)testingModeSwitchedOn {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180015",
@"Dynamically switch to the display flow for testing mode instance.");
[self.displayOnAppForegroundFlow stop];
[self.displayOnFetchDoneFlow start];
}
- (FIRIAMAutoDataCollectionSetting)FIAMProgrammaticAutoDataCollectionSetting {
id settingEntry = [[NSUserDefaults standardUserDefaults]
objectForKey:_userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting];
if (![settingEntry isKindOfClass:[NSNumber class]]) {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180014",
@"No auto data collection enable setting entry detected."
"So no FIAM programmatic setting from the app.");
return FIRIAMAutoDataCollectionSettingNone;
} else {
if ([(NSNumber *)settingEntry boolValue]) {
return FIRIAMAutoDataCollectionSettingEnabled;
} else {
return FIRIAMAutoDataCollectionSettingDisabled;
}
}
}
// the key for the plist entry to suppress auto start
static NSString *const kFirebaseInAppMessagingAutoDataCollectionKey =
@"FirebaseInAppMessagingAutomaticDataCollectionEnabled";
- (FIRIAMAutoDataCollectionSetting)FIAMPlistAutoDataCollectionSetting {
id fiamAutoDataCollectionPlistEntry = [[NSBundle mainBundle]
objectForInfoDictionaryKey:kFirebaseInAppMessagingAutoDataCollectionKey];
if ([fiamAutoDataCollectionPlistEntry isKindOfClass:[NSNumber class]]) {
BOOL fiamDataCollectionEnabledPlistSetting =
[(NSNumber *)fiamAutoDataCollectionPlistEntry boolValue];
if (fiamDataCollectionEnabledPlistSetting) {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180011",
@"Auto data collection is explicitly enabled in FIAM plist entry.");
return FIRIAMAutoDataCollectionSettingEnabled;
} else {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180012",
@"Auto data collection is explicitly disabled in FIAM plist entry.");
return FIRIAMAutoDataCollectionSettingDisabled;
}
} else {
return FIRIAMAutoDataCollectionSettingNone;
}
}
// Whether data collection is enabled by FIAM programmatic flag.
- (BOOL)automaticDataCollectionEnabled {
return
[self FIAMProgrammaticAutoDataCollectionSetting] != FIRIAMAutoDataCollectionSettingDisabled;
}
// Sets FIAM's programmatic flag for auto data collection.
- (void)setAutomaticDataCollectionEnabled:(BOOL)automaticDataCollectionEnabled {
if (automaticDataCollectionEnabled) {
[self resume];
} else {
[self pause];
}
}
- (BOOL)shouldRunSDKFlowsOnStartup {
// This can be controlled at 3 different levels in decsending priority. If a higher-priority
// setting exists, the lower level settings are ignored.
// 1. Setting made by the app by setting FIAM SDK's automaticDataCollectionEnabled flag.
// 2. FIAM specific data collection setting in plist file.
// 3. Global Firebase auto data collecting setting (carried over by currentSetting property).
FIRIAMAutoDataCollectionSetting programmaticSetting =
[self FIAMProgrammaticAutoDataCollectionSetting];
if (programmaticSetting == FIRIAMAutoDataCollectionSettingEnabled) {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180010",
@"FIAM auto data-collection is explicitly enabled, start SDK flows.");
return true;
} else if (programmaticSetting == FIRIAMAutoDataCollectionSettingDisabled) {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180013",
@"FIAM auto data-collection is explicitly disabled, do not start SDK flows.");
return false;
} else {
// No explicit setting from fiam's programmatic setting. Checking next level down.
FIRIAMAutoDataCollectionSetting fiamPlistDataCollectionSetting =
[self FIAMPlistAutoDataCollectionSetting];
if (fiamPlistDataCollectionSetting == FIRIAMAutoDataCollectionSettingNone) {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180018",
@"No programmatic or plist setting at FIAM level. Fallback to global Firebase "
"level setting.");
return self.currentSetting.isFirebaseAutoDataCollectionEnabled;
} else {
return fiamPlistDataCollectionSetting == FIRIAMAutoDataCollectionSettingEnabled;
}
}
}
- (void)resume {
// persist the setting
[[NSUserDefaults standardUserDefaults]
setObject:@(YES)
forKey:_userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting];
@synchronized(self) {
if (!_running) {
[self.fetchOnAppForegroundFlow start];
[self.displayOnAppForegroundFlow start];
[self.displayOnFIRAnalyticEventsFlow start];
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180019",
@"Start Firebase In-App Messaging flows from inactive.");
_running = YES;
} else {
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM180004",
@"Runtime is already active, resume is just a no-op");
}
}
}
- (void)pause {
// persist the setting
[[NSUserDefaults standardUserDefaults]
setObject:@(NO)
forKey:_userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting];
@synchronized(self) {
if (_running) {
[self.fetchOnAppForegroundFlow stop];
[self.displayOnAppForegroundFlow stop];
[self.displayOnFIRAnalyticEventsFlow stop];
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180006",
@"Shutdown Firebase In-App Messaging flows.");
_running = NO;
} else {
FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM180005",
@"No runtime active yet, pause is just a no-op");
}
}
}
- (void)setShouldSuppressMessageDisplay:(BOOL)shouldSuppress {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180003", @"Message display suppress set to %@",
@(shouldSuppress));
self.displayExecutor.suppressMessageDisplay = shouldSuppress;
}
- (void)startRuntimeWithSDKSettings:(FIRIAMSDKSettings *)settings {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
[self internalStartRuntimeWithSDKSettings:settings];
});
}
- (void)internalStartRuntimeWithSDKSettings:(FIRIAMSDKSettings *)settings {
if (_running) {
// Runtime has been started previously. Stop all the flows first.
[self.fetchOnAppForegroundFlow stop];
[self.displayOnAppForegroundFlow stop];
[self.displayOnFIRAnalyticEventsFlow stop];
}
self.currentSetting = settings;
FIRIAMTimerWithNSDate *timeFetcher = [[FIRIAMTimerWithNSDate alloc] init];
NSTimeInterval start = [timeFetcher currentTimestampInSeconds];
self.activityLogger =
[[FIRIAMActivityLogger alloc] initWithMaxCountBeforeReduce:settings.loggerMaxCountBeforeReduce
withSizeAfterReduce:settings.loggerSizeAfterReduce
verboseMode:settings.loggerInVerboseMode
loadFromCache:YES];
self.responseParser = [[FIRIAMFetchResponseParser alloc] initWithTimeFetcher:timeFetcher];
self.bookKeeper = [[FIRIAMBookKeeperViaUserDefaults alloc]
initWithUserDefaults:[NSUserDefaults standardUserDefaults]];
self.messageCache = [[FIRIAMMessageClientCache alloc] initWithBookkeeper:self.bookKeeper
usingResponseParser:self.responseParser];
self.fetchResultStorage = [[FIRIAMServerMsgFetchStorage alloc] init];
self.clientInfoFetcher = [[FIRIAMClientInfoFetcher alloc]
initWithFirebaseInstallations:[FIRInAppMessaging inAppMessaging].installations];
self.restfulFetcher =
[[FIRIAMMsgFetcherUsingRestful alloc] initWithHost:settings.apiServerHost
HTTPProtocol:settings.apiHttpProtocol
project:settings.firebaseProjectNumber
firebaseApp:settings.firebaseAppId
APIKey:settings.apiKey
fetchStorage:self.fetchResultStorage
instanceIDFetcher:self.clientInfoFetcher
usingURLSession:nil
responseParser:self.responseParser];
// start fetch on app foreground flow
FIRIAMFetchSetting *fetchSetting = [[FIRIAMFetchSetting alloc] init];
fetchSetting.fetchMinIntervalInMinutes = settings.fetchMinIntervalInMinutes;
// start render on app foreground flow
FIRIAMDisplaySetting *appForegroundDisplaysetting = [[FIRIAMDisplaySetting alloc] init];
appForegroundDisplaysetting.displayMinIntervalInMinutes =
settings.appFGRenderMinIntervalInMinutes;
// clearcut log expires after 14 days: give up on attempting to deliver them any more
NSInteger ctLogExpiresInSeconds = 14 * 24 * 60 * 60;
FIRIAMClearcutLogStorage *ctLogStorage =
[[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:ctLogExpiresInSeconds
withTimeFetcher:timeFetcher];
FIRIAMClearcutHttpRequestSender *clearcutRequestSender = [[FIRIAMClearcutHttpRequestSender alloc]
initWithClearcutHost:settings.clearcutServerHost
usingTimeFetcher:timeFetcher
withOSMajorVersion:[self.clientInfoFetcher getOSMajorVersion]];
FIRIAMClearcutUploader *ctUploader =
[[FIRIAMClearcutUploader alloc] initWithRequestSender:clearcutRequestSender
timeFetcher:timeFetcher
logStorage:ctLogStorage
usingStrategy:settings.clearcutStrategy
usingUserDefaults:nil];
FIRIAMClearcutLogger *clearcutLogger =
[[FIRIAMClearcutLogger alloc] initWithFBProjectNumber:settings.firebaseProjectNumber
fbAppId:settings.firebaseAppId
clientInfoFetcher:self.clientInfoFetcher
usingTimeFetcher:timeFetcher
usingUploader:ctUploader];
FIRIAMAnalyticsEventLoggerImpl *analyticsEventLogger = [[FIRIAMAnalyticsEventLoggerImpl alloc]
initWithClearcutLogger:clearcutLogger
usingTimeFetcher:timeFetcher
usingUserDefaults:nil
analytics:[FIRInAppMessaging inAppMessaging].analytics];
FIRIAMSDKModeManager *sdkModeManager =
[[FIRIAMSDKModeManager alloc] initWithUserDefaults:NSUserDefaults.standardUserDefaults
testingModeListener:self];
FIRIAMActionURLFollower *actionFollower = [FIRIAMActionURLFollower actionURLFollower];
self.displayExecutor =
[[FIRIAMDisplayExecutor alloc] initWithInAppMessaging:[FIRInAppMessaging inAppMessaging]
setting:appForegroundDisplaysetting
messageCache:self.messageCache
timeFetcher:timeFetcher
bookKeeper:self.bookKeeper
actionURLFollower:actionFollower
activityLogger:self.activityLogger
analyticsEventLogger:analyticsEventLogger];
self.fetchOnAppForegroundFlow =
[[FIRIAMFetchOnAppForegroundFlow alloc] initWithSetting:fetchSetting
messageCache:self.messageCache
messageFetcher:self.restfulFetcher
timeFetcher:timeFetcher
bookKeeper:self.bookKeeper
activityLogger:self.activityLogger
analyticsEventLogger:analyticsEventLogger
FIRIAMSDKModeManager:sdkModeManager
displayExecutor:self.displayExecutor];
// Setting the message display component and suppression. It's needed in case
// headless SDK is initialized after the these properties are already set on FIRInAppMessaging.
self.displayExecutor.messageDisplayComponent =
FIRInAppMessaging.inAppMessaging.messageDisplayComponent;
self.displayExecutor.suppressMessageDisplay =
FIRInAppMessaging.inAppMessaging.messageDisplaySuppressed;
// Both display flows are created on startup. But they would only be turned on (started) based on
// the sdk mode for the current instance
self.displayOnFetchDoneFlow = [[FIRIAMDisplayCheckOnFetchDoneNotificationFlow alloc]
initWithDisplayFlow:self.displayExecutor];
self.displayOnAppForegroundFlow =
[[FIRIAMDisplayCheckOnAppForegroundFlow alloc] initWithDisplayFlow:self.displayExecutor];
self.displayOnFIRAnalyticEventsFlow =
[[FIRIAMDisplayCheckOnAnalyticEventsFlow alloc] initWithDisplayFlow:self.displayExecutor];
self.messageCache.analycisEventDislayCheckFlow = self.displayOnFIRAnalyticEventsFlow;
[self.messageCache
loadMessageDataFromServerFetchStorage:self.fetchResultStorage
withCompletion:^(BOOL success) {
// start flows regardless whether we can load messages from fetch
// storage successfully
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180001",
@"Message loading from fetch storage was done.");
if ([self shouldRunSDKFlowsOnStartup]) {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180008",
@"Start SDK runtime components.");
[self.clientInfoFetcher
fetchFirebaseInstallationDataWithProjectNumber:
self.currentSetting.firebaseProjectNumber
withCompletion:^(
NSString *_Nullable FID,
NSString
*_Nullable FISToken,
NSError *_Nullable error) {
// Always dump the
// installation ID into log
// on startup to help
// developers to find it for
// their app instance.
FIRLogDebug(
kFIRLoggerInAppMessaging,
@"I-IAM180017",
@"Starting "
@"InAppMessaging "
@"runtime "
@"with "
"Firebase Installation "
"ID %@",
FID);
}];
[self.fetchOnAppForegroundFlow start];
[self.displayOnFIRAnalyticEventsFlow start];
self->_running = YES;
if (sdkModeManager.currentMode == FIRIAMSDKModeTesting) {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180007",
@"InAppMessaging testing mode enabled. App "
"foreground messages will be displayed following "
"fetch");
[self.displayOnFetchDoneFlow start];
} else {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180020",
@"Start regular display flow for non-testing "
"instance mode");
[self.displayOnAppForegroundFlow start];
// Simulate app going into foreground on startup
[self.displayExecutor checkAndDisplayNextAppForegroundMessage];
}
// One-time triggering of checks for both fetch flow
// upon SDK/app startup.
[self.fetchOnAppForegroundFlow
checkAndFetchForInitialAppLaunch:YES];
} else {
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180009",
@"No FIAM SDK startup due to settings.");
}
}];
FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180002",
@"Firebase In-App Messaging SDK version %@ finished startup in %lf seconds "
"with these settings: %@",
[self.clientInfoFetcher getIAMSDKVersion],
(double)([timeFetcher currentTimestampInSeconds] - start), settings);
}
@end
#endif // TARGET_OS_IOS || TARGET_OS_TV