| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- /*
- * 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 || TARGET_OS_VISION
- #import <GoogleUtilities/GULUserDefaults.h>
- #import "FirebaseCore/Extension/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 possibilities 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 = [[GULUserDefaults 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
- [[GULUserDefaults 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
- [[GULUserDefaults 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:[GULUserDefaults 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:GULUserDefaults.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 || TARGET_OS_VISION
|