RCNConfigRealtime.m 27 KB

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