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/RCNConfigFetch.h"
18
 
19
#import <GoogleUtilities/GULNSData+zlib.h>
20
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
21
#import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
22
#import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
23
#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
24
#import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
25
#import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
26
#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
27
 
28
#ifdef RCN_STAGING_SERVER
29
static NSString *const kServerURLDomain =
30
    @"https://staging-firebaseremoteconfig.sandbox.googleapis.com";
31
#else
32
static NSString *const kServerURLDomain = @"https://firebaseremoteconfig.googleapis.com";
33
#endif
34
 
35
static NSString *const kServerURLVersion = @"/v1";
36
static NSString *const kServerURLProjects = @"/projects/";
37
static NSString *const kServerURLNamespaces = @"/namespaces/";
38
static NSString *const kServerURLQuery = @":fetch?";
39
static NSString *const kServerURLKey = @"key=";
40
static NSString *const kRequestJSONKeyAppID = @"app_id";
41
 
42
static NSString *const kHTTPMethodPost = @"POST";  ///< HTTP request method config fetch using
43
static NSString *const kContentTypeHeaderName = @"Content-Type";  ///< HTTP Header Field Name
44
static NSString *const kContentEncodingHeaderName =
45
    @"Content-Encoding";                                                ///< HTTP Header Field Name
46
static NSString *const kAcceptEncodingHeaderName = @"Accept-Encoding";  ///< HTTP Header Field Name
47
static NSString *const kETagHeaderName = @"etag";                       ///< HTTP Header Field Name
48
static NSString *const kIfNoneMatchETagHeaderName = @"if-none-match";   ///< HTTP Header Field Name
49
static NSString *const kInstallationsAuthTokenHeaderName = @"x-goog-firebase-installations-auth";
50
// Sends the bundle ID. Refer to b/130301479 for details.
51
static NSString *const kiOSBundleIdentifierHeaderName =
52
    @"X-Ios-Bundle-Identifier";  ///< HTTP Header Field Name
53
 
54
/// Config HTTP request content type proto buffer
55
static NSString *const kContentTypeValueJSON = @"application/json";
56
 
57
/// HTTP status codes. Ref: https://cloud.google.com/apis/design/errors#error_retries
58
static NSInteger const kRCNFetchResponseHTTPStatusCodeOK = 200;
59
static NSInteger const kRCNFetchResponseHTTPStatusTooManyRequests = 429;
60
static NSInteger const kRCNFetchResponseHTTPStatusCodeInternalError = 500;
61
static NSInteger const kRCNFetchResponseHTTPStatusCodeServiceUnavailable = 503;
62
static NSInteger const kRCNFetchResponseHTTPStatusCodeGatewayTimeout = 504;
63
 
64
// Deprecated error code previously from FirebaseCore
65
static const NSInteger sFIRErrorCodeConfigFailed = -114;
66
 
67
#pragma mark - RCNConfig
68
 
69
@implementation RCNConfigFetch {
70
  RCNConfigContent *_content;
71
  RCNConfigSettings *_settings;
72
  id<FIRAnalyticsInterop> _analytics;
73
  RCNConfigExperiment *_experiment;
74
  dispatch_queue_t _lockQueue;  /// Guard the read/write operation.
75
  NSURLSession *_fetchSession;  /// Managed internally by the fetch instance.
76
  NSString *_FIRNamespace;
77
  FIROptions *_options;
78
}
79
 
80
- (instancetype)init {
81
  NSAssert(NO, @"Invalid initializer.");
82
  return nil;
83
}
84
 
85
/// Designated initializer
86
- (instancetype)initWithContent:(RCNConfigContent *)content
87
                      DBManager:(RCNConfigDBManager *)DBManager
88
                       settings:(RCNConfigSettings *)settings
89
                      analytics:(nullable id<FIRAnalyticsInterop>)analytics
90
                     experiment:(RCNConfigExperiment *)experiment
91
                          queue:(dispatch_queue_t)queue
92
                      namespace:(NSString *)FIRNamespace
