- Swift SDK README: Installation, Broadcast/Survey clients, SwiftUI integration, push notifications - Kotlin SDK README: Gradle setup, Jetpack Compose components, FCM integration
216 lines
9.4 KiB
Markdown
216 lines
9.4 KiB
Markdown
# 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`:
|
|
|
|
```swift
|
|
.package(path: "../learning_ai_common_plat/packages/swift-platform-sdk")
|
|
```
|
|
|
|
### 2. Configure at App Launch
|
|
|
|
```swift
|
|
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
|
|
|
|
```swift
|
|
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
|
|
|
|
```swift
|
|
// 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
|
|
|
|
```swift
|
|
flags.start(userId: auth.accessToken != nil ? "user-id" : nil)
|
|
if flags.isEnabled("peakpulse.pro_charts") {
|
|
// Show Pro charts
|
|
}
|
|
```
|
|
|
|
### 6. Sync Engine
|
|
|
|
```swift
|
|
// 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
|
|
|
|
1. **No `@MainActor`** — the SDK is thread-safe via NSLock. Product apps can wrap in `@MainActor` at the view model layer.
|
|
2. **No singletons** — product apps own the lifecycle. Create instances at app launch, inject where needed.
|
|
3. **No SwiftUI dependency** — pure Foundation. Works in watchOS, macOS, widgets, extensions.
|
|
4. **Protocol-based sync** — `BLSyncAdapter` lets each product define its own DTOs and endpoints while reusing the queue/timer/conflict plumbing.
|
|
5. **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
|
|
|
|
```swift
|
|
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
|
|
|
|
```swift
|
|
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](BROADCAST_SURVEY_GUIDE.md) for full documentation.
|
|
|