Skip to main content

m4a_agent/backend/
server.rs

1//! Tier 2: our server through its own product API (no Matrix).
2
3use std::sync::Arc;
4
5use zeroize::Zeroizing;
6
7use super::wire::{HttpWire, Wire};
8use super::{Backend, BackendKind, Capabilities, Session};
9use crate::error::{AgentError, Result};
10use crate::identity::{IdentityStore, SessionIdentity};
11use crate::keyauth;
12
13pub struct ServerBackend {
14    base: String,
15    wire: Arc<dyn Wire>,
16    #[cfg(feature = "engine")]
17    live: super::live::Live,
18}
19
20impl ServerBackend {
21    pub fn new(base_url: &str) -> Result<Self> {
22        Self::with_wire(base_url, Box::new(HttpWire::new(base_url)?))
23    }
24
25    /// Over any transport (tests).
26    pub fn with_wire(base_url: &str, wire: Box<dyn Wire>) -> Result<Self> {
27        Ok(Self {
28            base: base_url.trim_end_matches('/').to_string(),
29            wire: Arc::from(wire),
30            #[cfg(feature = "engine")]
31            live: super::live::Live::new(crate::engine::HttpExec::new(base_url)?),
32        })
33    }
34
35    fn established(&self, token: &str) {
36        #[cfg(feature = "engine")]
37        {
38            self.live.set_token(token);
39            // The product's own proxy serves the Matrix paths without the spec prefix.
40            self.live.with_exec(|e| e.set_keep_prefix(false));
41        }
42        let _ = token;
43    }
44}
45
46impl Backend for ServerBackend {
47    fn kind(&self) -> BackendKind {
48        BackendKind::Server
49    }
50    fn capabilities(&self) -> Capabilities {
51        Capabilities { rooms: false, federation: false }
52    }
53    fn server_ref(&self) -> &str {
54        &self.base
55    }
56
57    fn ensure_session(&self, ids: &IdentityStore, id: &mut SessionIdentity, invite: Option<&str>) -> Result<Session> {
58        if id.tier != BackendKind::Server || id.server_ref != self.base {
59            return Err(AgentError::Identity("identity belongs to another tier or server".into()));
60        }
61        if let Some(t) = keyauth::stored_token(ids, id)? {
62            let (st, v) = self.wire.get("/product/v1/me", Some(&t))?;
63            if st == 200 {
64                let nick = v.get("nick").and_then(|n| n.as_str()).unwrap_or_default().to_string();
65                self.established(&t);
66                return Ok(Session { token: Zeroizing::new(t), nick, cred_ref: id.key_id.clone(), user_id: None, device_id: None });
67            }
68        }
69        let (nick, token) = if id.enrolled {
70            keyauth::login(&*self.wire, ids, id)?
71        } else {
72            keyauth::enroll(&*self.wire, ids, id, invite.ok_or(AgentError::NeedsInvite)?)?
73        };
74        self.established(&token);
75        Ok(Session { token: Zeroizing::new(token), nick, cred_ref: id.key_id.clone(), user_id: None, device_id: None })
76    }
77
78    #[cfg(feature = "engine")]
79    fn execute(&self, request: &mail4agent_messenger::OutgoingRequest) -> Result<mail4agent_messenger::HttpResponseDescriptor> {
80        self.live.execute(request)
81    }
82
83    #[cfg(feature = "engine")]
84    fn wait_for_news(&self, timeout: std::time::Duration) -> Result<crate::engine::News> {
85        self.live.wait_for_news(timeout)
86    }
87
88    #[cfg(feature = "engine")]
89    fn whoami(&self) -> Result<(String, String)> {
90        self.live.whoami()
91    }
92
93    #[cfg(feature = "engine")]
94    fn keep_prefix(&self) -> bool {
95        self.live.keep_prefix()
96    }
97}