learning_ai_common_plat/packages/swift-platform-sdk/Sources/BLFeatureFlagClient.swift

87 lines
2.5 KiB
Swift

// Feature Flag Client
// Generic feature flag polling from platform-service /api/flags/poll.
// Flags cached in memory, re-polled at configurable interval.
// Matches the platform-service flags module API.
import Foundation
/// Generic feature flag client for all ByteLyst iOS apps.
/// Polls platform-service and caches flag values in memory.
public final class BLFeatureFlagClient {
private let config: BLPlatformConfig
private let client: BLPlatformClient
private let pollIntervalSec: TimeInterval
private var flags: [String: Bool] = [:]
private let flagsLock = NSLock()
private var pollTimer: Timer?
public init(
config: BLPlatformConfig,
client: BLPlatformClient,
pollIntervalSec: TimeInterval = 5 * 60
) {
self.config = config
self.client = client
self.pollIntervalSec = pollIntervalSec
}
// MARK: - Lifecycle
/// Start polling for feature flags.
public 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 { await self.fetchFlags(userId: userId) }
}
}
/// Stop polling.
public func stop() {
pollTimer?.invalidate()
pollTimer = nil
}
// MARK: - Query
/// Check if a feature flag is enabled.
public func isEnabled(_ key: String) -> Bool {
flagsLock.lock()
defer { flagsLock.unlock() }
return flags[key] == true
}
/// Get all current flag values.
public func allFlags() -> [String: Bool] {
flagsLock.lock()
defer { flagsLock.unlock() }
return flags
}
// MARK: - Fetch
private struct FlagsResponse: Codable {
let flags: [String: Bool]
}
private func fetchFlags(userId: String? = nil) async {
var path = "/api/flags/poll?platform=\(config.platform)"
if let userId { path += "&userId=\(userId)" }
do {
let result = try await client.request(
path: path,
responseType: FlagsResponse.self
)
flagsLock.lock()
flags = result.flags
flagsLock.unlock()
} catch {
// Keep existing flags on error silent failure
}
}
}