learning_ai_clock/ios/ChronoMind/Shared/Cloud/FeatureFlagService.swift

72 lines
2.4 KiB
Swift

// Feature Flag Client
// Polls platform-service /flags/poll for feature flags.
// Flags cached in memory, re-polled every 5 minutes.
// Consumers call FeatureFlagService.shared.isEnabled("flag_key").
import Foundation
@MainActor
final class FeatureFlagService: ObservableObject {
static let shared = FeatureFlagService()
@Published private(set) var flags: [String: Bool] = [:]
private var pollTimer: Timer?
private let pollIntervalSec: TimeInterval = 5 * 60 // 5 minutes
private let productId = "chronomind"
private var baseURL: String {
Bundle.main.object(forInfoDictionaryKey: "PLATFORM_SERVICE_URL") as? String
?? "https://api.chronomind.app"
}
private init() {}
// MARK: - Public API
func start(userId: String? = nil) {
Task { await fetchFlags(userId: userId) }
pollTimer?.invalidate()
pollTimer = Timer.scheduledTimer(withTimeInterval: pollIntervalSec, repeats: true) { [weak self] _ in
guard let self else { return }
Task { @MainActor in await self.fetchFlags(userId: userId) }
}
}
func stop() {
pollTimer?.invalidate()
pollTimer = nil
}
func isEnabled(_ key: String) -> Bool {
flags[key] == true
}
// MARK: - Fetch
private func fetchFlags(userId: String? = nil) async {
var components = URLComponents(string: "\(baseURL)/api/flags/poll")
var queryItems: [URLQueryItem] = [.init(name: "platform", value: "ios")]
if let userId { queryItems.append(.init(name: "userId", value: userId)) }
components?.queryItems = queryItems
guard let url = components?.url else { return }
var request = URLRequest(url: url)
request.setValue(productId, forHTTPHeaderField: "X-Product-Id")
request.timeoutInterval = 10
do {
let (data, response) = try await URLSession.shared.data(for: request)
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return }
struct FlagsResponse: Codable {
let flags: [String: Bool]
}
let parsed = try JSONDecoder().decode(FlagsResponse.self, from: data)
flags = parsed.flags
} catch {
// Keep existing flags on error
}
}
}