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#[bitwarden_ffi::wasm_record]
28pub struct ClientSettings {
29 pub identity_url: String,
31 pub api_url: String,
33 pub user_agent: String,
35 pub device_type: DeviceType,
37
38 pub device_identifier: Option<String>,
41 pub bitwarden_client_version: Option<String>,
43 pub bitwarden_package_type: Option<String>,
46}
47
48impl Default for ClientSettings {
49 fn default() -> Self {
50 Self {
51 identity_url: "https://identity.bitwarden.com".into(),
52 api_url: "https://api.bitwarden.com".into(),
53 user_agent: "Bitwarden Rust-SDK".into(),
54 device_type: DeviceType::SDK,
55 device_identifier: None,
56 bitwarden_client_version: None,
57 bitwarden_package_type: None,
58 }
59 }
60}
61
62#[allow(non_camel_case_types, missing_docs)]
63#[derive(Serialize, Deserialize, Copy, Clone, Debug, JsonSchema, PartialEq)]
64#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
65#[bitwarden_ffi::wasm_record]
66pub enum DeviceType {
67 Android = 0,
68 iOS = 1,
69 ChromeExtension = 2,
70 FirefoxExtension = 3,
71 OperaExtension = 4,
72 EdgeExtension = 5,
73 WindowsDesktop = 6,
74 MacOsDesktop = 7,
75 LinuxDesktop = 8,
76 ChromeBrowser = 9,
77 FirefoxBrowser = 10,
78 OperaBrowser = 11,
79 EdgeBrowser = 12,
80 IEBrowser = 13,
81 UnknownBrowser = 14,
82 AndroidAmazon = 15,
83 UWP = 16,
84 SafariBrowser = 17,
85 VivaldiBrowser = 18,
86 VivaldiExtension = 19,
87 SafariExtension = 20,
88 SDK = 21,
89 Server = 22,
90 WindowsCLI = 23,
91 MacOsCLI = 24,
92 LinuxCLI = 25,
93 DuckDuckGoBrowser = 26,
94}
95
96#[derive(Copy, Clone, Debug)]
97pub enum ClientName {
98 Web,
99 Browser,
100 Desktop,
101 Mobile,
102 Cli,
103}
104
105impl From<DeviceType> for Option<ClientName> {
106 fn from(device_type: DeviceType) -> Self {
107 match device_type {
108 DeviceType::Android | DeviceType::AndroidAmazon | DeviceType::iOS => {
109 Some(ClientName::Mobile)
110 }
111
112 DeviceType::ChromeBrowser
113 | DeviceType::FirefoxBrowser
114 | DeviceType::OperaBrowser
115 | DeviceType::EdgeBrowser
116 | DeviceType::IEBrowser
117 | DeviceType::SafariBrowser
118 | DeviceType::VivaldiBrowser
119 | DeviceType::DuckDuckGoBrowser
120 | DeviceType::UnknownBrowser => Some(ClientName::Web),
121
122 DeviceType::ChromeExtension
123 | DeviceType::FirefoxExtension
124 | DeviceType::OperaExtension
125 | DeviceType::EdgeExtension
126 | DeviceType::VivaldiExtension
127 | DeviceType::SafariExtension => Some(ClientName::Browser),
128
129 DeviceType::LinuxDesktop
130 | DeviceType::MacOsDesktop
131 | DeviceType::WindowsDesktop
132 | DeviceType::UWP => Some(ClientName::Desktop),
133
134 DeviceType::WindowsCLI | DeviceType::MacOsCLI | DeviceType::LinuxCLI => {
135 Some(ClientName::Cli)
136 }
137
138 DeviceType::SDK | DeviceType::Server => None,
139 }
140 }
141}
142
143impl fmt::Display for ClientName {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 let s = match self {
146 ClientName::Web => "web",
147 ClientName::Browser => "browser",
148 ClientName::Desktop => "desktop",
149 ClientName::Mobile => "mobile",
150 ClientName::Cli => "cli",
151 };
152 write!(f, "{}", s)
153 }
154}
155
156#[derive(Debug, Clone)]
161pub struct HostPlatformInfo {
162 pub user_agent: String,
163 pub device_type: DeviceType,
164 pub device_identifier: Option<String>,
165 pub bitwarden_client_version: Option<String>,
166 pub bitwarden_package_type: Option<String>,
167}
168
169impl From<&ClientSettings> for HostPlatformInfo {
170 fn from(settings: &ClientSettings) -> Self {
171 Self {
172 user_agent: settings.user_agent.clone(),
173 device_type: settings.device_type,
174 device_identifier: settings.device_identifier.clone(),
175 bitwarden_client_version: settings.bitwarden_client_version.clone(),
176 bitwarden_package_type: settings.bitwarden_package_type.clone(),
177 }
178 }
179}
180
181static HOST_PLATFORM_INFO: OnceLock<HostPlatformInfo> = OnceLock::new();
182
183pub fn init_host_platform_info(info: HostPlatformInfo) {
188 let _ = HOST_PLATFORM_INFO.set(info);
189}
190
191pub fn get_host_platform_info() -> &'static HostPlatformInfo {
196 HOST_PLATFORM_INFO
197 .get()
198 .expect("host platform info to be initialized")
199}
200
201impl HostPlatformInfo {
202 pub fn to_client_settings(&self, api_url: String, identity_url: String) -> ClientSettings {
204 ClientSettings {
205 user_agent: self.user_agent.clone(),
206 device_type: self.device_type,
207 device_identifier: self.device_identifier.clone(),
208 bitwarden_client_version: self.bitwarden_client_version.clone(),
209 bitwarden_package_type: self.bitwarden_package_type.clone(),
210 api_url,
211 identity_url,
212 }
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
225 fn second_init_does_not_overwrite_first() {
226 let first = HostPlatformInfo {
227 user_agent: "first".into(),
228 device_type: DeviceType::SDK,
229 device_identifier: Some("dev-1".into()),
230 bitwarden_client_version: Some("1.0.0".into()),
231 bitwarden_package_type: Some("test".into()),
232 };
233 init_host_platform_info(first);
234
235 let after_first_init = get_host_platform_info().clone();
238
239 let second = HostPlatformInfo {
240 user_agent: "second".into(),
241 device_type: DeviceType::SDK,
242 device_identifier: Some("dev-2".into()),
243 bitwarden_client_version: Some("2.0.0".into()),
244 bitwarden_package_type: Some("test".into()),
245 };
246 init_host_platform_info(second);
247
248 let after_second_init = get_host_platform_info();
249 assert_eq!(after_second_init.user_agent, after_first_init.user_agent);
250 assert_eq!(after_second_init.device_type, after_first_init.device_type);
251 assert_eq!(
252 after_second_init.device_identifier,
253 after_first_init.device_identifier
254 );
255 assert_eq!(
256 after_second_init.bitwarden_client_version,
257 after_first_init.bitwarden_client_version
258 );
259 assert_eq!(
260 after_second_init.bitwarden_package_type,
261 after_first_init.bitwarden_package_type
262 );
263 }
264}