From d487379437aae8d29d3073f7a799bef1063336a4 Mon Sep 17 00:00:00 2001 From: Pablo Guardiola Date: Fri, 29 May 2026 22:22:36 -0400 Subject: [PATCH 1/2] [POP-3811] support World ID walletkit user agents --- walletkit-core/src/user_agent.rs | 65 ++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/walletkit-core/src/user_agent.rs b/walletkit-core/src/user_agent.rs index ba67979d..4100e36c 100644 --- a/walletkit-core/src/user_agent.rs +++ b/walletkit-core/src/user_agent.rs @@ -1,5 +1,10 @@ //! User agent for HTTP requests. +const WORLD_APP_USER_AGENT_PRODUCT: &str = "WorldApp"; +const WORLD_ID_APP_USER_AGENT_PRODUCT: &str = "WorldID"; +const WORLD_ID_ANDROID_CLIENT_NAME: &str = "android-id"; +const WORLD_ID_IOS_CLIENT_NAME: &str = "ios-id"; + /// Represents a User-Agent string. #[derive(Debug, Clone, uniffi::Object)] pub struct UserAgent(pub String); @@ -41,6 +46,19 @@ impl UserAgentBuilder { next } + /// Appends the app product segment for the client name. + /// + /// Uses `WorldID/{app_version}` for World ID app clients (`android-id` / `ios-id`), + /// and `WorldApp/{app_version}` for all other clients. + #[must_use] + pub fn with_app_segment_for_client( + &self, + app_version: &str, + client_name: &str, + ) -> Self { + self.with_segment(user_agent_product_for_client(client_name), app_version) + } + /// Appends `walletkit-core/{crate version}`. #[must_use] pub fn with_walletkit_segment(&self) -> Self { @@ -51,6 +69,12 @@ impl UserAgentBuilder { next } + /// Appends `{client_name}/{os_version}` to match the app client suffix convention. + #[must_use] + pub fn with_client_segment(&self, client_name: &str, os_version: &str) -> Self { + self.with_segment(client_name, os_version) + } + /// Finalizes the header value as [`UserAgent`]. #[must_use] pub fn build(&self) -> UserAgent { @@ -64,6 +88,15 @@ impl Default for UserAgentBuilder { } } +fn user_agent_product_for_client(client_name: &str) -> &'static str { + match client_name { + WORLD_ID_ANDROID_CLIENT_NAME | WORLD_ID_IOS_CLIENT_NAME => { + WORLD_ID_APP_USER_AGENT_PRODUCT + } + _ => WORLD_APP_USER_AGENT_PRODUCT, + } +} + #[cfg(test)] mod tests { use super::*; @@ -96,6 +129,38 @@ mod tests { concat!("walletkit-core/", env!("CARGO_PKG_VERSION"), " CLI/1.2.3"); "walletkit_then_cli" )] + #[test_case( + &UserAgentBuilder::new() + .with_app_segment_for_client("4.0.2500", "android") + .with_walletkit_segment() + .with_client_segment("android", "15"), + concat!("WorldApp/4.0.2500 walletkit-core/", env!("CARGO_PKG_VERSION"), " android/15"); + "world_app_android_client" + )] + #[test_case( + &UserAgentBuilder::new() + .with_app_segment_for_client("4.0.2500", "ios") + .with_walletkit_segment() + .with_client_segment("ios", "26.4.2"), + concat!("WorldApp/4.0.2500 walletkit-core/", env!("CARGO_PKG_VERSION"), " ios/26.4.2"); + "world_app_ios_client" + )] + #[test_case( + &UserAgentBuilder::new() + .with_app_segment_for_client("1.0.100", "android-id") + .with_walletkit_segment() + .with_client_segment("android-id", "15"), + concat!("WorldID/1.0.100 walletkit-core/", env!("CARGO_PKG_VERSION"), " android-id/15"); + "world_id_android_client" + )] + #[test_case( + &UserAgentBuilder::new() + .with_app_segment_for_client("1.0.100", "ios-id") + .with_walletkit_segment() + .with_client_segment("ios-id", "26.4.2"), + concat!("WorldID/1.0.100 walletkit-core/", env!("CARGO_PKG_VERSION"), " ios-id/26.4.2"); + "world_id_ios_client" + )] fn user_agent_builder_expected(builder: &UserAgentBuilder, expected: &'static str) { assert_eq!(builder.build().to_string(), expected); } From 0f59b1514e3a7d4e9fc69ff62e23aa6185dfc98e Mon Sep 17 00:00:00 2001 From: Pablo Guardiola Date: Sat, 30 May 2026 13:07:28 -0400 Subject: [PATCH 2/2] [POP-3811] expose walletkit user agent header value --- walletkit-core/src/user_agent.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/walletkit-core/src/user_agent.rs b/walletkit-core/src/user_agent.rs index 4100e36c..46a2db50 100644 --- a/walletkit-core/src/user_agent.rs +++ b/walletkit-core/src/user_agent.rs @@ -17,6 +17,15 @@ impl std::fmt::Display for UserAgent { } } +#[uniffi::export] +impl UserAgent { + /// Returns the header value for FFI consumers. + #[must_use] + pub fn header_value(&self) -> String { + self.0.clone() + } +} + /// Builds the [`UserAgent`] string sent as the HTTP `User-Agent` header. /// /// Starts empty; call [`Self::with_segment`] for arbitrary `name/version` tokens and @@ -164,4 +173,22 @@ mod tests { fn user_agent_builder_expected(builder: &UserAgentBuilder, expected: &'static str) { assert_eq!(builder.build().to_string(), expected); } + + #[test] + fn user_agent_exposes_header_value_for_ffi_consumers() { + let user_agent = UserAgentBuilder::new() + .with_app_segment_for_client("1.0.100", "android-id") + .with_walletkit_segment() + .with_client_segment("android-id", "15") + .build(); + + assert_eq!( + user_agent.header_value(), + concat!( + "WorldID/1.0.100 walletkit-core/", + env!("CARGO_PKG_VERSION"), + " android-id/15" + ) + ); + } }