diff --git a/walletkit-core/src/user_agent.rs b/walletkit-core/src/user_agent.rs index ba67979d..46a2db50 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); @@ -12,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 @@ -41,6 +55,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 +78,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 +97,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,7 +138,57 @@ 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); } + + #[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" + ) + ); + } }