diff --git a/CHANGELOG.md b/CHANGELOG.md index dbcce066b7..15d3ef8507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ * Added `eval-jexl` command to nimbus-cli for evaluating JEXL targeting expressions against the app context. Useful for testing and debugging targeting expressions on iOS and Android. ([#7160](https://github.com/mozilla/application-services/pull/7160)) +### Relay + +* Added `X-Relay-Client` header to all Relay API requests with automatic platform detection (`appservices-ios`, `appservices-android`, etc.) to help the backend distinguish mobile vs desktop requests for telemetry. + [Full Changelog](In progress) # v148.0 (_2026-01-12_) diff --git a/components/relay/src/lib.rs b/components/relay/src/lib.rs index d374332710..314fedafee 100644 --- a/components/relay/src/lib.rs +++ b/components/relay/src/lib.rs @@ -124,6 +124,7 @@ impl RelayClient { if let Some(ref token) = self.auth_token { request = request.header(header_names::AUTHORIZATION, format!("Bearer {}", token))?; } + request = request.header("X-Relay-Client", format!("appservices-{}", std::env::consts::OS))?; Ok(request) } } @@ -132,6 +133,10 @@ impl RelayClient { impl RelayClient { /// Creates a new `RelayClient` instance. /// + /// The client automatically includes an `X-Relay-Client` header with the platform + /// identifier based on the target OS (e.g., "appservices-ios", "appservices-android", + /// "appservices-macos", etc.) to help the Relay backend distinguish mobile vs desktop requests. + /// /// # Parameters /// - `server_url`: Base URL for the Relay API. /// - `auth_token`: Optional relay-scoped access token (see struct docs). @@ -621,6 +626,50 @@ mod tests { assert!(address.enabled); } + #[test] + fn test_create_address_with_platform_header() { + viaduct_dev::init_backend_dev(); + + let expected_platform = format!("appservices-{}", std::env::consts::OS); + + let address_json = r#"{ + "mask_type": "alias", + "enabled": true, + "description": "Test", + "generated_for": "example.com", + "block_list_emails": false, + "used_on": "example.com", + "id": 1, + "address": "test123", + "domain": 2, + "full_address": "test123@mozmail.com", + "created_at": "2021-01-01T00:00:00Z", + "last_modified_at": "2021-01-01T00:00:00Z", + "last_used_at": null, + "num_forwarded": 0, + "num_blocked": 0, + "num_level_one_trackers_blocked": 0, + "num_replied": 0, + "num_spam": 0 + }"#; + + let _mock = mock("POST", "/api/v1/relayaddresses/") + .match_header("authorization", "Bearer mock_token") + .match_header("x-relay-client", expected_platform.as_str()) + .with_status(201) + .with_body(address_json) + .create(); + + let client = RelayClient::new(mockito::server_url(), Some("mock_token".to_string())); + + let address = client + .expect("success") + .create_address("Test", "example.com", "example.com") + .expect("should create address with platform header"); + + assert_eq!(address.full_address, "test123@mozmail.com"); + } + fn mock_profile_json( id: i64, has_premium: bool,