Skip to content
Merged
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
5 changes: 3 additions & 2 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
use std::io::Read;
use std::time::Duration;

use reqwest::blocking::Client;
use serde::Deserialize;
use thiserror::Error;

use crate::http;

const MAX_RESPONSE_BYTES: u64 = 64 * 1024;

#[derive(Debug, Deserialize)]
Expand Down Expand Up @@ -37,7 +38,7 @@ pub enum ApiError {
/// GET /v1/auth/me — returns the authenticated user's identity.
pub fn me(api_base: &str, access_token: &str) -> Result<Identity, ApiError> {
let endpoint = format!("{}/v1/auth/me", api_base.trim_end_matches('/'));
let http = Client::builder().timeout(Duration::from_secs(15)).build()?;
let http = http::client(Duration::from_secs(15))?;

let resp = http
.get(&endpoint)
Expand Down
7 changes: 2 additions & 5 deletions src/commands/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ use std::time::{Duration, SystemTime};

use anyhow::{Context, Result, anyhow};
use clap::Args;
use reqwest::blocking::Client;

use crate::auth::{self, AuthError};
use crate::browser;
use crate::credentials::{self, Credentials};
use crate::http;

use super::{DEFAULT_API_BASE, DEFAULT_WEB_BASE, ENV_API_URL, ENV_WEB_URL};

Expand Down Expand Up @@ -44,10 +44,7 @@ pub fn run(_args: LoginArgs) -> Result<()> {
return Err(anyhow!("login: no code entered"));
}

let http = Client::builder()
.timeout(Duration::from_secs(30))
.build()
.context("login: build HTTP client")?;
let http = http::client(Duration::from_secs(30)).context("login: build HTTP client")?;

let tokens = match auth::exchange_code(&http, &api_base, code, &pkce) {
Ok(t) => t,
Expand Down
45 changes: 45 additions & 0 deletions src/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! Shared reqwest Client builder. Centralizes the User-Agent string so
//! every outbound request labels itself as the CLI — the platform's
//! session list (and any audit log) can then render "MirrorStack CLI"
//! instead of falling through to a generic browser-UA parser.

use std::time::Duration;

use reqwest::blocking::{Client, ClientBuilder};

/// User-Agent shape: `mirrorstack-cli/<version> (<os>; <arch>)`.
/// Mirrors the convention of curl, gh CLI, brew, etc.
fn user_agent() -> String {
format!(
"mirrorstack-cli/{} ({}; {})",
env!("CARGO_PKG_VERSION"),
std::env::consts::OS,
std::env::consts::ARCH,
)
}

/// Construct a reqwest blocking Client with the CLI's User-Agent and a
/// caller-supplied timeout. Use this everywhere we make HTTP calls so
/// the UA stays consistent.
pub fn client(timeout: Duration) -> reqwest::Result<Client> {
builder(timeout).build()
}

/// Same as [`client`] but returns the builder so callers can layer on
/// additional config before `.build()`. Currently unused — exposed so
/// the seam is obvious for future commands.
pub fn builder(timeout: Duration) -> ClientBuilder {
Client::builder().timeout(timeout).user_agent(user_agent())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn user_agent_matches_expected_shape() {
let ua = user_agent();
assert!(ua.starts_with("mirrorstack-cli/"), "got {ua}");
assert!(ua.contains('('), "expected (os; arch) in {ua}");
}
}
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod auth;
mod browser;
mod commands;
mod credentials;
mod http;

fn main() -> ExitCode {
// Load .env from CWD if present. Process env vars still take precedence
Expand Down
Loading