RCNConfigRealtime.m 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. /*
  2. * Copyright 2022 Google LLC
  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/RCNConfigRealtime.h"
  17. #import <Foundation/Foundation.h>
  18. #import <GoogleUtilities/GULNSData+zlib.h>
  19. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  20. #import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
  21. #import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h"
  22. #import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
  23. #import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
  24. #import "FirebaseRemoteConfig/Sources/RCNDevice.h"
  25. /// URL params
  26. static NSString *const kServerURLDomain = @"https://firebaseremoteconfigrealtime.googleapis.com";
  27. static NSString *const kServerURLVersion = @"/v1";
  28. static NSString *const kServerURLProjects = @"/projects/";
  29. static NSString *const kServerURLNamespaces = @"/namespaces/";
  30. static NSString *const kServerURLQuery = @":streamFetchInvalidations?";
  31. static NSString *const kServerURLKey = @"key=";
  32. /// Realtime API enablement
  33. static NSString *const kServerForbiddenStatusCode = @"\"code\": 403";
  34. /// Header names
  35. static NSString *const kHTTPMethodPost = @"POST"; ///< HTTP request method config fetch using
  36. static NSString *const kContentTypeHeaderName = @"Content-Type"; ///< HTTP Header Field Name
  37. static NSString *const kContentEncodingHeaderName =
  38. @"Content-Encoding"; ///< HTTP Header Field Name
  39. static NSString *const kAcceptEncodingHeaderName = @"Accept"; ///< HTTP Header Field Name
  40. static NSString *const kETagHeaderName = @"etag"; ///< HTTP Header Field Name
  41. static NSString *const kIfNoneMatchETagHeaderName = @"if-none-match"; ///< HTTP Header Field Name
  42. static NSString *const kInstallationsAuthTokenHeaderName = @"x-goog-firebase-installations-auth";
  43. // Sends the bundle ID. Refer to b/130301479 for details.
  44. static NSString *const kiOSBundleIdentifierHeaderName =
  45. @"X-Ios-Bundle-Identifier"; ///< HTTP Header Field Name
  46. /// Retryable HTTP status code.
  47. static NSInteger const kRCNFetchResponseHTTPStatusOk = 200;
  48. static NSInteger const kRCNFetchResponseHTTPStatusClientTimeout = 429;
  49. static NSInteger const kRCNFetchResponseHTTPStatusTooManyRequests = 429;
  50. static NSInteger const kRCNFetchResponseHTTPStatusCodeBadGateway = 502;
  51. static NSInteger const kRCNFetchResponseHTTPStatusCodeServiceUnavailable = 503;
  52. static NSInteger const kRCNFetchResponseHTTPStatusCodeGatewayTimeout = 504;
  53. /// Invalidation message field names.
  54. static NSString *const kTemplateVersionNumberKey = @"latestTemplateVersionNumber";
  55. static NSString *const kIsFeatureDisabled = @"featureDisabled";
  56. static NSTimeInterval gTimeoutSeconds = 330;
  57. static NSInteger const gFetchAttempts = 3;
  58. // Retry parameters
  59. static NSInteger const gMaxRetries = 7;
  60. @interface FIRConfigUpdateListenerRegistration ()
  61. @property(strong, atomic, nonnull) RCNConfigUpdateCompletion completionHandler;
  62. @end
  63. @implementation FIRConfigUpdateListenerRegistration {
  64. RCNConfigRealtime *_realtimeClient;
  65. }
  66. - (instancetype)initWithClient:(RCNConfigRealtime *)realtimeClient
  67. completionHandler:(RCNConfigUpdateCompletion)completionHandler {
  68. self = [super init];
  69. if (self) {
  70. _realtimeClient = realtimeClient;
  71. _completionHandler = completionHandler;
  72. }
  73. return self;
  74. }
  75. - (void)remove {
  76. [self->_realtimeClient removeConfigUpdateListener:_completionHandler];
  77. }
  78. @end
  79. @interface RCNConfigRealtime ()
  80. @property(strong, atomic, nonnull) NSMutableSet<RCNConfigUpdateCompletion> *listeners;
  81. @property(strong, atomic, nonnull) dispatch_queue_t realtimeLockQueue;
  82. @property(strong, atomic, nonnull) NSNotificationCenter *notificationCenter;
  83. @property(strong, atomic) NSURLSession *session;
  84. @property(strong, atomic) NSURLSessionDataTask *dataTask;
  85. @property(strong, atomic) NSMutableURLRequest *request;
  86. @end
  87. @implementation RCNConfigRealtime {
  88. RCNConfigFetch *_configFetch;
  89. RCNConfigSettings *_settings;
  90. FIROptions *_options;
  91. NSString *_namespace;
  92. NSInteger _remainingRetryCount;
  93. bool _isRequestInProgress;
  94. bool _isInBackground;
  95. bool _isRealtimeDisabled;
  96. }
  97. - (instancetype)init:(RCNConfigFetch *)configFetch
  98. settings:(RCNConfigSettings *)settings
  99. namespace:(NSString *)namespace
  100. options:(FIROptions *)options {
  101. self = [super init];
  102. if (self) {
  103. _listeners = [[NSMutableSet alloc] init];
  104. _realtimeLockQueue = [RCNConfigRealtime realtimeRemoteConfigSerialQueue];
  105. _notificationCenter = [NSNotificationCenter defaultCenter];
  106. _configFetch = configFetch;
  107. _settings = settings;
  108. _options = options;
  109. _namespace = namespace;
  110. _remainingRetryCount = MAX(gMaxRetries - [_settings realtimeRetryCount], 1);
  111. _isRequestInProgress = false;
  112. _isRealtimeDisabled = false;
  113. _isInBackground = false;
  114. [self setUpHttpRequest];
  115. [self setUpHttpSession];
  116. [self backgroundChangeListener];
  117. }
  118. return self;
  119. }
  120. /// Singleton instance of serial queue for queuing all incoming RC calls.
  121. + (dispatch_queue_t)realtimeRemoteConfigSerialQueue {
  122. static dispatch_once_t onceToken;
  123. static dispatch_queue_t realtimeRemoteConfigQueue;
  124. dispatch_once(&onceToken, ^{
  125. realtimeRemoteConfigQueue =
  126. dispatch_queue_create(RCNRemoteConfigQueueLabel, DISPATCH_QUEUE_SERIAL);
  127. });
  128. return realtimeRemoteConfigQueue;
  129. }
  130. - (void)propogateErrors:(NSError *)error {
  131. __weak RCNConfigRealtime *weakSelf = self;
  132. dispatch_async(_realtimeLockQueue, ^{
  133. __strong RCNConfigRealtime *strongSelf = weakSelf;
  134. for (RCNConfigUpdateCompletion listener in strongSelf->_listeners) {
  135. listener(nil, error);
  136. }
  137. });
  138. }
  139. #pragma mark - Test Only Helpers
  140. // TESTING ONLY
  141. - (void)triggerListenerForTesting:(void (^_Nonnull)(FIRRemoteConfigUpdate *configUpdate,
  142. NSError *_Nullable error))listener {
  143. listener([[FIRRemoteConfigUpdate alloc] init], nil);
  144. }
  145. #pragma mark - Http Helpers
  146. - (NSString *)constructServerURL {
  147. NSString *serverURLStr = [[NSString alloc] initWithString:kServerURLDomain];
  148. serverURLStr = [serverURLStr stringByAppendingString:kServerURLVersion];
  149. serverURLStr = [serverURLStr stringByAppendingString:kServerURLProjects];
  150. serverURLStr = [serverURLStr stringByAppendingString:_options.GCMSenderID];
  151. serverURLStr = [serverURLStr stringByAppendingString:kServerURLNamespaces];
  152. /// Get the namespace from the fully qualified namespace string of "namespace:FIRAppName".
  153. NSString *namespace = [_namespace substringToIndex:[_namespace rangeOfString:@":"].location];
  154. serverURLStr = [serverURLStr stringByAppendingString:namespace];
  155. serverURLStr = [serverURLStr stringByAppendingString:kServerURLQuery];
  156. if (_options.APIKey) {
  157. serverURLStr = [serverURLStr stringByAppendingString:kServerURLKey];
  158. serverURLStr = [serverURLStr stringByAppendingString:_options.APIKey];
  159. } else {
  160. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000071",
  161. @"Missing `APIKey` from `FirebaseOptions`, please ensure the configured "
  162. @"`FirebaseApp` is configured with `FirebaseOptions` that contains an `APIKey`.");
  163. }
  164. return serverURLStr;
  165. }
  166. - (NSString *)FIRAppNameFromFullyQualifiedNamespace {
  167. return [[_namespace componentsSeparatedByString:@":"] lastObject];
  168. }
  169. - (void)reportCompletionOnHandler:(FIRRemoteConfigFetchCompletion)completionHandler
  170. withStatus:(FIRRemoteConfigFetchStatus)status
  171. withError:(NSError *)error {
  172. if (completionHandler) {
  173. dispatch_async(_realtimeLockQueue, ^{
  174. completionHandler(status, error);
  175. });
  176. }
  177. }
  178. /// Refresh installation ID token before fetching config. installation ID is now mandatory for fetch
  179. /// requests to work.(b/14751422).
  180. - (void)refreshInstallationsTokenWithCompletionHandler:
  181. (FIRRemoteConfigFetchCompletion)completionHandler {
  182. FIRInstallations *installations = [FIRInstallations
  183. installationsWithApp:[FIRApp appNamed:[self FIRAppNameFromFullyQualifiedNamespace]]];
  184. if (!installations || !_options.GCMSenderID) {
  185. NSString *errorDescription = @"Failed to get GCMSenderID";
  186. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000074", @"%@",
  187. [NSString stringWithFormat:@"%@", errorDescription]);
  188. return [self
  189. reportCompletionOnHandler:completionHandler
  190. withStatus:FIRRemoteConfigFetchStatusFailure
  191. withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
  192. code:FIRRemoteConfigErrorInternalError
  193. userInfo:@{
  194. NSLocalizedDescriptionKey : errorDescription
  195. }]];
  196. }
  197. __weak RCNConfigRealtime *weakSelf = self;
  198. FIRInstallationsTokenHandler installationsTokenHandler = ^(
  199. FIRInstallationsAuthTokenResult *tokenResult, NSError *error) {
  200. RCNConfigRealtime *strongSelf = weakSelf;
  201. if (strongSelf == nil) {
  202. return;
  203. }
  204. if (!tokenResult || !tokenResult.authToken || error) {
  205. NSString *errorDescription =
  206. [NSString stringWithFormat:@"Failed to get installations token. Error : %@.", error];
  207. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000073", @"%@",
  208. [NSString stringWithFormat:@"%@", errorDescription]);
  209. return [strongSelf
  210. reportCompletionOnHandler:completionHandler
  211. withStatus:FIRRemoteConfigFetchStatusFailure
  212. withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
  213. code:FIRRemoteConfigErrorInternalError
  214. userInfo:@{
  215. NSLocalizedDescriptionKey : errorDescription
  216. }]];
  217. }
  218. /// We have a valid token. Get the backing installationID.
  219. [installations installationIDWithCompletion:^(NSString *_Nullable identifier,
  220. NSError *_Nullable error) {
  221. RCNConfigRealtime *strongSelf = weakSelf;
  222. if (strongSelf == nil) {
  223. return;
  224. }
  225. // Dispatch to the RC serial queue to update settings on the queue.
  226. dispatch_async(strongSelf->_realtimeLockQueue, ^{
  227. RCNConfigRealtime *strongSelfQueue = weakSelf;
  228. if (strongSelfQueue == nil) {
  229. return;
  230. }
  231. /// Update config settings with the IID and token.
  232. strongSelfQueue->_settings.configInstallationsToken = tokenResult.authToken;
  233. strongSelfQueue->_settings.configInstallationsIdentifier = identifier;
  234. if (!identifier || error) {
  235. NSString *errorDescription =
  236. [NSString stringWithFormat:@"Error getting iid : %@.", error];
  237. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000055", @"%@",
  238. [NSString stringWithFormat:@"%@", errorDescription]);
  239. strongSelfQueue->_settings.isFetchInProgress = NO;
  240. return [strongSelfQueue
  241. reportCompletionOnHandler:completionHandler
  242. withStatus:FIRRemoteConfigFetchStatusFailure
  243. withError:[NSError
  244. errorWithDomain:FIRRemoteConfigErrorDomain
  245. code:FIRRemoteConfigErrorInternalError
  246. userInfo:@{
  247. NSLocalizedDescriptionKey : errorDescription
  248. }]];
  249. }
  250. FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000022", @"Success to get iid : %@.",
  251. strongSelfQueue->_settings.configInstallationsIdentifier);
  252. return [strongSelfQueue reportCompletionOnHandler:completionHandler
  253. withStatus:FIRRemoteConfigFetchStatusNoFetchYet
  254. withError:nil];
  255. });
  256. }];
  257. };
  258. FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000039", @"Starting requesting token.");
  259. [installations authTokenWithCompletion:installationsTokenHandler];
  260. }
  261. - (void)createRequestBodyWithCompletion:(void (^)(NSData *_Nonnull requestBody))completion {
  262. __weak __typeof(self) weakSelf = self;
  263. [self refreshInstallationsTokenWithCompletionHandler:^(FIRRemoteConfigFetchStatus status,
  264. NSError *_Nullable error) {
  265. __strong __typeof(self) strongSelf = weakSelf;
  266. if (!strongSelf) return;
  267. if (![strongSelf->_settings.configInstallationsIdentifier length]) {
  268. FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000013",
  269. @"Installation token retrieval failed. Realtime connection will not include "
  270. @"valid installations token.");
  271. }
  272. [strongSelf.request setValue:strongSelf->_settings.configInstallationsToken
  273. forHTTPHeaderField:kInstallationsAuthTokenHeaderName];
  274. if (strongSelf->_settings.lastETag) {
  275. [strongSelf.request setValue:strongSelf->_settings.lastETag
  276. forHTTPHeaderField:kIfNoneMatchETagHeaderName];
  277. }
  278. NSString *namespace = [strongSelf->_namespace
  279. substringToIndex:[strongSelf->_namespace rangeOfString:@":"].location];
  280. NSString *postBody = [NSString
  281. stringWithFormat:@"{project:'%@', namespace:'%@', lastKnownVersionNumber:'%@', appId:'%@', "
  282. @"sdkVersion:'%@', appInstanceId:'%@'}",
  283. [strongSelf->_options GCMSenderID], namespace,
  284. strongSelf->_configFetch.templateVersionNumber,
  285. strongSelf->_options.googleAppID, FIRRemoteConfigPodVersion(),
  286. strongSelf->_settings.configInstallationsIdentifier];
  287. NSData *postData = [postBody dataUsingEncoding:NSUTF8StringEncoding];
  288. NSError *compressionError;
  289. completion([NSData gul_dataByGzippingData:postData error:&compressionError]);
  290. }];
  291. }
  292. /// Creates request.
  293. - (void)setUpHttpRequest {
  294. NSString *address = [self constructServerURL];
  295. _request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:address]
  296. cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
  297. timeoutInterval:gTimeoutSeconds];
  298. [_request setHTTPMethod:kHTTPMethodPost];
  299. [_request setValue:@"application/json" forHTTPHeaderField:kContentTypeHeaderName];
  300. [_request setValue:@"application/json" forHTTPHeaderField:kAcceptEncodingHeaderName];
  301. [_request setValue:@"gzip" forHTTPHeaderField:kContentEncodingHeaderName];
  302. [_request setValue:@"true" forHTTPHeaderField:@"X-Google-GFE-Can-Retry"];
  303. [_request setValue:[_options APIKey] forHTTPHeaderField:@"X-Goog-Api-Key"];
  304. [_request setValue:[[NSBundle mainBundle] bundleIdentifier]
  305. forHTTPHeaderField:kiOSBundleIdentifierHeaderName];
  306. }
  307. /// Makes call to create session.
  308. - (void)setUpHttpSession {
  309. NSURLSessionConfiguration *sessionConfig =
  310. [[NSURLSessionConfiguration defaultSessionConfiguration] copy];
  311. [sessionConfig setTimeoutIntervalForResource:gTimeoutSeconds];
  312. [sessionConfig setTimeoutIntervalForRequest:gTimeoutSeconds];
  313. _session = [NSURLSession sessionWithConfiguration:sessionConfig
  314. delegate:self
  315. delegateQueue:[NSOperationQueue mainQueue]];
  316. }
  317. #pragma mark - Retry Helpers
  318. - (BOOL)canMakeConnection {
  319. BOOL noRunningConnection =
  320. self->_dataTask == nil || self->_dataTask.state != NSURLSessionTaskStateRunning;
  321. BOOL canMakeConnection = noRunningConnection && [self->_listeners count] > 0 &&
  322. !self->_isInBackground && !self->_isRealtimeDisabled;
  323. return canMakeConnection;
  324. }
  325. // Retry mechanism for HTTP connections
  326. - (void)retryHTTPConnection {
  327. __weak RCNConfigRealtime *weakSelf = self;
  328. dispatch_async(_realtimeLockQueue, ^{
  329. __strong RCNConfigRealtime *strongSelf = weakSelf;
  330. if (!strongSelf || strongSelf->_isInBackground) {
  331. return;
  332. }
  333. if ([strongSelf canMakeConnection] && strongSelf->_remainingRetryCount > 0) {
  334. NSTimeInterval backOffInterval = self->_settings.getRealtimeBackoffInterval;
  335. strongSelf->_remainingRetryCount--;
  336. [strongSelf->_settings setRealtimeRetryCount:[strongSelf->_settings realtimeRetryCount] + 1];
  337. dispatch_time_t executionDelay =
  338. dispatch_time(DISPATCH_TIME_NOW, (backOffInterval * NSEC_PER_SEC));
  339. dispatch_after(executionDelay, strongSelf->_realtimeLockQueue, ^{
  340. [strongSelf beginRealtimeStream];
  341. });
  342. } else {
  343. NSError *error = [NSError
  344. errorWithDomain:FIRRemoteConfigUpdateErrorDomain
  345. code:FIRRemoteConfigUpdateErrorStreamError
  346. userInfo:@{
  347. NSLocalizedDescriptionKey :
  348. @"Unable to connect to the server. Check your connection and try again."
  349. }];
  350. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000014", @"Cannot establish connection. Error: %@",
  351. error);
  352. [self propogateErrors:error];
  353. }
  354. });
  355. }
  356. - (void)backgroundChangeListener {
  357. [_notificationCenter addObserver:self
  358. selector:@selector(isInForeground)
  359. name:@"UIApplicationWillEnterForegroundNotification"
  360. object:nil];
  361. [_notificationCenter addObserver:self
  362. selector:@selector(isInBackground)
  363. name:@"UIApplicationDidEnterBackgroundNotification"
  364. object:nil];
  365. }
  366. - (void)isInForeground {
  367. __weak RCNConfigRealtime *weakSelf = self;
  368. dispatch_async(_realtimeLockQueue, ^{
  369. __strong RCNConfigRealtime *strongSelf = weakSelf;
  370. strongSelf->_isInBackground = false;
  371. [strongSelf beginRealtimeStream];
  372. });
  373. }
  374. - (void)isInBackground {
  375. __weak RCNConfigRealtime *weakSelf = self;
  376. dispatch_async(_realtimeLockQueue, ^{
  377. __strong RCNConfigRealtime *strongSelf = weakSelf;
  378. [strongSelf pauseRealtimeStream];
  379. strongSelf->_isInBackground = true;
  380. });
  381. }
  382. #pragma mark - Autofetch Helpers
  383. - (void)fetchLatestConfig:(NSInteger)remainingAttempts targetVersion:(NSInteger)targetVersion {
  384. __weak RCNConfigRealtime *weakSelf = self;
  385. dispatch_async(_realtimeLockQueue, ^{
  386. __strong RCNConfigRealtime *strongSelf = weakSelf;
  387. NSInteger attempts = remainingAttempts - 1;
  388. [strongSelf->_configFetch
  389. realtimeFetchConfigWithNoExpirationDuration:gFetchAttempts - attempts
  390. completionHandler:^(FIRRemoteConfigFetchStatus status,
  391. FIRRemoteConfigUpdate *update,
  392. NSError *error) {
  393. if (error != nil) {
  394. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000010",
  395. @"Failed to retrieve config due to fetch error. "
  396. @"Error: %@",
  397. error);
  398. return [self propogateErrors:error];
  399. }
  400. if (status == FIRRemoteConfigFetchStatusSuccess) {
  401. if ([strongSelf->_configFetch.templateVersionNumber
  402. integerValue] >= targetVersion) {
  403. // only notify listeners if there is a change
  404. if ([update updatedKeys].count > 0) {
  405. for (RCNConfigUpdateCompletion listener in strongSelf
  406. ->_listeners) {
  407. listener(update, nil);
  408. }
  409. }
  410. } else {
  411. FIRLogDebug(
  412. kFIRLoggerRemoteConfig, @"I-RCN000016",
  413. @"Fetched config's template version is outdated, "
  414. @"re-fetching");
  415. [strongSelf autoFetch:attempts targetVersion:targetVersion];
  416. }
  417. } else {
  418. FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000016",
  419. @"Fetched config's template version is "
  420. @"outdated, re-fetching");
  421. [strongSelf autoFetch:attempts targetVersion:targetVersion];
  422. }
  423. }];
  424. });
  425. }
  426. - (void)scheduleFetch:(NSInteger)remainingAttempts targetVersion:(NSInteger)targetVersion {
  427. /// Needs fetch to occur between 0 - 3 seconds. Randomize to not cause DDoS alerts in backend
  428. dispatch_time_t executionDelay =
  429. dispatch_time(DISPATCH_TIME_NOW, arc4random_uniform(4) * NSEC_PER_SEC);
  430. dispatch_after(executionDelay, _realtimeLockQueue, ^{
  431. [self fetchLatestConfig:remainingAttempts targetVersion:targetVersion];
  432. });
  433. }
  434. /// Perform fetch and handle developers callbacks
  435. - (void)autoFetch:(NSInteger)remainingAttempts targetVersion:(NSInteger)targetVersion {
  436. __weak RCNConfigRealtime *weakSelf = self;
  437. dispatch_async(_realtimeLockQueue, ^{
  438. __strong RCNConfigRealtime *strongSelf = weakSelf;
  439. if (remainingAttempts == 0) {
  440. NSError *error = [NSError errorWithDomain:FIRRemoteConfigUpdateErrorDomain
  441. code:FIRRemoteConfigUpdateErrorNotFetched
  442. userInfo:@{
  443. NSLocalizedDescriptionKey :
  444. @"Unable to fetch the latest version of the template."
  445. }];
  446. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000011",
  447. @"Ran out of fetch attempts, cannot find target config version.");
  448. [self propogateErrors:error];
  449. return;
  450. }
  451. [strongSelf scheduleFetch:remainingAttempts targetVersion:targetVersion];
  452. });
  453. }
  454. #pragma mark - NSURLSession Delegates
  455. - (void)evaluateStreamResponse:(NSDictionary *)response error:(NSError *)dataError {
  456. NSInteger updateTemplateVersion = 1;
  457. if (dataError == nil) {
  458. if ([response objectForKey:kTemplateVersionNumberKey]) {
  459. updateTemplateVersion = [[response objectForKey:kTemplateVersionNumberKey] integerValue];
  460. }
  461. if ([response objectForKey:kIsFeatureDisabled]) {
  462. self->_isRealtimeDisabled = [response objectForKey:kIsFeatureDisabled];
  463. }
  464. if (self->_isRealtimeDisabled) {
  465. [self pauseRealtimeStream];
  466. NSError *error = [NSError
  467. errorWithDomain:FIRRemoteConfigUpdateErrorDomain
  468. code:FIRRemoteConfigUpdateErrorUnavailable
  469. userInfo:@{
  470. NSLocalizedDescriptionKey :
  471. @"The server is temporarily unavailable. Try again in a few minutes."
  472. }];
  473. [self propogateErrors:error];
  474. } else {
  475. NSInteger clientTemplateVersion = [_configFetch.templateVersionNumber integerValue];
  476. if (updateTemplateVersion > clientTemplateVersion) {
  477. [self autoFetch:gFetchAttempts targetVersion:updateTemplateVersion];
  478. }
  479. }
  480. } else {
  481. NSError *error =
  482. [NSError errorWithDomain:FIRRemoteConfigUpdateErrorDomain
  483. code:FIRRemoteConfigUpdateErrorMessageInvalid
  484. userInfo:@{NSLocalizedDescriptionKey : @"Unable to parse ConfigUpdate."}];
  485. [self propogateErrors:error];
  486. }
  487. }
  488. /// Delegate to asynchronously handle every new notification that comes over the wire. Auto-fetches
  489. /// and runs callback for each new notification
  490. - (void)URLSession:(NSURLSession *)session
  491. dataTask:(NSURLSessionDataTask *)dataTask
  492. didReceiveData:(NSData *)data {
  493. NSError *dataError;
  494. NSString *strData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  495. /// If response data contains the API enablement link, return the entire message to the user in
  496. /// the form of a error.
  497. if ([strData containsString:kServerForbiddenStatusCode]) {
  498. NSError *error = [NSError errorWithDomain:FIRRemoteConfigUpdateErrorDomain
  499. code:FIRRemoteConfigUpdateErrorStreamError
  500. userInfo:@{NSLocalizedDescriptionKey : strData}];
  501. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000021", @"Cannot establish connection. %@", error);
  502. [self propogateErrors:error];
  503. return;
  504. }
  505. NSRange endRange = [strData rangeOfString:@"}"];
  506. NSRange beginRange = [strData rangeOfString:@"{"];
  507. if (beginRange.location != NSNotFound && endRange.location != NSNotFound) {
  508. FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000015",
  509. @"Received config update message on stream.");
  510. NSRange msgRange =
  511. NSMakeRange(beginRange.location, endRange.location - beginRange.location + 1);
  512. strData = [strData substringWithRange:msgRange];
  513. data = [strData dataUsingEncoding:NSUTF8StringEncoding];
  514. NSDictionary *response = [NSJSONSerialization JSONObjectWithData:data
  515. options:NSJSONReadingMutableContainers
  516. error:&dataError];
  517. [self evaluateStreamResponse:response error:dataError];
  518. }
  519. }
  520. /// Check if response code is retryable
  521. - (bool)isStatusCodeRetryable:(NSInteger)statusCode {
  522. return statusCode == kRCNFetchResponseHTTPStatusClientTimeout ||
  523. statusCode == kRCNFetchResponseHTTPStatusTooManyRequests ||
  524. statusCode == kRCNFetchResponseHTTPStatusCodeServiceUnavailable ||
  525. statusCode == kRCNFetchResponseHTTPStatusCodeBadGateway ||
  526. statusCode == kRCNFetchResponseHTTPStatusCodeGatewayTimeout;
  527. }
  528. /// Delegate to handle initial reply from the server
  529. - (void)URLSession:(NSURLSession *)session
  530. dataTask:(NSURLSessionDataTask *)dataTask
  531. didReceiveResponse:(NSURLResponse *)response
  532. completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
  533. _isRequestInProgress = false;
  534. NSHTTPURLResponse *_httpURLResponse = (NSHTTPURLResponse *)response;
  535. NSInteger statusCode = [_httpURLResponse statusCode];
  536. if (statusCode == 403) {
  537. completionHandler(NSURLSessionResponseAllow);
  538. return;
  539. }
  540. if (statusCode != kRCNFetchResponseHTTPStatusOk) {
  541. [self->_settings updateRealtimeExponentialBackoffTime];
  542. [self pauseRealtimeStream];
  543. if ([self isStatusCodeRetryable:statusCode]) {
  544. [self retryHTTPConnection];
  545. } else {
  546. NSError *error = [NSError
  547. errorWithDomain:FIRRemoteConfigUpdateErrorDomain
  548. code:FIRRemoteConfigUpdateErrorStreamError
  549. userInfo:@{
  550. NSLocalizedDescriptionKey :
  551. [NSString stringWithFormat:@"Unable to connect to the server. Try again in "
  552. @"a few minutes. Http Status code: %@",
  553. [@(statusCode) stringValue]]
  554. }];
  555. FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000021", @"Cannot establish connection. Error: %@",
  556. error);
  557. }
  558. } else {
  559. /// on success reset retry parameters
  560. _remainingRetryCount = gMaxRetries;
  561. [self->_settings setRealtimeRetryCount:0];
  562. }
  563. completionHandler(NSURLSessionResponseAllow);
  564. }
  565. /// Delegate to handle data task completion
  566. - (void)URLSession:(NSURLSession *)session
  567. task:(NSURLSessionTask *)task
  568. didCompleteWithError:(NSError *)error {
  569. _isRequestInProgress = false;
  570. if (error != nil && [error code] != NSURLErrorCancelled) {
  571. [self->_settings updateRealtimeExponentialBackoffTime];
  572. }
  573. [self pauseRealtimeStream];
  574. [self retryHTTPConnection];
  575. }
  576. /// Delegate to handle session invalidation
  577. - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
  578. if (!_isRequestInProgress) {
  579. if (error != nil) {
  580. [self->_settings updateRealtimeExponentialBackoffTime];
  581. }
  582. [self pauseRealtimeStream];
  583. [self retryHTTPConnection];
  584. }
  585. }
  586. #pragma mark - Top level methods
  587. - (void)beginRealtimeStream {
  588. __weak __typeof(self) weakSelf = self;
  589. dispatch_async(_realtimeLockQueue, ^{
  590. __strong __typeof(self) strongSelf = weakSelf;
  591. if (strongSelf->_settings.getRealtimeBackoffInterval > 0) {
  592. [strongSelf retryHTTPConnection];
  593. return;
  594. }
  595. if ([strongSelf canMakeConnection]) {
  596. __weak __typeof(self) weakSelf = strongSelf;
  597. [strongSelf createRequestBodyWithCompletion:^(NSData *_Nonnull requestBody) {
  598. __strong __typeof(self) strongSelf = weakSelf;
  599. if (!strongSelf) return;
  600. strongSelf->_isRequestInProgress = true;
  601. [strongSelf->_request setHTTPBody:requestBody];
  602. strongSelf->_dataTask = [strongSelf->_session dataTaskWithRequest:strongSelf->_request];
  603. [strongSelf->_dataTask resume];
  604. }];
  605. }
  606. });
  607. }
  608. - (void)pauseRealtimeStream {
  609. __weak RCNConfigRealtime *weakSelf = self;
  610. dispatch_async(_realtimeLockQueue, ^{
  611. __strong RCNConfigRealtime *strongSelf = weakSelf;
  612. if (strongSelf->_dataTask != nil) {
  613. [strongSelf->_dataTask cancel];
  614. strongSelf->_dataTask = nil;
  615. }
  616. });
  617. }
  618. - (FIRConfigUpdateListenerRegistration *)addConfigUpdateListener:
  619. (void (^_Nonnull)(FIRRemoteConfigUpdate *configUpdate, NSError *_Nullable error))listener {
  620. if (listener == nil) {
  621. return nil;
  622. }
  623. __block id listenerCopy = listener;
  624. __weak RCNConfigRealtime *weakSelf = self;
  625. dispatch_async(_realtimeLockQueue, ^{
  626. __strong RCNConfigRealtime *strongSelf = weakSelf;
  627. [strongSelf->_listeners addObject:listenerCopy];
  628. [strongSelf beginRealtimeStream];
  629. });
  630. return [[FIRConfigUpdateListenerRegistration alloc] initWithClient:self
  631. completionHandler:listenerCopy];
  632. }
  633. - (void)removeConfigUpdateListener:(void (^_Nonnull)(FIRRemoteConfigUpdate *configUpdate,
  634. NSError *_Nullable error))listener {
  635. __weak RCNConfigRealtime *weakSelf = self;
  636. dispatch_async(_realtimeLockQueue, ^{
  637. __strong RCNConfigRealtime *strongSelf = weakSelf;
  638. [strongSelf->_listeners removeObject:listener];
  639. if (strongSelf->_listeners.count == 0) {
  640. [strongSelf pauseRealtimeStream];
  641. }
  642. });
  643. }
  644. @end