FIRIAMRuntimeManager.m 22 KB

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