From 70635ba80edb16ce44f61850cbebf5921615e380 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Thu, 19 Mar 2026 21:09:24 -0700 Subject: [PATCH] feat(kotlin-sdk): add ByteLystPlatform unified entry point + 2 new test files (4.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New source: - ByteLystPlatform.kt — unified entry point wiring all services via Context + Config (secureStore, client, auth, telemetry, flags, killSwitch, auditLog) - start(userId?) / stop() lifecycle for telemetry + flags - Mirrors Swift ByteLystPlatform API New tests (2 files): - BLKillSwitchResultTest — ok(), disabled, default, copy - BLTelemetryEventTest — serialize, deserialize, optional fields --- .../com/bytelyst/platform/ByteLystPlatform.kt | 80 ++++++++++++++++ .../platform/BLKillSwitchResultTest.kt | 39 ++++++++ .../bytelyst/platform/BLTelemetryEventTest.kt | 92 +++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 packages/kotlin-platform-sdk/src/main/kotlin/com/bytelyst/platform/ByteLystPlatform.kt create mode 100644 packages/kotlin-platform-sdk/src/test/kotlin/com/bytelyst/platform/BLKillSwitchResultTest.kt create mode 100644 packages/kotlin-platform-sdk/src/test/kotlin/com/bytelyst/platform/BLTelemetryEventTest.kt diff --git a/packages/kotlin-platform-sdk/src/main/kotlin/com/bytelyst/platform/ByteLystPlatform.kt b/packages/kotlin-platform-sdk/src/main/kotlin/com/bytelyst/platform/ByteLystPlatform.kt new file mode 100644 index 00000000..64dfdb12 --- /dev/null +++ b/packages/kotlin-platform-sdk/src/main/kotlin/com/bytelyst/platform/ByteLystPlatform.kt @@ -0,0 +1,80 @@ +package com.bytelyst.platform + +import android.content.Context + +/** + * Unified entry point for the ByteLyst platform SDK. + * + * Creates and wires all platform services from a single config + context. + * Mirrors the Swift `ByteLystPlatform` API. + * + * Usage: + * ```kotlin + * val platform = ByteLystPlatform( + * context = applicationContext, + * config = BLPlatformConfig( + * productId = "chronomind", + * baseUrl = "https://api.chronomind.app", + * applicationId = "com.chronomind.app" + * ) + * ) + * + * platform.start() + * platform.telemetry.trackScreen("home") + * val isNew = platform.flags.isEnabled("new_feature") + * platform.stop() + * ``` + */ +class ByteLystPlatform( + context: Context, + val config: BLPlatformConfig, +) { + /** Secure storage (EncryptedSharedPreferences). */ + val secureStore: BLSecureStore = BLSecureStore(context, config.applicationId) + + /** HTTP client shared by services that need a token provider. */ + val client: BLPlatformClient = BLPlatformClient(config) { + secureStore.getString("access_token") + } + + /** Auth client (login, register, refresh, MFA, etc.). */ + val auth: BLAuthClient = BLAuthClient(config, secureStore) + + /** Telemetry event tracking + batch flush. */ + val telemetry: BLTelemetryClient = BLTelemetryClient(config, context) + + /** Feature flag polling. */ + val flags: BLFeatureFlagClient = BLFeatureFlagClient(config) + + /** Kill switch checker. */ + val killSwitch: BLKillSwitchClient = BLKillSwitchClient(config) + + /** Local rotating audit log. */ + val auditLog: BLAuditLogger = BLAuditLogger(context, config) + + /** Whether [start] has been called. */ + var isStarted: Boolean = false + private set + + /** + * Start all services: telemetry flush timer, feature flag polling. + * Call on app launch / Activity.onCreate. + */ + fun start(userId: String? = null) { + if (isStarted) return + isStarted = true + telemetry.start() + flags.init(userId) + } + + /** + * Stop all services: flush telemetry, stop flag polling. + * Call on app background / Activity.onDestroy. + */ + fun stop() { + if (!isStarted) return + isStarted = false + telemetry.stop() + flags.stop() + } +} diff --git a/packages/kotlin-platform-sdk/src/test/kotlin/com/bytelyst/platform/BLKillSwitchResultTest.kt b/packages/kotlin-platform-sdk/src/test/kotlin/com/bytelyst/platform/BLKillSwitchResultTest.kt new file mode 100644 index 00000000..bf1825c6 --- /dev/null +++ b/packages/kotlin-platform-sdk/src/test/kotlin/com/bytelyst/platform/BLKillSwitchResultTest.kt @@ -0,0 +1,39 @@ +package com.bytelyst.platform + +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class BLKillSwitchResultTest { + + @Test + fun `ok() should return not-disabled result`() { + val result = BLKillSwitchClient.KillSwitchResult.ok() + assertFalse(result.disabled) + assertNull(result.message) + } + + @Test + fun `disabled result should have message`() { + val result = BLKillSwitchClient.KillSwitchResult( + disabled = true, + message = "Under maintenance" + ) + assertTrue(result.disabled) + assertEquals("Under maintenance", result.message) + } + + @Test + fun `default result should not be disabled`() { + val result = BLKillSwitchClient.KillSwitchResult() + assertFalse(result.disabled) + assertNull(result.message) + } + + @Test + fun `data class copy should work`() { + val original = BLKillSwitchClient.KillSwitchResult.ok() + val modified = original.copy(disabled = true, message = "Updating") + assertTrue(modified.disabled) + assertEquals("Updating", modified.message) + } +} diff --git a/packages/kotlin-platform-sdk/src/test/kotlin/com/bytelyst/platform/BLTelemetryEventTest.kt b/packages/kotlin-platform-sdk/src/test/kotlin/com/bytelyst/platform/BLTelemetryEventTest.kt new file mode 100644 index 00000000..6d4de1a6 --- /dev/null +++ b/packages/kotlin-platform-sdk/src/test/kotlin/com/bytelyst/platform/BLTelemetryEventTest.kt @@ -0,0 +1,92 @@ +package com.bytelyst.platform + +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test + +class BLTelemetryEventTest { + + private val json = Json { ignoreUnknownKeys = true; encodeDefaults = true } + + @Test + fun `event should serialize to JSON`() { + val event = BLTelemetryClient.TelemetryEvent( + id = "evt-1", + productId = "testapp", + anonymousInstallId = "inst-1", + sessionId = "sess-1", + platform = "android", + channel = "native", + osFamily = "android", + osVersion = "14", + appVersion = "1.0.0", + buildNumber = "42", + releaseChannel = "beta", + eventType = "info", + module = "test", + eventName = "unit_test", + occurredAt = "2026-01-01T00:00:00Z", + ) + val jsonStr = json.encodeToString(event) + assertTrue(jsonStr.contains("\"productId\":\"testapp\"")) + assertTrue(jsonStr.contains("\"eventName\":\"unit_test\"")) + } + + @Test + fun `event should deserialize from JSON`() { + val jsonStr = """ + { + "id": "evt-1", + "productId": "testapp", + "anonymousInstallId": "inst-1", + "sessionId": "sess-1", + "platform": "android", + "channel": "native", + "osFamily": "android", + "osVersion": "14", + "appVersion": "1.0.0", + "buildNumber": "42", + "releaseChannel": "beta", + "eventType": "info", + "module": "test", + "eventName": "unit_test", + "occurredAt": "2026-01-01T00:00:00Z" + } + """.trimIndent() + val event = json.decodeFromString(jsonStr) + assertEquals("testapp", event.productId) + assertEquals("unit_test", event.eventName) + assertNull(event.feature) + assertNull(event.tags) + } + + @Test + fun `event with optional fields should serialize correctly`() { + val event = BLTelemetryClient.TelemetryEvent( + id = "evt-2", + productId = "testapp", + anonymousInstallId = "inst-1", + sessionId = "sess-1", + platform = "android", + channel = "native", + osFamily = "android", + osVersion = "14", + appVersion = "1.0.0", + buildNumber = "42", + releaseChannel = "beta", + eventType = "error", + module = "auth", + eventName = "login_failed", + feature = "social_login", + message = "Token expired", + tags = mapOf("provider" to "google"), + metrics = mapOf("retryCount" to 3.0), + occurredAt = "2026-01-01T00:00:00Z", + ) + val jsonStr = json.encodeToString(event) + assertTrue(jsonStr.contains("\"feature\":\"social_login\"")) + assertTrue(jsonStr.contains("\"provider\":\"google\"")) + assertTrue(jsonStr.contains("\"retryCount\":3.0")) + } +}