diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 96f77274..80282cb6 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,3 +1,4 @@ +import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING import com.powersync.plugins.sonatype.setupGithubRepository import de.undercouch.gradle.tasks.download.Download import org.gradle.api.tasks.testing.logging.TestExceptionFormat @@ -21,6 +22,7 @@ plugins { id("com.powersync.plugins.sonatype") alias(libs.plugins.mokkery) alias(libs.plugins.kotlin.atomicfu) + alias(libs.plugins.buildKonfig) } val binariesFolder = project.layout.buildDirectory.dir("binaries/desktop") @@ -315,8 +317,19 @@ android { } androidComponents.onVariants { - tasks.named("preBuild") { - dependsOn(moveJDBCJNIFiles) + tasks.named("preBuild") { + dependsOn(moveJDBCJNIFiles) + } +} + +buildkonfig { + packageName = "com.powersync.core" + defaultConfigs { + buildConfigField(STRING, "LIBRARY_VERSION", version.toString()) + + // TODO: Swift SDK relies on this too. + // Find out how to add a build flag to toggle between "powersync-kotlin" and "powersync-swift". + buildConfigField(STRING, "LIBRARY_NAME", "powersync-kotlin") } } @@ -349,4 +362,13 @@ tasks.withType { showStackTraces = true } } + +tasks.formatKotlinCommonMain { + exclude { it.file.name == "BuildKonfig.kt" } +} + +tasks.lintKotlinCommonMain { + exclude { it.file.name == "BuildKonfig.kt" } +} + setupGithubRepository() diff --git a/core/src/androidMain/kotlin/com/powersync/sync/SyncStream.android.kt b/core/src/androidMain/kotlin/com/powersync/sync/SyncStream.android.kt new file mode 100644 index 00000000..a9c4c67a --- /dev/null +++ b/core/src/androidMain/kotlin/com/powersync/sync/SyncStream.android.kt @@ -0,0 +1,9 @@ +package com.powersync.sync + +import android.os.Build + +internal actual fun getOS(): String { + val base = Build.VERSION.BASE_OS + val version = Build.VERSION.SDK_INT + return "android $base/$version" +} diff --git a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt index 23b3e5ed..4f8155ad 100644 --- a/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt +++ b/core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt @@ -8,6 +8,8 @@ import com.powersync.bucket.BucketStorage import com.powersync.bucket.Checkpoint import com.powersync.bucket.WriteCheckpointResponse import com.powersync.connectors.PowerSyncBackendConnector +import com.powersync.core.BuildKonfig.LIBRARY_NAME +import com.powersync.core.BuildKonfig.LIBRARY_VERSION import com.powersync.db.crud.CrudEntry import com.powersync.utils.JsonUtil import io.ktor.client.HttpClient @@ -35,6 +37,22 @@ import kotlinx.coroutines.flow.flow import kotlinx.datetime.Clock import kotlinx.serialization.encodeToString import kotlinx.serialization.json.JsonObject +import kotlin.collections.MutableSet +import kotlin.collections.buildList +import kotlin.collections.component1 +import kotlin.collections.component2 +import kotlin.collections.filter +import kotlin.collections.forEach +import kotlin.collections.isNotEmpty +import kotlin.collections.joinToString +import kotlin.collections.listOf +import kotlin.collections.map +import kotlin.collections.mutableMapOf +import kotlin.collections.mutableSetOf +import kotlin.collections.set +import kotlin.collections.toList +import kotlin.collections.toMutableList +import kotlin.collections.toMutableSet internal class SyncStream( private val bucketStorage: BucketStorage, @@ -172,7 +190,7 @@ internal class SyncStream( contentType(ContentType.Application.Json) headers { append(HttpHeaders.Authorization, "Token ${credentials.token}") - append("User-Id", credentials.userId ?: "") + append("User-Agent", powerSyncUserAgent()) } } if (response.status.value == 401) { @@ -186,6 +204,8 @@ internal class SyncStream( return body.data.writeCheckpoint } + private fun powerSyncUserAgent(): String = "$LIBRARY_NAME/$LIBRARY_VERSION ${getOS()}" + private fun streamingSyncRequest(req: StreamingSyncRequest): Flow = flow { val credentials = connector.getCredentialsCached() @@ -200,7 +220,7 @@ internal class SyncStream( contentType(ContentType.Application.Json) headers { append(HttpHeaders.Authorization, "Token ${credentials.token}") - append("User-Id", credentials.userId ?: "") + append("User-Agent", powerSyncUserAgent()) } timeout { socketTimeoutMillis = Long.MAX_VALUE } setBody(bodyJson) @@ -449,6 +469,8 @@ internal class SyncStream( } } +internal expect fun getOS(): String + internal data class SyncStreamState( var targetCheckpoint: Checkpoint?, var validatedCheckpoint: Checkpoint?, diff --git a/core/src/iosMain/kotlin/com/powersync/sync/SyncStream.ios.kt b/core/src/iosMain/kotlin/com/powersync/sync/SyncStream.ios.kt new file mode 100644 index 00000000..66b9de39 --- /dev/null +++ b/core/src/iosMain/kotlin/com/powersync/sync/SyncStream.ios.kt @@ -0,0 +1,10 @@ +package com.powersync.sync + +import platform.UIKit.UIDevice + +internal actual fun getOS(): String { + val current = UIDevice.currentDevice + val version = current.systemVersion + + return "ios $version" +} diff --git a/core/src/jvmMain/kotlin/com/powersync/sync/SyncStream.jvm.kt b/core/src/jvmMain/kotlin/com/powersync/sync/SyncStream.jvm.kt new file mode 100644 index 00000000..e8dee744 --- /dev/null +++ b/core/src/jvmMain/kotlin/com/powersync/sync/SyncStream.jvm.kt @@ -0,0 +1,7 @@ +package com.powersync.sync + +internal actual fun getOS(): String { + val os = System.getProperty("os.name") + val version = System.getProperty("os.version") + return "jvm $os/$version" +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 224c0422..a20ac503 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,7 @@ compose-preview = "1.7.8" androidxSqlite = "2.4.0" # plugins -android-gradle-plugin = "8.9.0" +android-gradle-plugin = "8.9.1" kmmBridge = "0.5.7" skie = "0.10.1" maven-publish = "0.27.0" @@ -40,6 +40,7 @@ mokkery = "2.7.1" kotlinter = "5.0.1" keeper = "0.16.1" atomicfu = "0.27.0" +buildKonfig = "0.17.0" # Sample - Android androidx-core = "1.15.0" @@ -128,6 +129,7 @@ kotlinter = { id = "org.jmailen.kotlinter", version.ref = "kotlinter" } keeper = { id = "com.slack.keeper", version.ref = "keeper" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-atomicfu = { id = "org.jetbrains.kotlinx.atomicfu", version.ref = "atomicfu" } +buildKonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildKonfig" } [bundles] sqldelight = [