| .. | ||
| Sources | ||
| Tests | ||
| Package.swift | ||
| README.md | ||
ByteLystPlatformSDK
Shared Swift platform client for all ByteLyst iOS/watchOS/macOS apps. Eliminates code duplication across products by providing a single source of truth for platform-service integration.
What's Inside
| File | What It Does |
|---|---|
BLPlatformConfig |
Product-specific configuration (productId, baseURL, bundleId, appGroupId) |
BLPlatformClient |
Generic HTTP client with auth injection, x-request-id, timeout |
BLKeychain |
Keychain CRUD for secure token storage |
BLTelemetryClient |
Telemetry event queue + batch flush (matches @bytelyst/telemetry-client) |
BLAuthClient |
Auth operations: login, register, refresh, password ops, email verify, delete account (matches @bytelyst/auth-client) |
BLFeatureFlagClient |
Feature flag polling from platform-service /api/flags/poll |
BLSyncEngine |
Generic offline-first sync engine with delta pull + batch push |
BLBlobClient |
Azure Blob Storage upload via SAS tokens from platform-service |
BLKillSwitchClient |
Kill switch check from platform-service (fail-open) |
BLLicenseClient |
License key activation + status via platform-service |
BLBiometricAuth |
Face ID / Touch ID wrapper (LocalAuthentication) |
BLCrashReporter |
MetricKit crash and hang reporting with local storage |
BLAuditLogger |
Local rotating JSON audit log for debugging |
Usage
1. Add to Xcode Project
In Xcode: File → Add Package Dependencies → Add Local... → select this directory:
../learning_ai_common_plat/packages/swift-platform-sdk/
Or in Package.swift:
.package(path: "../learning_ai_common_plat/packages/swift-platform-sdk")
2. Configure at App Launch
import ByteLystPlatformSDK
// Create config — one per app
let config = BLPlatformConfig.fromInfoPlist(
productId: "peakpulse",
defaultBaseURL: "https://api.peakpulse.app",
bundleId: "com.saravana.peakpulse"
)
// Create shared HTTP client
let client = BLPlatformClient(config: config)
// Create services
let telemetry = BLTelemetryClient(config: config, client: client)
let auth = BLAuthClient(config: config, client: client)
let flags = BLFeatureFlagClient(config: config, client: client)
3. Telemetry
telemetry.start()
telemetry.trackEvent("info", module: "session", name: "session_started", metrics: ["elevation": 2450.0])
telemetry.trackScreen("live_tracking")
// On app background:
telemetry.stop()
4. Auth
// Login
let user = try await auth.login(email: "user@example.com", password: "secret")
// Restore on launch
await auth.restoreSession()
// Listen for state changes
auth.onAuthStateChanged = { state in
switch state {
case .loggedIn(let user): print("Hello, \(user.displayName)")
case .loggedOut: print("Signed out")
case .error(let msg): print("Auth error: \(msg)")
case .loading: print("Loading...")
}
}
5. Feature Flags
flags.start(userId: auth.accessToken != nil ? "user-id" : nil)
if flags.isEnabled("peakpulse.pro_charts") {
// Show Pro charts
}
6. Sync Engine
// Implement your product-specific adapter
struct PeakSessionSyncAdapter: BLSyncAdapter {
typealias SyncItem = PeakSessionDTO
func pullDelta(since: Date?, client: BLPlatformClient) async throws -> [PeakSessionDTO] {
var path = "/api/peak-sessions/sync"
if let since { path += "?since=\(ISO8601DateFormatter().string(from: since))" }
return try await client.request(path: path, responseType: [PeakSessionDTO].self)
}
func pushBatch(_ items: [BLOfflineQueueItem], client: BLPlatformClient) async throws -> BLBatchResult {
let body = try JSONEncoder().encode(["items": items.compactMap(\.payload)])
let (data, _) = try await client.rawRequest(path: "/api/peak-sessions/batch", method: "POST", body: body)
return try JSONDecoder().decode(BLBatchResult.self, from: data)
}
}
let syncEngine = BLSyncEngine(
config: config,
client: client,
adapter: PeakSessionSyncAdapter()
)
Product Apps Using This SDK
| Product | Repo | Wrappers | Status |
|---|---|---|---|
| ChronoMind | learning_ai_clock |
5 files | ✅ Migrated (Cloud/ + Diagnostics/) |
| LysnrAI | learning_voice_ai_agent |
9 files | ✅ Migrated (Auth/ + Util/) |
| MindLyst | learning_multimodal_memory_agents |
4 files | ✅ Migrated (Services/) |
| PeakPulse | learning_ai_peakpulse |
— | New — will use SDK from day one |
| NomGap | learning_ai_fastgap |
— | React Native — uses TS packages |
What This Replaces
Before this SDK, each iOS app had its own copy of platform integration code:
| ChronoMind (old) | LysnrAI (old) | MindLyst (old) | SDK (new) |
|---|---|---|---|
KeychainHelper (53 lines) |
KeychainHelper (60 lines) |
KeychainHelper (60 lines) |
BLKeychain |
CMTelemetryService (139 lines) |
TelemetryService (288 lines) |
TelemetryService (139 lines) |
BLTelemetryClient |
CMAuthService (359 lines) |
AuthService (421 lines) |
AuthService (389 lines) |
BLAuthClient |
FeatureFlagService (72 lines) |
FeatureFlagService (71 lines) |
FeatureFlagService (72 lines) |
BLFeatureFlagClient |
CrashReporter (153 lines) |
— | — | BLCrashReporter |
| — | BlobService (118 lines) |
— | BLBlobClient |
| — | KillSwitchService (48 lines) |
— | BLKillSwitchClient |
| — | LicenseService (135 lines) |
— | BLLicenseClient |
| — | BiometricAuth (65 lines) |
— | BLBiometricAuth |
| — | AuditLogger (70 lines) |
— | BLAuditLogger |
PlatformSyncManager (450 lines) |
Various sync files | — | BLSyncEngine |
Total duplicated code eliminated: ~2,600+ lines across 3 product apps.
Design Decisions
- No
@MainActor— the SDK is thread-safe via NSLock. Product apps can wrap in@MainActorat the view model layer. - No singletons — product apps own the lifecycle. Create instances at app launch, inject where needed.
- No SwiftUI dependency — pure Foundation. Works in watchOS, macOS, widgets, extensions.
- Protocol-based sync —
BLSyncAdapterlets each product define its own DTOs and endpoints while reusing the queue/timer/conflict plumbing. - Fire-and-forget telemetry — errors never surface to the user. Matches the TypeScript package behavior.
Platforms
- iOS 17+
- watchOS 10+
- macOS 14+
Broadcast & Survey
New in v1.2: In-app messaging and survey capabilities.
Broadcast Client
import ByteLystPlatformSDK
let config = BLPlatformConfig(
productId: "lysnrai",
baseURL: URL(string: "https://api.bytelyst.io/v1")!,
getAuthToken: { await getToken() }
)
let broadcastClient = BLBroadcastClient(config: config)
// Start polling for messages
broadcastClient.startPolling(intervalMs: 60000) { messages in
// Handle new messages
}
// SwiftUI UI components
BLInAppMessageBanner(client: broadcastClient, position: .top)
BLBroadcastModal(client: broadcastClient)
Survey Client
let surveyClient = BLSurveyClient(config: config)
// Check for active surveys
let (survey, _) = await surveyClient.getActiveSurvey()
// Start and complete survey
await surveyClient.startSurvey(surveyId: survey.id)
await surveyClient.submitAnswer(surveyId: survey.id, questionId: "q1", answer: answer)
await surveyClient.completeSurvey(surveyId: survey.id)
// SwiftUI modal
BLSurveyModal(client: surveyClient)
See Broadcast & Survey Guide for full documentation.