93
                        options:(FIROptions *)options {
94
  self = [super init];
95
  if (self) {
96
    _FIRNamespace = FIRNamespace;
97
    _settings = settings;
98
    _analytics = analytics;
99
    _experiment = experiment;
100
    _lockQueue = queue;
101
    _content = content;
102
    _fetchSession = [self newFetchSession];
103
    _options = options;
104
  }
105
  return self;
106
}
107
 
108
/// Force a new NSURLSession creation for updated config.
109
- (void)recreateNetworkSession {
110
  if (_fetchSession) {
111
    [_fetchSession invalidateAndCancel];
112
  }
113
  _fetchSession = [self newFetchSession];
114
}
115
 
116
/// Return the current session. (Tests).
117
- (NSURLSession *)currentNetworkSession {
118
  return _fetchSession;
119
}
120
 
121
- (void)dealloc {
122
  [_fetchSession invalidateAndCancel];
123
}
124
 
125
#pragma mark - Fetch Config API
126
 
127
- (void)fetchConfigWithExpirationDuration:(NSTimeInterval)expirationDuration
128
                        completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler {
129
  // Note: We expect the googleAppID to always be available.
130
  BOOL hasDeviceContextChanged =
131
      FIRRemoteConfigHasDeviceContextChanged(_settings.deviceContext, _options.googleAppID);
132
 
133
  __weak RCNConfigFetch *weakSelf = self;
134
  dispatch_async(_lockQueue, ^{
135
    RCNConfigFetch *strongSelf = weakSelf;
136
    if (strongSelf == nil) {
137
      return;
138
    }
139
 
140
    // Check whether we are outside of the minimum fetch interval.
141
    if (![strongSelf->_settings hasMinimumFetchIntervalElapsed:expirationDuration] &&
142
        !hasDeviceContextChanged) {
143
      FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000051", @"Returning cached data.");
144
      return [strongSelf reportCompletionOnHandler:completionHandler
145
                                        withStatus:FIRRemoteConfigFetchStatusSuccess
146
                                         withError:nil];
147
    }
148
 
149
    // Check if a fetch is already in progress.
150
    if (strongSelf->_settings.isFetchInProgress) {
151
      // Check if we have some fetched data.
152
      if (strongSelf->_settings.lastFetchTimeInterval > 0) {
153
        FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000052",
154
                    @"A fetch is already in progress. Using previous fetch results.");
155
        return [strongSelf reportCompletionOnHandler:completionHandler
156
                                          withStatus:strongSelf->_settings.lastFetchStatus
157
                                           withError:nil];
158
      } else {
159
        FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000053",
160
                    @"A fetch is already in progress. Ignoring duplicate request.");
161
        NSError *error =
162
            [NSError errorWithDomain:FIRRemoteConfigErrorDomain
163
                                code:sFIRErrorCodeConfigFailed
164
                            userInfo:@{
165
                              NSLocalizedDescriptionKey :
166
                                  @"FetchError: Duplicate request while the previous one is pending"
167
                            }];
168
        return [strongSelf reportCompletionOnHandler:completionHandler
169
                                          withStatus:FIRRemoteConfigFetchStatusFailure
170
                                           withError:error];
171
      }
172
    }
173
 
174
    // Check whether cache data is within throttle limit.
175
    if ([strongSelf->_settings shouldThrottle] && !hasDeviceContextChanged) {
176
      // Must set lastFetchStatus before FailReason.
177
      strongSelf->_settings.lastFetchStatus = FIRRemoteConfigFetchStatusThrottled;
178
      strongSelf->_settings.lastFetchError = FIRRemoteConfigErrorThrottled;
179
      NSTimeInterval throttledEndTime = strongSelf->_settings.exponentialBackoffThrottleEndTime;
180
 
181
      NSError *error =
182
          [NSError errorWithDomain:FIRRemoteConfigErrorDomain
183
                              code:FIRRemoteConfigErrorThrottled
184
                          userInfo:@{
185
                            FIRRemoteConfigThrottledEndTimeInSecondsKey : @(throttledEndTime)
186
                          }];
187
      return [strongSelf reportCompletionOnHandler:completionHandler
188
                                        withStatus:strongSelf->_settings.lastFetchStatus
189
                                         withError:error];
190
    }
191
    strongSelf->_settings.isFetchInProgress = YES;
192
    [strongSelf refreshInstallationsTokenWithCompletionHandler:completionHandler];
193
  });
