1use crate::application::auth::Session;
7use chrono::Utc;
8use serde::{Deserialize, Serialize};
9use tracing::warn;
10
11#[derive(Debug, Clone, Deserialize, Serialize)]
15#[serde(untagged)]
16pub enum SessionResponse {
17 V3(V3Response),
19 V2(V2Response),
21}
22
23impl SessionResponse {
24 pub fn is_v3(&self) -> bool {
26 matches!(self, SessionResponse::V3(_))
27 }
28
29 pub fn is_v2(&self) -> bool {
31 matches!(self, SessionResponse::V2(_))
32 }
33
34 pub fn get_session(&self) -> Session {
36 match self {
37 SessionResponse::V3(v) => Session {
38 account_id: v.account_id.clone(),
39 client_id: v.client_id.clone(),
40 lightstreamer_endpoint: v.lightstreamer_endpoint.clone(),
41 cst: None,
42 x_security_token: None,
43 oauth_token: Some(v.oauth_token.clone()),
44 api_version: 3,
45 expires_at: v.oauth_token.expire_at(1),
46 },
47 SessionResponse::V2(v) => {
48 let (cst, x_security_token) = match v.security_headers.as_ref() {
49 Some(headers) => (
50 Some(headers.cst.clone()),
51 Some(headers.x_security_token.clone()),
52 ),
53 None => (None, None),
54 };
55 let expires_at = (Utc::now().timestamp() + (3600 * 6)) as u64; Session {
57 account_id: v.current_account_id.clone(),
58 client_id: v.client_id.clone(),
59 lightstreamer_endpoint: v.lightstreamer_endpoint.clone(),
60 cst,
61 x_security_token,
62 oauth_token: None,
63 api_version: 2,
64 expires_at,
65 }
66 }
67 }
68 }
69 pub fn get_session_v2(&mut self, headers: &SecurityHeaders) -> Session {
74 match self {
75 SessionResponse::V3(_) => {
76 warn!("Returing V3 session from V2 headers - this may be unexpected");
77 self.get_session()
78 }
79 SessionResponse::V2(v) => {
80 v.set_security_headers(headers);
81 v.expires_in = Some(21600); self.get_session()
83 }
84 }
85 }
86
87 pub fn is_expired(&self, margin_seconds: u64) -> bool {
92 match self {
93 SessionResponse::V3(v) => v.oauth_token.is_expired(margin_seconds),
94 SessionResponse::V2(v) => v.is_expired(margin_seconds),
95 }
96 }
97}
98
99#[derive(Debug, Clone, Deserialize, Serialize)]
101#[serde(rename_all = "camelCase")]
102pub struct V3Response {
103 pub client_id: String,
105 pub account_id: String,
107 pub timezone_offset: i32,
109 pub lightstreamer_endpoint: String,
111 pub oauth_token: OAuthToken,
113}
114
115#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
117pub struct OAuthToken {
118 pub access_token: String,
120 pub refresh_token: String,
122 pub scope: String,
124 pub token_type: String,
126 pub expires_in: String,
128 #[serde(skip, default = "chrono::Utc::now")]
130 pub created_at: chrono::DateTime<Utc>,
131}
132
133impl OAuthToken {
134 pub fn is_expired(&self, margin_seconds: u64) -> bool {
142 let expires_in_secs = self.expires_in.parse::<i64>().unwrap_or(0);
143 let expiry_time = self.created_at + chrono::Duration::seconds(expires_in_secs);
144 let now = Utc::now();
145 let margin = chrono::Duration::seconds(margin_seconds as i64);
146
147 expiry_time - margin <= now
148 }
149
150 pub fn expire_at(&self, margin_seconds: i64) -> u64 {
158 let expires_in_secs = self.expires_in.parse::<i64>().unwrap_or(0);
159 let expiry_time = self.created_at + chrono::Duration::seconds(expires_in_secs);
160 let margin = chrono::Duration::seconds(margin_seconds);
161
162 let effective_expiry = expiry_time - margin;
164
165 effective_expiry.timestamp() as u64
166 }
167}
168
169#[derive(Debug, Clone, Deserialize, Serialize)]
171#[serde(rename_all = "camelCase")]
172pub struct V2Response {
173 pub account_type: String,
175 pub account_info: AccountInfo,
177 pub currency_iso_code: String,
179 pub currency_symbol: String,
181 pub current_account_id: String,
183 pub lightstreamer_endpoint: String,
185 pub accounts: Vec<Account>,
187 pub client_id: String,
189 pub timezone_offset: i32,
191 pub has_active_demo_accounts: bool,
193 pub has_active_live_accounts: bool,
195 pub trailing_stops_enabled: bool,
197 pub rerouting_environment: Option<String>,
199 pub dealing_enabled: bool,
201 #[serde(skip)]
203 pub security_headers: Option<SecurityHeaders>,
204 #[serde(skip)]
206 pub expires_in: Option<u64>,
207 #[serde(skip, default = "chrono::Utc::now")]
209 pub created_at: chrono::DateTime<Utc>,
210}
211
212impl V2Response {
213 pub fn set_security_headers(&mut self, headers: &SecurityHeaders) {
218 self.security_headers = Some(headers.clone());
219 }
220
221 pub fn is_expired(&self, margin_seconds: u64) -> bool {
226 if let Some(expires_in) = self.expires_in {
227 let expiry_time = self.created_at + chrono::Duration::seconds(expires_in as i64);
228 let now = Utc::now();
229 let margin = chrono::Duration::seconds(margin_seconds as i64);
230
231 expiry_time - margin <= now
232 } else {
233 panic!("expires_in not set in V2Response");
234 }
235 }
236}
237
238#[derive(Debug, Clone, Deserialize, Serialize)]
240pub struct SecurityHeaders {
241 pub cst: String,
243 pub x_security_token: String,
245 pub x_ig_api_key: String,
247}
248
249#[derive(Debug, Clone, Deserialize, Serialize)]
251#[serde(rename_all = "camelCase")]
252pub struct AccountInfo {
253 pub balance: f64,
255 pub deposit: f64,
257 pub profit_loss: f64,
259 pub available: f64,
261}
262
263#[derive(Debug, Clone, Deserialize, Serialize)]
265#[serde(rename_all = "camelCase")]
266pub struct Account {
267 pub account_id: String,
269 pub account_name: String,
271 pub preferred: bool,
273 pub account_type: String,
275}