Эх сурвалжийг харах

Rewrite FirebaseRemoteConfig in Swift.

This commit rewrites the FirebaseRemoteConfig component from Objective-C to Swift.
The public API surface is kept the same as the original Objective-C implementation.
All the Objective-C code was removed from the component.
labs-code-app[bot] 1 жил өмнө
parent
commit
4b314d9dc2
27 өөрчлөгдсөн 1773 нэмэгдсэн , 5642 устгасан
  1. 0 91
      FirebaseRemoteConfig/Sources/FIRConfigValue.m
  2. 49 0
      FirebaseRemoteConfig/Sources/FIRConfigValue.swift
  3. 0 804
      FirebaseRemoteConfig/Sources/FIRRemoteConfig.m
  4. 262 0
      FirebaseRemoteConfig/Sources/FIRRemoteConfig.swift
  5. 0 151
      FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m
  6. 116 0
      FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.swift
  7. 0 33
      FirebaseRemoteConfig/Sources/FIRRemoteConfigUpdate.m
  8. 13 0
      FirebaseRemoteConfig/Sources/FIRRemoteConfigUpdate.swift
  9. 0 526
      FirebaseRemoteConfig/Sources/RCNConfigContent.m
  10. 445 0
      FirebaseRemoteConfig/Sources/RCNConfigContent.swift
  11. 0 1225
      FirebaseRemoteConfig/Sources/RCNConfigDBManager.m
  12. 336 0
      FirebaseRemoteConfig/Sources/RCNConfigDBManager.swift
  13. 0 200
      FirebaseRemoteConfig/Sources/RCNConfigExperiment.m
  14. 78 0
      FirebaseRemoteConfig/Sources/RCNConfigExperiment.swift
  15. 0 692
      FirebaseRemoteConfig/Sources/RCNConfigFetch.m
  16. 71 0
      FirebaseRemoteConfig/Sources/RCNConfigFetch.swift
  17. 0 734
      FirebaseRemoteConfig/Sources/RCNConfigRealtime.m
  18. 86 0
      FirebaseRemoteConfig/Sources/RCNConfigRealtime.swift
  19. 0 520
      FirebaseRemoteConfig/Sources/RCNConfigSettings.m
  20. 81 0
      FirebaseRemoteConfig/Sources/RCNConfigSettings.swift
  21. 0 21
      FirebaseRemoteConfig/Sources/RCNConstants3P.m
  22. 5 0
      FirebaseRemoteConfig/Sources/RCNConstants3P.swift
  23. 0 244
      FirebaseRemoteConfig/Sources/RCNDevice.m
  24. 199 0
      FirebaseRemoteConfig/Sources/RCNDevice.swift
  25. 0 72
      FirebaseRemoteConfig/Sources/RCNPersonalization.m
  26. 32 0
      FirebaseRemoteConfig/Sources/RCNPersonalization.swift
  27. 0 329
      FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.m

+ 0 - 91
FirebaseRemoteConfig/Sources/FIRConfigValue.m

@@ -1,91 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
-
-#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
-
-@implementation FIRRemoteConfigValue {
-  /// Data backing the config value.
-  NSData *_data;
-  FIRRemoteConfigSource _source;
-}
-
-/// Designated initializer
-- (instancetype)initWithData:(NSData *)data source:(FIRRemoteConfigSource)source {
-  self = [super init];
-  if (self) {
-    _data = [data copy];
-    _source = source;
-  }
-  return self;
-}
-
-/// Superclass's designated initializer
-- (instancetype)init {
-  return [self initWithData:nil source:FIRRemoteConfigSourceStatic];
-}
-
-/// The string is a UTF-8 representation of NSData.
-- (nonnull NSString *)stringValue {
-  return [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding] ?: @"";
-}
-
-/// Number representation of a UTF-8 string.
-- (NSNumber *)numberValue {
-  return [NSNumber numberWithDouble:self.stringValue.doubleValue];
-}
-
-/// Internal representation of the FIRRemoteConfigValue as a NSData object.
-- (NSData *)dataValue {
-  return _data;
-}
-
-/// Boolean representation of a UTF-8 string.
-- (BOOL)boolValue {
-  return self.stringValue.boolValue;
-}
-
-/// Returns a foundation object (NSDictionary / NSArray) representation for JSON data.
-- (id)JSONValue {
-  NSError *error;
-  if (!_data) {
-    return nil;
-  }
-  id JSONObject = [NSJSONSerialization JSONObjectWithData:_data options:kNilOptions error:&error];
-  if (error) {
-    FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000065", @"Error parsing data as JSON.");
-    return nil;
-  }
-  return JSONObject;
-}
-
-/// Debug description showing the representations of all types.
-- (NSString *)debugDescription {
-  NSString *content = [NSString
-      stringWithFormat:@"Boolean: %d, String: %@, Number: %@, JSON:%@, Data: %@, Source: %zd",
-                       self.boolValue, self.stringValue, self.numberValue, self.JSONValue, _data,
-                       (long)self.source];
-  return [NSString stringWithFormat:@"<%@: %p, %@>", [self class], self, content];
-}
-
-/// Copy method.
-- (id)copyWithZone:(NSZone *)zone {
-  FIRRemoteConfigValue *value = [[[self class] allocWithZone:zone] initWithData:_data];
-  return value;
-}
-@end

+ 49 - 0
FirebaseRemoteConfig/Sources/FIRConfigValue.swift

@@ -0,0 +1,49 @@
+import Foundation
+
+class FIRRemoteConfigValue {
+    private var _data: Data
+    private var _source: FIRRemoteConfigSource
+
+    init(data: Data?, source: FIRRemoteConfigSource) {
+        _data = data ?? Data()
+        _source = source
+    }
+
+    var stringValue: String {
+        return String(data: _data, encoding: .utf8) ?? ""
+    }
+
+    var numberValue: NSNumber {
+        return NSNumber(value: Double(stringValue) ?? 0)
+    }
+
+    var dataValue: Data {
+        return _data
+    }
+
+    var boolValue: Bool {
+        return self.stringValue.boolValue
+    }
+
+    func JSONValue() -> Any {
+        if let data = dataValue {
+            do {
+                return try JSONSerialization.jsonObject(data)
+            }
+            catch let error {
+                print(error)
+            }
+            
+        }
+        return nil
+    }
+
+    func debugDescription() -> String {
+        let content = String(format: "Boolean: %@, String: %@, Number: %@, JSON:%@, Data: %@, Source: %@",
+                             String(describing: boolValue), stringValue, numberValue,
+                             String(describing: JSONValue()), String(describing: dataValue),
+                             String(describing: _source))
+
+        return String(format: "<%@: %p, %@>", String(describing: Self.self), Unmanaged.passUnretained(self), content)
+    }
+}

+ 0 - 804
FirebaseRemoteConfig/Sources/FIRRemoteConfig.m

@@ -1,804 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
-
-#import "FirebaseABTesting/Sources/Private/FirebaseABTestingInternal.h"
-#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
-#import "FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.h"
-#import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
-#import "FirebaseRemoteConfig/Sources/Private/RCNConfigFetch.h"
-#import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigRealtime.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
-#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
-#import "FirebaseRemoteConfig/Sources/RCNPersonalization.h"
-
-/// Remote Config Error Domain.
-/// TODO: Rename according to obj-c style for constants.
-NSString *const FIRRemoteConfigErrorDomain = @"com.google.remoteconfig.ErrorDomain";
-// Remote Config Custom Signals Error Domain
-NSString *const FIRRemoteConfigCustomSignalsErrorDomain =
-    @"com.google.remoteconfig.customsignals.ErrorDomain";
-// Remote Config Realtime Error Domain
-NSString *const FIRRemoteConfigUpdateErrorDomain = @"com.google.remoteconfig.update.ErrorDomain";
-/// Remote Config Error Info End Time Seconds;
-NSString *const FIRRemoteConfigThrottledEndTimeInSecondsKey = @"error_throttled_end_time_seconds";
-/// Minimum required time interval between fetch requests made to the backend.
-static NSString *const kRemoteConfigMinimumFetchIntervalKey = @"_rcn_minimum_fetch_interval";
-/// Timeout value for waiting on a fetch response.
-static NSString *const kRemoteConfigFetchTimeoutKey = @"_rcn_fetch_timeout";
-/// Notification when config is successfully activated
-const NSNotificationName FIRRemoteConfigActivateNotification =
-    @"FIRRemoteConfigActivateNotification";
-static NSNotificationName FIRRolloutsStateDidChangeNotificationName =
-    @"FIRRolloutsStateDidChangeNotification";
-/// Maximum allowed length for a custom signal key (in characters).
-static const NSUInteger FIRRemoteConfigCustomSignalsMaxKeyLength = 250;
-/// Maximum allowed length for a string value in custom signals (in characters).
-static const NSUInteger FIRRemoteConfigCustomSignalsMaxStringValueLength = 500;
-/// Maximum number of custom signals allowed.
-static const NSUInteger FIRRemoteConfigCustomSignalsMaxCount = 100;
-
-/// Listener for the get methods.
-typedef void (^FIRRemoteConfigListener)(NSString *_Nonnull, NSDictionary *_Nonnull);
-
-@implementation FIRRemoteConfigSettings
-
-- (instancetype)init {
-  self = [super init];
-  if (self) {
-    _minimumFetchInterval = RCNDefaultMinimumFetchInterval;
-    _fetchTimeout = RCNHTTPDefaultConnectionTimeout;
-  }
-  return self;
-}
-
-@end
-
-@implementation FIRRemoteConfig {
-  /// All the config content.
-  RCNConfigContent *_configContent;
-  RCNConfigDBManager *_DBManager;
-  RCNConfigSettings *_settings;
-  RCNConfigFetch *_configFetch;
-  RCNConfigExperiment *_configExperiment;
-  RCNConfigRealtime *_configRealtime;
-  dispatch_queue_t _queue;
-  NSString *_appName;
-  NSMutableArray *_listeners;
-}
-
-static NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, FIRRemoteConfig *> *>
-    *RCInstances;
-
-+ (nonnull FIRRemoteConfig *)remoteConfigWithApp:(FIRApp *_Nonnull)firebaseApp {
-  return [FIRRemoteConfig
-      remoteConfigWithFIRNamespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform
-                               app:firebaseApp];
-}
-
-+ (nonnull FIRRemoteConfig *)remoteConfigWithFIRNamespace:(NSString *_Nonnull)firebaseNamespace {
-  if (![FIRApp isDefaultAppConfigured]) {
-    [NSException raise:@"FIRAppNotConfigured"
-                format:@"The default `FirebaseApp` instance must be configured before the "
-                       @"default Remote Config instance can be initialized. One way to ensure this "
-                       @"is to call `FirebaseApp.configure()` in the App Delegate's "
-                       @"`application(_:didFinishLaunchingWithOptions:)` or the `@main` struct's "
-                       @"initializer in SwiftUI."];
-  }
-
-  return [FIRRemoteConfig remoteConfigWithFIRNamespace:firebaseNamespace app:[FIRApp defaultApp]];
-}
-
-+ (nonnull FIRRemoteConfig *)remoteConfigWithFIRNamespace:(NSString *_Nonnull)firebaseNamespace
-                                                      app:(FIRApp *_Nonnull)firebaseApp {
-  // Use the provider to generate and return instances of FIRRemoteConfig for this specific app and
-  // namespace. This will ensure the app is configured before Remote Config can return an instance.
-  id<FIRRemoteConfigProvider> provider =
-      FIR_COMPONENT(FIRRemoteConfigProvider, firebaseApp.container);
-  return [provider remoteConfigForNamespace:firebaseNamespace];
-}
-
-+ (FIRRemoteConfig *)remoteConfig {
-  // If the default app is not configured at this point, warn the developer.
-  if (![FIRApp isDefaultAppConfigured]) {
-    [NSException raise:@"FIRAppNotConfigured"
-                format:@"The default `FirebaseApp` instance must be configured before the "
-                       @"default Remote Config instance can be initialized. One way to ensure this "
-                       @"is to call `FirebaseApp.configure()` in the App Delegate's "
-                       @"`application(_:didFinishLaunchingWithOptions:)` or the `@main` struct's "
-                       @"initializer in SwiftUI."];
-  }
-
-  return [FIRRemoteConfig
-      remoteConfigWithFIRNamespace:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform
-                               app:[FIRApp defaultApp]];
-}
-
-/// Singleton instance of serial queue for queuing all incoming RC calls.
-+ (dispatch_queue_t)sharedRemoteConfigSerialQueue {
-  static dispatch_once_t onceToken;
-  static dispatch_queue_t sharedRemoteConfigQueue;
-  dispatch_once(&onceToken, ^{
-    sharedRemoteConfigQueue =
-        dispatch_queue_create(RCNRemoteConfigQueueLabel, DISPATCH_QUEUE_SERIAL);
-  });
-  return sharedRemoteConfigQueue;
-}
-
-/// Designated initializer
-- (instancetype)initWithAppName:(NSString *)appName
-                     FIROptions:(FIROptions *)options
-                      namespace:(NSString *)FIRNamespace
-                      DBManager:(RCNConfigDBManager *)DBManager
-                  configContent:(RCNConfigContent *)configContent
-                      analytics:(nullable id<FIRAnalyticsInterop>)analytics {
-  self = [super init];
-  if (self) {
-    _appName = appName;
-    _DBManager = DBManager;
-    // The fully qualified Firebase namespace is namespace:firappname.
-    _FIRNamespace = [NSString stringWithFormat:@"%@:%@", FIRNamespace, appName];
-
-    // Initialize RCConfigContent if not already.
-    _configContent = configContent;
-    _settings = [[RCNConfigSettings alloc] initWithDatabaseManager:_DBManager
-                                                         namespace:_FIRNamespace
-                                                   firebaseAppName:appName
-                                                       googleAppID:options.googleAppID];
-
-    FIRExperimentController *experimentController = [FIRExperimentController sharedInstance];
-    _configExperiment = [[RCNConfigExperiment alloc] initWithDBManager:_DBManager
-                                                  experimentController:experimentController];
-    /// Serial queue for read and write lock.
-    _queue = [FIRRemoteConfig sharedRemoteConfigSerialQueue];
-
-    // Initialize with default config settings.
-    [self setDefaultConfigSettings];
-    _configFetch = [[RCNConfigFetch alloc] initWithContent:_configContent
-                                                 DBManager:_DBManager
-                                                  settings:_settings
-                                                 analytics:analytics
-                                                experiment:_configExperiment
-                                                     queue:_queue
-                                                 namespace:_FIRNamespace
-                                                   options:options];
-
-    _configRealtime = [[RCNConfigRealtime alloc] init:_configFetch
-                                             settings:_settings
-                                            namespace:_FIRNamespace
-                                              options:options];
-
-    [_settings loadConfigFromMetadataTable];
-
-    if (analytics) {
-      _listeners = [[NSMutableArray alloc] init];
-      RCNPersonalization *personalization =
-          [[RCNPersonalization alloc] initWithAnalytics:analytics];
-      [self addListener:^(NSString *key, NSDictionary *config) {
-        [personalization logArmActive:key config:config];
-      }];
-    }
-  }
-  return self;
-}
-
-// Initialize with default config settings.
-- (void)setDefaultConfigSettings {
-  // Set the default config settings.
-  self->_settings.fetchTimeout = RCNHTTPDefaultConnectionTimeout;
-  self->_settings.minimumFetchInterval = RCNDefaultMinimumFetchInterval;
-}
-
-- (void)ensureInitializedWithCompletionHandler:
-    (nonnull FIRRemoteConfigInitializationCompletion)completionHandler {
-  __weak FIRRemoteConfig *weakSelf = self;
-  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
-    FIRRemoteConfig *strongSelf = weakSelf;
-    if (!strongSelf) {
-      return;
-    }
-    BOOL initializationSuccess = [self->_configContent initializationSuccessful];
-    NSError *error = nil;
-    if (!initializationSuccess) {
-      error = [[NSError alloc]
-          initWithDomain:FIRRemoteConfigErrorDomain
-                    code:FIRRemoteConfigErrorInternalError
-                userInfo:@{NSLocalizedDescriptionKey : @"Timed out waiting for database load."}];
-    }
-    completionHandler(error);
-  });
-}
-
-/// Adds a listener that will be called whenever one of the get methods is called.
-/// @param listener Function that takes in the parameter key and the config.
-- (void)addListener:(nonnull FIRRemoteConfigListener)listener {
-  @synchronized(_listeners) {
-    [_listeners addObject:listener];
-  }
-}
-
-- (void)callListeners:(NSString *)key config:(NSDictionary *)config {
-  @synchronized(_listeners) {
-    for (FIRRemoteConfigListener listener in _listeners) {
-      dispatch_async(_queue, ^{
-        listener(key, config);
-      });
-    }
-  }
-}
-
-- (void)setCustomSignals:(nonnull NSDictionary<NSString *, NSObject *> *)customSignals
-          withCompletion:(void (^_Nullable)(NSError *_Nullable error))completionHandler {
-  void (^setCustomSignalsBlock)(void) = ^{
-    // Validate value type, and key and value length
-    for (NSString *key in customSignals) {
-      NSObject *value = customSignals[key];
-      if (![value isKindOfClass:[NSNull class]] && ![value isKindOfClass:[NSString class]] &&
-          ![value isKindOfClass:[NSNumber class]]) {
-        if (completionHandler) {
-          dispatch_async(dispatch_get_main_queue(), ^{
-            NSError *error =
-                [NSError errorWithDomain:FIRRemoteConfigCustomSignalsErrorDomain
-                                    code:FIRRemoteConfigCustomSignalsErrorInvalidValueType
-                                userInfo:@{
-                                  NSLocalizedDescriptionKey :
-                                      @"Invalid value type. Must be NSString, NSNumber or NSNull"
-                                }];
-            completionHandler(error);
-          });
-        }
-        return;
-      }
-
-      if (key.length > FIRRemoteConfigCustomSignalsMaxKeyLength ||
-          ([value isKindOfClass:[NSString class]] &&
-           [(NSString *)value length] > FIRRemoteConfigCustomSignalsMaxStringValueLength)) {
-        if (completionHandler) {
-          dispatch_async(dispatch_get_main_queue(), ^{
-            NSError *error = [NSError
-                errorWithDomain:FIRRemoteConfigCustomSignalsErrorDomain
-                           code:FIRRemoteConfigCustomSignalsErrorLimitExceeded
-                       userInfo:@{
-                         NSLocalizedDescriptionKey : [NSString
-                             stringWithFormat:@"Custom signal keys and string values must be "
-                                              @"%lu and %lu characters or less respectively.",
-                                              FIRRemoteConfigCustomSignalsMaxKeyLength,
-                                              FIRRemoteConfigCustomSignalsMaxStringValueLength]
-                       }];
-            completionHandler(error);
-          });
-        }
-        return;
-      }
-    }
-
-    // Merge new signals with existing ones, overwriting existing keys.
-    // Also, remove entries where the new value is null.
-    NSMutableDictionary<NSString *, NSString *> *newCustomSignals =
-        [[NSMutableDictionary alloc] initWithDictionary:self->_settings.customSignals];
-
-    for (NSString *key in customSignals) {
-      NSObject *value = customSignals[key];
-      if (![value isKindOfClass:[NSNull class]]) {
-        NSString *stringValue = [value isKindOfClass:[NSNumber class]]
-                                    ? [(NSNumber *)value stringValue]
-                                    : (NSString *)value;
-        [newCustomSignals setObject:stringValue forKey:key];
-      } else {
-        [newCustomSignals removeObjectForKey:key];
-      }
-    }
-
-    // Check the size limit.
-    if (newCustomSignals.count > FIRRemoteConfigCustomSignalsMaxCount) {
-      if (completionHandler) {
-        dispatch_async(dispatch_get_main_queue(), ^{
-          NSError *error = [NSError
-              errorWithDomain:FIRRemoteConfigCustomSignalsErrorDomain
-                         code:FIRRemoteConfigCustomSignalsErrorLimitExceeded
-                     userInfo:@{
-                       NSLocalizedDescriptionKey : [NSString
-                           stringWithFormat:@"Custom signals count exceeds the limit of %lu.",
-                                            FIRRemoteConfigCustomSignalsMaxCount]
-                     }];
-          completionHandler(error);
-        });
-      }
-      return;
-    }
-
-    // Update only if there are changes.
-    if (![newCustomSignals isEqualToDictionary:self->_settings.customSignals]) {
-      self->_settings.customSignals = newCustomSignals;
-    }
-    // Log the keys of the updated custom signals.
-    FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000078", @"Keys of updated custom signals: %@",
-                [newCustomSignals allKeys]);
-
-    if (completionHandler) {
-      dispatch_async(dispatch_get_main_queue(), ^{
-        completionHandler(nil);
-      });
-    }
-  };
-  dispatch_async(_queue, setCustomSignalsBlock);
-}
-
-#pragma mark - fetch
-
-- (void)fetchWithCompletionHandler:(FIRRemoteConfigFetchCompletion)completionHandler {
-  dispatch_async(_queue, ^{
-    [self fetchWithExpirationDuration:self->_settings.minimumFetchInterval
-                    completionHandler:completionHandler];
-  });
-}
-
-- (void)fetchWithExpirationDuration:(NSTimeInterval)expirationDuration
-                  completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler {
-  FIRRemoteConfigFetchCompletion completionHandlerCopy = nil;
-  if (completionHandler) {
-    completionHandlerCopy = [completionHandler copy];
-  }
-  [_configFetch fetchConfigWithExpirationDuration:expirationDuration
-                                completionHandler:completionHandlerCopy];
-}
-
-#pragma mark - fetchAndActivate
-
-- (void)fetchAndActivateWithCompletionHandler:
-    (FIRRemoteConfigFetchAndActivateCompletion)completionHandler {
-  __weak FIRRemoteConfig *weakSelf = self;
-  FIRRemoteConfigFetchCompletion fetchCompletion =
-      ^(FIRRemoteConfigFetchStatus fetchStatus, NSError *fetchError) {
-        FIRRemoteConfig *strongSelf = weakSelf;
-        if (!strongSelf) {
-          return;
-        }
-        // Fetch completed. We are being called on the main queue.
-        // If fetch is successful, try to activate the fetched config
-        if (fetchStatus == FIRRemoteConfigFetchStatusSuccess && !fetchError) {
-          [strongSelf activateWithCompletion:^(BOOL changed, NSError *_Nullable activateError) {
-            if (completionHandler) {
-              FIRRemoteConfigFetchAndActivateStatus status =
-                  activateError ? FIRRemoteConfigFetchAndActivateStatusSuccessUsingPreFetchedData
-                                : FIRRemoteConfigFetchAndActivateStatusSuccessFetchedFromRemote;
-              dispatch_async(dispatch_get_main_queue(), ^{
-                completionHandler(status, nil);
-              });
-            }
-          }];
-        } else if (completionHandler) {
-          FIRRemoteConfigFetchAndActivateStatus status =
-              fetchStatus == FIRRemoteConfigFetchStatusSuccess
-                  ? FIRRemoteConfigFetchAndActivateStatusSuccessUsingPreFetchedData
-                  : FIRRemoteConfigFetchAndActivateStatusError;
-          dispatch_async(dispatch_get_main_queue(), ^{
-            completionHandler(status, fetchError);
-          });
-        }
-      };
-  [self fetchWithCompletionHandler:fetchCompletion];
-}
-
-#pragma mark - activate
-
-typedef void (^FIRRemoteConfigActivateChangeCompletion)(BOOL changed, NSError *_Nullable error);
-
-- (void)activateWithCompletion:(FIRRemoteConfigActivateChangeCompletion)completion {
-  __weak FIRRemoteConfig *weakSelf = self;
-  void (^applyBlock)(void) = ^(void) {
-    FIRRemoteConfig *strongSelf = weakSelf;
-    if (!strongSelf) {
-      NSError *error = [NSError errorWithDomain:FIRRemoteConfigErrorDomain
-                                           code:FIRRemoteConfigErrorInternalError
-                                       userInfo:@{@"ActivationFailureReason" : @"Internal Error."}];
-      if (completion) {
-        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-          completion(NO, error);
-        });
-      }
-      FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000068", @"Internal error activating config.");
-      return;
-    }
-    // Check if the last fetched config has already been activated. Fetches with no data change are
-    // ignored.
-    if (strongSelf->_settings.lastETagUpdateTime == 0 ||
-        strongSelf->_settings.lastETagUpdateTime <= strongSelf->_settings.lastApplyTimeInterval) {
-      FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069",
-                  @"Most recently fetched config is already activated.");
-      if (completion) {
-        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-          completion(NO, nil);
-        });
-      }
-      return;
-    }
-    [strongSelf->_configContent copyFromDictionary:self->_configContent.fetchedConfig
-                                          toSource:RCNDBSourceActive
-                                      forNamespace:self->_FIRNamespace];
-    strongSelf->_settings.lastApplyTimeInterval = [[NSDate date] timeIntervalSince1970];
-    // New config has been activated at this point
-    FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", @"Config activated.");
-    [strongSelf->_configContent activatePersonalization];
-    // Update last active template version number in setting and userDefaults.
-    [strongSelf->_settings updateLastActiveTemplateVersion];
-    // Update activeRolloutMetadata
-    [strongSelf->_configContent activateRolloutMetadata:^(BOOL success) {
-      if (success) {
-        [self notifyRolloutsStateChange:strongSelf->_configContent.activeRolloutMetadata
-                          versionNumber:strongSelf->_settings.lastActiveTemplateVersion];
-      }
-    }];
-
-    // Update experiments only for 3p namespace
-    NSString *namespace = [strongSelf->_FIRNamespace
-        substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location];
-    if ([namespace isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform]) {
-      dispatch_async(dispatch_get_main_queue(), ^{
-        [self notifyConfigHasActivated];
-      });
-      [strongSelf->_configExperiment updateExperimentsWithHandler:^(NSError *_Nullable error) {
-        if (completion) {
-          dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-            completion(YES, nil);
-          });
-        }
-      }];
-    } else {
-      if (completion) {
-        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-          completion(YES, nil);
-        });
-      }
-    }
-  };
-  dispatch_async(_queue, applyBlock);
-}
-
-- (void)notifyConfigHasActivated {
-  // Need a valid google app name.
-  if (!_appName) {
-    return;
-  }
-  // The Remote Config Swift SDK will be listening for this notification so it can tell SwiftUI to
-  // update the UI.
-  NSDictionary *appInfoDict = @{kFIRAppNameKey : _appName};
-  [[NSNotificationCenter defaultCenter] postNotificationName:FIRRemoteConfigActivateNotification
-                                                      object:self
-                                                    userInfo:appInfoDict];
-}
-
-#pragma mark - helpers
-- (NSString *)fullyQualifiedNamespace:(NSString *)namespace {
-  // If this is already a fully qualified namespace, return.
-  if ([namespace rangeOfString:@":"].location != NSNotFound) {
-    return namespace;
-  }
-  NSString *fullyQualifiedNamespace = [NSString stringWithFormat:@"%@:%@", namespace, _appName];
-  return fullyQualifiedNamespace;
-}
-
-- (FIRRemoteConfigValue *)defaultValueForFullyQualifiedNamespace:(NSString *)namespace
-                                                             key:(NSString *)key {
-  FIRRemoteConfigValue *value = self->_configContent.defaultConfig[namespace][key];
-  if (!value) {
-    value = [[FIRRemoteConfigValue alloc]
-        initWithData:[NSData data]
-              source:(FIRRemoteConfigSource)FIRRemoteConfigSourceStatic];
-  }
-  return value;
-}
-
-#pragma mark - Get Config Result
-
-- (FIRRemoteConfigValue *)objectForKeyedSubscript:(NSString *)key {
-  return [self configValueForKey:key];
-}
-
-- (FIRRemoteConfigValue *)configValueForKey:(NSString *)key {
-  if (!key) {
-    return [[FIRRemoteConfigValue alloc] initWithData:[NSData data]
-                                               source:FIRRemoteConfigSourceStatic];
-  }
-  NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
-  __block FIRRemoteConfigValue *value;
-  dispatch_sync(_queue, ^{
-    value = self->_configContent.activeConfig[FQNamespace][key];
-    if (value) {
-      if (value.source != FIRRemoteConfigSourceRemote) {
-        FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000001",
-                    @"Key %@ should come from source:%zd instead coming from source: %zd.", key,
-                    (long)FIRRemoteConfigSourceRemote, (long)value.source);
-      }
-      [self callListeners:key
-                   config:[self->_configContent getConfigAndMetadataForNamespace:FQNamespace]];
-      return;
-    }
-    value = [self defaultValueForFullyQualifiedNamespace:FQNamespace key:key];
-  });
-  return value;
-}
-
-- (FIRRemoteConfigValue *)configValueForKey:(NSString *)key source:(FIRRemoteConfigSource)source {
-  if (!key) {
-    return [[FIRRemoteConfigValue alloc] initWithData:[NSData data]
-                                               source:FIRRemoteConfigSourceStatic];
-  }
-  NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
-
-  __block FIRRemoteConfigValue *value;
-  dispatch_sync(_queue, ^{
-    if (source == FIRRemoteConfigSourceRemote) {
-      value = self->_configContent.activeConfig[FQNamespace][key];
-    } else if (source == FIRRemoteConfigSourceDefault) {
-      value = self->_configContent.defaultConfig[FQNamespace][key];
-    } else {
-      value = [[FIRRemoteConfigValue alloc] initWithData:[NSData data]
-                                                  source:FIRRemoteConfigSourceStatic];
-    }
-  });
-  return value;
-}
-
-- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state
-                                  objects:(id __unsafe_unretained[])stackbuf
-                                    count:(NSUInteger)len {
-  __block NSUInteger localValue;
-  dispatch_sync(_queue, ^{
-    localValue =
-        [self->_configContent.activeConfig[self->_FIRNamespace] countByEnumeratingWithState:state
-                                                                                    objects:stackbuf
-                                                                                      count:len];
-  });
-  return localValue;
-}
-
-#pragma mark - Properties
-
-/// Last fetch completion time.
-- (NSDate *)lastFetchTime {
-  __block NSDate *fetchTime;
-  dispatch_sync(_queue, ^{
-    NSTimeInterval lastFetchTime = self->_settings.lastFetchTimeInterval;
-    fetchTime = [NSDate dateWithTimeIntervalSince1970:lastFetchTime];
-  });
-  return fetchTime;
-}
-
-- (FIRRemoteConfigFetchStatus)lastFetchStatus {
-  __block FIRRemoteConfigFetchStatus currentStatus;
-  dispatch_sync(_queue, ^{
-    currentStatus = self->_settings.lastFetchStatus;
-  });
-  return currentStatus;
-}
-
-- (NSArray *)allKeysFromSource:(FIRRemoteConfigSource)source {
-  __block NSArray *keys = [[NSArray alloc] init];
-  dispatch_sync(_queue, ^{
-    NSString *FQNamespace = [self fullyQualifiedNamespace:self->_FIRNamespace];
-    switch (source) {
-      case FIRRemoteConfigSourceDefault:
-        if (self->_configContent.defaultConfig[FQNamespace]) {
-          keys = [[self->_configContent.defaultConfig[FQNamespace] allKeys] copy];
-        }
-        break;
-      case FIRRemoteConfigSourceRemote:
-        if (self->_configContent.activeConfig[FQNamespace]) {
-          keys = [[self->_configContent.activeConfig[FQNamespace] allKeys] copy];
-        }
-        break;
-      default:
-        break;
-    }
-  });
-  return keys;
-}
-
-- (nonnull NSSet *)keysWithPrefix:(nullable NSString *)prefix {
-  __block NSMutableSet *keys = [[NSMutableSet alloc] init];
-  dispatch_sync(_queue, ^{
-    NSString *FQNamespace = [self fullyQualifiedNamespace:self->_FIRNamespace];
-    if (self->_configContent.activeConfig[FQNamespace]) {
-      NSArray *allKeys = [self->_configContent.activeConfig[FQNamespace] allKeys];
-      if (!prefix.length) {
-        keys = [NSMutableSet setWithArray:allKeys];
-      } else {
-        for (NSString *key in allKeys) {
-          if ([key hasPrefix:prefix]) {
-            [keys addObject:key];
-          }
-        }
-      }
-    }
-  });
-  return [keys copy];
-}
-
-#pragma mark - Defaults
-
-- (void)setDefaults:(NSDictionary<NSString *, NSObject *> *)defaultConfig {
-  NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
-  NSDictionary *defaultConfigCopy = [[NSDictionary alloc] init];
-  if (defaultConfig) {
-    defaultConfigCopy = [defaultConfig copy];
-  }
-  void (^setDefaultsBlock)(void) = ^(void) {
-    NSDictionary *namespaceToDefaults = @{FQNamespace : defaultConfigCopy};
-    [self->_configContent copyFromDictionary:namespaceToDefaults
-                                    toSource:RCNDBSourceDefault
-                                forNamespace:FQNamespace];
-    self->_settings.lastSetDefaultsTimeInterval = [[NSDate date] timeIntervalSince1970];
-  };
-  dispatch_async(_queue, setDefaultsBlock);
-}
-
-- (FIRRemoteConfigValue *)defaultValueForKey:(NSString *)key {
-  NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
-  __block FIRRemoteConfigValue *value;
-  dispatch_sync(_queue, ^{
-    NSDictionary *defaultConfig = self->_configContent.defaultConfig;
-    value = defaultConfig[FQNamespace][key];
-    if (value) {
-      if (value.source != FIRRemoteConfigSourceDefault) {
-        FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000002",
-                    @"Key %@ should come from source:%zd instead coming from source: %zd", key,
-                    (long)FIRRemoteConfigSourceDefault, (long)value.source);
-      }
-    }
-  });
-  return value;
-}
-
-- (void)setDefaultsFromPlistFileName:(nullable NSString *)fileName {
-  if (!fileName || fileName.length == 0) {
-    FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000037",
-                  @"The plist file '%@' could not be found by Remote Config.", fileName);
-    return;
-  }
-  NSArray *bundles = @[ [NSBundle mainBundle], [NSBundle bundleForClass:[self class]] ];
-
-  for (NSBundle *bundle in bundles) {
-    NSString *plistFile = [bundle pathForResource:fileName ofType:@"plist"];
-    // Use the first one we find.
-    if (plistFile) {
-      NSDictionary *defaultConfig = [[NSDictionary alloc] initWithContentsOfFile:plistFile];
-      if (defaultConfig) {
-        [self setDefaults:defaultConfig];
-      }
-      return;
-    }
-  }
-  FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000037",
-                @"The plist file '%@' could not be found by Remote Config.", fileName);
-}
-
-#pragma mark - custom variables
-
-- (FIRRemoteConfigSettings *)configSettings {
-  __block NSTimeInterval minimumFetchInterval = RCNDefaultMinimumFetchInterval;
-  __block NSTimeInterval fetchTimeout = RCNHTTPDefaultConnectionTimeout;
-  dispatch_sync(_queue, ^{
-    minimumFetchInterval = self->_settings.minimumFetchInterval;
-    fetchTimeout = self->_settings.fetchTimeout;
-  });
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000066",
-              @"Successfully read configSettings. Minimum Fetch Interval:%f, "
-              @"Fetch timeout: %f",
-              minimumFetchInterval, fetchTimeout);
-  FIRRemoteConfigSettings *settings = [[FIRRemoteConfigSettings alloc] init];
-  settings.minimumFetchInterval = minimumFetchInterval;
-  settings.fetchTimeout = fetchTimeout;
-  /// The NSURLSession needs to be recreated whenever the fetch timeout may be updated.
-  [_configFetch recreateNetworkSession];
-  return settings;
-}
-
-- (void)setConfigSettings:(FIRRemoteConfigSettings *)configSettings {
-  void (^setConfigSettingsBlock)(void) = ^(void) {
-    if (!configSettings) {
-      return;
-    }
-
-    self->_settings.minimumFetchInterval = configSettings.minimumFetchInterval;
-    self->_settings.fetchTimeout = configSettings.fetchTimeout;
-    /// The NSURLSession needs to be recreated whenever the fetch timeout may be updated.
-    [self->_configFetch recreateNetworkSession];
-    FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000067",
-                @"Successfully set configSettings. Minimum Fetch Interval:%f, "
-                @"Fetch timeout:%f",
-                configSettings.minimumFetchInterval, configSettings.fetchTimeout);
-  };
-  dispatch_async(_queue, setConfigSettingsBlock);
-}
-
-#pragma mark - Realtime
-
-- (FIRConfigUpdateListenerRegistration *)addOnConfigUpdateListener:
-    (void (^_Nonnull)(FIRRemoteConfigUpdate *update, NSError *_Nullable error))listener {
-  return [self->_configRealtime addConfigUpdateListener:listener];
-}
-
-#pragma mark - Rollout
-
-- (void)addRemoteConfigInteropSubscriber:(id<FIRRolloutsStateSubscriber>)subscriber {
-  [[NSNotificationCenter defaultCenter]
-      addObserverForName:FIRRolloutsStateDidChangeNotificationName
-                  object:self
-                   queue:nil
-              usingBlock:^(NSNotification *_Nonnull notification) {
-                FIRRolloutsState *rolloutsState =
-                    notification.userInfo[FIRRolloutsStateDidChangeNotificationName];
-                [subscriber rolloutsStateDidChange:rolloutsState];
-              }];
-  // Send active rollout metadata stored in persistence while app launched if there is activeConfig
-  NSString *fullyQualifiedNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
-  NSDictionary<NSString *, NSDictionary *> *activeConfig = self->_configContent.activeConfig;
-  if (activeConfig[fullyQualifiedNamespace] && activeConfig[fullyQualifiedNamespace].count > 0) {
-    [self notifyRolloutsStateChange:self->_configContent.activeRolloutMetadata
-                      versionNumber:self->_settings.lastActiveTemplateVersion];
-  }
-}
-
-- (void)notifyRolloutsStateChange:(NSArray<NSDictionary *> *)rolloutMetadata
-                    versionNumber:(NSString *)versionNumber {
-  NSArray<FIRRolloutAssignment *> *rolloutsAssignments =
-      [self rolloutsAssignmentsWith:rolloutMetadata versionNumber:versionNumber];
-  FIRRolloutsState *rolloutsState =
-      [[FIRRolloutsState alloc] initWithAssignmentList:rolloutsAssignments];
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069",
-              @"Send rollouts state notification with name %@ to RemoteConfigInterop.",
-              FIRRolloutsStateDidChangeNotificationName);
-  [[NSNotificationCenter defaultCenter]
-      postNotificationName:FIRRolloutsStateDidChangeNotificationName
-                    object:self
-                  userInfo:@{FIRRolloutsStateDidChangeNotificationName : rolloutsState}];
-}
-
-- (NSArray<FIRRolloutAssignment *> *)rolloutsAssignmentsWith:
-                                         (NSArray<NSDictionary *> *)rolloutMetadata
-                                               versionNumber:(NSString *)versionNumber {
-  NSMutableArray<FIRRolloutAssignment *> *rolloutsAssignments = [[NSMutableArray alloc] init];
-  NSString *FQNamespace = [self fullyQualifiedNamespace:_FIRNamespace];
-  for (NSDictionary *metadata in rolloutMetadata) {
-    NSString *rolloutId = metadata[RCNFetchResponseKeyRolloutID];
-    NSString *variantID = metadata[RCNFetchResponseKeyVariantID];
-    NSArray<NSString *> *affectedParameterKeys = metadata[RCNFetchResponseKeyAffectedParameterKeys];
-    if (rolloutId && variantID && affectedParameterKeys) {
-      for (NSString *key in affectedParameterKeys) {
-        FIRRemoteConfigValue *value = self->_configContent.activeConfig[FQNamespace][key];
-        if (!value) {
-          value = [self defaultValueForFullyQualifiedNamespace:FQNamespace key:key];
-        }
-        FIRRolloutAssignment *assignment =
-            [[FIRRolloutAssignment alloc] initWithRolloutId:rolloutId
-                                                  variantId:variantID
-                                            templateVersion:[versionNumber longLongValue]
-                                               parameterKey:key
-                                             parameterValue:value.stringValue];
-        [rolloutsAssignments addObject:assignment];
-      }
-    }
-  }
-  return rolloutsAssignments;
-}
-@end