194
}
195
 
196
#pragma mark - Fetch helpers
197
 
198
- (NSString *)FIRAppNameFromFullyQualifiedNamespace {
199
  return [[_FIRNamespace componentsSeparatedByString:@":"] lastObject];
200
}
201
/// Refresh installation ID token before fetching config. installation ID is now mandatory for fetch
202
/// requests to work.(b/14751422).
203
- (void)refreshInstallationsTokenWithCompletionHandler:
204
    (FIRRemoteConfigFetchCompletion)completionHandler {
205
  FIRInstallations *installations = [FIRInstallations
206
      installationsWithApp:[FIRApp appNamed:[self FIRAppNameFromFullyQualifiedNamespace]]];
207
  if (!installations || !_options.GCMSenderID) {
208
    NSString *errorDescription = @"Failed to get GCMSenderID";
209
    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000074", @"%@",
210
                [NSString stringWithFormat:@"%@", errorDescription]);
211
    self->_settings.isFetchInProgress = NO;
212
    return [self
213
        reportCompletionOnHandler:completionHandler
214
                       withStatus:FIRRemoteConfigFetchStatusFailure
215
                        withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
216
                                                      code:FIRRemoteConfigErrorInternalError
217
                                                  userInfo:@{
218
                                                    NSLocalizedDescriptionKey : errorDescription
219
                                                  }]];
220
  }
221
 
222
  __weak RCNConfigFetch *weakSelf = self;
223
  FIRInstallationsTokenHandler installationsTokenHandler = ^(
224
      FIRInstallationsAuthTokenResult *tokenResult, NSError *error) {
225
    RCNConfigFetch *strongSelf = weakSelf;
226
    if (strongSelf == nil) {
227
      return;
228
    }
229
 
230
    if (!tokenResult || !tokenResult.authToken || error) {
231
      NSString *errorDescription =
232
          [NSString stringWithFormat:@"Failed to get installations token. Error : %@.", error];
233
      FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000073", @"%@",
234
                  [NSString stringWithFormat:@"%@", errorDescription]);
235
      strongSelf->_settings.isFetchInProgress = NO;
236
      return [strongSelf
237
          reportCompletionOnHandler:completionHandler
238
                         withStatus:FIRRemoteConfigFetchStatusFailure
239
                          withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
240
                                                        code:FIRRemoteConfigErrorInternalError
241
                                                    userInfo:@{
242
                                                      NSLocalizedDescriptionKey : errorDescription
243
                                                    }]];
244
    }
245
 
246
    // We have a valid token. Get the backing installationID.
247
    [installations installationIDWithCompletion:^(NSString *_Nullable identifier,
248
                                                  NSError *_Nullable error) {
249
      RCNConfigFetch *strongSelf = weakSelf;
250
      if (strongSelf == nil) {
251
        return;
252
      }
253
 
254
      // Dispatch to the RC serial queue to update settings on the queue.
255
      dispatch_async(strongSelf->_lockQueue, ^{
256
        RCNConfigFetch *strongSelfQueue = weakSelf;
257
        if (strongSelfQueue == nil) {
258
          return;
259
        }
260
 
261
        // Update config settings with the IID and token.
262
        strongSelfQueue->_settings.configInstallationsToken = tokenResult.authToken;
263
        strongSelfQueue->_settings.configInstallationsIdentifier = identifier;
264
 
265
        if (!identifier || error) {
266
          NSString *errorDescription =
267
              [NSString stringWithFormat:@"Error getting iid : %@.", error];
268
          FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000055", @"%@",
269
                      [NSString stringWithFormat:@"%@", errorDescription]);
270
          strongSelfQueue->_settings.isFetchInProgress = NO;
271
          return [strongSelfQueue
272
              reportCompletionOnHandler:completionHandler
273
                             withStatus:FIRRemoteConfigFetchStatusFailure
274
                              withError:[NSError
275
                                            errorWithDomain:FIRRemoteConfigErrorDomain
276
                                                       code:FIRRemoteConfigErrorInternalError
277
                                                   userInfo:@{
278
                                                     NSLocalizedDescriptionKey : errorDescription
279
                                                   }]];
280
        }
281
 
282
        FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000022", @"Success to get iid : %@.",
283
                   strongSelfQueue->_settings.configInstallationsIdentifier);
284
        [strongSelf doFetchCall:completionHandler];
285
      });
