learning_ai_common_plat/packages/swift-platform-sdk/Sources/ByteLystPlatform.swift
saravanakumardb1 933390e89b feat(swift-sdk): add ByteLystPlatform unified entry point + 5 new test files (4.1)
New source:
- ByteLystPlatform.swift — unified entry point wiring all services
  (config, client, telemetry, flags, killSwitch, crashReporter, keychain, auditLog, auth)
- BLKeychainAccessor — convenience wrapper binding BLKeychain to a bundleId
- start(userId:) / stop() lifecycle for telemetry + flags + killSwitch

New tests (5 files, ~25 test cases):
- ByteLystPlatformTests — init, start/stop, idempotency, keychain accessor
- BLPlatformConfigTests — default + custom init
- BLKillSwitchClientTests — default state, reset
- BLFeatureFlagClientTests — empty flags, unknown key, stop
- BLTelemetryClientTests — installId stability, session rotation, track/flush

Also: add .build/ and .swiftpm/ to .gitignore
2026-03-19 21:05:58 -07:00

122 lines
4.2 KiB
Swift

// ByteLystPlatform
// Unified entry point for the ByteLyst platform SDK.
// Creates and wires all platform services from a single config.
//
// Usage:
// let platform = ByteLystPlatform(config: .init(
// productId: "peakpulse",
// baseURL: "https://api.peakpulse.app",
// bundleId: "com.saravana.peakpulse"
// ))
//
// platform.start() // Start telemetry + flags + kill switch
// platform.telemetry.trackScreen("home") // Track events
// let isNew = platform.flags.isEnabled("new_feature")
// platform.stop() // Flush + stop timers
import Foundation
/// Unified entry point that wires all ByteLyst platform services together.
/// Create one instance at app launch and access services via properties.
public final class ByteLystPlatform {
/// Platform configuration.
public let config: BLPlatformConfig
/// HTTP client shared by all services.
public let client: BLPlatformClient
/// Telemetry event tracking.
public let telemetry: BLTelemetryClient
/// Feature flag polling.
public let flags: BLFeatureFlagClient
/// Kill switch checker.
public let killSwitch: BLKillSwitchClient
/// Crash reporter (MetricKit).
public let crashReporter: BLCrashReporter
/// Keychain access (via bundleId as service).
public let keychain: BLKeychainAccessor
/// Audit logger.
public let auditLog: BLAuditLogger
/// Auth client.
public let auth: BLAuthClient
/// Whether `start()` has been called.
public private(set) var isStarted = false
public init(config: BLPlatformConfig) {
self.config = config
self.client = BLPlatformClient(config: config)
self.telemetry = BLTelemetryClient(config: config, client: client)
self.flags = BLFeatureFlagClient(config: config, client: client)
self.killSwitch = BLKillSwitchClient(config: config)
self.crashReporter = BLCrashReporter(productId: config.productId)
self.keychain = BLKeychainAccessor(service: config.bundleId)
self.auditLog = BLAuditLogger(productId: config.productId)
self.auth = BLAuthClient(config: config, client: client)
}
/// Test-only initializer that accepts a custom URLSessionConfiguration.
public init(config: BLPlatformConfig, sessionConfiguration: URLSessionConfiguration) {
self.config = config
self.client = BLPlatformClient(config: config, sessionConfiguration: sessionConfiguration)
self.telemetry = BLTelemetryClient(config: config, client: client)
self.flags = BLFeatureFlagClient(config: config, client: client)
self.killSwitch = BLKillSwitchClient(config: config)
self.crashReporter = BLCrashReporter(productId: config.productId)
self.keychain = BLKeychainAccessor(service: config.bundleId)
self.auditLog = BLAuditLogger(productId: config.productId)
self.auth = BLAuthClient(config: config, client: client)
}
// MARK: - Lifecycle
/// Start all services: telemetry flush timer, feature flag polling, kill switch check.
public func start(userId: String? = nil) {
guard !isStarted else { return }
isStarted = true
telemetry.start()
flags.start(userId: userId)
Task { await killSwitch.check() }
}
/// Stop all services: flush telemetry, stop flag polling.
public func stop() {
guard isStarted else { return }
isStarted = false
telemetry.stop()
flags.stop()
}
}
// MARK: - Keychain Accessor
/// Convenience wrapper around BLKeychain that binds to a specific service (bundleId).
public struct BLKeychainAccessor {
private let service: String
public init(service: String) {
self.service = service
}
@discardableResult
public func save(key: String, value: String) -> Bool {
BLKeychain.save(service: service, key: key, value: value)
}
public func read(key: String) -> String? {
BLKeychain.read(service: service, key: key)
}
@discardableResult
public func delete(key: String) -> Bool {
BLKeychain.delete(service: service, key: key)
}
}