FIRIAMRuntimeManager.m 21 KB

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