286
    }];
287
  };
288
 
289
  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000039", @"Starting requesting token.");
290
  [installations authTokenWithCompletion:installationsTokenHandler];
291
}
292
 
293
- (void)doFetchCall:(FIRRemoteConfigFetchCompletion)completionHandler {
294
  [self getAnalyticsUserPropertiesWithCompletionHandler:^(NSDictionary *userProperties) {
295
    dispatch_async(self->_lockQueue, ^{
296
      [self fetchWithUserProperties:userProperties completionHandler:completionHandler];
297
    });
298
  }];
299
}
300
 
301
- (void)getAnalyticsUserPropertiesWithCompletionHandler:
302
    (FIRAInteropUserPropertiesCallback)completionHandler {
303
  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000060", @"Fetch with user properties completed.");
304
  id<FIRAnalyticsInterop> analytics = self->_analytics;
305
  if (analytics == nil) {
306
    completionHandler(@{});
307
  } else {
308
    [analytics getUserPropertiesWithCallback:completionHandler];
309
  }
310
}
311
 
312
- (void)reportCompletionOnHandler:(FIRRemoteConfigFetchCompletion)completionHandler
313
                       withStatus:(FIRRemoteConfigFetchStatus)status
314
                        withError:(NSError *)error {
315
  if (completionHandler) {
316
    dispatch_async(dispatch_get_main_queue(), ^{
317
      completionHandler(status, error);
318
    });
319
  }
320
}
321
 
