Skip to content

Add User-Agent field #158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions core/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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")
Comment on lines +330 to +332
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we don't have to worry about this. We'll likely add an API for clients to add their own headers in the future. The Swift SDK could use that to always override the User-Agent header if necessary. So it should be fine if this always reports powersync-kotlin for now.

}
}

Expand Down Expand Up @@ -349,4 +362,13 @@ tasks.withType<KotlinTest> {
showStackTraces = true
}
}

tasks.formatKotlinCommonMain {
exclude { it.file.name == "BuildKonfig.kt" }
}

tasks.lintKotlinCommonMain {
exclude { it.file.name == "BuildKonfig.kt" }
}

setupGithubRepository()
Original file line number Diff line number Diff line change
@@ -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"
}
26 changes: 24 additions & 2 deletions core/src/commonMain/kotlin/com/powersync/sync/SyncStream.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we're doing this on all requests, a better place to put this may be in init:

fun HttpClientConfig<*>.configureClient() {
    install(HttpTimeout)
    install(ContentNegotiation)

    defaultRequest {
        headers {
            append("User-Agent", $LIBRARY_NAME/$LIBRARY_VERSION ${getOS()}")
        }
    }
}

}
}
if (response.status.value == 401) {
Expand All @@ -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<String> =
flow {
val credentials = connector.getCredentialsCached()
Expand All @@ -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)
Expand Down Expand Up @@ -449,6 +469,8 @@ internal class SyncStream(
}
}

internal expect fun getOS(): String
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer this to be in a separate file (e.g. utils/OSInfo.kt).


internal data class SyncStreamState(
var targetCheckpoint: Checkpoint?,
var validatedCheckpoint: Checkpoint?,
Expand Down
10 changes: 10 additions & 0 deletions core/src/iosMain/kotlin/com/powersync/sync/SyncStream.ios.kt
Original file line number Diff line number Diff line change
@@ -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"
}
7 changes: 7 additions & 0 deletions core/src/jvmMain/kotlin/com/powersync/sync/SyncStream.jvm.kt
Original file line number Diff line number Diff line change
@@ -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"
}
4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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 = [
Expand Down