RCNConfigSettingsInternal.swift 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. import Foundation
  2. import FirebaseCore // For FIRLogger
  3. // --- Placeholder Types ---
  4. // These will be replaced by actual translated classes later.
  5. typealias RCNConfigDBManager = AnyObject
  6. // RCNUserDefaultsManager is now translated
  7. typealias RCNDevice = AnyObject // Assuming RCNDevice provides static methods like RCNDevice.deviceCountry()
  8. // --- Constants ---
  9. // TODO: Move these to a central constants file
  10. private enum Constants {
  11. // From RCNConfigSettings.m
  12. static let exponentialBackoffMinimumInterval: TimeInterval = 120 // 2 mins
  13. static let exponentialBackoffMaximumInterval: TimeInterval = 14400 // 4 hours (60 * 60 * 4)
  14. // From RCNConfigConstants.h (assuming defaults if not found elsewhere)
  15. static let defaultMinimumFetchInterval: TimeInterval = 43200.0 // 12 hours
  16. static let httpDefaultConnectionTimeout: TimeInterval = 60.0
  17. // Keys from RCNConfigDBManager.h (assuming)
  18. static let keyDeviceContext = "device_context"
  19. static let keyAppContext = "app_context"
  20. static let keySuccessFetchTime = "success_fetch_time"
  21. static let keyFailureFetchTime = "failure_fetch_time"
  22. static let keyLastFetchStatus = "last_fetch_status"
  23. static let keyLastFetchError = "last_fetch_error"
  24. static let keyLastApplyTime = "last_apply_time"
  25. static let keyLastSetDefaultsTime = "last_set_defaults_time"
  26. static let keyBundleIdentifier = "bundle_identifier"
  27. static let keyNamespace = "namespace"
  28. static let keyFetchTime = "fetch_time"
  29. static let keyDigestPerNamespace = "digest_per_namespace" // Backwards compat only
  30. // Keys from RCNUserDefaultsManager.h (assuming)
  31. // Define as needed when RCNUserDefaultsManager is translated
  32. // Keys from RCNConfigFetch.m (assuming) - for request body
  33. static let analyticsFirstOpenTimePropertyName = "_fot"
  34. }
  35. /// Enum to map RCNUpdateOption to Swift enum, primarily for DB interaction selectors
  36. enum RCNUpdateOption: Int {
  37. case applyTime = 0
  38. case defaultTime = 1
  39. }
  40. /// Internal class containing settings, state, and metadata for a Remote Config instance.
  41. /// Mirrors the Objective-C class RCNConfigSettings.
  42. /// This class is intended for internal use within the FirebaseRemoteConfig module.
  43. /// Note: This class is not inherently thread-safe for all properties.
  44. /// The original Objective-C implementation relied on a serial dispatch queue (`_queue` in FIRRemoteConfig)
  45. /// for synchronization when accessing instances of this class. Callers (like RemoteConfig.swift)
  46. /// must ensure thread-safe access. Properties marked `atomic` in ObjC are handled here
  47. /// using basic Swift atomicity or placeholders requiring external locking.
  48. class RCNConfigSettingsInternal { // Not public
  49. // MARK: - Properties (Mirrored from RCNConfigSettings.h and .m)
  50. // Settable Public Settings
  51. var minimumFetchInterval: TimeInterval
  52. var fetchTimeout: TimeInterval
  53. // Readonly Properties (or internally set)
  54. let bundleIdentifier: String
  55. private(set) var successFetchTimes: [TimeInterval] // Equivalent to NSMutableArray
  56. private(set) var failureFetchTimes: [TimeInterval] // Equivalent to NSMutableArray
  57. private(set) var deviceContext: [String: Any] // Equivalent to NSMutableDictionary
  58. var customVariables: [String: Any] // Equivalent to NSMutableDictionary, settable internally
  59. private(set) var lastFetchStatus: RemoteConfigFetchStatus
  60. private(set) var lastFetchError: RemoteConfigError // Make sure RemoteConfigError enum is defined
  61. private(set) var lastApplyTimeInterval: TimeInterval
  62. private(set) var lastSetDefaultsTimeInterval: TimeInterval
  63. // Properties managed via RCNUserDefaultsManager
  64. // Use direct access via userDefaultsManager instance below
  65. var lastETag: String? {
  66. get { userDefaultsManager.lastETag }
  67. set { userDefaultsManager.lastETag = newValue }
  68. }
  69. var lastETagUpdateTime: TimeInterval {
  70. get { userDefaultsManager.lastETagUpdateTime }
  71. set { userDefaultsManager.lastETagUpdateTime = newValue }
  72. }
  73. var lastFetchTimeInterval: TimeInterval {
  74. get { userDefaultsManager.lastFetchTime }
  75. set { userDefaultsManager.lastFetchTime = newValue }
  76. }
  77. var lastFetchedTemplateVersion: String? { // Defaulted to "0" in RCNUserDefaultsManager getter if nil
  78. get { userDefaultsManager.lastFetchedTemplateVersion }
  79. set { userDefaultsManager.lastFetchedTemplateVersion = newValue }
  80. }
  81. var lastActiveTemplateVersion: String? { // Defaulted to "0" in RCNUserDefaultsManager getter if nil
  82. get { userDefaultsManager.lastActiveTemplateVersion }
  83. set { userDefaultsManager.lastActiveTemplateVersion = newValue }
  84. }
  85. var realtimeExponentialBackoffRetryInterval: TimeInterval {
  86. get { userDefaultsManager.currentRealtimeThrottlingRetryIntervalSeconds }
  87. set { userDefaultsManager.currentRealtimeThrottlingRetryIntervalSeconds = newValue }
  88. }
  89. var realtimeExponentialBackoffThrottleEndTime: TimeInterval {
  90. get { userDefaultsManager.realtimeThrottleEndTime }
  91. set { userDefaultsManager.realtimeThrottleEndTime = newValue }
  92. }
  93. var realtimeRetryCount: Int {
  94. get { userDefaultsManager.realtimeRetryCount }
  95. set { userDefaultsManager.realtimeRetryCount = newValue }
  96. }
  97. var customSignals: [String: String] { // Defaulted to [:] in RCNUserDefaultsManager getter if nil
  98. get { userDefaultsManager.customSignals }
  99. set { userDefaultsManager.customSignals = newValue }
  100. }
  101. // Throttling Properties
  102. var exponentialBackoffRetryInterval: TimeInterval // Not stored in userDefaults
  103. private(set) var exponentialBackoffThrottleEndTime: TimeInterval = 0 // Not stored in userDefaults
  104. // Installation ID and Token (marked atomic in ObjC)
  105. // Using basic String properties. Synchronization handled externally by RemoteConfig queue.
  106. var configInstallationsIdentifier: String?
  107. var configInstallationsToken: String?
  108. // Fetch In Progress Flag (marked atomic in ObjC)
  109. // Needs external synchronization (e.g., RemoteConfig queue)
  110. var isFetchInProgress: Bool = false
  111. // Dependencies (Placeholders - initialized in init)
  112. private let dbManager: RCNConfigDBManager
  113. private let userDefaultsManager: RCNUserDefaultsManager // Use actual translated class
  114. private let firebaseNamespace: String // Fully qualified (namespace:appName)
  115. private let googleAppID: String
  116. // MARK: - Initializer
  117. init(databaseManager: RCNConfigDBManager,
  118. namespace: String, // Fully qualified namespace
  119. firebaseAppName: String,
  120. googleAppID: String) {
  121. self.dbManager = databaseManager
  122. self.firebaseNamespace = namespace
  123. self.googleAppID = googleAppID
  124. // Bundle ID
  125. if let bundleID = Bundle.main.bundleIdentifier, !bundleID.isEmpty {
  126. self.bundleIdentifier = bundleID
  127. } else {
  128. self.bundleIdentifier = ""
  129. // TODO: Log warning: FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000038", ...)
  130. }
  131. // Initialize User Defaults Manager (Use actual translated init)
  132. self.userDefaultsManager = RCNUserDefaultsManager(appName: firebaseAppName, bundleID: bundleIdentifier, firebaseNamespace: namespace)
  133. // Check if DB is new and reset UserDefaults if needed
  134. // DB interaction still uses selector
  135. let isNewDB = self.dbManager.perform(#selector(RCNConfigDBManager.isNewDatabase))?.takeUnretainedValue() as? Bool ?? false
  136. if isNewDB {
  137. // TODO: Log notice: FIRLogNotice(kFIRLoggerRemoteConfig, @"I-RCN000072", ...)
  138. self.userDefaultsManager.resetUserDefaults() // Call actual method
  139. }
  140. // Initialize properties with default/loaded values
  141. self.minimumFetchInterval = Constants.defaultMinimumFetchInterval
  142. self.fetchTimeout = Constants.httpDefaultConnectionTimeout
  143. self.deviceContext = [:]
  144. self.customVariables = [:]
  145. self.successFetchTimes = []
  146. self.failureFetchTimes = []
  147. self.lastFetchStatus = .noFetchYet
  148. self.lastFetchError = .unknown // Assuming .unknown is 0 or default
  149. self.lastApplyTimeInterval = 0
  150. self.lastSetDefaultsTimeInterval = 0
  151. // Properties read from UserDefaults are now accessed via computed properties
  152. // self.lastFetchedTemplateVersion = self.userDefaultsManager.lastFetchedTemplateVersion
  153. // etc...
  154. // Initialize non-persistent state
  155. self.exponentialBackoffRetryInterval = Constants.exponentialBackoffMinimumInterval // Default start
  156. self.isFetchInProgress = false
  157. // Load persistent metadata from DB after initializing defaults
  158. // DB interaction still uses selector
  159. // This might overwrite some defaults like lastFetchStatus etc.
  160. self.loadConfigFromMetadataTable()
  161. // Note: lastETag, lastETagUpdateTime, lastFetchTimeInterval, customSignals are
  162. // also now handled by computed properties reading from userDefaultsManager.
  163. }
  164. // MARK: - UserDefaults Interaction (via RCNUserDefaultsManager)
  165. // Methods below are now simplified or removed as interaction happens via computed properties.
  166. // Setter updates UserDefaults directly via computed property setter
  167. func updateLastFetchTimeInterval(_ timeInterval: TimeInterval) {
  168. self.lastFetchTimeInterval = timeInterval
  169. }
  170. // Setter updates UserDefaults directly via computed property setter
  171. func updateLastFetchedTemplateVersion(_ version: String?) {
  172. self.lastFetchedTemplateVersion = version
  173. }
  174. // Setter updates UserDefaults directly via computed property setter
  175. func updateLastActiveTemplateVersionInUserDefaults(_ version: String?) {
  176. self.lastActiveTemplateVersion = version
  177. }
  178. // Setter updates UserDefaults directly via computed property setter
  179. func updateRealtimeExponentialBackoffRetryInterval(_ interval: TimeInterval) {
  180. self.realtimeExponentialBackoffRetryInterval = interval
  181. }
  182. // Setter updates UserDefaults directly via computed property setter
  183. func updateRealtimeThrottleEndTime(_ time: TimeInterval) {
  184. self.realtimeExponentialBackoffThrottleEndTime = time
  185. }
  186. // Setter updates UserDefaults directly via computed property setter
  187. func updateRealtimeRetryCount(_ count: Int) {
  188. self.realtimeRetryCount = count
  189. }
  190. // Setter updates UserDefaults directly via computed property setter
  191. func updateCustomSignals(_ signals: [String: String]) {
  192. self.customSignals = signals
  193. }
  194. // Internal setters for properties usually read from userDefaults
  195. func setLastETag(_ etag: String?) {
  196. let now = Date().timeIntervalSince1970
  197. // Set timestamp first, then etag via computed property setters
  198. self.lastETagUpdateTime = now
  199. self.lastETag = etag
  200. }
  201. // MARK: - Load/Save Metadata (DB Interaction via RCNConfigDBManager)
  202. @discardableResult
  203. func loadConfigFromMetadataTable() -> [String: Any]? {
  204. // DB Interaction - Keep selector
  205. // loadMetadataWithBundleIdentifier:namespace:
  206. guard let metadata = dbManager.perform(#selector(RCNConfigDBManager.loadMetadata(withBundleIdentifier:namespace:)),
  207. with: bundleIdentifier,
  208. with: firebaseNamespace)?.takeUnretainedValue() as? [String: Any]
  209. else {
  210. return nil
  211. }
  212. // Parse metadata dictionary and update self properties
  213. if let contextData = metadata[Constants.keyDeviceContext] as? Data,
  214. let contextDict = try? JSONSerialization.jsonObject(with: contextData) as? [String: Any] {
  215. self.deviceContext = contextDict
  216. }
  217. if let appContextData = metadata[Constants.keyAppContext] as? Data,
  218. let appContextDict = try? JSONSerialization.jsonObject(with: appContextData) as? [String: Any] {
  219. self.customVariables = appContextDict
  220. }
  221. if let successTimesData = metadata[Constants.keySuccessFetchTime] as? Data,
  222. let successTimesArray = try? JSONSerialization.jsonObject(with: successTimesData) as? [TimeInterval] {
  223. self.successFetchTimes = successTimesArray
  224. }
  225. if let failureTimesData = metadata[Constants.keyFailureFetchTime] as? Data,
  226. let failureTimesArray = try? JSONSerialization.jsonObject(with: failureTimesData) as? [TimeInterval] {
  227. self.failureFetchTimes = failureTimesArray
  228. }
  229. if let statusString = metadata[Constants.keyLastFetchStatus] as? String, let statusInt = Int(statusString), let status = RemoteConfigFetchStatus(rawValue: statusInt) {
  230. self.lastFetchStatus = status
  231. }
  232. if let errorString = metadata[Constants.keyLastFetchError] as? String, let errorInt = Int(errorString), let error = RemoteConfigError(rawValue: errorInt) {
  233. self.lastFetchError = error
  234. }
  235. if let applyTimeString = metadata[Constants.keyLastApplyTime] as? String, let applyTime = TimeInterval(applyTimeString) {
  236. self.lastApplyTimeInterval = applyTime
  237. } else if let applyTimeNum = metadata[Constants.keyLastApplyTime] as? NSNumber { // Handle potential NSNumber storage
  238. self.lastApplyTimeInterval = applyTimeNum.doubleValue
  239. }
  240. if let defaultTimeString = metadata[Constants.keyLastSetDefaultsTime] as? String, let defaultTime = TimeInterval(defaultTimeString) {
  241. self.lastSetDefaultsTimeInterval = defaultTime
  242. } else if let defaultTimeNum = metadata[Constants.keyLastSetDefaultsTime] as? NSNumber { // Handle potential NSNumber storage
  243. self.lastSetDefaultsTimeInterval = defaultTimeNum.doubleValue
  244. }
  245. // Note: Properties read from UserDefaults (e.g., lastFetchTimeInterval) are handled by computed properties now.
  246. return metadata
  247. }
  248. func updateMetadataTable() {
  249. // DB Interaction - Keep selector
  250. // deleteRecordWithBundleIdentifier:namespace:
  251. _ = dbManager.perform(#selector(RCNConfigDBManager.deleteRecord(withBundleIdentifier:namespace:)),
  252. with: bundleIdentifier,
  253. with: firebaseNamespace)
  254. // Serialize data - Requires properties to be valid for JSONSerialization
  255. guard let appContextData = try? JSONSerialization.data(withJSONObject: customVariables) else {
  256. // TODO: Log error: FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000028", ...)
  257. return
  258. }
  259. guard let deviceContextData = try? JSONSerialization.data(withJSONObject: deviceContext) else {
  260. // TODO: Log error: FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000029", ...)
  261. return
  262. }
  263. // Backward compat only
  264. guard let digestPerNamespaceData = try? JSONSerialization.data(withJSONObject: [:]) else { return }
  265. guard let successTimeData = try? JSONSerialization.data(withJSONObject: successFetchTimes) else {
  266. // TODO: Log error: FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000031", ...)
  267. return
  268. }
  269. guard let failureTimeData = try? JSONSerialization.data(withJSONObject: failureFetchTimes) else {
  270. // TODO: Log error: FIRLogError(kFIRLoggerRemoteConfig, @"I-RCN000032", ...)
  271. return
  272. }
  273. // Read lastFetchTimeInterval directly via computed property
  274. let columnNameToValue: [String: Any] = [
  275. Constants.keyBundleIdentifier: bundleIdentifier,
  276. Constants.keyNamespace: firebaseNamespace,
  277. Constants.keyFetchTime: self.lastFetchTimeInterval, // Read directly
  278. Constants.keyDigestPerNamespace: digestPerNamespaceData,
  279. Constants.keyDeviceContext: deviceContextData,
  280. Constants.keyAppContext: appContextData,
  281. Constants.keySuccessFetchTime: successTimeData,
  282. Constants.keyFailureFetchTime: failureTimeData,
  283. Constants.keyLastFetchStatus: String(lastFetchStatus.rawValue), // Store as String like ObjC
  284. Constants.keyLastFetchError: String(lastFetchError.rawValue), // Store as String like ObjC
  285. Constants.keyLastApplyTime: lastApplyTimeInterval, // Store as TimeInterval/Double
  286. Constants.keyLastSetDefaultsTime: lastSetDefaultsTimeInterval // Store as TimeInterval/Double
  287. ]
  288. // DB Interaction - Keep selector
  289. // insertMetadataTableWithValues:completionHandler:
  290. dbManager.perform(#selector(RCNConfigDBManager.insertMetadataTable(withValues:completionHandler:)),
  291. with: columnNameToValue,
  292. with: nil) // No completion handler in original call
  293. }
  294. // Specific update methods (used by FIRRemoteConfig setters)
  295. func updateLastApplyTimeIntervalInDB(_ timeInterval: TimeInterval) {
  296. self.lastApplyTimeInterval = timeInterval
  297. // DB Interaction - Keep selector
  298. // updateMetadataWithOption:namespace:values:completionHandler:
  299. dbManager.perform(#selector(RCNConfigDBManager.updateMetadata(withOption:namespace:values:completionHandler:)),
  300. with: RCNUpdateOption.applyTime.rawValue, // Use enum raw value
  301. with: firebaseNamespace,
  302. with: [timeInterval],
  303. with: nil)
  304. }
  305. func updateLastSetDefaultsTimeIntervalInDB(_ timeInterval: TimeInterval) {
  306. self.lastSetDefaultsTimeInterval = timeInterval
  307. // DB Interaction - Keep selector
  308. // updateMetadataWithOption:namespace:values:completionHandler:
  309. dbManager.perform(#selector(RCNConfigDBManager.updateMetadata(withOption:namespace:values:completionHandler:)),
  310. with: RCNUpdateOption.defaultTime.rawValue, // Use enum raw value
  311. with: firebaseNamespace,
  312. with: [timeInterval],
  313. with: nil)
  314. }
  315. // MARK: - State Update Methods
  316. func updateMetadataWithFetchSuccessStatus(_ fetchSuccess: Bool, templateVersion: String?) {
  317. // TODO: Log debug: FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000056", ...)
  318. updateFetchTimeWithSuccessFetch(fetchSuccess)
  319. lastFetchStatus = fetchSuccess ? .success : .failure
  320. lastFetchError = fetchSuccess ? .unknown : .internalError // Assuming internalError for generic failure
  321. if fetchSuccess {
  322. updateLastFetchTimeInterval(Date().timeIntervalSince1970) // Updates UserDefaults via computed property
  323. // TODO: Get device context - Requires RCNDevice translation
  324. // deviceContext = FIRRemoteConfigDeviceContextWithProjectIdentifier(_googleAppID);
  325. deviceContext = getDeviceContextPlaceholder(projectID: googleAppID) // Placeholder call
  326. if let version = templateVersion {
  327. updateLastFetchedTemplateVersion(version) // Updates UserDefaults via computed property
  328. }
  329. }
  330. updateMetadataTable() // DB Interaction - Keep selector usage within
  331. }
  332. func updateFetchTimeWithSuccessFetch(_ isSuccessfulFetch: Bool) {
  333. let epochTimeInterval = Date().timeIntervalSince1970
  334. if isSuccessfulFetch {
  335. successFetchTimes.append(epochTimeInterval)
  336. } else {
  337. failureFetchTimes.append(epochTimeInterval)
  338. }
  339. // Note: DB update happens in updateMetadataTable called by updateMetadataWithFetchSuccessStatus
  340. }
  341. func updateLastActiveTemplateVersion() {
  342. if let fetchedVersion = self.lastFetchedTemplateVersion { // Reads via computed property
  343. // Calls setter which updates UserDefaults and local cache
  344. updateLastActiveTemplateVersionInUserDefaults(fetchedVersion)
  345. }
  346. }
  347. // MARK: - Throttling Logic
  348. func updateExponentialBackoffTime() {
  349. if lastFetchStatus == .success {
  350. // TODO: Log Debug: FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000057", @"Throttling: Entering exponential backoff mode.")
  351. exponentialBackoffRetryInterval = Constants.exponentialBackoffMinimumInterval
  352. } else {
  353. // TODO: Log Debug: FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000057", @"Throttling: Updating throttling interval.")
  354. let doubledInterval = exponentialBackoffRetryInterval * 2
  355. exponentialBackoffRetryInterval = min(doubledInterval, Constants.exponentialBackoffMaximumInterval)
  356. }
  357. // Randomize +/- 50%
  358. let randomFactor = Double.random(in: -0.5...0.5)
  359. let randomizedRetryInterval = exponentialBackoffRetryInterval + (exponentialBackoffRetryInterval * randomFactor)
  360. exponentialBackoffThrottleEndTime = Date().timeIntervalSince1970 + randomizedRetryInterval
  361. }
  362. func updateRealtimeExponentialBackoffTime() {
  363. var currentRetryInterval = self.realtimeExponentialBackoffRetryInterval // Read via computed property
  364. if realtimeRetryCount == 0 {
  365. // TODO: Log Debug: FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058", @"Throttling: Entering exponential Realtime backoff mode.")
  366. currentRetryInterval = Constants.exponentialBackoffMinimumInterval
  367. } else {
  368. // TODO: Log Debug: FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000058", @"Throttling: Updating Realtime throttling interval.")
  369. let doubledInterval = currentRetryInterval * 2
  370. currentRetryInterval = min(doubledInterval, Constants.exponentialBackoffMaximumInterval)
  371. }
  372. let randomFactor = Double.random(in: -0.5...0.5)
  373. let randomizedRetryInterval = currentRetryInterval + (currentRetryInterval * randomFactor)
  374. let newEndTime = Date().timeIntervalSince1970 + randomizedRetryInterval
  375. // Update UserDefaults via computed property setters
  376. self.realtimeThrottleEndTime = newEndTime
  377. self.realtimeExponentialBackoffRetryInterval = currentRetryInterval
  378. }
  379. func getRealtimeBackoffInterval() -> TimeInterval {
  380. let now = Date().timeIntervalSince1970
  381. let endTime = self.realtimeThrottleEndTime // Read directly via computed property
  382. let interval = endTime - now
  383. return max(0, interval) // Return 0 if end time is in the past
  384. }
  385. func shouldThrottle() -> Bool {
  386. // Check if not successful and backoff time is in the future
  387. let now = Date().timeIntervalSince1970
  388. return lastFetchStatus != .success && exponentialBackoffThrottleEndTime > now
  389. }
  390. func hasMinimumFetchIntervalElapsed(minimumInterval: TimeInterval) -> Bool {
  391. let lastFetch = self.lastFetchTimeInterval // Read directly via computed property
  392. if lastFetch <= 0 { return true } // No successful fetch yet
  393. let diffInSeconds = Date().timeIntervalSince1970 - lastFetch
  394. return diffInSeconds > minimumInterval
  395. }
  396. // MARK: - Fetch Request Body Construction
  397. func nextRequestWithUserProperties(_ userProperties: [String: Any]?) -> String? {
  398. // Ensure required IDs are present
  399. guard let installationsID = configInstallationsIdentifier,
  400. let installationsToken = configInstallationsToken,
  401. let appID = googleAppID else {
  402. // TODO: Log error?
  403. return nil
  404. }
  405. // Device Info - Keep selectors for RCNDevice
  406. // Assume RCNDevice is an NSObject subclass for perform(#selector(...))
  407. let countryCode = RCNDevice.perform(#selector(RCNDevice.deviceCountry))?.takeUnretainedValue() as? String ?? ""
  408. let languageCode = RCNDevice.perform(#selector(RCNDevice.deviceLocale))?.takeUnretainedValue() as? String ?? ""
  409. let platformVersion = RCNDevice.perform(#selector(RCNDevice.systemVersion))?.takeUnretainedValue() as? String ?? "" // GULAppEnvironmentUtil.systemVersion()
  410. let timeZone = RCNDevice.perform(#selector(RCNDevice.timezone))?.takeUnretainedValue() as? String ?? ""
  411. let appVersion = RCNDevice.perform(#selector(RCNDevice.appVersion))?.takeUnretainedValue() as? String ?? ""
  412. let appBuild = RCNDevice.perform(#selector(RCNDevice.appBuildVersion))?.takeUnretainedValue() as? String ?? ""
  413. let sdkVersion = RCNDevice.perform(#selector(RCNDevice.podVersion))?.takeUnretainedValue() as? String ?? "" // Renamed selector assuming podVersion exists
  414. var components: [String: String] = [
  415. "app_instance_id": "'\(installationsID)'",
  416. "app_instance_id_token": "'\(installationsToken)'",
  417. "app_id": "'\(appID)'",
  418. "country_code": "'\(countryCode)'",
  419. "language_code": "'\(languageCode)'",
  420. "platform_version": "'\(platformVersion)'",
  421. "time_zone": "'\(timeZone)'",
  422. "package_name": "'\(bundleIdentifier)'",
  423. "app_version": "'\(appVersion)'",
  424. "app_build": "'\(appBuild)'",
  425. "sdk_version": "'\(sdkVersion)'"
  426. ]
  427. var analyticsProperties = userProperties ?? [:]
  428. // Handle first open time
  429. if let firstOpenTimeNum = analyticsProperties[Constants.analyticsFirstOpenTimePropertyName] as? NSNumber {
  430. let firstOpenTimeSeconds = firstOpenTimeNum.doubleValue / 1000.0
  431. let date = Date(timeIntervalSince1970: firstOpenTimeSeconds)
  432. let formatter = ISO8601DateFormatter() // Swift equivalent
  433. components["first_open_time"] = "'\(formatter.string(from: date))'"
  434. analyticsProperties.removeValue(forKey: Constants.analyticsFirstOpenTimePropertyName)
  435. }
  436. // Add remaining analytics properties
  437. if !analyticsProperties.isEmpty {
  438. if let jsonData = try? JSONSerialization.data(withJSONObject: analyticsProperties),
  439. let jsonString = String(data: jsonData, encoding: .utf8) {
  440. components["analytics_user_properties"] = jsonString // No extra quotes needed? Check ObjC impl string format
  441. }
  442. }
  443. // Add custom signals
  444. let currentCustomSignals = self.customSignals // Read directly via computed property
  445. if !currentCustomSignals.isEmpty {
  446. if let jsonData = try? JSONSerialization.data(withJSONObject: currentCustomSignals),
  447. let jsonString = String(data: jsonData, encoding: .utf8) {
  448. components["custom_signals"] = jsonString // No extra quotes needed? Check ObjC impl string format
  449. // TODO: Log Debug: FIRLogDebug(kFIRLoggerRemoteConfig, @"I-RCN000078", ...)
  450. }
  451. }
  452. // Construct final string - Requires careful formatting to match ObjC exactly
  453. let bodyString = components.map { key, value in "\(key):\(value)" }.joined(separator: ", ")
  454. return "{\(bodyString)}"
  455. }
  456. // MARK: - Placeholder Helpers
  457. private func getDeviceContextPlaceholder(projectID: String) -> [String: Any] {
  458. // TODO: Replace with actual call to translated RCNDevice function
  459. return ["project_id": projectID] // Minimal placeholder
  460. }
  461. // MARK: - Placeholder Selectors for Dependencies
  462. // Selectors for DB Manager (kept as placeholder)
  463. @objc private func isNewDatabase() -> Bool { return false }
  464. @objc private func loadMetadata(withBundleIdentifier id: String, namespace ns: String) -> [String: Any]? { return nil }
  465. @objc private func deleteRecord(withBundleIdentifier id: String, namespace ns: String) {}
  466. @objc private func insertMetadataTable(withValues values: [String: Any], completionHandler handler: Any?) {} // Handler is optional block
  467. @objc private func updateMetadata(withOption option: Int, namespace ns: String, values: [Any], completionHandler handler: Any?) {} // Handler is optional block
  468. // RCNDevice selectors (static methods)
  469. // Keep these until RCNDevice is translated
  470. @objc private static func deviceCountry() -> String { return "" }
  471. @objc private static func deviceLocale() -> String { return "" }
  472. @objc private static func systemVersion() -> String { return "" } // GULAppEnvironmentUtil
  473. @objc private static func timezone() -> String { return "" }
  474. @objc private static func appVersion() -> String { return "" }
  475. @objc private static func appBuildVersion() -> String { return "" }
  476. @objc private static func podVersion() -> String { return "" } // FIRRemoteConfigPodVersion
  477. }
  478. // Extension providing @objc methods for RemoteConfig.swift to call
  479. // This is still needed as RemoteConfig uses selectors for DB updates via these methods
  480. extension RCNConfigSettingsInternal {
  481. // Properties accessed directly by RemoteConfig.swift do not need @objc methods here
  482. // (e.g., lastFetchTimeInterval, lastFetchStatus, minimumFetchInterval, fetchTimeout,
  483. // lastETagUpdateTime, lastApplyTimeInterval, lastActiveTemplateVersion)
  484. // Keep methods that involve DB interaction selectors
  485. @objc func updateLastApplyTimeIntervalInDB(_ interval: TimeInterval) {
  486. updateLastApplyTimeIntervalInDB(interval) // Calls internal func with DB selector call
  487. }
  488. @objc func updateLastSetDefaultsTimeIntervalInDB(_ interval: TimeInterval) {
  489. updateLastSetDefaultsTimeIntervalInDB(interval) // Calls internal func with DB selector call
  490. }
  491. @objc func updateLastActiveTemplateVersion() { // Matches selector used in RemoteConfig.activate
  492. updateLastActiveTemplateVersion() // Calls internal func that updates property & UserDefaults
  493. }
  494. }