RemoteConfig.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. import Foundation
  2. import FirebaseCore // For FIROptions, FIRApp, FIRAnalyticsInterop, etc.
  3. // TODO: Import necessary modules like FirebaseInstallations if needed after translation
  4. // Placeholder types for internal Objective-C classes until they are translated
  5. // Keep DBManager placeholder if translation was skipped
  6. typealias RCNConfigContent = AnyObject
  7. typealias RCNConfigDBManager = AnyObject
  8. // RCNConfigSettingsInternal is now translated
  9. typealias RCNConfigFetch = AnyObject
  10. typealias RCNConfigExperiment = AnyObject
  11. typealias RCNConfigRealtime = AnyObject
  12. typealias FIRAnalyticsInterop = AnyObject // Assuming FIRAnalyticsInterop is ObjC protocol
  13. typealias FIRExperimentController = AnyObject // Placeholder
  14. // Define RemoteConfigSource enum based on previous translation
  15. @objc(FIRRemoteConfigSource) public enum RemoteConfigSource: Int {
  16. case remote = 0
  17. case defaultValue = 1
  18. case staticValue = 2
  19. }
  20. // Define RemoteConfigValue based on previous translation (simplified for context)
  21. @objc(FIRRemoteConfigValue) public class RemoteConfigValue: NSObject, NSCopying {
  22. let valueData: Data
  23. let source: RemoteConfigSource
  24. init(data: Data, source: RemoteConfigSource) {
  25. self.valueData = data; self.source = source; super.init()
  26. }
  27. override convenience init() { self.init(data: Data(), source: .staticValue) }
  28. @objc public func copy(with zone: NSZone? = nil) -> Any { return self }
  29. // Add properties like stringValue, boolValue etc. if needed by selectors below
  30. @objc public var stringValue: String { String(data: valueData, encoding: .utf8) ?? "" } // Placeholder implementation
  31. @objc public var numberValue: NSNumber { NSNumber(value: Double(stringValue) ?? 0.0) } // Placeholder
  32. @objc public var dataValue: Data { valueData } // Placeholder
  33. @objc public var boolValue: Bool { false } // Placeholder
  34. @objc public var jsonValue: Any? { nil } // Placeholder
  35. }
  36. // Constants mirroring Objective-C defines (move to a constants file later?)
  37. let defaultMinimumFetchInterval: TimeInterval = 43200.0 // 12 hours
  38. let defaultFetchTimeout: TimeInterval = 60.0
  39. struct RemoteConfigConstants {
  40. static let errorDomain = "com.google.remoteconfig.ErrorDomain"
  41. static let remoteConfigActivateNotification = Notification.Name("FIRRemoteConfigActivateNotification")
  42. static let appNameKey = "FIRAppNameKey" // Assuming kFIRAppNameKey maps to this
  43. static let googleMobilePlatformNamespace = "firebase" // Placeholder for FIRNamespaceGoogleMobilePlatform
  44. }
  45. // TODO: Define RemoteConfigFetchStatus, RemoteConfigFetchAndActivateStatus, RemoteConfigError enums
  46. @objc(FIRRemoteConfigFetchStatus) public enum RemoteConfigFetchStatus: Int {
  47. case noFetchYet = 0
  48. case success = 1
  49. case failure = 2
  50. case throttled = 3
  51. }
  52. @objc(FIRRemoteConfigFetchAndActivateStatus) public enum RemoteConfigFetchAndActivateStatus: Int {
  53. case successFetchedFromRemote = 0
  54. case successUsingPreFetchedData = 1
  55. case error = 2
  56. }
  57. @objc(FIRRemoteConfigError) public enum RemoteConfigError: Int {
  58. case unknown = 8001
  59. case throttled = 8002
  60. case internalError = 8003
  61. }
  62. /// Firebase Remote Config class.
  63. @objc(FIRRemoteConfig)
  64. public class RemoteConfig: NSObject {
  65. // --- Properties ---
  66. private let configContent: RCNConfigContent
  67. private let dbManager: RCNConfigDBManager
  68. private let settingsInternal: RCNConfigSettingsInternal // Use actual translated class
  69. private let configFetch: RCNConfigFetch
  70. private let configExperiment: RCNConfigExperiment
  71. private let configRealtime: RCNConfigRealtime
  72. private static let sharedQueue = DispatchQueue(label: "com.google.firebase.remoteconfig.serial")
  73. private let queue: DispatchQueue = RemoteConfig.sharedQueue
  74. private let appName: String
  75. private let firebaseNamespace: String // Fully qualified namespace (namespace:appName)
  76. @objc public var lastFetchTime: Date? {
  77. var fetchTimeInterval: TimeInterval = 0
  78. // Access directly via userDefaultsManager used by settingsInternal
  79. queue.sync {
  80. fetchTimeInterval = self.settingsInternal.lastFetchTimeInterval
  81. }
  82. return fetchTimeInterval > 0 ? Date(timeIntervalSince1970: fetchTimeInterval) : nil
  83. }
  84. @objc public var lastFetchStatus: RemoteConfigFetchStatus {
  85. var status: RemoteConfigFetchStatus = .noFetchYet
  86. // Access internal settings property safely on the queue
  87. queue.sync {
  88. status = self.settingsInternal.lastFetchStatus
  89. }
  90. return status
  91. }
  92. @objc public var configSettings: RemoteConfigSettings {
  93. get {
  94. let currentSettings = RemoteConfigSettings()
  95. // Access internal settings properties safely on the queue
  96. queue.sync {
  97. currentSettings.minimumFetchInterval = self.settingsInternal.minimumFetchInterval
  98. currentSettings.fetchTimeout = self.settingsInternal.fetchTimeout
  99. }
  100. // TODO: Log debug message? FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000066", ...)
  101. return currentSettings
  102. }
  103. set {
  104. // Update internal settings properties safely on the queue
  105. queue.async { // Use async for setter as ObjC does
  106. self.settingsInternal.minimumFetchInterval = newValue.minimumFetchInterval
  107. self.settingsInternal.fetchTimeout = newValue.fetchTimeout
  108. // TODO: Recreate network session if needed
  109. // This likely involves calling a method on the (translated) configFetch object
  110. _ = self.configFetch.perform(#selector(RCNConfigFetch.recreateNetworkSession))
  111. // TODO: Log debug message? FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000067", ...)
  112. }
  113. }
  114. }
  115. @objc(remoteConfig)
  116. public static func remoteConfig() -> RemoteConfig { return FIRRemoteConfig.remoteConfig() } // Bridge
  117. @objc(remoteConfigWithApp:)
  118. public static func remoteConfig(app: FirebaseApp) -> RemoteConfig { return FIRRemoteConfig.remoteConfig(with: app) } // Bridge
  119. init(appName: String, options: FIROptions, namespace: String, dbManager: RCNConfigDBManager, configContent: RCNConfigContent, analytics: FIRAnalyticsInterop?) {
  120. self.appName = appName
  121. self.firebaseNamespace = "\(namespace):\(appName)" // Corrected namespace format
  122. self.dbManager = dbManager
  123. self.configContent = configContent
  124. // Initialize RCNConfigSettingsInternal (Use actual translated init)
  125. // Pass dbManager placeholder, namespace, appName, options.googleAppID
  126. // Note: options.googleAppID might be optional, handle nil case
  127. self.settingsInternal = RCNConfigSettingsInternal(databaseManager: dbManager, namespace: self.firebaseNamespace, firebaseAppName: appName, googleAppID: options.googleAppID ?? "")
  128. // Initialize RCNConfigExperiment (Placeholder - requires translation)
  129. // let experimentController = FIRExperimentController.sharedInstance() // Requires FIRExperimentController translation
  130. self.configExperiment = RCNConfigExperiment() // Placeholder
  131. // Initialize RCNConfigFetch (Placeholder - requires translation)
  132. self.configFetch = RCNConfigFetch() // Placeholder
  133. // Initialize RCNConfigRealtime (Placeholder - requires translation)
  134. self.configRealtime = RCNConfigRealtime() // Placeholder
  135. super.init()
  136. }
  137. @available(*, unavailable, message: "Use RemoteConfig.remoteConfig() static method instead.")
  138. public override init() { fatalError("Use RemoteConfig.remoteConfig() static method instead.") }
  139. @objc(ensureInitializedWithCompletionHandler:)
  140. public func ensureInitialized(completionHandler: @escaping @Sendable (Error?) -> Void) {
  141. DispatchQueue.global(qos: .utility).async { [weak self] in
  142. guard let self = self else { return }
  143. var initializationSuccessful = false
  144. // Keep using selector for untranslated RCNConfigContent
  145. let successValue = self.configContent.perform(#selector(getter: RCNConfigContent.initializationSuccessful))?.takeUnretainedValue() as? Bool
  146. initializationSuccessful = successValue ?? false
  147. var error: Error? = nil
  148. if !initializationSuccessful {
  149. error = NSError(domain: RemoteConfigConstants.errorDomain, code: RemoteConfigError.internalError.rawValue, userInfo: [NSLocalizedDescriptionKey: "Timed out waiting for database load."])
  150. }
  151. completionHandler(error)
  152. }
  153. }
  154. // --- Fetch & Activate Methods ---
  155. @objc(fetchWithCompletionHandler:)
  156. public func fetch(completionHandler: ((RemoteConfigFetchStatus, Error?) -> Void)? = nil) {
  157. queue.async {
  158. // Use direct access to settingsInternal property
  159. let expirationDuration = self.settingsInternal.minimumFetchInterval
  160. self.fetch(withExpirationDuration: expirationDuration, completionHandler: completionHandler)
  161. }
  162. }
  163. @objc(fetchWithExpirationDuration:completionHandler:)
  164. public func fetch(withExpirationDuration expirationDuration: TimeInterval, completionHandler: ((RemoteConfigFetchStatus, Error?) -> Void)? = nil) {
  165. // Keep using selector for untranslated RCNConfigFetch
  166. _ = configFetch.perform(#selector(RCNConfigFetch.fetchConfig(withExpirationDuration:completionHandler:)),
  167. with: expirationDuration, with: completionHandler as Any?)
  168. }
  169. @objc(activateWithCompletion:)
  170. public func activate(completion: ((Bool, Error?) -> Void)? = nil) {
  171. queue.async { [weak self] in
  172. guard let self = self else {
  173. completion?(false, NSError(domain: RemoteConfigConstants.errorDomain, code: RemoteConfigError.internalError.rawValue, userInfo: [NSLocalizedDescriptionKey: "Internal error activating config: Instance deallocated."]))
  174. return
  175. }
  176. // Use direct access for settingsInternal properties
  177. // Check if the last fetched config has already been activated.
  178. if self.settingsInternal.lastETagUpdateTime <= 0 || self.settingsInternal.lastETagUpdateTime <= self.settingsInternal.lastApplyTimeInterval {
  179. // TODO: Log debug message? FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", ...)
  180. DispatchQueue.global().async { completion?(false, nil) } // Match ObjC global queue dispatch
  181. return
  182. }
  183. // Keep selectors for untranslated RCNConfigContent
  184. let fetchedConfigDict = self.configContent.perform(#selector(getter: RCNConfigContent.fetchedConfig))?.takeUnretainedValue() as? NSDictionary
  185. _ = self.configContent.perform(#selector(RCNConfigContent.copyFromDictionary(_:toSource:forNamespace:)), with: fetchedConfigDict, with: 1 /* Active */, with: self.firebaseNamespace)
  186. // Update last apply time via settingsInternal method (interacts with DB placeholder)
  187. let now = Date().timeIntervalSince1970
  188. self.settingsInternal.updateLastApplyTimeIntervalInDB(now)
  189. // TODO: Log debug message? FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000069", @"Config activated.")
  190. // Keep selector for untranslated RCNConfigContent
  191. _ = self.configContent.perform(#selector(RCNConfigContent.activatePersonalization))
  192. // Update last active template version via settingsInternal method
  193. self.settingsInternal.updateLastActiveTemplateVersion()
  194. // Keep selector for untranslated RCNConfigContent for rollout activation
  195. let rolloutCompletion: @convention(block) (Bool) -> Void = { success in
  196. if success {
  197. // Use actual property for version, keep selector for metadata
  198. let activeMetadata = self.configContent.perform(#selector(getter: RCNConfigContent.activeRolloutMetadata))?.takeUnretainedValue() as? NSArray // Placeholder type
  199. let versionNumber = self.settingsInternal.lastActiveTemplateVersion // Use actual property
  200. // TODO: Call notifyRolloutsStateChange - Requires translation
  201. }
  202. }
  203. _ = self.configContent.perform(#selector(RCNConfigContent.activateRolloutMetadata(_:)), with: rolloutCompletion as Any?)
  204. let namespacePrefix = self.firebaseNamespace.components(separatedBy: ":").first ?? ""
  205. if namespacePrefix == RemoteConfigConstants.googleMobilePlatformNamespace {
  206. DispatchQueue.main.async { self.notifyConfigHasActivated() } // Match ObjC main queue dispatch
  207. // Keep selector for untranslated RCNConfigExperiment
  208. let experimentCompletion: @convention(block) (Error?) -> Void = { error in DispatchQueue.global().async { completion?(true, error) } } // Match ObjC global queue dispatch
  209. _ = self.configExperiment.perform(#selector(RCNConfigExperiment.updateExperiments(handler:)), with: experimentCompletion as Any?)
  210. } else {
  211. DispatchQueue.global().async { completion?(true, nil) } // Match ObjC global queue dispatch
  212. }
  213. }
  214. }
  215. @objc(fetchAndActivateWithCompletionHandler:)
  216. public func fetchAndActivate(completionHandler: ((RemoteConfigFetchAndActivateStatus, Error?) -> Void)? = nil) {
  217. self.fetch { [weak self] status, error in
  218. guard let self = self else { return }
  219. if status == .success, error == nil {
  220. self.activate { changed, activateError in
  221. if let activateError = activateError {
  222. completionHandler?(.error, activateError)
  223. } else {
  224. completionHandler?(.successUsingPreFetchedData, nil) // Match ObjC
  225. }
  226. }
  227. } else {
  228. let fetchError = error ?? NSError(domain: RemoteConfigConstants.errorDomain, code: RemoteConfigError.internalError.rawValue, userInfo: [NSLocalizedDescriptionKey: "Fetch failed with status: \(status.rawValue)"])
  229. completionHandler?(.error, fetchError)
  230. }
  231. }
  232. }
  233. private func notifyConfigHasActivated() {
  234. guard !self.appName.isEmpty else { return }
  235. let appInfoDict = [RemoteConfigConstants.appNameKey: self.appName]
  236. NotificationCenter.default.post(name: RemoteConfigConstants.remoteConfigActivateNotification, object: self, userInfo: appInfoDict)
  237. }
  238. // --- Get Config Methods ---
  239. @objc public subscript(key: String) -> RemoteConfigValue {
  240. return configValue(forKey: key)
  241. }
  242. @objc(configValueForKey:)
  243. public func configValue(forKey key: String?) -> RemoteConfigValue {
  244. guard let key = key, !key.isEmpty else {
  245. return RemoteConfigValue(data: Data(), source: .staticValue)
  246. }
  247. return queue.sync { [weak self] () -> RemoteConfigValue in
  248. guard let self = self else {
  249. return RemoteConfigValue(data: Data(), source: .staticValue)
  250. }
  251. // Keep selectors for untranslated RCNConfigContent
  252. let activeConfig = self.configContent.perform(#selector(getter: RCNConfigContent.activeConfig))?.takeUnretainedValue() as? [String: [String: RemoteConfigValue]]
  253. if let value = activeConfig?[self.firebaseNamespace]?[key] {
  254. // TODO: Check value.source == .remote? Log error?
  255. // TODO: Call listeners?
  256. return value
  257. }
  258. // Call local defaultValue(forKey:) method
  259. let defaultValue = self.defaultValue(forKey: key)
  260. return defaultValue ?? RemoteConfigValue(data: Data(), source: .staticValue)
  261. }
  262. }
  263. @objc(configValueForKey:source:)
  264. public func configValue(forKey key: String?, source: RemoteConfigSource) -> RemoteConfigValue {
  265. guard let key = key, !key.isEmpty else {
  266. return RemoteConfigValue(data: Data(), source: .staticValue)
  267. }
  268. return queue.sync { [weak self] () -> RemoteConfigValue in
  269. guard let self = self else { return RemoteConfigValue(data: Data(), source: .staticValue) }
  270. // Keep selectors for untranslated RCNConfigContent
  271. var value: RemoteConfigValue? = nil
  272. switch source {
  273. case .remote:
  274. let activeConfig = self.configContent.perform(#selector(getter: RCNConfigContent.activeConfig))?.takeUnretainedValue() as? [String: [String: RemoteConfigValue]]
  275. value = activeConfig?[self.firebaseNamespace]?[key]
  276. case .defaultValue:
  277. let defaultConfig = self.configContent.perform(#selector(getter: RCNConfigContent.defaultConfig))?.takeUnretainedValue() as? [String: [String: RemoteConfigValue]]
  278. value = defaultConfig?[self.firebaseNamespace]?[key]
  279. case .staticValue:
  280. break
  281. @unknown default:
  282. break
  283. }
  284. return value ?? RemoteConfigValue(data: Data(), source: .staticValue)
  285. }
  286. }
  287. @objc(allKeysFromSource:)
  288. public func allKeys(from source: RemoteConfigSource) -> [String] {
  289. return queue.sync { [weak self] () -> [String] in
  290. guard let self = self else { return [] }
  291. // Keep selectors for untranslated RCNConfigContent
  292. var keys: [String]? = nil
  293. switch source {
  294. case .remote:
  295. let activeConfig = self.configContent.perform(#selector(getter: RCNConfigContent.activeConfig))?.takeUnretainedValue() as? [String: [String: RemoteConfigValue]]
  296. keys = activeConfig?[self.firebaseNamespace]?.keys.map { $0 }
  297. case .defaultValue:
  298. let defaultConfig = self.configContent.perform(#selector(getter: RCNConfigContent.defaultConfig))?.takeUnretainedValue() as? [String: [String: RemoteConfigValue]]
  299. keys = defaultConfig?[self.firebaseNamespace]?.keys.map { $0 }
  300. case .staticValue:
  301. break
  302. @unknown default:
  303. break
  304. }
  305. return keys ?? []
  306. }
  307. }
  308. @objc(keysWithPrefix:)
  309. public func keys(withPrefix prefix: String?) -> Set<String> {
  310. return queue.sync { [weak self] () -> Set<String> in
  311. guard let self = self else { return [] }
  312. // Keep selector for untranslated RCNConfigContent
  313. let activeConfig = self.configContent.perform(#selector(getter: RCNConfigContent.activeConfig))?.takeUnretainedValue() as? [String: [String: RemoteConfigValue]]
  314. guard let namespaceConfig = activeConfig?[self.firebaseNamespace] else {
  315. return []
  316. }
  317. let allKeys = namespaceConfig.keys
  318. guard let prefix = prefix, !prefix.isEmpty else {
  319. return Set(allKeys)
  320. }
  321. return Set(allKeys.filter { $0.hasPrefix(prefix) })
  322. }
  323. }
  324. // MARK: - Defaults
  325. @objc(setDefaults:)
  326. public func setDefaults(_ defaults: [String: NSObject]?) {
  327. let defaultsCopy = defaults ?? [:]
  328. queue.async { [weak self] in
  329. guard let self = self else { return }
  330. // Keep selectors for untranslated RCNConfigContent
  331. let namespaceToDefaults = [self.firebaseNamespace: defaultsCopy]
  332. _ = self.configContent.perform(#selector(RCNConfigContent.copyFromDictionary(_:toSource:forNamespace:)),
  333. with: namespaceToDefaults,
  334. with: 2, // RCNDBSourceDefault
  335. with: self.firebaseNamespace)
  336. // Update last set defaults time via settingsInternal method (interacts with DB placeholder)
  337. let now = Date().timeIntervalSince1970
  338. self.settingsInternal.updateLastSetDefaultsTimeIntervalInDB(now)
  339. }
  340. }
  341. @objc(setDefaultsFromPlistFileName:)
  342. public func setDefaults(fromPlist fileName: String?) {
  343. guard let fileName = fileName, !fileName.isEmpty else { return }
  344. let bundlesToSearch = [Bundle.main, Bundle(for: RemoteConfig.self)]
  345. var plistPath: String?
  346. for bundle in bundlesToSearch {
  347. if let path = bundle.path(forResource: fileName, ofType: "plist") {
  348. plistPath = path; break
  349. }
  350. }
  351. guard let finalPath = plistPath else { return }
  352. if let defaultsDict = NSDictionary(contentsOfFile: finalPath) as? [String: NSObject] {
  353. setDefaults(defaultsDict)
  354. }
  355. }
  356. @objc(defaultValueForKey:)
  357. public func defaultValue(forKey key: String?) -> RemoteConfigValue? {
  358. guard let key = key, !key.isEmpty else { return nil }
  359. return queue.sync { [weak self] () -> RemoteConfigValue? in
  360. guard let self = self else { return nil }
  361. // Keep selectors for untranslated RCNConfigContent
  362. let defaultConfig = self.configContent.perform(#selector(getter: RCNConfigContent.defaultConfig))?.takeUnretainedValue() as? [String: [String: RemoteConfigValue]]
  363. let value = defaultConfig?[self.firebaseNamespace]?[key]
  364. // TODO: Check source == .defaultValue?
  365. return value
  366. }
  367. }
  368. // MARK: - Placeholder selectors for untranslated classes
  369. // RCNConfigContent related
  370. @objc private func activeConfig() -> Any? { return nil }
  371. @objc private func defaultConfig() -> Any? { return nil }
  372. @objc private func fetchedConfig() -> NSDictionary? { return nil }
  373. @objc private func copyFromDictionary(_ dict: Any?, toSource source: Int, forNamespace ns: String) {}
  374. @objc private func activatePersonalization() {}
  375. @objc private func activeRolloutMetadata() -> NSArray? { return nil }
  376. @objc private func activateRolloutMetadata(_ completion: Any?) {}
  377. @objc private func initializationSuccessful() -> Bool { return false }
  378. // RCNConfigFetch related
  379. @objc private func recreateNetworkSession() {}
  380. @objc private func fetchConfig(withExpirationDuration duration: TimeInterval, completionHandler handler: Any?) {}
  381. // RCNConfigExperiment related
  382. @objc private func updateExperiments(handler: Any?) {}
  383. // Selectors for DB interactions via RCNConfigSettingsInternal
  384. @objc private func updateLastApplyTimeIntervalInDB(_ interval: TimeInterval) {}
  385. @objc private func updateLastSetDefaultsTimeIntervalInDB(_ interval: TimeInterval) {}
  386. } // End of RemoteConfig class