Skip to main content

trading_ig/session/
mod.rs

1//! Session lifecycle: login (v2/v3), refresh, switch account, logout.
2//!
3//! This module owns the *state* that the HTTP transport reads on every
4//! request: the active auth tokens and the bound account. Domain modules
5//! never touch session state directly.
6
7use std::sync::Arc;
8
9use serde::{Deserialize, Serialize};
10use tokio::sync::RwLock;
11
12use crate::client::http::Transport;
13use crate::error::{Error, Result};
14
15mod auth;
16#[cfg(feature = "encryption")]
17pub mod encryption;
18mod tokens;
19
20pub use auth::{SessionApi, SessionDetails, SwitchAccountResponse};
21pub use tokens::{AuthTokens, SessionState};
22
23/// User-supplied login credentials.
24#[derive(Debug, Clone)]
25pub enum Credentials {
26    /// Plain username + password (v2/v3 login).
27    Password { username: String, password: String },
28}
29
30impl Credentials {
31    pub fn password(username: impl Into<String>, password: impl Into<String>) -> Self {
32        Self::Password {
33            username: username.into(),
34            password: password.into(),
35        }
36    }
37}
38
39/// Subset of the `POST /session` response surfaced to callers.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct SessionInfo {
43    pub account_id: String,
44    pub client_id: String,
45    pub timezone_offset: Option<i32>,
46    pub lightstreamer_endpoint: String,
47    pub currency_iso_code: Option<String>,
48    pub locale: Option<String>,
49}
50
51/// Shared, mutable session state. Cheap to clone (Arc).
52#[derive(Debug, Clone, Default)]
53pub struct SharedSession {
54    inner: Arc<RwLock<SessionState>>,
55}
56
57impl SharedSession {
58    pub async fn snapshot(&self) -> SessionState {
59        self.inner.read().await.clone()
60    }
61
62    pub(crate) async fn replace(&self, new: SessionState) {
63        *self.inner.write().await = new;
64    }
65
66    pub(crate) async fn modify<F>(&self, f: F)
67    where
68        F: FnOnce(&mut SessionState),
69    {
70        let mut guard = self.inner.write().await;
71        f(&mut guard);
72    }
73
74    pub async fn require_authenticated(&self) -> Result<SessionState> {
75        let s = self.snapshot().await;
76        if s.tokens.is_some() {
77            Ok(s)
78        } else {
79            Err(Error::Auth(
80                "no active session — call session().login() first".into(),
81            ))
82        }
83    }
84}
85
86/// Internal handle: a `Transport` plus a `SharedSession`. Used by [`SessionApi`].
87#[derive(Debug, Clone)]
88pub(crate) struct SessionHandle {
89    pub(crate) transport: Transport,
90    pub(crate) session: SharedSession,
91    pub(crate) credentials: Option<Credentials>,
92}
93
94impl SessionHandle {
95    /// Wrap this handle in a [`SessionApi`] so callers can invoke session
96    /// operations (e.g. `login_v2`) without going through [`IgClient`].
97    #[cfg(feature = "stream")]
98    pub(crate) fn session_api(&self) -> SessionApi {
99        SessionApi {
100            handle: self.clone(),
101        }
102    }
103}