FIRRemoteConfig.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import Foundation
  2. import FirebaseABTesting
  3. import FirebaseCore
  4. class FIRRemoteConfigSettings {
  5. var minimumFetchInterval: TimeInterval = RCNConstants.RCNDefaultMinimumFetchInterval
  6. var fetchTimeout: TimeInterval = RCNConstants.RCNHTTPDefaultConnectionTimeout
  7. init() {}
  8. }
  9. class FIRRemoteConfig {
  10. static var RCInstances: [String: [String: FIRRemoteConfig]] = [:]
  11. static var sharedRemoteConfigQueue: DispatchQueue = DispatchQueue(label: RCNConstants.RCNRemoteConfigQueueLabel)
  12. var configContent: RCNConfigContent?
  13. var DBManager: RCNConfigDBManager?
  14. var settings: FIRRemoteConfigSettings?
  15. var configFetch: RCNConfigFetch?
  16. var configExperiment: RCNConfigExperiment?
  17. var configRealtime: RCNConfigRealtime?
  18. var queue: DispatchQueue?
  19. var appName: String?
  20. var listeners: [((String, [String: Any])) -> Void]?
  21. private var _FIRNamespace: String = ""
  22. private var _options: FIROptions?
  23. static func remoteConfig(app: FIRApp) -> FIRRemoteConfig {
  24. return remoteConfig(FIRNamespace: RCNConstants.FIRNamespaceGoogleMobilePlatform, app: app)
  25. }
  26. static func remoteConfig(FIRNamespace: String) -> FIRRemoteConfig {
  27. guard let app = FIRApp.defaultApp else {
  28. fatalError("The default `FirebaseApp` instance must be configured before the " +
  29. "default Remote Config instance can be initialized. One way to ensure this " +
  30. "is to call `FirebaseApp.configure()` in the App Delegate's " +
  31. "`application(_:didFinishLaunchingWithOptions:)` or the `@main` struct's " +
  32. "initializer in SwiftUI.")
  33. }
  34. return remoteConfig(FIRNamespace: FIRNamespace, app: app)
  35. }
  36. static func remoteConfig(FIRNamespace: String, app: FIRApp) -> FIRRemoteConfig {
  37. // Use the provider to generate and return instances of FIRRemoteConfig for this specific app and
  38. // namespace. This will ensure the app is configured before Remote Config can return an instance.
  39. guard let provider = FIRComponentContainer.shared.getComponent(FIRRemoteConfigProvider.self) else {
  40. fatalError("Component not found")
  41. }
  42. return provider.remoteConfigForNamespace(firebaseNamespace: FIRNamespace)
  43. }
  44. static func remoteConfig() -> FIRRemoteConfig {
  45. guard let app = FIRApp.defaultApp else {
  46. fatalError("The default `FirebaseApp` instance must be configured before the " +
  47. "default Remote Config instance can be initialized. One way to ensure this " +
  48. "is to call `FirebaseApp.configure()` in the App Delegate's " +
  49. "`application(_:didFinishLaunchingWithOptions:)` or the `@main` struct's " +
  50. "initializer in SwiftUI.")
  51. }
  52. return remoteConfig(FIRNamespace: RCNConstants.FIRNamespaceGoogleMobilePlatform, app: app)
  53. }
  54. static func sharedRemoteConfigSerialQueue() -> DispatchQueue {
  55. return FIRRemoteConfig.sharedRemoteConfigSerialQueue()
  56. }
  57. init(appName: String, FIRNamespace: String, options: FIROptions,
  58. DBManager: RCNConfigDBManager, configContent: RCNConfigContent,
  59. analytics: FIRAnalyticsInterop?) {
  60. }
  61. func callListeners(key: String, config: [String: Any]) {
  62. for listener in self.listeners ?? [] {
  63. listener(key, config)
  64. }
  65. }
  66. func setCustomSignals(customSignals: [String: NSObject], completionHandler: ((Error?) -> Void)?) {
  67. // Validate value type, and key and value length
  68. for (key, value) in customSignals {
  69. if let value = value as? NSNull {
  70. continue
  71. }
  72. if let value = value as? NSString {
  73. if value.count > FIRRemoteConfigCustomSignalsMaxStringValueLength {
  74. let error = NSError(domain: FIRRemoteConfigCustomSignalsErrorDomain, code: FIRRemoteConfigCustomSignalsErrorLimitExceeded, userInfo: nil)
  75. completionHandler?(error)
  76. return
  77. }
  78. }
  79. if key.count > FIRRemoteConfigCustomSignalsMaxKeyLength {
  80. let error = NSError(domain: FIRRemoteConfigCustomSignalsErrorDomain, code: FIRRemoteConfigCustomSignalsErrorLimitExceeded, userInfo: nil)
  81. completionHandler?(error)
  82. return
  83. }
  84. // Check the size limit.
  85. if customSignals.count > FIRRemoteConfigCustomSignalsMaxCount {
  86. let error = NSError(domain: FIRRemoteConfigCustomSignalsErrorDomain, code: FIRRemoteConfigCustomSignalsErrorLimitExceeded, userInfo: nil)
  87. completionHandler?(error)
  88. return
  89. }
  90. }
  91. // Merge new signals with existing ones, overwriting existing keys.
  92. // Also, remove entries where the new value is null.
  93. var newCustomSignals = [String: String]()
  94. for (key, value) in customSignals {
  95. if (value is NSNull) {
  96. [newCustomSignals removeObjectForKey:key];
  97. } else {
  98. NSString *stringValue = nil;
  99. if ([value isKindOfClass:[NSNumber class]]) {
  100. stringValue = [(NSNumber *)value stringValue];
  101. } else if ([value isKindOfClass:[NSString class]]) {
  102. stringValue = (NSString *)value;
  103. }
  104. [newCustomSignals setObject:stringValue forKey:key];
  105. }
  106. }
  107. // Check if there are changes
  108. if (newCustomSignals != self.settings?.customSignals) {
  109. self->_settings?.customSignals = newCustomSignals;
  110. }
  111. // Log the keys of the updated custom signals.
  112. FIRLogDebug(RCNRemoteConfigQueueLabel, @"I-RCN000078", "Keys of updated custom signals: %@",
  113. [newCustomSignals allKeys].joined(separator: ", "));
  114. if (completionHandler != nil) {
  115. completionHandler(nil);
  116. }
  117. }
  118. func fetchWithExpirationDuration(expirationDuration: TimeInterval,
  119. completionHandler: FIRRemoteConfigFetchCompletion?) {
  120. self->_configFetch?.fetchConfigWithExpirationDuration(expirationDuration: expirationDuration,
  121. completionHandler: completionHandler)
  122. }
  123. func fetchWithCompletionHandler(completionHandler: FIRRemoteConfigFetchCompletion?) {
  124. fetchWithExpirationDuration(expirationDuration: self.settings?.minimumFetchInterval ?? 0,
  125. completionHandler: completionHandler)
  126. }
  127. func fetchAndActivateWithCompletionHandler(completionHandler:
  128. FIRRemoteConfigFetchAndActivateCompletion?) {
  129. let fetchCompletion =
  130. {(_ fetchStatus: FIRRemoteConfigFetchStatus, fetchError: NSError?) in
  131. if fetchStatus == .success && fetchError == nil {
  132. [self activateWithCompletion:^(Bool changed, NSError * _Nullable activateError) {
  133. if (completionHandler) {
  134. let status =
  135. activateError ? FIRRemoteConfigFetchAndActivateStatus.successUsingPreFetchedData
  136. : FIRRemoteConfigFetchAndActivateStatus.successFetchedFromRemote
  137. dispatch_async(dispatch_get_main_queue(), ^{
  138. completionHandler(status, nil);
  139. });
  140. }
  141. }];
  142. } else if (completionHandler != nil) {
  143. FIRRemoteConfigFetchAndActivateStatus status =
  144. fetchStatus == FIRRemoteConfigFetchStatus.success ?
  145. FIRRemoteConfigFetchAndActivateStatus.successUsingPreFetchedData :
  146. FIRRemoteConfigFetchAndActivateStatus.error;
  147. dispatch_async(dispatch_get_main_queue(), ^{
  148. completionHandler(status, fetchError);
  149. });
  150. }
  151. };
  152. [self fetchWithCompletionHandler:fetchCompletion];
  153. }
  154. func activateWithCompletion(completion: ((Bool, NSError?) -> Void)?) {
  155. __weak FIRRemoteConfig *weakSelf = self;
  156. let applyBlock: () -> Void = {
  157. __strong FIRRemoteConfig *strongSelf = weakSelf;
  158. if strongSelf == nil {
  159. let error = NSError(domain: FIRRemoteConfigErrorDomain, code: FIRRemoteConfigErrorInternalError, userInfo: nil)
  160. if let completion {
  161. dispatch_async(DispatchQueue.global(qos: .default), ^{
  162. completion(false, error);
  163. });
  164. }
  165. FIRLogError(RCNRemoteConfigQueueLabel, @"I-RCN000068", "Internal error activating config.");
  166. return;
  167. }
  168. // Check if the last fetched config has already been activated. Fetches with no data change
  169. // are ignored.
  170. if (strongSelf->_settings.lastETagUpdateTime == 0 ||
  171. strongSelf->_settings.lastETagUpdateTime <= strongSelf->_settings.lastApplyTimeInterval) {
  172. FIRLogDebug(RCNRemoteConfigQueueLabel, @"I-RCN000069",
  173. @"Most recently fetched config is already activated.");
  174. if (completion) {
  175. dispatch_async(DispatchQueue.global(qos: .default), ^{
  176. completion(false, nil);
  177. });
  178. }
  179. return;
  180. }
  181. strongSelf->_configContent?.copyFromDictionary(
  182. from: strongSelf->_configContent.fetchedConfig ?? [:], toSource: .active,
  183. forNamespace: strongSelf->_FIRNamespace);
  184. strongSelf->_settings?.lastApplyTimeInterval =
  185. [[NSDate date] timeIntervalSince1970];
  186. // New config has been activated at this point
  187. FIRLogDebug(RCNRemoteConfigQueueLabel, @"I-RCN000069", @"Config activated.");
  188. // Update last active template version number in setting and userDefaults.
  189. [strongSelf->_settings updateLastActiveTemplateVersion];
  190. // Update activeRolloutMetadata
  191. [strongSelf->_configContent activateRolloutMetadata:^(BOOL success) {
  192. if (success) {
  193. [self notifyRolloutsStateChange:strongSelf->_configContent.activeRolloutMetadata
  194. versionNumber:strongSelf->_settings.lastActiveTemplateVersion];
  195. }
  196. }];
  197. // Update experiments only for 3p namespace
  198. NSString *namespace =
  199. [strongSelf->_FIRNamespace substringToIndex:[strongSelf->_FIRNamespace
  200. rangeOfString:@":"].location];
  201. if ([namespace isEqualToString:RCNConstants.FIRNamespaceGoogleMobilePlatform]) {
  202. dispatch_async(DispatchQueue.main, ^{
  203. [self notifyConfigHasActivated];
  204. });
  205. [strongSelf->_configExperiment updateExperimentsWithHandler:^(NSError *_Nullable error) {
  206. if (completion) {
  207. dispatch_async(DispatchQueue.global(qos: .default), ^{
  208. completion(true, nil);
  209. });
  210. }
  211. }];
  212. } else {
  213. if (completion) {
  214. dispatch_async(DispatchQueue.global(qos: .default), ^{
  215. completion(true, nil);
  216. });
  217. }
  218. }
  219. };
  220. dispatch_async(_queue ?? DispatchQueue.main, applyBlock);
  221. }
  222. func notifyConfigHasActivated() {
  223. // Need a valid google app name.
  224. guard let appName = _appName else {
  225. return
  226. }
  227. // The Remote Config Swift SDK will be listening for this notification so it can tell SwiftUI to
  228. // update the UI.
  229. let appInfoDict: [String: Any] = [kFIRAppNameKey: appName];
  230. NotificationCenter.default.post(name: FIRRemoteConfigActivateNotification, object: self,
  231. userInfo: appInfoDict)
  232. }
  233. }