FIRIAMRuntimeManager.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  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 <TargetConditionals.h>
  17. #if TARGET_OS_IOS || TARGET_OS_TV
  18. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  19. #import "FirebaseInAppMessaging/Sources/Analytics/FIRIAMAnalyticsEventLoggerImpl.h"
  20. #import "FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutHttpRequestSender.h"
  21. #import "FirebaseInAppMessaging/Sources/Analytics/FIRIAMClearcutLogStorage.h"
  22. #import "FirebaseInAppMessaging/Sources/FIRCore+InAppMessaging.h"
  23. #import "FirebaseInAppMessaging/Sources/FIRInAppMessagingPrivate.h"
  24. #import "FirebaseInAppMessaging/Sources/Flows/FIRIAMDisplayCheckOnFetchDoneNotificationFlow.h"
  25. #import "FirebaseInAppMessaging/Sources/Private/Analytics/FIRIAMClearcutLogger.h"
  26. #import "FirebaseInAppMessaging/Sources/Private/Analytics/FIRIAMClearcutUploader.h"
  27. #import "FirebaseInAppMessaging/Sources/Private/Analytics/FIRIAMClientInfoFetcher.h"
  28. #import "FirebaseInAppMessaging/Sources/Private/Data/FIRIAMFetchResponseParser.h"
  29. #import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMActivityLogger.h"
  30. #import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMBookKeeper.h"
  31. #import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMDisplayCheckOnAnalyticEventsFlow.h"
  32. #import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMDisplayCheckOnAppForegroundFlow.h"
  33. #import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMDisplayExecutor.h"
  34. #import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMFetchOnAppForegroundFlow.h"
  35. #import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMessageClientCache.h"
  36. #import "FirebaseInAppMessaging/Sources/Private/Flows/FIRIAMMsgFetcherUsingRestful.h"
  37. #import "FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMRuntimeManager.h"
  38. #import "FirebaseInAppMessaging/Sources/Private/Runtime/FIRIAMSDKModeManager.h"
  39. #import "FirebaseInAppMessaging/Sources/Public/FirebaseInAppMessaging/FIRInAppMessaging.h"
  40. // A enum indicating 3 different possiblities of a setting about auto data collection.
  41. typedef NS_ENUM(NSInteger, FIRIAMAutoDataCollectionSetting) {
  42. // This indicates that the config is not explicitly set.
  43. FIRIAMAutoDataCollectionSettingNone = 0,
  44. // This indicates that the setting explicitly enables the auto data collection.
  45. FIRIAMAutoDataCollectionSettingEnabled = 1,
  46. // This indicates that the setting explicitly disables the auto data collection.
  47. FIRIAMAutoDataCollectionSettingDisabled = 2,
  48. };
  49. @interface FIRIAMRuntimeManager () <FIRIAMTestingModeListener>
  50. @property(nonatomic, nonnull) FIRIAMMsgFetcherUsingRestful *restfulFetcher;
  51. @property(nonatomic, nonnull) FIRIAMDisplayCheckOnAppForegroundFlow *displayOnAppForegroundFlow;
  52. @property(nonatomic, nonnull) FIRIAMDisplayCheckOnFetchDoneNotificationFlow *displayOnFetchDoneFlow;
  53. @property(nonatomic, nonnull)
  54. FIRIAMDisplayCheckOnAnalyticEventsFlow *displayOnFIRAnalyticEventsFlow;
  55. @property(nonatomic, nonnull) FIRIAMFetchOnAppForegroundFlow *fetchOnAppForegroundFlow;
  56. @property(nonatomic, nonnull) FIRIAMClientInfoFetcher *clientInfoFetcher;
  57. @property(nonatomic, nonnull) FIRIAMFetchResponseParser *responseParser;
  58. @end
  59. static NSString *const _userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting =
  60. @"firebase-iam-sdk-auto-data-collection";
  61. @implementation FIRIAMRuntimeManager {
  62. // since we allow the SDK feature to be disabled/enabled at runtime, we need a field to track
  63. // its state on this
  64. BOOL _running;
  65. }
  66. + (FIRIAMRuntimeManager *)getSDKRuntimeInstance {
  67. static FIRIAMRuntimeManager *managerInstance = nil;
  68. static dispatch_once_t onceToken;
  69. dispatch_once(&onceToken, ^{
  70. managerInstance = [[FIRIAMRuntimeManager alloc] init];
  71. });
  72. return managerInstance;
  73. }
  74. // For protocol FIRIAMTestingModeListener.
  75. - (void)testingModeSwitchedOn {
  76. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180015",
  77. @"Dynamically switch to the display flow for testing mode instance.");
  78. [self.displayOnAppForegroundFlow stop];
  79. [self.displayOnFetchDoneFlow start];
  80. }
  81. - (FIRIAMAutoDataCollectionSetting)FIAMProgrammaticAutoDataCollectionSetting {
  82. id settingEntry = [[NSUserDefaults standardUserDefaults]
  83. objectForKey:_userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting];
  84. if (![settingEntry isKindOfClass:[NSNumber class]]) {
  85. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180014",
  86. @"No auto data collection enable setting entry detected."
  87. "So no FIAM programmatic setting from the app.");
  88. return FIRIAMAutoDataCollectionSettingNone;
  89. } else {
  90. if ([(NSNumber *)settingEntry boolValue]) {
  91. return FIRIAMAutoDataCollectionSettingEnabled;
  92. } else {
  93. return FIRIAMAutoDataCollectionSettingDisabled;
  94. }
  95. }
  96. }
  97. // the key for the plist entry to suppress auto start
  98. static NSString *const kFirebaseInAppMessagingAutoDataCollectionKey =
  99. @"FirebaseInAppMessagingAutomaticDataCollectionEnabled";
  100. - (FIRIAMAutoDataCollectionSetting)FIAMPlistAutoDataCollectionSetting {
  101. id fiamAutoDataCollectionPlistEntry = [[NSBundle mainBundle]
  102. objectForInfoDictionaryKey:kFirebaseInAppMessagingAutoDataCollectionKey];
  103. if ([fiamAutoDataCollectionPlistEntry isKindOfClass:[NSNumber class]]) {
  104. BOOL fiamDataCollectionEnabledPlistSetting =
  105. [(NSNumber *)fiamAutoDataCollectionPlistEntry boolValue];
  106. if (fiamDataCollectionEnabledPlistSetting) {
  107. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180011",
  108. @"Auto data collection is explicitly enabled in FIAM plist entry.");
  109. return FIRIAMAutoDataCollectionSettingEnabled;
  110. } else {
  111. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180012",
  112. @"Auto data collection is explicitly disabled in FIAM plist entry.");
  113. return FIRIAMAutoDataCollectionSettingDisabled;
  114. }
  115. } else {
  116. return FIRIAMAutoDataCollectionSettingNone;
  117. }
  118. }
  119. // Whether data collection is enabled by FIAM programmatic flag.
  120. - (BOOL)automaticDataCollectionEnabled {
  121. return
  122. [self FIAMProgrammaticAutoDataCollectionSetting] != FIRIAMAutoDataCollectionSettingDisabled;
  123. }
  124. // Sets FIAM's programmatic flag for auto data collection.
  125. - (void)setAutomaticDataCollectionEnabled:(BOOL)automaticDataCollectionEnabled {
  126. if (automaticDataCollectionEnabled) {
  127. [self resume];
  128. } else {
  129. [self pause];
  130. }
  131. }
  132. - (BOOL)shouldRunSDKFlowsOnStartup {
  133. // This can be controlled at 3 different levels in decsending priority. If a higher-priority
  134. // setting exists, the lower level settings are ignored.
  135. // 1. Setting made by the app by setting FIAM SDK's automaticDataCollectionEnabled flag.
  136. // 2. FIAM specific data collection setting in plist file.
  137. // 3. Global Firebase auto data collecting setting (carried over by currentSetting property).
  138. FIRIAMAutoDataCollectionSetting programmaticSetting =
  139. [self FIAMProgrammaticAutoDataCollectionSetting];
  140. if (programmaticSetting == FIRIAMAutoDataCollectionSettingEnabled) {
  141. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180010",
  142. @"FIAM auto data-collection is explicitly enabled, start SDK flows.");
  143. return true;
  144. } else if (programmaticSetting == FIRIAMAutoDataCollectionSettingDisabled) {
  145. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180013",
  146. @"FIAM auto data-collection is explicitly disabled, do not start SDK flows.");
  147. return false;
  148. } else {
  149. // No explicit setting from fiam's programmatic setting. Checking next level down.
  150. FIRIAMAutoDataCollectionSetting fiamPlistDataCollectionSetting =
  151. [self FIAMPlistAutoDataCollectionSetting];
  152. if (fiamPlistDataCollectionSetting == FIRIAMAutoDataCollectionSettingNone) {
  153. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180018",
  154. @"No programmatic or plist setting at FIAM level. Fallback to global Firebase "
  155. "level setting.");
  156. return self.currentSetting.isFirebaseAutoDataCollectionEnabled;
  157. } else {
  158. return fiamPlistDataCollectionSetting == FIRIAMAutoDataCollectionSettingEnabled;
  159. }
  160. }
  161. }
  162. - (void)resume {
  163. // persist the setting
  164. [[NSUserDefaults standardUserDefaults]
  165. setObject:@(YES)
  166. forKey:_userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting];
  167. @synchronized(self) {
  168. if (!_running) {
  169. [self.fetchOnAppForegroundFlow start];
  170. [self.displayOnAppForegroundFlow start];
  171. [self.displayOnFIRAnalyticEventsFlow start];
  172. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180019",
  173. @"Start Firebase In-App Messaging flows from inactive.");
  174. _running = YES;
  175. } else {
  176. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM180004",
  177. @"Runtime is already active, resume is just a no-op");
  178. }
  179. }
  180. }
  181. - (void)pause {
  182. // persist the setting
  183. [[NSUserDefaults standardUserDefaults]
  184. setObject:@(NO)
  185. forKey:_userDefaultsKeyForFIAMProgammaticAutoDataCollectionSetting];
  186. @synchronized(self) {
  187. if (_running) {
  188. [self.fetchOnAppForegroundFlow stop];
  189. [self.displayOnAppForegroundFlow stop];
  190. [self.displayOnFIRAnalyticEventsFlow stop];
  191. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180006",
  192. @"Shutdown Firebase In-App Messaging flows.");
  193. _running = NO;
  194. } else {
  195. FIRLogWarning(kFIRLoggerInAppMessaging, @"I-IAM180005",
  196. @"No runtime active yet, pause is just a no-op");
  197. }
  198. }
  199. }
  200. - (void)setShouldSuppressMessageDisplay:(BOOL)shouldSuppress {
  201. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180003", @"Message display suppress set to %@",
  202. @(shouldSuppress));
  203. self.displayExecutor.suppressMessageDisplay = shouldSuppress;
  204. }
  205. - (void)startRuntimeWithSDKSettings:(FIRIAMSDKSettings *)settings {
  206. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
  207. [self internalStartRuntimeWithSDKSettings:settings];
  208. });
  209. }
  210. - (void)internalStartRuntimeWithSDKSettings:(FIRIAMSDKSettings *)settings {
  211. if (_running) {
  212. // Runtime has been started previously. Stop all the flows first.
  213. [self.fetchOnAppForegroundFlow stop];
  214. [self.displayOnAppForegroundFlow stop];
  215. [self.displayOnFIRAnalyticEventsFlow stop];
  216. }
  217. self.currentSetting = settings;
  218. FIRIAMTimerWithNSDate *timeFetcher = [[FIRIAMTimerWithNSDate alloc] init];
  219. NSTimeInterval start = [timeFetcher currentTimestampInSeconds];
  220. self.activityLogger =
  221. [[FIRIAMActivityLogger alloc] initWithMaxCountBeforeReduce:settings.loggerMaxCountBeforeReduce
  222. withSizeAfterReduce:settings.loggerSizeAfterReduce
  223. verboseMode:settings.loggerInVerboseMode
  224. loadFromCache:YES];
  225. self.responseParser = [[FIRIAMFetchResponseParser alloc] initWithTimeFetcher:timeFetcher];
  226. self.bookKeeper = [[FIRIAMBookKeeperViaUserDefaults alloc]
  227. initWithUserDefaults:[NSUserDefaults standardUserDefaults]];
  228. self.messageCache = [[FIRIAMMessageClientCache alloc] initWithBookkeeper:self.bookKeeper
  229. usingResponseParser:self.responseParser];
  230. self.fetchResultStorage = [[FIRIAMServerMsgFetchStorage alloc] init];
  231. self.clientInfoFetcher = [[FIRIAMClientInfoFetcher alloc]
  232. initWithFirebaseInstallations:[FIRInAppMessaging inAppMessaging].installations];
  233. self.restfulFetcher =
  234. [[FIRIAMMsgFetcherUsingRestful alloc] initWithHost:settings.apiServerHost
  235. HTTPProtocol:settings.apiHttpProtocol
  236. project:settings.firebaseProjectNumber
  237. firebaseApp:settings.firebaseAppId
  238. APIKey:settings.apiKey
  239. fetchStorage:self.fetchResultStorage
  240. instanceIDFetcher:self.clientInfoFetcher
  241. usingURLSession:nil
  242. responseParser:self.responseParser];
  243. // start fetch on app foreground flow
  244. FIRIAMFetchSetting *fetchSetting = [[FIRIAMFetchSetting alloc] init];
  245. fetchSetting.fetchMinIntervalInMinutes = settings.fetchMinIntervalInMinutes;
  246. // start render on app foreground flow
  247. FIRIAMDisplaySetting *appForegroundDisplaysetting = [[FIRIAMDisplaySetting alloc] init];
  248. appForegroundDisplaysetting.displayMinIntervalInMinutes =
  249. settings.appFGRenderMinIntervalInMinutes;
  250. // clearcut log expires after 14 days: give up on attempting to deliver them any more
  251. NSInteger ctLogExpiresInSeconds = 14 * 24 * 60 * 60;
  252. FIRIAMClearcutLogStorage *ctLogStorage =
  253. [[FIRIAMClearcutLogStorage alloc] initWithExpireAfterInSeconds:ctLogExpiresInSeconds
  254. withTimeFetcher:timeFetcher];
  255. FIRIAMClearcutHttpRequestSender *clearcutRequestSender = [[FIRIAMClearcutHttpRequestSender alloc]
  256. initWithClearcutHost:settings.clearcutServerHost
  257. usingTimeFetcher:timeFetcher
  258. withOSMajorVersion:[self.clientInfoFetcher getOSMajorVersion]];
  259. FIRIAMClearcutUploader *ctUploader =
  260. [[FIRIAMClearcutUploader alloc] initWithRequestSender:clearcutRequestSender
  261. timeFetcher:timeFetcher
  262. logStorage:ctLogStorage
  263. usingStrategy:settings.clearcutStrategy
  264. usingUserDefaults:nil];
  265. FIRIAMClearcutLogger *clearcutLogger =
  266. [[FIRIAMClearcutLogger alloc] initWithFBProjectNumber:settings.firebaseProjectNumber
  267. fbAppId:settings.firebaseAppId
  268. clientInfoFetcher:self.clientInfoFetcher
  269. usingTimeFetcher:timeFetcher
  270. usingUploader:ctUploader];
  271. FIRIAMAnalyticsEventLoggerImpl *analyticsEventLogger = [[FIRIAMAnalyticsEventLoggerImpl alloc]
  272. initWithClearcutLogger:clearcutLogger
  273. usingTimeFetcher:timeFetcher
  274. usingUserDefaults:nil
  275. analytics:[FIRInAppMessaging inAppMessaging].analytics];
  276. FIRIAMSDKModeManager *sdkModeManager =
  277. [[FIRIAMSDKModeManager alloc] initWithUserDefaults:NSUserDefaults.standardUserDefaults
  278. testingModeListener:self];
  279. FIRIAMActionURLFollower *actionFollower = [FIRIAMActionURLFollower actionURLFollower];
  280. self.displayExecutor =
  281. [[FIRIAMDisplayExecutor alloc] initWithInAppMessaging:[FIRInAppMessaging inAppMessaging]
  282. setting:appForegroundDisplaysetting
  283. messageCache:self.messageCache
  284. timeFetcher:timeFetcher
  285. bookKeeper:self.bookKeeper
  286. actionURLFollower:actionFollower
  287. activityLogger:self.activityLogger
  288. analyticsEventLogger:analyticsEventLogger];
  289. self.fetchOnAppForegroundFlow =
  290. [[FIRIAMFetchOnAppForegroundFlow alloc] initWithSetting:fetchSetting
  291. messageCache:self.messageCache
  292. messageFetcher:self.restfulFetcher
  293. timeFetcher:timeFetcher
  294. bookKeeper:self.bookKeeper
  295. activityLogger:self.activityLogger
  296. analyticsEventLogger:analyticsEventLogger
  297. FIRIAMSDKModeManager:sdkModeManager
  298. displayExecutor:self.displayExecutor];
  299. // Setting the message display component and suppression. It's needed in case
  300. // headless SDK is initialized after the these properties are already set on FIRInAppMessaging.
  301. self.displayExecutor.messageDisplayComponent =
  302. FIRInAppMessaging.inAppMessaging.messageDisplayComponent;
  303. self.displayExecutor.suppressMessageDisplay =
  304. FIRInAppMessaging.inAppMessaging.messageDisplaySuppressed;
  305. // Both display flows are created on startup. But they would only be turned on (started) based on
  306. // the sdk mode for the current instance
  307. self.displayOnFetchDoneFlow = [[FIRIAMDisplayCheckOnFetchDoneNotificationFlow alloc]
  308. initWithDisplayFlow:self.displayExecutor];
  309. self.displayOnAppForegroundFlow =
  310. [[FIRIAMDisplayCheckOnAppForegroundFlow alloc] initWithDisplayFlow:self.displayExecutor];
  311. self.displayOnFIRAnalyticEventsFlow =
  312. [[FIRIAMDisplayCheckOnAnalyticEventsFlow alloc] initWithDisplayFlow:self.displayExecutor];
  313. self.messageCache.analycisEventDislayCheckFlow = self.displayOnFIRAnalyticEventsFlow;
  314. [self.messageCache
  315. loadMessageDataFromServerFetchStorage:self.fetchResultStorage
  316. withCompletion:^(BOOL success) {
  317. // start flows regardless whether we can load messages from fetch
  318. // storage successfully
  319. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180001",
  320. @"Message loading from fetch storage was done.");
  321. if ([self shouldRunSDKFlowsOnStartup]) {
  322. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180008",
  323. @"Start SDK runtime components.");
  324. [self.clientInfoFetcher
  325. fetchFirebaseInstallationDataWithProjectNumber:
  326. self.currentSetting.firebaseProjectNumber
  327. withCompletion:^(
  328. NSString *_Nullable FID,
  329. NSString
  330. *_Nullable FISToken,
  331. NSError *_Nullable error) {
  332. // Always dump the
  333. // installation ID into log
  334. // on startup to help
  335. // developers to find it for
  336. // their app instance.
  337. FIRLogDebug(
  338. kFIRLoggerInAppMessaging,
  339. @"I-IAM180017",
  340. @"Starting "
  341. @"InAppMessaging "
  342. @"runtime "
  343. @"with "
  344. "Firebase Installation "
  345. "ID %@",
  346. FID);
  347. }];
  348. [self.fetchOnAppForegroundFlow start];
  349. [self.displayOnFIRAnalyticEventsFlow start];
  350. self->_running = YES;
  351. if (sdkModeManager.currentMode == FIRIAMSDKModeTesting) {
  352. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180007",
  353. @"InAppMessaging testing mode enabled. App "
  354. "foreground messages will be displayed following "
  355. "fetch");
  356. [self.displayOnFetchDoneFlow start];
  357. } else {
  358. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180020",
  359. @"Start regular display flow for non-testing "
  360. "instance mode");
  361. [self.displayOnAppForegroundFlow start];
  362. // Simulate app going into foreground on startup
  363. [self.displayExecutor checkAndDisplayNextAppForegroundMessage];
  364. }
  365. // One-time triggering of checks for both fetch flow
  366. // upon SDK/app startup.
  367. [self.fetchOnAppForegroundFlow
  368. checkAndFetchForInitialAppLaunch:YES];
  369. } else {
  370. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180009",
  371. @"No FIAM SDK startup due to settings.");
  372. }
  373. }];
  374. FIRLogDebug(kFIRLoggerInAppMessaging, @"I-IAM180002",
  375. @"Firebase In-App Messaging SDK version %@ finished startup in %lf seconds "
  376. "with these settings: %@",
  377. [self.clientInfoFetcher getIAMSDKVersion],
  378. (double)([timeFetcher currentTimestampInSeconds] - start), settings);
  379. }
  380. @end
  381. #endif // TARGET_OS_IOS || TARGET_OS_TV