Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

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