use std::collections::HashMap;
use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use serde::{Deserialize, Serialize};
use crate::enums::platform::Platform;
use crate::errors::response_error::RequestError;
use crate::response_types::version::{RadiantVersion, ValorantAPIData, Version};
const ENTITLEMENTS_HEADER_KEY: &str = "X-Riot-Entitlements-JWT";
const CLIENT_VERSION_HEADER_KEY: &str = "X-Riot-ClientVersion";
const CLIENT_PLATFORM_HEADER_KEY: &str = "X-Riot-ClientPlatform";
const AUTHORIZATION_HEADER_KEY: &str = "Authorization";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CredentialsManager {
pub platform: Platform,
pub access_token: String,
pub entitlements_token: String,
pub client_version: String,
}
impl CredentialsManager {
pub async fn new_with_tokens(
platform: Platform,
access_token: String,
entitlements_token: String,
) -> reqwest::Result<CredentialsManager> {
Ok(CredentialsManager {
platform,
access_token,
entitlements_token,
client_version: Self::get_client_version().await?,
})
}
pub fn with_platform(&mut self, platform: Platform) -> &mut CredentialsManager {
self.platform = platform;
self
}
pub(super) async fn get_auth_headers(&self) -> Result<HashMap<String, String>, &str> {
let mut headers: HashMap<String, String> = HashMap::new();
headers.insert(
AUTHORIZATION_HEADER_KEY.to_string(),
format!("Bearer {}", self.access_token),
);
headers.insert(
ENTITLEMENTS_HEADER_KEY.to_string(),
self.entitlements_token.to_string(),
);
headers.insert(
CLIENT_VERSION_HEADER_KEY.to_string(),
self.client_version.to_string(),
);
headers.insert(
CLIENT_PLATFORM_HEADER_KEY.to_string(),
self.get_client_platform()
.map_err(|_| "Failed to get client platform")?,
);
Ok(headers)
}
async fn get_client_version() -> reqwest::Result<String> {
let v1 = Self::fetch_version_valorant_api().await;
let v2 = Self::fetch_version_radiant_connect().await;
match (v1, v2) {
(Ok(version1), Ok(version2)) => {
if Self::compare_versions(&version1, &version2) > 0 {
Ok(version1)
} else {
Ok(version2)
}
}
(Ok(version1), Err(_)) => Ok(version1),
(Err(_), Ok(version2)) => Ok(version2),
(Err(e), Err(_)) => Err(e),
}
}
async fn fetch_version_valorant_api() -> reqwest::Result<String> {
reqwest::get("https://valorant-api.com/v1/version")
.await?
.json::<ValorantAPIData<Version>>()
.await
.map(|r: ValorantAPIData<Version>| r.data.riot_client_version)
}
async fn fetch_version_radiant_connect() -> reqwest::Result<String> {
reqwest::get("https://api.radiantconnect.ca/api/version/latest")
.await?
.json::<ValorantAPIData<RadiantVersion>>()
.await
.map(|r: ValorantAPIData<RadiantVersion>| r.data.riot_client_version)
}
fn compare_versions(v1: &str, v2: &str) -> i32 {
let parse_version = |v: &str| -> Vec<u32> {
v.split('-')
.filter_map(|part| part.parse::<u32>().ok())
.collect()
};
let parts1 = parse_version(v1);
let parts2 = parse_version(v2);
for i in 0..parts1.len().max(parts2.len()) {
let p1 = parts1.get(i).copied().unwrap_or(0);
let p2 = parts2.get(i).copied().unwrap_or(0);
if p1 > p2 {
return 1;
} else if p1 < p2 {
return -1;
}
}
0
}
fn get_client_platform(&self) -> Result<String, RequestError> {
let client_platform = self.platform.get_client_version();
serde_json::to_string(&client_platform)
.map(|s| BASE64_STANDARD.encode(s.as_bytes()))
.map_err(|_| {
RequestError::ParseError("Failed to serialize client platform".to_string())
})
}
}
unsafe impl Send for CredentialsManager {}
unsafe impl Sync for CredentialsManager {}