322
- (void)fetchWithUserProperties:(NSDictionary *)userProperties
323
              completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler {
324
  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000061", @"Fetch with user properties initiated.");
325
 
326
  NSString *postRequestString = [_settings nextRequestWithUserProperties:userProperties];
327
 
328
  // Get POST request content.
329
  NSData *content = [postRequestString dataUsingEncoding:NSUTF8StringEncoding];
330
  NSError *compressionError;
331
  NSData *compressedContent = [NSData gul_dataByGzippingData:content error:&compressionError];
332
  if (compressionError) {
333
    NSString *errString = [NSString stringWithFormat:@"Failed to compress the config request."];
334
    FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000033", @"%@", errString);
335
 
336
    self->_settings.isFetchInProgress = NO;
337
    return [self
338
        reportCompletionOnHandler:completionHandler
339
                       withStatus:FIRRemoteConfigFetchStatusFailure
340
                        withError:[NSError
341
                                      errorWithDomain:FIRRemoteConfigErrorDomain
342
                                                 code:FIRRemoteConfigErrorInternalError
343
                                             userInfo:@{NSLocalizedDescriptionKey : errString}]];
344
  }
345
 
346
  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000040", @"Start config fetch.");
347
  __weak RCNConfigFetch *weakSelf = self;
348
  RCNConfigFetcherCompletion fetcherCompletion = ^(NSData *data, NSURLResponse *response,
349
                                                   NSError *error) {
350
    FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000050",
351
                @"config fetch completed. Error: %@ StatusCode: %ld", (error ? error : @"nil"),
352
                (long)[((NSHTTPURLResponse *)response) statusCode]);
353
 
354
    RCNConfigFetch *fetcherCompletionSelf = weakSelf;
355
    if (fetcherCompletionSelf == nil) {
356
      return;
357
    }
358
 
359
    // The fetch has completed.
360
    fetcherCompletionSelf->_settings.isFetchInProgress = NO;
361
 
362
    dispatch_async(fetcherCompletionSelf->_lockQueue, ^{
363
      RCNConfigFetch *strongSelf = weakSelf;
364
      if (strongSelf == nil) {
365
        return;
366
      }
367
 
368
      NSInteger statusCode = [((NSHTTPURLResponse *)response) statusCode];
369
 
370
      if (error || (statusCode != kRCNFetchResponseHTTPStatusCodeOK)) {
371
        // Update metadata about fetch failure.
372
        [strongSelf->_settings updateMetadataWithFetchSuccessStatus:NO];
373
        if (error) {
374
          if (strongSelf->_settings.lastFetchStatus == FIRRemoteConfigFetchStatusSuccess) {
375
            FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000025",
376
                        @"RCN Fetch failure: %@. Using cached config result.", error);
377
          } else {
378
            FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000026",
379
                        @"RCN Fetch failure: %@. No cached config result.", error);
380
          }
381
        }
382
        if (statusCode != kRCNFetchResponseHTTPStatusCodeOK) {
383
          FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000026",
384
                      @"RCN Fetch failure. Response http error code: %ld", (long)statusCode);
385
          // Response error code 429, 500, 503 will trigger exponential backoff mode.
386
          if (statusCode == kRCNFetchResponseHTTPStatusTooManyRequests ||
387
              statusCode == kRCNFetchResponseHTTPStatusCodeInternalError ||
388
              statusCode == kRCNFetchResponseHTTPStatusCodeServiceUnavailable ||
389
              statusCode == kRCNFetchResponseHTTPStatusCodeGatewayTimeout) {
390
            [strongSelf->_settings updateExponentialBackoffTime];
391
            if ([strongSelf->_settings shouldThrottle]) {
392
              // Must set lastFetchStatus before FailReason.
393
              strongSelf->_settings.lastFetchStatus = FIRRemoteConfigFetchStatusThrottled;
394
              strongSelf->_settings.lastFetchError = FIRRemoteConfigErrorThrottled;
395
              NSTimeInterval throttledEndTime =
396
                  strongSelf->_settings.exponentialBackoffThrottleEndTime;
397
 
398
              NSError *error = [NSError
399
                  errorWithDomain:FIRRemoteConfigErrorDomain
400
                             code:FIRRemoteConfigErrorThrottled
401
                         userInfo:@{
402
                           FIRRemoteConfigThrottledEndTimeInSecondsKey : @(throttledEndTime)
403
                         }];
404
              return [strongSelf reportCompletionOnHandler:completionHandler
405
                                                withStatus:strongSelf->_settings.lastFetchStatus
406
                                                 withError:error];
407
            }
408
          }
409
        }
410
        // Return back the received error.
411
        // Must set lastFetchStatus before setting Fetch Error.
412
        strongSelf->_settings.lastFetchStatus = FIRRemoteConfigFetchStatusFailure;
413
        strongSelf->_settings.lastFetchError = FIRRemoteConfigErrorInternalError;
414
        NSDictionary<NSErrorUserInfoKey, id> *userInfo = @{
415
          NSLocalizedDescriptionKey :
416
              ([error localizedDescription]
417
                   ?: [NSString
418
                          stringWithFormat:@"Internal Error. Status code: %ld", (long)statusCode])
419
        };
420
        return [strongSelf
421
            reportCompletionOnHandler:completionHandler
422
                           withStatus:FIRRemoteConfigFetchStatusFailure
423
                            withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
424
                                                          code:FIRRemoteConfigErrorInternalError
425
                                                      userInfo:userInfo]];
426
      }
427
 
428
      // Fetch was successful. Check if we have data.
429
      NSError *retError;
