RCNConfigSettings.m 23 KB

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