use serde::Serialize;
use std::env;
#[derive(Debug, Clone, Serialize)]
pub struct Telemetry {
pub sdk_name: &'static str,
pub sdk_version: &'static str,
pub os_name: String,
pub os_version: String,
pub platform: &'static str,
pub architecture: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
pub cpu_cores: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub locale: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub language: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub timezone: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub app_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub app_build: Option<String>,
}
impl Telemetry {
pub fn collect(app_version: Option<String>, app_build: Option<String>) -> Self {
Self {
sdk_name: crate::SDK_NAME,
sdk_version: crate::VERSION,
os_name: os_name(),
os_version: os_version(),
platform: platform(),
architecture: architecture(),
cpu_cores: num_cpus(),
locale: locale(),
language: language(),
timezone: timezone(),
app_version,
app_build,
}
}
}
fn os_name() -> String {
#[cfg(target_os = "macos")]
return "macOS".to_string();
#[cfg(target_os = "windows")]
return "Windows".to_string();
#[cfg(target_os = "linux")]
return "Linux".to_string();
#[cfg(target_os = "ios")]
return "iOS".to_string();
#[cfg(target_os = "android")]
return "Android".to_string();
#[cfg(not(any(
target_os = "macos",
target_os = "windows",
target_os = "linux",
target_os = "ios",
target_os = "android"
)))]
return env::consts::OS.to_string();
}
fn os_version() -> String {
env::consts::OS.to_string()
}
fn platform() -> &'static str {
#[cfg(any(target_os = "ios", target_os = "android"))]
return "mobile";
#[cfg(not(any(target_os = "ios", target_os = "android")))]
return "desktop";
}
fn architecture() -> &'static str {
env::consts::ARCH
}
fn num_cpus() -> Option<usize> {
std::thread::available_parallelism().ok().map(|p| p.get())
}
fn locale() -> Option<String> {
env::var("LANG").ok().or_else(|| env::var("LC_ALL").ok())
}
fn language() -> Option<String> {
locale().and_then(|l| {
l.split('_')
.next()
.map(|s| s.split('.').next().unwrap_or(s).to_string())
})
}
fn timezone() -> Option<String> {
env::var("TZ").ok()
}
pub fn generate_device_id() -> String {
if let Ok(id) = machine_uid::get() {
return format!("rust_{}", &id[..16.min(id.len())]);
}
let hostname = hostname::get()
.ok()
.and_then(|h| h.into_string().ok())
.unwrap_or_else(|| "unknown".to_string());
let username = env::var("USER")
.or_else(|_| env::var("USERNAME"))
.unwrap_or_else(|_| "unknown".to_string());
let combined = format!("{}:{}", hostname, username);
let hash = simple_hash(&combined);
format!("rust_{:016x}", hash)
}
fn simple_hash(s: &str) -> u64 {
let mut hash: u64 = 5381;
for byte in s.bytes() {
hash = hash.wrapping_mul(33).wrapping_add(byte as u64);
}
hash
}