learning_ai_common_plat/packages/swift-platform-sdk/Sources/BLFeatureFlagClient.swift
saravanakumardb1 78000cdf6a feat(swift-sdk): add ByteLystPlatformSDK — shared Swift package for all iOS/watchOS/macOS apps
Extracts duplicated platform integration code from ChronoMind + LysnrAI into a
single Swift Package. Eliminates ~1,100+ lines of copied code per product app.

Components:
- BLPlatformConfig — product-specific configuration (productId, baseURL, bundleId)
- BLPlatformClient — generic HTTP client with auth injection, x-request-id, timeout
- BLKeychain — Keychain CRUD for secure token storage
- BLTelemetryClient — telemetry queue + batch flush (matches @bytelyst/telemetry-client)
- BLAuthClient — full auth operations (matches @bytelyst/auth-client)
- BLFeatureFlagClient — feature flag polling from platform-service /flags/poll
- BLSyncEngine — generic offline-first sync with delta pull + batch push

Platforms: iOS 17+, watchOS 10+, macOS 14+
2026-02-28 22:12:20 -08:00

87 lines
2.5 KiB
Swift

// Feature Flag Client
// Generic feature flag polling from platform-service /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
}
}
}