RCNConfigSettings.m 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. /*
  2. * Copyright 2019 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 "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
  17. #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
  18. #import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
  19. #import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
  20. #import "FirebaseRemoteConfig/Sources/RCNDevice.h"
  21. #import "FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h"
  22. #import <GoogleUtilities/GULAppEnvironmentUtil.h>
  23. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  24. static NSString *const kRCNGroupPrefix = @"frc.group.";
  25. static NSString *const kRCNUserDefaultsKeyNamelastETag = @"lastETag";
  26. static NSString *const kRCNUserDefaultsKeyNameLastSuccessfulFetchTime = @"lastSuccessfulFetchTime";
  27. static NSString *const kRCNAnalyticsFirstOpenTimePropertyName = @"_fot";
  28. static const int kRCNExponentialBackoffMinimumInterval = 60 * 2; // 2 mins.
  29. static const int kRCNExponentialBackoffMaximumInterval = 60 * 60 * 4; // 4 hours.
  30. @interface RCNConfigSettings () {
  31. /// A list of successful fetch timestamps in seconds.
  32. NSMutableArray *_successFetchTimes;
  33. /// A list of failed fetch timestamps in seconds.
  34. NSMutableArray *_failureFetchTimes;
  35. /// Device conditions since last successful fetch from the backend. Device conditions including
  36. /// app
  37. /// version, iOS version, device localte, language, GMP project ID and Game project ID. Used for
  38. /// determing whether to throttle.
  39. NSMutableDictionary *_deviceContext;
  40. /// Custom variables (aka App context digest). This is the pending custom variables request before
  41. /// fetching.
  42. NSMutableDictionary *_customVariables;
  43. /// Last fetch status.
  44. FIRRemoteConfigFetchStatus _lastFetchStatus;
  45. /// Last fetch Error.
  46. FIRRemoteConfigError _lastFetchError;
  47. /// The time of last apply timestamp.
  48. NSTimeInterval _lastApplyTimeInterval;
  49. /// The time of last setDefaults timestamp.
  50. NSTimeInterval _lastSetDefaultsTimeInterval;
  51. /// The database manager.
  52. RCNConfigDBManager *_DBManager;
  53. // The namespace for this instance.
  54. NSString *_FIRNamespace;
  55. // The Google App ID of the configured FIRApp.
  56. NSString *_googleAppID;
  57. /// The user defaults manager scoped to this RC instance of FIRApp and namespace.
  58. RCNUserDefaultsManager *_userDefaultsManager;
  59. }
  60. @end
  61. @implementation RCNConfigSettings
  62. - (instancetype)initWithDatabaseManager:(RCNConfigDBManager *)manager
  63. namespace:(NSString *)FIRNamespace
  64. firebaseAppName:(NSString *)appName
  65. googleAppID:(NSString *)googleAppID {
  66. self = [super init];
  67. if (self) {
  68. _FIRNamespace = FIRNamespace;
  69. _googleAppID = googleAppID;
  70. _bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
  71. if (!_bundleIdentifier) {
  72. FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038",
  73. @"Main bundle identifier is missing. Remote Config might not work properly.");
  74. _bundleIdentifier = @"";
  75. }
  76. _minimumFetchInterval = RCNDefaultMinimumFetchInterval;
  77. _deviceContext = [[NSMutableDictionary alloc] init];
  78. _customVariables = [[NSMutableDictionary alloc] init];
  79. _successFetchTimes = [[NSMutableArray alloc] init];
  80. _failureFetchTimes = [[NSMutableArray alloc] init];
  81. _DBManager = manager;
  82. _userDefaultsManager = [[RCNUserDefaultsManager alloc] initWithAppName:appName
  83. bundleID:_bundleIdentifier
  84. namespace:_FIRNamespace];
  85. // Check if the config database is new. If so, clear the configs saved in userDefaults.
  86. if ([_DBManager isNewDatabase]) {
  87. FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000072",
  88. @"New config database created. Resetting user defaults.");
  89. [_userDefaultsManager resetUserDefaults];
  90. }
  91. _isFetchInProgress = NO;
  92. _lastFetchedTemplateVersion = [_userDefaultsManager lastFetchedTemplateVersion];
  93. _lastActiveTemplateVersion = [_userDefaultsManager lastActiveTemplateVersion];
  94. _realtimeExponentialBackoffRetryInterval =
  95. [_userDefaultsManager currentRealtimeThrottlingRetryIntervalSeconds];
  96. _realtimeExponentialBackoffThrottleEndTime = [_userDefaultsManager realtimeThrottleEndTime];
  97. _realtimeRetryCount = [_userDefaultsManager realtimeRetryCount];
  98. }
  99. return self;
  100. }
  101. #pragma mark - read from / update userDefaults
  102. - (NSString *)lastETag {
  103. return [_userDefaultsManager lastETag];
  104. }
  105. - (void)setLastETag:(NSString *)lastETag {
  106. [self setLastETagUpdateTime:[[NSDate date] timeIntervalSince1970]];
  107. [_userDefaultsManager setLastETag:lastETag];
  108. }
  109. - (void)setLastETagUpdateTime:(NSTimeInterval)lastETagUpdateTime {
  110. [_userDefaultsManager setLastETagUpdateTime:lastETagUpdateTime];
  111. }
  112. - (NSTimeInterval)lastFetchTimeInterval {
  113. return _userDefaultsManager.lastFetchTime;
  114. }
  115. - (NSTimeInterval)lastETagUpdateTime {
  116. return _userDefaultsManager.lastETagUpdateTime;
  117. }
  118. // TODO: Update logic for app extensions as required.
  119. - (void)updateLastFetchTimeInterval:(NSTimeInterval)lastFetchTimeInterval {
  120. _userDefaultsManager.lastFetchTime = lastFetchTimeInterval;
  121. }
  122. #pragma mark - load from DB
  123. - (NSDictionary *)loadConfigFromMetadataTable {
  124. NSDictionary *metadata = [[_DBManager loadMetadataWithBundleIdentifier:_bundleIdentifier
  125. namespace:_FIRNamespace] copy];
  126. if (metadata) {
  127. // TODO: Remove (all metadata in general) once ready to
  128. // migrate to user defaults completely.
  129. if (metadata[RCNKeyDeviceContext]) {
  130. self->_deviceContext = [metadata[RCNKeyDeviceContext] mutableCopy];
  131. }
  132. if (metadata[RCNKeyAppContext]) {
  133. self->_customVariables = [metadata[RCNKeyAppContext] mutableCopy];
  134. }
  135. if (metadata[RCNKeySuccessFetchTime]) {
  136. self->_successFetchTimes = [metadata[RCNKeySuccessFetchTime] mutableCopy];
  137. }
  138. if (metadata[RCNKeyFailureFetchTime]) {
  139. self->_failureFetchTimes = [metadata[RCNKeyFailureFetchTime] mutableCopy];
  140. }
  141. if (metadata[RCNKeyLastFetchStatus]) {
  142. self->_lastFetchStatus =
  143. (FIRRemoteConfigFetchStatus)[metadata[RCNKeyLastFetchStatus] intValue];
  144. }
  145. if (metadata[RCNKeyLastFetchError]) {
  146. self->_lastFetchError = (FIRRemoteConfigError)[metadata[RCNKeyLastFetchError] intValue];
  147. }
  148. if (metadata[RCNKeyLastApplyTime]) {
  149. self->_lastApplyTimeInterval = [metadata[RCNKeyLastApplyTime] doubleValue];
  150. }
  151. if (metadata[RCNKeyLastFetchStatus]) {
  152. self->_lastSetDefaultsTimeInterval = [metadata[RCNKeyLastSetDefaultsTime] doubleValue];
  153. }
  154. }
  155. return metadata;
  156. }
  157. #pragma mark - update DB/cached
  158. /// If the last fetch was not successful, update the (exponential backoff) period that we wait until
  159. /// fetching again. Any subsequent fetch requests will be checked and allowed only if past this
  160. /// throttle end time.
  161. - (void)updateExponentialBackoffTime {
  162. // If not in exponential backoff mode, reset the retry interval.
  163. if (_lastFetchStatus == FIRRemoteConfigFetchStatusSuccess) {
  164. FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000057",
  165. @"Throttling: Entering exponential backoff mode.");
  166. _exponentialBackoffRetryInterval = kRCNExponentialBackoffMinimumInterval;
  167. } else {
  168. FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000057",
  169. @"Throttling: Updating throttling interval.");
  170. // Double the retry interval until we hit the truncated exponential backoff. More info here:
  171. // https://cloud.google.com/storage/docs/exponential-backoff
  172. _exponentialBackoffRetryInterval =
  173. ((_exponentialBackoffRetryInterval * 2) < kRCNExponentialBackoffMaximumInterval)
  174. ? _exponentialBackoffRetryInterval * 2
  175. : _exponentialBackoffRetryInterval;
  176. }
  177. // Randomize the next retry interval.
  178. int randomPlusMinusInterval = ((arc4random() % 2) == 0) ? -1 : 1;
  179. NSTimeInterval randomizedRetryInterval =
  180. _exponentialBackoffRetryInterval +
  181. (0.5 * _exponentialBackoffRetryInterval * randomPlusMinusInterval);
  182. _exponentialBackoffThrottleEndTime =
  183. [[NSDate date] timeIntervalSince1970] + randomizedRetryInterval;
  184. }
  185. /// If the last Realtime stream attempt was not successful, update the (exponential backoff) period
  186. /// that we wait until trying again. Any subsequent Realtime requests will be checked and allowed
  187. /// only if past this throttle end time.
  188. - (void)updateRealtimeExponentialBackoffTime {
  189. // If there was only one stream attempt before, reset the retry interval.
  190. if (_realtimeRetryCount == 0) {
  191. FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058",
  192. @"Throttling: Entering exponential Realtime backoff mode.");
  193. _realtimeExponentialBackoffRetryInterval = kRCNExponentialBackoffMinimumInterval;
  194. } else {
  195. FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058",
  196. @"Throttling: Updating Realtime throttling interval.");
  197. // Double the retry interval until we hit the truncated exponential backoff. More info here:
  198. // https://cloud.google.com/storage/docs/exponential-backoff
  199. _realtimeExponentialBackoffRetryInterval =
  200. ((_realtimeExponentialBackoffRetryInterval * 2) < kRCNExponentialBackoffMaximumInterval)
  201. ? _realtimeExponentialBackoffRetryInterval * 2
  202. : _realtimeExponentialBackoffRetryInterval;
  203. }
  204. // Randomize the next retry interval.
  205. int randomPlusMinusInterval = ((arc4random() % 2) == 0) ? -1 : 1;
  206. NSTimeInterval randomizedRetryInterval =
  207. _realtimeExponentialBackoffRetryInterval +
  208. (0.5 * _realtimeExponentialBackoffRetryInterval * randomPlusMinusInterval);
  209. _realtimeExponentialBackoffThrottleEndTime =
  210. [[NSDate date] timeIntervalSince1970] + randomizedRetryInterval;
  211. [_userDefaultsManager setRealtimeThrottleEndTime:_realtimeExponentialBackoffThrottleEndTime];
  212. [_userDefaultsManager
  213. setCurrentRealtimeThrottlingRetryIntervalSeconds:_realtimeExponentialBackoffRetryInterval];
  214. }
  215. /// Increase the real-time stream's backoff period from the current time plus the retry interval.
  216. /// Any subsequent Realtime requests will be checked and allowed only if past this throttle end
  217. /// time.
  218. - (void)updateRealtimeBackoffTimeWithInterval:(NSTimeInterval)realtimeRetryInterval {
  219. _realtimeExponentialBackoffThrottleEndTime =
  220. [[NSDate date] timeIntervalSince1970] + realtimeRetryInterval;
  221. [_userDefaultsManager setRealtimeThrottleEndTime:_realtimeExponentialBackoffThrottleEndTime];
  222. }
  223. - (void)setRealtimeRetryCount:(int)realtimeRetryCount {
  224. _realtimeRetryCount = realtimeRetryCount;
  225. [_userDefaultsManager setRealtimeRetryCount:_realtimeRetryCount];
  226. }
  227. - (NSTimeInterval)getRealtimeBackoffInterval {
  228. NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
  229. return _realtimeExponentialBackoffThrottleEndTime - now;
  230. }
  231. - (void)updateMetadataWithFetchSuccessStatus:(BOOL)fetchSuccess
  232. templateVersion:(NSString *)templateVersion {
  233. FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000056", @"Updating metadata with fetch result.");
  234. [self updateFetchTimeWithSuccessFetch:fetchSuccess];
  235. _lastFetchStatus =
  236. fetchSuccess ? FIRRemoteConfigFetchStatusSuccess : FIRRemoteConfigFetchStatusFailure;
  237. _lastFetchError = fetchSuccess ? FIRRemoteConfigErrorUnknown : FIRRemoteConfigErrorInternalError;
  238. if (fetchSuccess) {
  239. [self updateLastFetchTimeInterval:[[NSDate date] timeIntervalSince1970]];
  240. // Note: We expect the googleAppID to always be available.
  241. _deviceContext = FIRRemoteConfigDeviceContextWithProjectIdentifier(_googleAppID);
  242. _lastFetchedTemplateVersion = templateVersion;
  243. [_userDefaultsManager setLastFetchedTemplateVersion:templateVersion];
  244. }
  245. [self updateMetadataTable];
  246. }
  247. - (void)updateFetchTimeWithSuccessFetch:(BOOL)isSuccessfulFetch {
  248. NSTimeInterval epochTimeInterval = [[NSDate date] timeIntervalSince1970];
  249. if (isSuccessfulFetch) {
  250. [_successFetchTimes addObject:@(epochTimeInterval)];
  251. } else {
  252. [_failureFetchTimes addObject:@(epochTimeInterval)];
  253. }
  254. }
  255. - (void)updateMetadataTable {
  256. [_DBManager deleteRecordWithBundleIdentifier:_bundleIdentifier namespace:_FIRNamespace];
  257. NSError *error;
  258. // Objects to be serialized cannot be invalid.
  259. if (!_bundleIdentifier) {
  260. return;
  261. }
  262. if (![NSJSONSerialization isValidJSONObject:_customVariables]) {
  263. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000028",
  264. @"Invalid custom variables to be serialized.");
  265. return;
  266. }
  267. if (![NSJSONSerialization isValidJSONObject:_deviceContext]) {
  268. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000029",
  269. @"Invalid device context to be serialized.");
  270. return;
  271. }
  272. if (![NSJSONSerialization isValidJSONObject:_successFetchTimes]) {
  273. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000031",
  274. @"Invalid success fetch times to be serialized.");
  275. return;
  276. }
  277. if (![NSJSONSerialization isValidJSONObject:_failureFetchTimes]) {
  278. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000032",
  279. @"Invalid failure fetch times to be serialized.");
  280. return;
  281. }
  282. NSData *serializedAppContext = [NSJSONSerialization dataWithJSONObject:_customVariables
  283. options:NSJSONWritingPrettyPrinted
  284. error:&error];
  285. NSData *serializedDeviceContext =
  286. [NSJSONSerialization dataWithJSONObject:_deviceContext
  287. options:NSJSONWritingPrettyPrinted
  288. error:&error];
  289. // The digestPerNamespace is not used and only meant for backwards DB compatibility.
  290. NSData *serializedDigestPerNamespace =
  291. [NSJSONSerialization dataWithJSONObject:@{} options:NSJSONWritingPrettyPrinted error:&error];
  292. NSData *serializedSuccessTime = [NSJSONSerialization dataWithJSONObject:_successFetchTimes
  293. options:NSJSONWritingPrettyPrinted
  294. error:&error];
  295. NSData *serializedFailureTime = [NSJSONSerialization dataWithJSONObject:_failureFetchTimes
  296. options:NSJSONWritingPrettyPrinted
  297. error:&error];
  298. if (!serializedDigestPerNamespace || !serializedDeviceContext || !serializedAppContext ||
  299. !serializedSuccessTime || !serializedFailureTime) {
  300. return;
  301. }
  302. NSDictionary *columnNameToValue = @{
  303. RCNKeyBundleIdentifier : _bundleIdentifier,
  304. RCNKeyNamespace : _FIRNamespace,
  305. RCNKeyFetchTime : @(self.lastFetchTimeInterval),
  306. RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
  307. RCNKeyDeviceContext : serializedDeviceContext,
  308. RCNKeyAppContext : serializedAppContext,
  309. RCNKeySuccessFetchTime : serializedSuccessTime,
  310. RCNKeyFailureFetchTime : serializedFailureTime,
  311. RCNKeyLastFetchStatus : [NSString stringWithFormat:@"%ld", (long)_lastFetchStatus],
  312. RCNKeyLastFetchError : [NSString stringWithFormat:@"%ld", (long)_lastFetchError],
  313. RCNKeyLastApplyTime : @(_lastApplyTimeInterval),
  314. RCNKeyLastSetDefaultsTime : @(_lastSetDefaultsTimeInterval)
  315. };
  316. [_DBManager insertMetadataTableWithValues:columnNameToValue completionHandler:nil];
  317. }
  318. - (void)updateLastActiveTemplateVersion {
  319. _lastActiveTemplateVersion = _lastFetchedTemplateVersion;
  320. [_userDefaultsManager setLastActiveTemplateVersion:_lastActiveTemplateVersion];
  321. }
  322. #pragma mark - fetch request
  323. /// Returns a fetch request with the latest device and config change.
  324. /// Whenever user issues a fetch api call, collect the latest request.
  325. - (NSString *)nextRequestWithUserProperties:(NSDictionary *)userProperties {
  326. // Note: We only set user properties as mentioned in the new REST API Design doc
  327. NSString *ret = [NSString stringWithFormat:@"{"];
  328. ret = [ret stringByAppendingString:[NSString stringWithFormat:@"app_instance_id:'%@'",
  329. _configInstallationsIdentifier]];
  330. ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_instance_id_token:'%@'",
  331. _configInstallationsToken]];
  332. ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_id:'%@'", _googleAppID]];
  333. ret = [ret stringByAppendingString:[NSString stringWithFormat:@", country_code:'%@'",
  334. FIRRemoteConfigDeviceCountry()]];
  335. ret = [ret stringByAppendingString:[NSString stringWithFormat:@", language_code:'%@'",
  336. FIRRemoteConfigDeviceLocale()]];
  337. ret = [ret
  338. stringByAppendingString:[NSString stringWithFormat:@", platform_version:'%@'",
  339. [GULAppEnvironmentUtil systemVersion]]];
  340. ret = [ret stringByAppendingString:[NSString stringWithFormat:@", time_zone:'%@'",
  341. FIRRemoteConfigTimezone()]];
  342. ret = [ret stringByAppendingString:[NSString stringWithFormat:@", package_name:'%@'",
  343. _bundleIdentifier]];
  344. ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_version:'%@'",
  345. FIRRemoteConfigAppVersion()]];
  346. ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_build:'%@'",
  347. FIRRemoteConfigAppBuildVersion()]];
  348. ret = [ret stringByAppendingString:[NSString stringWithFormat:@", sdk_version:'%@'",
  349. FIRRemoteConfigPodVersion()]];
  350. if (userProperties && userProperties.count > 0) {
  351. NSError *error;
  352. // Extract first open time from user properties and send as a separate field
  353. NSNumber *firstOpenTime = userProperties[kRCNAnalyticsFirstOpenTimePropertyName];
  354. NSMutableDictionary *remainingUserProperties = [userProperties mutableCopy];
  355. if (firstOpenTime != nil) {
  356. NSDate *date = [NSDate dateWithTimeIntervalSince1970:([firstOpenTime longValue] / 1000)];
  357. NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
  358. NSString *firstOpenTimeISOString = [formatter stringFromDate:date];
  359. ret = [ret stringByAppendingString:[NSString stringWithFormat:@", first_open_time:'%@'",
  360. firstOpenTimeISOString]];
  361. [remainingUserProperties removeObjectForKey:kRCNAnalyticsFirstOpenTimePropertyName];
  362. }
  363. if (remainingUserProperties.count > 0) {
  364. NSData *jsonData = [NSJSONSerialization dataWithJSONObject:remainingUserProperties
  365. options:0
  366. error:&error];
  367. if (!error) {
  368. ret = [ret
  369. stringByAppendingString:[NSString
  370. stringWithFormat:@", analytics_user_properties:%@",
  371. [[NSString alloc]
  372. initWithData:jsonData
  373. encoding:NSUTF8StringEncoding]]];
  374. }
  375. }
  376. }
  377. NSDictionary<NSString *, NSString *> *customSignals = [self customSignals];
  378. if (customSignals.count > 0) {
  379. NSError *error;
  380. NSData *jsonData = [NSJSONSerialization dataWithJSONObject:customSignals
  381. options:0
  382. error:&error];
  383. if (!error) {
  384. ret = [ret
  385. stringByAppendingString:[NSString
  386. stringWithFormat:@", custom_signals:%@",
  387. [[NSString alloc]
  388. initWithData:jsonData
  389. encoding:NSUTF8StringEncoding]]];
  390. // Log the keys of the custom signals sent during fetch.
  391. FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000078",
  392. @"Keys of custom signals during fetch: %@", [customSignals allKeys]);
  393. }
  394. }
  395. ret = [ret stringByAppendingString:@"}"];
  396. return ret;
  397. }
  398. #pragma mark - getter/setter
  399. - (void)setLastFetchError:(FIRRemoteConfigError)lastFetchError {
  400. if (_lastFetchError != lastFetchError) {
  401. _lastFetchError = lastFetchError;
  402. [_DBManager updateMetadataWithOption:RCNUpdateOptionFetchStatus
  403. namespace:_FIRNamespace
  404. values:@[ @(_lastFetchStatus), @(_lastFetchError) ]
  405. completionHandler:nil];
  406. }
  407. }
  408. - (NSArray *)successFetchTimes {
  409. return [_successFetchTimes copy];
  410. }
  411. - (NSArray *)failureFetchTimes {
  412. return [_failureFetchTimes copy];
  413. }
  414. - (NSDictionary *)customVariables {
  415. return [_customVariables copy];
  416. }
  417. - (NSDictionary *)deviceContext {
  418. return [_deviceContext copy];
  419. }
  420. - (void)setCustomVariables:(NSDictionary *)customVariables {
  421. _customVariables = [[NSMutableDictionary alloc] initWithDictionary:customVariables];
  422. [self updateMetadataTable];
  423. }
  424. - (void)setMinimumFetchInterval:(NSTimeInterval)minimumFetchInterval {
  425. if (minimumFetchInterval < 0) {
  426. _minimumFetchInterval = 0;
  427. } else {
  428. _minimumFetchInterval = minimumFetchInterval;
  429. }
  430. }
  431. - (void)setFetchTimeout:(NSTimeInterval)fetchTimeout {
  432. if (fetchTimeout <= 0) {
  433. _fetchTimeout = RCNHTTPDefaultConnectionTimeout;
  434. } else {
  435. _fetchTimeout = fetchTimeout;
  436. }
  437. }
  438. - (void)setLastApplyTimeInterval:(NSTimeInterval)lastApplyTimestamp {
  439. _lastApplyTimeInterval = lastApplyTimestamp;
  440. [_DBManager updateMetadataWithOption:RCNUpdateOptionApplyTime
  441. namespace:_FIRNamespace
  442. values:@[ @(lastApplyTimestamp) ]
  443. completionHandler:nil];
  444. }
  445. - (void)setLastSetDefaultsTimeInterval:(NSTimeInterval)lastSetDefaultsTimestamp {
  446. _lastSetDefaultsTimeInterval = lastSetDefaultsTimestamp;
  447. [_DBManager updateMetadataWithOption:RCNUpdateOptionDefaultTime
  448. namespace:_FIRNamespace
  449. values:@[ @(lastSetDefaultsTimestamp) ]
  450. completionHandler:nil];
  451. }
  452. - (NSDictionary<NSString *, NSString *> *)customSignals {
  453. return [_userDefaultsManager customSignals];
  454. }
  455. - (void)setCustomSignals:(NSDictionary<NSString *, NSString *> *)customSignals {
  456. [_userDefaultsManager setCustomSignals:customSignals];
  457. }
  458. #pragma mark Throttling
  459. - (BOOL)hasMinimumFetchIntervalElapsed:(NSTimeInterval)minimumFetchInterval {
  460. if (self.lastFetchTimeInterval == 0) return YES;
  461. // Check if last config fetch is within minimum fetch interval in seconds.
  462. NSTimeInterval diffInSeconds = [[NSDate date] timeIntervalSince1970] - self.lastFetchTimeInterval;
  463. return diffInSeconds > minimumFetchInterval;
  464. }
  465. - (BOOL)shouldThrottle {
  466. NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
  467. return ((self.lastFetchTimeInterval > 0) &&
  468. (_lastFetchStatus != FIRRemoteConfigFetchStatusSuccess) &&
  469. (_exponentialBackoffThrottleEndTime - now > 0));
  470. }
  471. @end