use anyhow::{bail, Context, Result};
use serde_json::{json, Value};
pub struct CreatedSession {
pub id: String,
pub metadata_version: u64,
}
pub async fn create_session(
hub_url: &str,
token: &str,
machine_id: &str,
session_id: Option<&str>,
session_name: &str,
cwd: &str,
hostname: &str,
app_version: &str,
) -> Result<CreatedSession> {
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(30))
.build()?;
let tag = session_id
.map(|s| s.to_string())
.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
let mut body = json!({
"tag": tag,
"metadata": {
"path": cwd,
"host": hostname,
"machineId": machine_id,
"flavor": "pi",
"version": app_version,
"os": std::env::consts::OS,
"name": session_name,
"capabilities": { "terminal": false },
"startedBy": "terminal",
"lifecycleState": "running"
},
"machine": {
"id": machine_id,
"metadata": {
"host": hostname,
"os": std::env::consts::OS,
"version": app_version,
"machineId": machine_id
}
}
});
if let Some(sid) = session_id {
body["id"] = json!(sid);
}
let url = format!("{}/cli/sessions", hub_url.trim_end_matches('/'));
log::debug!("POST {url} body={body}");
let resp = client
.post(&url)
.header("Authorization", format!("Bearer {token}"))
.header("Content-Type", "application/json")
.header("X-Xagent-Protocol-Version", "1")
.json(&body)
.send()
.await
.context("POST /cli/sessions")?;
let status = resp.status();
let text = resp.text().await.unwrap_or_default();
if !status.is_success() {
bail!("POST /cli/sessions failed {status}: {}", truncate(&text, 500));
}
let v: Value = serde_json::from_str(&text)
.with_context(|| format!("parsing /cli/sessions response: {}", truncate(&text, 500)))?;
let session = v
.get("session")
.ok_or_else(|| anyhow::anyhow!("missing session in response: {}", truncate(&text, 300)))?;
let returned_id = session
.get("id")
.and_then(|v| v.as_str())
.map(|s| s.to_string())
.ok_or_else(|| anyhow::anyhow!("missing session.id"))?;
let metadata_version = session
.get("metadataVersion")
.and_then(|v| v.as_u64())
.unwrap_or(0);
Ok(CreatedSession {
id: returned_id,
metadata_version,
})
}
fn truncate(s: &str, n: usize) -> String {
if s.len() <= n {
s.to_string()
} else {
format!("{}…", &s[..n])
}
}