use std::{fmt, sync::OnceLock};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
#[cfg_attr(
feature = "wasm",
derive(tsify::Tsify),
tsify(into_wasm_abi, from_wasm_abi)
)]
pub struct ClientSettings {
pub identity_url: String,
pub api_url: String,
pub user_agent: String,
pub device_type: DeviceType,
pub device_identifier: Option<String>,
pub bitwarden_client_version: Option<String>,
pub bitwarden_package_type: Option<String>,
}
impl Default for ClientSettings {
fn default() -> Self {
Self {
identity_url: "https://identity.bitwarden.com".into(),
api_url: "https://api.bitwarden.com".into(),
user_agent: "Bitwarden Rust-SDK".into(),
device_type: DeviceType::SDK,
device_identifier: None,
bitwarden_client_version: None,
bitwarden_package_type: None,
}
}
}
#[allow(non_camel_case_types, missing_docs)]
#[derive(Serialize, Deserialize, Copy, Clone, Debug, JsonSchema, PartialEq)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
#[cfg_attr(
feature = "wasm",
derive(tsify::Tsify),
tsify(into_wasm_abi, from_wasm_abi)
)]
pub enum DeviceType {
Android = 0,
iOS = 1,
ChromeExtension = 2,
FirefoxExtension = 3,
OperaExtension = 4,
EdgeExtension = 5,
WindowsDesktop = 6,
MacOsDesktop = 7,
LinuxDesktop = 8,
ChromeBrowser = 9,
FirefoxBrowser = 10,
OperaBrowser = 11,
EdgeBrowser = 12,
IEBrowser = 13,
UnknownBrowser = 14,
AndroidAmazon = 15,
UWP = 16,
SafariBrowser = 17,
VivaldiBrowser = 18,
VivaldiExtension = 19,
SafariExtension = 20,
SDK = 21,
Server = 22,
WindowsCLI = 23,
MacOsCLI = 24,
LinuxCLI = 25,
DuckDuckGoBrowser = 26,
}
#[derive(Copy, Clone, Debug)]
pub enum ClientName {
Web,
Browser,
Desktop,
Mobile,
Cli,
}
impl From<DeviceType> for Option<ClientName> {
fn from(device_type: DeviceType) -> Self {
match device_type {
DeviceType::Android | DeviceType::AndroidAmazon | DeviceType::iOS => {
Some(ClientName::Mobile)
}
DeviceType::ChromeBrowser
| DeviceType::FirefoxBrowser
| DeviceType::OperaBrowser
| DeviceType::EdgeBrowser
| DeviceType::IEBrowser
| DeviceType::SafariBrowser
| DeviceType::VivaldiBrowser
| DeviceType::DuckDuckGoBrowser
| DeviceType::UnknownBrowser => Some(ClientName::Web),
DeviceType::ChromeExtension
| DeviceType::FirefoxExtension
| DeviceType::OperaExtension
| DeviceType::EdgeExtension
| DeviceType::VivaldiExtension
| DeviceType::SafariExtension => Some(ClientName::Browser),
DeviceType::LinuxDesktop
| DeviceType::MacOsDesktop
| DeviceType::WindowsDesktop
| DeviceType::UWP => Some(ClientName::Desktop),
DeviceType::WindowsCLI | DeviceType::MacOsCLI | DeviceType::LinuxCLI => {
Some(ClientName::Cli)
}
DeviceType::SDK | DeviceType::Server => None,
}
}
}
impl fmt::Display for ClientName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
ClientName::Web => "web",
ClientName::Browser => "browser",
ClientName::Desktop => "desktop",
ClientName::Mobile => "mobile",
ClientName::Cli => "cli",
};
write!(f, "{}", s)
}
}
#[derive(Debug, Clone)]
pub struct HostPlatformInfo {
pub user_agent: String,
pub device_type: DeviceType,
pub device_identifier: Option<String>,
pub bitwarden_client_version: Option<String>,
pub bitwarden_package_type: Option<String>,
}
impl From<&ClientSettings> for HostPlatformInfo {
fn from(settings: &ClientSettings) -> Self {
Self {
user_agent: settings.user_agent.clone(),
device_type: settings.device_type,
device_identifier: settings.device_identifier.clone(),
bitwarden_client_version: settings.bitwarden_client_version.clone(),
bitwarden_package_type: settings.bitwarden_package_type.clone(),
}
}
}
static HOST_PLATFORM_INFO: OnceLock<HostPlatformInfo> = OnceLock::new();
pub fn init_host_platform_info(info: HostPlatformInfo) {
let _ = HOST_PLATFORM_INFO.set(info);
}
pub fn get_host_platform_info() -> &'static HostPlatformInfo {
HOST_PLATFORM_INFO
.get()
.expect("host platform info to be initialized")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn init_then_get_preserves_first_value() {
let prev_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let not_set = std::panic::catch_unwind(get_host_platform_info);
std::panic::set_hook(prev_hook);
assert!(not_set.is_err(), "expected panic before init");
let first = HostPlatformInfo {
user_agent: "first".into(),
device_type: DeviceType::SDK,
device_identifier: Some("dev-1".into()),
bitwarden_client_version: Some("1.0.0".into()),
bitwarden_package_type: Some("test".into()),
};
init_host_platform_info(first.clone());
let got = get_host_platform_info();
assert_eq!(got.user_agent, first.user_agent);
assert_eq!(got.device_type, first.device_type);
assert_eq!(got.device_identifier, first.device_identifier);
let second = HostPlatformInfo {
user_agent: "second".into(),
..first.clone()
};
init_host_platform_info(second);
let after = get_host_platform_info();
assert_eq!(after.user_agent, first.user_agent);
}
}