1use std::{fmt, sync::OnceLock};
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone)]
25#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
26#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
27#[cfg_attr(
28 feature = "wasm",
29 derive(tsify::Tsify),
30 tsify(into_wasm_abi, from_wasm_abi)
31)]
32pub struct ClientSettings {
33 pub identity_url: String,
35 pub api_url: String,
37 pub user_agent: String,
39 pub device_type: DeviceType,
41
42 pub device_identifier: Option<String>,
45 pub bitwarden_client_version: Option<String>,
47 pub bitwarden_package_type: Option<String>,
50}
51
52impl Default for ClientSettings {
53 fn default() -> Self {
54 Self {
55 identity_url: "https://identity.bitwarden.com".into(),
56 api_url: "https://api.bitwarden.com".into(),
57 user_agent: "Bitwarden Rust-SDK".into(),
58 device_type: DeviceType::SDK,
59 device_identifier: None,
60 bitwarden_client_version: None,
61 bitwarden_package_type: None,
62 }
63 }
64}
65
66#[allow(non_camel_case_types, missing_docs)]
67#[derive(Serialize, Deserialize, Copy, Clone, Debug, JsonSchema, PartialEq)]
68#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
69#[cfg_attr(
70 feature = "wasm",
71 derive(tsify::Tsify),
72 tsify(into_wasm_abi, from_wasm_abi)
73)]
74pub enum DeviceType {
75 Android = 0,
76 iOS = 1,
77 ChromeExtension = 2,
78 FirefoxExtension = 3,
79 OperaExtension = 4,
80 EdgeExtension = 5,
81 WindowsDesktop = 6,
82 MacOsDesktop = 7,
83 LinuxDesktop = 8,
84 ChromeBrowser = 9,
85 FirefoxBrowser = 10,
86 OperaBrowser = 11,
87 EdgeBrowser = 12,
88 IEBrowser = 13,
89 UnknownBrowser = 14,
90 AndroidAmazon = 15,
91 UWP = 16,
92 SafariBrowser = 17,
93 VivaldiBrowser = 18,
94 VivaldiExtension = 19,
95 SafariExtension = 20,
96 SDK = 21,
97 Server = 22,
98 WindowsCLI = 23,
99 MacOsCLI = 24,
100 LinuxCLI = 25,
101 DuckDuckGoBrowser = 26,
102}
103
104#[derive(Copy, Clone, Debug)]
105pub enum ClientName {
106 Web,
107 Browser,
108 Desktop,
109 Mobile,
110 Cli,
111}
112
113impl From<DeviceType> for Option<ClientName> {
114 fn from(device_type: DeviceType) -> Self {
115 match device_type {
116 DeviceType::Android | DeviceType::AndroidAmazon | DeviceType::iOS => {
117 Some(ClientName::Mobile)
118 }
119
120 DeviceType::ChromeBrowser
121 | DeviceType::FirefoxBrowser
122 | DeviceType::OperaBrowser
123 | DeviceType::EdgeBrowser
124 | DeviceType::IEBrowser
125 | DeviceType::SafariBrowser
126 | DeviceType::VivaldiBrowser
127 | DeviceType::DuckDuckGoBrowser
128 | DeviceType::UnknownBrowser => Some(ClientName::Web),
129
130 DeviceType::ChromeExtension
131 | DeviceType::FirefoxExtension
132 | DeviceType::OperaExtension
133 | DeviceType::EdgeExtension
134 | DeviceType::VivaldiExtension
135 | DeviceType::SafariExtension => Some(ClientName::Browser),
136
137 DeviceType::LinuxDesktop
138 | DeviceType::MacOsDesktop
139 | DeviceType::WindowsDesktop
140 | DeviceType::UWP => Some(ClientName::Desktop),
141
142 DeviceType::WindowsCLI | DeviceType::MacOsCLI | DeviceType::LinuxCLI => {
143 Some(ClientName::Cli)
144 }
145
146 DeviceType::SDK | DeviceType::Server => None,
147 }
148 }
149}
150
151impl fmt::Display for ClientName {
152 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153 let s = match self {
154 ClientName::Web => "web",
155 ClientName::Browser => "browser",
156 ClientName::Desktop => "desktop",
157 ClientName::Mobile => "mobile",
158 ClientName::Cli => "cli",
159 };
160 write!(f, "{}", s)
161 }
162}
163
164#[derive(Debug, Clone)]
169pub struct HostPlatformInfo {
170 pub user_agent: String,
171 pub device_type: DeviceType,
172 pub device_identifier: Option<String>,
173 pub bitwarden_client_version: Option<String>,
174 pub bitwarden_package_type: Option<String>,
175}
176
177impl From<&ClientSettings> for HostPlatformInfo {
178 fn from(settings: &ClientSettings) -> Self {
179 Self {
180 user_agent: settings.user_agent.clone(),
181 device_type: settings.device_type,
182 device_identifier: settings.device_identifier.clone(),
183 bitwarden_client_version: settings.bitwarden_client_version.clone(),
184 bitwarden_package_type: settings.bitwarden_package_type.clone(),
185 }
186 }
187}
188
189static HOST_PLATFORM_INFO: OnceLock<HostPlatformInfo> = OnceLock::new();
190
191pub fn init_host_platform_info(info: HostPlatformInfo) {
196 let _ = HOST_PLATFORM_INFO.set(info);
197}
198
199pub fn get_host_platform_info() -> &'static HostPlatformInfo {
204 HOST_PLATFORM_INFO
205 .get()
206 .expect("host platform info to be initialized")
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
216 fn init_then_get_preserves_first_value() {
217 let prev_hook = std::panic::take_hook();
219 std::panic::set_hook(Box::new(|_| {}));
220 let not_set = std::panic::catch_unwind(get_host_platform_info);
221 std::panic::set_hook(prev_hook);
222 assert!(not_set.is_err(), "expected panic before init");
223
224 let first = HostPlatformInfo {
225 user_agent: "first".into(),
226 device_type: DeviceType::SDK,
227 device_identifier: Some("dev-1".into()),
228 bitwarden_client_version: Some("1.0.0".into()),
229 bitwarden_package_type: Some("test".into()),
230 };
231 init_host_platform_info(first.clone());
232
233 let got = get_host_platform_info();
234 assert_eq!(got.user_agent, first.user_agent);
235 assert_eq!(got.device_type, first.device_type);
236 assert_eq!(got.device_identifier, first.device_identifier);
237
238 let second = HostPlatformInfo {
239 user_agent: "second".into(),
240 ..first.clone()
241 };
242 init_host_platform_info(second);
243
244 let after = get_host_platform_info();
245 assert_eq!(after.user_agent, first.user_agent);
246 }
247}