feat: send a User-Agent on relay WebSocket upgrades (#9246)

Relay WebSocket clients in this repo now send a `User-Agent` of the form
`<name>/<version>` on the upgrade request. The version comes from the
build.

| Client | User-Agent |
|---|---|
| `buzz-acp` | `buzz-acp/<crate version>` |
| `buzz-cli` | `buzz-cli/<crate version>` |
| `buzz-ws-client` (default) | `buzz-ws-client/<crate version>` |
| `buzz-pairing-cli` | `buzz-pairing-cli/<crate version>` |
| `buzz-audio-client` (default) | `buzz-audio-client/<crate version>` |
| mobile (relay and huddle sockets) | `Buzz-Mobile/<app version>` |

- **`buzz-ws-client`**: every connect path sends the default. A
`User-Agent` in the caller's `connect_with_headers` headers replaces it,
so other consumers can identify themselves without any new API.
`buzz-cli` uses this to send its own name.
- **`buzz-audio-client`**: keeps a `User-Agent` already on the
host-supplied request, and adds its default otherwise.
- **Mobile**: `openRelayWebSocket` sets the HTTP client's `userAgent`,
which also covers the huddle transport. Startup waits up to 500 ms for
the app version from `package_info_plus` before the app runs, so the
first socket carries it; if the lookup fails or times out, the value is
`Buzz-Mobile`.

Each client has a test asserting the header on the upgrade request.

---------

Signed-off-by: Will Pfleger <wpfleger@block.xyz>
Co-authored-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
This commit is contained in:
Will Pfleger
2026-11-08 20:43:55 -05:00
committed by GitHub
parent 31fa53174d
commit 65e864301d
12 changed files with 300 additions and 18 deletions

View File

@@ -12,6 +12,8 @@ use crate::error::CliError;
/// NIP-FI assertion header (`docs/nips/NIP-FI.md` §Client-attached transport).
const FEDERATED_IDENTITY_HEADER: &str = "Nostr-Federated-Identity";
/// `User-Agent` sent on every WebSocket upgrade.
const USER_AGENT: &str = concat!("buzz-cli/", env!("CARGO_PKG_VERSION"));
/// Descriptor returned by the relay after a successful upload.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -704,10 +706,14 @@ impl BuzzClient {
})
}
/// The NIP-FI assertion as WebSocket upgrade headers (empty without an
/// evidence source).
/// The WebSocket upgrade headers: the CLI's `User-Agent`, and the NIP-FI
/// assertion when there is an evidence source.
async fn upgrade_headers(&self) -> Result<buzz_ws_client::HeaderMap, CliError> {
let mut headers = buzz_ws_client::HeaderMap::new();
headers.insert(
"user-agent",
buzz_ws_client::HeaderValue::from_static(USER_AGENT),
);
if let Some(assertion) = self.evidence().await? {
let mut value =
buzz_ws_client::HeaderValue::from_str(&format!("Bearer {assertion}"))
@@ -4095,6 +4101,20 @@ mod ws_deadline_tests {
pub(super) type Log = Arc<std::sync::Mutex<Vec<String>>>;
/// Every upgrade names the CLI and its version.
#[tokio::test]
async fn upgrade_headers_carry_the_cli_user_agent() {
let keys = Keys::generate();
let c = BuzzClient::new(
"http://127.0.0.1:1".into(),
Credentials::from_signer(Arc::new(keys.clone()), keys.public_key()),
)
.unwrap();
let headers = c.upgrade_headers().await.unwrap();
assert_eq!(headers.get("user-agent").unwrap(), super::USER_AGENT);
assert!(super::USER_AGENT.starts_with("buzz-cli/"));
}
/// A sign-in signature that comes back after the relay's 5 s deadline
/// reconnects and signs the new challenge, never the old one.
#[tokio::test]