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 unlock(&self) -> bitwarden_unlock::UnlockClient {
101 self.0.unlock()
102 }
103
104 pub fn crypto_sync_handler(&self) -> bitwarden_crypto_sync_handler::CryptoSyncHandlerClient {
106 self.0.crypto_sync_handler()
107 }
108
109 pub fn vault(&self) -> VaultClient {
111 VaultClient(self.0.vault())
112 }
113
114 pub fn collections(&self) -> vault::collections::CollectionsClient {
120 vault::collections::CollectionsClient(self.0.collections())
121 }
122
123 #[allow(missing_docs)]
124 pub fn platform(&self) -> PlatformClient {
125 PlatformClient(self.0.0.clone())
126 }
127
128 pub fn generators(&self) -> GeneratorClients {
130 GeneratorClients(self.0.generator())
131 }
132
133 pub fn exporters(&self) -> ExporterClient {
135 ExporterClient(self.0.exporters())
136 }
137
138 pub fn importers(&self) -> ImporterClient {
140 ImporterClient(self.0.importers())
141 }
142
143 pub fn sends(&self) -> SendClient {
145 SendClient(self.0.sends())
146 }
147
148 pub fn send_sync_handler(&self) -> bitwarden_send::SendSyncHandlerClient {
150 self.0.send_sync_handler()
151 }
152
153 pub fn ssh(&self) -> SshClient {
155 SshClient()
156 }
157
158 pub fn random(&self) -> bitwarden_random::SdkRandomNumberClient {
160 bitwarden_random::SdkRandomNumberClient::new()
161 }
162
163 pub fn auth(&self) -> AuthClient {
165 AuthClient(self.0.0.clone())
166 }
167
168 pub fn gov_mode(&self) -> bool {
170 self.0.0.gov_mode()
171 }
172
173 pub fn policies(&self) -> policies::PoliciesClient {
175 use bitwarden_policies::PoliciesClientExt;
176 policies::PoliciesClient(self.0.0.policies())
177 }
178
179 pub fn echo(&self, msg: String) -> String {
181 msg
182 }
183
184 pub async fn http_get(&self, url: String) -> Result<String> {
186 let client = self.0.0.internal.get_http_client();
187 let res = client
188 .get(&url)
189 .send()
190 .await
191 .map_err(|e| Error::Api(e.into()))?;
192
193 res.text().await.map_err(|e| Error::Api(e.into()))
194 }
195}
196
197#[cfg(feature = "debug-capabilities")]
198#[uniffi::export]
199impl Client {
200 pub fn debug(&self) -> debug::DebugClient {
204 debug::DebugClient(self.0.debug())
205 }
206}
207
208static INIT: Once = Once::new();
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
212pub enum LogLevel {
213 Trace,
215 Debug,
217 Info,
219 Warn,
221 Error,
223}
224
225impl LogLevel {
226 fn as_str(&self) -> &'static str {
227 match self {
228 LogLevel::Trace => "trace",
229 LogLevel::Debug => "debug",
230 LogLevel::Info => "info",
231 LogLevel::Warn => "warn",
232 LogLevel::Error => "error",
233 }
234 }
235}
236
237impl From<&tracing::Level> for LogLevel {
238 fn from(level: &tracing::Level) -> Self {
239 match *level {
240 tracing::Level::TRACE => LogLevel::Trace,
241 tracing::Level::DEBUG => LogLevel::Debug,
242 tracing::Level::INFO => LogLevel::Info,
243 tracing::Level::WARN => LogLevel::Warn,
244 tracing::Level::ERROR => LogLevel::Error,
245 }
246 }
247}
248
249#[uniffi::export(default(platform_logger = true))]
276pub fn init_logger(
277 callback: Option<Arc<dyn LogCallback>>,
278 level: Option<LogLevel>,
279 platform_logger: bool,
280) {
281 use tracing_subscriber::{EnvFilter, layer::SubscriberExt as _, util::SubscriberInitExt as _};
282
283 INIT.call_once(|| {
284 let level = level.as_ref().map(|l| l.as_str()).unwrap_or("info");
290 let filter = EnvFilter::builder()
291 .with_default_directive(
292 option_env!("RUST_LOG")
293 .unwrap_or(level)
294 .parse()
295 .expect("should provide valid log level at compile time."),
296 )
297 .from_env_lossy();
298
299 let fmtlayer = platform_logger.then(|| {
301 tracing_subscriber::fmt::layer()
302 .with_ansi(true)
303 .with_file(true)
304 .with_line_number(true)
305 .with_target(true)
306 .pretty()
307 });
308
309 let registry = tracing_subscriber::registry().with(fmtlayer).with(filter);
311
312 let callback_layer = callback.map(log_callback::CallbackLayer::new);
315 let registry = registry.with(callback_layer);
316 #[cfg(target_os = "ios")]
317 {
318 const TAG: &str = "com.8bit.bitwarden";
319 registry
320 .with(platform_logger.then(|| tracing_oslog::OsLogger::new(TAG, "default")))
321 .init();
322 }
323
324 #[cfg(target_os = "android")]
325 {
326 const TAG: &str = "com.bitwarden.sdk";
327 registry
328 .with(platform_logger.then(|| {
329 tracing_android::layer(TAG)
330 .expect("initialization of android logcat tracing layer")
331 }))
332 .init();
333 }
334
335 #[cfg(not(any(target_os = "android", target_os = "ios")))]
336 {
337 registry.init();
338 }
339 #[cfg(feature = "dangerous-crypto-debug")]
340 tracing::warn!(
341 "Dangerous crypto debug features are enabled. THIS MUST NOT BE USED IN PRODUCTION BUILDS!!"
342 );
343 });
344}
345
346fn setup_error_converter() {
349 bitwarden_uniffi_error::set_error_to_uniffi_error(|e| {
350 crate::error::BitwardenError::Conversion(e.to_string()).into()
351 });
352}
353#[cfg(test)]
354mod tests {
355 use std::sync::Mutex;
356
357 use super::*;
358 #[derive(Debug)]
360 struct MockTokenProvider;
361
362 #[async_trait::async_trait]
363 impl ClientManagedTokens for MockTokenProvider {
364 async fn get_access_token(&self) -> Option<String> {
365 Some("mock_token".to_string())
366 }
367 }
368 struct TestLogCallback {
370 logs: Arc<Mutex<Vec<(LogLevel, String, String)>>>,
371 }
372 impl LogCallback for TestLogCallback {
373 fn on_log(&self, level: LogLevel, target: String, message: String) -> Result<()> {
374 self.logs
375 .lock()
376 .expect("Failed to lock logs mutex")
377 .push((level, target, message));
378 Ok(())
379 }
380 }
381
382 #[test]
387 fn test_callback_receives_logs() {
388 let logs = Arc::new(Mutex::new(Vec::new()));
389 let callback = Arc::new(TestLogCallback { logs: logs.clone() });
390
391 init_logger(Some(callback), None, true);
393
394 let _client = Client::new(
396 Arc::new(MockTokenProvider),
397 None,
398 Arc::new(ManagedSettingsBindingClient::new()),
399 );
400
401 tracing::info!("test message from SDK");
403
404 let captured = logs.lock().expect("Failed to lock logs mutex");
406 assert!(!captured.is_empty(), "Callback should receive logs");
407
408 let test_log = captured
410 .iter()
411 .find(|(_, _, msg)| msg.contains("test message"))
412 .expect("Should find our test log message");
413
414 assert_eq!(test_log.0, LogLevel::Info);
415 assert!(test_log.2.contains("test message"));
416 }
417}