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#[allow(missing_docs)]
15pub mod error;
16mod log_callback;
17#[allow(missing_docs)]
18pub mod platform;
19#[allow(missing_docs)]
20pub mod policies;
21#[allow(missing_docs)]
22pub mod tool;
23mod uniffi_support;
24#[allow(missing_docs)]
25pub mod vault;
26
27#[cfg(target_os = "android")]
28mod android_support;
29
30use crypto::CryptoClient;
31use error::{Error, Result};
32pub use log_callback::LogCallback;
33use platform::PlatformClient;
34pub use platform::{
35 AcquiredCookie, BootstrapConfig, ServerCommunicationConfig, ServerCommunicationConfigClient,
36 ServerCommunicationConfigRepository, SsoCookieVendorConfig,
37};
38use tool::{ExporterClient, GeneratorClients, ImporterClient, SendClient, SshClient};
39use vault::VaultClient;
40
41#[allow(missing_docs)]
42#[derive(uniffi::Object)]
43pub struct Client(pub(crate) bitwarden_pm::PasswordManagerClient);
44
45#[uniffi::export(async_runtime = "tokio")]
46impl Client {
47 #[uniffi::constructor]
49 pub fn new(
50 token_provider: Arc<dyn ClientManagedTokens>,
51 settings: Option<ClientSettings>,
52 ) -> Self {
53 init_logger(None, None);
54 setup_error_converter();
55
56 #[cfg(target_os = "android")]
57 android_support::init();
58
59 Self(bitwarden_pm::PasswordManagerClient::new_with_client_tokens(
60 settings,
61 token_provider,
62 ))
63 }
64
65 pub fn crypto(&self) -> CryptoClient {
67 CryptoClient(self.0.crypto())
68 }
69
70 pub fn km_state_bridge(
73 &self,
74 ) -> bitwarden_core::key_management::state_bridge::StateBridgeClient {
75 self.0.0.km_state_bridge()
76 }
77
78 pub fn user_crypto_management(
80 &self,
81 ) -> bitwarden_user_crypto_management::UserCryptoManagementClient {
82 self.0.user_crypto_management()
83 }
84
85 pub fn crypto_sync_handler(&self) -> bitwarden_crypto_sync_handler::CryptoSyncHandlerClient {
87 self.0.crypto_sync_handler()
88 }
89
90 pub fn vault(&self) -> VaultClient {
92 VaultClient(self.0.vault())
93 }
94
95 #[allow(missing_docs)]
96 pub fn platform(&self) -> PlatformClient {
97 PlatformClient(self.0.0.clone())
98 }
99
100 pub fn generators(&self) -> GeneratorClients {
102 GeneratorClients(self.0.generator())
103 }
104
105 pub fn exporters(&self) -> ExporterClient {
107 ExporterClient(self.0.exporters())
108 }
109
110 pub fn importers(&self) -> ImporterClient {
112 ImporterClient(self.0.importers())
113 }
114
115 pub fn sends(&self) -> SendClient {
117 SendClient(self.0.sends())
118 }
119
120 pub fn ssh(&self) -> SshClient {
122 SshClient()
123 }
124
125 pub fn random(&self) -> bitwarden_random::SdkRandomNumberClient {
127 bitwarden_random::SdkRandomNumberClient::new()
128 }
129
130 pub fn auth(&self) -> AuthClient {
132 AuthClient(self.0.0.clone())
133 }
134
135 pub fn gov_mode(&self) -> bool {
137 self.0.0.gov_mode()
138 }
139
140 pub fn policies(&self) -> policies::PoliciesClient {
142 use bitwarden_policies::PoliciesClientExt;
143 policies::PoliciesClient(self.0.0.policies())
144 }
145
146 pub fn echo(&self, msg: String) -> String {
148 msg
149 }
150
151 pub async fn http_get(&self, url: String) -> Result<String> {
153 let client = self.0.0.internal.get_http_client();
154 let res = client
155 .get(&url)
156 .send()
157 .await
158 .map_err(|e| Error::Api(e.into()))?;
159
160 res.text().await.map_err(|e| Error::Api(e.into()))
161 }
162}
163
164static INIT: Once = Once::new();
165
166#[derive(uniffi::Enum)]
168pub enum LogLevel {
169 Trace,
171 Debug,
173 Info,
175 Warn,
177 Error,
179}
180
181impl LogLevel {
182 fn as_str(&self) -> &'static str {
183 match self {
184 LogLevel::Trace => "trace",
185 LogLevel::Debug => "debug",
186 LogLevel::Info => "info",
187 LogLevel::Warn => "warn",
188 LogLevel::Error => "error",
189 }
190 }
191}
192
193#[uniffi::export]
217pub fn init_logger(callback: Option<Arc<dyn LogCallback>>, level: Option<LogLevel>) {
218 use tracing_subscriber::{EnvFilter, layer::SubscriberExt as _, util::SubscriberInitExt as _};
219
220 INIT.call_once(|| {
221 let level = level.as_ref().map(|l| l.as_str()).unwrap_or("info");
227 let filter = EnvFilter::builder()
228 .with_default_directive(
229 option_env!("RUST_LOG")
230 .unwrap_or(level)
231 .parse()
232 .expect("should provide valid log level at compile time."),
233 )
234 .from_env_lossy();
235
236 let fmtlayer = tracing_subscriber::fmt::layer()
237 .with_ansi(true)
238 .with_file(true)
239 .with_line_number(true)
240 .with_target(true)
241 .pretty();
242
243 let registry = tracing_subscriber::registry().with(fmtlayer).with(filter);
245
246 let callback_layer = callback.map(log_callback::CallbackLayer::new);
249 let registry = registry.with(callback_layer);
250 #[cfg(target_os = "ios")]
251 {
252 const TAG: &str = "com.8bit.bitwarden";
253 registry
254 .with(tracing_oslog::OsLogger::new(TAG, "default"))
255 .init();
256 }
257
258 #[cfg(target_os = "android")]
259 {
260 const TAG: &str = "com.bitwarden.sdk";
261 registry
262 .with(
263 tracing_android::layer(TAG)
264 .expect("initialization of android logcat tracing layer"),
265 )
266 .init();
267 }
268
269 #[cfg(not(any(target_os = "android", target_os = "ios")))]
270 {
271 registry.init();
272 }
273 #[cfg(feature = "dangerous-crypto-debug")]
274 tracing::warn!(
275 "Dangerous crypto debug features are enabled. THIS MUST NOT BE USED IN PRODUCTION BUILDS!!"
276 );
277 });
278}
279
280fn setup_error_converter() {
283 bitwarden_uniffi_error::set_error_to_uniffi_error(|e| {
284 crate::error::BitwardenError::Conversion(e.to_string()).into()
285 });
286}
287#[cfg(test)]
288mod tests {
289 use std::sync::Mutex;
290
291 use super::*;
292 #[derive(Debug)]
294 struct MockTokenProvider;
295
296 #[async_trait::async_trait]
297 impl ClientManagedTokens for MockTokenProvider {
298 async fn get_access_token(&self) -> Option<String> {
299 Some("mock_token".to_string())
300 }
301 }
302 struct TestLogCallback {
304 logs: Arc<Mutex<Vec<(String, String, String)>>>,
305 }
306 impl LogCallback for TestLogCallback {
307 fn on_log(&self, level: String, target: String, message: String) -> Result<()> {
308 self.logs
309 .lock()
310 .expect("Failed to lock logs mutex")
311 .push((level, target, message));
312 Ok(())
313 }
314 }
315
316 #[test]
321 fn test_callback_receives_logs() {
322 let logs = Arc::new(Mutex::new(Vec::new()));
323 let callback = Arc::new(TestLogCallback { logs: logs.clone() });
324
325 init_logger(Some(callback), None);
327
328 let _client = Client::new(Arc::new(MockTokenProvider), None);
330
331 tracing::info!("test message from SDK");
333
334 let captured = logs.lock().expect("Failed to lock logs mutex");
336 assert!(!captured.is_empty(), "Callback should receive logs");
337
338 let test_log = captured
340 .iter()
341 .find(|(_, _, msg)| msg.contains("test message"))
342 .expect("Should find our test log message");
343
344 assert_eq!(test_log.0, "INFO");
345 assert!(test_log.2.contains("test message"));
346 }
347}