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 #[must_use]
26 #[inline]
27 pub fn is_v3(&self) -> bool {
28 matches!(self, SessionResponse::V3(_))
29 }
30
31 #[must_use]
33 #[inline]
34 pub fn is_v2(&self) -> bool {
35 matches!(self, SessionResponse::V2(_))
36 }
37
38 #[must_use]
40 pub fn get_session(&self) -> Session {
41 match self {
42 SessionResponse::V3(v) => Session {
43 account_id: v.account_id.clone(),
44 client_id: v.client_id.clone(),
45 lightstreamer_endpoint: v.lightstreamer_endpoint.clone(),
46 cst: None,
47 x_security_token: None,
48 oauth_token: Some(v.oauth_token.clone()),
49 api_version: 3,
50 expires_at: v.oauth_token.expire_at(1),
51 },
52 SessionResponse::V2(v) => {
53 let (cst, x_security_token) = match v.security_headers.as_ref() {
54 Some(headers) => (
55 Some(headers.cst.clone()),
56 Some(headers.x_security_token.clone()),
57 ),
58 None => (None, None),
59 };
60 let expires_at = (Utc::now().timestamp() + (3600 * 6)) as u64; Session {
62 account_id: v.current_account_id.clone(),
63 client_id: v.client_id.clone(),
64 lightstreamer_endpoint: v.lightstreamer_endpoint.clone(),
65 cst,
66 x_security_token,
67 oauth_token: None,
68 api_version: 2,
69 expires_at,
70 }
71 }
72 }
73 }
74 pub fn get_session_v2(&mut self, headers: &SecurityHeaders) -> Session {
79 match self {
80 SessionResponse::V3(_) => {
81 warn!("Returing V3 session from V2 headers - this may be unexpected");
82 self.get_session()
83 }
84 SessionResponse::V2(v) => {
85 v.set_security_headers(headers);
86 v.expires_in = Some(21600); self.get_session()
88 }
89 }
90 }
91
92 #[must_use]
97 #[inline]
98 pub fn is_expired(&self, margin_seconds: u64) -> bool {
99 match self {
100 SessionResponse::V3(v) => v.oauth_token.is_expired(margin_seconds),
101 SessionResponse::V2(v) => v.is_expired(margin_seconds),
102 }
103 }
104}
105
106#[derive(Debug, Clone, Deserialize, Serialize)]
108#[serde(rename_all = "camelCase")]
109pub struct V3Response {
110 pub client_id: String,
112 pub account_id: String,
114 pub timezone_offset: i32,
116 pub lightstreamer_endpoint: String,
118 pub oauth_token: OAuthToken,
120}
121
122#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
124pub struct OAuthToken {
125 pub access_token: String,
127 pub refresh_token: String,
129 pub scope: String,
131 pub token_type: String,
133 pub expires_in: String,
135 #[serde(skip, default = "chrono::Utc::now")]
137 pub created_at: chrono::DateTime<Utc>,
138}
139
140impl OAuthToken {
141 #[must_use]
149 #[inline]
150 pub fn is_expired(&self, margin_seconds: u64) -> bool {
151 let expires_in_secs = self.expires_in.parse::<i64>().unwrap_or(0);
152 let expiry_time = self.created_at + chrono::Duration::seconds(expires_in_secs);
153 let now = Utc::now();
154 let margin = chrono::Duration::seconds(margin_seconds as i64);
155
156 expiry_time - margin <= now
157 }
158
159 #[must_use]
167 pub fn expire_at(&self, margin_seconds: i64) -> u64 {
168 let expires_in_secs = self.expires_in.parse::<i64>().unwrap_or(0);
169 let expiry_time = self.created_at + chrono::Duration::seconds(expires_in_secs);
170 let margin = chrono::Duration::seconds(margin_seconds);
171
172 let effective_expiry = expiry_time - margin;
174
175 effective_expiry.timestamp() as u64
176 }
177}
178
179#[derive(Debug, Clone, Deserialize, Serialize)]
181#[serde(rename_all = "camelCase")]
182pub struct V2Response {
183 pub account_type: String,
185 pub account_info: AccountInfo,
187 pub currency_iso_code: String,
189 pub currency_symbol: String,
191 pub current_account_id: String,
193 pub lightstreamer_endpoint: String,
195 pub accounts: Vec<Account>,
197 pub client_id: String,
199 pub timezone_offset: i32,
201 pub has_active_demo_accounts: bool,
203 pub has_active_live_accounts: bool,
205 pub trailing_stops_enabled: bool,
207 pub rerouting_environment: Option<String>,
209 pub dealing_enabled: bool,
211 #[serde(skip)]
213 pub security_headers: Option<SecurityHeaders>,
214 #[serde(skip)]
216 pub expires_in: Option<u64>,
217 #[serde(skip, default = "chrono::Utc::now")]
219 pub created_at: chrono::DateTime<Utc>,
220}
221
222impl V2Response {
223 pub fn set_security_headers(&mut self, headers: &SecurityHeaders) {
228 self.security_headers = Some(headers.clone());
229 }
230
231 #[must_use]
239 pub fn is_expired(&self, margin_seconds: u64) -> bool {
240 match self.expires_in {
241 Some(expires_in) => {
242 let expiry_time = self.created_at + chrono::Duration::seconds(expires_in as i64);
243 let now = Utc::now();
244 let margin = chrono::Duration::seconds(margin_seconds as i64);
245 expiry_time - margin <= now
246 }
247 None => true,
249 }
250 }
251}
252
253#[derive(Debug, Clone, Deserialize, Serialize)]
255pub struct SecurityHeaders {
256 pub cst: String,
258 pub x_security_token: String,
260 pub x_ig_api_key: String,
262}
263
264#[derive(Debug, Clone, Deserialize, Serialize)]
266#[serde(rename_all = "camelCase")]
267pub struct AccountInfo {
268 pub balance: f64,
270 pub deposit: f64,
272 pub profit_loss: f64,
274 pub available: f64,
276}
277
278#[derive(Debug, Clone, Deserialize, Serialize)]
280#[serde(rename_all = "camelCase")]
281pub struct Account {
282 pub account_id: String,
284 pub account_name: String,
286 pub preferred: bool,
288 pub account_type: String,
290}