Skip to content
Open
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# Unreleased

* [FIX] RUM: send the running SDK's name in the remote configuration request instead of the literal `android`. A cross-platform wrapper sets the source to its own name (`react-native`, `flutter`, ...) and the native SDK already stamps that value on every event it sends; the configuration request was the one place that disagreed, so a configuration rule targeting the wrapper never matched the app running it. The same request already reported the wrapper's SDK version, which made a single request describe itself as two different SDKs. A native app is unaffected — the source falls back to `android` when no wrapper sets it. **Rollout note:** an app embedding a wrapper now sends the wrapper's name, so a rule written against `sdk=android` to reach such an app stops matching and has to be retargeted.

---

# 0.7.0 / 2026-09-09

* [FEATURE] RUM: take the session sample rate from the application's settings in the Flashcat console instead of only from the value passed to `RumConfiguration.Builder.setSessionSampleRate()`, so it can be changed without shipping a new release of the app. Off by default — enable it with `RumConfiguration.Builder.setRemoteConfigurationEnabled(true)`. Left off, the SDK makes no extra request, opens no extra storage, starts no extra thread, and behaves exactly as it did before. A change applies to sessions started after it arrives, so a session already under way is normally never redrawn. Two changes are exceptions and end the running session so the next one starts under the new rate: one the console marks for immediate activation, and a rate crossing zero in either direction — switching collection off is an emergency stop, and switching it back on has nothing to preserve, since while the rate was zero no session was being collected and none was drawn against a real rate. The values passed to init keep applying until the first settings arrive and whenever the endpoint cannot be reached — a failed, timed-out or unreadable response never moves a rate. Events report the rate the session was actually drawn under, together with the settings version it came from, so server-side extrapolation and audits line up with the draw.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -804,7 +804,8 @@ internal class RumFeature(
clientToken = context.clientToken,
env = context.env,
appVersion = context.version,
sdkVersion = context.sdkVersion
sdkVersion = context.sdkVersion,
source = context.source
),
store = store,
initialSessionSampleRate = sampleRate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -527,17 +527,24 @@ internal class RemoteConfigController(
*
* The SDK version rides along purely as information: it keys nothing on this side (see the
* store key), and the server may one day target a configuration at a range of them.
*
* `sdk` carries the source, not the literal "android": a cross-platform wrapper sets that
* field to its own name (react-native, flutter, ...) and only falls back to "android" for a
* native app, so a rule targeting the wrapper matches the runtime the app actually is. It
* also keeps the pair honest - the SDK version already reports the wrapper's version, so a
* literal here would describe one request as two different SDKs.
*/
fun buildConfigUrl(
intakeUrl: String,
clientToken: String,
env: String,
appVersion: String,
sdkVersion: String
sdkVersion: String,
source: String
): String {
val parameters = buildString {
append("?client_token=").append(encode(clientToken))
append("&sdk=android")
append("&sdk=").append(encode(source))
if (env.isNotEmpty()) append("&env=").append(encode(env))
if (appVersion.isNotEmpty()) append("&app_version=").append(encode(appVersion))
if (sdkVersion.isNotEmpty()) append("&sdk_version=").append(encode(sdkVersion))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,8 @@ internal class RemoteConfigControllerTest {
clientToken = "token",
env = "staging",
appVersion = "1.2.3",
sdkVersion = "2.26.0"
sdkVersion = "2.26.0",
source = "android"
)

assertThat(url).startsWith("https://rum.example.com/api/v2/rum/config?")
Expand All @@ -666,14 +667,35 @@ internal class RemoteConfigControllerTest {
clientToken = "token",
env = "",
appVersion = "",
sdkVersion = ""
sdkVersion = "",
source = "android"
)

assertThat(url).doesNotContain("env=")
assertThat(url).doesNotContain("app_version=")
assertThat(url).doesNotContain("sdk_version=")
}

@Test
fun `M report the wrapper rather than the native SDK W buildConfigUrl()`() {
// A cross-platform wrapper sets the source to its own name, and the native SDK carries it
// on every event it sends. The configuration request has to agree, or a rule targeting the
// wrapper never matches the app that is actually running it.
val url = RemoteConfigController.buildConfigUrl(
intakeUrl = "https://rum.example.com/api/v2/rum",
clientToken = "token",
env = "staging",
appVersion = "1.2.3",
sdkVersion = "9.9.9",
source = "react-native"
)

assertThat(url).contains("sdk=react-native")
assertThat(url).doesNotContain("sdk=android")
// The version already describes the wrapper, so the name has to describe it too.
assertThat(url).contains("sdk_version=9.9.9")
}

@Test
fun `M store the custom bag verbatim W apply()`() {
testedController.apply(body(custom = """{"viplist":["u-1","u-2"],"debug":true}"""))
Expand Down
Loading