From d4f2ae99d1f402eaa5a599a8bab2ee5d9a4431e9 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 8 Aug 2025 14:37:59 +0200 Subject: [PATCH 1/9] feat: add basic js-compatible iterator --- Cargo.lock | 1 + crates/bitwarden-wasm-internal/Cargo.toml | 1 + .../src/platform/iter.rs | 60 +++++++++++++++++++ .../src/platform/mod.rs | 5 ++ 4 files changed, 67 insertions(+) create mode 100644 crates/bitwarden-wasm-internal/src/platform/iter.rs diff --git a/Cargo.lock b/Cargo.lock index 04f97f266..bbede041d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -778,6 +778,7 @@ dependencies = [ "bitwarden-vault", "console_error_panic_hook", "console_log", + "js-sys", "log", "serde", "tsify", diff --git a/crates/bitwarden-wasm-internal/Cargo.toml b/crates/bitwarden-wasm-internal/Cargo.toml index a85384f6e..a70f71e13 100644 --- a/crates/bitwarden-wasm-internal/Cargo.toml +++ b/crates/bitwarden-wasm-internal/Cargo.toml @@ -31,6 +31,7 @@ bitwarden-threading = { workspace = true } bitwarden-vault = { workspace = true, features = ["wasm"] } console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } +js-sys = { workspace = true, optional = true } log = "0.4.20" serde = { workspace = true } tsify = { workspace = true } diff --git a/crates/bitwarden-wasm-internal/src/platform/iter.rs b/crates/bitwarden-wasm-internal/src/platform/iter.rs new file mode 100644 index 000000000..9b9ae16e6 --- /dev/null +++ b/crates/bitwarden-wasm-internal/src/platform/iter.rs @@ -0,0 +1,60 @@ +use std::str; + +use bitwarden_core::Client; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct IteratorClient(Client); + +impl IteratorClient { + pub fn new(client: Client) -> Self { + Self(client) + } +} + +#[wasm_bindgen] +impl IteratorClient { + pub fn state(&self) -> IteratorClient { + IteratorClient::new(self.0.clone()) + } + + pub fn create_js_iterator(&self) -> JsRustIterator { + to_js_iterator(get_test_iteration()) + } +} + +fn get_test_iteration() -> impl Iterator { + (0..10).map(|x| x * 2) +} + +#[wasm_bindgen] +/// An iterable that wraps a Rust iterator and can be used in JavaScript +pub struct JsRustIterator { + iter: Box>, + // next_value: Option, +} + +impl JsRustIterator { + pub fn new(iter: Box>) -> Self { + Self { iter } + } +} + +#[wasm_bindgen] +impl JsRustIterator { + pub fn next(&mut self) -> Option { + self.iter.next() + } +} + +// extern "C" { +// #[wasm_bindgen(js_name = "getTestIteration")] +// fn get_test_iteration_js() -> js_sys::Iterator; +// } + +pub fn to_js_iterator(iter: impl Iterator + 'static) -> JsRustIterator { + JsRustIterator::new(Box::new(iter)) +} + +// #[wasm_bindgen] +// pub struct PlatformClient(Client); diff --git a/crates/bitwarden-wasm-internal/src/platform/mod.rs b/crates/bitwarden-wasm-internal/src/platform/mod.rs index 7d7d83141..4df92bd7c 100644 --- a/crates/bitwarden-wasm-internal/src/platform/mod.rs +++ b/crates/bitwarden-wasm-internal/src/platform/mod.rs @@ -2,6 +2,7 @@ use bitwarden_core::{Client, Flags}; use bitwarden_vault::{Cipher, Folder}; use wasm_bindgen::prelude::*; +mod iter; mod repository; pub mod token_provider; @@ -20,6 +21,10 @@ impl PlatformClient { StateClient::new(self.0.clone()) } + pub fn iter(&self) -> iter::IteratorClient { + iter::IteratorClient::new(self.0.clone()) + } + /// Load feature flags into the client pub fn load_flags(&self, flags: Flags) -> Result<(), JsValue> { self.0.internal.set_flags(&flags); From 9449b6b68d4bae227f389f8a8a9ffc04ca8608e5 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Wed, 13 Aug 2025 11:21:36 +0200 Subject: [PATCH 2/9] wip --- crates/bitwarden-wasm-internal/src/platform/iter.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/bitwarden-wasm-internal/src/platform/iter.rs b/crates/bitwarden-wasm-internal/src/platform/iter.rs index 9b9ae16e6..25d5ae664 100644 --- a/crates/bitwarden-wasm-internal/src/platform/iter.rs +++ b/crates/bitwarden-wasm-internal/src/platform/iter.rs @@ -27,6 +27,11 @@ fn get_test_iteration() -> impl Iterator { (0..10).map(|x| x * 2) } +#[wasm_bindgen] +pub struct JsRustAsyncIterator { + iter: Box>, +} + #[wasm_bindgen] /// An iterable that wraps a Rust iterator and can be used in JavaScript pub struct JsRustIterator { From cfc980fe84399e4b31b5bdbd808f36585206362b Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 22 Aug 2025 10:41:31 +0200 Subject: [PATCH 3/9] feat: add js-compatible stream --- Cargo.lock | 1 + crates/bitwarden-wasm-internal/Cargo.toml | 1 + .../src/platform/iter.rs | 40 ++++++++++++++++--- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bbede041d..a09053ac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -778,6 +778,7 @@ dependencies = [ "bitwarden-vault", "console_error_panic_hook", "console_log", + "futures", "js-sys", "log", "serde", diff --git a/crates/bitwarden-wasm-internal/Cargo.toml b/crates/bitwarden-wasm-internal/Cargo.toml index a70f71e13..7e43f9a29 100644 --- a/crates/bitwarden-wasm-internal/Cargo.toml +++ b/crates/bitwarden-wasm-internal/Cargo.toml @@ -31,6 +31,7 @@ bitwarden-threading = { workspace = true } bitwarden-vault = { workspace = true, features = ["wasm"] } console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } +futures = "0.3.31" js-sys = { workspace = true, optional = true } log = "0.4.20" serde = { workspace = true } diff --git a/crates/bitwarden-wasm-internal/src/platform/iter.rs b/crates/bitwarden-wasm-internal/src/platform/iter.rs index 25d5ae664..eb4aa328d 100644 --- a/crates/bitwarden-wasm-internal/src/platform/iter.rs +++ b/crates/bitwarden-wasm-internal/src/platform/iter.rs @@ -1,6 +1,8 @@ -use std::str; +use std::{pin::Pin, time::Duration}; use bitwarden_core::Client; +use bitwarden_threading::time::sleep; +use futures::{stream, StreamExt}; use wasm_bindgen::prelude::*; #[wasm_bindgen] @@ -21,15 +23,41 @@ impl IteratorClient { pub fn create_js_iterator(&self) -> JsRustIterator { to_js_iterator(get_test_iteration()) } + + pub fn create_js_async_iterator(&self) -> JsRustAsyncIterator { + JsRustAsyncIterator::new( + stream::iter(get_test_iteration()).then(|x| async move { async_operation(x).await }), + ) + } } fn get_test_iteration() -> impl Iterator { (0..10).map(|x| x * 2) } +async fn async_operation(input: i32) -> i32 { + sleep(Duration::from_millis(500)).await; + input * 2 +} + #[wasm_bindgen] pub struct JsRustAsyncIterator { - iter: Box>, + iter: Pin>>, +} + +impl JsRustAsyncIterator { + pub fn new(iter: impl futures::stream::Stream + 'static) -> Self { + Self { + iter: Box::pin(iter), + } + } +} + +#[wasm_bindgen] +impl JsRustAsyncIterator { + pub async fn next(&mut self) -> Option { + self.iter.next().await + } } #[wasm_bindgen] @@ -40,8 +68,10 @@ pub struct JsRustIterator { } impl JsRustIterator { - pub fn new(iter: Box>) -> Self { - Self { iter } + pub fn new(iter: impl Iterator + 'static) -> Self { + Self { + iter: Box::new(iter), + } } } @@ -58,7 +88,7 @@ impl JsRustIterator { // } pub fn to_js_iterator(iter: impl Iterator + 'static) -> JsRustIterator { - JsRustIterator::new(Box::new(iter)) + JsRustIterator::new(iter) } // #[wasm_bindgen] From 085f8b8c49b7b8e1e0b998353259a360d76071eb Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 22 Aug 2025 10:52:10 +0200 Subject: [PATCH 4/9] feat: add iter crate. --- Cargo.lock | 8 ++++++++ Cargo.toml | 1 + crates/bitwarden-iter/.cargo/config | 2 ++ crates/bitwarden-iter/Cargo.toml | 25 +++++++++++++++++++++++++ crates/bitwarden-iter/README.md | 3 +++ crates/bitwarden-iter/src/lib.rs | 1 + 6 files changed, 40 insertions(+) create mode 100644 crates/bitwarden-iter/.cargo/config create mode 100644 crates/bitwarden-iter/Cargo.toml create mode 100644 crates/bitwarden-iter/README.md create mode 100644 crates/bitwarden-iter/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a09053ac7..35e3469ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -582,6 +582,14 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "bitwarden-iter" +version = "1.0.0" +dependencies = [ + "wasm-bindgen", + "wasm-bindgen-futures", +] + [[package]] name = "bitwarden-send" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 778a91e39..242f4c211 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ bitwarden-exporters = { path = "crates/bitwarden-exporters", version = "=1.0.0" bitwarden-fido = { path = "crates/bitwarden-fido", version = "=1.0.0" } bitwarden-generators = { path = "crates/bitwarden-generators", version = "=1.0.0" } bitwarden-ipc = { path = "crates/bitwarden-ipc", version = "=1.0.0" } +bitwarden-iter = { path = "crates/bitwarden-iter", version = "=1.0.0" } bitwarden-send = { path = "crates/bitwarden-send", version = "=1.0.0" } bitwarden-sm = { path = "bitwarden_license/bitwarden-sm", version = "=1.0.0" } bitwarden-ssh = { path = "crates/bitwarden-ssh", version = "=1.0.0" } diff --git a/crates/bitwarden-iter/.cargo/config b/crates/bitwarden-iter/.cargo/config new file mode 100644 index 000000000..4ec2f3b86 --- /dev/null +++ b/crates/bitwarden-iter/.cargo/config @@ -0,0 +1,2 @@ +[target.wasm32-unknown-unknown] +runner = 'wasm-bindgen-test-runner' diff --git a/crates/bitwarden-iter/Cargo.toml b/crates/bitwarden-iter/Cargo.toml new file mode 100644 index 000000000..74dff7cfd --- /dev/null +++ b/crates/bitwarden-iter/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "bitwarden-iter" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true +homepage.workspace = true +repository.workspace = true +license-file.workspace = true +keywords.workspace = true + +[package.metadata.cargo-udeps.ignore] +development = ["tokio-test"] # only used in doc-tests + +[features] +wasm = ["dep:wasm-bindgen", "dep:wasm-bindgen-futures"] + +[dependencies] +wasm-bindgen = { workspace = true, optional = true } + +[target.'cfg(target_arch="wasm32")'.dependencies] +wasm-bindgen-futures = { workspace = true, optional = true } + +[lints] +workspace = true diff --git a/crates/bitwarden-iter/README.md b/crates/bitwarden-iter/README.md new file mode 100644 index 000000000..44394dd1c --- /dev/null +++ b/crates/bitwarden-iter/README.md @@ -0,0 +1,3 @@ +# bitwarden-iter + +Utility crate for providing streamed calculation using iterators over Bitwarden data structures. diff --git a/crates/bitwarden-iter/src/lib.rs b/crates/bitwarden-iter/src/lib.rs new file mode 100644 index 000000000..7913c79d6 --- /dev/null +++ b/crates/bitwarden-iter/src/lib.rs @@ -0,0 +1 @@ +#![doc = include_str!("../README.md")] From 7121dc51e325696172cca98af8ece867b9514acf Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 22 Aug 2025 10:57:45 +0200 Subject: [PATCH 5/9] feat: create new iter crate --- Cargo.lock | 1 + .../src/platform => bitwarden-iter/src}/iter.rs | 0 crates/bitwarden-iter/src/lib.rs | 2 ++ crates/bitwarden-iter/src/wasm.rs | 0 crates/bitwarden-wasm-internal/Cargo.toml | 1 + crates/bitwarden-wasm-internal/src/lib.rs | 1 + crates/bitwarden-wasm-internal/src/platform/mod.rs | 5 ----- 7 files changed, 5 insertions(+), 5 deletions(-) rename crates/{bitwarden-wasm-internal/src/platform => bitwarden-iter/src}/iter.rs (100%) create mode 100644 crates/bitwarden-iter/src/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index 35e3469ed..3318ec7bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -780,6 +780,7 @@ dependencies = [ "bitwarden-exporters", "bitwarden-generators", "bitwarden-ipc", + "bitwarden-iter", "bitwarden-ssh", "bitwarden-state", "bitwarden-threading", diff --git a/crates/bitwarden-wasm-internal/src/platform/iter.rs b/crates/bitwarden-iter/src/iter.rs similarity index 100% rename from crates/bitwarden-wasm-internal/src/platform/iter.rs rename to crates/bitwarden-iter/src/iter.rs diff --git a/crates/bitwarden-iter/src/lib.rs b/crates/bitwarden-iter/src/lib.rs index 7913c79d6..8292715df 100644 --- a/crates/bitwarden-iter/src/lib.rs +++ b/crates/bitwarden-iter/src/lib.rs @@ -1 +1,3 @@ #![doc = include_str!("../README.md")] + +pub mod wasm; diff --git a/crates/bitwarden-iter/src/wasm.rs b/crates/bitwarden-iter/src/wasm.rs new file mode 100644 index 000000000..e69de29bb diff --git a/crates/bitwarden-wasm-internal/Cargo.toml b/crates/bitwarden-wasm-internal/Cargo.toml index 7e43f9a29..f3bb703dd 100644 --- a/crates/bitwarden-wasm-internal/Cargo.toml +++ b/crates/bitwarden-wasm-internal/Cargo.toml @@ -25,6 +25,7 @@ bitwarden-error = { workspace = true } bitwarden-exporters = { workspace = true, features = ["wasm"] } bitwarden-generators = { workspace = true, features = ["wasm"] } bitwarden-ipc = { workspace = true, features = ["wasm"] } +bitwarden-iter = { workspace = true, features = ["wasm"] } bitwarden-ssh = { workspace = true, features = ["wasm"] } bitwarden-state = { workspace = true, features = ["wasm"] } bitwarden-threading = { workspace = true } diff --git a/crates/bitwarden-wasm-internal/src/lib.rs b/crates/bitwarden-wasm-internal/src/lib.rs index 253ec8ffd..8995c28d3 100644 --- a/crates/bitwarden-wasm-internal/src/lib.rs +++ b/crates/bitwarden-wasm-internal/src/lib.rs @@ -8,5 +8,6 @@ mod pure_crypto; mod ssh; pub use bitwarden_ipc::wasm::*; +pub use bitwarden_iter::wasm::*; pub use client::BitwardenClient; pub use init::init_sdk; diff --git a/crates/bitwarden-wasm-internal/src/platform/mod.rs b/crates/bitwarden-wasm-internal/src/platform/mod.rs index 4df92bd7c..7d7d83141 100644 --- a/crates/bitwarden-wasm-internal/src/platform/mod.rs +++ b/crates/bitwarden-wasm-internal/src/platform/mod.rs @@ -2,7 +2,6 @@ use bitwarden_core::{Client, Flags}; use bitwarden_vault::{Cipher, Folder}; use wasm_bindgen::prelude::*; -mod iter; mod repository; pub mod token_provider; @@ -21,10 +20,6 @@ impl PlatformClient { StateClient::new(self.0.clone()) } - pub fn iter(&self) -> iter::IteratorClient { - iter::IteratorClient::new(self.0.clone()) - } - /// Load feature flags into the client pub fn load_flags(&self, flags: Flags) -> Result<(), JsValue> { self.0.internal.set_flags(&flags); From 472670b62f723b76479fc40d147e7fee51328c2c Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 22 Aug 2025 11:43:32 +0200 Subject: [PATCH 6/9] fix: some compiler warnings --- Cargo.lock | 2 +- crates/bitwarden-iter/Cargo.toml | 2 + crates/bitwarden-iter/src/iter.rs | 94 ----------------------- crates/bitwarden-iter/src/wasm.rs | 85 ++++++++++++++++++++ crates/bitwarden-wasm-internal/Cargo.toml | 1 - 5 files changed, 88 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3318ec7bd..0db4f8347 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -586,6 +586,7 @@ dependencies = [ name = "bitwarden-iter" version = "1.0.0" dependencies = [ + "futures", "wasm-bindgen", "wasm-bindgen-futures", ] @@ -787,7 +788,6 @@ dependencies = [ "bitwarden-vault", "console_error_panic_hook", "console_log", - "futures", "js-sys", "log", "serde", diff --git a/crates/bitwarden-iter/Cargo.toml b/crates/bitwarden-iter/Cargo.toml index 74dff7cfd..85067f9f4 100644 --- a/crates/bitwarden-iter/Cargo.toml +++ b/crates/bitwarden-iter/Cargo.toml @@ -16,7 +16,9 @@ development = ["tokio-test"] # only used in doc-tests wasm = ["dep:wasm-bindgen", "dep:wasm-bindgen-futures"] [dependencies] +futures = "0.3.31" wasm-bindgen = { workspace = true, optional = true } +wasm-bindgen-futures = { workspace = true, optional = true } [target.'cfg(target_arch="wasm32")'.dependencies] wasm-bindgen-futures = { workspace = true, optional = true } diff --git a/crates/bitwarden-iter/src/iter.rs b/crates/bitwarden-iter/src/iter.rs index eb4aa328d..8b1378917 100644 --- a/crates/bitwarden-iter/src/iter.rs +++ b/crates/bitwarden-iter/src/iter.rs @@ -1,95 +1 @@ -use std::{pin::Pin, time::Duration}; -use bitwarden_core::Client; -use bitwarden_threading::time::sleep; -use futures::{stream, StreamExt}; -use wasm_bindgen::prelude::*; - -#[wasm_bindgen] -pub struct IteratorClient(Client); - -impl IteratorClient { - pub fn new(client: Client) -> Self { - Self(client) - } -} - -#[wasm_bindgen] -impl IteratorClient { - pub fn state(&self) -> IteratorClient { - IteratorClient::new(self.0.clone()) - } - - pub fn create_js_iterator(&self) -> JsRustIterator { - to_js_iterator(get_test_iteration()) - } - - pub fn create_js_async_iterator(&self) -> JsRustAsyncIterator { - JsRustAsyncIterator::new( - stream::iter(get_test_iteration()).then(|x| async move { async_operation(x).await }), - ) - } -} - -fn get_test_iteration() -> impl Iterator { - (0..10).map(|x| x * 2) -} - -async fn async_operation(input: i32) -> i32 { - sleep(Duration::from_millis(500)).await; - input * 2 -} - -#[wasm_bindgen] -pub struct JsRustAsyncIterator { - iter: Pin>>, -} - -impl JsRustAsyncIterator { - pub fn new(iter: impl futures::stream::Stream + 'static) -> Self { - Self { - iter: Box::pin(iter), - } - } -} - -#[wasm_bindgen] -impl JsRustAsyncIterator { - pub async fn next(&mut self) -> Option { - self.iter.next().await - } -} - -#[wasm_bindgen] -/// An iterable that wraps a Rust iterator and can be used in JavaScript -pub struct JsRustIterator { - iter: Box>, - // next_value: Option, -} - -impl JsRustIterator { - pub fn new(iter: impl Iterator + 'static) -> Self { - Self { - iter: Box::new(iter), - } - } -} - -#[wasm_bindgen] -impl JsRustIterator { - pub fn next(&mut self) -> Option { - self.iter.next() - } -} - -// extern "C" { -// #[wasm_bindgen(js_name = "getTestIteration")] -// fn get_test_iteration_js() -> js_sys::Iterator; -// } - -pub fn to_js_iterator(iter: impl Iterator + 'static) -> JsRustIterator { - JsRustIterator::new(iter) -} - -// #[wasm_bindgen] -// pub struct PlatformClient(Client); diff --git a/crates/bitwarden-iter/src/wasm.rs b/crates/bitwarden-iter/src/wasm.rs index e69de29bb..9dd4230c9 100644 --- a/crates/bitwarden-iter/src/wasm.rs +++ b/crates/bitwarden-iter/src/wasm.rs @@ -0,0 +1,85 @@ +#![allow(missing_docs)] + +use std::pin::Pin; + +use futures::StreamExt; +use wasm_bindgen::prelude::*; + +// #[wasm_bindgen] +// impl IteratorClient { +// pub fn state(&self) -> IteratorClient { +// IteratorClient::new(self.0.clone()) +// } + +// pub fn create_js_iterator(&self) -> JsRustIterator { +// to_js_iterator(get_test_iteration()) +// } + +// pub fn create_js_async_iterator(&self) -> JsRustAsyncIterator { +// JsRustAsyncIterator::new( +// stream::iter(get_test_iteration()).then(|x| async move { async_operation(x).await }), +// ) +// } +// } + +// fn get_test_iteration() -> impl Iterator { +// (0..10).map(|x| x * 2) +// } + +// async fn async_operation(input: i32) -> i32 { +// input * 2 +// } + +#[wasm_bindgen] +pub struct JsRustAsyncIterator { + iter: Pin>>, +} + +impl JsRustAsyncIterator { + pub fn new(iter: impl futures::stream::Stream + 'static) -> Self { + Self { + iter: Box::pin(iter), + } + } +} + +#[wasm_bindgen] +impl JsRustAsyncIterator { + pub async fn next(&mut self) -> Option { + self.iter.next().await + } +} + +#[wasm_bindgen] +/// An iterable that wraps a Rust iterator and can be used in JavaScript +pub struct JsRustIterator { + iter: Box>, + // next_value: Option, +} + +impl JsRustIterator { + pub fn new(iter: impl Iterator + 'static) -> Self { + Self { + iter: Box::new(iter), + } + } +} + +#[wasm_bindgen] +impl JsRustIterator { + pub fn next(&mut self) -> Option { + self.iter.next() + } +} + +// extern "C" { +// #[wasm_bindgen(js_name = "getTestIteration")] +// fn get_test_iteration_js() -> js_sys::Iterator; +// } + +pub fn to_js_iterator(iter: impl Iterator + 'static) -> JsRustIterator { + JsRustIterator::new(iter) +} + +// #[wasm_bindgen] +// pub struct PlatformClient(Client); diff --git a/crates/bitwarden-wasm-internal/Cargo.toml b/crates/bitwarden-wasm-internal/Cargo.toml index f3bb703dd..189759f47 100644 --- a/crates/bitwarden-wasm-internal/Cargo.toml +++ b/crates/bitwarden-wasm-internal/Cargo.toml @@ -32,7 +32,6 @@ bitwarden-threading = { workspace = true } bitwarden-vault = { workspace = true, features = ["wasm"] } console_error_panic_hook = "0.1.7" console_log = { version = "1.0.0", features = ["color"] } -futures = "0.3.31" js-sys = { workspace = true, optional = true } log = "0.4.20" serde = { workspace = true } From 93fe34bf47967e4f5465466a1e956c745e1535d0 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 22 Aug 2025 12:19:59 +0200 Subject: [PATCH 7/9] feat: add cipher view decryption iterator --- Cargo.lock | 1 + crates/bitwarden-iter/src/iter.rs | 26 ++++++++++++++++ crates/bitwarden-iter/src/lib.rs | 4 +++ crates/bitwarden-iter/src/wasm.rs | 5 +++ crates/bitwarden-vault/Cargo.toml | 6 ++-- .../src/cipher/cipher_client.rs | 31 +++++++++++++++++++ crates/bitwarden-vault/src/cipher/mod.rs | 2 +- 7 files changed, 72 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0db4f8347..94321b69d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -746,6 +746,7 @@ dependencies = [ "bitwarden-core", "bitwarden-crypto", "bitwarden-error", + "bitwarden-iter", "bitwarden-state", "bitwarden-test", "chrono", diff --git a/crates/bitwarden-iter/src/iter.rs b/crates/bitwarden-iter/src/iter.rs index 8b1378917..9463c13b5 100644 --- a/crates/bitwarden-iter/src/iter.rs +++ b/crates/bitwarden-iter/src/iter.rs @@ -1 +1,27 @@ +#![allow(missing_docs)] +use std::pin::Pin; + +pub struct BwIterator { + pub iter: Box>, +} + +impl BwIterator { + pub fn new(iter: impl Iterator + 'static) -> Self { + Self { + iter: Box::new(iter), + } + } +} + +pub struct BwStream { + pub stream: Pin>>, +} + +impl BwStream { + pub fn new(stream: impl futures::stream::Stream + 'static) -> Self { + Self { + stream: Box::pin(stream), + } + } +} diff --git a/crates/bitwarden-iter/src/lib.rs b/crates/bitwarden-iter/src/lib.rs index 8292715df..aae79c993 100644 --- a/crates/bitwarden-iter/src/lib.rs +++ b/crates/bitwarden-iter/src/lib.rs @@ -1,3 +1,7 @@ #![doc = include_str!("../README.md")] +mod iter; + pub mod wasm; + +pub use iter::{BwIterator, BwStream}; diff --git a/crates/bitwarden-iter/src/wasm.rs b/crates/bitwarden-iter/src/wasm.rs index 9dd4230c9..8fa0e6853 100644 --- a/crates/bitwarden-iter/src/wasm.rs +++ b/crates/bitwarden-iter/src/wasm.rs @@ -30,6 +30,11 @@ use wasm_bindgen::prelude::*; // input * 2 // } +// #[wasm_bindgen] +// pub fn into_iterable>() { + +// } + #[wasm_bindgen] pub struct JsRustAsyncIterator { iter: Pin>>, diff --git a/crates/bitwarden-vault/Cargo.toml b/crates/bitwarden-vault/Cargo.toml index 5ceab6aaa..ac99e728e 100644 --- a/crates/bitwarden-vault/Cargo.toml +++ b/crates/bitwarden-vault/Cargo.toml @@ -18,13 +18,14 @@ keywords.workspace = true uniffi = [ "bitwarden-core/uniffi", "bitwarden-crypto/uniffi", - "dep:uniffi" + "dep:uniffi", ] # Uniffi bindings wasm = [ "bitwarden-core/wasm", + "bitwarden-iter/wasm", "dep:tsify", "dep:wasm-bindgen", - "dep:wasm-bindgen-futures" + "dep:wasm-bindgen-futures", ] # WASM support [dependencies] @@ -34,6 +35,7 @@ bitwarden-collections = { workspace = true, features = ["wasm"] } bitwarden-core = { workspace = true, features = ["internal"] } bitwarden-crypto = { workspace = true } bitwarden-error = { workspace = true } +bitwarden-iter = { workspace = true } bitwarden-state = { workspace = true } chrono = { workspace = true } data-encoding = ">=2.0, <3" diff --git a/crates/bitwarden-vault/src/cipher/cipher_client.rs b/crates/bitwarden-vault/src/cipher/cipher_client.rs index 45e9d846a..d892594e7 100644 --- a/crates/bitwarden-vault/src/cipher/cipher_client.rs +++ b/crates/bitwarden-vault/src/cipher/cipher_client.rs @@ -1,5 +1,6 @@ use bitwarden_core::{key_management::SymmetricKeyId, Client, OrganizationId}; use bitwarden_crypto::{CompositeEncryptable, IdentifyKey, SymmetricCryptoKey}; +use bitwarden_iter::BwIterator; #[cfg(feature = "wasm")] use wasm_bindgen::prelude::*; @@ -15,6 +16,26 @@ pub struct CiphersClient { pub(crate) client: Client, } +#[cfg_attr(feature = "wasm", wasm_bindgen)] +#[allow(missing_docs)] +pub struct CipherIterator(BwIterator); + +impl Into> for CipherIterator { + fn into(self) -> BwIterator { + self.0 + } +} + +#[cfg_attr(feature = "wasm", wasm_bindgen)] +#[allow(missing_docs)] +pub struct CipherViewIterator(BwIterator>); + +impl Into>> for CipherViewIterator { + fn into(self) -> BwIterator> { + self.0 + } +} + #[cfg_attr(feature = "wasm", wasm_bindgen)] impl CiphersClient { #[allow(missing_docs)] @@ -104,6 +125,16 @@ impl CiphersClient { Ok(cipher_view) } + #[allow(missing_docs)] + pub fn decrypt_stream(&self, cipher_iter: CipherIterator) -> CipherViewIterator { + let client = self.client.clone(); + let iter = cipher_iter.0.iter.map(move |cipher| { + let key_store = client.internal.get_key_store(); + key_store.decrypt(&cipher).map_err(DecryptError::from) + }); + CipherViewIterator(BwIterator::new(iter)) + } + #[allow(missing_docs)] pub fn decrypt_list(&self, ciphers: Vec) -> Result, DecryptError> { let key_store = self.client.internal.get_key_store(); diff --git a/crates/bitwarden-vault/src/cipher/mod.rs b/crates/bitwarden-vault/src/cipher/mod.rs index ee36cc1b7..e01c94df4 100644 --- a/crates/bitwarden-vault/src/cipher/mod.rs +++ b/crates/bitwarden-vault/src/cipher/mod.rs @@ -22,7 +22,7 @@ pub use cipher::{ Cipher, CipherError, CipherListView, CipherListViewType, CipherRepromptType, CipherType, CipherView, DecryptCipherListResult, EncryptionContext, }; -pub use cipher_client::CiphersClient; +pub use cipher_client::{CipherIterator, CipherViewIterator, CiphersClient}; pub use field::{FieldType, FieldView}; pub use identity::IdentityView; pub use login::{ From 76df69253becb821dc606f4a45725805189c9da0 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 22 Aug 2025 13:35:58 +0200 Subject: [PATCH 8/9] feat: add streamabe autofill credentials --- Cargo.lock | 5 +++ crates/bitwarden-fido/Cargo.toml | 6 +++ crates/bitwarden-fido/src/authenticator.rs | 30 +++++++++++++- crates/bitwarden-fido/src/lib.rs | 4 ++ crates/bitwarden-fido/src/traits.rs | 6 ++- crates/bitwarden-fido/src/types.rs | 3 ++ crates/bitwarden-fido/src/wasm.rs | 41 +++++++++++++++++++ crates/bitwarden-iter/src/iter.rs | 9 ++++ crates/bitwarden-uniffi/src/platform/fido2.rs | 10 ++++- .../src/cipher/cipher_client.rs | 38 +++++++++++++++++ crates/bitwarden-vault/src/cipher/mod.rs | 4 +- 11 files changed, 152 insertions(+), 4 deletions(-) create mode 100644 crates/bitwarden-fido/src/wasm.rs diff --git a/Cargo.lock b/Cargo.lock index 94321b69d..74a0969fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -525,6 +525,8 @@ dependencies = [ "base64", "bitwarden-core", "bitwarden-crypto", + "bitwarden-error", + "bitwarden-iter", "bitwarden-vault", "chrono", "coset", @@ -537,8 +539,11 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.12", + "tsify", "uniffi", "uuid", + "wasm-bindgen", + "wasm-bindgen-futures", ] [[package]] diff --git a/crates/bitwarden-fido/Cargo.toml b/crates/bitwarden-fido/Cargo.toml index 0f880a945..e2d3c80d7 100644 --- a/crates/bitwarden-fido/Cargo.toml +++ b/crates/bitwarden-fido/Cargo.toml @@ -16,12 +16,15 @@ keywords.workspace = true [features] uniffi = ["dep:uniffi", "bitwarden-core/uniffi", "bitwarden-vault/uniffi"] +wasm = ["dep:wasm-bindgen"] [dependencies] async-trait = { workspace = true } base64 = ">=0.22.1, <0.23" bitwarden-core = { workspace = true } bitwarden-crypto = { workspace = true } +bitwarden-error = { workspace = true } +bitwarden-iter = { workspace = true } bitwarden-vault = { workspace = true } chrono = { workspace = true } coset = ">=0.3.7, <0.4" @@ -38,6 +41,9 @@ serde_json = { workspace = true } thiserror = { workspace = true } uniffi = { workspace = true, optional = true } uuid = { workspace = true } +tsify = { workspace = true, optional = true } +wasm-bindgen = { workspace = true, optional = true } +wasm-bindgen-futures = { workspace = true, optional = true } [lints] workspace = true diff --git a/crates/bitwarden-fido/src/authenticator.rs b/crates/bitwarden-fido/src/authenticator.rs index 8216bb16b..2fba5b7f2 100644 --- a/crates/bitwarden-fido/src/authenticator.rs +++ b/crates/bitwarden-fido/src/authenticator.rs @@ -2,7 +2,9 @@ use std::sync::Mutex; use bitwarden_core::{Client, VaultLockedError}; use bitwarden_crypto::CryptoError; -use bitwarden_vault::{CipherError, CipherView, EncryptionContext}; +use bitwarden_error::bitwarden_error; +use bitwarden_iter::BwIterator; +use bitwarden_vault::{CipherError, CipherView, DecryptError, EncryptionContext}; use itertools::Itertools; use log::error; use passkey::{ @@ -84,8 +86,11 @@ pub enum SilentlyDiscoverCredentialsError { } #[allow(missing_docs)] +#[bitwarden_error(flat)] #[derive(Debug, Error)] pub enum CredentialsForAutofillError { + #[error(transparent)] + DecryptError(#[from] DecryptError), #[error(transparent)] CipherError(#[from] CipherError), #[error(transparent)] @@ -291,6 +296,29 @@ impl<'a> Fido2Authenticator<'a> { .collect() } + #[allow(missing_docs)] + pub async fn credentials_for_autofill_stream( + &mut self, + ) -> Result< + BwIterator>, + CredentialsForAutofillError, + > { + let all_credentials = self.credential_store.all_credentials_stream().await?; + + let iter = all_credentials + .into_iter() + .map( + |cipher| -> Result, CredentialsForAutofillError> { + Ok(Fido2CredentialAutofillView::from_cipher_list_view( + &cipher?, + )?) + }, + ) + .flatten_ok(); + + Ok(BwIterator::new(iter)) + } + pub(super) fn get_authenticator( &self, create_credential: bool, diff --git a/crates/bitwarden-fido/src/lib.rs b/crates/bitwarden-fido/src/lib.rs index 79a431daa..80bdc00df 100644 --- a/crates/bitwarden-fido/src/lib.rs +++ b/crates/bitwarden-fido/src/lib.rs @@ -20,6 +20,10 @@ mod client_fido; mod crypto; mod traits; mod types; + +#[cfg(feature = "wasm")] +pub mod wasm; + pub use authenticator::{ CredentialsForAutofillError, Fido2Authenticator, GetAssertionError, MakeCredentialError, SilentlyDiscoverCredentialsError, diff --git a/crates/bitwarden-fido/src/traits.rs b/crates/bitwarden-fido/src/traits.rs index 7a5e5d071..4e1206c29 100644 --- a/crates/bitwarden-fido/src/traits.rs +++ b/crates/bitwarden-fido/src/traits.rs @@ -1,4 +1,6 @@ -use bitwarden_vault::{CipherListView, CipherView, EncryptionContext, Fido2CredentialNewView}; +use bitwarden_vault::{ + CipherListView, CipherListViewIterator, CipherView, EncryptionContext, Fido2CredentialNewView, +}; use passkey::authenticator::UIHint; use thiserror::Error; @@ -47,6 +49,8 @@ pub trait Fido2CredentialStore: Send + Sync { async fn all_credentials(&self) -> Result, Fido2CallbackError>; async fn save_credential(&self, cred: EncryptionContext) -> Result<(), Fido2CallbackError>; + + async fn all_credentials_stream(&self) -> Result; } #[allow(missing_docs)] diff --git a/crates/bitwarden-fido/src/types.rs b/crates/bitwarden-fido/src/types.rs index 5d69e6436..c320b7a07 100644 --- a/crates/bitwarden-fido/src/types.rs +++ b/crates/bitwarden-fido/src/types.rs @@ -8,6 +8,8 @@ use passkey::types::webauthn::UserVerificationRequirement; use reqwest::Url; use serde::{Deserialize, Serialize}; use thiserror::Error; +#[cfg(feature = "wasm")] +use tsify::Tsify; use super::{ get_enum_from_string_name, string_to_guid_bytes, InvalidGuid, SelectedCredential, UnknownEnum, @@ -18,6 +20,7 @@ use super::{ #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase", deny_unknown_fields)] #[cfg_attr(feature = "uniffi", derive(uniffi::Record))] +#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))] pub struct Fido2CredentialAutofillView { pub credential_id: Vec, pub cipher_id: uuid::Uuid, diff --git a/crates/bitwarden-fido/src/wasm.rs b/crates/bitwarden-fido/src/wasm.rs new file mode 100644 index 000000000..87575d63b --- /dev/null +++ b/crates/bitwarden-fido/src/wasm.rs @@ -0,0 +1,41 @@ +use bitwarden_iter::BwIterator; +use bitwarden_vault::CipherListViewIterator; +use itertools::Itertools; +use wasm_bindgen::prelude::*; + +use crate::{CredentialsForAutofillError, Fido2CredentialAutofillView}; + +#[wasm_bindgen] +#[allow(missing_docs)] +pub struct Fido2CredentialAutofillViewIterator( + BwIterator>, +); + +#[wasm_bindgen] +impl Fido2CredentialAutofillViewIterator { + #[allow(missing_docs)] + pub fn next(&mut self) -> Option { + // Simplify the return because wasm_bindgen doesn't like Option> and I'm just + // proving the concept + self.0.iter.next().transpose().ok().flatten() + } +} + +#[wasm_bindgen] +#[allow(missing_docs)] +pub async fn credentials_for_autofill_stream( + all_credentials: CipherListViewIterator, +) -> Result { + let iter = all_credentials + .into_iter() + .map( + |cipher| -> Result, CredentialsForAutofillError> { + Ok(Fido2CredentialAutofillView::from_cipher_list_view( + &cipher?, + )?) + }, + ) + .flatten_ok(); + + Ok(Fido2CredentialAutofillViewIterator(BwIterator::new(iter))) +} diff --git a/crates/bitwarden-iter/src/iter.rs b/crates/bitwarden-iter/src/iter.rs index 9463c13b5..813e4736f 100644 --- a/crates/bitwarden-iter/src/iter.rs +++ b/crates/bitwarden-iter/src/iter.rs @@ -14,6 +14,15 @@ impl BwIterator { } } +impl IntoIterator for BwIterator { + type Item = T; + type IntoIter = Box>; + + fn into_iter(self) -> Self::IntoIter { + self.iter + } +} + pub struct BwStream { pub stream: Pin>>, } diff --git a/crates/bitwarden-uniffi/src/platform/fido2.rs b/crates/bitwarden-uniffi/src/platform/fido2.rs index 812d3ae6c..ee8c37ebe 100644 --- a/crates/bitwarden-uniffi/src/platform/fido2.rs +++ b/crates/bitwarden-uniffi/src/platform/fido2.rs @@ -7,7 +7,9 @@ use bitwarden_fido::{ PublicKeyCredentialAuthenticatorAttestationResponse, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, }; -use bitwarden_vault::{CipherListView, CipherView, EncryptionContext, Fido2CredentialNewView}; +use bitwarden_vault::{ + CipherListView, CipherListViewIterator, CipherView, EncryptionContext, Fido2CredentialNewView, +}; use crate::error::{Error, Result}; @@ -274,6 +276,12 @@ impl bitwarden_fido::Fido2CredentialStore for UniffiTraitBridge<&dyn Fido2Creden async fn save_credential(&self, cred: EncryptionContext) -> Result<(), BitFido2CallbackError> { self.0.save_credential(cred).await.map_err(Into::into) } + + async fn all_credentials_stream( + &self, + ) -> Result { + unimplemented!() + } } // Uniffi seems to have trouble generating code for Android when a local trait returns a type from diff --git a/crates/bitwarden-vault/src/cipher/cipher_client.rs b/crates/bitwarden-vault/src/cipher/cipher_client.rs index d892594e7..00bd0133d 100644 --- a/crates/bitwarden-vault/src/cipher/cipher_client.rs +++ b/crates/bitwarden-vault/src/cipher/cipher_client.rs @@ -36,6 +36,34 @@ impl Into>> for CipherViewIterator { } } +impl IntoIterator for CipherViewIterator { + type Item = Result; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +#[cfg_attr(feature = "wasm", wasm_bindgen)] +#[allow(missing_docs)] +pub struct CipherListViewIterator(BwIterator>); + +impl Into>> for CipherListViewIterator { + fn into(self) -> BwIterator> { + self.0 + } +} + +impl IntoIterator for CipherListViewIterator { + type Item = Result; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + #[cfg_attr(feature = "wasm", wasm_bindgen)] impl CiphersClient { #[allow(missing_docs)] @@ -142,6 +170,16 @@ impl CiphersClient { Ok(cipher_views) } + #[allow(missing_docs)] + pub fn decrypt_list_stream(&self, cipher_iter: CipherIterator) -> CipherListViewIterator { + let client = self.client.clone(); + let iter = cipher_iter.0.iter.map(move |cipher| { + let key_store = client.internal.get_key_store(); + key_store.decrypt(&cipher).map_err(DecryptError::from) + }); + CipherListViewIterator(BwIterator::new(iter)) + } + /// Decrypt cipher list with failures /// Returns both successfully decrypted ciphers and any that failed to decrypt pub fn decrypt_list_with_failures(&self, ciphers: Vec) -> DecryptCipherListResult { diff --git a/crates/bitwarden-vault/src/cipher/mod.rs b/crates/bitwarden-vault/src/cipher/mod.rs index e01c94df4..29fd5230c 100644 --- a/crates/bitwarden-vault/src/cipher/mod.rs +++ b/crates/bitwarden-vault/src/cipher/mod.rs @@ -22,7 +22,9 @@ pub use cipher::{ Cipher, CipherError, CipherListView, CipherListViewType, CipherRepromptType, CipherType, CipherView, DecryptCipherListResult, EncryptionContext, }; -pub use cipher_client::{CipherIterator, CipherViewIterator, CiphersClient}; +pub use cipher_client::{ + CipherIterator, CipherListViewIterator, CipherViewIterator, CiphersClient, +}; pub use field::{FieldType, FieldView}; pub use identity::IdentityView; pub use login::{ From 4ce38fe23830dcc75f9594b03fe51bbecbf65ef8 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Fri, 22 Aug 2025 13:38:31 +0200 Subject: [PATCH 9/9] feat: expose fido wasm --- Cargo.lock | 1 + crates/bitwarden-fido/Cargo.toml | 7 ++++++- crates/bitwarden-fido/src/lib.rs | 1 + crates/bitwarden-wasm-internal/Cargo.toml | 1 + crates/bitwarden-wasm-internal/src/lib.rs | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 74a0969fb..746f84176 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -785,6 +785,7 @@ dependencies = [ "bitwarden-crypto", "bitwarden-error", "bitwarden-exporters", + "bitwarden-fido", "bitwarden-generators", "bitwarden-ipc", "bitwarden-iter", diff --git a/crates/bitwarden-fido/Cargo.toml b/crates/bitwarden-fido/Cargo.toml index e2d3c80d7..2382a12fb 100644 --- a/crates/bitwarden-fido/Cargo.toml +++ b/crates/bitwarden-fido/Cargo.toml @@ -16,7 +16,12 @@ keywords.workspace = true [features] uniffi = ["dep:uniffi", "bitwarden-core/uniffi", "bitwarden-vault/uniffi"] -wasm = ["dep:wasm-bindgen"] +wasm = [ + "dep:wasm-bindgen", + "dep:wasm-bindgen-futures", + "dep:tsify", + "bitwarden-vault/wasm", +] [dependencies] async-trait = { workspace = true } diff --git a/crates/bitwarden-fido/src/lib.rs b/crates/bitwarden-fido/src/lib.rs index 80bdc00df..da3eecd41 100644 --- a/crates/bitwarden-fido/src/lib.rs +++ b/crates/bitwarden-fido/src/lib.rs @@ -22,6 +22,7 @@ mod traits; mod types; #[cfg(feature = "wasm")] +#[allow(missing_docs)] pub mod wasm; pub use authenticator::{ diff --git a/crates/bitwarden-wasm-internal/Cargo.toml b/crates/bitwarden-wasm-internal/Cargo.toml index 189759f47..cc56fc79d 100644 --- a/crates/bitwarden-wasm-internal/Cargo.toml +++ b/crates/bitwarden-wasm-internal/Cargo.toml @@ -23,6 +23,7 @@ bitwarden-core = { workspace = true, features = ["wasm", "internal"] } bitwarden-crypto = { workspace = true, features = ["wasm"] } bitwarden-error = { workspace = true } bitwarden-exporters = { workspace = true, features = ["wasm"] } +bitwarden-fido = { workspace = true, features = ["wasm"] } bitwarden-generators = { workspace = true, features = ["wasm"] } bitwarden-ipc = { workspace = true, features = ["wasm"] } bitwarden-iter = { workspace = true, features = ["wasm"] } diff --git a/crates/bitwarden-wasm-internal/src/lib.rs b/crates/bitwarden-wasm-internal/src/lib.rs index 8995c28d3..77f013462 100644 --- a/crates/bitwarden-wasm-internal/src/lib.rs +++ b/crates/bitwarden-wasm-internal/src/lib.rs @@ -7,6 +7,7 @@ mod platform; mod pure_crypto; mod ssh; +pub use bitwarden_fido::wasm::*; pub use bitwarden_ipc::wasm::*; pub use bitwarden_iter::wasm::*; pub use client::BitwardenClient;