430
      if (!data) {
431
        FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000043", @"RCN Fetch: No data in fetch response");
432
        return [strongSelf reportCompletionOnHandler:completionHandler
433
                                          withStatus:FIRRemoteConfigFetchStatusSuccess
434
                                           withError:nil];
435
      }
436
 
437
      // Config fetch succeeded.
438
      // JSONObjectWithData is always expected to return an NSDictionary in our case
439
      NSMutableDictionary *fetchedConfig =
440
          [NSJSONSerialization JSONObjectWithData:data
441
                                          options:NSJSONReadingMutableContainers
442
                                            error:&retError];
443
      if (retError) {
444
        FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000042",
445
                    @"RCN Fetch failure: %@. Could not parse response data as JSON", error);
446
      }
447
 
448
      // Check and log if we received an error from the server
449
      if (fetchedConfig && fetchedConfig.count == 1 && fetchedConfig[RCNFetchResponseKeyError]) {
450
        NSString *errStr = [NSString stringWithFormat:@"RCN Fetch Failure: Server returned error:"];
451
        NSDictionary *errDict = fetchedConfig[RCNFetchResponseKeyError];
452
        if (errDict[RCNFetchResponseKeyErrorCode]) {
453
          errStr = [errStr
454
              stringByAppendingString:[NSString
455
                                          stringWithFormat:@"code: %@",
456
                                                           errDict[RCNFetchResponseKeyErrorCode]]];
457
        }
458
        if (errDict[RCNFetchResponseKeyErrorStatus]) {
459
          errStr = [errStr stringByAppendingString:
460
                               [NSString stringWithFormat:@". Status: %@",
461
                                                          errDict[RCNFetchResponseKeyErrorStatus]]];
462
        }
463
        if (errDict[RCNFetchResponseKeyErrorMessage]) {
464
          errStr =
465
              [errStr stringByAppendingString:
466
                          [NSString stringWithFormat:@". Message: %@",
467
                                                     errDict[RCNFetchResponseKeyErrorMessage]]];
468
        }
469
        FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000044", @"%@.", errStr);
470
        return [strongSelf
471
            reportCompletionOnHandler:completionHandler
472
                           withStatus:FIRRemoteConfigFetchStatusFailure
473
                            withError:[NSError
474
                                          errorWithDomain:FIRRemoteConfigErrorDomain
475
                                                     code:FIRRemoteConfigErrorInternalError
476
                                                 userInfo:@{NSLocalizedDescriptionKey : errStr}]];
477
      }
478
 
479
      // Add the fetched config to the database.
480
      if (fetchedConfig) {
481
        // Update config content to cache and DB.
482
        [strongSelf->_content updateConfigContentWithResponse:fetchedConfig
483
                                                 forNamespace:strongSelf->_FIRNamespace];
484
        // Update experiments only for 3p namespace
485
        NSString *namespace = [strongSelf->_FIRNamespace
486
            substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location];
487
        if ([namespace isEqualToString:FIRNamespaceGoogleMobilePlatform]) {
488
          [strongSelf->_experiment updateExperimentsWithResponse:
489
                                       fetchedConfig[RCNFetchResponseKeyExperimentDescriptions]];
490
        }
491
      } else {
492
        FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000063",
493
                    @"Empty response with no fetched config.");
494
      }
495
 
496
      // We had a successful fetch. Update the current eTag in settings if different.
497
      NSString *latestETag = ((NSHTTPURLResponse *)response).allHeaderFields[kETagHeaderName];
498
      if (!strongSelf->_settings.lastETag ||
499
          !([strongSelf->_settings.lastETag isEqualToString:latestETag])) {
500
        strongSelf->_settings.lastETag = latestETag;
501
      }
502
 
503
      [strongSelf->_settings updateMetadataWithFetchSuccessStatus:YES];
504
      return [strongSelf reportCompletionOnHandler:completionHandler
505
                                        withStatus:FIRRemoteConfigFetchStatusSuccess
506
                                         withError:nil];
507
    });
508
  };
509
 
