RCNUserDefaultsManager.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import Foundation
  2. import FirebaseCore // For FIRLogger
  3. // TODO: Move keys to a central constants file
  4. private enum UserDefaultsKeys {
  5. static let lastETag = "lastETag"
  6. static let lastETagUpdateTime = "lastETagUpdateTime"
  7. static let lastSuccessfulFetchTime = "lastSuccessfulFetchTime"
  8. static let lastFetchStatus = "lastFetchStatus" // Note: This seems unused in RCNConfigSettingsInternal read path
  9. static let isClientThrottled = "isClientThrottledWithExponentialBackoff"
  10. static let throttleEndTime = "throttleEndTime"
  11. static let currentThrottlingRetryInterval = "currentThrottlingRetryInterval"
  12. static let realtimeThrottleEndTime = "throttleRealtimeEndTime"
  13. static let currentRealtimeThrottlingRetryInterval = "currentRealtimeThrottlingRetryInterval"
  14. static let realtimeRetryCount = "realtimeRetryCount"
  15. static let lastFetchedTemplateVersion = "fetchTemplateVersion" // From RCNConfigConstants? Check key name
  16. static let lastActiveTemplateVersion = "activeTemplateVersion" // From RCNConfigConstants? Check key name
  17. static let customSignals = "customSignals"
  18. // Grouping constants from ObjC implementation
  19. static let groupPrefix = "group"
  20. static let groupSuffix = "firebase"
  21. }
  22. /// Wraps UserDefaults to provide scoped, thread-safe access for Remote Config settings.
  23. class RCNUserDefaultsManager {
  24. private let userDefaults: UserDefaults
  25. private let firebaseAppName: String
  26. private let firebaseNamespace: String // Just the namespace part (e.g., "firebase")
  27. private let bundleIdentifier: String
  28. private let lock = NSLock() // Lock for synchronizing writes
  29. // MARK: - Initialization
  30. /// Designated initializer.
  31. init(appName: String, bundleID: String, firebaseNamespace qualifiedNamespace: String) {
  32. self.firebaseAppName = appName
  33. self.bundleIdentifier = bundleID
  34. // Extract namespace part from "namespace:appName"
  35. if let range = qualifiedNamespace.range(of: ":") {
  36. self.firebaseNamespace = String(qualifiedNamespace[..<range.lowerBound])
  37. } else {
  38. // TODO: Log error - Namespace not fully qualified
  39. print("Error: Namespace '\(qualifiedNamespace)' is not fully qualified.")
  40. self.firebaseNamespace = qualifiedNamespace // Use as is, might cause issues
  41. }
  42. // Get shared UserDefaults instance for the app group derived from bundle ID
  43. self.userDefaults = RCNUserDefaultsManager.sharedUserDefaults(forBundleIdentifier: bundleID)
  44. }
  45. // MARK: - Static Methods for Shared UserDefaults
  46. private static func userDefaultsSuiteName(forBundleIdentifier bundleIdentifier: String) -> String {
  47. // Ensure bundleIdentifier is not empty? ObjC didn't check here.
  48. return "\(UserDefaultsKeys.groupPrefix).\(bundleIdentifier).\(UserDefaultsKeys.groupSuffix)"
  49. }
  50. private static func sharedUserDefaults(forBundleIdentifier bundleIdentifier: String) -> UserDefaults {
  51. // This mimics dispatch_once behavior implicitly through static initialization in Swift >= 1.2
  52. struct Static {
  53. static let instance = UserDefaults(suiteName: userDefaultsSuiteName(forBundleIdentifier: bundleIdentifier)) ?? UserDefaults.standard // Fallback unlikely needed
  54. }
  55. return Static.instance
  56. }
  57. // MARK: - Scoped Key Path
  58. /// Generates the key path for accessing the setting within UserDefaults: AppName.Namespace.Key
  59. private func scopedKeyPath(forKey key: String) -> String {
  60. return "\(firebaseAppName).\(firebaseNamespace).\(key)"
  61. }
  62. // MARK: - Read/Write Helpers (with locking)
  63. private func readValue<T>(forKey key: String) -> T? {
  64. // Reading UserDefaults is thread-safe, no lock needed technically,
  65. // but ObjC implementation read inside @synchronized block indirectly via instanceUserDefaults.
  66. // Let's keep reads simple for now.
  67. return userDefaults.value(forKeyPath: scopedKeyPath(forKey: key)) as? T
  68. }
  69. private func writeValue(_ value: Any?, forKey key: String) {
  70. lock.lock()
  71. defer { lock.unlock() }
  72. // We need to read the current app dictionary, then the namespace dictionary,
  73. // modify it, and write the whole app dictionary back. This mimics the ObjC logic.
  74. let appKey = firebaseAppName
  75. let namespaceKey = firebaseNamespace
  76. let settingKey = key
  77. var appDict = userDefaults.dictionary(forKey: appKey) ?? [:]
  78. var namespaceDict = appDict[namespaceKey] as? [String: Any] ?? [:]
  79. if let newValue = value {
  80. namespaceDict[settingKey] = newValue
  81. } else {
  82. namespaceDict.removeValue(forKey: settingKey) // Remove if value is nil
  83. }
  84. appDict[namespaceKey] = namespaceDict // Put potentially modified namespace dict back
  85. userDefaults.set(appDict, forKey: appKey) // Write the whole app dict back
  86. // Mimic explicit synchronize call, though often discouraged in Swift.
  87. // Required for potential app extension communication relying on it.
  88. userDefaults.synchronize()
  89. }
  90. // MARK: - Public Properties (Computed)
  91. var lastETag: String? {
  92. get { readValue(forKey: UserDefaultsKeys.lastETag) }
  93. set { writeValue(newValue, forKey: UserDefaultsKeys.lastETag) }
  94. }
  95. var lastETagUpdateTime: TimeInterval {
  96. get { readValue(forKey: UserDefaultsKeys.lastETagUpdateTime) ?? 0.0 }
  97. set { writeValue(newValue, forKey: UserDefaultsKeys.lastETagUpdateTime) }
  98. }
  99. var lastFetchTime: TimeInterval {
  100. get { readValue(forKey: UserDefaultsKeys.lastSuccessfulFetchTime) ?? 0.0 }
  101. set { writeValue(newValue, forKey: UserDefaultsKeys.lastSuccessfulFetchTime) }
  102. }
  103. // lastFetchStatus seems unused internally for read, only written? Keep setter for now.
  104. // var lastFetchStatus: String? {
  105. // get { readValue(forKey: UserDefaultsKeys.lastFetchStatus) }
  106. // set { writeValue(newValue, forKey: UserDefaultsKeys.lastFetchStatus) }
  107. // }
  108. var isClientThrottledWithExponentialBackoff: Bool {
  109. get { readValue(forKey: UserDefaultsKeys.isClientThrottled) ?? false }
  110. set { writeValue(newValue, forKey: UserDefaultsKeys.isClientThrottled) }
  111. }
  112. var throttleEndTime: TimeInterval {
  113. get { readValue(forKey: UserDefaultsKeys.throttleEndTime) ?? 0.0 }
  114. set { writeValue(newValue, forKey: UserDefaultsKeys.throttleEndTime) }
  115. }
  116. var currentThrottlingRetryIntervalSeconds: TimeInterval {
  117. get { readValue(forKey: UserDefaultsKeys.currentThrottlingRetryInterval) ?? 0.0 }
  118. set { writeValue(newValue, forKey: UserDefaultsKeys.currentThrottlingRetryInterval) }
  119. }
  120. var realtimeThrottleEndTime: TimeInterval {
  121. get { readValue(forKey: UserDefaultsKeys.realtimeThrottleEndTime) ?? 0.0 }
  122. set { writeValue(newValue, forKey: UserDefaultsKeys.realtimeThrottleEndTime) }
  123. }
  124. var currentRealtimeThrottlingRetryIntervalSeconds: TimeInterval {
  125. get { readValue(forKey: UserDefaultsKeys.currentRealtimeThrottlingRetryInterval) ?? 0.0 }
  126. set { writeValue(newValue, forKey: UserDefaultsKeys.currentRealtimeThrottlingRetryInterval) }
  127. }
  128. var realtimeRetryCount: Int {
  129. get { readValue(forKey: UserDefaultsKeys.realtimeRetryCount) ?? 0 }
  130. set { writeValue(newValue, forKey: UserDefaultsKeys.realtimeRetryCount) }
  131. }
  132. var lastFetchedTemplateVersion: String? { // Defaulted to "0" in ObjC getter if nil
  133. get { readValue(forKey: UserDefaultsKeys.lastFetchedTemplateVersion) ?? "0" }
  134. set { writeValue(newValue, forKey: UserDefaultsKeys.lastFetchedTemplateVersion) }
  135. }
  136. var lastActiveTemplateVersion: String? { // Defaulted to "0" in ObjC getter if nil
  137. get { readValue(forKey: UserDefaultsKeys.lastActiveTemplateVersion) ?? "0" }
  138. set { writeValue(newValue, forKey: UserDefaultsKeys.lastActiveTemplateVersion) }
  139. }
  140. var customSignals: [String: String] {
  141. get { readValue(forKey: UserDefaultsKeys.customSignals) ?? [:] } // Default to empty dict
  142. set { writeValue(newValue, forKey: UserDefaultsKeys.customSignals) }
  143. }
  144. // MARK: - Public Methods
  145. /// Delete all saved user defaults for this instance (App Name + Namespace scope).
  146. func resetUserDefaults() {
  147. lock.lock()
  148. defer { lock.unlock() }
  149. let appKey = firebaseAppName
  150. let namespaceKey = firebaseNamespace
  151. var appDict = userDefaults.dictionary(forKey: appKey) ?? [:]
  152. appDict.removeValue(forKey: namespaceKey) // Remove the namespace dict
  153. if appDict.isEmpty {
  154. userDefaults.removeObject(forKey: appKey) // Remove app dict if empty
  155. } else {
  156. userDefaults.set(appDict, forKey: appKey) // Write back modified app dict
  157. }
  158. userDefaults.synchronize()
  159. }
  160. // MARK: - Placeholder Selectors (for @objc calls from RCNConfigSettingsInternal)
  161. // These allow RCNConfigSettingsInternal to call this Swift class via selectors
  162. // until RCNConfigSettingsInternal is updated to call Swift methods directly.
  163. @objc func lastETagObjc() -> String? { return lastETag }
  164. @objc func setLastETagObjc(_ etag: String?) { lastETag = etag }
  165. @objc func lastETagUpdateTimeObjc() -> TimeInterval { return lastETagUpdateTime }
  166. @objc func setLastETagUpdateTimeObjc(_ time: TimeInterval) { lastETagUpdateTime = time }
  167. @objc func lastFetchTimeObjc() -> TimeInterval { return lastFetchTime }
  168. @objc func setLastFetchTimeObjc(_ time: TimeInterval) { lastFetchTime = time }
  169. // No getter for lastFetchStatus needed?
  170. // @objc func setLastFetchStatusObjc(_ status: String?) { lastFetchStatus = status }
  171. @objc func isClientThrottledWithExponentialBackoffObjc() -> Bool { return isClientThrottledWithExponentialBackoff }
  172. @objc func setIsClientThrottledWithExponentialBackoffObjc(_ throttled: Bool) { isClientThrottledWithExponentialBackoff = throttled }
  173. @objc func throttleEndTimeObjc() -> TimeInterval { return throttleEndTime }
  174. @objc func setThrottleEndTimeObjc(_ time: TimeInterval) { throttleEndTime = time }
  175. @objc func currentThrottlingRetryIntervalSecondsObjc() -> TimeInterval { return currentThrottlingRetryIntervalSeconds }
  176. @objc func setCurrentThrottlingRetryIntervalSecondsObjc(_ interval: TimeInterval) { currentThrottlingRetryIntervalSeconds = interval }
  177. @objc func realtimeThrottleEndTimeObjc() -> TimeInterval { return realtimeThrottleEndTime }
  178. @objc func setRealtimeThrottleEndTimeObjc(_ time: TimeInterval) { realtimeThrottleEndTime = time }
  179. @objc func currentRealtimeThrottlingRetryIntervalSecondsObjc() -> TimeInterval { return currentRealtimeThrottlingRetryIntervalSeconds }
  180. @objc func setCurrentRealtimeThrottlingRetryIntervalSecondsObjc(_ interval: TimeInterval) { currentRealtimeThrottlingRetryIntervalSeconds = interval }
  181. @objc func realtimeRetryCountObjc() -> Int { return realtimeRetryCount }
  182. @objc func setRealtimeRetryCountObjc(_ count: Int) { realtimeRetryCount = count }
  183. @objc func lastFetchedTemplateVersionObjc() -> String? { return lastFetchedTemplateVersion }
  184. @objc func setLastFetchedTemplateVersionObjc(_ version: String?) { lastFetchedTemplateVersion = version }
  185. @objc func lastActiveTemplateVersionObjc() -> String? { return lastActiveTemplateVersion }
  186. @objc func setLastActiveTemplateVersionObjc(_ version: String?) { lastActiveTemplateVersion = version }
  187. @objc func customSignalsObjc() -> [String: String]? { return customSignals }
  188. @objc func setCustomSignalsObjc(_ signals: [String: String]?) { customSignals = signals ?? [:] }
  189. @objc func resetUserDefaultsObjc() { resetUserDefaults() }
  190. }
  191. // Temporary placeholder for RemoteConfigSource enum if not defined elsewhere yet
  192. //@objc(FIRRemoteConfigSource) public enum RemoteConfigSource: Int { case remote, defaultValue, staticValue }