From c888a4fc29a2d8a239224d77989abd25bd61fc71 Mon Sep 17 00:00:00 2001 From: "Garen J. Torikian" Date: Mon, 17 Aug 2026 13:57:24 -0400 Subject: [PATCH] feat(transport): send a WorkOS Android/ User-Agent header Requests previously went out with OkHttp's default UA (okhttp/4.12.0), making Android SDK traffic unattributable in fct_log_events_sdk. The header mirrors the WorkOS iOS/ shape with an end-anchored version so the data platform's regex convention applies unchanged. The version const is stamped by release-please via a new extra-files entry, keeping it in lockstep with build.gradle.kts. --- release-please-config.json | 4 ++++ .../kotlin/com/workos/android/internal/Transport.kt | 11 +++++++++++ .../com/workos/android/TransportBehaviorTest.kt | 13 +++++++++++++ 3 files changed, 28 insertions(+) diff --git a/release-please-config.json b/release-please-config.json index 3f15295..5674d93 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -10,6 +10,10 @@ { "type": "generic", "path": "build.gradle.kts" + }, + { + "type": "generic", + "path": "src/main/kotlin/com/workos/android/internal/Transport.kt" } ] } diff --git a/src/main/kotlin/com/workos/android/internal/Transport.kt b/src/main/kotlin/com/workos/android/internal/Transport.kt index 1562817..091d37c 100644 --- a/src/main/kotlin/com/workos/android/internal/Transport.kt +++ b/src/main/kotlin/com/workos/android/internal/Transport.kt @@ -40,6 +40,16 @@ private const val BACKOFF_MULTIPLIER = 2.0 private const val MAX_DELAY_MS = 8_000L private const val JITTER_FACTOR = 0.5 +// Stamped by release-please on every release (see release-please-config.json); +// must stay on one line with the marker comment or it stops being updated. +private const val VERSION = "0.2.1" // x-release-please-version + +/** + * Mirrors the other WorkOS SDKs' `WorkOS /` shape, which the + * data platform parses with an end-anchored regex — nothing may follow the version. + */ +private const val USER_AGENT = "WorkOS Android/$VERSION" + /** A fully-read HTTP exchange. Read before any error decision so the body is available. */ private class RawResponse( val code: Int, @@ -113,6 +123,7 @@ public class Transport( .method(method, requestBody) .header("Authorization", "Bearer " + configuration.apiKey) .header("Content-Type", "application/json") + .header("User-Agent", USER_AGENT) if (idempotencyKey != null) builder.header("Idempotency-Key", idempotencyKey) val extra = options?.headers if (extra != null) { diff --git a/src/test/kotlin/com/workos/android/TransportBehaviorTest.kt b/src/test/kotlin/com/workos/android/TransportBehaviorTest.kt index 1c9f348..de006a1 100644 --- a/src/test/kotlin/com/workos/android/TransportBehaviorTest.kt +++ b/src/test/kotlin/com/workos/android/TransportBehaviorTest.kt @@ -55,6 +55,19 @@ class TransportBehaviorTest { assertEquals("Bearer sk_test_123", server.awaitRequest().headerValue("Authorization")) } + @Test + fun `sends a WorkOS Android user agent ending in the version`() = + runTest { + val (c, server) = testClient(responding = """{"data":[],"list_metadata":{}}""") + + c.organizations.list() + + val userAgent = server.awaitRequest().headerValue("User-Agent") + assertNotNull(userAgent) + // End-anchored like the data platform's SDK-attribution regexes. + assertTrue(userAgent.matches(Regex("""WorkOS Android/[^\s]+""")), "unexpected User-Agent: $userAgent") + } + @Test fun `retries a 500 and then succeeds`() = runTest {