510
  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000061", @"Making remote config fetch.");
511
 
512
  NSURLSessionDataTask *dataTask = [self URLSessionDataTaskWithContent:compressedContent
513
                                                     completionHandler:fetcherCompletion];
514
  [dataTask resume];
515
}
516
 
517
- (NSString *)constructServerURL {
518
  NSString *serverURLStr = [[NSString alloc] initWithString:kServerURLDomain];
519
  serverURLStr = [serverURLStr stringByAppendingString:kServerURLVersion];
520
  serverURLStr = [serverURLStr stringByAppendingString:kServerURLProjects];
521
  serverURLStr = [serverURLStr stringByAppendingString:_options.projectID];
522
  serverURLStr = [serverURLStr stringByAppendingString:kServerURLNamespaces];
523
 
524
  // Get the namespace from the fully qualified namespace string of "namespace:FIRAppName".
525
  NSString *namespace =
526
      [_FIRNamespace substringToIndex:[_FIRNamespace rangeOfString:@":"].location];
527
  serverURLStr = [serverURLStr stringByAppendingString:namespace];
528
  serverURLStr = [serverURLStr stringByAppendingString:kServerURLQuery];
529
 
530
  if (_options.APIKey) {
531
    serverURLStr = [serverURLStr stringByAppendingString:kServerURLKey];
532
    serverURLStr = [serverURLStr stringByAppendingString:_options.APIKey];
533
  } else {
534
    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000071",
535
                @"Missing `APIKey` from `FirebaseOptions`, please ensure the configured "
536
                @"`FirebaseApp` is configured with `FirebaseOptions` that contains an `APIKey`.");
537
  }
538
 
539
  return serverURLStr;
540
}
541
 
542
- (NSURLSession *)newFetchSession {
543
  NSURLSessionConfiguration *config =
544
      [[NSURLSessionConfiguration defaultSessionConfiguration] copy];
545
  config.timeoutIntervalForRequest = _settings.fetchTimeout;
546
  config.timeoutIntervalForResource = _settings.fetchTimeout;
547
  NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
548
  return session;
549
}
550
 
551
- (NSURLSessionDataTask *)URLSessionDataTaskWithContent:(NSData *)content
552
                                      completionHandler:
553
                                          (RCNConfigFetcherCompletion)fetcherCompletion {
554
  NSURL *URL = [NSURL URLWithString:[self constructServerURL]];
555
  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000046", @"%@",
556
              [NSString stringWithFormat:@"Making config request: %@", [URL absoluteString]]);
557
 
558
  NSTimeInterval timeoutInterval = _fetchSession.configuration.timeoutIntervalForResource;
559
  NSMutableURLRequest *URLRequest =
560
      [[NSMutableURLRequest alloc] initWithURL:URL
561
                                   cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
562
                               timeoutInterval:timeoutInterval];
563
  URLRequest.HTTPMethod = kHTTPMethodPost;
564
  [URLRequest setValue:kContentTypeValueJSON forHTTPHeaderField:kContentTypeHeaderName];
565
  [URLRequest setValue:_settings.configInstallationsToken
566
      forHTTPHeaderField:kInstallationsAuthTokenHeaderName];
567
  [URLRequest setValue:[[NSBundle mainBundle] bundleIdentifier]
568
      forHTTPHeaderField:kiOSBundleIdentifierHeaderName];
569
  [URLRequest setValue:@"gzip" forHTTPHeaderField:kContentEncodingHeaderName];
570
  [URLRequest setValue:@"gzip" forHTTPHeaderField:kAcceptEncodingHeaderName];
571
  // Set the eTag from the last successful fetch, if available.
572
  if (_settings.lastETag) {
573
    [URLRequest setValue:_settings.lastETag forHTTPHeaderField:kIfNoneMatchETagHeaderName];
574
  }
575
  [URLRequest setHTTPBody:content];
576
 
577
  return [_fetchSession dataTaskWithRequest:URLRequest completionHandler:fetcherCompletion];
578
}
579
 
580
@end