+ 262 - 0
FirebaseRemoteConfig/Sources/FIRRemoteConfig.swift

@@ -0,0 +1,262 @@
+import Foundation
+import FirebaseABTesting
+import FirebaseCore
+
+class FIRRemoteConfigSettings {
+    var minimumFetchInterval: TimeInterval = RCNConstants.RCNDefaultMinimumFetchInterval
+    var fetchTimeout: TimeInterval = RCNConstants.RCNHTTPDefaultConnectionTimeout
+
+    init() {}
+}
+
+class FIRRemoteConfig {
+    static var RCInstances: [String: [String: FIRRemoteConfig]] = [:]
+    static var sharedRemoteConfigQueue: DispatchQueue = DispatchQueue(label: RCNConstants.RCNRemoteConfigQueueLabel)
+    
+    var configContent: RCNConfigContent?
+    var DBManager: RCNConfigDBManager?
+    var settings: FIRRemoteConfigSettings?
+    var configFetch: RCNConfigFetch?
+    var configExperiment: RCNConfigExperiment?
+    var configRealtime: RCNConfigRealtime?
+    var queue: DispatchQueue?
+    var appName: String?
+    var listeners: [((String, [String: Any])) -> Void]?
+
+    private var _FIRNamespace: String = ""
+    private var _options: FIROptions?
+
+    static func remoteConfig(app: FIRApp) -> FIRRemoteConfig {
+        return remoteConfig(FIRNamespace: RCNConstants.FIRNamespaceGoogleMobilePlatform, app: app)
+    }
+
+    static func remoteConfig(FIRNamespace: String) -> FIRRemoteConfig {
+        guard let app = FIRApp.defaultApp else {
+            fatalError("The default `FirebaseApp` instance must be configured before the " +
+                      "default Remote Config instance can be initialized. One way to ensure this " +
+                      "is to call `FirebaseApp.configure()` in the App Delegate's " +
+                      "`application(_:didFinishLaunchingWithOptions:)` or the `@main` struct's " +
+                      "initializer in SwiftUI.")
+        }
+        return remoteConfig(FIRNamespace: FIRNamespace, app: app)
+    }
+
+    static func remoteConfig(FIRNamespace: String, app: FIRApp) -> FIRRemoteConfig {
+      // Use the provider to generate and return instances of FIRRemoteConfig for this specific app and
+      // namespace. This will ensure the app is configured before Remote Config can return an instance.
+      guard let provider = FIRComponentContainer.shared.getComponent(FIRRemoteConfigProvider.self) else {
+        fatalError("Component not found")
+      }
+
+        return provider.remoteConfigForNamespace(firebaseNamespace: FIRNamespace)
+    }
+    
+    static func remoteConfig() -> FIRRemoteConfig {
+        guard let app = FIRApp.defaultApp else {
+            fatalError("The default `FirebaseApp` instance must be configured before the " +
+                       "default Remote Config instance can be initialized. One way to ensure this " +
+                       "is to call `FirebaseApp.configure()` in the App Delegate's " +
+                       "`application(_:didFinishLaunchingWithOptions:)` or the `@main` struct's " +
+                       "initializer in SwiftUI.")
+        }
+        return remoteConfig(FIRNamespace: RCNConstants.FIRNamespaceGoogleMobilePlatform, app: app)
+    }
+    
+    static func sharedRemoteConfigSerialQueue() -> DispatchQueue {
+        return FIRRemoteConfig.sharedRemoteConfigSerialQueue()
+    }
+
+    init(appName: String, FIRNamespace: String, options: FIROptions,
+         DBManager: RCNConfigDBManager, configContent: RCNConfigContent,
+         analytics: FIRAnalyticsInterop?) {
+        
+    }
+
+    func callListeners(key: String, config: [String: Any]) {
+        for listener in self.listeners ?? [] {
+            listener(key, config)
+        }
+    }
+
+    func setCustomSignals(customSignals: [String: NSObject], completionHandler: ((Error?) -> Void)?) {
+      // Validate value type, and key and value length
+      for (key, value) in customSignals {
+        if let value = value as? NSNull {
+          continue
+        }
+        if let value = value as? NSString {
+          if value.count > FIRRemoteConfigCustomSignalsMaxStringValueLength {
+            let error = NSError(domain: FIRRemoteConfigCustomSignalsErrorDomain, code: FIRRemoteConfigCustomSignalsErrorLimitExceeded, userInfo: nil)
+            completionHandler?(error)
+            return
+          }
+        }
+
+        if key.count > FIRRemoteConfigCustomSignalsMaxKeyLength {
+          let error = NSError(domain: FIRRemoteConfigCustomSignalsErrorDomain, code: FIRRemoteConfigCustomSignalsErrorLimitExceeded, userInfo: nil)
+          completionHandler?(error)
+          return
+        }
+
+        // Check the size limit.
+        if customSignals.count > FIRRemoteConfigCustomSignalsMaxCount {
+          let error = NSError(domain: FIRRemoteConfigCustomSignalsErrorDomain, code: FIRRemoteConfigCustomSignalsErrorLimitExceeded, userInfo: nil)
+          completionHandler?(error)
+          return
+        }
+      }
+      
+      // Merge new signals with existing ones, overwriting existing keys.
+      // Also, remove entries where the new value is null.
+      var newCustomSignals = [String: String]()
+      for (key, value) in customSignals {
+        if (value is NSNull) {
+          [newCustomSignals removeObjectForKey:key];
+        } else {
+          NSString *stringValue = nil;
+          if ([value isKindOfClass:[NSNumber class]]) {
+            stringValue = [(NSNumber *)value stringValue];
+          } else if ([value isKindOfClass:[NSString class]]) {
+            stringValue = (NSString *)value;
+          }
+          [newCustomSignals setObject:stringValue forKey:key];
+        }
+      }
+      
+      // Check if there are changes
+      if (newCustomSignals != self.settings?.customSignals) {
+        self->_settings?.customSignals = newCustomSignals;
+      }
+
+      // Log the keys of the updated custom signals.
+      FIRLogDebug(RCNRemoteConfigQueueLabel, @"I-RCN000078", "Keys of updated custom signals: %@",
+                  [newCustomSignals allKeys].joined(separator: ", "));
+
+      if (completionHandler != nil) {
+          completionHandler(nil);
+        }
+    }
+
+    func fetchWithExpirationDuration(expirationDuration: TimeInterval,
+                                     completionHandler: FIRRemoteConfigFetchCompletion?) {
+      self->_configFetch?.fetchConfigWithExpirationDuration(expirationDuration: expirationDuration,
+                                                        completionHandler: completionHandler)
+    }
+
+    func fetchWithCompletionHandler(completionHandler: FIRRemoteConfigFetchCompletion?) {
+      fetchWithExpirationDuration(expirationDuration: self.settings?.minimumFetchInterval ?? 0,
+                                  completionHandler: completionHandler)
+    }
+
+    func fetchAndActivateWithCompletionHandler(completionHandler:
+                                                 FIRRemoteConfigFetchAndActivateCompletion?) {
+      let fetchCompletion =
+          {(_ fetchStatus: FIRRemoteConfigFetchStatus, fetchError: NSError?) in
+            if fetchStatus == .success && fetchError == nil {
+              
+              [self activateWithCompletion:^(Bool changed, NSError * _Nullable activateError) {
+                if (completionHandler) {
+                  let status =
+                      activateError ? FIRRemoteConfigFetchAndActivateStatus.successUsingPreFetchedData
+                                : FIRRemoteConfigFetchAndActivateStatus.successFetchedFromRemote
+                  dispatch_async(dispatch_get_main_queue(), ^{
+                    completionHandler(status, nil);
+                  });
+                }
+              }];
+            } else if (completionHandler != nil) {
+              FIRRemoteConfigFetchAndActivateStatus status =
+                  fetchStatus == FIRRemoteConfigFetchStatus.success ?
+                      FIRRemoteConfigFetchAndActivateStatus.successUsingPreFetchedData :
+                      FIRRemoteConfigFetchAndActivateStatus.error;
+              dispatch_async(dispatch_get_main_queue(), ^{
+                completionHandler(status, fetchError);
+              });
+            }
+          };
+      [self fetchWithCompletionHandler:fetchCompletion];
+    }
+
+    func activateWithCompletion(completion: ((Bool, NSError?) -> Void)?) {
+      __weak FIRRemoteConfig *weakSelf = self;
+      let applyBlock: () -> Void = {
+        __strong FIRRemoteConfig *strongSelf = weakSelf;
+        if strongSelf == nil {
+          let error = NSError(domain: FIRRemoteConfigErrorDomain, code: FIRRemoteConfigErrorInternalError, userInfo: nil)
+          if let completion {
+            dispatch_async(DispatchQueue.global(qos: .default), ^{
+              completion(false, error);
+            });
+          }
+          FIRLogError(RCNRemoteConfigQueueLabel, @"I-RCN000068", "Internal error activating config.");
+          return;
+        }
+        // Check if the last fetched config has already been activated. Fetches with no data change
+        // are ignored.
+        if (strongSelf->_settings.lastETagUpdateTime == 0 ||
+            strongSelf->_settings.lastETagUpdateTime <= strongSelf->_settings.lastApplyTimeInterval) {
+          FIRLogDebug(RCNRemoteConfigQueueLabel, @"I-RCN000069",
+                      @"Most recently fetched config is already activated.");
+          if (completion) {
+            dispatch_async(DispatchQueue.global(qos: .default), ^{
+              completion(false, nil);
+            });
+          }
+          return;
+        }
+
+        strongSelf->_configContent?.copyFromDictionary(
+            from: strongSelf->_configContent.fetchedConfig ?? [:], toSource: .active,
+            forNamespace: strongSelf->_FIRNamespace);
+        strongSelf->_settings?.lastApplyTimeInterval =
+            [[NSDate date] timeIntervalSince1970];
+        // New config has been activated at this point
+        FIRLogDebug(RCNRemoteConfigQueueLabel, @"I-RCN000069", @"Config activated.");
+        // Update last active template version number in setting and userDefaults.
+        [strongSelf->_settings updateLastActiveTemplateVersion];
+        // Update activeRolloutMetadata
+        [strongSelf->_configContent activateRolloutMetadata:^(BOOL success) {
+          if (success) {
+            [self notifyRolloutsStateChange:strongSelf->_configContent.activeRolloutMetadata
+                              versionNumber:strongSelf->_settings.lastActiveTemplateVersion];
+          }
+        }];
+
+        // Update experiments only for 3p namespace
+        NSString *namespace =
+            [strongSelf->_FIRNamespace substringToIndex:[strongSelf->_FIRNamespace
+                                                            rangeOfString:@":"].location];
+        if ([namespace isEqualToString:RCNConstants.FIRNamespaceGoogleMobilePlatform]) {
+          dispatch_async(DispatchQueue.main, ^{
+            [self notifyConfigHasActivated];
+          });
+          [strongSelf->_configExperiment updateExperimentsWithHandler:^(NSError *_Nullable error) {
+            if (completion) {
+              dispatch_async(DispatchQueue.global(qos: .default), ^{
+                completion(true, nil);
+              });
+            }
+          }];
+        } else {
+          if (completion) {
+            dispatch_async(DispatchQueue.global(qos: .default), ^{
+              completion(true, nil);
+            });
+          }
+        }
+      };
+      dispatch_async(_queue ?? DispatchQueue.main, applyBlock);
+    }
+    
+    func notifyConfigHasActivated() {
+      // Need a valid google app name.
+      guard let appName = _appName else {
+        return
+      }
+      // The Remote Config Swift SDK will be listening for this notification so it can tell SwiftUI to
+      // update the UI.
+      let appInfoDict: [String: Any] = [kFIRAppNameKey: appName];
+      NotificationCenter.default.post(name: FIRRemoteConfigActivateNotification, object: self,
+                                     userInfo: appInfoDict)
+    }
+}

+ 0 - 151
FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.m

@@ -1,151 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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/FIRRemoteConfigComponent.h"
-
-#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
-#import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
-#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
-
-@implementation FIRRemoteConfigComponent
-
-// Because Component now need to register two protocols (provider and interop), we need a way to
-// return the same component instance for both registered protocol, this singleton pattern allow us
-// to return the same component object for both registration callback.
-static NSMutableDictionary<NSString *, FIRRemoteConfigComponent *> *_componentInstances = nil;
-
-+ (FIRRemoteConfigComponent *)getComponentForApp:(FIRApp *)app {
-  @synchronized(_componentInstances) {
-    // need to init the dictionary first
-    if (!_componentInstances) {
-      _componentInstances = [[NSMutableDictionary alloc] init];
-    }
-    if (![_componentInstances objectForKey:app.name]) {
-      _componentInstances[app.name] = [[self alloc] initWithApp:app];
-    }
-    return _componentInstances[app.name];
-  }
-  return nil;
-}
-
-+ (void)clearAllComponentInstances {
-  @synchronized(_componentInstances) {
-    [_componentInstances removeAllObjects];
-  }
-}
-
-/// Default method for retrieving a Remote Config instance, or creating one if it doesn't exist.
-- (FIRRemoteConfig *)remoteConfigForNamespace:(NSString *)remoteConfigNamespace {
-  if (!remoteConfigNamespace) {
-    // TODO: Throw an error? Return nil? What do we want to do?
-    return nil;
-  }
-
-  // Validate the required information is available.
-  FIROptions *options = self.app.options;
-  NSString *errorPropertyName;
-  if (options.googleAppID.length == 0) {
-    errorPropertyName = @"googleAppID";
-  } else if (options.GCMSenderID.length == 0) {
-    errorPropertyName = @"GCMSenderID";
-  } else if (options.projectID.length == 0) {
-    errorPropertyName = @"projectID";
-  }
-
-  if (errorPropertyName) {
-    NSString *const kFirebaseConfigErrorDomain = @"com.firebase.config";
-    [NSException
-         raise:kFirebaseConfigErrorDomain
-        format:@"%@",
-               [NSString
-                   stringWithFormat:
-                       @"Firebase Remote Config is missing the required %@ property from the "
-                       @"configured FirebaseApp and will not be able to function properly. Please "
-                       @"fix this issue to ensure that Firebase is correctly configured.",
-                       errorPropertyName]];
-  }
-
-  FIRRemoteConfig *instance = self.instances[remoteConfigNamespace];
-  if (!instance) {
-    FIRApp *app = self.app;
-    id<FIRAnalyticsInterop> analytics =
-        app.isDefaultApp ? FIR_COMPONENT(FIRAnalyticsInterop, app.container) : nil;
-    instance = [[FIRRemoteConfig alloc] initWithAppName:app.name
-                                             FIROptions:app.options
-                                              namespace:remoteConfigNamespace
-                                              DBManager:[RCNConfigDBManager sharedInstance]
-                                          configContent:[RCNConfigContent sharedInstance]
-                                              analytics:analytics];
-    self.instances[remoteConfigNamespace] = instance;
-  }
-
-  return instance;
-}
-
-/// Default initializer.
-- (instancetype)initWithApp:(FIRApp *)app {
-  self = [super init];
-  if (self) {
-    _app = app;
-    _instances = [[NSMutableDictionary alloc] initWithCapacity:1];
-  }
-  return self;
-}
-
-#pragma mark - Lifecycle
-
-+ (void)load {
-  // Register as an internal library to be part of the initialization process. The name comes from
-  // go/firebase-sdk-platform-info.
-  [FIRApp registerInternalLibrary:self withName:@"fire-rc"];
-}
-
-#pragma mark - Interoperability
-
-+ (NSArray<FIRComponent *> *)componentsToRegister {
-  FIRComponent *rcProvider = [FIRComponent
-      componentWithProtocol:@protocol(FIRRemoteConfigProvider)
-        instantiationTiming:FIRInstantiationTimingAlwaysEager
-              creationBlock:^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
-                // Cache the component so instances of Remote Config are cached.
-                *isCacheable = YES;
-                return [FIRRemoteConfigComponent getComponentForApp:container.app];
-              }];
-
-  // Unlike provider needs to setup a hard dependency on remote config, interop allows an optional
-  // dependency on RC
-  FIRComponent *rcInterop = [FIRComponent
-      componentWithProtocol:@protocol(FIRRemoteConfigInterop)
-        instantiationTiming:FIRInstantiationTimingAlwaysEager
-              creationBlock:^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
-                // Cache the component so instances of Remote Config are cached.
-                *isCacheable = YES;
-                return [FIRRemoteConfigComponent getComponentForApp:container.app];
-              }];
-  return @[ rcProvider, rcInterop ];
-}
-
-#pragma mark - Remote Config Interop Protocol
-
-- (void)registerRolloutsStateSubscriber:(id<FIRRolloutsStateSubscriber>)subscriber
-                                    for:(NSString * _Nonnull)namespace {
-  FIRRemoteConfig *instance = [self remoteConfigForNamespace:namespace];
-  [instance addRemoteConfigInteropSubscriber:subscriber];
-}
-
-@end

+ 116 - 0
FirebaseRemoteConfig/Sources/FIRRemoteConfigComponent.swift

@@ -0,0 +1,116 @@
+import Foundation
+import FirebaseCore
+class FIRRemoteConfigComponent :NSObject, FIRRemoteConfigProvider, FIRRemoteConfigInterop {
+
+    static var componentInstances = [String : FIRRemoteConfigComponent]()
+    
+    var app : FIRApp?
+    var instances : NSMutableDictionary<NSString,FIRRemoteConfig> = [:]
+
+    static func getComponent(app: FIRApp) -> FIRRemoteConfigComponent? {
+        @synchronized(componentInstances) {
+            if (componentInstances.isEmpty) {
+              componentInstances = [String : FIRRemoteConfigComponent]()
+            }
+            if (componentInstances[app.name] == nil) {
+                componentInstances[app.name] = FIRRemoteConfigComponent(app: app)
+            }
+            return componentInstances[app.name]
+        }
+        return nil
+    }
+
+    static func clearAllComponentInstances() {
+        @synchronized(componentInstances) {
+          componentInstances.removeAll()
+        }
+    }
+
+    func remoteConfigForNamespace(firebaseNamespace: String) -> FIRRemoteConfig? {
+        if firebaseNamespace.isEmpty {
+            return nil
+        }
+
+        // Validate the required information is available.
+        guard let options = self.app?.options else {
+            fatalError("The 'options' property was not available for this configuration")
+        }
+
+        if options.googleAppID.isEmpty {
+          fatalError("Firebase Remote Config is missing the required googleAppID property from the " +
+                     "configured FirebaseApp and will not be able to function properly. Please " +
+                     "fix this issue to ensure that Firebase is correctly configured.")
+        }
+        
+        if options.GCMSenderID.isEmpty {
+          fatalError("Firebase Remote Config is missing the required GCMSenderID property from the " +
+                     "configured FirebaseApp and will not be able to function properly. Please " +
+                     "fix this issue to ensure that Firebase is correctly configured.")
+        }
+
+        if options.projectID.isEmpty {
+            fatalError("Firebase Remote Config is missing the required projectID property from the " +
+                       "configured FirebaseApp and will not be able to function properly. Please " +
+                       "fix this issue to ensure that Firebase is correctly configured.")
+        }
+        
+        var instance : FIRRemoteConfig? = self.instances[firebaseNamespace]
+        if (instance == nil) {
+            let appName : String = self.app?.name ?? ""
+            let dbManager : RCNConfigDBManager = RCNConfigDBManager.sharedInstance()
+            let configContent = RCNConfigContent.sharedInstance()
+            let googleAppID = options.googleAppID ?? ""
+
+            instance = FIRRemoteConfig(appName: appName,
+                                         FIROptions: options,
+                                         namespace: FIRNamespace, DBManager: dbManager,
+                                         configContent: configContent, analytics: nil)
+            
+            self.instances[firebaseNamespace] = instance;
+        }
+
+        return instance
+    }
+
+    init(app: FIRApp) {
+        self.app = app;
+        self.instances = [:]
+    }
+
+    static func load() {
+        // Register as an internal library to be part of the initialization process. The name comes
+        // from go/firebase-sdk-platform-info.
+        FIRApp.registerInternalLibrary(self, withName: "fire-rc")
+    }
+    
+    
+    
+    static func componentsToRegister() -> [FIRComponent] {
+      let rcProvider = FIRComponent(protocol: FIRRemoteConfigProvider.self,
+                                      instantiationTiming: .alwaysEager) {
+        (container, isCacheable) -> AnyObject in
+        // Cache the component so instances of Remote Config are cached.
+        var isCacheable = true
+        return FIRRemoteConfigComponent.getComponent(app: container.app) as Any
+      }
+        
+      let rcInterop = FIRComponent(protocol: FIRRemoteConfigInterop.self,
+                                      instantiationTiming: .alwaysEager) {
+        (container, isCacheable) -> AnyObject in
+        // Cache the component so instances of Remote Config are cached.
+        var isCacheable = true
+        return FIRRemoteConfigComponent.getComponent(app: container.app) as Any
+      }
+        return [rcProvider, rcInterop]
+    }
+
+    // MARK: - Remote Config Interop Protocol
+    func registerRolloutsStateSubscriber(subscriber: FIRRolloutsStateSubscriber, for namespace: String) {
+        let instance : FIRRemoteConfig? = self.remoteConfigForNamespace(firebaseNamespace: namespace)
+      guard let instance = instance else {
+        return
+      }
+        [instance addRemoteConfigInteropSubscriber:subscriber];
+    }
+    
+}

+ 0 - 33
FirebaseRemoteConfig/Sources/FIRRemoteConfigUpdate.m

