use crate::application::auth::Session;
use chrono::Utc;
use serde::{Deserialize, Serialize};
use tracing::warn;
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum SessionResponse {
V3(V3Response),
V2(V2Response),
}
impl SessionResponse {
#[must_use]
#[inline]
pub fn is_v3(&self) -> bool {
matches!(self, SessionResponse::V3(_))
}
#[must_use]
#[inline]
pub fn is_v2(&self) -> bool {
matches!(self, SessionResponse::V2(_))
}
#[must_use]
pub fn get_session(&self) -> Session {
match self {
SessionResponse::V3(v) => Session {
account_id: v.account_id.clone(),
client_id: v.client_id.clone(),
lightstreamer_endpoint: v.lightstreamer_endpoint.clone(),
cst: None,
x_security_token: None,
oauth_token: Some(v.oauth_token.clone()),
api_version: 3,
expires_at: v.oauth_token.expire_at(1),
},
SessionResponse::V2(v) => {
let (cst, x_security_token) = match v.security_headers.as_ref() {
Some(headers) => (
Some(headers.cst.clone()),
Some(headers.x_security_token.clone()),
),
None => (None, None),
};
let expires_at = (Utc::now().timestamp() + (3600 * 6)) as u64; Session {
account_id: v.current_account_id.clone(),
client_id: v.client_id.clone(),
lightstreamer_endpoint: v.lightstreamer_endpoint.clone(),
cst,
x_security_token,
oauth_token: None,
api_version: 2,
expires_at,
}
}
}
}
pub fn get_session_v2(&mut self, headers: &SecurityHeaders) -> Session {
match self {
SessionResponse::V3(_) => {
warn!("Returing V3 session from V2 headers - this may be unexpected");
self.get_session()
}
SessionResponse::V2(v) => {
v.set_security_headers(headers);
v.expires_in = Some(21600); self.get_session()
}
}
}
#[must_use]
#[inline]
pub fn is_expired(&self, margin_seconds: u64) -> bool {
match self {
SessionResponse::V3(v) => v.oauth_token.is_expired(margin_seconds),
SessionResponse::V2(v) => v.is_expired(margin_seconds),
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct V3Response {
pub client_id: String,
pub account_id: String,
pub timezone_offset: i32,
pub lightstreamer_endpoint: String,
pub oauth_token: OAuthToken,
}
#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
pub struct OAuthToken {
pub access_token: String,
pub refresh_token: String,
pub scope: String,
pub token_type: String,
pub expires_in: String,
#[serde(skip, default = "chrono::Utc::now")]
pub created_at: chrono::DateTime<Utc>,
}
impl OAuthToken {
#[must_use]
#[inline]
pub fn is_expired(&self, margin_seconds: u64) -> bool {
let expires_in_secs = self.expires_in.parse::<i64>().unwrap_or(0);
let expiry_time = self.created_at + chrono::Duration::seconds(expires_in_secs);
let now = Utc::now();
let margin = chrono::Duration::seconds(margin_seconds as i64);
expiry_time - margin <= now
}
#[must_use]
pub fn expire_at(&self, margin_seconds: i64) -> u64 {
let expires_in_secs = self.expires_in.parse::<i64>().unwrap_or(0);
let expiry_time = self.created_at + chrono::Duration::seconds(expires_in_secs);
let margin = chrono::Duration::seconds(margin_seconds);
let effective_expiry = expiry_time - margin;
effective_expiry.timestamp() as u64
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct V2Response {
pub account_type: String,
pub account_info: AccountInfo,
pub currency_iso_code: String,
pub currency_symbol: String,
pub current_account_id: String,
pub lightstreamer_endpoint: String,
pub accounts: Vec<Account>,
pub client_id: String,
pub timezone_offset: i32,
pub has_active_demo_accounts: bool,
pub has_active_live_accounts: bool,
pub trailing_stops_enabled: bool,
pub rerouting_environment: Option<String>,
pub dealing_enabled: bool,
#[serde(skip)]
pub security_headers: Option<SecurityHeaders>,
#[serde(skip)]
pub expires_in: Option<u64>,
#[serde(skip, default = "chrono::Utc::now")]
pub created_at: chrono::DateTime<Utc>,
}
impl V2Response {
pub fn set_security_headers(&mut self, headers: &SecurityHeaders) {
self.security_headers = Some(headers.clone());
}
#[must_use]
pub fn is_expired(&self, margin_seconds: u64) -> bool {
match self.expires_in {
Some(expires_in) => {
let expiry_time = self.created_at + chrono::Duration::seconds(expires_in as i64);
let now = Utc::now();
let margin = chrono::Duration::seconds(margin_seconds as i64);
expiry_time - margin <= now
}
None => true,
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SecurityHeaders {
pub cst: String,
pub x_security_token: String,
pub x_ig_api_key: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AccountInfo {
pub balance: f64,
pub deposit: f64,
pub profit_loss: f64,
pub available: f64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Account {
pub account_id: String,
pub account_name: String,
pub preferred: bool,
pub account_type: String,
}