| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715 |
- /*
- * Copyright 2022 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #import "FirebaseRemoteConfig/Sources/RCNConfigRealtime.h"
- #import <Foundation/Foundation.h>
- #import <GoogleUtilities/GULNSData+zlib.h>
- #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
- #import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
- #import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h"
- #import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
- #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
- #import "FirebaseRemoteConfig/Sources/RCNDevice.h"
- /// URL params
- static NSString *const kServerURLDomain = @"https://firebaseremoteconfigrealtime.googleapis.com";
- static NSString *const kServerURLVersion = @"/v1";
- static NSString *const kServerURLProjects = @"/projects/";
- static NSString *const kServerURLNamespaces = @"/namespaces/";
- static NSString *const kServerURLQuery = @":streamFetchInvalidations?";
- static NSString *const kServerURLKey = @"key=";
- /// Realtime API enablement
- static NSString *const kServerForbiddenStatusCode = @"\"code\": 403";
- /// Header names
- static NSString *const kHTTPMethodPost = @"POST"; ///< HTTP request method config fetch using
- static NSString *const kContentTypeHeaderName = @"Content-Type"; ///< HTTP Header Field Name
- static NSString *const kContentEncodingHeaderName =
- @"Content-Encoding"; ///< HTTP Header Field Name
- static NSString *const kAcceptEncodingHeaderName = @"Accept"; ///< HTTP Header Field Name
- static NSString *const kETagHeaderName = @"etag"; ///< HTTP Header Field Name
- static NSString *const kIfNoneMatchETagHeaderName = @"if-none-match"; ///< HTTP Header Field Name
- static NSString *const kInstallationsAuthTokenHeaderName = @"x-goog-firebase-installations-auth";
- // Sends the bundle ID. Refer to b/130301479 for details.
- static NSString *const kiOSBundleIdentifierHeaderName =
- @"X-Ios-Bundle-Identifier"; ///< HTTP Header Field Name
- /// Retryable HTTP status code.
- static NSInteger const kRCNFetchResponseHTTPStatusOk = 200;
- static NSInteger const kRCNFetchResponseHTTPStatusClientTimeout = 429;
- static NSInteger const kRCNFetchResponseHTTPStatusTooManyRequests = 429;
- static NSInteger const kRCNFetchResponseHTTPStatusCodeBadGateway = 502;
- static NSInteger const kRCNFetchResponseHTTPStatusCodeServiceUnavailable = 503;
- static NSInteger const kRCNFetchResponseHTTPStatusCodeGatewayTimeout = 504;
- /// Invalidation message field names.
- static NSString *const kTemplateVersionNumberKey = @"latestTemplateVersionNumber";
- static NSString *const kIsFeatureDisabled = @"featureDisabled";
- static NSTimeInterval gTimeoutSeconds = 330;
- static NSInteger const gFetchAttempts = 3;
- // Retry parameters
- static NSInteger const gMaxRetries = 7;
- @interface FIRConfigUpdateListenerRegistration ()
- @property(strong, atomic, nonnull) RCNConfigUpdateCompletion completionHandler;
- @end
- @implementation FIRConfigUpdateListenerRegistration {
- RCNConfigRealtime *_realtimeClient;
- }
- - (instancetype)initWithClient:(RCNConfigRealtime *)realtimeClient
- completionHandler:(RCNConfigUpdateCompletion)completionHandler {
- self = [super init];
- if (self) {
- _realtimeClient = realtimeClient;
- _completionHandler = completionHandler;
- }
- return self;
- }
- - (void)remove {
- [self->_realtimeClient removeConfigUpdateListener:_completionHandler];
- }
- @end
- @interface RCNConfigRealtime ()
- @property(strong, atomic, nonnull) NSMutableSet<RCNConfigUpdateCompletion> *listeners;
- @property(strong, atomic, nonnull) dispatch_queue_t realtimeLockQueue;
- @property(strong, atomic, nonnull) NSNotificationCenter *notificationCenter;
- @property(strong, atomic) NSURLSession *session;
- @property(strong, atomic) NSURLSessionDataTask *dataTask;
- @property(strong, atomic) NSMutableURLRequest *request;
- @end
- @implementation RCNConfigRealtime {
- RCNConfigFetch *_configFetch;
- RCNConfigSettings *_settings;
- FIROptions *_options;
- NSString *_namespace;
- NSInteger _remainingRetryCount;
- bool _isRequestInProgress;
- bool _isInBackground;
- bool _isRealtimeDisabled;
- }
- - (instancetype)init:(RCNConfigFetch *)configFetch
- settings:(RCNConfigSettings *)settings
- namespace:(NSString *)namespace
- options:(FIROptions *)options {
- self = [super init];
- if (self) {
- _listeners = [[NSMutableSet alloc] init];
- _realtimeLockQueue = [RCNConfigRealtime realtimeRemoteConfigSerialQueue];
- _notificationCenter = [NSNotificationCenter defaultCenter];
- _configFetch = configFetch;
- _settings = settings;
- _options = options;
- _namespace = namespace;
- _remainingRetryCount = MAX(gMaxRetries - [_settings realtimeRetryCount], 1);
- _isRequestInProgress = false;
- _isRealtimeDisabled = false;
- _isInBackground = false;
- [self setUpHttpRequest];
- [self setUpHttpSession];
- [self backgroundChangeListener];
- }
- return self;
- }
- /// Singleton instance of serial queue for queuing all incoming RC calls.
- + (dispatch_queue_t)realtimeRemoteConfigSerialQueue {
- static dispatch_once_t onceToken;
- static dispatch_queue_t realtimeRemoteConfigQueue;
- dispatch_once(&onceToken, ^{
- realtimeRemoteConfigQueue =
- dispatch_queue_create(RCNRemoteConfigQueueLabel, DISPATCH_QUEUE_SERIAL);
- });
- return realtimeRemoteConfigQueue;
- }
- - (void)propogateErrors:(NSError *)error {
- __weak RCNConfigRealtime *weakSelf = self;
- dispatch_async(_realtimeLockQueue, ^{
- __strong RCNConfigRealtime *strongSelf = weakSelf;
- for (RCNConfigUpdateCompletion listener in strongSelf->_listeners) {
- listener(nil, error);
- }
- });
- }
- #pragma mark - Test Only Helpers
- // TESTING ONLY
- - (void)triggerListenerForTesting:(void (^_Nonnull)(FIRRemoteConfigUpdate *configUpdate,
- NSError *_Nullable error))listener {
- listener([[FIRRemoteConfigUpdate alloc] init], nil);
- }
- #pragma mark - Http Helpers
- - (NSString *)constructServerURL {
- NSString *serverURLStr = [[NSString alloc] initWithString:kServerURLDomain];
- serverURLStr = [serverURLStr stringByAppendingString:kServerURLVersion];
- serverURLStr = [serverURLStr stringByAppendingString:kServerURLProjects];
- serverURLStr = [serverURLStr stringByAppendingString:_options.GCMSenderID];
- serverURLStr = [serverURLStr stringByAppendingString:kServerURLNamespaces];
- /// Get the namespace from the fully qualified namespace string of "namespace:FIRAppName".
- NSString *namespace = [_namespace substringToIndex:[_namespace rangeOfString:@":"].location];
- serverURLStr = [serverURLStr stringByAppendingString:namespace];
- serverURLStr = [serverURLStr stringByAppendingString:kServerURLQuery];
- if (_options.APIKey) {
- serverURLStr = [serverURLStr stringByAppendingString:kServerURLKey];
- serverURLStr = [serverURLStr stringByAppendingString:_options.APIKey];
- } else {
- FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000071",
- @"Missing `APIKey` from `FirebaseOptions`, please ensure the configured "
- @"`FirebaseApp` is configured with `FirebaseOptions` that contains an `APIKey`.");
- }
- return serverURLStr;
- }
- - (NSString *)FIRAppNameFromFullyQualifiedNamespace {
- return [[_namespace componentsSeparatedByString:@":"] lastObject];
- }
- - (void)reportCompletionOnHandler:(FIRRemoteConfigFetchCompletion)completionHandler
- withStatus:(FIRRemoteConfigFetchStatus)status
- withError:(NSError *)error {
- if (completionHandler) {
- dispatch_async(_realtimeLockQueue, ^{
- completionHandler(status, error);
- });
- }
- }
- /// Refresh installation ID token before fetching config. installation ID is now mandatory for fetch
- /// requests to work.(b/14751422).
- - (void)refreshInstallationsTokenWithCompletionHandler:
- (FIRRemoteConfigFetchCompletion)completionHandler {
- FIRInstallations *installations = [FIRInstallations
- installationsWithApp:[FIRApp appNamed:[self FIRAppNameFromFullyQualifiedNamespace]]];
- if (!installations || !_options.GCMSenderID) {
- NSString *errorDescription = @"Failed to get GCMSenderID";
- FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000074", @"%@",
- [NSString stringWithFormat:@"%@", errorDescription]);
- return [self
- reportCompletionOnHandler:completionHandler
- withStatus:FIRRemoteConfigFetchStatusFailure
- withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
- code:FIRRemoteConfigErrorInternalError
- userInfo:@{
- NSLocalizedDescriptionKey : errorDescription
- }]];
- }
- __weak RCNConfigRealtime *weakSelf = self;
- FIRInstallationsTokenHandler installationsTokenHandler = ^(
- FIRInstallationsAuthTokenResult *tokenResult, NSError *error) {
- RCNConfigRealtime *strongSelf = weakSelf;
- if (strongSelf == nil) {
- return;
- }
- if (!tokenResult || !tokenResult.authToken || error) {
- NSString *errorDescription =
- [NSString stringWithFormat:@"Failed to get installations token. Error : %@.", error];
- FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000073", @"%@",
- [NSString stringWithFormat:@"%@", errorDescription]);
- return [strongSelf
- reportCompletionOnHandler:completionHandler
- withStatus:FIRRemoteConfigFetchStatusFailure
- withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
- code:FIRRemoteConfigErrorInternalError
- userInfo:@{
- NSLocalizedDescriptionKey : errorDescription
- }]];
- }
- /// We have a valid token. Get the backing installationID.
- [installations installationIDWithCompletion:^(NSString *_Nullable identifier,
- NSError *_Nullable error) {
- RCNConfigRealtime *strongSelf = weakSelf;
- if (strongSelf == nil) {
- return;
- }
- // Dispatch to the RC serial queue to update settings on the queue.
- dispatch_async(strongSelf->_realtimeLockQueue, ^{
- RCNConfigRealtime *strongSelfQueue = weakSelf;
- if (strongSelfQueue == nil) {
- return;
- }
- /// Update config settings with the IID and token.
- strongSelfQueue->_settings.configInstallationsToken = tokenResult.authToken;
- strongSelfQueue->_settings.configInstallationsIdentifier = identifier;
- if (!identifier || error) {
- NSString *errorDescription =
- [NSString stringWithFormat:@"Error getting iid : %@.", error];
- FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000055", @"%@",
- [NSString stringWithFormat:@"%@", errorDescription]);
- strongSelfQueue->_settings.isFetchInProgress = NO;
- return [strongSelfQueue
- reportCompletionOnHandler:completionHandler
- withStatus:FIRRemoteConfigFetchStatusFailure
- withError:[NSError
- errorWithDomain:FIRRemoteConfigErrorDomain
- code:FIRRemoteConfigErrorInternalError
- userInfo:@{
- NSLocalizedDescriptionKey : errorDescription
- }]];
- }
- FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000022", @"Success to get iid : %@.",
- strongSelfQueue->_settings.configInstallationsIdentifier);
- });
- }];
- };
- FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000039", @"Starting requesting token.");
- [installations authTokenWithCompletion:installationsTokenHandler];
- }
- - (void)setRequestBody {
- [self refreshInstallationsTokenWithCompletionHandler:^(FIRRemoteConfigFetchStatus status,
- NSError *_Nullable error) {
- if (status != FIRRemoteConfigFetchStatusSuccess) {
- FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000013", @"Installation token retrival failed.");
- }
- }];
- [_request setValue:_settings.configInstallationsToken
- forHTTPHeaderField:kInstallationsAuthTokenHeaderName];
- if (_settings.lastETag) {
- [_request setValue:_settings.lastETag forHTTPHeaderField:kIfNoneMatchETagHeaderName];
- }
- NSString *namespace = [_namespace substringToIndex:[_namespace rangeOfString:@":"].location];
- NSString *postBody = [NSString
- stringWithFormat:@"{project:'%@', namespace:'%@', lastKnownVersionNumber:'%@', appId:'%@', "
- @"sdkVersion:'%@'}",
- [self->_options GCMSenderID], namespace, _configFetch.templateVersionNumber,
- _options.googleAppID, FIRRemoteConfigPodVersion()];
- NSData *postData = [postBody dataUsingEncoding:NSUTF8StringEncoding];
- NSError *compressionError;
- NSData *compressedContent = [NSData gul_dataByGzippingData:postData error:&compressionError];
- [_request setHTTPBody:compressedContent];
- }
- /// Creates request.
- - (void)setUpHttpRequest {
- NSString *address = [self constructServerURL];
- _request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:address]
- cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
- timeoutInterval:gTimeoutSeconds];
- [_request setHTTPMethod:kHTTPMethodPost];
- [_request setValue:@"application/json" forHTTPHeaderField:kContentTypeHeaderName];
- [_request setValue:@"application/json" forHTTPHeaderField:kAcceptEncodingHeaderName];
- [_request setValue:@"gzip" forHTTPHeaderField:kContentEncodingHeaderName];
- [_request setValue:@"true" forHTTPHeaderField:@"X-Google-GFE-Can-Retry"];
- [_request setValue:[_options APIKey] forHTTPHeaderField:@"X-Goog-Api-Key"];
- [_request setValue:[[NSBundle mainBundle] bundleIdentifier]
- forHTTPHeaderField:kiOSBundleIdentifierHeaderName];
- }
- /// Makes call to create session.
- - (void)setUpHttpSession {
- NSURLSessionConfiguration *sessionConfig =
- [[NSURLSessionConfiguration defaultSessionConfiguration] copy];
- [sessionConfig setTimeoutIntervalForResource:gTimeoutSeconds];
- [sessionConfig setTimeoutIntervalForRequest:gTimeoutSeconds];
- _session = [NSURLSession sessionWithConfiguration:sessionConfig
- delegate:self
- delegateQueue:[NSOperationQueue mainQueue]];
- }
- #pragma mark - Retry Helpers
- // Retry mechanism for HTTP connections
- - (void)retryHTTPConnection {
- __weak RCNConfigRealtime *weakSelf = self;
- dispatch_async(_realtimeLockQueue, ^{
- __strong RCNConfigRealtime *strongSelf = weakSelf;
- if (strongSelf->_isInBackground) {
- return;
- }
- bool noRunningConnection =
- strongSelf->_dataTask == nil || strongSelf->_dataTask.state != NSURLSessionTaskStateRunning;
- bool canMakeConnection = noRunningConnection && [strongSelf->_listeners count] > 0 &&
- !strongSelf->_isRealtimeDisabled;
- if (canMakeConnection && strongSelf->_remainingRetryCount > 0) {
- NSTimeInterval backOffInterval = self->_settings.getRealtimeBackoffInterval;
- strongSelf->_remainingRetryCount--;
- [strongSelf->_settings setRealtimeRetryCount:[strongSelf->_settings realtimeRetryCount] + 1];
- dispatch_time_t executionDelay =
- dispatch_time(DISPATCH_TIME_NOW, (backOffInterval * NSEC_PER_SEC));
- dispatch_after(executionDelay, strongSelf->_realtimeLockQueue, ^{
- [strongSelf beginRealtimeStream];
- });
- } else {
- NSError *error = [NSError
- errorWithDomain:FIRRemoteConfigUpdateErrorDomain
- code:FIRRemoteConfigUpdateErrorStreamError
- userInfo:@{
- NSLocalizedDescriptionKey :
- @"Unable to connect to the server. Check your connection and try again."
- }];
- FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000014", @"Cannot establish connection. Error: %@",
- error);
- [self propogateErrors:error];
- }
- });
- }
- - (void)backgroundChangeListener {
- [_notificationCenter addObserver:self
- selector:@selector(isInForeground)
- name:@"UIApplicationWillEnterForegroundNotification"
- object:nil];
- [_notificationCenter addObserver:self
- selector:@selector(isInBackground)
- name:@"UIApplicationDidEnterBackgroundNotification"
- object:nil];
- }
- - (void)isInForeground {
- __weak RCNConfigRealtime *weakSelf = self;
- dispatch_async(_realtimeLockQueue, ^{
- __strong RCNConfigRealtime *strongSelf = weakSelf;
- strongSelf->_isInBackground = false;
- [strongSelf beginRealtimeStream];
- });
- }
- - (void)isInBackground {
- __weak RCNConfigRealtime *weakSelf = self;
- dispatch_async(_realtimeLockQueue, ^{
- __strong RCNConfigRealtime *strongSelf = weakSelf;
- [strongSelf pauseRealtimeStream];
- strongSelf->_isInBackground = true;
- });
- }
- #pragma mark - Autofetch Helpers
- - (void)fetchLatestConfig:(NSInteger)remainingAttempts targetVersion:(NSInteger)targetVersion {
- __weak RCNConfigRealtime *weakSelf = self;
- dispatch_async(_realtimeLockQueue, ^{
- __strong RCNConfigRealtime *strongSelf = weakSelf;
- NSInteger attempts = remainingAttempts - 1;
- [strongSelf->_configFetch
- realtimeFetchConfigWithNoExpirationDuration:gFetchAttempts - attempts
- completionHandler:^(FIRRemoteConfigFetchStatus status,
- FIRRemoteConfigUpdate *update,
- NSError *error) {
- if (error != nil) {
- FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000010",
- @"Failed to retrive config due to fetch error. "
- @"Error: %@",
- error);
- return [self propogateErrors:error];
- }
- if (status == FIRRemoteConfigFetchStatusSuccess) {
- if ([strongSelf->_configFetch.templateVersionNumber
- integerValue] >= targetVersion) {
- // only notify listeners if there is a change
- if ([update updatedKeys].count > 0) {
- for (RCNConfigUpdateCompletion listener in strongSelf
- ->_listeners) {
- listener(update, nil);
- }
- }
- } else {
- FIRLogDebug(
- kFIRLoggerRemoteConfig, @"I-RCN000016",
- @"Fetched config's template version is outdated, "
- @"re-fetching");
- [strongSelf autoFetch:attempts targetVersion:targetVersion];
- }
- } else {
- FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000016",
- @"Fetched config's template version is "
- @"outdated, re-fetching");
- [strongSelf autoFetch:attempts targetVersion:targetVersion];
- }
- }];
- });
- }
- - (void)scheduleFetch:(NSInteger)remainingAttempts targetVersion:(NSInteger)targetVersion {
- /// Needs fetch to occur between 0 - 3 seconds. Randomize to not cause DDoS alerts in backend
- dispatch_time_t executionDelay =
- dispatch_time(DISPATCH_TIME_NOW, arc4random_uniform(4) * NSEC_PER_SEC);
- dispatch_after(executionDelay, _realtimeLockQueue, ^{
- [self fetchLatestConfig:remainingAttempts targetVersion:targetVersion];
- });
- }
- /// Perform fetch and handle developers callbacks
- - (void)autoFetch:(NSInteger)remainingAttempts targetVersion:(NSInteger)targetVersion {
- __weak RCNConfigRealtime *weakSelf = self;
- dispatch_async(_realtimeLockQueue, ^{
- __strong RCNConfigRealtime *strongSelf = weakSelf;
- if (remainingAttempts == 0) {
- NSError *error = [NSError errorWithDomain:FIRRemoteConfigUpdateErrorDomain
- code:FIRRemoteConfigUpdateErrorNotFetched
- userInfo:@{
- NSLocalizedDescriptionKey :
- @"Unable to fetch the latest version of the template.."
- }];
- FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000011",
- @"Ran out of fetch attempts, cannot find target config version.");
- [self propogateErrors:error];
- return;
- }
- [strongSelf scheduleFetch:remainingAttempts targetVersion:targetVersion];
- });
- }
- #pragma mark - NSURLSession Delegates
- - (void)evaluateStreamResponse:(NSDictionary *)response error:(NSError *)dataError {
- NSInteger updateTemplateVersion = 1;
- if (dataError == nil) {
- if ([response objectForKey:kTemplateVersionNumberKey]) {
- updateTemplateVersion = [[response objectForKey:kTemplateVersionNumberKey] integerValue];
- }
- if ([response objectForKey:kIsFeatureDisabled]) {
- self->_isRealtimeDisabled = [response objectForKey:kIsFeatureDisabled];
- }
- if (self->_isRealtimeDisabled) {
- [self pauseRealtimeStream];
- NSError *error = [NSError
- errorWithDomain:FIRRemoteConfigUpdateErrorDomain
- code:FIRRemoteConfigUpdateErrorUnavailable
- userInfo:@{
- NSLocalizedDescriptionKey :
- @"The server is temporarily unavailable. Try again in a few minutes."
- }];
- [self propogateErrors:error];
- } else {
- NSInteger clientTemplateVersion = [_configFetch.templateVersionNumber integerValue];
- if (updateTemplateVersion > clientTemplateVersion) {
- [self autoFetch:gFetchAttempts targetVersion:updateTemplateVersion];
- }
- }
- } else {
- NSError *error =
- [NSError errorWithDomain:FIRRemoteConfigUpdateErrorDomain
- code:FIRRemoteConfigUpdateErrorMessageInvalid
- userInfo:@{NSLocalizedDescriptionKey : @"Unable to parse ConfigUpdate."}];
- [self propogateErrors:error];
- }
- }
- /// Delegate to asynchronously handle every new notification that comes over the wire. Auto-fetches
- /// and runs callback for each new notification
- - (void)URLSession:(NSURLSession *)session
- dataTask:(NSURLSessionDataTask *)dataTask
- didReceiveData:(NSData *)data {
- NSError *dataError;
- NSString *strData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
- /// If response data contains the API enablement link, return the entire message to the user in
- /// the form of a error.
- if ([strData containsString:kServerForbiddenStatusCode]) {
- NSError *error = [NSError errorWithDomain:FIRRemoteConfigUpdateErrorDomain
- code:FIRRemoteConfigUpdateErrorStreamError
- userInfo:@{NSLocalizedDescriptionKey : strData}];
- FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000021", @"Cannot establish connection. %@", error);
- [self propogateErrors:error];
- return;
- }
- NSRange endRange = [strData rangeOfString:@"}"];
- NSRange beginRange = [strData rangeOfString:@"{"];
- if (beginRange.location != NSNotFound && endRange.location != NSNotFound) {
- FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000015",
- @"Received config update message on stream.");
- NSRange msgRange =
- NSMakeRange(beginRange.location, endRange.location - beginRange.location + 1);
- strData = [strData substringWithRange:msgRange];
- data = [strData dataUsingEncoding:NSUTF8StringEncoding];
- NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data
- options:NSJSONReadingMutableContainers
- error:&dataError];
- [self evaluateStreamResponse:response error:dataError];
- }
- }
- /// Check if response code is retryable
- - (bool)isStatusCodeRetryable:(NSInteger)statusCode {
- return statusCode == kRCNFetchResponseHTTPStatusClientTimeout ||
- statusCode == kRCNFetchResponseHTTPStatusTooManyRequests ||
- statusCode == kRCNFetchResponseHTTPStatusCodeServiceUnavailable ||
- statusCode == kRCNFetchResponseHTTPStatusCodeBadGateway ||
- statusCode == kRCNFetchResponseHTTPStatusCodeGatewayTimeout;
- }
- /// Delegate to handle initial reply from the server
- - (void)URLSession:(NSURLSession *)session
- dataTask:(NSURLSessionDataTask *)dataTask
- didReceiveResponse:(NSURLResponse *)response
- completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
- _isRequestInProgress = false;
- NSHTTPURLResponse *_httpURLResponse = (NSHTTPURLResponse *)response;
- NSInteger statusCode = [_httpURLResponse statusCode];
- if (statusCode == 403) {
- completionHandler(NSURLSessionResponseAllow);
- return;
- }
- if (statusCode != kRCNFetchResponseHTTPStatusOk) {
- [self->_settings updateRealtimeExponentialBackoffTime];
- [self pauseRealtimeStream];
- if ([self isStatusCodeRetryable:statusCode]) {
- [self retryHTTPConnection];
- } else {
- NSError *error = [NSError
- errorWithDomain:FIRRemoteConfigUpdateErrorDomain
- code:FIRRemoteConfigUpdateErrorStreamError
- userInfo:@{
- NSLocalizedDescriptionKey :
- [NSString stringWithFormat:@"Unable to connect to the server. Try again in "
- @"a few minutes. Http Status code: %@",
- [@(statusCode) stringValue]]
- }];
- FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000021", @"Cannot establish connection. Error: %@",
- error);
- }
- } else {
- /// on success reset retry parameters
- _remainingRetryCount = gMaxRetries;
- [self->_settings setRealtimeRetryCount:0];
- }
- completionHandler(NSURLSessionResponseAllow);
- }
- /// Delegate to handle data task completion
- - (void)URLSession:(NSURLSession *)session
- task:(NSURLSessionTask *)task
- didCompleteWithError:(NSError *)error {
- _isRequestInProgress = false;
- if (error != nil && [error code] != NSURLErrorCancelled) {
- [self->_settings updateRealtimeExponentialBackoffTime];
- }
- [self pauseRealtimeStream];
- [self retryHTTPConnection];
- }
- /// Delegate to handle session invalidation
- - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
- if (!_isRequestInProgress) {
- if (error != nil) {
- [self->_settings updateRealtimeExponentialBackoffTime];
- }
- [self pauseRealtimeStream];
- [self retryHTTPConnection];
- }
- }
- #pragma mark - Top level methods
- - (void)beginRealtimeStream {
- __weak RCNConfigRealtime *weakSelf = self;
- dispatch_async(_realtimeLockQueue, ^{
- __strong RCNConfigRealtime *strongSelf = weakSelf;
- bool noRunningConnection =
- strongSelf->_dataTask == nil || strongSelf->_dataTask.state != NSURLSessionTaskStateRunning;
- bool canMakeConnection = noRunningConnection && [strongSelf->_listeners count] > 0 &&
- !strongSelf->_isInBackground && !strongSelf->_isRealtimeDisabled;
- if (self->_settings.getRealtimeBackoffInterval > 0) {
- [self retryHTTPConnection];
- return;
- }
- if (canMakeConnection) {
- strongSelf->_isRequestInProgress = true;
- [strongSelf setRequestBody];
- strongSelf->_dataTask = [strongSelf->_session dataTaskWithRequest:strongSelf->_request];
- [strongSelf->_dataTask resume];
- }
- });
- }
- - (void)pauseRealtimeStream {
- __weak RCNConfigRealtime *weakSelf = self;
- dispatch_async(_realtimeLockQueue, ^{
- __strong RCNConfigRealtime *strongSelf = weakSelf;
- if (strongSelf->_dataTask != nil) {
- [strongSelf->_dataTask cancel];
- strongSelf->_dataTask = nil;
- }
- });
- }
- - (FIRConfigUpdateListenerRegistration *)addConfigUpdateListener:
- (void (^_Nonnull)(FIRRemoteConfigUpdate *configUpdate, NSError *_Nullable error))listener {
- if (listener == nil) {
- return nil;
- }
- __weak RCNConfigRealtime *weakSelf = self;
- dispatch_async(_realtimeLockQueue, ^{
- __strong RCNConfigRealtime *strongSelf = weakSelf;
- [strongSelf->_listeners addObject:listener];
- [strongSelf beginRealtimeStream];
- });
- return [[FIRConfigUpdateListenerRegistration alloc] initWithClient:self
- completionHandler:listener];
- }
- - (void)removeConfigUpdateListener:(void (^_Nonnull)(FIRRemoteConfigUpdate *configUpdate,
- NSError *_Nullable error))listener {
- __weak RCNConfigRealtime *weakSelf = self;
- dispatch_async(_realtimeLockQueue, ^{
- __strong RCNConfigRealtime *strongSelf = weakSelf;
- [strongSelf->_listeners removeObject:listener];
- if (strongSelf->_listeners.count == 0) {
- [strongSelf pauseRealtimeStream];
- }
- });
- }
- @end
|