1#![doc = include_str!("../README.md")]
2
3uniffi::setup_scaffolding!();
4
5use std::sync::{Arc, Once};
6
7use auth::AuthClient;
8use bitwarden_core::{ClientSettings, auth::ClientManagedTokens};
9
10#[allow(missing_docs)]
11pub mod auth;
12#[allow(missing_docs)]
13pub mod crypto;
14#[cfg(feature = "debug-capabilities")]
17pub mod debug;
18#[allow(missing_docs)]
19pub mod error;
20mod log_callback;
21#[allow(missing_docs)]
22pub mod managed_settings;
23#[allow(missing_docs)]
24pub mod platform;
25#[allow(missing_docs)]
26pub mod policies;
27#[allow(missing_docs)]
28pub mod tools;
29mod uniffi_support;
30#[allow(missing_docs)]
31pub mod vault;
32
33#[cfg(target_os = "android")]
34mod android_support;
35
36use crypto::CryptoClient;
37use error::{Error, Result};
38pub use log_callback::LogCallback;
39pub use managed_settings::ManagedSettingsBindingClient;
40use platform::PlatformClient;
41pub use platform::{
42 AcquiredCookie, BootstrapConfig, ServerCommunicationConfig, ServerCommunicationConfigClient,
43 ServerCommunicationConfigRepository, SsoCookieVendorConfig,
44};
45use tools::{ExporterClient, GeneratorClients, ImporterClient, SendClient, SshClient};
46use vault::VaultClient;
47
48#[allow(missing_docs)]
49#[derive(uniffi::Object)]
50pub struct Client(pub(crate) bitwarden_pm::PasswordManagerClient);
51
52#[uniffi::export(async_runtime = "tokio")]
53impl Client {
54 #[uniffi::constructor]
61 pub fn new(
62 token_provider: Arc<dyn ClientManagedTokens>,
63 settings: Option<ClientSettings>,
64 managed_settings: Arc<ManagedSettingsBindingClient>,
65 ) -> Self {
66 init_logger(None, None, true);
67 setup_error_converter();
68
69 #[cfg(target_os = "android")]
70 android_support::init();
71
72 Self(bitwarden_pm::PasswordManagerClient::new_with_client_tokens(
73 settings,
74 token_provider,
75 &managed_settings.0,
76 ))
77 }
78
79 pub fn crypto(&self) -> CryptoClient {
81 CryptoClient(self.0.crypto())
82 }
83
84 pub fn km_state_bridge(
87 &self,
88 ) -> bitwarden_core::key_management::state_bridge::StateBridgeClient {
89 self.0.0.km_state_bridge()
90 }
91
92 pub fn user_crypto_management(
94 &self,
95 ) -> bitwarden_user_crypto_management::UserCryptoManagementClient {
96 self.0.user_crypto_management()
97 }
98
99 pub fn crypto_sync_handler(&self) -> bitwarden_crypto_sync_handler::CryptoSyncHandlerClient {
101 self.0.crypto_sync_handler()
102 }
103
104 pub fn vault(&self) -> VaultClient {
106 VaultClient(self.0.vault())
107 }
108
109 pub fn collections(&self) -> vault::collections::CollectionsClient {
115 vault::collections::CollectionsClient(self.0.collections())
116 }
117
118 #[allow(missing_docs)]
119 pub fn platform(&self) -> PlatformClient {
120 PlatformClient(self.0.0.clone())
121 }
122
123 pub fn generators(&self) -> GeneratorClients {
125 GeneratorClients(self.0.generator())
126 }
127
128 pub fn exporters(&self) -> ExporterClient {
130 ExporterClient(self.0.exporters())
131 }
132
133 pub fn importers(&self) -> ImporterClient {
135 ImporterClient(self.0.importers())
136 }
137
138 pub fn sends(&self) -> SendClient {
140 SendClient(self.0.sends())
141 }
142
143 pub fn send_sync_handler(&self) -> bitwarden_send::SendSyncHandlerClient {
145 self.0.send_sync_handler()
146 }
147
148 pub fn ssh(&self) -> SshClient {
150 SshClient()
151 }
152
153 pub fn random(&self) -> bitwarden_random::SdkRandomNumberClient {
155 bitwarden_random::SdkRandomNumberClient::new()
156 }
157
158 pub fn auth(&self) -> AuthClient {
160 AuthClient(self.0.0.clone())
161 }
162
163 pub fn gov_mode(&self) -> bool {
165 self.0.0.gov_mode()
166 }
167
168 pub fn policies(&self) -> policies::PoliciesClient {
170 use bitwarden_policies::PoliciesClientExt;
171 policies::PoliciesClient(self.0.0.policies())
172 }
173
174 pub fn echo(&self, msg: String) -> String {
176 msg
177 }
178
179 pub async fn http_get(&self, url: String) -> Result<String> {
181 let client = self.0.0.internal.get_http_client();
182 let res = client
183 .get(&url)
184 .send()
185 .await
186 .map_err(|e| Error::Api(e.into()))?;
187
188 res.text().await.map_err(|e| Error::Api(e.into()))
189 }
190}
191
192#[cfg(feature = "debug-capabilities")]
193#[uniffi::export]
194impl Client {
195 pub fn debug(&self) -> debug::DebugClient {
199 debug::DebugClient(self.0.debug())
200 }
201}
202
203static INIT: Once = Once::new();
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
207pub enum LogLevel {
208 Trace,
210 Debug,
212 Info,
214 Warn,
216 Error,
218}
219
220impl LogLevel {
221 fn as_str(&self) -> &'static str {
222 match self {
223 LogLevel::Trace => "trace",
224 LogLevel::Debug => "debug",
225 LogLevel::Info => "info",
226 LogLevel::Warn => "warn",
227 LogLevel::Error => "error",
228 }
229 }
230}
231
232impl From<&tracing::Level> for LogLevel {
233 fn from(level: &tracing::Level) -> Self {
234 match *level {
235 tracing::Level::TRACE => LogLevel::Trace,
236 tracing::Level::DEBUG => LogLevel::Debug,
237 tracing::Level::INFO => LogLevel::Info,
238 tracing::Level::WARN => LogLevel::Warn,
239 tracing::Level::ERROR => LogLevel::Error,
240 }
241 }
242}
243
244#[uniffi::export(default(platform_logger = true))]
271pub fn init_logger(
272 callback: Option<Arc<dyn LogCallback>>,
273 level: Option<LogLevel>,
274 platform_logger: bool,
275) {
276 use tracing_subscriber::{EnvFilter, layer::SubscriberExt as _, util::SubscriberInitExt as _};
277
278 INIT.call_once(|| {
279 let level = level.as_ref().map(|l| l.as_str()).unwrap_or("info");
285 let filter = EnvFilter::builder()
286 .with_default_directive(
287 option_env!("RUST_LOG")
288 .unwrap_or(level)
289 .parse()
290 .expect("should provide valid log level at compile time."),
291 )
292 .from_env_lossy();
293
294 let fmtlayer = platform_logger.then(|| {
296 tracing_subscriber::fmt::layer()
297 .with_ansi(true)
298 .with_file(true)
299 .with_line_number(true)
300 .with_target(true)
301 .pretty()
302 });
303
304 let registry = tracing_subscriber::registry().with(fmtlayer).with(filter);
306
307 let callback_layer = callback.map(log_callback::CallbackLayer::new);
310 let registry = registry.with(callback_layer);
311 #[cfg(target_os = "ios")]
312 {
313 const TAG: &str = "com.8bit.bitwarden";
314 registry
315 .with(platform_logger.then(|| tracing_oslog::OsLogger::new(TAG, "default")))
316 .init();
317 }
318
319 #[cfg(target_os = "android")]
320 {
321 const TAG: &str = "com.bitwarden.sdk";
322 registry
323 .with(platform_logger.then(|| {
324 tracing_android::layer(TAG)
325 .expect("initialization of android logcat tracing layer")
326 }))
327 .init();
328 }
329
330 #[cfg(not(any(target_os = "android", target_os = "ios")))]
331 {
332 registry.init();
333 }
334 #[cfg(feature = "dangerous-crypto-debug")]
335 tracing::warn!(
336 "Dangerous crypto debug features are enabled. THIS MUST NOT BE USED IN PRODUCTION BUILDS!!"
337 );
338 });
339}
340
341fn setup_error_converter() {
344 bitwarden_uniffi_error::set_error_to_uniffi_error(|e| {
345 crate::error::BitwardenError::Conversion(e.to_string()).into()
346 });
347}
348#[cfg(test)]
349mod tests {
350 use std::sync::Mutex;
351
352 use super::*;
353 #[derive(Debug)]
355 struct MockTokenProvider;
356
357 #[async_trait::async_trait]
358 impl ClientManagedTokens for MockTokenProvider {
359 async fn get_access_token(&self) -> Option<String> {
360 Some("mock_token".to_string())
361 }
362 }
363 struct TestLogCallback {
365 logs: Arc<Mutex<Vec<(LogLevel, String, String)>>>,
366 }
367 impl LogCallback for TestLogCallback {
368 fn on_log(&self, level: LogLevel, target: String, message: String) -> Result<()> {
369 self.logs
370 .lock()
371 .expect("Failed to lock logs mutex")
372 .push((level, target, message));
373 Ok(())
374 }
375 }
376
377 #[test]
382 fn test_callback_receives_logs() {
383 let logs = Arc::new(Mutex::new(Vec::new()));
384 let callback = Arc::new(TestLogCallback { logs: logs.clone() });
385
386 init_logger(Some(callback), None, true);
388
389 let _client = Client::new(
391 Arc::new(MockTokenProvider),
392 None,
393 Arc::new(ManagedSettingsBindingClient::new()),
394 );
395
396 tracing::info!("test message from SDK");
398
399 let captured = logs.lock().expect("Failed to lock logs mutex");
401 assert!(!captured.is_empty(), "Callback should receive logs");
402
403 let test_log = captured
405 .iter()
406 .find(|(_, _, msg)| msg.contains("test message"))
407 .expect("Should find our test log message");
408
409 assert_eq!(test_log.0, LogLevel::Info);
410 assert!(test_log.2.contains("test message"));
411 }
412}