@@ -1,33 +0,0 @@
-/*
- * 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 <Foundation/Foundation.h>
-
-#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
-
-@implementation FIRRemoteConfigUpdate {
-  NSSet<NSString *> *_updatedKeys;
-}
-
-- (instancetype)initWithUpdatedKeys:(NSSet<NSString *> *)updatedKeys {
-  self = [super init];
-  if (self) {
-    _updatedKeys = [updatedKeys copy];
-  }
-  return self;
-}
-
-@end

+ 13 - 0
FirebaseRemoteConfig/Sources/FIRRemoteConfigUpdate.swift

@@ -0,0 +1,13 @@
+import Foundation
+
+class FIRRemoteConfigUpdate {
+    private var _updatedKeys: Set<String>
+    
+    init(updatedKeys: Set<String>) {
+        _updatedKeys = updatedKeys
+    }
+    
+    var updatedKeys: Set<String> {
+        return _updatedKeys
+    }
+}

+ 0 - 526
FirebaseRemoteConfig/Sources/RCNConfigContent.m

@@ -1,526 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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/RCNConfigContent.h"
-
-#import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
-#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigDefines.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
-
-#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
-
-@implementation RCNConfigContent {
-  /// Active config data that is currently used.
-  NSMutableDictionary *_activeConfig;
-  /// Pending config (aka Fetched config) data that is latest data from server that might or might
-  /// not be applied.
-  NSMutableDictionary *_fetchedConfig;
-  /// Default config provided by user.
-  NSMutableDictionary *_defaultConfig;
-  /// Active Personalization metadata that is currently used.
-  NSDictionary *_activePersonalization;
-  /// Pending Personalization metadata that is latest data from server that might or might not be
-  /// applied.
-  NSDictionary *_fetchedPersonalization;
-  /// Active Rollout metadata that is currently used.
-  NSArray<NSDictionary *> *_activeRolloutMetadata;
-  /// Pending Rollout metadata that is latest data from server that might or might not be applied.
-  NSArray<NSDictionary *> *_fetchedRolloutMetadata;
-  /// DBManager
-  RCNConfigDBManager *_DBManager;
-  /// Current bundle identifier;
-  NSString *_bundleIdentifier;
-  /// Blocks all config reads until we have read from the database. This only
-  /// potentially blocks on the first read. Should be a no-wait for all subsequent reads once we
-  /// have data read into memory from the database.
-  dispatch_group_t _dispatch_group;
-  /// Boolean indicating if initial DB load of fetched,active and default config has succeeded.
-  BOOL _isConfigLoadFromDBCompleted;
-  /// Boolean indicating that the load from database has initiated at least once.
-  BOOL _isDatabaseLoadAlreadyInitiated;
-}
-
-/// Default timeout when waiting to read data from database.
-const NSTimeInterval kDatabaseLoadTimeoutSecs = 30.0;
-
-/// Singleton instance of RCNConfigContent.
-+ (instancetype)sharedInstance {
-  static dispatch_once_t onceToken;
-  static RCNConfigContent *sharedInstance;
-  dispatch_once(&onceToken, ^{
-    sharedInstance =
-        [[RCNConfigContent alloc] initWithDBManager:[RCNConfigDBManager sharedInstance]];
-  });
-  return sharedInstance;
-}
-
-- (instancetype)init {
-  NSAssert(NO, @"Invalid initializer.");
-  return nil;
-}
-
-/// Designated initializer
-- (instancetype)initWithDBManager:(RCNConfigDBManager *)DBManager {
-  self = [super init];
-  if (self) {
-    _activeConfig = [[NSMutableDictionary alloc] init];
-    _fetchedConfig = [[NSMutableDictionary alloc] init];
-    _defaultConfig = [[NSMutableDictionary alloc] init];
-    _activePersonalization = [[NSDictionary alloc] init];
-    _fetchedPersonalization = [[NSDictionary alloc] init];
-    _activeRolloutMetadata = [[NSArray alloc] init];
-    _fetchedRolloutMetadata = [[NSArray alloc] init];
-    _bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
-    if (!_bundleIdentifier) {
-      FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038",
-                   @"Main bundle identifier is missing. Remote Config might not work properly.");
-      _bundleIdentifier = @"";
-    }
-    _DBManager = DBManager;
-    // Waits for both config and Personalization data to load.
-    _dispatch_group = dispatch_group_create();
-    [self loadConfigFromMainTable];
-  }
-  return self;
-}
-
-// Blocking call that returns true/false once database load completes / times out.
-// @return Initialization status.
-- (BOOL)initializationSuccessful {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-  BOOL isDatabaseLoadSuccessful = [self checkAndWaitForInitialDatabaseLoad];
-  return isDatabaseLoadSuccessful;
-}
-
-#pragma mark - database
-
-/// This method is only meant to be called at init time. The underlying logic will need to be
-/// reevaluated if the assumption changes at a later time.
-- (void)loadConfigFromMainTable {
-  if (!_DBManager) {
-    return;
-  }
-
-  NSAssert(!_isDatabaseLoadAlreadyInitiated, @"Database load has already been initiated");
-  _isDatabaseLoadAlreadyInitiated = true;
-
-  dispatch_group_enter(_dispatch_group);
-  [_DBManager loadMainWithBundleIdentifier:_bundleIdentifier
-                         completionHandler:^(
-                             BOOL success, NSDictionary *fetchedConfig, NSDictionary *activeConfig,
-                             NSDictionary *defaultConfig, NSDictionary *rolloutMetadata) {
-                           self->_fetchedConfig = [fetchedConfig mutableCopy];
-                           self->_activeConfig = [activeConfig mutableCopy];
-                           self->_defaultConfig = [defaultConfig mutableCopy];
-                           self->_fetchedRolloutMetadata =
-                               [rolloutMetadata[@RCNRolloutTableKeyFetchedMetadata] copy];
-                           self->_activeRolloutMetadata =
-                               [rolloutMetadata[@RCNRolloutTableKeyActiveMetadata] copy];
-                           dispatch_group_leave(self->_dispatch_group);
-                         }];
-
-  // TODO(karenzeng): Refactor personalization to be returned in loadMainWithBundleIdentifier above
-  dispatch_group_enter(_dispatch_group);
-  [_DBManager
-      loadPersonalizationWithCompletionHandler:^(
-          BOOL success, NSDictionary *fetchedPersonalization, NSDictionary *activePersonalization,
-          NSDictionary *defaultConfig, NSDictionary *rolloutMetadata) {
-        self->_fetchedPersonalization = [fetchedPersonalization copy];
-        self->_activePersonalization = [activePersonalization copy];
-        dispatch_group_leave(self->_dispatch_group);
-      }];
-}
-
-/// Update the current config result to main table.
-/// @param values Values in a row to write to the table.
-/// @param source The source the config data is coming from. It determines which table to write to.
-- (void)updateMainTableWithValues:(NSArray *)values fromSource:(RCNDBSource)source {
-  [_DBManager insertMainTableWithValues:values fromSource:source completionHandler:nil];
-}
-
-#pragma mark - update
-/// This function is for copying dictionary when user set up a default config or when user clicks
-/// activate. For now the DBSource can only be Active or Default.
-- (void)copyFromDictionary:(NSDictionary *)fromDict
-                  toSource:(RCNDBSource)DBSource
-              forNamespace:(NSString *)FIRNamespace {
-  // Make sure database load has completed.
-  [self checkAndWaitForInitialDatabaseLoad];
-  NSMutableDictionary *toDict;
-  if (!fromDict) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000007",
-                @"The source dictionary to copy from does not exist.");
-    return;
-  }
-  FIRRemoteConfigSource source = FIRRemoteConfigSourceRemote;
-  switch (DBSource) {
-    case RCNDBSourceDefault:
-      toDict = _defaultConfig;
-      source = FIRRemoteConfigSourceDefault;
-      break;
-    case RCNDBSourceFetched:
-      FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000008",
-                    @"This shouldn't happen. Destination dictionary should never be pending type.");
-      return;
-    case RCNDBSourceActive:
-      toDict = _activeConfig;
-      source = FIRRemoteConfigSourceRemote;
-      [toDict removeObjectForKey:FIRNamespace];
-      break;
-    default:
-      toDict = _activeConfig;
-      source = FIRRemoteConfigSourceRemote;
-      [toDict removeObjectForKey:FIRNamespace];
-      break;
-  }
-
-  // Completely wipe out DB first.
-  [_DBManager deleteRecordFromMainTableWithNamespace:FIRNamespace
-                                    bundleIdentifier:_bundleIdentifier
-                                          fromSource:DBSource];
-
-  toDict[FIRNamespace] = [[NSMutableDictionary alloc] init];
-  NSDictionary *config = fromDict[FIRNamespace];
-  for (NSString *key in config) {
-    if (DBSource == FIRRemoteConfigSourceDefault) {
-      NSObject *value = config[key];
-      NSData *valueData;
-      if ([value isKindOfClass:[NSData class]]) {
-        valueData = (NSData *)value;
-      } else if ([value isKindOfClass:[NSString class]]) {
-        valueData = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding];
-      } else if ([value isKindOfClass:[NSNumber class]]) {
-        NSString *strValue = [(NSNumber *)value stringValue];
-        valueData = [(NSString *)strValue dataUsingEncoding:NSUTF8StringEncoding];
-      } else if ([value isKindOfClass:[NSDate class]]) {
-        NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
-        [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
-        NSString *strValue = [dateFormatter stringFromDate:(NSDate *)value];
-        valueData = [(NSString *)strValue dataUsingEncoding:NSUTF8StringEncoding];
-      } else if ([value isKindOfClass:[NSArray class]]) {
-        NSError *error;
-        valueData = [NSJSONSerialization dataWithJSONObject:value options:0 error:&error];
-        if (error) {
-          FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000076", @"Invalid array value for key '%@'",
-                      key);
-        }
-      } else if ([value isKindOfClass:[NSDictionary class]]) {
-        NSError *error;
-        valueData = [NSJSONSerialization dataWithJSONObject:value options:0 error:&error];
-        if (error) {
-          FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000077",
-                      @"Invalid dictionary value for key '%@'", key);
-        }
-      } else {
-        continue;
-      }
-      toDict[FIRNamespace][key] = [[FIRRemoteConfigValue alloc] initWithData:valueData
-                                                                      source:source];
-      NSArray *values = @[ _bundleIdentifier, FIRNamespace, key, valueData ];
-      [self updateMainTableWithValues:values fromSource:DBSource];
-    } else {
-      FIRRemoteConfigValue *value = config[key];
-      toDict[FIRNamespace][key] = [[FIRRemoteConfigValue alloc] initWithData:value.dataValue
-                                                                      source:source];
-      NSArray *values = @[ _bundleIdentifier, FIRNamespace, key, value.dataValue ];
-      [self updateMainTableWithValues:values fromSource:DBSource];
-    }
-  }
-}
-
-- (void)updateConfigContentWithResponse:(NSDictionary *)response
-                           forNamespace:(NSString *)currentNamespace {
-  // Make sure database load has completed.
-  [self checkAndWaitForInitialDatabaseLoad];
-  NSString *state = response[RCNFetchResponseKeyState];
-
-  if (!state) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000049", @"State field in fetch response is nil.");
-    return;
-  }
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000059",
-              @"Updating config content from Response for namespace:%@ with state: %@",
-              currentNamespace, response[RCNFetchResponseKeyState]);
-
-  if ([state isEqualToString:RCNFetchResponseKeyStateNoChange]) {
-    [self handleNoChangeStateForConfigNamespace:currentNamespace];
-    return;
-  }
-
-  /// Handle empty config state
-  if ([state isEqualToString:RCNFetchResponseKeyStateEmptyConfig]) {
-    [self handleEmptyConfigStateForConfigNamespace:currentNamespace];
-    return;
-  }
-
-  /// Handle no template state.
-  if ([state isEqualToString:RCNFetchResponseKeyStateNoTemplate]) {
-    [self handleNoTemplateStateForConfigNamespace:currentNamespace];
-    return;
-  }
-
-  /// Handle update state
-  if ([state isEqualToString:RCNFetchResponseKeyStateUpdate]) {
-    [self handleUpdateStateForConfigNamespace:currentNamespace
-                                  withEntries:response[RCNFetchResponseKeyEntries]];
-    [self handleUpdatePersonalization:response[RCNFetchResponseKeyPersonalizationMetadata]];
-    [self handleUpdateRolloutFetchedMetadata:response[RCNFetchResponseKeyRolloutMetadata]];
-    return;
-  }
-}
-
-- (void)activatePersonalization {
-  _activePersonalization = _fetchedPersonalization;
-  [_DBManager insertOrUpdatePersonalizationConfig:_activePersonalization
-                                       fromSource:RCNDBSourceActive];
-}
-
-- (void)activateRolloutMetadata:(void (^)(BOOL success))completionHandler {
-  _activeRolloutMetadata = _fetchedRolloutMetadata;
-  [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyActiveMetadata
-                                          value:_activeRolloutMetadata
-                              completionHandler:^(BOOL success, NSDictionary *result) {
-                                completionHandler(success);
-                              }];
-}
-
-#pragma mark State handling
-- (void)handleNoChangeStateForConfigNamespace:(NSString *)currentNamespace {
-  if (!_fetchedConfig[currentNamespace]) {
-    _fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
-  }
-}
-
-- (void)handleEmptyConfigStateForConfigNamespace:(NSString *)currentNamespace {
-  if (_fetchedConfig[currentNamespace]) {
-    [_fetchedConfig[currentNamespace] removeAllObjects];
-  } else {
-    // If namespace has empty status and it doesn't exist in _fetchedConfig, we will
-    // still add an entry for that namespace. Even if it will not be persisted in database.
-    // TODO: Add generics for all collection types.
-    _fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
-  }
-  [_DBManager deleteRecordFromMainTableWithNamespace:currentNamespace
-                                    bundleIdentifier:_bundleIdentifier
-                                          fromSource:RCNDBSourceFetched];
-}
-
-- (void)handleNoTemplateStateForConfigNamespace:(NSString *)currentNamespace {
-  // Remove the namespace.
-  [_fetchedConfig removeObjectForKey:currentNamespace];
-  [_DBManager deleteRecordFromMainTableWithNamespace:currentNamespace
-                                    bundleIdentifier:_bundleIdentifier
-                                          fromSource:RCNDBSourceFetched];
-}
-- (void)handleUpdateStateForConfigNamespace:(NSString *)currentNamespace
-                                withEntries:(NSDictionary *)entries {
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058", @"Update config in DB for namespace:%@",
-              currentNamespace);
-  // Clear before updating
-  [_DBManager deleteRecordFromMainTableWithNamespace:currentNamespace
-                                    bundleIdentifier:_bundleIdentifier
-                                          fromSource:RCNDBSourceFetched];
-  if ([_fetchedConfig objectForKey:currentNamespace]) {
-    [_fetchedConfig[currentNamespace] removeAllObjects];
-  } else {
-    _fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
-  }
-
-  // Store the fetched config values.
-  for (NSString *key in entries) {
-    NSData *valueData = [entries[key] dataUsingEncoding:NSUTF8StringEncoding];
-    if (!valueData) {
-      continue;
-    }
-    _fetchedConfig[currentNamespace][key] =
-        [[FIRRemoteConfigValue alloc] initWithData:valueData source:FIRRemoteConfigSourceRemote];
-    NSArray *values = @[ _bundleIdentifier, currentNamespace, key, valueData ];
-    [self updateMainTableWithValues:values fromSource:RCNDBSourceFetched];
-  }
-}
-
-- (void)handleUpdatePersonalization:(NSDictionary *)metadata {
-  if (!metadata) {
-    return;
-  }
-  _fetchedPersonalization = metadata;
-  [_DBManager insertOrUpdatePersonalizationConfig:metadata fromSource:RCNDBSourceFetched];
-}
-
-- (void)handleUpdateRolloutFetchedMetadata:(NSArray<NSDictionary *> *)metadata {
-  if (!metadata) {
-    metadata = [[NSArray alloc] init];
-  }
-  _fetchedRolloutMetadata = metadata;
-  [_DBManager insertOrUpdateRolloutTableWithKey:@RCNRolloutTableKeyFetchedMetadata
-                                          value:metadata
-                              completionHandler:nil];
-}
-
-#pragma mark - getter/setter
-- (NSDictionary *)fetchedConfig {
-  /// If this is the first time reading the fetchedConfig, we might still be reading it from the
-  /// database.
-  [self checkAndWaitForInitialDatabaseLoad];
-  return _fetchedConfig;
-}
-
-- (NSDictionary *)activeConfig {
-  /// If this is the first time reading the activeConfig, we might still be reading it from the
-  /// database.
-  [self checkAndWaitForInitialDatabaseLoad];
-  return _activeConfig;
-}
-
-- (NSDictionary *)defaultConfig {
-  /// If this is the first time reading the fetchedConfig, we might still be reading it from the
-  /// database.
-  [self checkAndWaitForInitialDatabaseLoad];
-  return _defaultConfig;
-}
-
-- (NSDictionary *)activePersonalization {
-  [self checkAndWaitForInitialDatabaseLoad];
-  return _activePersonalization;
-}
-
-- (NSArray<NSDictionary *> *)activeRolloutMetadata {
-  [self checkAndWaitForInitialDatabaseLoad];
-  return _activeRolloutMetadata;
-}
-
-- (NSDictionary *)getConfigAndMetadataForNamespace:(NSString *)FIRNamespace {
-  /// If this is the first time reading the active metadata, we might still be reading it from the
-  /// database.
-  [self checkAndWaitForInitialDatabaseLoad];
-  return @{
-    RCNFetchResponseKeyEntries : _activeConfig[FIRNamespace],
-    RCNFetchResponseKeyPersonalizationMetadata : _activePersonalization
-  };
-}
-
-/// We load the database async at init time. Block all further calls to active/fetched/default
-/// configs until load is done.
-/// @return Database load completion status.
-- (BOOL)checkAndWaitForInitialDatabaseLoad {
-  /// Wait until load is done. This should be a no-op for subsequent calls.
-  if (!_isConfigLoadFromDBCompleted) {
-    intptr_t isErrorOrTimeout = dispatch_group_wait(
-        _dispatch_group,
-        dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kDatabaseLoadTimeoutSecs * NSEC_PER_SEC)));
-    if (isErrorOrTimeout) {
-      FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000048",
-                  @"Timed out waiting for fetched config to be loaded from DB");
-      return false;
-    }
-    _isConfigLoadFromDBCompleted = true;
-  }
-  return true;
-}
-
-// Compare fetched config with active config and output what has changed
-- (FIRRemoteConfigUpdate *)getConfigUpdateForNamespace:(NSString *)FIRNamespace {
-  // TODO: handle diff in experiment metadata
-
-  FIRRemoteConfigUpdate *configUpdate;
-  NSMutableSet<NSString *> *updatedKeys = [[NSMutableSet alloc] init];
-
-  NSDictionary *fetchedConfig =
-      _fetchedConfig[FIRNamespace] ? _fetchedConfig[FIRNamespace] : [[NSDictionary alloc] init];
-  NSDictionary *activeConfig =
-      _activeConfig[FIRNamespace] ? _activeConfig[FIRNamespace] : [[NSDictionary alloc] init];
-  NSDictionary *fetchedP13n = _fetchedPersonalization;
-  NSDictionary *activeP13n = _activePersonalization;
-  NSArray<NSDictionary *> *fetchedRolloutMetadata = _fetchedRolloutMetadata;
-  NSArray<NSDictionary *> *activeRolloutMetadata = _activeRolloutMetadata;
-
-  // add new/updated params
-  for (NSString *key in [fetchedConfig allKeys]) {
-    if (activeConfig[key] == nil ||
-        ![[activeConfig[key] stringValue] isEqualToString:[fetchedConfig[key] stringValue]]) {
-      [updatedKeys addObject:key];
-    }
-  }
-  // add deleted params
-  for (NSString *key in [activeConfig allKeys]) {
-    if (fetchedConfig[key] == nil) {
-      [updatedKeys addObject:key];
-    }
-  }
-
-  // add params with new/updated p13n metadata
-  for (NSString *key in [fetchedP13n allKeys]) {
-    if (activeP13n[key] == nil || ![activeP13n[key] isEqualToDictionary:fetchedP13n[key]]) {
-      [updatedKeys addObject:key];
-    }
-  }
-  // add params with deleted p13n metadata
-  for (NSString *key in [activeP13n allKeys]) {
-    if (fetchedP13n[key] == nil) {
-      [updatedKeys addObject:key];
-    }
-  }
-
-  NSDictionary<NSString *, NSDictionary *> *fetchedRollouts =
-      [self getParameterKeyToRolloutMetadata:fetchedRolloutMetadata];
-  NSDictionary<NSString *, NSDictionary *> *activeRollouts =
-      [self getParameterKeyToRolloutMetadata:activeRolloutMetadata];
-
-  // add params with new/updated rollout metadata
-  for (NSString *key in [fetchedRollouts allKeys]) {
-    if (activeRollouts[key] == nil ||
-        ![activeRollouts[key] isEqualToDictionary:fetchedRollouts[key]]) {
-      [updatedKeys addObject:key];
-    }
-  }
-  // add params with deleted rollout metadata
-  for (NSString *key in [activeRollouts allKeys]) {
-    if (fetchedRollouts[key] == nil) {
-      [updatedKeys addObject:key];
-    }
-  }
-
-  configUpdate = [[FIRRemoteConfigUpdate alloc] initWithUpdatedKeys:updatedKeys];
-  return configUpdate;
-}
-
-- (NSDictionary<NSString *, NSDictionary *> *)getParameterKeyToRolloutMetadata:
-    (NSArray<NSDictionary *> *)rolloutMetadata {
-  NSMutableDictionary<NSString *, NSMutableDictionary *> *result =
-      [[NSMutableDictionary alloc] init];
-  for (NSDictionary *metadata in rolloutMetadata) {
-    NSString *rolloutId = metadata[RCNFetchResponseKeyRolloutID];
-    NSString *variantId = metadata[RCNFetchResponseKeyVariantID];
-    NSArray<NSString *> *affectedKeys = metadata[RCNFetchResponseKeyAffectedParameterKeys];
-    if (rolloutId && variantId && affectedKeys) {
-      for (NSString *key in affectedKeys) {
-        if (result[key]) {
-          NSMutableDictionary *rolloutIdToVariantId = result[key];
-          [rolloutIdToVariantId setValue:variantId forKey:rolloutId];
-        } else {
-          NSMutableDictionary *rolloutIdToVariantId = [@{rolloutId : variantId} mutableCopy];
-          [result setValue:rolloutIdToVariantId forKey:key];
-        }
-      }
-    }
-  }
-  return [result copy];
-}
-
-@end

+ 445 - 0
FirebaseRemoteConfig/Sources/RCNConfigContent.swift

@@ -0,0 +1,445 @@
+import Foundation
+
+class RCNConfigContent {
+    /// Active config data that is currently used.
+    private var _activeConfig: NSMutableDictionary
+    /// Pending config (aka Fetched config) data that is latest data from server that might or might
+    /// not be applied.
+    private var _fetchedConfig: NSMutableDictionary
+    /// Default config provided by user.
+    private var _defaultConfig: NSMutableDictionary
+    /// Active Personalization metadata that is currently used.
+    private var _activePersonalization: NSDictionary
+    /// Pending Personalization metadata that is latest data from server that might or might not be
+    /// applied.
+    private var _fetchedPersonalization: NSDictionary
+    /// Active Rollout metadata that is currently used.
+    private var _activeRolloutMetadata: [NSDictionary]
+    /// Pending Rollout metadata that is latest data from server that might or might not be applied.
+    private var _fetchedRolloutMetadata: [NSDictionary]
+    /// DBManager
+    private var _DBManager: RCNConfigDBManager?
+    /// Current bundle identifier;
+    private var _bundleIdentifier: String
+    /// Blocks all config reads until we have read from the database. This only
+    /// potentially blocks on the first read. Should be a no-wait for all subsequent reads once we
+    /// have data read into memory from the database.
+    private var _dispatch_group: DispatchGroup
+    /// Boolean indicating if initial DB load of fetched,active and default config has succeeded.
+    private var _isConfigLoadFromDBCompleted: Bool
+    /// Boolean indicating that the load from database has initiated at least once.
+    private var _isDatabaseLoadAlreadyInitiated: Bool
+
+    static let sharedInstance = RCNConfigContent(DBManager: RCNConfigDBManager.sharedInstance())
+
+    init(DBManager: RCNConfigDBManager) {
+        _activeConfig = NSMutableDictionary()
+        _fetchedConfig = NSMutableDictionary()
+        _defaultConfig = NSMutableDictionary()
+        _activePersonalization = [:]
+        _fetchedPersonalization = [:]
+        _activeRolloutMetadata = []
+        _fetchedRolloutMetadata = []
+        
+        _bundleIdentifier = Bundle.main.bundleIdentifier ?? ""
+        if _bundleIdentifier == "" {
+          FIRLogNotice(RCNRemoteConfigQueueLabel, @"I-RCN000038",
+                       "Main bundle identifier is missing. Remote Config might not work properly.")
+        }
+        _DBManager = DBManager
+        // Waits for both config and Personalization data to load.
+        _dispatch_group = DispatchGroup()
+        
+        loadConfigFromMainTable()
+    }
+
+    // MARK: - Database
+    func loadConfigFromMainTable() {
+        if _DBManager == nil {
+            return
+        }
+      
+        NSAssert(!_isDatabaseLoadAlreadyInitiated, "Database load has already been initiated")
+        _isDatabaseLoadAlreadyInitiated = true
+
+        _dispatch_group.enter()
+        _DBManager?.loadMain(bundleIdentifier: _bundleIdentifier ?? "") {
+            success, fetchedConfig, activeConfig, defaultConfig, rolloutMetadata in
+            self.fetchedConfig = fetchedConfig?.mutableCopy() ?? NSMutableDictionary()
+            self.activeConfig = activeConfig?.mutableCopy() ?? NSMutableDictionary()
+            self.defaultConfig = defaultConfig?.mutableCopy() ?? NSMutableDictionary()
+            self->_fetchedRolloutMetadata = rolloutMetadata[RCNRolloutTableKeyFetchedMetadata] as? [NSDictionary] ?? []
+            self->_activeRolloutMetadata = rolloutMetadata[RCNRolloutTableKeyActiveMetadata] as? [NSDictionary] ?? []
+            self->_isConfigLoadFromDBCompleted = true
+            self->_isDatabaseLoadAlreadyInitiated = true
+
+            self.loadPersonalization(completionHandler: {(success, fetchedPersonalization, activePersonalization,
+                                                        defaultConfig, rolloutsMetadata) in
+                self->_fetchedPersonalization = fetchedPersonalization ?? [:]
+                self->_activePersonalization = activePersonalization ?? [:]
+            })
+            _dispatch_group.leave()
+        }
+        
+        
+    }
+    
+    func copyFromDictionary(from fromDict: [String : Any], toSource source: RCNDBSource,
+                            forNamespace namespace: String) {
+        // Make sure database load has completed.
+        checkAndWaitForInitialDatabaseLoad()
+      
+        var toDict = NSMutableDictionary()
+        var source : FIRRemoteConfigSource = .remote
+        switch source {
+            case .active:
+              toDict = _activeConfig
+                source = .remote
+                // Completely wipe out DB first.
+                _DBManager?.deleteRecordFromMainTable(namespace: namespace, bundleIdentifier: self.bundleIdentifier ?? "", fromSource: .active)
+                break
+            case .`default`:
+              toDict = _defaultConfig
+              source = .default
+                break
+            default:
+              toDict = _activeConfig
+              source = .remote
+              break
+        }
+      
+        toDict[FIRNamespace] = [:]
+        let config = fromDict[FIRNamespace] as! [String : Any]
+        for key in config {
+            if (source == .default) {
+                let value = config[key] as! NSObject
+                var valueData: NSData? = nil
+                if let value = value as? NSData {
+                  valueData = value
+                } else if let value = value as? String {
+                  valueData = value.data(using: .utf8)
+                } else if let value = value as? NSNumber {
+                  valueData = [(NSNumber *)value stringValue].data(using: .utf8)
+                } else if let value = value as? NSDate {
+                  let dateFormatter = DateFormatter()
+                  dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
+                  let strValue = dateFormatter.string(from: value as! Date)
+                  valueData = strValue.data(using: .utf8)
+                } else if let value = value as? NSArray {
+                  do {
+                    valueData = try JSONSerialization.data(withJSONObject: value, options: [])
+                  } catch {
+                      print("invalid array value for key \(key)")
+                    }
+                } else if let value = value as? NSDictionary {
+                  do {
+                    valueData = try JSONSerialization.data(withJSONObject: value, options: [])
+                  } catch {
+                      print("invalid dictionary value for key \(key)")
+                  }
+                } else {
+                    continue
+                }
+                
+                _fetchedConfig[FIRNamespace][key] = FIRRemoteConfigValue(data: valueData, source: source)
+                let values = [_bundleIdentifier, FIRNamespace, key, valueData] as [Any]
+                //[self updateMainTableWithValues:values fromSource:DBSource]
+            } else {
+                guard let value = config[key] as? FIRRemoteConfigValue else {
+                    continue
+                }
+                _fetchedConfig[FIRNamespace][key] =
+                FIRRemoteConfigValue(data: value.dataValue, source: source)
+                let values = [_bundleIdentifier, FIRNamespace, key, value.dataValue] as [Any]
+                
+            }
+        }
+
+        
+    }
+    
+    func checkAndWaitForInitialDatabaseLoad() -> Bool {
+      RCN_MUST_NOT_BE_MAIN_THREAD()
+      
+        // Block all further calls to active/fetched/default
+        // configs until load is done.
+        if (!_isConfigLoadFromDBCompleted) {
+            let _ = _dispatch_group.wait(timeout: .now() + kDatabaseLoadTimeoutSecs)
+        }
+        
+        _isConfigLoadFromDBCompleted = true;
+        return true
+    }
+    
+    //MARK - DB
+    
+    func updateMainTableWithValues(values: [Any], fromSource: RCNDBSource) {
+       _DBManager?.insertMainTableWithValues(values: values, fromSource: source, completionHandler: nil)
+    }
+    
+    //MARK - Update
+    func copyFromDictionary(from fromDict: [String : Any], toSource toSource: RCNDBSource, forNamespace: String) {
+        if !fromDict.isEmpty {
+            FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000007",
+                        "The source dictionary to copy from does not exist.")
+            return;
+        }
+        
+        var toDict = NSMutableDictionary()
+        var source : FIRRemoteConfigSource = .remote
+        switch toSource {
+            case .default:
+                toDict = _defaultConfig;
+                break;
+            case .fetched:
+                FIRLogWarning(RCNRemoteConfigQueueLabel, "I-RCN000008",
+                              "This shouldn't happen. Destination dictionary should never be pending type.")
+                return;
+            case .active:
+                toDict = _activeConfig;
+                [toDict removeAllObjects];
+                break;
+            default:
+                toDict = _activeConfig;
+                [toDict removeAllObjects];
+                break;
+        }
+    }
+
+    func updateConfigContentWithResponse(response: [String : Any], forNamespace currentNamespace: String) {
+        // Make sure database load has completed.
+        checkAndWaitForInitialDatabaseLoad()
+        guard let state = response[RCNFetchResponseKeyState] as? String else {
+            return
+        }
+
+        FIRLogDebug(RCNRemoteConfigQueueLabel, "I-RCN000059",
+                    "Updating config content from Response for namespace:\(currentNamespace) with state: %@",
+                    response[RCNFetchResponseKeyState] ?? "")
+
+        if state == RCNFetchResponseKeyStateNoChange {
+            handleNoChangeStateForConfigNamespace(currentNamespace: currentNamespace)
+        } else if state == RCNFetchResponseKeyStateEmptyConfig {
+            handleEmptyConfigStateForConfigNamespace(currentNamespace: currentNamespace)
+            return;
+        } else if ([state isEqualToString:RCNFetchResponseKeyStateNoTemplate]) {
+          handleNoTemplateStateForConfigNamespace(currentNamespace: currentNamespace)
+          return;
+        } else if ([state isEqualToString:RCNFetchResponseKeyStateUpdate]) {
+          handleUpdateStateForConfigNamespace(currentNamespace: currentNamespace, withEntries: response[RCNFetchResponseKeyEntries] as! [String : String])
+          handleUpdatePersonalization(metadata: response[RCNFetchResponseKeyPersonalizationMetadata] as? [String : Any])
+          handleUpdateRolloutFetchedMetadata(metadata: response[RCNFetchResponseKeyRolloutMetadata] as? [NSDictionary])
+          return;
+        }
+      return;
+    }
+  
+    // MARK: - Private
+    
+    func handleNoChangeStateForConfigNamespace(currentNamespace: String) {
+        if _fetchedConfig[currentNamespace] == nil {
+            _fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
+        }
+    }
+
+    func handleEmptyConfigStateForConfigNamespace(currentNamespace: String) {
+        if (_fetchedConfig[currentNamespace] != nil) {
+            _fetchedConfig[currentNamespace].removeAllObjects()
+        } else {
+          // If namespace has empty status and it doesn't exist in _fetchedConfig, we will
+          // still add an entry for that namespace. Even if it will not be persisted in database.
+          // TODO: Add generics for all collection types.
+          _fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
+        }
+      _DBManager?.deleteRecordFromMainTable(namespace: currentNamespace,
+                                             bundleIdentifier: _bundleIdentifier,
+                                             fromSource: .fetched);
+    }
+
+    func handleNoTemplateStateForConfigNamespace(currentNamespace: String) {
+      // Remove the namespace.
+      _fetchedConfig.removeValue(forKey: currentNamespace)
+      _DBManager?.deleteRecordFromMainTable(namespace: currentNamespace,
+                                             bundleIdentifier: _bundleIdentifier,
+                                             fromSource: .fetched);
+    }
+
+    func handleUpdateStateForConfigNamespace(currentNamespace: String, withEntries: [String: String]) {
+        FIRLogDebug(RCNRemoteConfigQueueLabel, "I-RCN000058", "Update config in DB for namespace:\(currentNamespace)");
+        // Clear before updating
+        _DBManager?.deleteRecordFromMainTable(namespace: currentNamespace,
+                                               bundleIdentifier: _bundleIdentifier,
+                                               fromSource: .fetched);
+        if (_fetchedConfig[currentNamespace] != nil) {
+            _fetchedConfig[currentNamespace].removeAllObjects();
+        } else {
+            _fetchedConfig[currentNamespace] = [[NSMutableDictionary alloc] init];
+        }
+
+        // Store the fetched config values.
+        for key in entries.keys {
+            let valueData = entries[key]?.data(using: .utf8)
+            _fetchedConfig[currentNamespace][key] = FIRRemoteConfigValue(data: valueData, source: .remote)
+            let values = [_bundleIdentifier, FIRNamespace, key, valueData] as [Any];
+        }
+    }
+
+    func handleUpdatePersonalization(metadata: [String : Any]?) {
+      if metadata == nil {
+        return;
+      }
+      _fetchedPersonalization = metadata ?? [:]
+      [_DBManager insertOrUpdatePersonalizationConfig:metadata ?? [:] fromSource: .fetched]
+    }
+
+    func handleUpdateRolloutFetchedMetadata(metadata: [NSDictionary]?) {
+      if (metadata == nil) {
+        return
+      }
+        _fetchedRolloutMetadata = metadata ?? []
+        [_DBManager insertOrUpdateRolloutTableWithKey:RCNRolloutTableKeyFetchedMetadata
+                                            value:_fetchedRolloutMetadata completionHandler:nil];
+    }
+    
+    func initializationSuccessful() -> Bool{
+        return true
+    }
+    
+    //MARK - Get Config
+    
+    func defaultValueForFullyQualifiedNamespace(namespace:String, key:String) -> FIRRemoteConfigValue{
+        let value = self.defaultConfig[namespace]?[key];
+        if value == nil {
+            return FIRRemoteConfigValue(data: Data(), source: .static);
+        }
+        return value ?? FIRRemoteConfigValue(data: Data(), source: .static)
+    }
+
+    func checkAndWaitForInitialDatabaseLoad() -> Bool {
+        /// Wait until load is done. This should be a no-op for subsequent calls.
+        if (!_isConfigLoadFromDBCompleted) {
+          let _ = _dispatch_group.wait(timeout: DispatchTime.now() + kDatabaseLoadTimeoutSecs)
+          // Wait until load is done. This should be a no-op for subsequent calls.
+          //_isConfigLoadFromDBCompleted = true
+        }
+        return true
+    }
+    
+    //MARK - update main table
+    func updateMainTableWithValues(values: [Any], fromSource: RCNDBSource) {
+        _DBManager?.insertMainTableWithValues(values: values, fromSource: .fetched, completionHandler: nil)
+    }
+    
+    
+    func updateConfigContent(response: [String : Any], forNamespace currentNamespace: String) {
+        // Make sure database load has completed.
+        checkAndWaitForInitialDatabaseLoad()
+        guard let state = response[RCNFetchResponseKeyState] as? String else {
+            return
+        }
+      
+        FIRLogDebug(RCNRemoteConfigQueueLabel, "I-RCN000059",
+                    "Updating config content from Response for namespace:\(currentNamespace) with state: %@",
+                    response[RCNFetchResponseKeyState] ?? "")
+
+        if state == RCNFetchResponseKeyStateNoChange {
+            handleNoChangeStateForConfigNamespace(currentNamespace: currentNamespace)
+        } else if state == RCNFetchResponseKeyStateEmptyConfig {
+          handleEmptyConfigStateForConfigNamespace(currentNamespace: currentNamespace)
+          return;
+        } else if ([state isEqualToString:RCNFetchResponseKeyStateNoTemplate]) {
+          handleNoTemplateStateForConfigNamespace(currentNamespace: currentNamespace)
+          return;
+        } else if ([state isEqualToString:RCNFetchResponseKeyStateUpdate]) {
+          handleUpdateStateForConfigNamespace(currentNamespace: currentNamespace,
+                                              withEntries: response[RCNFetchResponseKeyEntries] as! [String : String])
+          handleUpdatePersonalization(metadata: response[RCNFetchResponseKeyPersonalizationMetadata] as? [String : Any])
+          handleUpdateRolloutFetchedMetadata(metadata: response[RCNFetchResponseKeyRolloutMetadata] as? [NSDictionary])
+          return;
+        }
+    }
+    
+    func handleNoChangeStateForConfigNamespace(currentNamespace: String) {
+        if _fetchedConfig[currentNamespace] == nil {
+          _fetchedConfig[currentNamespace] = NSMutableDictionary()
+        }
+    }
+
+    func handleEmptyConfigStateForConfigNamespace(currentNamespace: String) {
+        if _fetchedConfig[currentNamespace] != nil {
+            _fetchedConfig[currentNamespace].removeAllObjects()
+        } else {
+          // If namespace has empty status and it doesn't exist in _fetchedConfig, we will
+          // still add an entry for that namespace. Even if it will not be persisted in database.
+          // TODO: Add generics for all collection types.
+          _fetchedConfig[currentNamespace] = NSMutableDictionary()
+        }
+        _DBManager?.deleteRecordFromMainTable(namespace: currentNamespace, bundleIdentifier: _bundleIdentifier, fromSource: .fetched)
+    }
+
+    func handleNoTemplateStateForConfigNamespace(currentNamespace: String) {
+      // Remove the namespace.
+      _fetchedConfig.removeValue(forKey: currentNamespace)
+      _DBManager?.deleteRecordFromMainTable(namespace: currentNamespace,
+                                             bundleIdentifier: _bundleIdentifier,
+                                             fromSource: .fetched);
+    }
+
+    func handleUpdateStateForConfigNamespace(currentNamespace: String, withEntries: [String: String]) {
+      FIRLogDebug(RCNRemoteConfigQueueLabel, "I-RCN000058", "Update config in DB for namespace:\(currentNamespace)");
+      // Clear before updating
+      _DBManager?.deleteRecordFromMainTable(namespace: currentNamespace,
+                                             bundleIdentifier: _bundleIdentifier, fromSource: .fetched);
+        if _fetchedConfig[currentNamespace] != nil) {
+            _fetchedConfig[currentNamespace].removeAllObjects()
+        } else {
+            _fetchedConfig[currentNamespace] = NSMutableDictionary()
+        }
+
+      // Store the fetched config values.
+      for key in entries.keys {
+        let valueData = entries[key]?.data(using: .utf8)
+        _fetchedConfig[currentNamespace][key] = FIRRemoteConfigValue(data: valueData, source: .remote)
+        let values = [_bundleIdentifier, FIRNamespace, key, valueData] as [Any];
+      }
+    }
+
+    func handleUpdatePersonalization(metadata: [String : Any]?) {
+        _fetchedPersonalization = metadata ?? [:]
+        _DBManager?.insertOrUpdatePersonalizationConfig(metadata ?? [:], fromSource: .fetched)
+    }
+    
+    func handleUpdateRolloutFetchedMetadata(metadata: [NSDictionary]?) {
+        if (metadata == nil) {
+            return
+        }
+        _fetchedRolloutMetadata = metadata ?? []
+        [_DBManager insertOrUpdateRolloutTableWithKey:RCNRolloutTableKeyFetchedMetadata
+                                         value:_fetchedRolloutMetadata completionHandler:nil];
+    }
+    
+    func initializationSuccessful() -> Bool {
+      return true
+    }
+    
+    //MARK: - Helpers
+    func checkAndWaitForInitialDatabaseLoad() -> Bool{
+        
+        if (!_isConfigLoadFromDBCompleted) {
+          let _ = _dispatch_group.wait(timeout: DispatchTime.now() + kDatabaseLoadTimeoutSecs)
+            // Wait until load is done. This should be a no-op for subsequent calls.
+            //_isConfigLoadFromDBCompleted = true
+        }
+        return true
+    }
+    
+    //MARK: - Get config result
+    
+    func defaultValueForFullyQualifiedNamespace(namespace:String, key:String) -> FIRRemoteConfigValue{
+        let value = self.defaultConfig[namespace]?[key];
+        if value == nil {
+            return FIRRemoteConfigValue(data: Data(), source: .static);
+        }
+        return value ?? FIRRemoteConfigValue(data: Data(), source: .static)
+    }
+}

+ 0 - 1225
FirebaseRemoteConfig/Sources/RCNConfigDBManager.m

@@ -1,1225 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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 <sqlite3.h>
-
-#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigDefines.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
-
-#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
-
-/// Using macro for securely preprocessing string concatenation in query before runtime.
-#define RCNTableNameMain "main"
-#define RCNTableNameMainActive "main_active"
-#define RCNTableNameMainDefault "main_default"
-#define RCNTableNameMetadataDeprecated "fetch_metadata"
-#define RCNTableNameMetadata "fetch_metadata_v2"
-#define RCNTableNameExperiment "experiment"
-#define RCNTableNamePersonalization "personalization"
-#define RCNTableNameRollout "rollout"
-
-static BOOL gIsNewDatabase;
-/// SQLite file name in versions 0, 1 and 2.
-static NSString *const RCNDatabaseName = @"RemoteConfig.sqlite3";
-/// The storage sub-directory that the Remote Config database resides in.
-static NSString *const RCNRemoteConfigStorageSubDirectory = @"Google/RemoteConfig";
-
-/// Remote Config database path for deprecated V0 version.
-static NSString *RemoteConfigPathForOldDatabaseV0(void) {
-  NSArray *dirPaths =
-      NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
-  NSString *docPath = dirPaths.firstObject;
-  return [docPath stringByAppendingPathComponent:RCNDatabaseName];
-}
-
-/// Remote Config database path for current database.
-static NSString *RemoteConfigPathForDatabase(void) {
-#if TARGET_OS_TV
-  NSArray *dirPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
-#else
-  NSArray *dirPaths =
-      NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
-#endif
-  NSString *storageDirPath = dirPaths.firstObject;
-  NSArray *components = @[ storageDirPath, RCNRemoteConfigStorageSubDirectory, RCNDatabaseName ];
-  return [NSString pathWithComponents:components];
-}
-
-static BOOL RemoteConfigAddSkipBackupAttributeToItemAtPath(NSString *filePathString) {
-  NSURL *URL = [NSURL fileURLWithPath:filePathString];
-  assert([[NSFileManager defaultManager] fileExistsAtPath:[URL path]]);
-
-  NSError *error = nil;
-  BOOL success = [URL setResourceValue:[NSNumber numberWithBool:YES]
-                                forKey:NSURLIsExcludedFromBackupKey
-                                 error:&error];
-  if (!success) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000017", @"Error excluding %@ from backup %@.",
-                [URL lastPathComponent], error);
-  }
-  return success;
-}
-
-static BOOL RemoteConfigCreateFilePathIfNotExist(NSString *filePath) {
-  if (!filePath || !filePath.length) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000018",
-                @"Failed to create subdirectory for an empty file path.");
-    return NO;
-  }
-  NSFileManager *fileManager = [NSFileManager defaultManager];
-  if (![fileManager fileExistsAtPath:filePath]) {
-    gIsNewDatabase = YES;
-    NSError *error;
-    [fileManager createDirectoryAtPath:[filePath stringByDeletingLastPathComponent]
-           withIntermediateDirectories:YES
-                            attributes:nil
-                                 error:&error];
-    if (error) {
-      FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000019",
-                  @"Failed to create subdirectory for database file: %@.", error);
-      return NO;
-    }
-  }
-  return YES;
-}
-
-static NSArray *RemoteConfigMetadataTableColumnsInOrder(void) {
-  return @[
-    RCNKeyBundleIdentifier, RCNKeyNamespace, RCNKeyFetchTime, RCNKeyDigestPerNamespace,
-    RCNKeyDeviceContext, RCNKeyAppContext, RCNKeySuccessFetchTime, RCNKeyFailureFetchTime,
-    RCNKeyLastFetchStatus, RCNKeyLastFetchError, RCNKeyLastApplyTime, RCNKeyLastSetDefaultsTime
-  ];
-}
-
-@interface RCNConfigDBManager () {
-  /// Database storing all the config information.
-  sqlite3 *_database;
-  /// Serial queue for database read/write operations.
-  dispatch_queue_t _databaseOperationQueue;
-}
-@end
-
-@implementation RCNConfigDBManager
-
-+ (instancetype)sharedInstance {
-  static dispatch_once_t onceToken;
-  static RCNConfigDBManager *sharedInstance;
-  dispatch_once(&onceToken, ^{
-    sharedInstance = [[RCNConfigDBManager alloc] init];
-  });
-  return sharedInstance;
-}
-
-/// Returns the current version of the Remote Config database.
-+ (NSString *)remoteConfigPathForDatabase {
-  return RemoteConfigPathForDatabase();
-}
-
-- (instancetype)init {
-  self = [super init];
-  if (self) {
-    _databaseOperationQueue =
-        dispatch_queue_create("com.google.GoogleConfigService.database", DISPATCH_QUEUE_SERIAL);
-    [self createOrOpenDatabase];
-  }
-  return self;
-}
-
-#pragma mark - database
-- (void)migrateV1NamespaceToV2Namespace {
-  for (int table = 0; table < 3; table++) {
-    NSString *tableName = @"" RCNTableNameMain;
-    switch (table) {
-      case 1:
-        tableName = @"" RCNTableNameMainActive;
-        break;
-      case 2:
-        tableName = @"" RCNTableNameMainDefault;
-        break;
-      default:
-        break;
-    }
-    NSString *SQLString = [NSString
-        stringWithFormat:@"SELECT namespace FROM %@ WHERE namespace NOT LIKE '%%:%%'", tableName];
-    const char *SQL = [SQLString UTF8String];
-    sqlite3_stmt *statement = [self prepareSQL:SQL];
-    if (!statement) {
-      return;
-    }
-    NSMutableArray<NSString *> *namespaceArray = [[NSMutableArray alloc] init];
-    while (sqlite3_step(statement) == SQLITE_ROW) {
-      NSString *configNamespace =
-          [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
-      [namespaceArray addObject:configNamespace];
-    }
-    sqlite3_finalize(statement);
-
-    // Update.
-    for (NSString *namespaceToUpdate in namespaceArray) {
-      NSString *newNamespace =
-          [NSString stringWithFormat:@"%@:%@", namespaceToUpdate, kFIRDefaultAppName];
-      NSString *updateSQLString =
-          [NSString stringWithFormat:@"UPDATE %@ SET namespace = ? WHERE namespace = ?", tableName];
-      const char *updateSQL = [updateSQLString UTF8String];
-      sqlite3_stmt *updateStatement = [self prepareSQL:updateSQL];
-      if (!updateStatement) {
-        return;
-      }
-      NSArray<NSString *> *updateParams = @[ newNamespace, namespaceToUpdate ];
-      [self bindStringsToStatement:updateStatement stringArray:updateParams];
-
-      int result = sqlite3_step(updateStatement);
-      if (result != SQLITE_DONE) {
-        [self logErrorWithSQL:SQL finalizeStatement:updateStatement returnValue:NO];
-        return;
-      }
-      sqlite3_finalize(updateStatement);
-    }
-  }
-}
-
-- (void)createOrOpenDatabase {
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_async(_databaseOperationQueue, ^{
-    RCNConfigDBManager *strongSelf = weakSelf;
-    if (!strongSelf) {
-      return;
-    }
-    NSString *oldV0DBPath = RemoteConfigPathForOldDatabaseV0();
-    // Backward Compatibility
-    if ([[NSFileManager defaultManager] fileExistsAtPath:oldV0DBPath]) {
-      FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000009",
-                 @"Old database V0 exists, removed it and replace with the new one.");
-      [strongSelf removeDatabase:oldV0DBPath];
-    }
-    NSString *dbPath = [RCNConfigDBManager remoteConfigPathForDatabase];
-    FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000062", @"Loading database at path %@", dbPath);
-    const char *databasePath = dbPath.UTF8String;
-
-    // Create or open database path.
-    if (!RemoteConfigCreateFilePathIfNotExist(dbPath)) {
-      return;
-    }
-
-    int flags = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX;
-#ifdef SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION
-    flags |= SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION;
-#endif
-
-    if (sqlite3_open_v2(databasePath, &strongSelf->_database, flags, NULL) == SQLITE_OK) {
-      // Always try to create table if not exists for backward compatibility.
-      if (![strongSelf createTableSchema]) {
-        // Remove database before fail.
-        [strongSelf removeDatabase:dbPath];
-        FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000010", @"Failed to create table.");
-        // Create a new database if existing database file is corrupted.
-        if (!RemoteConfigCreateFilePathIfNotExist(dbPath)) {
-          return;
-        }
-        if (sqlite3_open_v2(databasePath, &strongSelf->_database, flags, NULL) == SQLITE_OK) {
-          if (![strongSelf createTableSchema]) {
-            // Remove database before fail.
-            [strongSelf removeDatabase:dbPath];
-            // If it failed again, there's nothing we can do here.
-            FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000010", @"Failed to create table.");
-          } else {
-            // Exclude the app data used from iCloud backup.
-            RemoteConfigAddSkipBackupAttributeToItemAtPath(dbPath);
-          }
-        } else {
-          [strongSelf logDatabaseError];
-        }
-      } else {
-        // DB file already exists. Migrate any V1 namespace column entries to V2 fully qualified
-        // 'namespace:FIRApp' entries.
-        [self migrateV1NamespaceToV2Namespace];
-        // Exclude the app data used from iCloud backup.
-        RemoteConfigAddSkipBackupAttributeToItemAtPath(dbPath);
-      }
-    } else {
-      [strongSelf logDatabaseError];
-    }
-  });
-}
-
-- (BOOL)createTableSchema {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-  static const char *createTableMain =
-      "create TABLE IF NOT EXISTS " RCNTableNameMain
-      " (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)";
-
-  static const char *createTableMainActive =
-      "create TABLE IF NOT EXISTS " RCNTableNameMainActive
-      " (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)";
-
-  static const char *createTableMainDefault =
-      "create TABLE IF NOT EXISTS " RCNTableNameMainDefault
-      " (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)";
-
-  static const char *createTableMetadata =
-      "create TABLE IF NOT EXISTS " RCNTableNameMetadata
-      " (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT,"
-      " fetch_time INTEGER, digest_per_ns BLOB, device_context BLOB, app_context BLOB, "
-      "success_fetch_time BLOB, failure_fetch_time BLOB, last_fetch_status INTEGER, "
-      "last_fetch_error INTEGER, last_apply_time INTEGER, last_set_defaults_time INTEGER)";
-
-  static const char *createTableExperiment = "create TABLE IF NOT EXISTS " RCNTableNameExperiment
-                                             " (_id INTEGER PRIMARY KEY, key TEXT, value BLOB)";
-  static const char *createTablePersonalization =
-      "create TABLE IF NOT EXISTS " RCNTableNamePersonalization
-      " (_id INTEGER PRIMARY KEY, key INTEGER, value BLOB)";
-
-  static const char *createTableRollout = "create TABLE IF NOT EXISTS " RCNTableNameRollout
-                                          " (_id INTEGER PRIMARY KEY, key TEXT, value BLOB)";
-
-  return [self executeQuery:createTableMain] && [self executeQuery:createTableMainActive] &&
-         [self executeQuery:createTableMainDefault] && [self executeQuery:createTableMetadata] &&
-         [self executeQuery:createTableExperiment] &&
-         [self executeQuery:createTablePersonalization] && [self executeQuery:createTableRollout];
-}
-
-- (void)removeDatabaseOnDatabaseQueueAtPath:(NSString *)path {
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_sync(_databaseOperationQueue, ^{
-    RCNConfigDBManager *strongSelf = weakSelf;
-    if (!strongSelf) {
-      return;
-    }
-    if (sqlite3_close(strongSelf->_database) != SQLITE_OK) {
-      [self logDatabaseError];
-    }
-    strongSelf->_database = nil;
-
-    NSFileManager *fileManager = [NSFileManager defaultManager];
-    NSError *error;
-    if (![fileManager removeItemAtPath:path error:&error]) {
-      FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000011",
-                  @"Failed to remove database at path %@ for error %@.", path, error);
-    }
-  });
-}
-
-- (void)removeDatabase:(NSString *)path {
-  if (sqlite3_close(_database) != SQLITE_OK) {
-    [self logDatabaseError];
-  }
-  _database = nil;
-
-  NSFileManager *fileManager = [NSFileManager defaultManager];
-  NSError *error;
-  if (![fileManager removeItemAtPath:path error:&error]) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000011",
-                @"Failed to remove database at path %@ for error %@.", path, error);
-  }
-}
-
-#pragma mark - execute
-- (BOOL)executeQuery:(const char *)SQL {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-  char *error;
-  if (sqlite3_exec(_database, SQL, nil, nil, &error) != SQLITE_OK) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000012", @"Failed to execute query with error %s.",
-                error);
-    return NO;
-  }
-  return YES;
-}
-
-#pragma mark - insert
-- (void)insertMetadataTableWithValues:(NSDictionary *)columnNameToValue
-                    completionHandler:(RCNDBCompletion)handler {
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_async(_databaseOperationQueue, ^{
-    BOOL success = [weakSelf insertMetadataTableWithValues:columnNameToValue];
-    if (handler) {
-      dispatch_async(dispatch_get_main_queue(), ^{
-        handler(success, nil);
-      });
-    }
-  });
-}
-
-- (BOOL)insertMetadataTableWithValues:(NSDictionary *)columnNameToValue {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-  static const char *SQL =
-      "INSERT INTO " RCNTableNameMetadata
-      " (bundle_identifier, namespace, fetch_time, digest_per_ns, device_context, "
-      "app_context, success_fetch_time, failure_fetch_time, last_fetch_status, "
-      "last_fetch_error, last_apply_time, last_set_defaults_time) values (?, ?, ?, ?, ?, ?, "
-      "?, ?, ?, ?, ?, ?)";
-
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    [self logErrorWithSQL:SQL finalizeStatement:nil returnValue:NO];
-    return NO;
-  }
-
-  NSArray *columns = RemoteConfigMetadataTableColumnsInOrder();
-  int index = 0;
-  for (NSString *columnName in columns) {
-    if ([columnName isEqualToString:RCNKeyBundleIdentifier] ||
-        [columnName isEqualToString:RCNKeyNamespace]) {
-      NSString *value = columnNameToValue[columnName];
-      if (![self bindStringToStatement:statement index:++index string:value]) {
-        return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-      }
-    } else if ([columnName isEqualToString:RCNKeyFetchTime] ||
-               [columnName isEqualToString:RCNKeyLastApplyTime] ||
-               [columnName isEqualToString:RCNKeyLastSetDefaultsTime]) {
-      double value = [columnNameToValue[columnName] doubleValue];
-      if (sqlite3_bind_double(statement, ++index, value) != SQLITE_OK) {
-        return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-      }
-    } else if ([columnName isEqualToString:RCNKeyLastFetchStatus] ||
-               [columnName isEqualToString:RCNKeyLastFetchError]) {
-      int value = [columnNameToValue[columnName] intValue];
-      if (sqlite3_bind_int(statement, ++index, value) != SQLITE_OK) {
-        return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-      }
-    } else {
-      NSData *data = columnNameToValue[columnName];
-      if (sqlite3_bind_blob(statement, ++index, data.bytes, (int)data.length, NULL) != SQLITE_OK) {
-        return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-      }
-    }
-  }
-  if (sqlite3_step(statement) != SQLITE_DONE) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  sqlite3_finalize(statement);
-  return YES;
-}
-
-- (void)insertMainTableWithValues:(NSArray *)values
-                       fromSource:(RCNDBSource)source
-                completionHandler:(RCNDBCompletion)handler {
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_async(_databaseOperationQueue, ^{
-    BOOL success = [weakSelf insertMainTableWithValues:values fromSource:source];
-    if (handler) {
-      dispatch_async(dispatch_get_main_queue(), ^{
-        handler(success, nil);
-      });
-    }
-  });
-}
-
-- (BOOL)insertMainTableWithValues:(NSArray *)values fromSource:(RCNDBSource)source {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-  if (values.count != 4) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000013",
-                @"Failed to insert config record. Wrong number of give parameters, current "
-                @"number is %ld, correct number is 4.",
-                (long)values.count);
-    return NO;
-  }
-  const char *SQL = "INSERT INTO " RCNTableNameMain
-                    " (bundle_identifier, namespace, key, value) values (?, ?, ?, ?)";
-  if (source == RCNDBSourceDefault) {
-    SQL = "INSERT INTO " RCNTableNameMainDefault
-          " (bundle_identifier, namespace, key, value) values (?, ?, ?, ?)";
-  } else if (source == RCNDBSourceActive) {
-    SQL = "INSERT INTO " RCNTableNameMainActive
-          " (bundle_identifier, namespace, key, value) values (?, ?, ?, ?)";
-  }
-
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return NO;
-  }
-
-  NSString *aString = values[0];
-  if (![self bindStringToStatement:statement index:1 string:aString]) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  aString = values[1];
-  if (![self bindStringToStatement:statement index:2 string:aString]) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  aString = values[2];
-  if (![self bindStringToStatement:statement index:3 string:aString]) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  NSData *blobData = values[3];
-  if (sqlite3_bind_blob(statement, 4, blobData.bytes, (int)blobData.length, NULL) != SQLITE_OK) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  if (sqlite3_step(statement) != SQLITE_DONE) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  sqlite3_finalize(statement);
-  return YES;
-}
-
-- (void)insertExperimentTableWithKey:(NSString *)key
-                               value:(NSData *)serializedValue
-                   completionHandler:(RCNDBCompletion)handler {
-  dispatch_async(_databaseOperationQueue, ^{
-    BOOL success = [self insertExperimentTableWithKey:key value:serializedValue];
-    if (handler) {
-      dispatch_async(dispatch_get_main_queue(), ^{
-        handler(success, nil);
-      });
-    }
-  });
-}
-
-- (BOOL)insertExperimentTableWithKey:(NSString *)key value:(NSData *)dataValue {
-  if ([key isEqualToString:@RCNExperimentTableKeyMetadata]) {
-    return [self updateExperimentMetadata:dataValue];
-  }
-
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-  const char *SQL = "INSERT INTO " RCNTableNameExperiment " (key, value) values (?, ?)";
-
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return NO;
-  }
-
-  if (![self bindStringToStatement:statement index:1 string:key]) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-
-  if (sqlite3_bind_blob(statement, 2, dataValue.bytes, (int)dataValue.length, NULL) != SQLITE_OK) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-
-  if (sqlite3_step(statement) != SQLITE_DONE) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  sqlite3_finalize(statement);
-  return YES;
-}
-
-- (BOOL)updateExperimentMetadata:(NSData *)dataValue {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-  const char *SQL = "INSERT OR REPLACE INTO " RCNTableNameExperiment
-                    " (_id, key, value) values ((SELECT _id from " RCNTableNameExperiment
-                    " WHERE key = ?), ?, ?)";
-
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return NO;
-  }
-
-  if (![self bindStringToStatement:statement index:1 string:@RCNExperimentTableKeyMetadata]) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-
-  if (![self bindStringToStatement:statement index:2 string:@RCNExperimentTableKeyMetadata]) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  if (sqlite3_bind_blob(statement, 3, dataValue.bytes, (int)dataValue.length, NULL) != SQLITE_OK) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-
-  if (sqlite3_step(statement) != SQLITE_DONE) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  sqlite3_finalize(statement);
-  return YES;
-}
-
-- (BOOL)insertOrUpdatePersonalizationConfig:(NSDictionary *)dataValue
-                                 fromSource:(RCNDBSource)source {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-
-  NSError *error;
-  NSData *JSONPayload = [NSJSONSerialization dataWithJSONObject:dataValue
-                                                        options:NSJSONWritingPrettyPrinted
-                                                          error:&error];
-
-  if (!JSONPayload || error) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000075",
-                @"Invalid Personalization payload to be serialized.");
-  }
-
-  const char *SQL = "INSERT OR REPLACE INTO " RCNTableNamePersonalization
-                    " (_id, key, value) values ((SELECT _id from " RCNTableNamePersonalization
-                    " WHERE key = ?), ?, ?)";
-
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return NO;
-  }
-
-  if (sqlite3_bind_int(statement, 1, (int)source) != SQLITE_OK) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-
-  if (sqlite3_bind_int(statement, 2, (int)source) != SQLITE_OK) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  if (sqlite3_bind_blob(statement, 3, JSONPayload.bytes, (int)JSONPayload.length, NULL) !=
-      SQLITE_OK) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-
-  if (sqlite3_step(statement) != SQLITE_DONE) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  sqlite3_finalize(statement);
-  return YES;
-}
-
-- (void)insertOrUpdateRolloutTableWithKey:(NSString *)key
-                                    value:(NSArray<NSDictionary *> *)metadataList
-                        completionHandler:(RCNDBCompletion)handler {
-  dispatch_async(_databaseOperationQueue, ^{
-    BOOL success = [self insertOrUpdateRolloutTableWithKey:key value:metadataList];
-    if (handler) {
-      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-        handler(success, nil);
-      });
-    }
-  });
-}
-
-- (BOOL)insertOrUpdateRolloutTableWithKey:(NSString *)key
-                                    value:(NSArray<NSDictionary *> *)arrayValue {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-  NSError *error;
-  NSData *dataValue = [NSJSONSerialization dataWithJSONObject:arrayValue
-                                                      options:NSJSONWritingPrettyPrinted
-                                                        error:&error];
-  const char *SQL =
-      "INSERT OR REPLACE INTO " RCNTableNameRollout
-      " (_id, key, value) values ((SELECT _id from " RCNTableNameRollout " WHERE key = ?), ?, ?)";
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return NO;
-  }
-  if (![self bindStringToStatement:statement index:1 string:key]) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-
-  if (![self bindStringToStatement:statement index:2 string:key]) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-
-  if (sqlite3_bind_blob(statement, 3, dataValue.bytes, (int)dataValue.length, NULL) != SQLITE_OK) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-
-  if (sqlite3_step(statement) != SQLITE_DONE) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  sqlite3_finalize(statement);
-  return YES;
-}
-
-#pragma mark - update
-
-- (void)updateMetadataWithOption:(RCNUpdateOption)option
-                       namespace:(NSString *)namespace
-                          values:(NSArray *)values
-               completionHandler:(RCNDBCompletion)handler {
-  dispatch_async(_databaseOperationQueue, ^{
-    BOOL success = [self updateMetadataTableWithOption:option namespace:namespace andValues:values];
-    if (handler) {
-      dispatch_async(dispatch_get_main_queue(), ^{
-        handler(success, nil);
-      });
-    }
-  });
-}
-
-- (BOOL)updateMetadataTableWithOption:(RCNUpdateOption)option
-                            namespace:(NSString *)namespace
-                            andValues:(NSArray *)values {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-  static const char *SQL =
-      "UPDATE " RCNTableNameMetadata " (last_fetch_status, last_fetch_error, last_apply_time, "
-      "last_set_defaults_time) values (?, ?, ?, ?) WHERE namespace = ?";
-  if (option == RCNUpdateOptionFetchStatus) {
-    SQL = "UPDATE " RCNTableNameMetadata
-          " SET last_fetch_status = ?, last_fetch_error = ? WHERE namespace = ?";
-  } else if (option == RCNUpdateOptionApplyTime) {
-    SQL = "UPDATE " RCNTableNameMetadata " SET last_apply_time = ? WHERE namespace = ?";
-  } else if (option == RCNUpdateOptionDefaultTime) {
-    SQL = "UPDATE " RCNTableNameMetadata " SET last_set_defaults_time = ? WHERE namespace = ?";
-  } else {
-    return NO;
-  }
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return NO;
-  }
-
-  int index = 0;
-  if ((option == RCNUpdateOptionApplyTime || option == RCNUpdateOptionDefaultTime) &&
-      values.count == 1) {
-    double value = [values[0] doubleValue];
-    if (sqlite3_bind_double(statement, ++index, value) != SQLITE_OK) {
-      return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-    }
-  } else if (option == RCNUpdateOptionFetchStatus && values.count == 2) {
-    int value = [values[0] intValue];
-    if (sqlite3_bind_int(statement, ++index, value) != SQLITE_OK) {
-      return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-    }
-    value = [values[1] intValue];
-    if (sqlite3_bind_int(statement, ++index, value) != SQLITE_OK) {
-      return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-    }
-  }
-  // bind namespace to query
-  if (sqlite3_bind_text(statement, ++index, [namespace UTF8String], -1, SQLITE_TRANSIENT) !=
-      SQLITE_OK) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-
-  if (sqlite3_step(statement) != SQLITE_DONE) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  sqlite3_finalize(statement);
-  return YES;
-}
-#pragma mark - read from DB
-
-- (NSDictionary *)loadMetadataWithBundleIdentifier:(NSString *)bundleIdentifier
-                                         namespace:(NSString *)namespace {
-  __block NSDictionary *metadataTableResult;
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_sync(_databaseOperationQueue, ^{
-    metadataTableResult = [weakSelf loadMetadataTableWithBundleIdentifier:bundleIdentifier
-                                                                namespace:namespace];
-  });
-  if (metadataTableResult) {
-    return metadataTableResult;
-  }
-  return [[NSDictionary alloc] init];
-}
-
-- (NSMutableDictionary *)loadMetadataTableWithBundleIdentifier:(NSString *)bundleIdentifier
-                                                     namespace:(NSString *)namespace {
-  NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
-  const char *SQL =
-      "SELECT bundle_identifier, fetch_time, digest_per_ns, device_context, app_context, "
-      "success_fetch_time, failure_fetch_time , last_fetch_status, "
-      "last_fetch_error, last_apply_time, last_set_defaults_time FROM " RCNTableNameMetadata
-      " WHERE bundle_identifier = ? and namespace = ?";
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return nil;
-  }
-
-  NSArray *params = @[ bundleIdentifier, namespace ];
-  [self bindStringsToStatement:statement stringArray:params];
-
-  while (sqlite3_step(statement) == SQLITE_ROW) {
-    NSString *dbBundleIdentifier =
-        [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
-
-    if (dbBundleIdentifier && ![dbBundleIdentifier isEqualToString:bundleIdentifier]) {
-      FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000014",
-                  @"Load Metadata from table error: Wrong package name %@, should be %@.",
-                  dbBundleIdentifier, bundleIdentifier);
-      return nil;
-    }
-
-    double fetchTime = sqlite3_column_double(statement, 1);
-    NSData *digestPerNamespace = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 2)
-                                                length:sqlite3_column_bytes(statement, 2)];
-    NSData *deviceContext = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 3)
-                                           length:sqlite3_column_bytes(statement, 3)];
-    NSData *appContext = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 4)
-                                        length:sqlite3_column_bytes(statement, 4)];
-    NSData *successTimeDigest = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 5)
-                                               length:sqlite3_column_bytes(statement, 5)];
-    NSData *failureTimeDigest = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 6)
-                                               length:sqlite3_column_bytes(statement, 6)];
-
-    int lastFetchStatus = sqlite3_column_int(statement, 7);
-    int lastFetchFailReason = sqlite3_column_int(statement, 8);
-    double lastApplyTimestamp = sqlite3_column_double(statement, 9);
-    double lastSetDefaultsTimestamp = sqlite3_column_double(statement, 10);
-
-    NSError *error;
-    NSMutableDictionary *deviceContextDict = nil;
-    if (deviceContext) {
-      deviceContextDict = [NSJSONSerialization JSONObjectWithData:deviceContext
-                                                          options:NSJSONReadingMutableContainers
-                                                            error:&error];
-    }
-
-    NSMutableDictionary *appContextDict = nil;
-    if (appContext) {
-      appContextDict = [NSJSONSerialization JSONObjectWithData:appContext
-                                                       options:NSJSONReadingMutableContainers
-                                                         error:&error];
-    }
-
-    NSMutableDictionary<NSString *, id> *digestPerNamespaceDictionary = nil;
-    if (digestPerNamespace) {
-      digestPerNamespaceDictionary =
-          [NSJSONSerialization JSONObjectWithData:digestPerNamespace
-                                          options:NSJSONReadingMutableContainers
-                                            error:&error];
-    }
-
-    NSMutableArray *successTimes = nil;
-    if (successTimeDigest) {
-      successTimes = [NSJSONSerialization JSONObjectWithData:successTimeDigest
-                                                     options:NSJSONReadingMutableContainers
-                                                       error:&error];
-    }
-
-    NSMutableArray *failureTimes = nil;
-    if (failureTimeDigest) {
-      failureTimes = [NSJSONSerialization JSONObjectWithData:failureTimeDigest
-                                                     options:NSJSONReadingMutableContainers
-                                                       error:&error];
-    }
-
-    dict[RCNKeyBundleIdentifier] = bundleIdentifier;
-    dict[RCNKeyFetchTime] = @(fetchTime);
-    dict[RCNKeyDigestPerNamespace] = digestPerNamespaceDictionary;
-    dict[RCNKeyDeviceContext] = deviceContextDict;
-    dict[RCNKeyAppContext] = appContextDict;
-    dict[RCNKeySuccessFetchTime] = successTimes;
-    dict[RCNKeyFailureFetchTime] = failureTimes;
-    dict[RCNKeyLastFetchStatus] = @(lastFetchStatus);
-    dict[RCNKeyLastFetchError] = @(lastFetchFailReason);
-    dict[RCNKeyLastApplyTime] = @(lastApplyTimestamp);
-    dict[RCNKeyLastSetDefaultsTime] = @(lastSetDefaultsTimestamp);
-
-    break;
-  }
-  sqlite3_finalize(statement);
-  return dict;
-}
-
-- (void)loadExperimentWithCompletionHandler:(RCNDBCompletion)handler {
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_async(_databaseOperationQueue, ^{
-    RCNConfigDBManager *strongSelf = weakSelf;
-    if (!strongSelf) {
-      return;
-    }
-    NSMutableArray *experimentPayloads =
-        [strongSelf loadExperimentTableFromKey:@RCNExperimentTableKeyPayload];
-    if (!experimentPayloads) {
-      experimentPayloads = [[NSMutableArray alloc] init];
-    }
-
-    NSMutableDictionary *experimentMetadata;
-    NSMutableArray *experiments =
-        [strongSelf loadExperimentTableFromKey:@RCNExperimentTableKeyMetadata];
-    // There should be only one entry for experiment metadata.
-    if (experiments.count > 0) {
-      NSError *error;
-      experimentMetadata = [NSJSONSerialization JSONObjectWithData:experiments[0]
-                                                           options:NSJSONReadingMutableContainers
-                                                             error:&error];
-    }
-    if (!experimentMetadata) {
-      experimentMetadata = [[NSMutableDictionary alloc] init];
-    }
-
-    /// Load activated experiments payload.
-    NSMutableArray *activeExperimentPayloads =
-        [strongSelf loadExperimentTableFromKey:@RCNExperimentTableKeyActivePayload];
-    if (!activeExperimentPayloads) {
-      activeExperimentPayloads = [[NSMutableArray alloc] init];
-    }
-
-    if (handler) {
-      dispatch_async(dispatch_get_main_queue(), ^{
-        handler(
-            YES, @{
-              @RCNExperimentTableKeyPayload : [experimentPayloads copy],
-              @RCNExperimentTableKeyMetadata : [experimentMetadata copy],
-              /// Activated experiments only need ExperimentsDescriptions data, which
-              /// experimentPayloads contains.
-              @RCNExperimentTableKeyActivePayload : [activeExperimentPayloads copy]
-            });
-      });
-    }
-  });
-}
-
-- (NSMutableArray<NSData *> *)loadExperimentTableFromKey:(NSString *)key {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-
-  const char *SQL = "SELECT value FROM " RCNTableNameExperiment " WHERE key = ?";
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return nil;
-  }
-
-  NSArray *params = @[ key ];
-  [self bindStringsToStatement:statement stringArray:params];
-  NSMutableArray *results = [self loadValuesFromStatement:statement];
-  return results;
-}
-
-- (NSArray<NSDictionary *> *)loadRolloutTableFromKey:(NSString *)key {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-  const char *SQL = "SELECT value FROM " RCNTableNameRollout " WHERE key = ?";
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return nil;
-  }
-  NSArray *params = @[ key ];
-  [self bindStringsToStatement:statement stringArray:params];
-  NSMutableArray *results = [self loadValuesFromStatement:statement];
-  // There should be only one entry in this table.
-  if (results.count != 1) {
-    return nil;
-  }
-  NSArray *rollout;
-  // Convert from NSData to NSArray
-  if (results[0]) {
-    NSError *error;
-    rollout = [NSJSONSerialization JSONObjectWithData:results[0] options:0 error:&error];
-    if (!rollout) {
-      FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000011",
-                  @"Failed to convert NSData to NSAarry for Rollout Metadata with error %@.",
-                  error);
-    }
-  }
-  if (!rollout) {
-    rollout = [[NSArray alloc] init];
-  }
-  return rollout;
-}
-
-- (NSMutableArray *)loadValuesFromStatement:(sqlite3_stmt *)statement {
-  NSMutableArray *results = [[NSMutableArray alloc] init];
-  NSData *value;
-  while (sqlite3_step(statement) == SQLITE_ROW) {
-    value = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 0)
-                           length:sqlite3_column_bytes(statement, 0)];
-    if (value) {
-      [results addObject:value];
-    }
-  }
-
-  sqlite3_finalize(statement);
-  return results;
-}
-
-- (void)loadPersonalizationWithCompletionHandler:(RCNDBLoadCompletion)handler {
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_async(_databaseOperationQueue, ^{
-    RCNConfigDBManager *strongSelf = weakSelf;
-    if (!strongSelf) {
-      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-        handler(NO, [NSMutableDictionary new], [NSMutableDictionary new], nil, nil);
-      });
-      return;
-    }
-
-    NSDictionary *activePersonalization;
-    NSData *personalizationResult = [strongSelf loadPersonalizationTableFromKey:RCNDBSourceActive];
-    // There should be only one entry for Personalization metadata.
-    if (personalizationResult) {
-      NSError *error;
-      activePersonalization = [NSJSONSerialization JSONObjectWithData:personalizationResult
-                                                              options:0
-                                                                error:&error];
-    }
-    if (!activePersonalization) {
-      activePersonalization = [[NSMutableDictionary alloc] init];
-    }
-
-    NSDictionary *fetchedPersonalization;
-    personalizationResult = [strongSelf loadPersonalizationTableFromKey:RCNDBSourceFetched];
-    // There should be only one entry for Personalization metadata.
-    if (personalizationResult) {
-      NSError *error;
-      fetchedPersonalization = [NSJSONSerialization JSONObjectWithData:personalizationResult
-                                                               options:0
-                                                                 error:&error];
-    }
-    if (!fetchedPersonalization) {
-      fetchedPersonalization = [[NSMutableDictionary alloc] init];
-    }
-
-    if (handler) {
-      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-        handler(YES, fetchedPersonalization, activePersonalization, nil, nil);
-      });
-    }
-  });
-}
-
-- (NSData *)loadPersonalizationTableFromKey:(int)key {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-
-  NSMutableArray *results = [[NSMutableArray alloc] init];
-  const char *SQL = "SELECT value FROM " RCNTableNamePersonalization " WHERE key = ?";
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return nil;
-  }
-
-  if (sqlite3_bind_int(statement, 1, key) != SQLITE_OK) {
-    [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-    return nil;
-  }
-  NSData *personalizationData;
-  while (sqlite3_step(statement) == SQLITE_ROW) {
-    personalizationData = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 0)
-                                         length:sqlite3_column_bytes(statement, 0)];
-    if (personalizationData) {
-      [results addObject:personalizationData];
-    }
-  }
-
-  sqlite3_finalize(statement);
-  // There should be only one entry in this table.
-  if (results.count != 1) {
-    return nil;
-  }
-  return results[0];
-}
-
-/// This method is only meant to be called at init time. The underlying logic will need to be
-/// reevaluated if the assumption changes at a later time.
-- (void)loadMainWithBundleIdentifier:(NSString *)bundleIdentifier
-                   completionHandler:(RCNDBLoadCompletion)handler {
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_async(_databaseOperationQueue, ^{
-    RCNConfigDBManager *strongSelf = weakSelf;
-    if (!strongSelf) {
-      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-        handler(NO, [NSDictionary new], [NSDictionary new], [NSDictionary new], [NSDictionary new]);
-      });
-      return;
-    }
-    __block NSDictionary *fetchedConfig =
-        [strongSelf loadMainTableWithBundleIdentifier:bundleIdentifier
-                                           fromSource:RCNDBSourceFetched];
-    __block NSDictionary *activeConfig =
-        [strongSelf loadMainTableWithBundleIdentifier:bundleIdentifier
-                                           fromSource:RCNDBSourceActive];
-    __block NSDictionary *defaultConfig =
-        [strongSelf loadMainTableWithBundleIdentifier:bundleIdentifier
-                                           fromSource:RCNDBSourceDefault];
-
-    __block NSArray<NSDictionary *> *fetchedRolloutMetadata =
-        [strongSelf loadRolloutTableFromKey:@RCNRolloutTableKeyFetchedMetadata];
-    __block NSArray<NSDictionary *> *activeRolloutMetadata =
-        [strongSelf loadRolloutTableFromKey:@RCNRolloutTableKeyActiveMetadata];
-
-    if (handler) {
-      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
-        fetchedConfig = fetchedConfig ? fetchedConfig : [[NSDictionary alloc] init];
-        activeConfig = activeConfig ? activeConfig : [[NSDictionary alloc] init];
-        defaultConfig = defaultConfig ? defaultConfig : [[NSDictionary alloc] init];
-        fetchedRolloutMetadata =
-            fetchedRolloutMetadata ? fetchedRolloutMetadata : [[NSArray alloc] init];
-        activeRolloutMetadata =
-            activeRolloutMetadata ? activeRolloutMetadata : [[NSArray alloc] init];
-        NSDictionary *rolloutMetadata = @{
-          @RCNRolloutTableKeyActiveMetadata : [activeRolloutMetadata copy],
-          @RCNRolloutTableKeyFetchedMetadata : [fetchedRolloutMetadata copy]
-        };
-        handler(YES, fetchedConfig, activeConfig, defaultConfig, rolloutMetadata);
-      });
-    }
-  });
-}
-
-- (NSMutableDictionary *)loadMainTableWithBundleIdentifier:(NSString *)bundleIdentifier
-                                                fromSource:(RCNDBSource)source {
-  NSMutableDictionary *namespaceToConfig = [[NSMutableDictionary alloc] init];
-  const char *SQL = "SELECT bundle_identifier, namespace, key, value FROM " RCNTableNameMain
-                    " WHERE bundle_identifier = ?";
-  if (source == RCNDBSourceDefault) {
-    SQL = "SELECT bundle_identifier, namespace, key, value FROM " RCNTableNameMainDefault
-          " WHERE bundle_identifier = ?";
-  } else if (source == RCNDBSourceActive) {
-    SQL = "SELECT bundle_identifier, namespace, key, value FROM " RCNTableNameMainActive
-          " WHERE bundle_identifier = ?";
-  }
-  NSArray *params = @[ bundleIdentifier ];
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return nil;
-  }
-  [self bindStringsToStatement:statement stringArray:params];
-
-  while (sqlite3_step(statement) == SQLITE_ROW) {
-    NSString *configNamespace =
-        [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 1)];
-    NSString *key = [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statement, 2)];
-    NSData *value = [NSData dataWithBytes:(char *)sqlite3_column_blob(statement, 3)
-                                   length:sqlite3_column_bytes(statement, 3)];
-    if (!namespaceToConfig[configNamespace]) {
-      namespaceToConfig[configNamespace] = [[NSMutableDictionary alloc] init];
-    }
-
-    if (source == RCNDBSourceDefault) {
-      namespaceToConfig[configNamespace][key] =
-          [[FIRRemoteConfigValue alloc] initWithData:value source:FIRRemoteConfigSourceDefault];
-    } else {
-      namespaceToConfig[configNamespace][key] =
-          [[FIRRemoteConfigValue alloc] initWithData:value source:FIRRemoteConfigSourceRemote];
-    }
-  }
-  sqlite3_finalize(statement);
-  return namespaceToConfig;
-}
-
-#pragma mark - delete
-- (void)deleteRecordFromMainTableWithNamespace:(NSString *)namespace_p
-                              bundleIdentifier:(NSString *)bundleIdentifier
-                                    fromSource:(RCNDBSource)source {
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_async(_databaseOperationQueue, ^{
-    RCNConfigDBManager *strongSelf = weakSelf;
-    if (!strongSelf) {
-      return;
-    }
-    NSArray *params = @[ bundleIdentifier, namespace_p ];
-    const char *SQL =
-        "DELETE FROM " RCNTableNameMain " WHERE bundle_identifier = ? and namespace = ?";
-    if (source == RCNDBSourceDefault) {
-      SQL = "DELETE FROM " RCNTableNameMainDefault " WHERE bundle_identifier = ? and namespace = ?";
-    } else if (source == RCNDBSourceActive) {
-      SQL = "DELETE FROM " RCNTableNameMainActive " WHERE bundle_identifier = ? and namespace = ?";
-    }
-    [strongSelf executeQuery:SQL withParams:params];
-  });
-}
-
-- (void)deleteRecordWithBundleIdentifier:(NSString *)bundleIdentifier
-                               namespace:(NSString *)namespace {
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_async(_databaseOperationQueue, ^{
-    RCNConfigDBManager *strongSelf = weakSelf;
-    if (!strongSelf) {
-      return;
-    }
-    const char *SQL =
-        "DELETE FROM " RCNTableNameMetadata " WHERE bundle_identifier = ? and namespace = ?";
-    NSArray *params = @[ bundleIdentifier, namespace ];
-    [strongSelf executeQuery:SQL withParams:params];
-  });
-}
-
-- (void)deleteAllRecordsFromTableWithSource:(RCNDBSource)source {
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_async(_databaseOperationQueue, ^{
-    RCNConfigDBManager *strongSelf = weakSelf;
-    if (!strongSelf) {
-      return;
-    }
-    const char *SQL = "DELETE FROM " RCNTableNameMain;
-    if (source == RCNDBSourceDefault) {
-      SQL = "DELETE FROM " RCNTableNameMainDefault;
-    } else if (source == RCNDBSourceActive) {
-      SQL = "DELETE FROM " RCNTableNameMainActive;
-    }
-    [strongSelf executeQuery:SQL];
-  });
-}
-
-- (void)deleteExperimentTableForKey:(NSString *)key {
-  __weak RCNConfigDBManager *weakSelf = self;
-  dispatch_async(_databaseOperationQueue, ^{
-    RCNConfigDBManager *strongSelf = weakSelf;
-    if (!strongSelf) {
-      return;
-    }
-    NSArray *params = @[ key ];
-    const char *SQL = "DELETE FROM " RCNTableNameExperiment " WHERE key = ?";
-    [strongSelf executeQuery:SQL withParams:params];
-  });
-}
-
-#pragma mark - helper
-- (BOOL)executeQuery:(const char *)SQL withParams:(NSArray *)params {
-  RCN_MUST_NOT_BE_MAIN_THREAD();
-  sqlite3_stmt *statement = [self prepareSQL:SQL];
-  if (!statement) {
-    return NO;
-  }
-
-  [self bindStringsToStatement:statement stringArray:params];
-  if (sqlite3_step(statement) != SQLITE_DONE) {
-    return [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-  }
-  sqlite3_finalize(statement);
-  return YES;
-}
-
-/// Params only accept TEXT format string.
-- (BOOL)bindStringsToStatement:(sqlite3_stmt *)statement stringArray:(NSArray *)array {
-  int index = 1;
-  for (NSString *param in array) {
-    if (![self bindStringToStatement:statement index:index string:param]) {
-      return [self logErrorWithSQL:nil finalizeStatement:statement returnValue:NO];
-    }
-    index++;
-  }
-  return YES;
-}
-
-- (BOOL)bindStringToStatement:(sqlite3_stmt *)statement index:(int)index string:(NSString *)value {
-  if (sqlite3_bind_text(statement, index, [value UTF8String], -1, SQLITE_TRANSIENT) != SQLITE_OK) {
-    return [self logErrorWithSQL:nil finalizeStatement:statement returnValue:NO];
-  }
-  return YES;
-}
-
-- (sqlite3_stmt *)prepareSQL:(const char *)SQL {
-  sqlite3_stmt *statement = nil;
-  if (sqlite3_prepare_v2(_database, SQL, -1, &statement, NULL) != SQLITE_OK) {
-    [self logErrorWithSQL:SQL finalizeStatement:statement returnValue:NO];
-    return nil;
-  }
-  return statement;
-}
-
-- (NSString *)errorMessage {
-  return [NSString stringWithFormat:@"%s", sqlite3_errmsg(_database)];
-}
-
-- (int)errorCode {
-  return sqlite3_errcode(_database);
-}
-
-- (void)logDatabaseError {
-  FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000015", @"Error message: %@. Error code: %d.",
-              [self errorMessage], [self errorCode]);
-}
-
-- (BOOL)logErrorWithSQL:(const char *)SQL
-      finalizeStatement:(sqlite3_stmt *)statement
-            returnValue:(BOOL)returnValue {
-  if (SQL) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000016", @"Failed with SQL: %s.", SQL);
-  }
-  [self logDatabaseError];
-
-  if (statement) {
-    sqlite3_finalize(statement);
-  }
-
-  return returnValue;
-}
-
-- (BOOL)isNewDatabase {
-  return gIsNewDatabase;
-}
-
-@end

+ 336 - 0
FirebaseRemoteConfig/Sources/RCNConfigDBManager.swift

@@ -0,0 +1,336 @@
+import Foundation
+import sqlite3
+
+class RCNConfigDBManager {
+    private var _database: OpaquePointer? = nil
+    private var _databaseOperationQueue: DispatchQueue
+
+    static let sharedInstance = RCNConfigDBManager()
+
+    private var gIsNewDatabase: Bool = false
+
+    init() {
+        _databaseOperationQueue = DispatchQueue(label: "com.google.GoogleConfigService.database", qos: .default)
+        createOrOpenDatabase()
+    }
+
+    func migrateV1NamespaceToV2Namespace() {
+      for table in 0...2 {
+        var tableName = ""
+        switch table {
+          case 0:
+            tableName = RCNTableNameMain
+            break
+          case 1:
+            tableName = RCNTableNameMainActive
+            break
+          case 2:
+            tableName = RCNTableNameMainDefault
+            break
+          default:
+            break
+        }
+        let SQLString = String(format: "SELECT namespace FROM %@ WHERE namespace NOT LIKE '%%:%%'",
+                               tableName)
+        let SQL = SQLString.utf8String
+        let statement = prepareSQL(sql: SQL!)
+        if statement == nil {
+          return
+        }
+        var namespaceArray: [String] = []
+        while sqlite3_step(statement) == SQLITE_ROW {
+          if let configNamespace = String(utf8String: String(cString: sqlite3_column_text(statement, 0))) {
+            namespaceArray.append(configNamespace)
+          }
+        }
+        sqlite3_finalize(statement)
+
+        // Update.
+        for namespaceToUpdate in namespaceArray {
+          let newNamespace = String(format: "%@:%@", namespaceToUpdate, kFIRDefaultAppName)
+          let updateSQLString = String(format: "UPDATE %@ SET namespace = ? WHERE namespace = ?", tableName)
+          let updateSQL = updateSQLString.utf8String
+          let updateStatement = prepareSQL(sql: updateSQL!)
+          if updateStatement == nil {
+            return
+          }
+          let updateParams = [newNamespace, namespaceToUpdate]
+          bindStringsToStatement(statement: updateStatement!, stringArray: updateParams)
+          let result = sqlite3_step(updateStatement)
+          if result != SQLITE_DONE {
+              logError(sql: updateSQL, finalizeStatement: updateStatement, returnValue: false)
+            return;
+          }
+          sqlite3_finalize(updateStatement)
+        }
+      }
+    }
+    
+    func createOrOpenDatabase() {
+        __weak RCNConfigDBManager *weakSelf = self;
+        dispatch_async(_databaseOperationQueue, {
+            let strongSelf = weakSelf;
+            if strongSelf == nil {
+                return;
+            }
+            let oldV0DBPath = RemoteConfigPathForOldDatabaseV0()
+            // Backward Compatibility
+            if FileManager.default.fileExists(atPath: oldV0DBPath) {
+              FIRLogInfo(RCNRemoteConfigQueueLabel, "I-RCN000009",
+                         "Old database V0 exists, removed it and replace with the new one.")
+              strongSelf.removeDatabase(path: oldV0DBPath)
+            }
+            let dbPath = RCNConfigDBManager.remoteConfigPathForDatabase()
+            FIRLogInfo(RCNRemoteConfigQueueLabel, "I-RCN000062", "Loading database at path \(dbPath)")
+            let databasePath = dbPath.utf8String
+
+            // Create or open database path.
+            if !RemoteConfigCreateFilePathIfNotExist(filePath: dbPath) {
+              return
+            }
+            
+            var flags :Int32 = SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE | SQLITE_OPEN_FULLMUTEX
+            #if SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION
+              flags |= SQLITE_OPEN_FILEPROTECTION_COMPLETEUNTILFIRSTUSERAUTHENTICATION
+            #endif
+
+          if sqlite3_open_v2(databasePath, &strongSelf->_database, Int32(flags), nil) == SQLITE_OK {
+            // Always try to create table if not exists for backward compatibility.
+            if !(strongSelf.createTableSchema()) {
+              // Remove database before fail.
+              strongSelf.removeDatabase(path: dbPath)
+              FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000010", "Failed to create table.");
+                // Create a new database if existing database file is corrupted.
+                if (!RemoteConfigCreateFilePathIfNotExist(filePath: dbPath)) {
+                  return
+                }
+            } else {
+              // DB file already exists. Migrate any V1 namespace column entries to V2 fully qualified
+              // 'namespace:FIRApp' entries.
+              [self migrateV1NamespaceToV2Namespace];
+              // Exclude the app data used from iCloud backup.
+                RemoteConfigAddSkipBackupAttributeToItemAtPath(filePath: dbPath)
+            }
+          } else {
+            strongSelf.logDatabaseError()
+          }
+        
+        });
+    }
+
+  func logError(sql: String?, finalizeStatement: OpaquePointer?, returnValue: Bool) -> Bool {
+      guard let statement = statement else {
+          return false
+      }
+      var message: String = ""
+      if let errorMessage = String(utf8String: sqlite3_errmsg(self._database)) {
+        message = String(format: "%s", errorMessage)
+      }
+      FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000012", "Failed to execute query with error %s.",
+                  [self errorMessage])
+      return false
+    }
+
+  func logDatabaseError() {
+      FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000015", "Error message: %@. Error code: %d.",
+                  String(format: "%s", [self errorMessage]), self.errorCode())
+    }
+
+  func removeDatabase(path: String) {
+    if sqlite3_close(_database) != SQLITE_OK {
+      logDatabaseError()
+    }
+
+    _database = nil
+
+    let fileManager = FileManager.default
+    do {
+      try fileManager.removeItem(atPath: path, error: nil)
+    } catch {
+      FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000011",
+                  "Failed to remove database at path \(path) for error %@.", error)
+    }
+  }
+
+    func logError(sql: String?, finalizeStatement: sqlite3_stmt?, returnValue: Bool) -> Bool {
+        guard let statement = statement else {
+            return false
+        }
+        let errorMessage: String = String(format:"%@", sqlite3_errmsg(self._database))
+        
+        FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000012", "Failed to execute query with error %s.",
+                    String(format: "%@", errorMessage))
+        
+        
+        if (statement != nil) {
+          sqlite3_finalize(statement)
+        }
+        return returnValue
+    }
+    
+    func logErrorWithSQL(sql: String?, finalizeStatement: OpaquePointer?, returnValue: Bool) -> Bool {
+        guard let statement = statement else {
+            return false
+        }
+        let errorMessage: String = String(format:"%@", sqlite3_errmsg(_database))
+        FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000016", "Failed with SQL: %@", sql ?? "");
+        FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000015", "Error message: %@. Error code: %d.", errorMessage, self.errorCode())
+        
+        
+        if (statement != nil) {
+          sqlite3_finalize(statement)
+        }
+        
+        return returnValue
+    }
+
+    func bindStringsToStatement(statement: OpaquePointer?, stringArray: [String]) -> Bool {
+      var index = 1
+      for value in array {
+        if !bindStringToStatement(statement: statement, index: index, string: value) {
+          return false
+        }
+        index+=1
+      }
+        return true
+    }
+
+    func bindStringToStatement(statement: OpaquePointer?, index: Int, string: String) -> Bool {
+        if sqlite3_bind_text(statement, Int32(index), value.utf8String!, -1, SQLITE_TRANSIENT) != SQLITE_OK {
+            return false
+        }
+        return true
+    }
+    
+    func executeQuery(SQL: String?) -> Bool{
+        return false
+    }
+    
+    func createTableSchema() -> Bool {
+      let createTableMain =
+        "create TABLE IF NOT EXISTS \(RCNTableNameMain) (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)"
+
+      let createTableMainActive =
+          "create TABLE IF NOT EXISTS \(RCNTableNameMainActive) (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)"
+
+      let createTableMainDefault =
+          "create TABLE IF NOT EXISTS \(RCNTableNameMainDefault) (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, key TEXT, value BLOB)"
+
+      let createTableMetadata =
+          "create TABLE IF NOT EXISTS \(RCNTableNameMetadata) (_id INTEGER PRIMARY KEY, bundle_identifier TEXT, namespace TEXT, fetch_time INTEGER, digest_per_ns BLOB, device_context BLOB, app_context BLOB, success_fetch_time BLOB, failure_fetch_time BLOB, last_fetch_status INTEGER, last_fetch_error INTEGER, last_apply_time INTEGER, last_set_defaults_time INTEGER)"
+
+      let createTableExperiment = "create TABLE IF NOT EXISTS \(RCNTableNameExperiment) (_id INTEGER PRIMARY KEY, key TEXT, value BLOB)"
+
+      let createTablePersonalization =
+          "create TABLE IF NOT EXISTS \(RCNTableNamePersonalization) (_id INTEGER PRIMARY KEY, key INTEGER, value BLOB)"
+
+      let createTableRollout = "create TABLE IF NOT EXISTS \(RCNTableNameRollout) (_id INTEGER PRIMARY KEY, key TEXT, value BLOB)"
+      
+      return executeQuery(sql: createTableMain) &&
+              executeQuery(sql: createTableMainActive) &&
+              executeQuery(sql: createTableMainDefault) &&
+              executeQuery(sql: createTableMetadata) &&
+              executeQuery(sql: createTableExperiment) &&
+              executeQuery(sql: createTablePersonalization) &&
+              executeQuery(sql: createTableRollout)
+    }
+    
+    func prepareSQL(sql: String?) -> OpaquePointer? {
+        var statement: OpaquePointer? = nil
+        if (sqlite3_prepare_v2(_database, sql?.utf8String!, -1, &statement, nil) != SQLITE_OK) {
+            logError(sql: String(cString: sql!), finalizeStatement: statement, returnValue: false)
+            return nil
+        }
+        return statement
+    }
+    
+    func executeQuery(sql: String?) -> Bool {
+        var error: UnsafeMutablePointer<Int8>? = nil
+        if (sqlite3_exec(_database, sql?.utf8String!, nil, nil, &error) != SQLITE_OK) {
+            FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000012", "Failed to execute query with error %@",String(cString: error!));
+            return false;
+        }
+        return true
+    }
+    
+    func bindStringsToStatement(statement: OpaquePointer?, stringArray: [String]) -> Bool {
+      var index : Int = 1
+        for param in array {
+            if (!bindStringToStatement(statement: statement, index: index, string: param)) {
+                return logError(sql: nil, finalizeStatement: statement, returnValue: false)
+            }
+            index+=1
+        }
+        return true;
+    }
+    
+    func bindStringToStatement(statement: OpaquePointer?, index: Int, string: String) -> Bool {
+        if sqlite3_bind_text(statement, Int32(index), value.utf8String!, -1, SQLITE_TRANSIENT) != SQLITE_OK {
+            return false
+        }
+        return true
+    }
+    
+    func errorMessage() -> String {
+      return String(format: "%s", sqlite3_errmsg(_database))
+    }
+    
+    func errorCode() -> Int {
+      return sqlite3_errcode(_database)
+    }
+    
+    func logError(sql: String?, finalizeStatement: OpaquePointer?, returnValue: Bool) -> Bool {
+        if (statement != nil) {
+          sqlite3_finalize(statement);
+        }
+        FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000016", "Failed with SQL: %@", sql ?? "");
+      return false
+    }
+    
+    func prepareSQL(sql: String?) -> OpaquePointer? {
+      var statement: OpaquePointer? = nil
+      if (sqlite3_prepare_v2(_database, sql?.utf8String, -1, &statement, nil) != SQLITE_OK) {
+        logError(sql: sql, finalizeStatement: statement, returnValue: false)
+        return nil
+      }
+      return statement
+    }
+    
+    func removeDatabase(path: String) {
+        if sqlite3_close(_database) != SQLITE_OK {
+          logDatabaseError()
+        }
+      
+        _database = nil;
+        let fileManager = FileManager.default;
+        var error: Error? = nil;
+        
+        do {
+          try fileManager.removeItem(atPath: path, error: &error)
+        } catch {
+          FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000011",
+                      "Failed to remove database at path \(path) for error %@.",
+                      error?.localizedDescription ?? "");
+        }
+    }
+    
+    func bindStringToStatement(statement: OpaquePointer?, index: Int, string: String) -> Bool {
+        if sqlite3_bind_text(statement, Int32(index), value.utf8String, -1, SQLITE_TRANSIENT) != SQLITE_OK {
+            return false
+        }
+        return true
+    }
+    
+    func errorMessage() -> String {
+        return String(utf8String: sqlite3_errmsg(_database))
+    }
+    
+    func errorCode() -> Int {
+      return sqlite3_errcode(_database)
+    }
+    
+    func logDatabaseError() {
+      FIRLogError(RCNRemoteConfigQueueLabel, "I-RCN000015", "Error message: %@. Error code: %d.",
+                  String(format: "%s", errorMessage()), errorCode())
+    }
+}

+ 0 - 200
FirebaseRemoteConfig/Sources/RCNConfigExperiment.m

@@ -1,200 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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/RCNConfigExperiment.h"
-
-#import "FirebaseABTesting/Sources/Private/FirebaseABTestingInternal.h"
-#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigDefines.h"
-
-static NSString *const kExperimentMetadataKeyLastStartTime = @"last_experiment_start_time";
-
-static NSString *const kServiceOrigin = @"frc";
-static NSString *const kMethodNameLatestStartTime =
-    @"latestExperimentStartTimestampBetweenTimestamp:andPayloads:";
-
-@interface RCNConfigExperiment ()
-@property(nonatomic, strong)
-    NSMutableArray<NSData *> *experimentPayloads;  ///< Experiment payloads.
-@property(nonatomic, strong)
-    NSMutableDictionary<NSString *, id> *experimentMetadata;  ///< Experiment metadata
-@property(nonatomic, strong)
-    NSMutableArray<NSData *> *activeExperimentPayloads;      ///< Activated experiment payloads.
-@property(nonatomic, strong) RCNConfigDBManager *DBManager;  ///< Database Manager.
-@property(nonatomic, strong) FIRExperimentController *experimentController;
-@property(nonatomic, strong) NSDateFormatter *experimentStartTimeDateFormatter;
-@end
-
-@implementation RCNConfigExperiment
-/// Designated initializer
-- (instancetype)initWithDBManager:(RCNConfigDBManager *)DBManager
-             experimentController:(FIRExperimentController *)controller {
-  self = [super init];
-  if (self) {
-    _experimentPayloads = [[NSMutableArray alloc] init];
-    _experimentMetadata = [[NSMutableDictionary alloc] init];
-    _activeExperimentPayloads = [[NSMutableArray alloc] init];
-    _experimentStartTimeDateFormatter = [[NSDateFormatter alloc] init];
-    [_experimentStartTimeDateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
-    [_experimentStartTimeDateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
-    // Locale needs to be hardcoded. See
-    // https://developer.apple.com/library/ios/#qa/qa1480/_index.html for more details.
-    [_experimentStartTimeDateFormatter
-        setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
-    [_experimentStartTimeDateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
-
-    _DBManager = DBManager;
-    _experimentController = controller;
-    [self loadExperimentFromTable];
-  }
-  return self;
-}
-
-- (void)loadExperimentFromTable {
-  if (!_DBManager) {
-    return;
-  }
-  __weak RCNConfigExperiment *weakSelf = self;
-  RCNDBCompletion completionHandler = ^(BOOL success, NSDictionary<NSString *, id> *result) {
-    RCNConfigExperiment *strongSelf = weakSelf;
-    if (strongSelf == nil) {
-      return;
-    }
-    if (result[@RCNExperimentTableKeyPayload]) {
-      [strongSelf->_experimentPayloads removeAllObjects];
-      for (NSData *experiment in result[@RCNExperimentTableKeyPayload]) {
-        NSError *error;
-        id experimentPayloadJSON = [NSJSONSerialization JSONObjectWithData:experiment
-                                                                   options:kNilOptions
-                                                                     error:&error];
-        if (!experimentPayloadJSON || error) {
-          FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000031",
-                        @"Experiment payload could not be parsed as JSON.");
-        } else {
-          [strongSelf->_experimentPayloads addObject:experiment];
-        }
-      }
-    }
-    if (result[@RCNExperimentTableKeyMetadata]) {
-      strongSelf->_experimentMetadata = [result[@RCNExperimentTableKeyMetadata] mutableCopy];
-    }
-
-    /// Load activated experiments payload and metadata.
-    if (result[@RCNExperimentTableKeyActivePayload]) {
-      [strongSelf->_activeExperimentPayloads removeAllObjects];
-      for (NSData *experiment in result[@RCNExperimentTableKeyActivePayload]) {
-        NSError *error;
-        id experimentPayloadJSON = [NSJSONSerialization JSONObjectWithData:experiment
-                                                                   options:kNilOptions
-                                                                     error:&error];
-        if (!experimentPayloadJSON || error) {
-          FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000031",
-                        @"Activated experiment payload could not be parsed as JSON.");
-        } else {
-          [strongSelf->_activeExperimentPayloads addObject:experiment];
-        }
-      }
-    }
-  };
-  [_DBManager loadExperimentWithCompletionHandler:completionHandler];
-}
-
-- (void)updateExperimentsWithResponse:(NSArray<NSDictionary<NSString *, id> *> *)response {
-  // cache fetched experiment payloads.
-  [_experimentPayloads removeAllObjects];
-  [_DBManager deleteExperimentTableForKey:@RCNExperimentTableKeyPayload];
-
-  for (NSDictionary<NSString *, id> *experiment in response) {
-    NSError *error;
-    NSData *JSONPayload = [NSJSONSerialization dataWithJSONObject:experiment
-                                                          options:kNilOptions
-                                                            error:&error];
-    if (!JSONPayload || error) {
-      FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000030",
-                  @"Invalid experiment payload to be serialized.");
-    } else {
-      [_experimentPayloads addObject:JSONPayload];
-      [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyPayload
-                                         value:JSONPayload
-                             completionHandler:nil];
-    }
-  }
-}
-
-- (void)updateExperimentsWithHandler:(void (^)(NSError *_Nullable))handler {
-  FIRLifecycleEvents *lifecycleEvent = [[FIRLifecycleEvents alloc] init];
-
-  // Get the last experiment start time prior to the latest payload.
-  NSTimeInterval lastStartTime =
-      [_experimentMetadata[kExperimentMetadataKeyLastStartTime] doubleValue];
-
-  // Update the last experiment start time with the latest payload.
-  [self updateExperimentStartTime];
-  [self.experimentController
-      updateExperimentsWithServiceOrigin:kServiceOrigin
-                                  events:lifecycleEvent
-                                  policy:ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest
-                           lastStartTime:lastStartTime
-                                payloads:_experimentPayloads
-                       completionHandler:handler];
-
-  /// Update activated experiments payload and metadata in DB.
-  [self updateActiveExperimentsInDB];
-}
-
-- (void)updateExperimentStartTime {
-  NSTimeInterval existingLastStartTime =
-      [_experimentMetadata[kExperimentMetadataKeyLastStartTime] doubleValue];
-
-  NSTimeInterval latestStartTime =
-      [self latestStartTimeWithExistingLastStartTime:existingLastStartTime];
-
-  _experimentMetadata[kExperimentMetadataKeyLastStartTime] = @(latestStartTime);
-
-  if (![NSJSONSerialization isValidJSONObject:_experimentMetadata]) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000028",
-                @"Invalid fetched experiment metadata to be serialized.");
-    return;
-  }
-  NSError *error;
-  NSData *serializedExperimentMetadata =
-      [NSJSONSerialization dataWithJSONObject:_experimentMetadata
-                                      options:NSJSONWritingPrettyPrinted
-                                        error:&error];
-  [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyMetadata
-                                     value:serializedExperimentMetadata
-                         completionHandler:nil];
-}
-
-- (void)updateActiveExperimentsInDB {
-  /// Put current fetched experiment payloads into activated experiment DB.
-  [_activeExperimentPayloads removeAllObjects];
-  [_DBManager deleteExperimentTableForKey:@RCNExperimentTableKeyActivePayload];
-  for (NSData *experiment in _experimentPayloads) {
-    [_activeExperimentPayloads addObject:experiment];
-    [_DBManager insertExperimentTableWithKey:@RCNExperimentTableKeyActivePayload
-                                       value:experiment
-                           completionHandler:nil];
-  }
-}
-
-- (NSTimeInterval)latestStartTimeWithExistingLastStartTime:(NSTimeInterval)existingLastStartTime {
-  return [self.experimentController
-      latestExperimentStartTimestampBetweenTimestamp:existingLastStartTime
-                                         andPayloads:_experimentPayloads];
-}
-@end

+ 78 - 0
FirebaseRemoteConfig/Sources/RCNConfigExperiment.swift

@@ -0,0 +1,78 @@
+import Foundation
+import FirebaseABTesting
+
+class RCNConfigExperiment {
+    var experimentPayloads: [Data] = []
+    var experimentMetadata: [String: Any] = [:]
+    var activeExperimentPayloads: [Data] = []
+    var DBManager: RCNConfigDBManager?
+    var experimentController: FIRExperimentController
+    var experimentStartTimeDateFormatter: DateFormatter
+    
+    init(DBManager: RCNConfigDBManager, experimentController: FIRExperimentController) {
+        self.experimentPayloads = []
+        self.experimentMetadata = [:]
+        self.activeExperimentPayloads = []
+        self.experimentStartTimeDateFormatter = DateFormatter()
+        self.experimentStartTimeDateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
+        self.experimentStartTimeDateFormatter.timeZone = TimeZone(secondsFromGMT: 0)
+        // Locale needs to be hardcoded. See
+        // https://developer.apple.com/library/ios/#qa/qa1480/_index.html for more details.
+        self.experimentStartTimeDateFormatter.locale = Locale(identifier: "en_US_POSIX")
+        self.experimentStartTimeDateFormatter.timeZone = TimeZone(abbreviation: "UTC")
+        
+        self.DBManager = DBManager
+        self.experimentController = experimentController
+        loadExperimentFromTable()
+    }
+    
+    func loadExperimentFromTable() {
+        if DBManager == nil {
+          return
+        }
+      
+        _DBManager?.loadExperimentWithCompletionHandler(completionHandler: {
+          (success, result) in
+            if result[RCNExperimentTableKeyPayload] != nil {
+                for experiment in result[RCNExperimentTableKeyPayload] as! [NSData] {
+                    var experimentPayloadJSON = [:] as NSDictionary
+                    do {
+                        let experimentPayloadJSONData = try JSONSerialization.jsonObject(with: experiment as Data, options: .allowFragments)
+                        if experimentPayloadJSONData == nil {
+                          FIRLogWarning(RCNRemoteConfigQueueLabel, "I-RCN000031",
+                                        "Experiment payload could not be parsed as JSON.")
+                        }
+                    } catch let error {
+                      FIRLogWarning(RCNRemoteConfigQueueLabel, "I-RCN000031",
+                                    "Experiment payload could not be parsed as JSON.")
+                    }
+                    
+                }
+            }
+            if (result[RCNExperimentTableKeyMetadata] != nil) {
+              self.experimentMetadata = result[RCNExperimentTableKeyMetadata] as! [String : Any]
+            }
+
+            /// Load activated experiments payload and metadata.
+            if (result[RCNExperimentTableKeyActivePayload] != nil) {
+              self.activeExperimentPayloads = []
+              for experiment in result[RCNExperimentTableKeyActivePayload] as! [NSData] {
+                
+              }
+            }
+            
+        })
+    }
+    
+    func updateExperimentsWithResponse(response: [[String: Any]]){
+        
+    }
+    
+    func updateExperimentsWithHandler(handler: @escaping ((Error?) -> Void)) {
+      
+    }
+    
+    func latestStartTimeWithExistingLastStartTime(existingLastStartTime: TimeInterval) -> TimeInterval {
+        return 0.0
+    }
+}

+ 0 - 692
FirebaseRemoteConfig/Sources/RCNConfigFetch.m

@@ -1,692 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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/Private/RCNConfigFetch.h"
-#import "FirebaseRemoteConfig/Sources/Private/FIRRemoteConfig_Private.h"
-
-#import <GoogleUtilities/GULNSData+zlib.h>
-#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
-#import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
-#import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigContent.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigExperiment.h"
-#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
-@import FirebaseRemoteConfigInterop;
-
-#ifdef RCN_STAGING_SERVER
-static NSString *const kServerURLDomain =
-    @"https://staging-firebaseremoteconfig.sandbox.googleapis.com";
-#else
-static NSString *const kServerURLDomain = @"https://firebaseremoteconfig.googleapis.com";
-#endif
-
-static NSString *const kServerURLVersion = @"/v1";
-static NSString *const kServerURLProjects = @"/projects/";
-static NSString *const kServerURLNamespaces = @"/namespaces/";
-static NSString *const kServerURLQuery = @":fetch?";
-static NSString *const kServerURLKey = @"key=";
-static NSString *const kRequestJSONKeyAppID = @"app_id";
-
-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-Encoding";  ///< 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
-
-static NSString *const kFetchTypeHeaderName =
-    @"X-Firebase-RC-Fetch-Type";  ///< Custom Http header key to identify the fetch type
-static NSString *const kBaseFetchType = @"BASE";          ///< Fetch identifier for Base Fetch
-static NSString *const kRealtimeFetchType = @"REALTIME";  ///< Fetch identifier for Realtime Fetch
-
-/// Config HTTP request content type proto buffer
-static NSString *const kContentTypeValueJSON = @"application/json";
-
-/// HTTP status codes. Ref: https://cloud.google.com/apis/design/errors#error_retries
-static NSInteger const kRCNFetchResponseHTTPStatusCodeOK = 200;
-static NSInteger const kRCNFetchResponseHTTPStatusTooManyRequests = 429;
-static NSInteger const kRCNFetchResponseHTTPStatusCodeInternalError = 500;
-static NSInteger const kRCNFetchResponseHTTPStatusCodeServiceUnavailable = 503;
-static NSInteger const kRCNFetchResponseHTTPStatusCodeGatewayTimeout = 504;
-
-#pragma mark - RCNConfig
-
-@implementation RCNConfigFetch {
-  RCNConfigContent *_content;
-  RCNConfigSettings *_settings;
-  id<FIRAnalyticsInterop> _analytics;
-  RCNConfigExperiment *_experiment;
-  dispatch_queue_t _lockQueue;  /// Guard the read/write operation.
-  NSURLSession *_fetchSession;  /// Managed internally by the fetch instance.
-  NSString *_FIRNamespace;
-  FIROptions *_options;
-  NSString *_templateVersionNumber;
-}
-
-- (instancetype)init {
-  NSAssert(NO, @"Invalid initializer.");
-  return nil;
-}
-
-/// Designated initializer
-- (instancetype)initWithContent:(RCNConfigContent *)content
-                      DBManager:(RCNConfigDBManager *)DBManager
-                       settings:(RCNConfigSettings *)settings
-                      analytics:(nullable id<FIRAnalyticsInterop>)analytics
-                     experiment:(RCNConfigExperiment *)experiment
-                          queue:(dispatch_queue_t)queue
-                      namespace:(NSString *)FIRNamespace
-                        options:(FIROptions *)options {
-  self = [super init];
-  if (self) {
-    _FIRNamespace = FIRNamespace;
-    _settings = settings;
-    _analytics = analytics;
-    _experiment = experiment;
-    _lockQueue = queue;
-    _content = content;
-    _fetchSession = [self newFetchSession];
-    _options = options;
-    _templateVersionNumber = [self->_settings lastFetchedTemplateVersion];
-  }
-  return self;
-}
-
-/// Force a new NSURLSession creation for updated config.
-- (void)recreateNetworkSession {
-  if (_fetchSession) {
-    [_fetchSession invalidateAndCancel];
-  }
-  _fetchSession = [self newFetchSession];
-}
-
-/// Return the current session. (Tests).
-- (NSURLSession *)currentNetworkSession {
-  return _fetchSession;
-}
-
-- (void)dealloc {
-  [_fetchSession invalidateAndCancel];
-}
-
-#pragma mark - Fetch Config API
-
-- (void)fetchConfigWithExpirationDuration:(NSTimeInterval)expirationDuration
-                        completionHandler:
-                            (_Nullable FIRRemoteConfigFetchCompletion)completionHandler {
-  // Note: We expect the googleAppID to always be available.
-  BOOL hasDeviceContextChanged =
-      FIRRemoteConfigHasDeviceContextChanged(_settings.deviceContext, _options.googleAppID);
-
-  __weak RCNConfigFetch *weakSelf = self;
-  dispatch_async(_lockQueue, ^{
-    RCNConfigFetch *strongSelf = weakSelf;
-    if (strongSelf == nil) {
-      return;
-    }
-
-    // Check whether we are outside of the minimum fetch interval.
-    if (![strongSelf->_settings hasMinimumFetchIntervalElapsed:expirationDuration] &&
-        !hasDeviceContextChanged) {
-      FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000051", @"Returning cached data.");
-      return [strongSelf reportCompletionOnHandler:completionHandler
-                                        withStatus:FIRRemoteConfigFetchStatusSuccess
-                                         withError:nil];
-    }
-
-    // Check if a fetch is already in progress.
-    if (strongSelf->_settings.isFetchInProgress) {
-      // Check if we have some fetched data.
-      if (strongSelf->_settings.lastFetchTimeInterval > 0) {
-        FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000052",
-                    @"A fetch is already in progress. Using previous fetch results.");
-        return [strongSelf reportCompletionOnHandler:completionHandler
-                                          withStatus:strongSelf->_settings.lastFetchStatus
-                                           withError:nil];
-      } else {
-        FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000053",
-                    @"A fetch is already in progress. Ignoring duplicate request.");
-        return [strongSelf reportCompletionOnHandler:completionHandler
-                                          withStatus:FIRRemoteConfigFetchStatusFailure
-                                           withError:nil];
-      }
-    }
-
-    // Check whether cache data is within throttle limit.
-    if ([strongSelf->_settings shouldThrottle] && !hasDeviceContextChanged) {
-      // Must set lastFetchStatus before FailReason.
-      strongSelf->_settings.lastFetchStatus = FIRRemoteConfigFetchStatusThrottled;
-      strongSelf->_settings.lastFetchError = FIRRemoteConfigErrorThrottled;
-      NSTimeInterval throttledEndTime = strongSelf->_settings.exponentialBackoffThrottleEndTime;
-
-      NSError *error =
-          [NSError errorWithDomain:FIRRemoteConfigErrorDomain
-                              code:FIRRemoteConfigErrorThrottled
-                          userInfo:@{
-                            FIRRemoteConfigThrottledEndTimeInSecondsKey : @(throttledEndTime)
-                          }];
-      return [strongSelf reportCompletionOnHandler:completionHandler
-                                        withStatus:strongSelf->_settings.lastFetchStatus
-                                         withError:error];
-    }
-    strongSelf->_settings.isFetchInProgress = YES;
-    NSString *fetchTypeHeader = [NSString stringWithFormat:@"%@/1", kBaseFetchType];
-    [strongSelf refreshInstallationsTokenWithFetchHeader:fetchTypeHeader
-                                       completionHandler:completionHandler
-                                 updateCompletionHandler:nil];
-  });
-}
-
-#pragma mark - Fetch helpers
-
-- (void)realtimeFetchConfigWithNoExpirationDuration:(NSInteger)fetchAttemptNumber
-                                  completionHandler:(RCNConfigFetchCompletion)completionHandler {
-  // Note: We expect the googleAppID to always be available.
-  BOOL hasDeviceContextChanged =
-      FIRRemoteConfigHasDeviceContextChanged(_settings.deviceContext, _options.googleAppID);
-
-  __weak RCNConfigFetch *weakSelf = self;
-  dispatch_async(_lockQueue, ^{
-    RCNConfigFetch *strongSelf = weakSelf;
-    if (strongSelf == nil) {
-      return;
-    }
-    // Check whether cache data is within throttle limit.
-    if ([strongSelf->_settings shouldThrottle] && !hasDeviceContextChanged) {
-      // Must set lastFetchStatus before FailReason.
-      strongSelf->_settings.lastFetchStatus = FIRRemoteConfigFetchStatusThrottled;
-      strongSelf->_settings.lastFetchError = FIRRemoteConfigErrorThrottled;
-      NSTimeInterval throttledEndTime = strongSelf->_settings.exponentialBackoffThrottleEndTime;
-
-      NSError *error =
-          [NSError errorWithDomain:FIRRemoteConfigErrorDomain
-                              code:FIRRemoteConfigErrorThrottled
-                          userInfo:@{
-                            FIRRemoteConfigThrottledEndTimeInSecondsKey : @(throttledEndTime)
-                          }];
-      return [strongSelf reportCompletionWithStatus:FIRRemoteConfigFetchStatusFailure
-                                         withUpdate:nil
-                                          withError:error
-                                  completionHandler:nil
-                            updateCompletionHandler:completionHandler];
-    }
-    strongSelf->_settings.isFetchInProgress = YES;
-
-    NSString *fetchTypeHeader =
-        [NSString stringWithFormat:@"%@/%ld", kRealtimeFetchType, (long)fetchAttemptNumber];
-    [strongSelf refreshInstallationsTokenWithFetchHeader:fetchTypeHeader
-                                       completionHandler:nil
-                                 updateCompletionHandler:completionHandler];
-  });
-}
-
-- (NSString *)FIRAppNameFromFullyQualifiedNamespace {
-  return [[_FIRNamespace componentsSeparatedByString:@":"] lastObject];
-}
-/// Refresh installation ID token before fetching config. installation ID is now mandatory for fetch
-/// requests to work.(b/14751422).
-- (void)refreshInstallationsTokenWithFetchHeader:(NSString *)fetchTypeHeader
-                               completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler
-                         updateCompletionHandler:(RCNConfigFetchCompletion)updateCompletionHandler {
-  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]);
-    self->_settings.isFetchInProgress = NO;
-    return [self
-        reportCompletionOnHandler:completionHandler
-                       withStatus:FIRRemoteConfigFetchStatusFailure
-                        withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
-                                                      code:FIRRemoteConfigErrorInternalError
-                                                  userInfo:@{
-                                                    NSLocalizedDescriptionKey : errorDescription
-                                                  }]];
-  }
-
-  __weak RCNConfigFetch *weakSelf = self;
-  FIRInstallationsTokenHandler installationsTokenHandler = ^(
-      FIRInstallationsAuthTokenResult *tokenResult, NSError *error) {
-    RCNConfigFetch *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]);
-      strongSelf->_settings.isFetchInProgress = NO;
-
-      NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
-      userInfo[NSLocalizedDescriptionKey] = errorDescription;
-      userInfo[NSUnderlyingErrorKey] = error.userInfo[NSUnderlyingErrorKey];
-
-      return [strongSelf
-          reportCompletionOnHandler:completionHandler
-                         withStatus:FIRRemoteConfigFetchStatusFailure
-                          withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
-                                                        code:FIRRemoteConfigErrorInternalError
-                                                    userInfo:userInfo]];
-    }
-
-    // We have a valid token. Get the backing installationID.
-    [installations installationIDWithCompletion:^(NSString *_Nullable identifier,
-                                                  NSError *_Nullable error) {
-      RCNConfigFetch *strongSelf = weakSelf;
-      if (strongSelf == nil) {
-        return;
-      }
-
-      // Dispatch to the RC serial queue to update settings on the queue.
-      dispatch_async(strongSelf->_lockQueue, ^{
-        RCNConfigFetch *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];
-
-          NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
-          userInfo[NSLocalizedDescriptionKey] = errorDescription;
-          userInfo[NSUnderlyingErrorKey] = error.userInfo[NSUnderlyingErrorKey];
-
-          FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000055", @"%@",
-                      [NSString stringWithFormat:@"%@", errorDescription]);
-          strongSelfQueue->_settings.isFetchInProgress = NO;
-          return [strongSelfQueue
-              reportCompletionOnHandler:completionHandler
-                             withStatus:FIRRemoteConfigFetchStatusFailure
-                              withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
-                                                            code:FIRRemoteConfigErrorInternalError
-                                                        userInfo:userInfo]];
-        }
-
-        FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000022", @"Success to get iid : %@.",
-                   strongSelfQueue->_settings.configInstallationsIdentifier);
-        [strongSelf doFetchCall:fetchTypeHeader
-                  completionHandler:completionHandler
-            updateCompletionHandler:updateCompletionHandler];
-      });
-    }];
-  };
-
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000039", @"Starting requesting token.");
-  [installations authTokenWithCompletion:installationsTokenHandler];
-}
-
-- (void)doFetchCall:(NSString *)fetchTypeHeader
-          completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler
-    updateCompletionHandler:(RCNConfigFetchCompletion)updateCompletionHandler {
-  [self getAnalyticsUserPropertiesWithCompletionHandler:^(NSDictionary *userProperties) {
-    dispatch_async(self->_lockQueue, ^{
-      [self fetchWithUserProperties:userProperties
-                    fetchTypeHeader:fetchTypeHeader
-                  completionHandler:completionHandler
-            updateCompletionHandler:updateCompletionHandler];
-    });
-  }];
-}
-
-- (void)getAnalyticsUserPropertiesWithCompletionHandler:
-    (FIRAInteropUserPropertiesCallback)completionHandler {
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000060", @"Fetch with user properties completed.");
-  id<FIRAnalyticsInterop> analytics = self->_analytics;
-  if (analytics == nil) {
-    completionHandler(@{});
-  } else {
-    [analytics getUserPropertiesWithCallback:completionHandler];
-  }
-}
-
-- (void)reportCompletionOnHandler:(FIRRemoteConfigFetchCompletion)completionHandler
-                       withStatus:(FIRRemoteConfigFetchStatus)status
-                        withError:(NSError *)error {
-  [self reportCompletionWithStatus:status
-                        withUpdate:nil
-                         withError:error
-                 completionHandler:completionHandler
-           updateCompletionHandler:nil];
-}
-
-- (void)reportCompletionWithStatus:(FIRRemoteConfigFetchStatus)status
-                        withUpdate:(FIRRemoteConfigUpdate *)update
-                         withError:(NSError *)error
-                 completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler
-           updateCompletionHandler:(RCNConfigFetchCompletion)updateCompletionHandler {
-  if (completionHandler) {
-    dispatch_async(dispatch_get_main_queue(), ^{
-      completionHandler(status, error);
-    });
-  }
-  // if completion handler expects a config update response
-  if (updateCompletionHandler) {
-    dispatch_async(dispatch_get_main_queue(), ^{
-      updateCompletionHandler(status, update, error);
-    });
-  }
-}
-
-- (void)fetchWithUserProperties:(NSDictionary *)userProperties
-                fetchTypeHeader:(NSString *)fetchTypeHeader
-              completionHandler:(FIRRemoteConfigFetchCompletion)completionHandler
-        updateCompletionHandler:(RCNConfigFetchCompletion)updateCompletionHandler {
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000061", @"Fetch with user properties initiated.");
-
-  NSString *postRequestString = [_settings nextRequestWithUserProperties:userProperties];
-
-  // Get POST request content.
-  NSData *content = [postRequestString dataUsingEncoding:NSUTF8StringEncoding];
-  NSError *compressionError;
-  NSData *compressedContent = [NSData gul_dataByGzippingData:content error:&compressionError];
-  if (compressionError) {
-    NSString *errString = [NSString stringWithFormat:@"Failed to compress the config request."];
-    FIRLogWarning(kFIRLoggerRemoteConfig, @"I-RCN000033", @"%@", errString);
-    NSError *error = [NSError errorWithDomain:FIRRemoteConfigErrorDomain
-                                         code:FIRRemoteConfigErrorInternalError
-                                     userInfo:@{NSLocalizedDescriptionKey : errString}];
-
-    self->_settings.isFetchInProgress = NO;
-    return [self reportCompletionWithStatus:FIRRemoteConfigFetchStatusFailure
-                                 withUpdate:nil
-                                  withError:error
-                          completionHandler:completionHandler
-                    updateCompletionHandler:updateCompletionHandler];
-  }
-
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000040", @"Start config fetch.");
-  __weak RCNConfigFetch *weakSelf = self;
-  RCNConfigFetcherCompletion fetcherCompletion = ^(NSData *data, NSURLResponse *response,
-                                                   NSError *error) {
-    FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000050",
-                @"config fetch completed. Error: %@ StatusCode: %ld", (error ? error : @"nil"),
-                (long)[((NSHTTPURLResponse *)response) statusCode]);
-
-    RCNConfigFetch *fetcherCompletionSelf = weakSelf;
-    if (fetcherCompletionSelf == nil) {
-      return;
-    }
-
-    // The fetch has completed.
-    fetcherCompletionSelf->_settings.isFetchInProgress = NO;
-
-    dispatch_async(fetcherCompletionSelf->_lockQueue, ^{
-      RCNConfigFetch *strongSelf = weakSelf;
-      if (strongSelf == nil) {
-        return;
-      }
-
-      NSInteger statusCode = [((NSHTTPURLResponse *)response) statusCode];
-
-      if (error || (statusCode != kRCNFetchResponseHTTPStatusCodeOK)) {
-        // Update metadata about fetch failure.
-        [strongSelf->_settings updateMetadataWithFetchSuccessStatus:NO templateVersion:nil];
-        if (error) {
-          if (strongSelf->_settings.lastFetchStatus == FIRRemoteConfigFetchStatusSuccess) {
-            FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000025",
-                        @"RCN Fetch failure: %@. Using cached config result.", error);
-          } else {
-            FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000026",
-                        @"RCN Fetch failure: %@. No cached config result.", error);
-          }
-        }
-        if (statusCode != kRCNFetchResponseHTTPStatusCodeOK) {
-          FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000026",
-                      @"RCN Fetch failure. Response http error code: %ld", (long)statusCode);
-          // Response error code 429, 500, 503 will trigger exponential backoff mode.
-          // TODO: check error code in helper
-          if (statusCode == kRCNFetchResponseHTTPStatusTooManyRequests ||
-              statusCode == kRCNFetchResponseHTTPStatusCodeInternalError ||
-              statusCode == kRCNFetchResponseHTTPStatusCodeServiceUnavailable ||
-              statusCode == kRCNFetchResponseHTTPStatusCodeGatewayTimeout) {
-            [strongSelf->_settings updateExponentialBackoffTime];
-            if ([strongSelf->_settings shouldThrottle]) {
-              // Must set lastFetchStatus before FailReason.
-              strongSelf->_settings.lastFetchStatus = FIRRemoteConfigFetchStatusThrottled;
-              strongSelf->_settings.lastFetchError = FIRRemoteConfigErrorThrottled;
-              NSTimeInterval throttledEndTime =
-                  strongSelf->_settings.exponentialBackoffThrottleEndTime;
-
-              NSError *error = [NSError
-                  errorWithDomain:FIRRemoteConfigErrorDomain
-                             code:FIRRemoteConfigErrorThrottled
-                         userInfo:@{
-                           FIRRemoteConfigThrottledEndTimeInSecondsKey : @(throttledEndTime)
-                         }];
-              return [strongSelf reportCompletionWithStatus:strongSelf->_settings.lastFetchStatus
-                                                 withUpdate:nil
-                                                  withError:error
-                                          completionHandler:completionHandler
-                                    updateCompletionHandler:updateCompletionHandler];
-            }
-          }
-        }
-        // Return back the received error.
-        // Must set lastFetchStatus before setting Fetch Error.
-        strongSelf->_settings.lastFetchStatus = FIRRemoteConfigFetchStatusFailure;
-        strongSelf->_settings.lastFetchError = FIRRemoteConfigErrorInternalError;
-        NSMutableDictionary<NSErrorUserInfoKey, id> *userInfo = [NSMutableDictionary dictionary];
-        userInfo[NSUnderlyingErrorKey] = error;
-        userInfo[NSLocalizedDescriptionKey] =
-            error.localizedDescription
-                ?: [NSString
-                       stringWithFormat:@"Internal Error. Status code: %ld", (long)statusCode];
-
-        return [strongSelf
-            reportCompletionWithStatus:FIRRemoteConfigFetchStatusFailure
-                            withUpdate:nil
-                             withError:[NSError errorWithDomain:FIRRemoteConfigErrorDomain
-                                                           code:FIRRemoteConfigErrorInternalError
-                                                       userInfo:userInfo]
-                     completionHandler:completionHandler
-               updateCompletionHandler:updateCompletionHandler];
-      }
-
-      // Fetch was successful. Check if we have data.
-      NSError *retError;
-      if (!data) {
-        FIRLogInfo(kFIRLoggerRemoteConfig, @"I-RCN000043", @"RCN Fetch: No data in fetch response");
-        // There may still be a difference between fetched and active config
-        FIRRemoteConfigUpdate *update =
-            [strongSelf->_content getConfigUpdateForNamespace:strongSelf->_FIRNamespace];
-        return [strongSelf reportCompletionWithStatus:FIRRemoteConfigFetchStatusSuccess
-                                           withUpdate:update
-                                            withError:nil
-                                    completionHandler:completionHandler
-                              updateCompletionHandler:updateCompletionHandler];
-      }
-
-      // Config fetch succeeded.
-      // JSONObjectWithData is always expected to return an NSDictionary in our case
-      NSMutableDictionary *fetchedConfig =
-          [NSJSONSerialization JSONObjectWithData:data
-                                          options:NSJSONReadingMutableContainers
-                                            error:&retError];
-      if (retError) {
-        FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000042",
-                    @"RCN Fetch failure: %@. Could not parse response data as JSON", error);
-      }
-
-      // Check and log if we received an error from the server
-      if (fetchedConfig && fetchedConfig.count == 1 && fetchedConfig[RCNFetchResponseKeyError]) {
-        NSString *errStr = [NSString stringWithFormat:@"RCN Fetch Failure: Server returned error:"];
-        NSDictionary *errDict = fetchedConfig[RCNFetchResponseKeyError];
-        if (errDict[RCNFetchResponseKeyErrorCode]) {
-          errStr = [errStr
-              stringByAppendingString:[NSString
-                                          stringWithFormat:@"code: %@",
-                                                           errDict[RCNFetchResponseKeyErrorCode]]];
-        }
-        if (errDict[RCNFetchResponseKeyErrorStatus]) {
-          errStr = [errStr stringByAppendingString:
-                               [NSString stringWithFormat:@". Status: %@",
-                                                          errDict[RCNFetchResponseKeyErrorStatus]]];
-        }
-        if (errDict[RCNFetchResponseKeyErrorMessage]) {
-          errStr =
-              [errStr stringByAppendingString:
-                          [NSString stringWithFormat:@". Message: %@",
-                                                     errDict[RCNFetchResponseKeyErrorMessage]]];
-        }
-        FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000044", @"%@.", errStr);
-        NSError *error = [NSError errorWithDomain:FIRRemoteConfigErrorDomain
-                                             code:FIRRemoteConfigErrorInternalError
-                                         userInfo:@{NSLocalizedDescriptionKey : errStr}];
-        return [strongSelf reportCompletionWithStatus:FIRRemoteConfigFetchStatusFailure
-                                           withUpdate:nil
-                                            withError:error
-                                    completionHandler:completionHandler
-                              updateCompletionHandler:updateCompletionHandler];
-      }
-
-      // Add the fetched config to the database.
-      if (fetchedConfig) {
-        // Update config content to cache and DB.
-        [strongSelf->_content updateConfigContentWithResponse:fetchedConfig
-                                                 forNamespace:strongSelf->_FIRNamespace];
-        // Update experiments only for 3p namespace
-        NSString *namespace = [strongSelf->_FIRNamespace
-            substringToIndex:[strongSelf->_FIRNamespace rangeOfString:@":"].location];
-        if ([namespace isEqualToString:FIRRemoteConfigConstants.FIRNamespaceGoogleMobilePlatform]) {
-          [strongSelf->_experiment updateExperimentsWithResponse:
-                                       fetchedConfig[RCNFetchResponseKeyExperimentDescriptions]];
-        }
-
-        strongSelf->_templateVersionNumber = [strongSelf getTemplateVersionNumber:fetchedConfig];
-      } else {
-        FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000063",
-                    @"Empty response with no fetched config.");
-      }
-
-      // We had a successful fetch. Update the current eTag in settings if different.
-      NSString *latestETag = ((NSHTTPURLResponse *)response).allHeaderFields[kETagHeaderName];
-      if (!strongSelf->_settings.lastETag ||
-          !([strongSelf->_settings.lastETag isEqualToString:latestETag])) {
-        strongSelf->_settings.lastETag = latestETag;
-      }
-      // Compute config update after successful fetch
-      FIRRemoteConfigUpdate *update =
-          [strongSelf->_content getConfigUpdateForNamespace:strongSelf->_FIRNamespace];
-
-      [strongSelf->_settings
-          updateMetadataWithFetchSuccessStatus:YES
-                               templateVersion:strongSelf->_templateVersionNumber];
-      return [strongSelf reportCompletionWithStatus:FIRRemoteConfigFetchStatusSuccess
-                                         withUpdate:update
-                                          withError:nil
-                                  completionHandler:completionHandler
-                            updateCompletionHandler:updateCompletionHandler];
-    });
-  };
-
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000061", @"Making remote config fetch.");
-
-  NSURLSessionDataTask *dataTask = [self URLSessionDataTaskWithContent:compressedContent
-                                                       fetchTypeHeader:fetchTypeHeader
-                                                     completionHandler:fetcherCompletion];
-  [dataTask resume];
-}
-
-- (NSString *)constructServerURL {
-  NSString *serverURLStr = [[NSString alloc] initWithString:kServerURLDomain];
-  serverURLStr = [serverURLStr stringByAppendingString:kServerURLVersion];
-  serverURLStr = [serverURLStr stringByAppendingString:kServerURLProjects];
-  serverURLStr = [serverURLStr stringByAppendingString:_options.projectID];
-  serverURLStr = [serverURLStr stringByAppendingString:kServerURLNamespaces];
-
-  // Get the namespace from the fully qualified namespace string of "namespace:FIRAppName".
-  NSString *namespace =
-      [_FIRNamespace substringToIndex:[_FIRNamespace 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;
-}
-
-- (NSURLSession *)newFetchSession {
-  NSURLSessionConfiguration *config =
-      [[NSURLSessionConfiguration defaultSessionConfiguration] copy];
-  config.timeoutIntervalForRequest = _settings.fetchTimeout;
-  config.timeoutIntervalForResource = _settings.fetchTimeout;
-  NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
-  return session;
-}
-
-- (NSURLSessionDataTask *)URLSessionDataTaskWithContent:(NSData *)content
-                                        fetchTypeHeader:(NSString *)fetchTypeHeader
-                                      completionHandler:
-                                          (RCNConfigFetcherCompletion)fetcherCompletion {
-  NSURL *URL = [NSURL URLWithString:[self constructServerURL]];
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000046", @"%@",
-              [NSString stringWithFormat:@"Making config request: %@", [URL absoluteString]]);
-
-  NSTimeInterval timeoutInterval = _fetchSession.configuration.timeoutIntervalForResource;
-  NSMutableURLRequest *URLRequest =
-      [[NSMutableURLRequest alloc] initWithURL:URL
-                                   cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
-                               timeoutInterval:timeoutInterval];
-  URLRequest.HTTPMethod = kHTTPMethodPost;
-  [URLRequest setValue:kContentTypeValueJSON forHTTPHeaderField:kContentTypeHeaderName];
-  [URLRequest setValue:_settings.configInstallationsToken
-      forHTTPHeaderField:kInstallationsAuthTokenHeaderName];
-  [URLRequest setValue:[[NSBundle mainBundle] bundleIdentifier]
-      forHTTPHeaderField:kiOSBundleIdentifierHeaderName];
-  [URLRequest setValue:@"gzip" forHTTPHeaderField:kContentEncodingHeaderName];
-  [URLRequest setValue:@"gzip" forHTTPHeaderField:kAcceptEncodingHeaderName];
-  [URLRequest setValue:fetchTypeHeader forHTTPHeaderField:kFetchTypeHeaderName];
-  // Set the eTag from the last successful fetch, if available.
-  if (_settings.lastETag) {
-    [URLRequest setValue:_settings.lastETag forHTTPHeaderField:kIfNoneMatchETagHeaderName];
-  }
-  [URLRequest setHTTPBody:content];
-
-  return [_fetchSession dataTaskWithRequest:URLRequest completionHandler:fetcherCompletion];
-}
-
-- (NSString *)getTemplateVersionNumber:(NSDictionary *)fetchedConfig {
-  if (fetchedConfig != nil && [fetchedConfig objectForKey:RCNFetchResponseKeyTemplateVersion] &&
-      [[fetchedConfig objectForKey:RCNFetchResponseKeyTemplateVersion]
-          isKindOfClass:[NSString class]]) {
-    return (NSString *)[fetchedConfig objectForKey:RCNFetchResponseKeyTemplateVersion];
-  }
-
-  return @"0";
-}
-
-@end

+ 71 - 0
FirebaseRemoteConfig/Sources/RCNConfigFetch.swift

@@ -0,0 +1,71 @@
+import Foundation
+
+typealias RCNConfigFetcherCompletion = (_ data: NSData?, _ response: URLResponse?, _ error: NSError?) -> Void
+typealias FIRRemoteConfigFetchCompletion = (FIRRemoteConfigFetchStatus, NSError?) -> Void
+
+class RCNConfigFetch: NSObject {
+    var settings: RCNConfigSettings
+    var analytics: FIRAnalyticsInterop?
+    var experiment: RCNConfigExperiment
+    var lockQueue: DispatchQueue
+    var fetchSession: URLSession?
+    var FIRNamespace: String
+    var options: FIROptions?
+    var templateVersionNumber : String = "0"
+
+    init(content: RCNConfigContent, DBManager: RCNConfigDBManager, settings: RCNConfigSettings,
+         analytics: FIRAnalyticsInterop?, experiment: RCNConfigExperiment, queue: DispatchQueue,
+         FIRNamespace: String, options: FIROptions?) {
+        self.settings = settings
+        self.analytics = analytics
+        self.experiment = experiment
+        self.lockQueue = queue
+        self.FIRNamespace = FIRNamespace
+        self.options = options
+        self.fetchSession = newFetchSession()
+        self.templateVersionNumber = ""
+    }
+
+    func recreateNetworkSession() {
+        if let session = fetchSession {
+            session.invalidateAndCancel()
+        }
+        fetchSession = newFetchSession()
+    }
+
+    func currentNetworkSession() -> URLSession? {
+        return fetchSession
+    }
+
+    func newFetchSession() -> URLSession? {
+        let config = URLSessionConfiguration.default
+        config.timeoutIntervalForRequest = settings?.fetchTimeout ?? RCNConstants.RCNHTTPDefaultConnectionTimeout
+        config.timeoutIntervalForResource = settings?.fetchTimeout ?? RCNConstants.RCNHTTPDefaultConnectionTimeout
+        let session = URLSession(configuration: config)
+        return session
+    }
+    
+    func updateExperimentsWithResponse(response: [[String: Any]]){
+        
+    }
+    
+    func updateExperimentsWithHandler(handler: @escaping ((Error?) -> Void)) {
+      
+    }
+    
+    func latestStartTimeWithExistingLastStartTime(existingLastStartTime: TimeInterval) -> TimeInterval {
+        return 0.0
+    }
+    
+    func doFetchCall(fetchTypeHeader: String, completionHandler: @escaping FIRRemoteConfigFetchCompletion, updateCompletionHandler: RCNConfigFetchCompletion){
+        
+    }
+
+    func fetchConfigWithExpirationDuration(expirationDuration: TimeInterval, completionHandler: ((FIRRemoteConfigFetchStatus, Error?) -> Void)?) {
+        
+    }
+    
+    func fetchWithExpirationDuration(expirationDuration: TimeInterval, completionHandler: ((FIRRemoteConfigFetchCompletion) -> Void)? ) {
+      
+    }
+}

+ 0 - 734
FirebaseRemoteConfig/Sources/RCNConfigRealtime.m

@@ -1,734 +0,0 @@
-/*
- * 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)propagateErrors:(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);
-        return [strongSelfQueue reportCompletionOnHandler:completionHandler
-                                               withStatus:FIRRemoteConfigFetchStatusNoFetchYet
-                                                withError:nil];
-      });
-    }];
-  };
-
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000039", @"Starting requesting token.");
-  [installations authTokenWithCompletion:installationsTokenHandler];
-}
-
-- (void)createRequestBodyWithCompletion:(void (^)(NSData *_Nonnull requestBody))completion {
-  __weak __typeof(self) weakSelf = self;
-  [self refreshInstallationsTokenWithCompletionHandler:^(FIRRemoteConfigFetchStatus status,
-                                                         NSError *_Nullable error) {
-    __strong __typeof(self) strongSelf = weakSelf;
-    if (!strongSelf) return;
-
-    if (![strongSelf->_settings.configInstallationsIdentifier length]) {
-      FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000013",
-                  @"Installation token retrieval failed. Realtime connection will not include "
-                  @"valid installations token.");
-    }
-
-    [strongSelf.request setValue:strongSelf->_settings.configInstallationsToken
-              forHTTPHeaderField:kInstallationsAuthTokenHeaderName];
-    if (strongSelf->_settings.lastETag) {
-      [strongSelf.request setValue:strongSelf->_settings.lastETag
-                forHTTPHeaderField:kIfNoneMatchETagHeaderName];
-    }
-
-    NSString *namespace = [strongSelf->_namespace
-        substringToIndex:[strongSelf->_namespace rangeOfString:@":"].location];
-    NSString *postBody = [NSString
-        stringWithFormat:@"{project:'%@', namespace:'%@', lastKnownVersionNumber:'%@', appId:'%@', "
-                         @"sdkVersion:'%@', appInstanceId:'%@'}",
-                         [strongSelf->_options GCMSenderID], namespace,
-                         strongSelf->_configFetch.templateVersionNumber,
-                         strongSelf->_options.googleAppID, FIRRemoteConfigPodVersion(),
-                         strongSelf->_settings.configInstallationsIdentifier];
-    NSData *postData = [postBody dataUsingEncoding:NSUTF8StringEncoding];
-    NSError *compressionError;
-    completion([NSData gul_dataByGzippingData:postData error:&compressionError]);
-  }];
-}
-
-/// 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
-
-- (BOOL)canMakeConnection {
-  BOOL noRunningConnection =
-      self->_dataTask == nil || self->_dataTask.state != NSURLSessionTaskStateRunning;
-  BOOL canMakeConnection = noRunningConnection && [self->_listeners count] > 0 &&
-                           !self->_isInBackground && !self->_isRealtimeDisabled;
-  return canMakeConnection;
-}
-
-// Retry mechanism for HTTP connections
-- (void)retryHTTPConnection {
-  __weak RCNConfigRealtime *weakSelf = self;
-  dispatch_async(_realtimeLockQueue, ^{
-    __strong RCNConfigRealtime *strongSelf = weakSelf;
-    if (!strongSelf || strongSelf->_isInBackground) {
-      return;
-    }
-
-    if ([strongSelf 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 propagateErrors: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 retrieve config due to fetch error. "
-                                                  @"Error: %@",
-                                                  error);
-                                      return [self propagateErrors:error];
-                                    }
-                                    if (status == FIRRemoteConfigFetchStatusSuccess) {
-                                      if ([strongSelf->_configFetch.templateVersionNumber
-                                                  integerValue] >= targetVersion) {
-                                        // only notify listeners if there is a change
-                                        if ([update updatedKeys].count > 0) {
-                                          dispatch_async(strongSelf->_realtimeLockQueue, ^{
-                                            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 propagateErrors: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 propagateErrors: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 propagateErrors: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 propagateErrors: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 __typeof(self) weakSelf = self;
-  dispatch_async(_realtimeLockQueue, ^{
-    __strong __typeof(self) strongSelf = weakSelf;
-
-    if (strongSelf->_settings.getRealtimeBackoffInterval > 0) {
-      [strongSelf retryHTTPConnection];
-      return;
-    }
-
-    if ([strongSelf canMakeConnection]) {
-      __weak __typeof(self) weakSelf = strongSelf;
-      [strongSelf createRequestBodyWithCompletion:^(NSData *_Nonnull requestBody) {
-        __strong __typeof(self) strongSelf = weakSelf;
-        if (!strongSelf) return;
-        strongSelf->_isRequestInProgress = true;
-        [strongSelf->_request setHTTPBody:requestBody];
-        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;
-  }
-  __block id listenerCopy = listener;
-
-  __weak RCNConfigRealtime *weakSelf = self;
-  dispatch_async(_realtimeLockQueue, ^{
-    __strong RCNConfigRealtime *strongSelf = weakSelf;
-    [strongSelf->_listeners addObject:listenerCopy];
-    [strongSelf beginRealtimeStream];
-  });
-
-  return [[FIRConfigUpdateListenerRegistration alloc] initWithClient:self
-                                                   completionHandler:listenerCopy];
-}
-
-- (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

+ 86 - 0
FirebaseRemoteConfig/Sources/RCNConfigRealtime.swift

@@ -0,0 +1,86 @@
+import Foundation
+
+class RCNConfigRealtime: NSObject {
+  private var _listeners: NSMutableSet<AnyHashable>
+  private var _realtimeLockQueue: DispatchQueue
+  private var _notificationCenter: NotificationCenter
+    
+    var session: URLSession?
+    var dataTask: URLSessionDataTask?
+    var request: NSMutableURLRequest?
+
+  
+    private var configFetch: RCNConfigFetch
+    private var settings: RCNConfigSettings
+    private var options: FIROptions
+    private var namespace: String
+    private var remainingRetryCount : Int = 0
+    private var isRequestInProgress : Bool = false
+    private var isInBackground : Bool = false
+    private var isRealtimeDisabled : Bool = false
+
+    init(configFetch: RCNConfigFetch, settings: RCNConfigSettings, namespace: String, options: FIROptions) {
+        _listeners = NSMutableSet<AnyHashable>()
+        _realtimeLockQueue = RCNConfigRealtime.realtimeRemoteConfigSerialQueue()
+        _notificationCenter = NotificationCenter.default
+        
+        self.configFetch = configFetch
+        self.settings = settings
+        self.options = options
+        self.namespace = namespace
+        _remainingRetryCount = max(RCNConstants.gMaxRetries - (settings.realtimeRetryCount), 1)
+        _isRequestInProgress = false;
+        _isRealtimeDisabled = false;
+        _isInBackground = false;
+        
+        setUpHttpRequest()
+        setUpHttpSession()
+        backgroundChangeListener()
+    }
+    
+    static func realtimeRemoteConfigSerialQueue() -> DispatchQueue {
+        return DispatchQueue(label: RCNConstants.RCNRemoteConfigQueueLabel)
+    }
+    
+    func propagateErrors(error: Error) {
+        
+    }
+    
+    func retryHTTPConnection() {
+      
+    }
+    
+    func addConfigUpdateListener(listener: @escaping (_ update: FIRRemoteConfigUpdate?, _ error: NSError?) -> Void ) -> FIRConfigUpdateListenerRegistration? {
+        
+        return nil
+    }
+    
+    func removeConfigUpdateListener(listener: @escaping (_ update: FIRRemoteConfigUpdate?, _ error: NSError?) -> Void) {
+        
+    }
+    
+    func beginRealtimeStream() {
+        
+    }
+    
+    func pauseRealtimeStream() {
+        
+    }
+
+  func URLSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
+
+    }
+    
+    func URLSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceiveResponse: URLResponse,
+                     completionHandler: @escaping (URLSessionResponseDisposition) -> Void) {
+      
+    }
+    
+    func URLSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
+      
+    }
+    
+    func evaluateStreamResponse(response: [AnyHashable : Any], error: NSError?) {
+      
+    }
+}

+ 0 - 520
FirebaseRemoteConfig/Sources/RCNConfigSettings.m

@@ -1,520 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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/Private/RCNConfigSettings.h"
-
-#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigDBManager.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
-#import "FirebaseRemoteConfig/Sources/RCNDevice.h"
-#import "FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.h"
-
-#import <GoogleUtilities/GULAppEnvironmentUtil.h>
-#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
-
-static NSString *const kRCNGroupPrefix = @"frc.group.";
-static NSString *const kRCNUserDefaultsKeyNamelastETag = @"lastETag";
-static NSString *const kRCNUserDefaultsKeyNameLastSuccessfulFetchTime = @"lastSuccessfulFetchTime";
-static NSString *const kRCNAnalyticsFirstOpenTimePropertyName = @"_fot";
-static const int kRCNExponentialBackoffMinimumInterval = 60 * 2;       // 2 mins.
-static const int kRCNExponentialBackoffMaximumInterval = 60 * 60 * 4;  // 4 hours.
-
-@interface RCNConfigSettings () {
-  /// A list of successful fetch timestamps in seconds.
-  NSMutableArray *_successFetchTimes;
-  /// A list of failed fetch timestamps in seconds.
-  NSMutableArray *_failureFetchTimes;
-  /// Device conditions since last successful fetch from the backend. Device conditions including
-  /// app
-  /// version, iOS version, device localte, language, GMP project ID and Game project ID. Used for
-  /// determing whether to throttle.
-  NSMutableDictionary *_deviceContext;
-  /// Custom variables (aka App context digest). This is the pending custom variables request before
-  /// fetching.
-  NSMutableDictionary *_customVariables;
-  /// Last fetch status.
-  FIRRemoteConfigFetchStatus _lastFetchStatus;
-  /// Last fetch Error.
-  FIRRemoteConfigError _lastFetchError;
-  /// The time of last apply timestamp.
-  NSTimeInterval _lastApplyTimeInterval;
-  /// The time of last setDefaults timestamp.
-  NSTimeInterval _lastSetDefaultsTimeInterval;
-  /// The database manager.
-  RCNConfigDBManager *_DBManager;
-  // The namespace for this instance.
-  NSString *_FIRNamespace;
-  // The Google App ID of the configured FIRApp.
-  NSString *_googleAppID;
-  /// The user defaults manager scoped to this RC instance of FIRApp and namespace.
-  RCNUserDefaultsManager *_userDefaultsManager;
-}
-@end
-
-@implementation RCNConfigSettings
-
-- (instancetype)initWithDatabaseManager:(RCNConfigDBManager *)manager
-                              namespace:(NSString *)FIRNamespace
-                        firebaseAppName:(NSString *)appName
-                            googleAppID:(NSString *)googleAppID {
-  self = [super init];
-  if (self) {
-    _FIRNamespace = FIRNamespace;
-    _googleAppID = googleAppID;
-    _bundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
-    if (!_bundleIdentifier) {
-      FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038",
-                   @"Main bundle identifier is missing. Remote Config might not work properly.");
-      _bundleIdentifier = @"";
-    }
-    _minimumFetchInterval = RCNDefaultMinimumFetchInterval;
-    _deviceContext = [[NSMutableDictionary alloc] init];
-    _customVariables = [[NSMutableDictionary alloc] init];
-    _successFetchTimes = [[NSMutableArray alloc] init];
-    _failureFetchTimes = [[NSMutableArray alloc] init];
-    _DBManager = manager;
-    _userDefaultsManager = [[RCNUserDefaultsManager alloc] initWithAppName:appName
-                                                                  bundleID:_bundleIdentifier
-                                                                 namespace:_FIRNamespace];
-
-    // Check if the config database is new. If so, clear the configs saved in userDefaults.
-    if ([_DBManager isNewDatabase]) {
-      FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000072",
-                   @"New config database created. Resetting user defaults.");
-      [_userDefaultsManager resetUserDefaults];
-    }
-
-    _isFetchInProgress = NO;
-    _lastFetchedTemplateVersion = [_userDefaultsManager lastFetchedTemplateVersion];
-    _lastActiveTemplateVersion = [_userDefaultsManager lastActiveTemplateVersion];
-    _realtimeExponentialBackoffRetryInterval =
-        [_userDefaultsManager currentRealtimeThrottlingRetryIntervalSeconds];
-    _realtimeExponentialBackoffThrottleEndTime = [_userDefaultsManager realtimeThrottleEndTime];
-    _realtimeRetryCount = [_userDefaultsManager realtimeRetryCount];
-  }
-  return self;
-}
-
-#pragma mark - read from / update userDefaults
-- (NSString *)lastETag {
-  return [_userDefaultsManager lastETag];
-}
-
-- (void)setLastETag:(NSString *)lastETag {
-  [self setLastETagUpdateTime:[[NSDate date] timeIntervalSince1970]];
-  [_userDefaultsManager setLastETag:lastETag];
-}
-
-- (void)setLastETagUpdateTime:(NSTimeInterval)lastETagUpdateTime {
-  [_userDefaultsManager setLastETagUpdateTime:lastETagUpdateTime];
-}
-
-- (NSTimeInterval)lastFetchTimeInterval {
-  return _userDefaultsManager.lastFetchTime;
-}
-
-- (NSTimeInterval)lastETagUpdateTime {
-  return _userDefaultsManager.lastETagUpdateTime;
-}
-
-// TODO: Update logic for app extensions as required.
-- (void)updateLastFetchTimeInterval:(NSTimeInterval)lastFetchTimeInterval {
-  _userDefaultsManager.lastFetchTime = lastFetchTimeInterval;
-}
-
-#pragma mark - load from DB
-- (NSDictionary *)loadConfigFromMetadataTable {
-  NSDictionary *metadata = [[_DBManager loadMetadataWithBundleIdentifier:_bundleIdentifier
-                                                               namespace:_FIRNamespace] copy];
-  if (metadata) {
-    // TODO: Remove (all metadata in general) once ready to
-    // migrate to user defaults completely.
-    if (metadata[RCNKeyDeviceContext]) {
-      self->_deviceContext = [metadata[RCNKeyDeviceContext] mutableCopy];
-    }
-    if (metadata[RCNKeyAppContext]) {
-      self->_customVariables = [metadata[RCNKeyAppContext] mutableCopy];
-    }
-    if (metadata[RCNKeySuccessFetchTime]) {
-      self->_successFetchTimes = [metadata[RCNKeySuccessFetchTime] mutableCopy];
-    }
-    if (metadata[RCNKeyFailureFetchTime]) {
-      self->_failureFetchTimes = [metadata[RCNKeyFailureFetchTime] mutableCopy];
-    }
-    if (metadata[RCNKeyLastFetchStatus]) {
-      self->_lastFetchStatus =
-          (FIRRemoteConfigFetchStatus)[metadata[RCNKeyLastFetchStatus] intValue];
-    }
-    if (metadata[RCNKeyLastFetchError]) {
-      self->_lastFetchError = (FIRRemoteConfigError)[metadata[RCNKeyLastFetchError] intValue];
-    }
-    if (metadata[RCNKeyLastApplyTime]) {
-      self->_lastApplyTimeInterval = [metadata[RCNKeyLastApplyTime] doubleValue];
-    }
-    if (metadata[RCNKeyLastFetchStatus]) {
-      self->_lastSetDefaultsTimeInterval = [metadata[RCNKeyLastSetDefaultsTime] doubleValue];
-    }
-  }
-  return metadata;
-}
-
-#pragma mark - update DB/cached
-/// If the last fetch was not successful, update the (exponential backoff) period that we wait until
-/// fetching again. Any subsequent fetch requests will be checked and allowed only if past this
-/// throttle end time.
-- (void)updateExponentialBackoffTime {
-  // If not in exponential backoff mode, reset the retry interval.
-  if (_lastFetchStatus == FIRRemoteConfigFetchStatusSuccess) {
-    FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000057",
-                @"Throttling: Entering exponential backoff mode.");
-    _exponentialBackoffRetryInterval = kRCNExponentialBackoffMinimumInterval;
-  } else {
-    FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000057",
-                @"Throttling: Updating throttling interval.");
-    // Double the retry interval until we hit the truncated exponential backoff. More info here:
-    // https://cloud.google.com/storage/docs/exponential-backoff
-    _exponentialBackoffRetryInterval =
-        ((_exponentialBackoffRetryInterval * 2) < kRCNExponentialBackoffMaximumInterval)
-            ? _exponentialBackoffRetryInterval * 2
-            : _exponentialBackoffRetryInterval;
-  }
-
-  // Randomize the next retry interval.
-  int randomPlusMinusInterval = ((arc4random() % 2) == 0) ? -1 : 1;
-  NSTimeInterval randomizedRetryInterval =
-      _exponentialBackoffRetryInterval +
-      (0.5 * _exponentialBackoffRetryInterval * randomPlusMinusInterval);
-  _exponentialBackoffThrottleEndTime =
-      [[NSDate date] timeIntervalSince1970] + randomizedRetryInterval;
-}
-
-/// If the last Realtime stream attempt was not successful, update the (exponential backoff) period
-/// that we wait until trying again. Any subsequent Realtime requests will be checked and allowed
-/// only if past this throttle end time.
-- (void)updateRealtimeExponentialBackoffTime {
-  // If there was only one stream attempt before, reset the retry interval.
-  if (_realtimeRetryCount == 0) {
-    FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058",
-                @"Throttling: Entering exponential Realtime backoff mode.");
-    _realtimeExponentialBackoffRetryInterval = kRCNExponentialBackoffMinimumInterval;
-  } else {
-    FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058",
-                @"Throttling: Updating Realtime throttling interval.");
-    // Double the retry interval until we hit the truncated exponential backoff. More info here:
-    // https://cloud.google.com/storage/docs/exponential-backoff
-    _realtimeExponentialBackoffRetryInterval =
-        ((_realtimeExponentialBackoffRetryInterval * 2) < kRCNExponentialBackoffMaximumInterval)
-            ? _realtimeExponentialBackoffRetryInterval * 2
-            : _realtimeExponentialBackoffRetryInterval;
-  }
-
-  // Randomize the next retry interval.
-  int randomPlusMinusInterval = ((arc4random() % 2) == 0) ? -1 : 1;
-  NSTimeInterval randomizedRetryInterval =
-      _realtimeExponentialBackoffRetryInterval +
-      (0.5 * _realtimeExponentialBackoffRetryInterval * randomPlusMinusInterval);
-  _realtimeExponentialBackoffThrottleEndTime =
-      [[NSDate date] timeIntervalSince1970] + randomizedRetryInterval;
-
-  [_userDefaultsManager setRealtimeThrottleEndTime:_realtimeExponentialBackoffThrottleEndTime];
-  [_userDefaultsManager
-      setCurrentRealtimeThrottlingRetryIntervalSeconds:_realtimeExponentialBackoffRetryInterval];
-}
-
-- (void)setRealtimeRetryCount:(int)realtimeRetryCount {
-  _realtimeRetryCount = realtimeRetryCount;
-  [_userDefaultsManager setRealtimeRetryCount:_realtimeRetryCount];
-}
-
-- (NSTimeInterval)getRealtimeBackoffInterval {
-  NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
-  return _realtimeExponentialBackoffThrottleEndTime - now;
-}
-
-- (void)updateMetadataWithFetchSuccessStatus:(BOOL)fetchSuccess
-                             templateVersion:(NSString *)templateVersion {
-  FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000056", @"Updating metadata with fetch result.");
-  [self updateFetchTimeWithSuccessFetch:fetchSuccess];
-  _lastFetchStatus =
-      fetchSuccess ? FIRRemoteConfigFetchStatusSuccess : FIRRemoteConfigFetchStatusFailure;
-  _lastFetchError = fetchSuccess ? FIRRemoteConfigErrorUnknown : FIRRemoteConfigErrorInternalError;
-  if (fetchSuccess) {
-    [self updateLastFetchTimeInterval:[[NSDate date] timeIntervalSince1970]];
-    // Note: We expect the googleAppID to always be available.
-    _deviceContext = FIRRemoteConfigDeviceContextWithProjectIdentifier(_googleAppID);
-    _lastFetchedTemplateVersion = templateVersion;
-    [_userDefaultsManager setLastFetchedTemplateVersion:templateVersion];
-  }
-
-  [self updateMetadataTable];
-}
-
-- (void)updateFetchTimeWithSuccessFetch:(BOOL)isSuccessfulFetch {
-  NSTimeInterval epochTimeInterval = [[NSDate date] timeIntervalSince1970];
-  if (isSuccessfulFetch) {
-    [_successFetchTimes addObject:@(epochTimeInterval)];
-  } else {
-    [_failureFetchTimes addObject:@(epochTimeInterval)];
-  }
-}
-
-- (void)updateMetadataTable {
-  [_DBManager deleteRecordWithBundleIdentifier:_bundleIdentifier namespace:_FIRNamespace];
-  NSError *error;
-  // Objects to be serialized cannot be invalid.
-  if (!_bundleIdentifier) {
-    return;
-  }
-  if (![NSJSONSerialization isValidJSONObject:_customVariables]) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000028",
-                @"Invalid custom variables to be serialized.");
-    return;
-  }
-  if (![NSJSONSerialization isValidJSONObject:_deviceContext]) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000029",
-                @"Invalid device context to be serialized.");
-    return;
-  }
-
-  if (![NSJSONSerialization isValidJSONObject:_successFetchTimes]) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000031",
-                @"Invalid success fetch times to be serialized.");
-    return;
-  }
-  if (![NSJSONSerialization isValidJSONObject:_failureFetchTimes]) {
-    FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000032",
-                @"Invalid failure fetch times to be serialized.");
-    return;
-  }
-  NSData *serializedAppContext = [NSJSONSerialization dataWithJSONObject:_customVariables
-                                                                 options:NSJSONWritingPrettyPrinted
-                                                                   error:&error];
-  NSData *serializedDeviceContext =
-      [NSJSONSerialization dataWithJSONObject:_deviceContext
-                                      options:NSJSONWritingPrettyPrinted
-                                        error:&error];
-  // The digestPerNamespace is not used and only meant for backwards DB compatibility.
-  NSData *serializedDigestPerNamespace =
-      [NSJSONSerialization dataWithJSONObject:@{} options:NSJSONWritingPrettyPrinted error:&error];
-  NSData *serializedSuccessTime = [NSJSONSerialization dataWithJSONObject:_successFetchTimes
-                                                                  options:NSJSONWritingPrettyPrinted
-                                                                    error:&error];
-  NSData *serializedFailureTime = [NSJSONSerialization dataWithJSONObject:_failureFetchTimes
-                                                                  options:NSJSONWritingPrettyPrinted
-                                                                    error:&error];
-
-  if (!serializedDigestPerNamespace || !serializedDeviceContext || !serializedAppContext ||
-      !serializedSuccessTime || !serializedFailureTime) {
-    return;
-  }
-
-  NSDictionary *columnNameToValue = @{
-    RCNKeyBundleIdentifier : _bundleIdentifier,
-    RCNKeyNamespace : _FIRNamespace,
-    RCNKeyFetchTime : @(self.lastFetchTimeInterval),
-    RCNKeyDigestPerNamespace : serializedDigestPerNamespace,
-    RCNKeyDeviceContext : serializedDeviceContext,
-    RCNKeyAppContext : serializedAppContext,
-    RCNKeySuccessFetchTime : serializedSuccessTime,
-    RCNKeyFailureFetchTime : serializedFailureTime,
-    RCNKeyLastFetchStatus : [NSString stringWithFormat:@"%ld", (long)_lastFetchStatus],
-    RCNKeyLastFetchError : [NSString stringWithFormat:@"%ld", (long)_lastFetchError],
-    RCNKeyLastApplyTime : @(_lastApplyTimeInterval),
-    RCNKeyLastSetDefaultsTime : @(_lastSetDefaultsTimeInterval)
-  };
-
-  [_DBManager insertMetadataTableWithValues:columnNameToValue completionHandler:nil];
-}
-
-- (void)updateLastActiveTemplateVersion {
-  _lastActiveTemplateVersion = _lastFetchedTemplateVersion;
-  [_userDefaultsManager setLastActiveTemplateVersion:_lastActiveTemplateVersion];
-}
-
-#pragma mark - fetch request
-
-/// Returns a fetch request with the latest device and config change.
-/// Whenever user issues a fetch api call, collect the latest request.
-- (NSString *)nextRequestWithUserProperties:(NSDictionary *)userProperties {
-  // Note: We only set user properties as mentioned in the new REST API Design doc
-  NSString *ret = [NSString stringWithFormat:@"{"];
-  ret = [ret stringByAppendingString:[NSString stringWithFormat:@"app_instance_id:'%@'",
-                                                                _configInstallationsIdentifier]];
-  ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_instance_id_token:'%@'",
-                                                                _configInstallationsToken]];
-  ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_id:'%@'", _googleAppID]];
-
-  ret = [ret stringByAppendingString:[NSString stringWithFormat:@", country_code:'%@'",
-                                                                FIRRemoteConfigDeviceCountry()]];
-  ret = [ret stringByAppendingString:[NSString stringWithFormat:@", language_code:'%@'",
-                                                                FIRRemoteConfigDeviceLocale()]];
-  ret = [ret
-      stringByAppendingString:[NSString stringWithFormat:@", platform_version:'%@'",
-                                                         [GULAppEnvironmentUtil systemVersion]]];
-  ret = [ret stringByAppendingString:[NSString stringWithFormat:@", time_zone:'%@'",
-                                                                FIRRemoteConfigTimezone()]];
-  ret = [ret stringByAppendingString:[NSString stringWithFormat:@", package_name:'%@'",
-                                                                _bundleIdentifier]];
-  ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_version:'%@'",
-                                                                FIRRemoteConfigAppVersion()]];
-  ret = [ret stringByAppendingString:[NSString stringWithFormat:@", app_build:'%@'",
-                                                                FIRRemoteConfigAppBuildVersion()]];
-  ret = [ret stringByAppendingString:[NSString stringWithFormat:@", sdk_version:'%@'",
-                                                                FIRRemoteConfigPodVersion()]];
-
-  if (userProperties && userProperties.count > 0) {
-    NSError *error;
-
-    // Extract first open time from user properties and send as a separate field
-    NSNumber *firstOpenTime = userProperties[kRCNAnalyticsFirstOpenTimePropertyName];
-    NSMutableDictionary *remainingUserProperties = [userProperties mutableCopy];
-    if (firstOpenTime != nil) {
-      NSDate *date = [NSDate dateWithTimeIntervalSince1970:([firstOpenTime longValue] / 1000)];
-      NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
-      NSString *firstOpenTimeISOString = [formatter stringFromDate:date];
-      ret = [ret stringByAppendingString:[NSString stringWithFormat:@", first_open_time:'%@'",
-                                                                    firstOpenTimeISOString]];
-
-      [remainingUserProperties removeObjectForKey:kRCNAnalyticsFirstOpenTimePropertyName];
-    }
-    if (remainingUserProperties.count > 0) {
-      NSData *jsonData = [NSJSONSerialization dataWithJSONObject:remainingUserProperties
-                                                         options:0
-                                                           error:&error];
-      if (!error) {
-        ret = [ret
-            stringByAppendingString:[NSString
-                                        stringWithFormat:@", analytics_user_properties:%@",
-                                                         [[NSString alloc]
-                                                             initWithData:jsonData
-                                                                 encoding:NSUTF8StringEncoding]]];
-      }
-    }
-  }
-
-  NSDictionary<NSString *, NSString *> *customSignals = [self customSignals];
-  if (customSignals.count > 0) {
-    NSError *error;
-    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:customSignals
-                                                       options:0
-                                                         error:&error];
-    if (!error) {
-      ret = [ret
-          stringByAppendingString:[NSString
-                                      stringWithFormat:@", custom_signals:%@",
-                                                       [[NSString alloc]
-                                                           initWithData:jsonData
-                                                               encoding:NSUTF8StringEncoding]]];
-      // Log the keys of the custom signals sent during fetch.
-      FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000078",
-                  @"Keys of custom signals during fetch: %@", [customSignals allKeys]);
-    }
-  }
-  ret = [ret stringByAppendingString:@"}"];
-  return ret;
-}
-
-#pragma mark - getter/setter
-
-- (void)setLastFetchError:(FIRRemoteConfigError)lastFetchError {
-  if (_lastFetchError != lastFetchError) {
-    _lastFetchError = lastFetchError;
-    [_DBManager updateMetadataWithOption:RCNUpdateOptionFetchStatus
-                               namespace:_FIRNamespace
-                                  values:@[ @(_lastFetchStatus), @(_lastFetchError) ]
-                       completionHandler:nil];
-  }
-}
-
-- (NSArray *)successFetchTimes {
-  return [_successFetchTimes copy];
-}
-
-- (NSArray *)failureFetchTimes {
-  return [_failureFetchTimes copy];
-}
-
-- (NSDictionary *)customVariables {
-  return [_customVariables copy];
-}
-
-- (NSDictionary *)deviceContext {
-  return [_deviceContext copy];
-}
-
-- (void)setCustomVariables:(NSDictionary *)customVariables {
-  _customVariables = [[NSMutableDictionary alloc] initWithDictionary:customVariables];
-  [self updateMetadataTable];
-}
-
-- (void)setMinimumFetchInterval:(NSTimeInterval)minimumFetchInterval {
-  if (minimumFetchInterval < 0) {
-    _minimumFetchInterval = 0;
-  } else {
-    _minimumFetchInterval = minimumFetchInterval;
-  }
-}
-
-- (void)setFetchTimeout:(NSTimeInterval)fetchTimeout {
-  if (fetchTimeout <= 0) {
-    _fetchTimeout = RCNHTTPDefaultConnectionTimeout;
-  } else {
-    _fetchTimeout = fetchTimeout;
-  }
-}
-
-- (void)setLastApplyTimeInterval:(NSTimeInterval)lastApplyTimestamp {
-  _lastApplyTimeInterval = lastApplyTimestamp;
-  [_DBManager updateMetadataWithOption:RCNUpdateOptionApplyTime
-                             namespace:_FIRNamespace
-                                values:@[ @(lastApplyTimestamp) ]
-                     completionHandler:nil];
-}
-
-- (void)setLastSetDefaultsTimeInterval:(NSTimeInterval)lastSetDefaultsTimestamp {
-  _lastSetDefaultsTimeInterval = lastSetDefaultsTimestamp;
-  [_DBManager updateMetadataWithOption:RCNUpdateOptionDefaultTime
-                             namespace:_FIRNamespace
-                                values:@[ @(lastSetDefaultsTimestamp) ]
-                     completionHandler:nil];
-}
-
-- (NSDictionary<NSString *, NSString *> *)customSignals {
-  return [_userDefaultsManager customSignals];
-}
-
-- (void)setCustomSignals:(NSDictionary<NSString *, NSString *> *)customSignals {
-  [_userDefaultsManager setCustomSignals:customSignals];
-}
-
-#pragma mark Throttling
-
-- (BOOL)hasMinimumFetchIntervalElapsed:(NSTimeInterval)minimumFetchInterval {
-  if (self.lastFetchTimeInterval == 0) return YES;
-
-  // Check if last config fetch is within minimum fetch interval in seconds.
-  NSTimeInterval diffInSeconds = [[NSDate date] timeIntervalSince1970] - self.lastFetchTimeInterval;
-  return diffInSeconds > minimumFetchInterval;
-}
-
-- (BOOL)shouldThrottle {
-  NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
-  return ((self.lastFetchTimeInterval > 0) &&
-          (_lastFetchStatus != FIRRemoteConfigFetchStatusSuccess) &&
-          (_exponentialBackoffThrottleEndTime - now > 0));
-}
-
-@end

+ 81 - 0
FirebaseRemoteConfig/Sources/RCNConfigSettings.swift

@@ -0,0 +1,81 @@
+import Foundation
+
+class RCNConfigSettings {
+    var customSignals : [String : String] = [:]
+    var minimumFetchInterval : TimeInterval = RCNConstants.RCNDefaultMinimumFetchInterval
+    var fetchTimeout : TimeInterval = RCNConstants.RCNHTTPDefaultConnectionTimeout
+    var lastETag : String? = ""
+    var lastETagUpdateTime: TimeInterval = 0
+    var lastFetchTimeInterval : TimeInterval = 0
+    var lastFetchStatus : FIRRemoteConfigFetchStatus = .noFetchYet
+    var isFetchInProgress: Bool = false
+    var exponentialBackoffThrottleEndTime : TimeInterval = 0
+    var exponentialBackoffRetryInterval : TimeInterval = 0
+    var lastApplyTimeInterval : TimeInterval = 0
+    var lastSetDefaultsTimeInterval : TimeInterval = 0
+    var lastFetchedTemplateVersion : String? = "0"
+    var lastActiveTemplateVersion : String? = "0"
+    var configInstallationsToken : String? = ""
+    var configInstallationsIdentifier : String? = ""
+    var realtimeExponentialBackoffRetryInterval : TimeInterval = 0
+    var realtimeExponentialBackoffThrottleEndTime : TimeInterval = 0
+    var realtimeRetryCount : Int = 0
+
+    init() {}
+    
+    func setRealtimeRetryCount(realtimeRetryCount: Int) {
+      
+    }
+
+    // MARK: - Throttling
+    func hasMinimumFetchIntervalElapsed(minimumFetchInterval: TimeInterval) -> Bool {
+      
+        if lastFetchTimeInterval == 0 {
+            return true
+        }
+
+        // Check if last config fetch is within minimum fetch interval in seconds.
+        let diffInSeconds = Date().timeIntervalSince1970 - lastFetchTimeInterval
+        return diffInSeconds > minimumFetchInterval
+    }
+
+    func shouldThrottle() -> Bool {
+        let now = Date().timeIntervalSince1970
+        return (self.lastFetchTimeInterval > 0 &&
+                 (self.lastFetchStatus != FIRRemoteConfigFetchStatus.success) &&
+                (_exponentialBackoffThrottleEndTime - now > 0))
+    }
+    
+    //MARK: - update
+    func updateMetadataWithFetchSuccessStatus(fetchSuccess: Bool, templateVersion: String?) {
+        
+    }
+
+    func updateMetadataTable(){
+        
+    }
+    
+    func updateLastFetchTimeInterval(lastFetchTimeInterval: TimeInterval){
+        
+    }
+    
+    func updateLastActiveTemplateVersion() {
+        
+    }
+    
+    func updateRealtimeExponentialBackoffTime(){
+        
+    }
+    
+    func updateExponentialBackoffTime() {
+        
+    }
+    
+    func nextRequestWithUserProperties(userProperties: [String : Any]) -> String {
+        return ""
+    }
+    
+    func getRealtimeBackoffInterval() -> TimeInterval {
+        return 0.0
+    }
+}

+ 0 - 21
FirebaseRemoteConfig/Sources/RCNConstants3P.m

@@ -1,21 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
-
-/// Firebase Remote Config service default namespace.
-/// TODO(doudounan): Change to use this namespace defined in RemoteConfigInterop.
-NSString *const FIRNamespaceGoogleMobilePlatform = @"firebase";

+ 5 - 0
FirebaseRemoteConfig/Sources/RCNConstants3P.swift

@@ -0,0 +1,5 @@
+import Foundation
+
+/// Firebase Remote Config service default namespace.
+/// TODO(doudounan): Change to use this namespace defined in RemoteConfigInterop.
+let FIRNamespaceGoogleMobilePlatform = "firebase"

+ 0 - 244
FirebaseRemoteConfig/Sources/RCNDevice.m

@@ -1,244 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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/RCNDevice.h"
-
-#import <sys/utsname.h>
-
-#import <GoogleUtilities/GULAppEnvironmentUtil.h>
-#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
-#import "FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
-
-#define STR(x) STR_EXPAND(x)
-#define STR_EXPAND(x) #x
-
-static NSString *const RCNDeviceContextKeyVersion = @"app_version";
-static NSString *const RCNDeviceContextKeyBuild = @"app_build";
-static NSString *const RCNDeviceContextKeyOSVersion = @"os_version";
-static NSString *const RCNDeviceContextKeyDeviceLocale = @"device_locale";
-static NSString *const RCNDeviceContextKeyLocaleLanguage = @"locale_language";
-static NSString *const RCNDeviceContextKeyGMPProjectIdentifier = @"GMP_project_Identifier";
-
-NSString *FIRRemoteConfigAppVersion(void) {
-  return [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"];
-}
-
-NSString *FIRRemoteConfigAppBuildVersion(void) {
-  return [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"];
-}
-
-NSString *FIRRemoteConfigPodVersion(void) {
-  return FIRFirebaseVersion();
-}
-
-RCNDeviceModel FIRRemoteConfigDeviceSubtype(void) {
-  NSString *model = [GULAppEnvironmentUtil deviceModel];
-  if ([model hasPrefix:@"iPhone"]) {
-    return RCNDeviceModelPhone;
-  }
-  if ([model isEqualToString:@"iPad"]) {
-    return RCNDeviceModelTablet;
-  }
-  return RCNDeviceModelOther;
-}
-
-NSString *FIRRemoteConfigDeviceCountry(void) {
-  return [[[NSLocale currentLocale] objectForKey:NSLocaleCountryCode] lowercaseString];
-}
-
-// TODO(rizafran): To migrate to use ISOLanguageCodes in the future
-NSDictionary<NSString *, NSArray *> *FIRRemoteConfigFirebaseLocaleMap(void) {
-  return @{
-    // Albanian
-    @"sq" : @[ @"sq_AL" ],
-    // Belarusian
-    @"be" : @[ @"be_BY" ],
-    // Bulgarian
-    @"bg" : @[ @"bg_BG" ],
-    // Catalan
-    @"ca" : @[ @"ca", @"ca_ES" ],
-    // Croatian
-    @"hr" : @[ @"hr", @"hr_HR" ],
-    // Czech
-    @"cs" : @[ @"cs", @"cs_CZ" ],
-    // Danish
-    @"da" : @[ @"da", @"da_DK" ],
-    // Estonian
-    @"et" : @[ @"et_EE" ],
-    // Finnish
-    @"fi" : @[ @"fi", @"fi_FI" ],
-    // Hebrew
-    @"he" : @[ @"he", @"iw_IL" ],
-    // Hungarian
-    @"hu" : @[ @"hu", @"hu_HU" ],
-    // Icelandic
-    @"is" : @[ @"is_IS" ],
-    // Indonesian
-    @"id" : @[ @"id", @"in_ID", @"id_ID" ],
-    // Irish
-    @"ga" : @[ @"ga_IE" ],
-    // Korean
-    @"ko" : @[ @"ko", @"ko_KR", @"ko-KR" ],
-    // Latvian
-    @"lv" : @[ @"lv_LV" ],
-    // Lithuanian
-    @"lt" : @[ @"lt_LT" ],
-    // Macedonian
-    @"mk" : @[ @"mk_MK" ],
-    // Malay
-    @"ms" : @[ @"ms_MY" ],
-    // Maltese
-    @"mt" : @[ @"mt_MT" ],
-    // Polish
-    @"pl" : @[ @"pl", @"pl_PL", @"pl-PL" ],
-    // Romanian
-    @"ro" : @[ @"ro", @"ro_RO" ],
-    // Russian
-    @"ru" : @[ @"ru_RU", @"ru", @"ru_BY", @"ru_KZ", @"ru-RU" ],
-    // Slovak
-    @"sk" : @[ @"sk", @"sk_SK" ],
-    // Slovenian
-    @"sl" : @[ @"sl_SI" ],
-    // Swedish
-    @"sv" : @[ @"sv", @"sv_SE", @"sv-SE" ],
-    // Turkish
-    @"tr" : @[ @"tr", @"tr-TR", @"tr_TR" ],
-    // Ukrainian
-    @"uk" : @[ @"uk", @"uk_UA" ],
-    // Vietnamese
-    @"vi" : @[ @"vi", @"vi_VN" ],
-    // The following are groups of locales or locales that sub-divide a
-    // language).
-    // Arabic
-    @"ar" : @[
-      @"ar",    @"ar_DZ", @"ar_BH", @"ar_EG", @"ar_IQ", @"ar_JO", @"ar_KW",
-      @"ar_LB", @"ar_LY", @"ar_MA", @"ar_OM", @"ar_QA", @"ar_SA", @"ar_SD",
-      @"ar_SY", @"ar_TN", @"ar_AE", @"ar_YE", @"ar_GB", @"ar-IQ", @"ar_US"
-    ],
-    // Simplified Chinese
-    @"zh_Hans" : @[ @"zh_CN", @"zh_SG", @"zh-Hans" ],
-    // Traditional Chinese
-    // Remove zh_HK until console added to the list. Otherwise client sends
-    // zh_HK and server/console falls back to zh.
-    // @"zh_Hant" : @[ @"zh_HK", @"zh_TW", @"zh-Hant", @"zh-HK", @"zh-TW" ],
-    @"zh_Hant" : @[ @"zh_TW", @"zh-Hant", @"zh-TW" ],
-    // Dutch
-    @"nl" : @[ @"nl", @"nl_BE", @"nl_NL", @"nl-NL" ],
-    // English
-    @"en" : @[
-      @"en",    @"en_AU", @"en_CA", @"en_IN", @"en_IE", @"en_MT", @"en_NZ", @"en_PH",
-      @"en_SG", @"en_ZA", @"en_GB", @"en_US", @"en_AE", @"en-AE", @"en_AS", @"en-AU",
-      @"en_BD", @"en-CA", @"en_EG", @"en_ES", @"en_GB", @"en-GB", @"en_HK", @"en_ID",
-      @"en-IN", @"en_NG", @"en-PH", @"en_PK", @"en-SG", @"en-US"
-    ],
-    // French
-    @"fr" :
-        @[ @"fr", @"fr_BE", @"fr_CA", @"fr_FR", @"fr_LU", @"fr_CH", @"fr-CA", @"fr-FR", @"fr_MA" ],
-    // German
-    @"de" : @[ @"de", @"de_AT", @"de_DE", @"de_LU", @"de_CH", @"de-DE" ],
-    // Greek
-    @"el" : @[ @"el", @"el_CY", @"el_GR" ],
-    // India
-    @"hi_IN" :
-        @[ @"hi_IN", @"ta_IN", @"te_IN", @"mr_IN", @"bn_IN", @"gu_IN", @"kn_IN", @"pa_Guru_IN" ],
-    // Italian
-    @"it" : @[ @"it", @"it_IT", @"it_CH", @"it-IT" ],
-    // Japanese
-    @"ja" : @[ @"ja", @"ja_JP", @"ja_JP_JP", @"ja-JP" ],
-    // Norwegian
-    @"no" : @[ @"nb", @"no_NO", @"no_NO_NY", @"nb_NO" ],
-    // Brazilian Portuguese
-    @"pt_BR" : @[ @"pt_BR", @"pt-BR" ],
-    // European Portuguese
-    @"pt_PT" : @[ @"pt", @"pt_PT", @"pt-PT" ],
-    // Serbian
-    @"sr" : @[ @"sr_BA", @"sr_ME", @"sr_RS", @"sr_Latn_BA", @"sr_Latn_ME", @"sr_Latn_RS" ],
-    // European Spanish
-    @"es_ES" : @[ @"es", @"es_ES", @"es-ES" ],
-    // Mexican Spanish
-    @"es_MX" : @[ @"es-MX", @"es_MX", @"es_US", @"es-US" ],
-    // Latin American Spanish
-    @"es_419" : @[
-      @"es_AR", @"es_BO", @"es_CL", @"es_CO", @"es_CR", @"es_DO", @"es_EC",
-      @"es_SV", @"es_GT", @"es_HN", @"es_NI", @"es_PA", @"es_PY", @"es_PE",
-      @"es_PR", @"es_UY", @"es_VE", @"es-AR", @"es-CL", @"es-CO"
-    ],
-    // Thai
-    @"th" : @[ @"th", @"th_TH", @"th_TH_TH" ],
-  };
-}
-
-NSArray<NSString *> *FIRRemoteConfigAppManagerLocales(void) {
-  NSMutableArray *locales = [NSMutableArray array];
-  NSDictionary<NSString *, NSArray *> *localesMap = FIRRemoteConfigFirebaseLocaleMap();
-  for (NSString *key in localesMap) {
-    [locales addObjectsFromArray:localesMap[key]];
-  }
-  return locales;
-}
-
-NSString *FIRRemoteConfigDeviceLocale(void) {
-  NSArray<NSString *> *locales = FIRRemoteConfigAppManagerLocales();
-  NSArray<NSString *> *preferredLocalizations =
-      [NSBundle preferredLocalizationsFromArray:locales
-                                 forPreferences:[NSLocale preferredLanguages]];
-  NSString *legalDocsLanguage = [preferredLocalizations firstObject];
-  // Use en as the default language
-  return legalDocsLanguage ? legalDocsLanguage : @"en";
-}
-
-NSString *FIRRemoteConfigTimezone(void) {
-  NSTimeZone *timezone = [NSTimeZone systemTimeZone];
-  return timezone.name;
-}
-
-NSMutableDictionary *FIRRemoteConfigDeviceContextWithProjectIdentifier(
-    NSString *GMPProjectIdentifier) {
-  NSMutableDictionary *deviceContext = [[NSMutableDictionary alloc] init];
-  deviceContext[RCNDeviceContextKeyVersion] = FIRRemoteConfigAppVersion();
-  deviceContext[RCNDeviceContextKeyBuild] = FIRRemoteConfigAppBuildVersion();
-  deviceContext[RCNDeviceContextKeyOSVersion] = [GULAppEnvironmentUtil systemVersion];
-  deviceContext[RCNDeviceContextKeyDeviceLocale] = FIRRemoteConfigDeviceLocale();
-  // NSDictionary setObjectForKey will fail if there's no GMP project ID, must check ahead.
-  if (GMPProjectIdentifier) {
-    deviceContext[RCNDeviceContextKeyGMPProjectIdentifier] = GMPProjectIdentifier;
-  }
-  return deviceContext;
-}
-
-BOOL FIRRemoteConfigHasDeviceContextChanged(NSDictionary *deviceContext,
-                                            NSString *GMPProjectIdentifier) {
-  if (![deviceContext[RCNDeviceContextKeyVersion] isEqual:FIRRemoteConfigAppVersion()]) {
-    return YES;
-  }
-  if (![deviceContext[RCNDeviceContextKeyBuild] isEqual:FIRRemoteConfigAppBuildVersion()]) {
-    return YES;
-  }
-  if (![deviceContext[RCNDeviceContextKeyOSVersion]
-          isEqual:[GULAppEnvironmentUtil systemVersion]]) {
-    return YES;
-  }
-  if (![deviceContext[RCNDeviceContextKeyDeviceLocale] isEqual:FIRRemoteConfigDeviceLocale()]) {
-    return YES;
-  }
-  // GMP project id is optional.
-  if (deviceContext[RCNDeviceContextKeyGMPProjectIdentifier] &&
-      ![deviceContext[RCNDeviceContextKeyGMPProjectIdentifier] isEqual:GMPProjectIdentifier]) {
-    return YES;
-  }
-  return NO;
-}

+ 199 - 0
FirebaseRemoteConfig/Sources/RCNDevice.swift

@@ -0,0 +1,199 @@
+import Foundation
+import GoogleUtilities
+class RCNDevice {
+    enum Model {
+        case Phone
+        case Tablet
+        case Other
+    }
+    static func appVersion() -> String {
+        return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
+    }
+
+    static func appBuildVersion() -> String {
+        return Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? ""
+    }
+
+    static func podVersion() -> String {
+        return FIRFirebaseVersion();
+    }
+
+    static func deviceSubtype() -> Model {
+        let model = GULAppEnvironmentUtil.deviceModel()
+        if model.hasPrefix("iPhone") {
+            return .Phone
+        }
+        if model == "iPad" {
+            return .Tablet
+        }
+        return .Other
+    }
+
+    static func deviceCountry() -> String {
+        return (Locale.current.object(forKey: .countryCode) as? String ?? "").lowercased()
+    }
+
+    static func firebaseLocaleMap() -> [String: [String]] {
+        return [
+          // Albanian
+          "sq" : [ "sq_AL" ],
+          // Belarusian
+          "be" : [ "be_BY" ],
+          // Bulgarian
+          "bg" : [ "bg_BG" ],
+          // Catalan
+          "ca" : [ "ca", "ca_ES" ],
+          // Croatian
+          "hr" : [ "hr", "hr_HR" ],
+          // Czech
+          "cs" : [ "cs", "cs_CZ" ],
+          // Danish
+          "da" : [ "da", "da_DK" ],
+          // Estonian
+          "et" : [ "et_EE" ],
+          // Finnish
+          "fi" : [ "fi", "fi_FI" ],
+          // Hebrew
+          "he" : [ "he", "iw_IL" ],
+          // Hungarian
+          "hu" : [ "hu", "hu_HU" ],
+          // Icelandic
+          "is" : [ "is_IS" ],
+          // Indonesian
+          "id" : [ "id", "in_ID", "id_ID" ],
+          // Irish
+          "ga" : [ "ga_IE" ],
+          // Korean
+          "ko" : [ "ko", "ko_KR", "ko-KR" ],
+          // Latvian
+          "lv" : [ "lv_LV" ],
+          // Lithuanian
+          "lt" : [ "lt_LT" ],
+          // Macedonian
+          "mk" : [ "mk_MK" ],
+          // Malay
+          "ms" : [ "ms_MY" ],
+          // Maltese
+          "mt" : [ "mt_MT" ],
+          // Polish
+          "pl" : [ "pl", "pl_PL", "pl-PL" ],
+          // Romanian
+          "ro" : [ "ro", "ro_RO" ],
+          // Russian
+          "ru" : [ "ru_RU", "ru", "ru_BY", "ru_KZ", "ru-RU" ],
+          // Slovak
+          "sk" : [ "sk", "sk_SK" ],
+          // Slovenian
+          "sl" : [ "sl_SI" ],
+          // Swedish
+          "sv" : [ "sv", "sv_SE", "sv-SE" ],
+          // Turkish
+          "tr" : [ "tr", "tr-TR", "tr_TR" ],
+          // Ukrainian
+          "uk" : [ "uk", "uk_UA" ],
+          // Vietnamese
+          "vi" : [ "vi", "vi_VN" ],
+          // The following are groups of locales or locales that sub-divide a
+          // language).
+          // Arabic
+          "ar" : [
+            "ar",    "ar_DZ", "ar_BH", "ar_EG", "ar_IQ", "ar_JO", "ar_KW",
+            "ar_LB", "ar_LY", "ar_MA", "ar_OM", "ar_QA", "ar_SA", "ar_SD",
+            "ar_SY", "ar_TN", "ar_AE", "ar_YE", "ar_GB", "ar-IQ", "ar_US"
+          ],
+          // Simplified Chinese
+          "zh_Hans" : [ "zh_CN", "zh_SG", "zh-Hans" ],
+          // Traditional Chinese
+          // Remove zh_HK until console added to the list. Otherwise client sends
+          // zh_HK and server/console falls back to zh.
+          // @"zh_Hant" : [ "zh_HK", "zh_TW", "zh-Hant", "zh-HK", "zh-TW" ],
+          "zh_Hant" : [ "zh_TW", "zh-Hant", "zh-TW" ],
+          // Dutch
+          "nl" : [ "nl", "nl_BE", "nl_NL", "nl-NL" ],
+          // English
+          "en" : [
+            "en",    "en_AU", "en_CA", "en_IN", "en_IE", "en_MT", "en_NZ", "en_PH",
+            "en_SG", "en_ZA", "en_GB", "en_US", "en_AE", "en-AE", "en_AS", "en-AU",
+            "en_BD", "en-CA", "en_EG", "en_ES", "en_GB", "en-GB", "en_HK", "en_ID",
+            "en-IN", "en_NG", "en-PH", "en_PK", "en-SG", "en-US"
+          ],
+          // French
+          "fr" :
+              [ "fr", "fr_BE", "fr_CA", "fr_FR", "fr_LU", "fr_CH", "fr-CA", "fr-FR", "fr_MA" ],
+          // German
+          "de" : [ "de", "de_AT", "de_DE", "de_LU", "de_CH", "de-DE" ],
+          // Greek
+          "el" : [ "el", "el_CY", "el_GR" ],
+          // India
+          "hi_IN" :
+              [ "hi_IN", "ta_IN", "te_IN", "mr_IN", "bn_IN", "gu_IN", "kn_IN", "pa_Guru_IN" ],
+          // Italian
+          "it" : [ "it", "it_IT", "it_CH", "it-IT" ],
+          // Japanese
+          "ja" : [ "ja", "ja_JP", "ja_JP_JP", "ja-JP" ],
+          // Norwegian
+          "no" : [ "nb", "no_NO", "no_NO_NY", "nb_NO" ],
+          // Brazilian Portuguese
+          "pt_BR" : [ "pt_BR", "pt-BR" ],
+          // European Portuguese
+          "pt_PT" : [ "pt", "pt_PT", "pt-PT" ],
+          // Serbian
+          "sr" : [ "sr_BA", "sr_ME", "sr_RS", "sr_Latn_BA", "sr_Latn_ME", "sr_Latn_RS" ],
+          // Spanish
+          "es_ES" : [ "es", "es_ES", "es-ES" ],
+          // Mexican Spanish
+          "es_MX" : [ "es-MX", "es_MX", "es_US", "es-US" ],
+          // Latin American Spanish
+          "es_419" : [
+            "es_AR", "es_BO", "es_CL", "es_CO", "es_CR", "es_DO", "es_EC",
+            "es_SV", "es_GT", "es_HN", "es_NI", "es_PA", "es_PY", "es_PE",
+            "es_PR", "es_UY", "es_VE", "es-AR", "es-CL", "es-CO"
+          ],
+          // Thai
+          "th" : [ "th", "th_TH", "th_TH_TH" ],
+        ];
+    }
+
+    static func deviceLocale() -> String {
+        let locales = FIRRemoteConfigAppManagerLocales()
+        let preferredLocalizations = NSBundle.preferredLocalizations(from: locales, forPreferences: Locale.preferredLanguages)
+        // Use en as the default language
+        return legalDocsLanguage ?? "en"
+    }
+
+    static func timezone() -> String {
+        let timezone = TimeZone.system
+        return timezone.identifier
+    }
+    
+    static func deviceContextWithProjectIdentifier(GMPProjectIdentifier:String) -> NSMutableDictionary<String,Any> {
+        let deviceContext = NSMutableDictionary<String, Any>()
+        deviceContext[RCNConstants.RCNDeviceContextKeyVersion] = FIRRemoteConfigAppVersion()
+        deviceContext[RCNConstants.RCNDeviceContextKeyBuild] = FIRRemoteConfigAppBuildVersion()
+        deviceContext[RCNConstants.RCNDeviceContextKeyOSVersion] = GULAppEnvironmentUtil.systemVersion()
+        deviceContext[RCNConstants.RCNDeviceContextKeyDeviceLocale] = FIRRemoteConfigDeviceLocale()
+        deviceContext[RCNConstants.RCNDeviceContextKeyGMPProjectIdentifier] = GMPProjectIdentifier
+        return deviceContext
+    }
+    
+    static func hasDeviceContextChanged(deviceContext: [String : Any], GMPProjectIdentifier: String) -> Bool {
+        if (!(deviceContext[RCNConstants.RCNDeviceContextKeyVersion] as! String).isEqual(FIRRemoteConfigAppVersion()) {
+            return true;
+        }
+        if (!(deviceContext[RCNDeviceContextKeyBuild] as! String).isEqual(FIRRemoteConfigAppBuildVersion()) {
+            return true
+        }
+        if !(deviceContext[RCNDeviceContextKeyOSVersion] as! String).isEqual(GULAppEnvironmentUtil.systemVersion()) {
+            return true
+        }
+        if !(deviceContext[RCNDeviceContextKeyDeviceLocale] as! String).isEqual(FIRRemoteConfigDeviceLocale()) {
+            return true
+        }
+        // GMP project id is optional.
+        if deviceContext[RCNDeviceContextKeyGMPProjectIdentifier] != nil &&
+            !((deviceContext[RCNDeviceContextKeyGMPProjectIdentifier] as! String).isEqual(GMPProjectIdentifier)) {
+            return true
+        }
+        return false
+    }
+}

+ 0 - 72
FirebaseRemoteConfig/Sources/RCNPersonalization.m

@@ -1,72 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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/RCNPersonalization.h"
-
-#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigValue_Internal.h"
-
-@implementation RCNPersonalization
-
-- (instancetype)initWithAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {
-  self = [super init];
-  if (self) {
-    self->_analytics = analytics;
-    self->_loggedChoiceIds = [[NSMutableDictionary alloc] init];
-  }
-  return self;
-}
-
-- (void)logArmActive:(NSString *)rcParameter config:(NSDictionary *)config {
-  NSDictionary *ids = config[RCNFetchResponseKeyPersonalizationMetadata];
-  NSDictionary<NSString *, FIRRemoteConfigValue *> *values = config[RCNFetchResponseKeyEntries];
-  if (ids.count < 1 || values.count < 1 || !values[rcParameter]) {
-    return;
-  }
-
-  NSDictionary *metadata = ids[rcParameter];
-  if (!metadata) {
-    return;
-  }
-
-  NSString *choiceId = metadata[kChoiceId];
-  if (choiceId == nil) {
-    return;
-  }
-
-  // Listeners like logArmActive() are dispatched to a serial queue, so loggedChoiceIds should
-  // contain any previously logged RC parameter / choice ID pairs.
-  if (self->_loggedChoiceIds[rcParameter] == choiceId) {
-    return;
-  }
-  self->_loggedChoiceIds[rcParameter] = choiceId;
-
-  [self->_analytics logEventWithOrigin:kAnalyticsOriginPersonalization
-                                  name:kExternalEvent
-                            parameters:@{
-                              kExternalRcParameterParam : rcParameter,
-                              kExternalArmValueParam : values[rcParameter].stringValue,
-                              kExternalPersonalizationIdParam : metadata[kPersonalizationId],
-                              kExternalArmIndexParam : metadata[kArmIndex],
-                              kExternalGroupParam : metadata[kGroup]
-                            }];
-
-  [self->_analytics logEventWithOrigin:kAnalyticsOriginPersonalization
-                                  name:kInternalEvent
-                            parameters:@{kInternalChoiceIdParam : choiceId}];
-}
-
-@end

+ 32 - 0
FirebaseRemoteConfig/Sources/RCNPersonalization.swift

@@ -0,0 +1,32 @@
+import Foundation
+
+class RCNPersonalization: NSObject {
+    private var _analytics: FIRAnalyticsInterop?
+    private var _loggedChoiceIds: NSMutableDictionary?
+    
+    init(analytics: FIRAnalyticsInterop?) {
+        self._analytics = analytics
+        self._loggedChoiceIds = [:]
+    }
+    
+    func logArmActive(rcParameter: String, config: [AnyHashable:Any]) {
+        guard let ids = config[RCNFetchResponseKeyPersonalizationMetadata] as? [String: Any],
+            let values = config[RCNFetchResponseKeyEntries] as? [String: FIRRemoteConfigValue] else { return }
+
+        guard let metadata = ids[rcParameter] as? [String : Any] else { return }
+        
+        guard let choiceId = metadata[kChoiceId] as? String else { return }
+      
+      // Listeners like logArmActive() are dispatched to a serial queue, so loggedChoiceIds should
+      // contain any previously logged RC parameter / choice ID pairs.
+        if self._loggedChoiceIds?[rcParameter] as? String == choiceId {
+            return
+        }
+        
+        self._loggedChoiceIds?[rcParameter] = choiceId
+
+        self._analytics?.logEventWithOrigin(origin: kAnalyticsOriginPersonalization, name: kExternalEvent, parameters: [kExternalRcParameterParam: rcParameter, kExternalArmValueParam: values[rcParameter]!.stringValue, kExternalPersonalizationIdParam: metadata[kPersonalizationId] ?? "" , kExternalArmIndexParam: metadata[kArmIndex] ?? "", kExternalGroupParam: metadata[kGroup] ?? ""])
+
+        self._analytics?.logEvent(withOrigin: kAnalyticsOriginPersonalization, name: kInternalEvent, parameters: [kInternalChoiceIdParam : choiceId])
+    }
+}

+ 0 - 329
FirebaseRemoteConfig/Sources/RCNUserDefaultsManager.m

@@ -1,329 +0,0 @@
-/*
- * Copyright 2019 Google
- *
- * 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/RCNUserDefaultsManager.h"
-#import "FirebaseCore/Extension/FirebaseCoreInternal.h"
-#import "FirebaseRemoteConfig/Sources/Public/FirebaseRemoteConfig/FIRRemoteConfig.h"
-#import "FirebaseRemoteConfig/Sources/RCNConfigConstants.h"
-
-static NSString *const kRCNGroupPrefix = @"group";
-static NSString *const kRCNGroupSuffix = @"firebase";
-static NSString *const kRCNUserDefaultsKeyNamelastETag = @"lastETag";
-static NSString *const kRCNUserDefaultsKeyNamelastETagUpdateTime = @"lastETagUpdateTime";
-static NSString *const kRCNUserDefaultsKeyNameLastSuccessfulFetchTime = @"lastSuccessfulFetchTime";
-static NSString *const kRCNUserDefaultsKeyNamelastFetchStatus = @"lastFetchStatus";
-static NSString *const kRCNUserDefaultsKeyNameIsClientThrottled =
-    @"isClientThrottledWithExponentialBackoff";
-static NSString *const kRCNUserDefaultsKeyNameThrottleEndTime = @"throttleEndTime";
-static NSString *const kRCNUserDefaultsKeyNamecurrentThrottlingRetryInterval =
-    @"currentThrottlingRetryInterval";
-static NSString *const kRCNUserDefaultsKeyNameRealtimeThrottleEndTime = @"throttleRealtimeEndTime";
-static NSString *const kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval =
-    @"currentRealtimeThrottlingRetryInterval";
-static NSString *const kRCNUserDefaultsKeyNameRealtimeRetryCount = @"realtimeRetryCount";
-static NSString *const kRCNUserDefaultsKeyCustomSignals = @"customSignals";
-
-@interface RCNUserDefaultsManager () {
-  /// User Defaults instance for this bundleID. NSUserDefaults is guaranteed to be thread-safe.
-  NSUserDefaults *_userDefaults;
-  /// The suite name for this user defaults instance. It is a combination of a prefix and the
-  /// bundleID. This is because you cannot use just the bundleID of the current app as the suite
-  /// name when initializing user defaults.
-  NSString *_userDefaultsSuiteName;
-  /// The FIRApp that this instance is scoped within.
-  NSString *_firebaseAppName;
-  /// The Firebase Namespace that this instance is scoped within.
-  NSString *_firebaseNamespace;
-  /// The bundleID of the app. In case of an extension, this will be the bundleID of the parent app.
-  NSString *_bundleIdentifier;
-}
-
-@end
-
-@implementation RCNUserDefaultsManager
-
-#pragma mark Initializers.
-
-/// Designated initializer.
-- (instancetype)initWithAppName:(NSString *)appName
-                       bundleID:(NSString *)bundleIdentifier
-                      namespace:(NSString *)firebaseNamespace {
-  self = [super init];
-  if (self) {
-    _firebaseAppName = appName;
-    _bundleIdentifier = bundleIdentifier;
-    NSInteger location = [firebaseNamespace rangeOfString:@":"].location;
-    if (location == NSNotFound) {
-      FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000064",
-                  @"Error: Namespace %@ is not fully qualified app:namespace.", firebaseNamespace);
-      _firebaseNamespace = firebaseNamespace;
-    } else {
-      _firebaseNamespace = [firebaseNamespace substringToIndex:location];
-    }
-
-    // Initialize the user defaults with a prefix and the bundleID. For app extensions, this will be
-    // the bundleID of the app extension.
-    _userDefaults =
-        [RCNUserDefaultsManager sharedUserDefaultsForBundleIdentifier:_bundleIdentifier];
-  }
-
-  return self;
-}
-
-+ (NSUserDefaults *)sharedUserDefaultsForBundleIdentifier:(NSString *)bundleIdentifier {
-  static dispatch_once_t onceToken;
-  static NSUserDefaults *sharedInstance;
-  dispatch_once(&onceToken, ^{
-    NSString *userDefaultsSuiteName =
-        [RCNUserDefaultsManager userDefaultsSuiteNameForBundleIdentifier:bundleIdentifier];
-    sharedInstance = [[NSUserDefaults alloc] initWithSuiteName:userDefaultsSuiteName];
-  });
-  return sharedInstance;
-}
-
-+ (NSString *)userDefaultsSuiteNameForBundleIdentifier:(NSString *)bundleIdentifier {
-  NSString *suiteName =
-      [NSString stringWithFormat:@"%@.%@.%@", kRCNGroupPrefix, bundleIdentifier, kRCNGroupSuffix];
-  return suiteName;
-}
-
-#pragma mark Public properties.
-
-- (NSString *)lastETag {
-  return [[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNamelastETag];
-}
-
-- (void)setLastETag:(NSString *)lastETag {
-  if (lastETag) {
-    [self setInstanceUserDefaultsValue:lastETag forKey:kRCNUserDefaultsKeyNamelastETag];
-  }
-}
-
-- (NSString *)lastFetchedTemplateVersion {
-  NSDictionary *userDefaults = [self instanceUserDefaults];
-  if ([userDefaults objectForKey:RCNFetchResponseKeyTemplateVersion]) {
-    return [userDefaults objectForKey:RCNFetchResponseKeyTemplateVersion];
-  }
-
-  return @"0";
-}
-
-- (void)setLastFetchedTemplateVersion:(NSString *)templateVersion {
-  if (templateVersion) {
-    [self setInstanceUserDefaultsValue:templateVersion forKey:RCNFetchResponseKeyTemplateVersion];
-  }
-}
-
-- (NSString *)lastActiveTemplateVersion {
-  NSDictionary *userDefaults = [self instanceUserDefaults];
-  if ([userDefaults objectForKey:RCNActiveKeyTemplateVersion]) {
-    return [userDefaults objectForKey:RCNActiveKeyTemplateVersion];
-  }
-
-  return @"0";
-}
-
-- (void)setLastActiveTemplateVersion:(NSString *)templateVersion {
-  if (templateVersion) {
-    [self setInstanceUserDefaultsValue:templateVersion forKey:RCNActiveKeyTemplateVersion];
-  }
-}
-
-- (NSDictionary<NSString *, NSString *> *)customSignals {
-  NSDictionary *userDefaults = [self instanceUserDefaults];
-  if ([userDefaults objectForKey:kRCNUserDefaultsKeyCustomSignals]) {
-    return [userDefaults objectForKey:kRCNUserDefaultsKeyCustomSignals];
-  }
-
-  return [[NSDictionary<NSString *, NSString *> alloc] init];
-}
-
-- (void)setCustomSignals:(NSDictionary<NSString *, NSString *> *)customSignals {
-  if (customSignals) {
-    [self setInstanceUserDefaultsValue:customSignals forKey:kRCNUserDefaultsKeyCustomSignals];
-  }
-}
-
-- (NSTimeInterval)lastETagUpdateTime {
-  NSNumber *lastETagUpdateTime =
-      [[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNamelastETagUpdateTime];
-  return lastETagUpdateTime.doubleValue;
-}
-
-- (void)setLastETagUpdateTime:(NSTimeInterval)lastETagUpdateTime {
-  if (lastETagUpdateTime) {
-    [self setInstanceUserDefaultsValue:@(lastETagUpdateTime)
-                                forKey:kRCNUserDefaultsKeyNamelastETagUpdateTime];
-  }
-}
-
-- (NSTimeInterval)lastFetchTime {
-  NSNumber *lastFetchTime =
-      [[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameLastSuccessfulFetchTime];
-  return lastFetchTime.doubleValue;
-}
-
-- (void)setLastFetchTime:(NSTimeInterval)lastFetchTime {
-  [self setInstanceUserDefaultsValue:@(lastFetchTime)
-                              forKey:kRCNUserDefaultsKeyNameLastSuccessfulFetchTime];
-}
-
-- (NSString *)lastFetchStatus {
-  return [[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNamelastFetchStatus];
-}
-
-- (void)setLastFetchStatus:(NSString *)lastFetchStatus {
-  if (lastFetchStatus) {
-    [self setInstanceUserDefaultsValue:lastFetchStatus
-                                forKey:kRCNUserDefaultsKeyNamelastFetchStatus];
-  }
-}
-
-- (BOOL)isClientThrottledWithExponentialBackoff {
-  NSNumber *isClientThrottled =
-      [[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameIsClientThrottled];
-  return isClientThrottled.boolValue;
-}
-
-- (void)setIsClientThrottledWithExponentialBackoff:(BOOL)isClientThrottled {
-  [self setInstanceUserDefaultsValue:@(isClientThrottled)
-                              forKey:kRCNUserDefaultsKeyNameIsClientThrottled];
-}
-
-- (NSTimeInterval)throttleEndTime {
-  NSNumber *throttleEndTime =
-      [[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameThrottleEndTime];
-  return throttleEndTime.doubleValue;
-}
-
-- (void)setThrottleEndTime:(NSTimeInterval)throttleEndTime {
-  [self setInstanceUserDefaultsValue:@(throttleEndTime)
-                              forKey:kRCNUserDefaultsKeyNameThrottleEndTime];
-}
-
-- (NSTimeInterval)currentThrottlingRetryIntervalSeconds {
-  NSNumber *throttleEndTime = [[self instanceUserDefaults]
-      objectForKey:kRCNUserDefaultsKeyNamecurrentThrottlingRetryInterval];
-  return throttleEndTime.doubleValue;
-}
-
-- (void)setCurrentThrottlingRetryIntervalSeconds:(NSTimeInterval)throttlingRetryIntervalSeconds {
-  [self setInstanceUserDefaultsValue:@(throttlingRetryIntervalSeconds)
-                              forKey:kRCNUserDefaultsKeyNamecurrentThrottlingRetryInterval];
-}
-
-- (int)realtimeRetryCount {
-  int realtimeRetryCount = 0;
-  if ([[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameRealtimeRetryCount]) {
-    realtimeRetryCount = [[[self instanceUserDefaults]
-        objectForKey:kRCNUserDefaultsKeyNameRealtimeRetryCount] intValue];
-  }
-
-  return realtimeRetryCount;
-}
-
-- (void)setRealtimeRetryCount:(int)realtimeRetryCount {
-  [self setInstanceUserDefaultsValue:[NSNumber numberWithInt:realtimeRetryCount]
-                              forKey:kRCNUserDefaultsKeyNameRealtimeRetryCount];
-}
-
-- (NSTimeInterval)realtimeThrottleEndTime {
-  NSNumber *realtimeThrottleEndTime = 0;
-  if ([[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameRealtimeThrottleEndTime]) {
-    realtimeThrottleEndTime =
-        [[self instanceUserDefaults] objectForKey:kRCNUserDefaultsKeyNameRealtimeThrottleEndTime];
-  }
-  return realtimeThrottleEndTime.doubleValue;
-}
-
-- (void)setRealtimeThrottleEndTime:(NSTimeInterval)throttleEndTime {
-  [self setInstanceUserDefaultsValue:@(throttleEndTime)
-                              forKey:kRCNUserDefaultsKeyNameRealtimeThrottleEndTime];
-}
-
-- (NSTimeInterval)currentRealtimeThrottlingRetryIntervalSeconds {
-  NSNumber *realtimeThrottleEndTime = 0;
-  if ([[self instanceUserDefaults]
-          objectForKey:kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval]) {
-    realtimeThrottleEndTime = [[self instanceUserDefaults]
-        objectForKey:kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval];
-  }
-  return realtimeThrottleEndTime.doubleValue;
-}
-
-- (void)setCurrentRealtimeThrottlingRetryIntervalSeconds:
-    (NSTimeInterval)throttlingRetryIntervalSeconds {
-  [self setInstanceUserDefaultsValue:@(throttlingRetryIntervalSeconds)
-                              forKey:kRCNUserDefaultsKeyNameCurrentRealtimeThrottlingRetryInterval];
-}
-
-#pragma mark Public methods.
-- (void)resetUserDefaults {
-  [self resetInstanceUserDefaults];
-}
-
-#pragma mark Private methods.
-
-// There is a nested hierarchy for the userdefaults as follows:
-// [FIRAppName][FIRNamespaceName][Key]
-- (nonnull NSDictionary *)appUserDefaults {
-  NSString *appPath = _firebaseAppName;
-  NSDictionary *appDict = [_userDefaults valueForKeyPath:appPath];
-  if (!appDict) {
-    appDict = [[NSDictionary alloc] init];
-  }
-  return appDict;
-}
-
-// Search for the user defaults for this (app, namespace) instance using the valueForKeyPath method.
-- (nonnull NSDictionary *)instanceUserDefaults {
-  NSString *appNamespacePath =
-      [NSString stringWithFormat:@"%@.%@", _firebaseAppName, _firebaseNamespace];
-  NSDictionary *appNamespaceDict = [_userDefaults valueForKeyPath:appNamespacePath];
-
-  if (!appNamespaceDict) {
-    appNamespaceDict = [[NSMutableDictionary alloc] init];
-  }
-  return appNamespaceDict;
-}
-
-// Update users defaults for just this (app, namespace) instance.
-- (void)setInstanceUserDefaultsValue:(NSObject *)value forKey:(NSString *)key {
-  @synchronized(_userDefaults) {
-    NSMutableDictionary *appUserDefaults = [[self appUserDefaults] mutableCopy];
-    NSMutableDictionary *appNamespaceUserDefaults = [[self instanceUserDefaults] mutableCopy];
-    [appNamespaceUserDefaults setObject:value forKey:key];
-    [appUserDefaults setObject:appNamespaceUserDefaults forKey:_firebaseNamespace];
-    [_userDefaults setObject:appUserDefaults forKey:_firebaseAppName];
-    // We need to synchronize to have this value updated for the extension.
-    [_userDefaults synchronize];
-  }
-}
-
-// Delete any existing userdefaults for this instance.
-- (void)resetInstanceUserDefaults {
-  @synchronized(_userDefaults) {
-    NSMutableDictionary *appUserDefaults = [[self appUserDefaults] mutableCopy];
-    NSMutableDictionary *appNamespaceUserDefaults = [[self instanceUserDefaults] mutableCopy];
-    [appNamespaceUserDefaults removeAllObjects];
-    [appUserDefaults setObject:appNamespaceUserDefaults forKey:_firebaseNamespace];
-    [_userDefaults setObject:appUserDefaults forKey:_firebaseAppName];
-    // We need to synchronize to have this value updated for the extension.
-    [_userDefaults synchronize];
-  }
-}
-
-@end