diff --git a/Cargo.lock b/Cargo.lock index fce55ab9e..7ac00d962 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7305,9 +7305,10 @@ dependencies = [ name = "xmtp_api_http" version = "0.1.0" dependencies = [ - "async-stream", "async-trait", + "bytes", "futures", + "pin-project-lite", "reqwest 0.12.9", "serde", "serde_json", @@ -7483,6 +7484,7 @@ dependencies = [ "openssl", "openssl-sys", "parking_lot 0.12.3", + "pin-project-lite", "prost", "rand", "reqwest 0.12.9", diff --git a/Cargo.toml b/Cargo.toml index 77828087e..88b92f316 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,8 +37,7 @@ ctor = "0.2" ed25519 = "2.2.3" ed25519-dalek = { version = "2.1.1", features = ["zeroize"] } ethers = { version = "2.0", default-features = false } -futures = "0.3.30" -futures-core = "0.3.30" +futures = { version = "0.3.30", default-features = false } getrandom = { version = "0.2", default-features = false } hex = "0.4.3" hkdf = "0.12.3" @@ -62,16 +61,7 @@ tls_codec = "0.4.1" tokio = { version = "1.35.1", default-features = false } uuid = "1.10" vergen-git2 = "1.0.2" -wasm-timer = "0.2" web-time = "1.1" -# Changing this version and rustls may potentially break the android build. Use Caution. -# Test with Android and Swift first. -# Its probably preferable to one day use https://github.com/rustls/rustls-platform-verifier -# Until then, always test agains iOS/Android after updating these dependencies & making a PR -# Related Issues: -# - https://github.com/seanmonstar/reqwest/issues/2159 -# - https://github.com/hyperium/tonic/pull/1974 -# - https://github.com/rustls/rustls-platform-verifier/issues/58 bincode = "1.3" console_error_panic_hook = "0.1" const_format = "0.2" @@ -88,6 +78,14 @@ openssl = { version = "0.10", features = ["vendored"] } openssl-sys = { version = "0.9", features = ["vendored"] } parking_lot = "0.12.3" sqlite-web = "0.0.1" +# Changing this version and rustls may potentially break the android build. Use Caution. +# Test with Android and Swift first. +# Its probably preferable to one day use https://github.com/rustls/rustls-platform-verifier +# Until then, always test agains iOS/Android after updating these dependencies & making a PR +# Related Issues: +# - https://github.com/seanmonstar/reqwest/issues/2159 +# - https://github.com/hyperium/tonic/pull/1974 +# - https://github.com/rustls/rustls-platform-verifier/issues/58 tonic = { version = "0.12", default-features = false } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", default-features = false } @@ -102,7 +100,8 @@ criterion = { version = "0.5", features = [ "html_reports", "async_tokio", ]} - once_cell = "1.2" +once_cell = "1.2" +pin-project-lite = "0.2" # Internal Crate Dependencies xmtp_api_grpc = { path = "xmtp_api_grpc" } diff --git a/common/src/test.rs b/common/src/test.rs index 4cfb2442d..e8ae377a6 100644 --- a/common/src/test.rs +++ b/common/src/test.rs @@ -108,6 +108,10 @@ pub fn rand_u64() -> u64 { crypto_utils::rng().gen() } +pub fn rand_i64() -> i64 { + crypto_utils::rng().gen() +} + #[cfg(not(target_arch = "wasm32"))] pub fn tmp_path() -> String { let db_name = crate::rand_string::<24>(); diff --git a/lock b/lock new file mode 100644 index 000000000..191acf5a4 --- /dev/null +++ b/lock @@ -0,0 +1,2148 @@ +bindings_node v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/bindings_node) +├── futures v0.3.31 +│ ├── futures-channel v0.3.31 +│ │ ├── futures-core v0.3.31 +│ │ └── futures-sink v0.3.31 +│ ├── futures-core v0.3.31 +│ ├── futures-io v0.3.31 +│ ├── futures-sink v0.3.31 +│ ├── futures-task v0.3.31 +│ └── futures-util v0.3.31 +│ ├── futures-core v0.3.31 +│ ├── futures-macro v0.3.31 (proc-macro) +│ │ ├── proc-macro2 v1.0.92 +│ │ │ └── unicode-ident v1.0.14 +│ │ ├── quote v1.0.37 +│ │ │ └── proc-macro2 v1.0.92 (*) +│ │ └── syn v2.0.90 +│ │ ├── proc-macro2 v1.0.92 (*) +│ │ ├── quote v1.0.37 (*) +│ │ └── unicode-ident v1.0.14 +│ ├── futures-sink v0.3.31 +│ ├── futures-task v0.3.31 +│ ├── pin-project-lite v0.2.15 +│ ├── pin-utils v0.1.0 +│ └── slab v0.4.9 +│ [build-dependencies] +│ └── autocfg v1.4.0 +├── hex v0.4.3 +├── napi v2.16.13 +│ ├── bitflags v2.6.0 +│ ├── ctor v0.2.9 (proc-macro) +│ │ ├── quote v1.0.37 (*) +│ │ └── syn v2.0.90 (*) +│ ├── napi-sys v2.4.0 +│ ├── once_cell v1.20.2 +│ └── tokio v1.42.0 +│ ├── bytes v1.9.0 +│ │ └── serde v1.0.215 +│ │ └── serde_derive v1.0.215 (proc-macro) +│ │ ├── proc-macro2 v1.0.92 (*) +│ │ ├── quote v1.0.37 (*) +│ │ └── syn v2.0.90 (*) +│ ├── libc v0.2.168 +│ ├── mio v1.0.3 +│ │ └── libc v0.2.168 +│ ├── parking_lot v0.12.3 +│ │ ├── lock_api v0.4.12 +│ │ │ └── scopeguard v1.2.0 +│ │ │ [build-dependencies] +│ │ │ └── autocfg v1.4.0 +│ │ └── parking_lot_core v0.9.10 +│ │ ├── cfg-if v1.0.0 +│ │ ├── libc v0.2.168 +│ │ └── smallvec v1.13.2 +│ ├── pin-project-lite v0.2.15 +│ ├── signal-hook-registry v1.4.2 +│ │ └── libc v0.2.168 +│ ├── socket2 v0.5.8 +│ │ └── libc v0.2.168 +│ └── tokio-macros v2.4.0 (proc-macro) +│ ├── proc-macro2 v1.0.92 (*) +│ ├── quote v1.0.37 (*) +│ └── syn v2.0.90 (*) +├── napi-derive v2.16.13 (proc-macro) +│ ├── cfg-if v1.0.0 +│ ├── convert_case v0.6.0 +│ │ └── unicode-segmentation v1.12.0 +│ ├── napi-derive-backend v1.0.75 +│ │ ├── convert_case v0.6.0 (*) +│ │ ├── once_cell v1.20.2 +│ │ ├── proc-macro2 v1.0.92 (*) +│ │ ├── quote v1.0.37 (*) +│ │ ├── regex v1.11.1 +│ │ │ ├── aho-corasick v1.1.3 +│ │ │ │ └── memchr v2.7.4 +│ │ │ ├── memchr v2.7.4 +│ │ │ ├── regex-automata v0.4.9 +│ │ │ │ ├── aho-corasick v1.1.3 (*) +│ │ │ │ ├── memchr v2.7.4 +│ │ │ │ └── regex-syntax v0.8.5 +│ │ │ └── regex-syntax v0.8.5 +│ │ ├── semver v1.0.23 +│ │ │ └── serde v1.0.215 (*) +│ │ └── syn v2.0.90 (*) +│ ├── proc-macro2 v1.0.92 (*) +│ ├── quote v1.0.37 (*) +│ └── syn v2.0.90 (*) +├── prost v0.13.4 +│ ├── bytes v1.9.0 (*) +│ └── prost-derive v0.13.4 (proc-macro) +│ ├── anyhow v1.0.94 +│ ├── itertools v0.13.0 +│ │ └── either v1.13.0 +│ ├── proc-macro2 v1.0.92 (*) +│ ├── quote v1.0.37 (*) +│ └── syn v2.0.90 (*) +├── tokio v1.42.0 (*) +├── tracing v0.1.41 +│ ├── log v0.4.22 +│ ├── pin-project-lite v0.2.15 +│ ├── tracing-attributes v0.1.28 (proc-macro) +│ │ ├── proc-macro2 v1.0.92 (*) +│ │ ├── quote v1.0.37 (*) +│ │ └── syn v2.0.90 (*) +│ └── tracing-core v0.1.33 +│ ├── once_cell v1.20.2 +│ └── valuable v0.1.0 +│ └── valuable-derive v0.1.0 (proc-macro) +│ ├── proc-macro2 v1.0.92 (*) +│ ├── quote v1.0.37 (*) +│ └── syn v1.0.109 +│ ├── proc-macro2 v1.0.92 (*) +│ ├── quote v1.0.37 (*) +│ └── unicode-ident v1.0.14 +├── tracing-subscriber v0.3.19 +│ ├── chrono v0.4.39 +│ │ ├── iana-time-zone v0.1.61 +│ │ │ └── core-foundation-sys v0.8.7 +│ │ └── num-traits v0.2.19 +│ │ [build-dependencies] +│ │ └── autocfg v1.4.0 +│ ├── matchers v0.1.0 +│ │ └── regex-automata v0.1.10 +│ │ └── regex-syntax v0.6.29 +│ ├── nu-ansi-term v0.46.0 +│ │ └── overload v0.1.1 +│ ├── once_cell v1.20.2 +│ ├── regex v1.11.1 (*) +│ ├── serde v1.0.215 (*) +│ ├── serde_json v1.0.133 +│ │ ├── itoa v1.0.14 +│ │ ├── memchr v2.7.4 +│ │ ├── ryu v1.0.18 +│ │ └── serde v1.0.215 (*) +│ ├── sharded-slab v0.1.7 +│ │ └── lazy_static v1.5.0 +│ ├── thread_local v1.1.8 +│ │ ├── cfg-if v1.0.0 +│ │ └── once_cell v1.20.2 +│ ├── time v0.3.37 +│ │ ├── deranged v0.3.11 +│ │ │ └── powerfmt v0.2.0 +│ │ ├── itoa v1.0.14 +│ │ ├── num-conv v0.1.0 +│ │ ├── powerfmt v0.2.0 +│ │ ├── time-core v0.1.2 +│ │ └── time-macros v0.2.19 (proc-macro) +│ │ ├── num-conv v0.1.0 +│ │ └── time-core v0.1.2 +│ ├── tracing v0.1.41 (*) +│ ├── tracing-core v0.1.33 (*) +│ ├── tracing-serde v0.2.0 +│ │ ├── serde v1.0.215 (*) +│ │ ├── tracing-core v0.1.33 (*) +│ │ ├── valuable v0.1.0 (*) +│ │ └── valuable-serde v0.1.0 +│ │ ├── serde v1.0.215 (*) +│ │ └── valuable v0.1.0 (*) +│ ├── valuable v0.1.0 (*) +│ └── valuable-serde v0.1.0 (*) +├── xmtp_api_grpc v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_grpc) +│ ├── async-stream v0.3.6 +│ │ ├── async-stream-impl v0.3.6 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ ├── quote v1.0.37 (*) +│ │ │ └── syn v2.0.90 (*) +│ │ ├── futures-core v0.3.31 +│ │ └── pin-project-lite v0.2.15 +│ ├── async-trait v0.1.83 (proc-macro) +│ │ ├── proc-macro2 v1.0.92 (*) +│ │ ├── quote v1.0.37 (*) +│ │ └── syn v2.0.90 (*) +│ ├── base64 v0.22.1 +│ ├── futures v0.3.31 (*) +│ ├── hex v0.4.3 +│ ├── prost v0.13.4 (*) +│ ├── tokio v1.42.0 (*) +│ ├── tonic v0.12.3 +│ │ ├── async-stream v0.3.6 (*) +│ │ ├── async-trait v0.1.83 (proc-macro) (*) +│ │ ├── axum v0.7.9 +│ │ │ ├── async-trait v0.1.83 (proc-macro) (*) +│ │ │ ├── axum-core v0.4.5 +│ │ │ │ ├── async-trait v0.1.83 (proc-macro) (*) +│ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ ├── http v1.2.0 +│ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ └── itoa v1.0.14 +│ │ │ │ ├── http-body v1.0.1 +│ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ └── http v1.2.0 (*) +│ │ │ │ ├── http-body-util v0.1.2 +│ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ │ ├── http v1.2.0 (*) +│ │ │ │ │ ├── http-body v1.0.1 (*) +│ │ │ │ │ └── pin-project-lite v0.2.15 +│ │ │ │ ├── mime v0.3.17 +│ │ │ │ ├── pin-project-lite v0.2.15 +│ │ │ │ ├── rustversion v1.0.18 (proc-macro) +│ │ │ │ ├── sync_wrapper v1.0.2 +│ │ │ │ │ └── futures-core v0.3.31 +│ │ │ │ ├── tower-layer v0.3.3 +│ │ │ │ └── tower-service v0.3.3 +│ │ │ ├── bytes v1.9.0 (*) +│ │ │ ├── futures-util v0.3.31 (*) +│ │ │ ├── http v1.2.0 (*) +│ │ │ ├── http-body v1.0.1 (*) +│ │ │ ├── http-body-util v0.1.2 (*) +│ │ │ ├── itoa v1.0.14 +│ │ │ ├── matchit v0.7.3 +│ │ │ ├── memchr v2.7.4 +│ │ │ ├── mime v0.3.17 +│ │ │ ├── percent-encoding v2.3.1 +│ │ │ ├── pin-project-lite v0.2.15 +│ │ │ ├── rustversion v1.0.18 (proc-macro) +│ │ │ ├── serde v1.0.215 (*) +│ │ │ ├── sync_wrapper v1.0.2 (*) +│ │ │ ├── tower v0.5.1 +│ │ │ │ ├── futures-core v0.3.31 +│ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ ├── pin-project-lite v0.2.15 +│ │ │ │ ├── sync_wrapper v0.1.2 +│ │ │ │ ├── tower-layer v0.3.3 +│ │ │ │ └── tower-service v0.3.3 +│ │ │ ├── tower-layer v0.3.3 +│ │ │ └── tower-service v0.3.3 +│ │ ├── base64 v0.22.1 +│ │ ├── bytes v1.9.0 (*) +│ │ ├── h2 v0.4.7 +│ │ │ ├── atomic-waker v1.1.2 +│ │ │ ├── bytes v1.9.0 (*) +│ │ │ ├── fnv v1.0.7 +│ │ │ ├── futures-core v0.3.31 +│ │ │ ├── futures-sink v0.3.31 +│ │ │ ├── http v1.2.0 (*) +│ │ │ ├── indexmap v2.7.0 +│ │ │ │ ├── equivalent v1.0.1 +│ │ │ │ └── hashbrown v0.15.2 +│ │ │ ├── slab v0.4.9 (*) +│ │ │ ├── tokio v1.42.0 (*) +│ │ │ ├── tokio-util v0.7.13 +│ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ ├── futures-core v0.3.31 +│ │ │ │ ├── futures-sink v0.3.31 +│ │ │ │ ├── pin-project-lite v0.2.15 +│ │ │ │ └── tokio v1.42.0 (*) +│ │ │ └── tracing v0.1.41 (*) +│ │ ├── http v1.2.0 (*) +│ │ ├── http-body v1.0.1 (*) +│ │ ├── http-body-util v0.1.2 (*) +│ │ ├── hyper v1.5.1 +│ │ │ ├── bytes v1.9.0 (*) +│ │ │ ├── futures-channel v0.3.31 (*) +│ │ │ ├── futures-util v0.3.31 (*) +│ │ │ ├── h2 v0.4.7 (*) +│ │ │ ├── http v1.2.0 (*) +│ │ │ ├── http-body v1.0.1 (*) +│ │ │ ├── httparse v1.9.5 +│ │ │ ├── httpdate v1.0.3 +│ │ │ ├── itoa v1.0.14 +│ │ │ ├── pin-project-lite v0.2.15 +│ │ │ ├── smallvec v1.13.2 +│ │ │ ├── tokio v1.42.0 (*) +│ │ │ └── want v0.3.1 +│ │ │ └── try-lock v0.2.5 +│ │ ├── hyper-timeout v0.5.2 +│ │ │ ├── hyper v1.5.1 (*) +│ │ │ ├── hyper-util v0.1.10 +│ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ ├── futures-channel v0.3.31 (*) +│ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ ├── http v1.2.0 (*) +│ │ │ │ ├── http-body v1.0.1 (*) +│ │ │ │ ├── hyper v1.5.1 (*) +│ │ │ │ ├── pin-project-lite v0.2.15 +│ │ │ │ ├── socket2 v0.5.8 (*) +│ │ │ │ ├── tokio v1.42.0 (*) +│ │ │ │ ├── tower-service v0.3.3 +│ │ │ │ └── tracing v0.1.41 (*) +│ │ │ ├── pin-project-lite v0.2.15 +│ │ │ ├── tokio v1.42.0 (*) +│ │ │ └── tower-service v0.3.3 +│ │ ├── hyper-util v0.1.10 (*) +│ │ ├── percent-encoding v2.3.1 +│ │ ├── pin-project v1.1.7 +│ │ │ └── pin-project-internal v1.1.7 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ ├── quote v1.0.37 (*) +│ │ │ └── syn v2.0.90 (*) +│ │ ├── prost v0.13.4 (*) +│ │ ├── rustls-native-certs v0.8.1 +│ │ │ ├── rustls-pki-types v1.10.0 +│ │ │ └── security-framework v3.0.1 +│ │ │ ├── bitflags v2.6.0 +│ │ │ ├── core-foundation v0.10.0 +│ │ │ │ ├── core-foundation-sys v0.8.7 +│ │ │ │ └── libc v0.2.168 +│ │ │ ├── core-foundation-sys v0.8.7 +│ │ │ ├── libc v0.2.168 +│ │ │ └── security-framework-sys v2.12.1 +│ │ │ ├── core-foundation-sys v0.8.7 +│ │ │ └── libc v0.2.168 +│ │ ├── rustls-pemfile v2.2.0 +│ │ │ └── rustls-pki-types v1.10.0 +│ │ ├── socket2 v0.5.8 (*) +│ │ ├── tokio v1.42.0 (*) +│ │ ├── tokio-rustls v0.26.1 +│ │ │ ├── rustls v0.23.19 +│ │ │ │ ├── log v0.4.22 +│ │ │ │ ├── once_cell v1.20.2 +│ │ │ │ ├── ring v0.17.8 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── getrandom v0.2.15 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ └── libc v0.2.168 +│ │ │ │ │ ├── spin v0.9.8 +│ │ │ │ │ └── untrusted v0.9.0 +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── cc v1.2.3 +│ │ │ │ │ ├── jobserver v0.1.32 +│ │ │ │ │ │ └── libc v0.2.168 +│ │ │ │ │ ├── libc v0.2.168 +│ │ │ │ │ └── shlex v1.3.0 +│ │ │ │ ├── rustls-pki-types v1.10.0 +│ │ │ │ ├── rustls-webpki v0.102.8 +│ │ │ │ │ ├── ring v0.17.8 (*) +│ │ │ │ │ ├── rustls-pki-types v1.10.0 +│ │ │ │ │ └── untrusted v0.9.0 +│ │ │ │ ├── subtle v2.6.1 +│ │ │ │ └── zeroize v1.8.1 +│ │ │ │ └── zeroize_derive v1.4.2 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ └── syn v2.0.90 (*) +│ │ │ └── tokio v1.42.0 (*) +│ │ ├── tokio-stream v0.1.17 +│ │ │ ├── futures-core v0.3.31 +│ │ │ ├── pin-project-lite v0.2.15 +│ │ │ ├── tokio v1.42.0 (*) +│ │ │ └── tokio-util v0.7.13 (*) +│ │ ├── tower v0.4.13 +│ │ │ ├── futures-core v0.3.31 +│ │ │ ├── futures-util v0.3.31 (*) +│ │ │ ├── indexmap v1.9.3 +│ │ │ │ └── hashbrown v0.12.3 +│ │ │ │ [build-dependencies] +│ │ │ │ └── autocfg v1.4.0 +│ │ │ ├── pin-project v1.1.7 (*) +│ │ │ ├── pin-project-lite v0.2.15 +│ │ │ ├── rand v0.8.5 +│ │ │ │ ├── libc v0.2.168 +│ │ │ │ ├── rand_chacha v0.3.1 +│ │ │ │ │ ├── ppv-lite86 v0.2.20 +│ │ │ │ │ │ └── zerocopy v0.7.35 +│ │ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ │ └── zerocopy-derive v0.7.35 (proc-macro) +│ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ └── rand_core v0.6.4 +│ │ │ │ │ └── getrandom v0.2.15 (*) +│ │ │ │ └── rand_core v0.6.4 (*) +│ │ │ ├── slab v0.4.9 (*) +│ │ │ ├── tokio v1.42.0 (*) +│ │ │ ├── tokio-util v0.7.13 (*) +│ │ │ ├── tower-layer v0.3.3 +│ │ │ ├── tower-service v0.3.3 +│ │ │ └── tracing v0.1.41 (*) +│ │ ├── tower-layer v0.3.3 +│ │ ├── tower-service v0.3.3 +│ │ ├── tracing v0.1.41 (*) +│ │ └── webpki-roots v0.26.7 +│ │ └── rustls-pki-types v1.10.0 +│ ├── tracing v0.1.41 (*) +│ ├── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) +│ │ ├── async-trait v0.1.83 (proc-macro) (*) +│ │ ├── futures v0.3.31 (*) +│ │ ├── hex v0.4.3 +│ │ ├── openmls v0.6.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) +│ │ │ ├── log v0.4.22 +│ │ │ ├── openmls_traits v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) +│ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ └── tls_codec v0.4.1 +│ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ ├── tls_codec_derive v0.4.1 (proc-macro) +│ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ ├── rayon v1.10.0 +│ │ │ │ ├── either v1.13.0 +│ │ │ │ └── rayon-core v1.12.1 +│ │ │ │ ├── crossbeam-deque v0.8.5 +│ │ │ │ │ ├── crossbeam-epoch v0.9.18 +│ │ │ │ │ │ └── crossbeam-utils v0.8.20 +│ │ │ │ │ └── crossbeam-utils v0.8.20 +│ │ │ │ └── crossbeam-utils v0.8.20 +│ │ │ ├── serde v1.0.215 (*) +│ │ │ ├── thiserror v1.0.69 +│ │ │ │ └── thiserror-impl v1.0.69 (proc-macro) +│ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ └── syn v2.0.90 (*) +│ │ │ └── tls_codec v0.4.1 (*) +│ │ ├── openmls_rust_crypto v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) +│ │ │ ├── aes-gcm v0.10.3 +│ │ │ │ ├── aead v0.5.2 +│ │ │ │ │ ├── crypto-common v0.1.6 +│ │ │ │ │ │ ├── generic-array v0.14.7 +│ │ │ │ │ │ │ ├── typenum v1.17.0 +│ │ │ │ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ └── version_check v0.9.5 +│ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ └── typenum v1.17.0 +│ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ ├── aes v0.8.4 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── cipher v0.4.4 +│ │ │ │ │ │ ├── crypto-common v0.1.6 (*) +│ │ │ │ │ │ ├── inout v0.1.3 +│ │ │ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ │ │ └── cpufeatures v0.2.16 +│ │ │ │ │ └── libc v0.2.168 +│ │ │ │ ├── cipher v0.4.4 (*) +│ │ │ │ ├── ctr v0.9.2 +│ │ │ │ │ └── cipher v0.4.4 (*) +│ │ │ │ ├── ghash v0.5.1 +│ │ │ │ │ ├── opaque-debug v0.3.1 +│ │ │ │ │ └── polyval v0.6.2 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── cpufeatures v0.2.16 (*) +│ │ │ │ │ ├── opaque-debug v0.3.1 +│ │ │ │ │ └── universal-hash v0.5.1 +│ │ │ │ │ ├── crypto-common v0.1.6 (*) +│ │ │ │ │ └── subtle v2.6.1 +│ │ │ │ └── subtle v2.6.1 +│ │ │ ├── chacha20poly1305 v0.10.1 +│ │ │ │ ├── aead v0.5.2 (*) +│ │ │ │ ├── chacha20 v0.9.1 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ └── cipher v0.4.4 (*) +│ │ │ │ ├── cipher v0.4.4 (*) +│ │ │ │ ├── poly1305 v0.8.0 +│ │ │ │ │ ├── opaque-debug v0.3.1 +│ │ │ │ │ └── universal-hash v0.5.1 (*) +│ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ ├── ed25519-dalek v2.1.1 +│ │ │ │ ├── curve25519-dalek v4.1.3 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── digest v0.10.7 +│ │ │ │ │ │ ├── block-buffer v0.10.4 +│ │ │ │ │ │ │ └── generic-array v0.14.7 (*) +│ │ │ │ │ │ ├── const-oid v0.9.6 +│ │ │ │ │ │ ├── crypto-common v0.1.6 (*) +│ │ │ │ │ │ └── subtle v2.6.1 +│ │ │ │ │ ├── subtle v2.6.1 +│ │ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ │ │ [build-dependencies] +│ │ │ │ │ └── rustc_version v0.4.1 +│ │ │ │ │ └── semver v1.0.23 (*) +│ │ │ │ ├── ed25519 v2.2.3 +│ │ │ │ │ └── signature v2.0.0 +│ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ └── rand_core v0.6.4 (*) +│ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ ├── sha2 v0.10.8 +│ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ ├── cpufeatures v0.2.16 (*) +│ │ │ │ │ └── digest v0.10.7 (*) +│ │ │ │ ├── signature v2.0.0 (*) +│ │ │ │ ├── subtle v2.6.1 +│ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ ├── hkdf v0.12.4 +│ │ │ │ └── hmac v0.12.1 +│ │ │ │ └── digest v0.10.7 (*) +│ │ │ ├── hmac v0.12.1 (*) +│ │ │ ├── hpke-rs v0.2.0 +│ │ │ │ ├── hpke-rs-crypto v0.2.0 +│ │ │ │ │ └── rand_core v0.6.4 (*) +│ │ │ │ ├── log v0.4.22 +│ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ ├── tls_codec v0.4.1 (*) +│ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ ├── hpke-rs-crypto v0.2.0 (*) +│ │ │ ├── hpke-rs-rust-crypto v0.2.0 +│ │ │ │ ├── aes-gcm v0.10.3 (*) +│ │ │ │ ├── chacha20poly1305 v0.10.1 (*) +│ │ │ │ ├── hkdf v0.12.4 (*) +│ │ │ │ ├── hpke-rs-crypto v0.2.0 (*) +│ │ │ │ ├── p256 v0.13.2 +│ │ │ │ │ ├── ecdsa v0.16.9 +│ │ │ │ │ │ ├── der v0.7.9 +│ │ │ │ │ │ │ ├── const-oid v0.9.6 +│ │ │ │ │ │ │ ├── pem-rfc7468 v0.7.0 +│ │ │ │ │ │ │ │ └── base64ct v1.6.0 +│ │ │ │ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ ├── elliptic-curve v0.13.8 +│ │ │ │ │ │ │ ├── base16ct v0.2.0 +│ │ │ │ │ │ │ ├── crypto-bigint v0.5.5 +│ │ │ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ │ │ ├── subtle v2.6.1 +│ │ │ │ │ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ │ ├── ff v0.13.0 +│ │ │ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ │ │ └── subtle v2.6.1 +│ │ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ │ ├── group v0.13.0 +│ │ │ │ │ │ │ │ ├── ff v0.13.0 (*) +│ │ │ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ │ │ └── subtle v2.6.1 +│ │ │ │ │ │ │ ├── hkdf v0.12.4 (*) +│ │ │ │ │ │ │ ├── pem-rfc7468 v0.7.0 (*) +│ │ │ │ │ │ │ ├── pkcs8 v0.10.2 +│ │ │ │ │ │ │ │ ├── der v0.7.9 (*) +│ │ │ │ │ │ │ │ └── spki v0.7.3 +│ │ │ │ │ │ │ │ └── der v0.7.9 (*) +│ │ │ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ │ │ ├── sec1 v0.7.3 +│ │ │ │ │ │ │ │ ├── base16ct v0.2.0 +│ │ │ │ │ │ │ │ ├── der v0.7.9 (*) +│ │ │ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ │ │ ├── pkcs8 v0.10.2 (*) +│ │ │ │ │ │ │ │ ├── subtle v2.6.1 +│ │ │ │ │ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ │ │ │ │ ├── subtle v2.6.1 +│ │ │ │ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ │ │ │ ├── rfc6979 v0.4.0 +│ │ │ │ │ │ │ ├── hmac v0.12.1 (*) +│ │ │ │ │ │ │ └── subtle v2.6.1 +│ │ │ │ │ │ ├── signature v2.0.0 (*) +│ │ │ │ │ │ └── spki v0.7.3 (*) +│ │ │ │ │ ├── elliptic-curve v0.13.8 (*) +│ │ │ │ │ ├── primeorder v0.13.6 +│ │ │ │ │ │ └── elliptic-curve v0.13.8 (*) +│ │ │ │ │ └── sha2 v0.10.8 (*) +│ │ │ │ ├── p384 v0.13.0 +│ │ │ │ │ ├── elliptic-curve v0.13.8 (*) +│ │ │ │ │ └── primeorder v0.13.6 (*) +│ │ │ │ ├── rand_chacha v0.3.1 (*) +│ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ └── x25519-dalek v2.0.1 +│ │ │ │ ├── curve25519-dalek v4.1.3 (*) +│ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ ├── openmls_memory_storage v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) +│ │ │ │ ├── log v0.4.22 +│ │ │ │ ├── openmls_traits v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ └── thiserror v1.0.69 (*) +│ │ │ ├── openmls_traits v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ │ │ ├── p256 v0.13.2 (*) +│ │ │ ├── rand v0.8.5 (*) +│ │ │ ├── rand_chacha v0.3.1 (*) +│ │ │ ├── serde v1.0.215 (*) +│ │ │ ├── sha2 v0.10.8 (*) +│ │ │ ├── thiserror v1.0.69 (*) +│ │ │ └── tls_codec v0.4.1 (*) +│ │ ├── pbjson v0.7.0 +│ │ │ ├── base64 v0.21.7 +│ │ │ └── serde v1.0.215 (*) +│ │ ├── pbjson-types v0.7.0 +│ │ │ ├── bytes v1.9.0 (*) +│ │ │ ├── chrono v0.4.39 (*) +│ │ │ ├── pbjson v0.7.0 (*) +│ │ │ ├── prost v0.13.4 (*) +│ │ │ └── serde v1.0.215 (*) +│ │ │ [build-dependencies] +│ │ │ ├── pbjson-build v0.7.0 +│ │ │ │ ├── heck v0.5.0 +│ │ │ │ ├── itertools v0.13.0 (*) +│ │ │ │ ├── prost v0.13.4 (*) +│ │ │ │ └── prost-types v0.13.4 +│ │ │ │ └── prost v0.13.4 (*) +│ │ │ └── prost-build v0.13.4 +│ │ │ ├── heck v0.5.0 +│ │ │ ├── itertools v0.13.0 (*) +│ │ │ ├── log v0.4.22 +│ │ │ ├── multimap v0.10.0 +│ │ │ ├── once_cell v1.20.2 +│ │ │ ├── petgraph v0.6.5 +│ │ │ │ ├── fixedbitset v0.4.2 +│ │ │ │ └── indexmap v2.7.0 (*) +│ │ │ ├── prettyplease v0.2.25 +│ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ └── syn v2.0.90 (*) +│ │ │ ├── prost v0.13.4 (*) +│ │ │ ├── prost-types v0.13.4 (*) +│ │ │ ├── regex v1.11.1 (*) +│ │ │ ├── syn v2.0.90 (*) +│ │ │ └── tempfile v3.15.0 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── fastrand v2.3.0 +│ │ │ ├── getrandom v0.2.15 (*) +│ │ │ ├── once_cell v1.20.2 +│ │ │ └── rustix v0.38.42 +│ │ │ ├── bitflags v2.6.0 +│ │ │ ├── errno v0.3.10 +│ │ │ │ └── libc v0.2.168 +│ │ │ └── libc v0.2.168 +│ │ ├── prost v0.13.4 (*) +│ │ ├── serde v1.0.215 (*) +│ │ ├── tonic v0.12.3 (*) +│ │ ├── tracing v0.1.41 (*) +│ │ └── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) +│ │ ├── futures v0.3.31 (*) +│ │ ├── parking_lot v0.12.3 (*) +│ │ ├── rand v0.8.5 (*) +│ │ ├── tokio v1.42.0 (*) +│ │ ├── tracing v0.1.41 (*) +│ │ ├── tracing-subscriber v0.3.19 (*) +│ │ ├── web-time v1.1.0 +│ │ └── xmtp_cryptography v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_cryptography) +│ │ ├── curve25519-dalek v4.1.3 (*) +│ │ ├── ecdsa v0.16.9 (*) +│ │ ├── ed25519-dalek v2.1.1 (*) +│ │ ├── ethers v2.0.14 +│ │ │ ├── ethers-addressbook v2.0.14 +│ │ │ │ ├── ethers-core v2.0.14 +│ │ │ │ │ ├── arrayvec v0.7.6 +│ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ ├── cargo_metadata v0.18.1 +│ │ │ │ │ │ ├── camino v1.1.9 +│ │ │ │ │ │ │ └── serde v1.0.215 (*) +│ │ │ │ │ │ ├── cargo-platform v0.1.9 +│ │ │ │ │ │ │ └── serde v1.0.215 (*) +│ │ │ │ │ │ ├── semver v1.0.23 (*) +│ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ │ │ └── thiserror v1.0.69 (*) +│ │ │ │ │ ├── chrono v0.4.39 (*) +│ │ │ │ │ ├── const-hex v1.14.0 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ └── hex v0.4.3 +│ │ │ │ │ ├── elliptic-curve v0.13.8 (*) +│ │ │ │ │ ├── ethabi v18.0.0 +│ │ │ │ │ │ ├── ethereum-types v0.14.1 +│ │ │ │ │ │ │ ├── ethbloom v0.13.0 +│ │ │ │ │ │ │ │ ├── crunchy v0.2.2 +│ │ │ │ │ │ │ │ ├── fixed-hash v0.8.0 +│ │ │ │ │ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ │ │ │ ├── rustc-hex v2.1.0 +│ │ │ │ │ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ │ │ │ │ ├── impl-codec v0.6.0 +│ │ │ │ │ │ │ │ │ └── parity-scale-codec v3.6.12 +│ │ │ │ │ │ │ │ │ ├── arrayvec v0.7.6 +│ │ │ │ │ │ │ │ │ ├── byte-slice-cast v1.2.2 +│ │ │ │ │ │ │ │ │ ├── impl-trait-for-tuples v0.2.3 (proc-macro) +│ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ │ │ │ │ ├── parity-scale-codec-derive v3.6.12 (proc-macro) +│ │ │ │ │ │ │ │ │ │ ├── proc-macro-crate v3.2.0 +│ │ │ │ │ │ │ │ │ │ │ └── toml_edit v0.22.22 +│ │ │ │ │ │ │ │ │ │ │ ├── indexmap v2.7.0 (*) +│ │ │ │ │ │ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ │ │ │ │ │ ├── serde_spanned v0.6.8 +│ │ │ │ │ │ │ │ │ │ │ │ └── serde v1.0.215 (*) +│ │ │ │ │ │ │ │ │ │ │ ├── toml_datetime v0.6.8 +│ │ │ │ │ │ │ │ │ │ │ │ └── serde v1.0.215 (*) +│ │ │ │ │ │ │ │ │ │ │ └── winnow v0.6.20 +│ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ │ │ │ │ │ └── serde v1.0.215 (*) +│ │ │ │ │ │ │ │ ├── impl-rlp v0.3.0 +│ │ │ │ │ │ │ │ │ └── rlp v0.5.2 +│ │ │ │ │ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ │ │ │ │ ├── rlp-derive v0.1.0 (proc-macro) +│ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ │ │ │ │ │ └── rustc-hex v2.1.0 +│ │ │ │ │ │ │ │ ├── impl-serde v0.4.0 +│ │ │ │ │ │ │ │ │ └── serde v1.0.215 (*) +│ │ │ │ │ │ │ │ ├── scale-info v2.11.6 +│ │ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ │ ├── derive_more v1.0.0 +│ │ │ │ │ │ │ │ │ │ └── derive_more-impl v1.0.0 (proc-macro) +│ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ │ │ │ │ ├── parity-scale-codec v3.6.12 (*) +│ │ │ │ │ │ │ │ │ └── scale-info-derive v2.11.6 (proc-macro) +│ │ │ │ │ │ │ │ │ ├── proc-macro-crate v3.2.0 (*) +│ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ │ │ │ └── tiny-keccak v2.0.2 +│ │ │ │ │ │ │ │ └── crunchy v0.2.2 +│ │ │ │ │ │ │ ├── fixed-hash v0.8.0 (*) +│ │ │ │ │ │ │ ├── impl-codec v0.6.0 (*) +│ │ │ │ │ │ │ ├── impl-rlp v0.3.0 (*) +│ │ │ │ │ │ │ ├── impl-serde v0.4.0 (*) +│ │ │ │ │ │ │ ├── primitive-types v0.12.2 +│ │ │ │ │ │ │ │ ├── fixed-hash v0.8.0 (*) +│ │ │ │ │ │ │ │ ├── impl-codec v0.6.0 (*) +│ │ │ │ │ │ │ │ ├── impl-rlp v0.3.0 (*) +│ │ │ │ │ │ │ │ ├── impl-serde v0.4.0 (*) +│ │ │ │ │ │ │ │ ├── scale-info v2.11.6 (*) +│ │ │ │ │ │ │ │ └── uint v0.9.5 +│ │ │ │ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ │ │ │ ├── crunchy v0.2.2 +│ │ │ │ │ │ │ │ ├── hex v0.4.3 +│ │ │ │ │ │ │ │ └── static_assertions v1.1.0 +│ │ │ │ │ │ │ ├── scale-info v2.11.6 (*) +│ │ │ │ │ │ │ └── uint v0.9.5 (*) +│ │ │ │ │ │ ├── hex v0.4.3 +│ │ │ │ │ │ ├── once_cell v1.20.2 +│ │ │ │ │ │ ├── regex v1.11.1 (*) +│ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ │ │ ├── sha3 v0.10.8 +│ │ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ │ └── keccak v0.1.5 +│ │ │ │ │ │ │ └── cpufeatures v0.2.16 (*) +│ │ │ │ │ │ ├── thiserror v1.0.69 (*) +│ │ │ │ │ │ └── uint v0.9.5 (*) +│ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ ├── k256 v0.13.4 +│ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ ├── ecdsa v0.16.9 (*) +│ │ │ │ │ │ ├── elliptic-curve v0.13.8 (*) +│ │ │ │ │ │ ├── once_cell v1.20.2 +│ │ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ │ └── signature v2.0.0 (*) +│ │ │ │ │ ├── num_enum v0.7.3 +│ │ │ │ │ │ └── num_enum_derive v0.7.3 (proc-macro) +│ │ │ │ │ │ ├── proc-macro-crate v3.2.0 (*) +│ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ ├── once_cell v1.20.2 +│ │ │ │ │ ├── open-fastrlp v0.1.4 +│ │ │ │ │ │ ├── arrayvec v0.7.6 +│ │ │ │ │ │ ├── auto_impl v1.2.0 (proc-macro) +│ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ │ ├── ethereum-types v0.14.1 (*) +│ │ │ │ │ │ └── open-fastrlp-derive v0.1.1 (proc-macro) +│ │ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ └── syn v1.0.109 (*) +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ ├── rlp v0.5.2 (*) +│ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ │ ├── strum v0.26.3 +│ │ │ │ │ │ └── strum_macros v0.26.4 (proc-macro) +│ │ │ │ │ │ ├── heck v0.5.0 +│ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ ├── rustversion v1.0.18 (proc-macro) +│ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ ├── syn v2.0.90 +│ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ └── unicode-ident v1.0.14 +│ │ │ │ │ ├── tempfile v3.15.0 (*) +│ │ │ │ │ ├── thiserror v1.0.69 (*) +│ │ │ │ │ ├── tiny-keccak v2.0.2 (*) +│ │ │ │ │ └── unicode-xid v0.2.6 +│ │ │ │ ├── once_cell v1.20.2 +│ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ └── serde_json v1.0.133 (*) +│ │ │ ├── ethers-contract v2.0.14 +│ │ │ │ ├── const-hex v1.14.0 (*) +│ │ │ │ ├── ethers-contract-abigen v2.0.14 +│ │ │ │ │ ├── Inflector v0.11.4 +│ │ │ │ │ │ ├── lazy_static v1.5.0 +│ │ │ │ │ │ └── regex v1.11.1 (*) +│ │ │ │ │ ├── const-hex v1.14.0 (*) +│ │ │ │ │ ├── dunce v1.0.5 +│ │ │ │ │ ├── ethers-core v2.0.14 (*) +│ │ │ │ │ ├── eyre v0.6.12 +│ │ │ │ │ │ ├── indenter v0.3.3 +│ │ │ │ │ │ └── once_cell v1.20.2 +│ │ │ │ │ ├── prettyplease v0.2.25 (*) +│ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ ├── regex v1.11.1 (*) +│ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ │ ├── syn v2.0.90 (*) +│ │ │ │ │ ├── toml v0.8.19 +│ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ ├── serde_spanned v0.6.8 (*) +│ │ │ │ │ │ ├── toml_datetime v0.6.8 (*) +│ │ │ │ │ │ └── toml_edit v0.22.22 +│ │ │ │ │ │ ├── indexmap v2.7.0 (*) +│ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ ├── serde_spanned v0.6.8 (*) +│ │ │ │ │ │ ├── toml_datetime v0.6.8 (*) +│ │ │ │ │ │ └── winnow v0.6.20 +│ │ │ │ │ └── walkdir v2.5.0 +│ │ │ │ │ └── same-file v1.0.6 +│ │ │ │ ├── ethers-contract-derive v2.0.14 (proc-macro) +│ │ │ │ │ ├── Inflector v0.11.4 (*) +│ │ │ │ │ ├── const-hex v1.14.0 (*) +│ │ │ │ │ ├── ethers-contract-abigen v2.0.14 +│ │ │ │ │ │ ├── Inflector v0.11.4 (*) +│ │ │ │ │ │ ├── const-hex v1.14.0 (*) +│ │ │ │ │ │ ├── dunce v1.0.5 +│ │ │ │ │ │ ├── ethers-core v2.0.14 (*) +│ │ │ │ │ │ ├── eyre v0.6.12 (*) +│ │ │ │ │ │ ├── prettyplease v0.2.25 (*) +│ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ ├── regex v1.11.1 (*) +│ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ ├── serde_json v1.0.133 +│ │ │ │ │ │ │ ├── itoa v1.0.14 +│ │ │ │ │ │ │ ├── memchr v2.7.4 +│ │ │ │ │ │ │ ├── ryu v1.0.18 +│ │ │ │ │ │ │ └── serde v1.0.215 (*) +│ │ │ │ │ │ ├── syn v2.0.90 (*) +│ │ │ │ │ │ ├── toml v0.8.19 (*) +│ │ │ │ │ │ └── walkdir v2.5.0 (*) +│ │ │ │ │ ├── ethers-core v2.0.14 (*) +│ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ ├── ethers-core v2.0.14 (*) +│ │ │ │ ├── ethers-providers v2.0.14 +│ │ │ │ │ ├── async-trait v0.1.83 (proc-macro) (*) +│ │ │ │ │ ├── auto_impl v1.2.0 (proc-macro) (*) +│ │ │ │ │ ├── base64 v0.21.7 +│ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ ├── const-hex v1.14.0 (*) +│ │ │ │ │ ├── enr v0.10.0 +│ │ │ │ │ │ ├── base64 v0.21.7 +│ │ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ │ ├── hex v0.4.3 +│ │ │ │ │ │ ├── k256 v0.13.4 (*) +│ │ │ │ │ │ ├── log v0.4.22 +│ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ ├── rlp v0.5.2 (*) +│ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ ├── sha3 v0.10.8 (*) +│ │ │ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ │ │ ├── ethers-core v2.0.14 (*) +│ │ │ │ │ ├── futures-core v0.3.31 +│ │ │ │ │ ├── futures-timer v3.0.3 +│ │ │ │ │ │ ├── gloo-timers v0.2.6 +│ │ │ │ │ │ │ ├── futures-channel v0.3.31 (*) +│ │ │ │ │ │ │ ├── futures-core v0.3.31 +│ │ │ │ │ │ │ ├── js-sys v0.3.76 +│ │ │ │ │ │ │ │ ├── once_cell v1.20.2 +│ │ │ │ │ │ │ │ └── wasm-bindgen v0.2.99 +│ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ ├── once_cell v1.20.2 +│ │ │ │ │ │ │ │ └── wasm-bindgen-macro v0.2.99 (proc-macro) +│ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ └── wasm-bindgen-macro-support v0.2.99 +│ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ ├── syn v2.0.90 (*) +│ │ │ │ │ │ │ │ ├── wasm-bindgen-backend v0.2.99 +│ │ │ │ │ │ │ │ │ ├── bumpalo v3.16.0 +│ │ │ │ │ │ │ │ │ ├── log v0.4.22 +│ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ ├── syn v2.0.90 (*) +│ │ │ │ │ │ │ │ │ └── wasm-bindgen-shared v0.2.99 +│ │ │ │ │ │ │ │ └── wasm-bindgen-shared v0.2.99 +│ │ │ │ │ │ │ └── wasm-bindgen v0.2.99 (*) +│ │ │ │ │ │ └── send_wrapper v0.4.0 +│ │ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ │ ├── hashers v1.0.1 +│ │ │ │ │ │ └── fxhash v0.2.1 +│ │ │ │ │ │ └── byteorder v1.5.0 +│ │ │ │ │ ├── http v0.2.12 +│ │ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ │ └── itoa v1.0.14 +│ │ │ │ │ ├── instant v0.1.13 +│ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ ├── jsonwebtoken v8.3.0 +│ │ │ │ │ │ ├── base64 v0.21.7 +│ │ │ │ │ │ ├── pem v1.1.1 +│ │ │ │ │ │ │ └── base64 v0.13.1 +│ │ │ │ │ │ ├── ring v0.16.20 +│ │ │ │ │ │ │ └── untrusted v0.7.1 +│ │ │ │ │ │ │ [build-dependencies] +│ │ │ │ │ │ │ └── cc v1.2.3 (*) +│ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ │ │ └── simple_asn1 v0.6.2 +│ │ │ │ │ │ ├── num-bigint v0.4.6 +│ │ │ │ │ │ │ ├── num-integer v0.1.46 +│ │ │ │ │ │ │ │ └── num-traits v0.2.19 (*) +│ │ │ │ │ │ │ └── num-traits v0.2.19 (*) +│ │ │ │ │ │ ├── num-traits v0.2.19 (*) +│ │ │ │ │ │ ├── thiserror v1.0.69 (*) +│ │ │ │ │ │ └── time v0.3.37 (*) +│ │ │ │ │ ├── once_cell v1.20.2 +│ │ │ │ │ ├── pin-project v1.1.7 (*) +│ │ │ │ │ ├── reqwest v0.11.27 +│ │ │ │ │ │ ├── base64 v0.21.7 +│ │ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ │ ├── encoding_rs v0.8.35 +│ │ │ │ │ │ │ └── cfg-if v1.0.0 +│ │ │ │ │ │ ├── futures-core v0.3.31 +│ │ │ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ │ │ ├── h2 v0.3.26 +│ │ │ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ │ │ ├── futures-core v0.3.31 +│ │ │ │ │ │ │ ├── futures-sink v0.3.31 +│ │ │ │ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ │ │ │ ├── http v0.2.12 (*) +│ │ │ │ │ │ │ ├── indexmap v2.7.0 (*) +│ │ │ │ │ │ │ ├── slab v0.4.9 (*) +│ │ │ │ │ │ │ ├── tokio v1.42.0 (*) +│ │ │ │ │ │ │ ├── tokio-util v0.7.13 (*) +│ │ │ │ │ │ │ └── tracing v0.1.41 (*) +│ │ │ │ │ │ ├── http v0.2.12 (*) +│ │ │ │ │ │ ├── http-body v0.4.6 +│ │ │ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ │ │ ├── http v0.2.12 (*) +│ │ │ │ │ │ │ └── pin-project-lite v0.2.15 +│ │ │ │ │ │ ├── hyper v0.14.31 +│ │ │ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ │ │ ├── futures-channel v0.3.31 (*) +│ │ │ │ │ │ │ ├── futures-core v0.3.31 +│ │ │ │ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ │ │ │ ├── h2 v0.3.26 (*) +│ │ │ │ │ │ │ ├── http v0.2.12 (*) +│ │ │ │ │ │ │ ├── http-body v0.4.6 (*) +│ │ │ │ │ │ │ ├── httparse v1.9.5 +│ │ │ │ │ │ │ ├── httpdate v1.0.3 +│ │ │ │ │ │ │ ├── itoa v1.0.14 +│ │ │ │ │ │ │ ├── pin-project-lite v0.2.15 +│ │ │ │ │ │ │ ├── socket2 v0.5.8 (*) +│ │ │ │ │ │ │ ├── tokio v1.42.0 (*) +│ │ │ │ │ │ │ ├── tower-service v0.3.3 +│ │ │ │ │ │ │ ├── tracing v0.1.41 (*) +│ │ │ │ │ │ │ └── want v0.3.1 (*) +│ │ │ │ │ │ ├── hyper-rustls v0.24.2 +│ │ │ │ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ │ │ │ ├── http v0.2.12 (*) +│ │ │ │ │ │ │ ├── hyper v0.14.31 (*) +│ │ │ │ │ │ │ ├── rustls v0.21.12 +│ │ │ │ │ │ │ │ ├── log v0.4.22 +│ │ │ │ │ │ │ │ ├── ring v0.17.8 (*) +│ │ │ │ │ │ │ │ ├── rustls-webpki v0.101.7 +│ │ │ │ │ │ │ │ │ ├── ring v0.17.8 (*) +│ │ │ │ │ │ │ │ │ └── untrusted v0.9.0 +│ │ │ │ │ │ │ │ └── sct v0.7.1 +│ │ │ │ │ │ │ │ ├── ring v0.17.8 (*) +│ │ │ │ │ │ │ │ └── untrusted v0.9.0 +│ │ │ │ │ │ │ ├── tokio v1.42.0 (*) +│ │ │ │ │ │ │ └── tokio-rustls v0.24.1 +│ │ │ │ │ │ │ ├── rustls v0.21.12 (*) +│ │ │ │ │ │ │ └── tokio v1.42.0 (*) +│ │ │ │ │ │ ├── hyper-tls v0.5.0 +│ │ │ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ │ │ ├── hyper v0.14.31 (*) +│ │ │ │ │ │ │ ├── native-tls v0.2.12 +│ │ │ │ │ │ │ │ ├── libc v0.2.168 +│ │ │ │ │ │ │ │ ├── security-framework v2.11.1 +│ │ │ │ │ │ │ │ │ ├── bitflags v2.6.0 +│ │ │ │ │ │ │ │ │ ├── core-foundation v0.9.4 +│ │ │ │ │ │ │ │ │ │ ├── core-foundation-sys v0.8.7 +│ │ │ │ │ │ │ │ │ │ └── libc v0.2.168 +│ │ │ │ │ │ │ │ │ ├── core-foundation-sys v0.8.7 +│ │ │ │ │ │ │ │ │ ├── libc v0.2.168 +│ │ │ │ │ │ │ │ │ └── security-framework-sys v2.12.1 (*) +│ │ │ │ │ │ │ │ ├── security-framework-sys v2.12.1 (*) +│ │ │ │ │ │ │ │ └── tempfile v3.15.0 (*) +│ │ │ │ │ │ │ ├── tokio v1.42.0 (*) +│ │ │ │ │ │ │ └── tokio-native-tls v0.3.1 +│ │ │ │ │ │ │ ├── native-tls v0.2.12 (*) +│ │ │ │ │ │ │ └── tokio v1.42.0 (*) +│ │ │ │ │ │ ├── ipnet v2.10.1 +│ │ │ │ │ │ ├── log v0.4.22 +│ │ │ │ │ │ ├── mime v0.3.17 +│ │ │ │ │ │ ├── native-tls v0.2.12 (*) +│ │ │ │ │ │ ├── once_cell v1.20.2 +│ │ │ │ │ │ ├── percent-encoding v2.3.1 +│ │ │ │ │ │ ├── pin-project-lite v0.2.15 +│ │ │ │ │ │ ├── rustls v0.21.12 (*) +│ │ │ │ │ │ ├── rustls-pemfile v1.0.4 +│ │ │ │ │ │ │ └── base64 v0.21.7 +│ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ │ │ ├── serde_urlencoded v0.7.1 +│ │ │ │ │ │ │ ├── form_urlencoded v1.2.1 +│ │ │ │ │ │ │ │ └── percent-encoding v2.3.1 +│ │ │ │ │ │ │ ├── itoa v1.0.14 +│ │ │ │ │ │ │ ├── ryu v1.0.18 +│ │ │ │ │ │ │ └── serde v1.0.215 (*) +│ │ │ │ │ │ ├── sync_wrapper v0.1.2 +│ │ │ │ │ │ ├── system-configuration v0.5.1 +│ │ │ │ │ │ │ ├── bitflags v1.3.2 +│ │ │ │ │ │ │ ├── core-foundation v0.9.4 (*) +│ │ │ │ │ │ │ └── system-configuration-sys v0.5.0 +│ │ │ │ │ │ │ ├── core-foundation-sys v0.8.7 +│ │ │ │ │ │ │ └── libc v0.2.168 +│ │ │ │ │ │ ├── tokio v1.42.0 (*) +│ │ │ │ │ │ ├── tokio-native-tls v0.3.1 (*) +│ │ │ │ │ │ ├── tokio-rustls v0.24.1 (*) +│ │ │ │ │ │ ├── tower-service v0.3.3 +│ │ │ │ │ │ ├── url v2.5.4 +│ │ │ │ │ │ │ ├── form_urlencoded v1.2.1 (*) +│ │ │ │ │ │ │ ├── idna v1.0.3 +│ │ │ │ │ │ │ │ ├── idna_adapter v1.2.0 +│ │ │ │ │ │ │ │ │ ├── icu_normalizer v1.5.0 +│ │ │ │ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) +│ │ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ │ │ │ │ │ ├── icu_collections v1.5.0 +│ │ │ │ │ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) (*) +│ │ │ │ │ │ │ │ │ │ │ ├── yoke v0.7.5 +│ │ │ │ │ │ │ │ │ │ │ │ ├── stable_deref_trait v1.2.0 +│ │ │ │ │ │ │ │ │ │ │ │ ├── yoke-derive v0.7.5 (proc-macro) +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── syn v2.0.90 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ └── synstructure v0.13.1 +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ │ │ │ │ │ │ │ └── zerofrom v0.1.5 +│ │ │ │ │ │ │ │ │ │ │ │ └── zerofrom-derive v0.1.5 (proc-macro) +│ │ │ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ │ │ │ ├── syn v2.0.90 (*) +│ │ │ │ │ │ │ │ │ │ │ │ └── synstructure v0.13.1 (*) +│ │ │ │ │ │ │ │ │ │ │ ├── zerofrom v0.1.5 (*) +│ │ │ │ │ │ │ │ │ │ │ └── zerovec v0.10.4 +│ │ │ │ │ │ │ │ │ │ │ ├── yoke v0.7.5 (*) +│ │ │ │ │ │ │ │ │ │ │ ├── zerofrom v0.1.5 (*) +│ │ │ │ │ │ │ │ │ │ │ └── zerovec-derive v0.10.3 (proc-macro) +│ │ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ │ │ │ │ │ ├── icu_normalizer_data v1.5.0 +│ │ │ │ │ │ │ │ │ │ ├── icu_properties v1.5.1 +│ │ │ │ │ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) (*) +│ │ │ │ │ │ │ │ │ │ │ ├── icu_collections v1.5.0 (*) +│ │ │ │ │ │ │ │ │ │ │ ├── icu_locid_transform v1.5.0 +│ │ │ │ │ │ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) (*) +│ │ │ │ │ │ │ │ │ │ │ │ ├── icu_locid v1.5.0 +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── litemap v0.7.4 +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── tinystr v0.7.6 +│ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ │ └── zerovec v0.10.4 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── writeable v0.5.5 +│ │ │ │ │ │ │ │ │ │ │ │ │ └── zerovec v0.10.4 (*) +│ │ │ │ │ │ │ │ │ │ │ │ ├── icu_locid_transform_data v1.5.0 +│ │ │ │ │ │ │ │ │ │ │ │ ├── icu_provider v1.5.0 +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── displaydoc v0.2.5 (proc-macro) (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── icu_locid v1.5.0 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── icu_provider_macros v1.5.0 (proc-macro) +│ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── stable_deref_trait v1.2.0 +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── tinystr v0.7.6 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── writeable v0.5.5 +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── yoke v0.7.5 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ ├── zerofrom v0.1.5 (*) +│ │ │ │ │ │ │ │ │ │ │ │ │ └── zerovec v0.10.4 (*) +│ │ │ │ │ │ │ │ │ │ │ │ ├── tinystr v0.7.6 (*) +│ │ │ │ │ │ │ │ │ │ │ │ └── zerovec v0.10.4 (*) +│ │ │ │ │ │ │ │ │ │ │ ├── icu_properties_data v1.5.0 +│ │ │ │ │ │ │ │ │ │ │ ├── icu_provider v1.5.0 (*) +│ │ │ │ │ │ │ │ │ │ │ ├── tinystr v0.7.6 (*) +│ │ │ │ │ │ │ │ │ │ │ └── zerovec v0.10.4 (*) +│ │ │ │ │ │ │ │ │ │ ├── icu_provider v1.5.0 (*) +│ │ │ │ │ │ │ │ │ │ ├── smallvec v1.13.2 +│ │ │ │ │ │ │ │ │ │ ├── utf16_iter v1.0.5 +│ │ │ │ │ │ │ │ │ │ ├── utf8_iter v1.0.4 +│ │ │ │ │ │ │ │ │ │ ├── write16 v1.0.0 +│ │ │ │ │ │ │ │ │ │ └── zerovec v0.10.4 (*) +│ │ │ │ │ │ │ │ │ └── icu_properties v1.5.1 (*) +│ │ │ │ │ │ │ │ ├── smallvec v1.13.2 +│ │ │ │ │ │ │ │ └── utf8_iter v1.0.4 +│ │ │ │ │ │ │ ├── percent-encoding v2.3.1 +│ │ │ │ │ │ │ └── serde v1.0.215 (*) +│ │ │ │ │ │ └── webpki-roots v0.25.4 +│ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ │ ├── thiserror v1.0.69 (*) +│ │ │ │ │ ├── tokio v1.42.0 (*) +│ │ │ │ │ ├── tokio-tungstenite v0.20.1 +│ │ │ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ │ │ ├── log v0.4.22 +│ │ │ │ │ │ ├── native-tls v0.2.12 (*) +│ │ │ │ │ │ ├── rustls v0.21.12 (*) +│ │ │ │ │ │ ├── tokio v1.42.0 (*) +│ │ │ │ │ │ ├── tokio-native-tls v0.3.1 (*) +│ │ │ │ │ │ ├── tokio-rustls v0.24.1 (*) +│ │ │ │ │ │ ├── tungstenite v0.20.1 +│ │ │ │ │ │ │ ├── byteorder v1.5.0 +│ │ │ │ │ │ │ ├── bytes v1.9.0 (*) +│ │ │ │ │ │ │ ├── data-encoding v2.6.0 +│ │ │ │ │ │ │ ├── http v0.2.12 (*) +│ │ │ │ │ │ │ ├── httparse v1.9.5 +│ │ │ │ │ │ │ ├── log v0.4.22 +│ │ │ │ │ │ │ ├── native-tls v0.2.12 (*) +│ │ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ │ ├── rustls v0.21.12 (*) +│ │ │ │ │ │ │ ├── sha1 v0.10.6 +│ │ │ │ │ │ │ │ ├── cfg-if v1.0.0 +│ │ │ │ │ │ │ │ ├── cpufeatures v0.2.16 (*) +│ │ │ │ │ │ │ │ └── digest v0.10.7 (*) +│ │ │ │ │ │ │ ├── thiserror v1.0.69 (*) +│ │ │ │ │ │ │ ├── url v2.5.4 (*) +│ │ │ │ │ │ │ └── utf-8 v0.7.6 +│ │ │ │ │ │ └── webpki-roots v0.25.4 +│ │ │ │ │ ├── tracing v0.1.41 (*) +│ │ │ │ │ ├── tracing-futures v0.2.5 +│ │ │ │ │ │ ├── pin-project v1.1.7 (*) +│ │ │ │ │ │ └── tracing v0.1.41 (*) +│ │ │ │ │ └── url v2.5.4 (*) +│ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ ├── once_cell v1.20.2 +│ │ │ │ ├── pin-project v1.1.7 (*) +│ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ └── thiserror v1.0.69 (*) +│ │ │ ├── ethers-core v2.0.14 (*) +│ │ │ ├── ethers-middleware v2.0.14 +│ │ │ │ ├── async-trait v0.1.83 (proc-macro) (*) +│ │ │ │ ├── auto_impl v1.2.0 (proc-macro) (*) +│ │ │ │ ├── ethers-contract v2.0.14 (*) +│ │ │ │ ├── ethers-core v2.0.14 (*) +│ │ │ │ ├── ethers-providers v2.0.14 (*) +│ │ │ │ ├── ethers-signers v2.0.14 +│ │ │ │ │ ├── async-trait v0.1.83 (proc-macro) (*) +│ │ │ │ │ ├── coins-bip32 v0.8.7 +│ │ │ │ │ │ ├── bs58 v0.5.1 +│ │ │ │ │ │ │ └── sha2 v0.10.8 (*) +│ │ │ │ │ │ ├── coins-core v0.8.7 +│ │ │ │ │ │ │ ├── base64 v0.21.7 +│ │ │ │ │ │ │ ├── bech32 v0.9.1 +│ │ │ │ │ │ │ ├── bs58 v0.5.1 (*) +│ │ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ │ │ ├── hex v0.4.3 +│ │ │ │ │ │ │ ├── ripemd v0.1.3 +│ │ │ │ │ │ │ │ └── digest v0.10.7 (*) +│ │ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ │ ├── serde_derive v1.0.215 (proc-macro) (*) +│ │ │ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ │ │ ├── sha3 v0.10.8 (*) +│ │ │ │ │ │ │ └── thiserror v1.0.69 (*) +│ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ ├── hmac v0.12.1 (*) +│ │ │ │ │ │ ├── k256 v0.13.4 (*) +│ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ │ └── thiserror v1.0.69 (*) +│ │ │ │ │ ├── coins-bip39 v0.8.7 +│ │ │ │ │ │ ├── bitvec v1.0.1 +│ │ │ │ │ │ │ ├── funty v2.0.0 +│ │ │ │ │ │ │ ├── radium v0.7.0 +│ │ │ │ │ │ │ ├── tap v1.0.1 +│ │ │ │ │ │ │ └── wyz v0.5.1 +│ │ │ │ │ │ │ └── tap v1.0.1 +│ │ │ │ │ │ ├── coins-bip32 v0.8.7 (*) +│ │ │ │ │ │ ├── hmac v0.12.1 (*) +│ │ │ │ │ │ ├── once_cell v1.20.2 +│ │ │ │ │ │ ├── pbkdf2 v0.12.2 +│ │ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ │ └── hmac v0.12.1 (*) +│ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ │ └── thiserror v1.0.69 (*) +│ │ │ │ │ ├── const-hex v1.14.0 (*) +│ │ │ │ │ ├── elliptic-curve v0.13.8 (*) +│ │ │ │ │ ├── eth-keystore v0.5.0 +│ │ │ │ │ │ ├── aes v0.8.4 (*) +│ │ │ │ │ │ ├── ctr v0.9.2 (*) +│ │ │ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ │ │ ├── hex v0.4.3 +│ │ │ │ │ │ ├── hmac v0.12.1 (*) +│ │ │ │ │ │ ├── pbkdf2 v0.11.0 +│ │ │ │ │ │ │ └── digest v0.10.7 (*) +│ │ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ │ ├── scrypt v0.10.0 +│ │ │ │ │ │ │ ├── hmac v0.12.1 (*) +│ │ │ │ │ │ │ ├── pbkdf2 v0.11.0 (*) +│ │ │ │ │ │ │ ├── salsa20 v0.10.2 +│ │ │ │ │ │ │ │ └── cipher v0.4.4 (*) +│ │ │ │ │ │ │ └── sha2 v0.10.8 (*) +│ │ │ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ │ ├── sha3 v0.10.8 (*) +│ │ │ │ │ │ ├── thiserror v1.0.69 (*) +│ │ │ │ │ │ └── uuid v0.8.2 +│ │ │ │ │ │ ├── getrandom v0.2.15 (*) +│ │ │ │ │ │ └── serde v1.0.215 (*) +│ │ │ │ │ ├── ethers-core v2.0.14 (*) +│ │ │ │ │ ├── rand v0.8.5 (*) +│ │ │ │ │ ├── sha2 v0.10.8 (*) +│ │ │ │ │ ├── thiserror v1.0.69 (*) +│ │ │ │ │ └── tracing v0.1.41 (*) +│ │ │ │ ├── futures-channel v0.3.31 (*) +│ │ │ │ ├── futures-locks v0.7.1 +│ │ │ │ │ ├── futures-channel v0.3.31 (*) +│ │ │ │ │ └── futures-task v0.3.31 +│ │ │ │ ├── futures-util v0.3.31 (*) +│ │ │ │ ├── instant v0.1.13 (*) +│ │ │ │ ├── reqwest v0.11.27 (*) +│ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ ├── serde_json v1.0.133 (*) +│ │ │ │ ├── thiserror v1.0.69 (*) +│ │ │ │ ├── tokio v1.42.0 (*) +│ │ │ │ ├── tracing v0.1.41 (*) +│ │ │ │ ├── tracing-futures v0.2.5 (*) +│ │ │ │ └── url v2.5.4 (*) +│ │ │ ├── ethers-providers v2.0.14 (*) +│ │ │ └── ethers-signers v2.0.14 (*) +│ │ ├── hex v0.4.3 +│ │ ├── k256 v0.13.4 (*) +│ │ ├── openmls_basic_credential v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) +│ │ │ ├── ed25519-dalek v2.1.1 (*) +│ │ │ ├── openmls_traits v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ │ │ ├── p256 v0.13.2 (*) +│ │ │ ├── rand v0.8.5 (*) +│ │ │ ├── serde v1.0.215 (*) +│ │ │ └── tls_codec v0.4.1 (*) +│ │ ├── openmls_traits v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ │ ├── rand v0.8.5 (*) +│ │ ├── rand_chacha v0.3.1 (*) +│ │ ├── rustc-hex v2.1.0 +│ │ ├── serde v1.0.215 (*) +│ │ ├── sha2 v0.10.8 (*) +│ │ ├── sha3 v0.10.8 (*) +│ │ ├── thiserror v2.0.6 +│ │ │ └── thiserror-impl v2.0.6 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ ├── quote v1.0.37 (*) +│ │ │ └── syn v2.0.90 (*) +│ │ ├── tls_codec v0.4.1 (*) +│ │ ├── tracing v0.1.41 (*) +│ │ └── zeroize v1.8.1 (*) +│ │ [dev-dependencies] +│ │ ├── bincode v1.3.3 +│ │ │ └── serde v1.0.215 (*) +│ │ └── openmls_basic_credential v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ │ [dev-dependencies] +│ │ ├── thiserror v2.0.6 (*) +│ │ └── tokio v1.42.0 (*) +│ ├── xmtp_v2 v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_v2) +│ │ ├── aes-gcm v0.10.3 (*) +│ │ ├── ecdsa v0.15.1 +│ │ │ ├── der v0.6.1 +│ │ │ │ ├── const-oid v0.9.6 +│ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ ├── elliptic-curve v0.12.3 +│ │ │ │ ├── base16ct v0.1.1 +│ │ │ │ ├── crypto-bigint v0.4.9 +│ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ ├── subtle v2.6.1 +│ │ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ │ ├── der v0.6.1 (*) +│ │ │ │ ├── digest v0.10.7 (*) +│ │ │ │ ├── ff v0.12.1 +│ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ └── subtle v2.6.1 +│ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ ├── group v0.12.1 +│ │ │ │ │ ├── ff v0.12.1 (*) +│ │ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ │ └── subtle v2.6.1 +│ │ │ │ ├── hkdf v0.12.4 (*) +│ │ │ │ ├── pkcs8 v0.9.0 +│ │ │ │ │ ├── der v0.6.1 (*) +│ │ │ │ │ └── spki v0.6.0 +│ │ │ │ │ ├── base64ct v1.6.0 +│ │ │ │ │ └── der v0.6.1 (*) +│ │ │ │ ├── rand_core v0.6.4 (*) +│ │ │ │ ├── sec1 v0.3.0 +│ │ │ │ │ ├── base16ct v0.1.1 +│ │ │ │ │ ├── der v0.6.1 (*) +│ │ │ │ │ ├── generic-array v0.14.7 (*) +│ │ │ │ │ ├── pkcs8 v0.9.0 (*) +│ │ │ │ │ ├── subtle v2.6.1 +│ │ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ │ ├── subtle v2.6.1 +│ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ ├── rfc6979 v0.3.1 +│ │ │ │ ├── crypto-bigint v0.4.9 (*) +│ │ │ │ ├── hmac v0.12.1 (*) +│ │ │ │ └── zeroize v1.8.1 (*) +│ │ │ └── signature v2.0.0 (*) +│ │ ├── generic-array v0.14.7 (*) +│ │ ├── hex v0.4.3 +│ │ ├── hkdf v0.12.4 (*) +│ │ ├── k256 v0.12.0 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── ecdsa v0.15.1 (*) +│ │ │ ├── elliptic-curve v0.12.3 (*) +│ │ │ ├── once_cell v1.20.2 +│ │ │ ├── sha2 v0.10.8 (*) +│ │ │ └── signature v2.0.0 (*) +│ │ ├── rand v0.8.5 (*) +│ │ ├── sha2 v0.10.8 (*) +│ │ └── sha3 v0.10.8 (*) +│ └── zeroize v1.8.1 (*) +│ [dev-dependencies] +│ ├── uuid v1.11.0 +│ │ ├── getrandom v0.2.15 (*) +│ │ └── rand v0.8.5 (*) +│ └── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +├── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) +├── xmtp_cryptography v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_cryptography) (*) +├── xmtp_id v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_id) +│ ├── async-trait v0.1.83 (proc-macro) (*) +│ ├── chrono v0.4.39 (*) +│ ├── ed25519-dalek v2.1.1 (*) +│ ├── ethers v2.0.14 (*) +│ ├── futures v0.3.31 (*) +│ ├── hex v0.4.3 +│ ├── openmls v0.6.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ ├── openmls_traits v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ ├── prost v0.13.4 (*) +│ ├── rand v0.8.5 (*) +│ ├── regex v1.11.1 (*) +│ ├── rustc-hex v2.1.0 +│ ├── serde v1.0.215 (*) +│ ├── serde_json v1.0.133 (*) +│ ├── sha2 v0.10.8 (*) +│ ├── thiserror v2.0.6 (*) +│ ├── tokio v1.42.0 (*) +│ ├── tracing v0.1.41 (*) +│ ├── url v2.5.4 (*) +│ ├── web-time v1.1.0 +│ ├── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) +│ ├── xmtp_cryptography v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_cryptography) (*) +│ └── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +│ [dev-dependencies] +│ ├── ed25519-dalek v2.1.1 (*) +│ ├── wasm-bindgen-test v0.3.49 +│ │ ├── js-sys v0.3.76 (*) +│ │ ├── scoped-tls v1.0.1 +│ │ ├── wasm-bindgen v0.2.99 (*) +│ │ ├── wasm-bindgen-futures v0.4.49 +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── js-sys v0.3.76 (*) +│ │ │ ├── once_cell v1.20.2 +│ │ │ └── wasm-bindgen v0.2.99 (*) +│ │ └── wasm-bindgen-test-macro v0.3.49 (proc-macro) +│ │ ├── proc-macro2 v1.0.92 (*) +│ │ ├── quote v1.0.37 (*) +│ │ └── syn v2.0.90 (*) +│ ├── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) +│ └── xmtp_v2 v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_v2) (*) +├── xmtp_mls v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_mls) +│ ├── aes-gcm v0.10.3 (*) +│ ├── async-stream v0.3.6 (*) +│ ├── async-trait v0.1.83 (proc-macro) (*) +│ ├── bincode v1.3.3 (*) +│ ├── chrono v0.4.39 (*) +│ ├── console_error_panic_hook v0.1.7 +│ │ ├── cfg-if v1.0.0 +│ │ └── wasm-bindgen v0.2.99 (*) +│ ├── const_format v0.2.34 +│ │ └── const_format_proc_macros v0.2.34 (proc-macro) +│ │ ├── proc-macro2 v1.0.92 (*) +│ │ ├── quote v1.0.37 (*) +│ │ └── unicode-xid v0.2.6 +│ ├── diesel v2.2.4 (https://github.com/diesel-rs/diesel?branch=master#b3cca0b4) +│ │ ├── diesel_derives v2.2.0 (proc-macro) (https://github.com/diesel-rs/diesel?branch=master#b3cca0b4) +│ │ │ ├── diesel_table_macro_syntax v0.2.0 (https://github.com/diesel-rs/diesel?branch=master#b3cca0b4) +│ │ │ │ └── syn v2.0.90 (*) +│ │ │ ├── dsl_auto_type v0.1.0 (https://github.com/diesel-rs/diesel?branch=master#b3cca0b4) +│ │ │ │ ├── darling v0.20.10 +│ │ │ │ │ ├── darling_core v0.20.10 +│ │ │ │ │ │ ├── fnv v1.0.7 +│ │ │ │ │ │ ├── ident_case v1.0.1 +│ │ │ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ │ ├── strsim v0.11.1 +│ │ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ │ └── darling_macro v0.20.10 (proc-macro) +│ │ │ │ │ ├── darling_core v0.20.10 (*) +│ │ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ │ └── syn v2.0.90 (*) +│ │ │ │ ├── either v1.13.0 +│ │ │ │ ├── heck v0.5.0 +│ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ └── syn v2.0.90 (*) +│ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ ├── quote v1.0.37 (*) +│ │ │ └── syn v2.0.90 (*) +│ │ ├── downcast-rs v1.2.1 +│ │ ├── libsqlite3-sys v0.29.0 +│ │ │ └── openssl-sys v0.9.104 +│ │ │ └── libc v0.2.168 +│ │ │ [build-dependencies] +│ │ │ ├── cc v1.2.3 (*) +│ │ │ ├── openssl-src v300.4.1+3.4.0 +│ │ │ │ └── cc v1.2.3 (*) +│ │ │ ├── pkg-config v0.3.31 +│ │ │ └── vcpkg v0.2.15 +│ │ │ [build-dependencies] +│ │ │ ├── cc v1.2.3 (*) +│ │ │ ├── pkg-config v0.3.31 +│ │ │ └── vcpkg v0.2.15 +│ │ └── r2d2 v0.8.10 +│ │ ├── log v0.4.22 +│ │ ├── parking_lot v0.12.3 (*) +│ │ └── scheduled-thread-pool v0.2.7 +│ │ └── parking_lot v0.12.3 (*) +│ ├── diesel_migrations v2.2.0 (https://github.com/diesel-rs/diesel?branch=master#b3cca0b4) +│ │ ├── diesel v2.2.4 (https://github.com/diesel-rs/diesel?branch=master#b3cca0b4) (*) +│ │ ├── migrations_internals v2.2.0 (https://github.com/diesel-rs/diesel?branch=master#b3cca0b4) +│ │ │ ├── serde v1.0.215 (*) +│ │ │ └── toml v0.8.19 (*) +│ │ └── migrations_macros v2.2.0 (proc-macro) (https://github.com/diesel-rs/diesel?branch=master#b3cca0b4) +│ │ ├── migrations_internals v2.2.0 (https://github.com/diesel-rs/diesel?branch=master#b3cca0b4) (*) +│ │ ├── proc-macro2 v1.0.92 (*) +│ │ └── quote v1.0.37 (*) +│ ├── dyn-clone v1.0.17 +│ ├── futures v0.3.31 (*) +│ ├── hex v0.4.3 +│ ├── hkdf v0.12.4 (*) +│ ├── hmac v0.12.1 (*) +│ ├── libsqlite3-sys v0.29.0 (*) +│ ├── mockall v0.13.1 +│ │ ├── cfg-if v1.0.0 +│ │ ├── downcast v0.11.0 +│ │ ├── fragile v2.0.0 +│ │ ├── mockall_derive v0.13.1 (proc-macro) +│ │ │ ├── cfg-if v1.0.0 +│ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ ├── quote v1.0.37 (*) +│ │ │ └── syn v2.0.90 (*) +│ │ ├── predicates v3.1.2 +│ │ │ ├── anstyle v1.0.10 +│ │ │ └── predicates-core v1.0.8 +│ │ └── predicates-tree v1.0.11 +│ │ ├── predicates-core v1.0.8 +│ │ └── termtree v0.4.1 +│ ├── openmls v0.6.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ ├── openmls_rust_crypto v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ ├── openmls_traits v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ ├── openssl v0.10.68 +│ │ ├── bitflags v2.6.0 +│ │ ├── cfg-if v1.0.0 +│ │ ├── foreign-types v0.3.2 +│ │ │ └── foreign-types-shared v0.1.1 +│ │ ├── libc v0.2.168 +│ │ ├── once_cell v1.20.2 +│ │ ├── openssl-macros v0.1.1 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ ├── quote v1.0.37 (*) +│ │ │ └── syn v2.0.90 (*) +│ │ └── openssl-sys v0.9.104 (*) +│ ├── openssl-sys v0.9.104 (*) +│ ├── parking_lot v0.12.3 (*) +│ ├── pin-project-lite v0.2.15 +│ ├── prost v0.13.4 (*) +│ ├── rand v0.8.5 (*) +│ ├── reqwest v0.12.9 +│ │ ├── base64 v0.22.1 +│ │ ├── bytes v1.9.0 (*) +│ │ ├── encoding_rs v0.8.35 (*) +│ │ ├── futures-core v0.3.31 +│ │ ├── futures-util v0.3.31 (*) +│ │ ├── h2 v0.4.7 (*) +│ │ ├── http v1.2.0 (*) +│ │ ├── http-body v1.0.1 (*) +│ │ ├── http-body-util v0.1.2 (*) +│ │ ├── hyper v1.5.1 (*) +│ │ ├── hyper-tls v0.6.0 +│ │ │ ├── bytes v1.9.0 (*) +│ │ │ ├── http-body-util v0.1.2 (*) +│ │ │ ├── hyper v1.5.1 (*) +│ │ │ ├── hyper-util v0.1.10 (*) +│ │ │ ├── native-tls v0.2.12 (*) +│ │ │ ├── tokio v1.42.0 (*) +│ │ │ ├── tokio-native-tls v0.3.1 (*) +│ │ │ └── tower-service v0.3.3 +│ │ ├── hyper-util v0.1.10 (*) +│ │ ├── ipnet v2.10.1 +│ │ ├── log v0.4.22 +│ │ ├── mime v0.3.17 +│ │ ├── native-tls v0.2.12 (*) +│ │ ├── once_cell v1.20.2 +│ │ ├── percent-encoding v2.3.1 +│ │ ├── pin-project-lite v0.2.15 +│ │ ├── rustls-pemfile v2.2.0 (*) +│ │ ├── serde v1.0.215 (*) +│ │ ├── serde_json v1.0.133 (*) +│ │ ├── serde_urlencoded v0.7.1 (*) +│ │ ├── sync_wrapper v1.0.2 (*) +│ │ ├── system-configuration v0.6.1 +│ │ │ ├── bitflags v2.6.0 +│ │ │ ├── core-foundation v0.9.4 (*) +│ │ │ └── system-configuration-sys v0.6.0 +│ │ │ ├── core-foundation-sys v0.8.7 +│ │ │ └── libc v0.2.168 +│ │ ├── tokio v1.42.0 (*) +│ │ ├── tokio-native-tls v0.3.1 (*) +│ │ ├── tokio-util v0.7.13 (*) +│ │ ├── tower-service v0.3.3 +│ │ └── url v2.5.4 (*) +│ ├── serde v1.0.215 (*) +│ ├── serde_json v1.0.133 (*) +│ ├── sha2 v0.10.8 (*) +│ ├── thiserror v2.0.6 (*) +│ ├── tls_codec v0.4.1 (*) +│ ├── tokio v1.42.0 (*) +│ ├── tokio-stream v0.1.17 (*) +│ ├── tracing v0.1.41 (*) +│ ├── tracing-subscriber v0.3.19 (*) +│ ├── tracing-wasm v0.2.1 +│ │ ├── tracing v0.1.41 (*) +│ │ ├── tracing-subscriber v0.3.19 (*) +│ │ └── wasm-bindgen v0.2.99 (*) +│ ├── trait-variant v0.1.2 (proc-macro) +│ │ ├── proc-macro2 v1.0.92 (*) +│ │ ├── quote v1.0.37 (*) +│ │ └── syn v2.0.90 (*) +│ ├── xmtp_api_grpc v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_grpc) (*) +│ ├── xmtp_api_http v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_http) +│ │ ├── async-trait v0.1.83 (proc-macro) (*) +│ │ ├── bytes v1.9.0 (*) +│ │ ├── futures v0.3.31 (*) +│ │ ├── pin-project-lite v0.2.15 +│ │ ├── reqwest v0.12.9 (*) +│ │ ├── serde v1.0.215 (*) +│ │ ├── serde_json v1.0.133 (*) +│ │ ├── thiserror v2.0.6 (*) +│ │ ├── tokio v1.42.0 (*) +│ │ ├── tracing v0.1.41 (*) +│ │ └── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +│ │ [dev-dependencies] +│ │ ├── tokio v1.42.0 (*) +│ │ └── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +│ ├── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) +│ ├── xmtp_content_types v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_content_types) +│ │ ├── prost v0.13.4 (*) +│ │ ├── rand v0.8.5 (*) +│ │ ├── thiserror v2.0.6 (*) +│ │ ├── tonic v0.12.3 (*) +│ │ ├── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) +│ │ └── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +│ │ [dev-dependencies] +│ │ └── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) +│ ├── xmtp_cryptography v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_cryptography) (*) +│ ├── xmtp_id v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_id) (*) +│ ├── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +│ └── zeroize v1.8.1 (*) +│ [dev-dependencies] +│ ├── anyhow v1.0.94 +│ ├── const_format v0.2.34 (*) +│ ├── ctor v0.2.9 (proc-macro) (*) +│ ├── ethers v2.0.14 (*) +│ ├── mockall v0.13.1 (*) +│ ├── mockito v1.6.1 +│ │ ├── assert-json-diff v2.0.2 +│ │ │ ├── serde v1.0.215 (*) +│ │ │ └── serde_json v1.0.133 (*) +│ │ ├── bytes v1.9.0 (*) +│ │ ├── colored v2.1.0 +│ │ │ └── lazy_static v1.5.0 +│ │ ├── futures-util v0.3.31 (*) +│ │ ├── http v1.2.0 (*) +│ │ ├── http-body v1.0.1 (*) +│ │ ├── http-body-util v0.1.2 (*) +│ │ ├── hyper v1.5.1 (*) +│ │ ├── hyper-util v0.1.10 (*) +│ │ ├── log v0.4.22 +│ │ ├── rand v0.8.5 (*) +│ │ ├── regex v1.11.1 (*) +│ │ ├── serde_json v1.0.133 (*) +│ │ ├── serde_urlencoded v0.7.1 (*) +│ │ ├── similar v2.6.0 +│ │ └── tokio v1.42.0 (*) +│ ├── openmls v0.6.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ ├── openmls_basic_credential v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +│ ├── tempfile v3.15.0 (*) +│ ├── tracing-subscriber v0.3.19 (*) +│ ├── wasm-bindgen-test v0.3.49 (*) +│ ├── xmtp_api_grpc v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_grpc) (*) +│ ├── xmtp_api_http v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_http) (*) +│ ├── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) +│ ├── xmtp_id v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_id) (*) +│ └── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +└── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +[build-dependencies] +└── napi-build v2.1.3 + +bindings_wasm v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/bindings_wasm) +├── console_error_panic_hook v0.1.7 (*) +├── hex v0.4.3 +├── js-sys v0.3.76 (*) +├── prost v0.13.4 (*) +├── serde v1.0.215 (*) +├── serde-wasm-bindgen v0.6.5 +│ ├── js-sys v0.3.76 (*) +│ ├── serde v1.0.215 (*) +│ └── wasm-bindgen v0.2.99 (*) +├── tokio v1.42.0 (*) +├── tracing v0.1.41 (*) +├── tracing-subscriber v0.3.19 (*) +├── tracing-web v0.1.3 +│ ├── js-sys v0.3.76 (*) +│ ├── tracing-core v0.1.33 (*) +│ ├── tracing-subscriber v0.3.19 (*) +│ ├── wasm-bindgen v0.2.99 (*) +│ └── web-sys v0.3.76 +│ ├── js-sys v0.3.76 (*) +│ └── wasm-bindgen v0.2.99 (*) +├── wasm-bindgen v0.2.99 (*) +├── wasm-bindgen-futures v0.4.49 (*) +├── xmtp_api_http v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_http) (*) +├── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) +├── xmtp_cryptography v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_cryptography) (*) +├── xmtp_id v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_id) (*) +├── xmtp_mls v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_mls) (*) +└── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +[dev-dependencies] +└── wasm-bindgen-test v0.3.49 (*) + +mls_validation_service v0.1.4 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/mls_validation_service) +├── clap v4.5.23 +│ ├── clap_builder v4.5.23 +│ │ ├── anstream v0.6.18 +│ │ │ ├── anstyle v1.0.10 +│ │ │ ├── anstyle-parse v0.2.6 +│ │ │ │ └── utf8parse v0.2.2 +│ │ │ ├── anstyle-query v1.1.2 +│ │ │ ├── colorchoice v1.0.3 +│ │ │ ├── is_terminal_polyfill v1.70.1 +│ │ │ └── utf8parse v0.2.2 +│ │ ├── anstyle v1.0.10 +│ │ ├── clap_lex v0.7.4 +│ │ └── strsim v0.11.1 +│ └── clap_derive v4.5.18 (proc-macro) +│ ├── heck v0.5.0 +│ ├── proc-macro2 v1.0.92 (*) +│ ├── quote v1.0.37 (*) +│ └── syn v2.0.90 (*) +├── ethers v2.0.14 (*) +├── futures v0.3.31 (*) +├── hex v0.4.3 +├── openmls v0.6.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +├── openmls_rust_crypto v0.3.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +├── thiserror v2.0.6 (*) +├── tokio v1.42.0 (*) +├── tonic v0.12.3 (*) +├── tracing v0.1.41 (*) +├── tracing-subscriber v0.3.19 (*) +├── warp v0.3.7 +│ ├── bytes v1.9.0 (*) +│ ├── futures-channel v0.3.31 (*) +│ ├── futures-util v0.3.31 (*) +│ ├── headers v0.3.9 +│ │ ├── base64 v0.21.7 +│ │ ├── bytes v1.9.0 (*) +│ │ ├── headers-core v0.2.0 +│ │ │ └── http v0.2.12 (*) +│ │ ├── http v0.2.12 (*) +│ │ ├── httpdate v1.0.3 +│ │ ├── mime v0.3.17 +│ │ └── sha1 v0.10.6 (*) +│ ├── http v0.2.12 (*) +│ ├── hyper v0.14.31 (*) +│ ├── log v0.4.22 +│ ├── mime v0.3.17 +│ ├── mime_guess v2.0.5 +│ │ ├── mime v0.3.17 +│ │ └── unicase v2.8.0 +│ │ [build-dependencies] +│ │ └── unicase v2.8.0 +│ ├── multer v2.1.0 +│ │ ├── bytes v1.9.0 (*) +│ │ ├── encoding_rs v0.8.35 (*) +│ │ ├── futures-util v0.3.31 (*) +│ │ ├── http v0.2.12 (*) +│ │ ├── httparse v1.9.5 +│ │ ├── log v0.4.22 +│ │ ├── memchr v2.7.4 +│ │ ├── mime v0.3.17 +│ │ └── spin v0.9.8 +│ │ [build-dependencies] +│ │ └── version_check v0.9.5 +│ ├── percent-encoding v2.3.1 +│ ├── pin-project v1.1.7 (*) +│ ├── scoped-tls v1.0.1 +│ ├── serde v1.0.215 (*) +│ ├── serde_json v1.0.133 (*) +│ ├── serde_urlencoded v0.7.1 (*) +│ ├── tokio v1.42.0 (*) +│ ├── tokio-tungstenite v0.21.0 +│ │ ├── futures-util v0.3.31 (*) +│ │ ├── log v0.4.22 +│ │ ├── tokio v1.42.0 (*) +│ │ └── tungstenite v0.21.0 +│ │ ├── byteorder v1.5.0 +│ │ ├── bytes v1.9.0 (*) +│ │ ├── data-encoding v2.6.0 +│ │ ├── http v1.2.0 (*) +│ │ ├── httparse v1.9.5 +│ │ ├── log v0.4.22 +│ │ ├── rand v0.8.5 (*) +│ │ ├── sha1 v0.10.6 (*) +│ │ ├── thiserror v1.0.69 (*) +│ │ ├── url v2.5.4 (*) +│ │ └── utf-8 v0.7.6 +│ ├── tokio-util v0.7.13 (*) +│ ├── tower-service v0.3.3 +│ └── tracing v0.1.41 (*) +├── xmtp_cryptography v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_cryptography) (*) +├── xmtp_id v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_id) (*) +├── xmtp_mls v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_mls) (*) +└── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +[build-dependencies] +└── vergen-git2 v1.0.2 + ├── anyhow v1.0.94 + ├── derive_builder v0.20.2 + │ └── derive_builder_macro v0.20.2 (proc-macro) + │ ├── derive_builder_core v0.20.2 + │ │ ├── darling v0.20.10 (*) + │ │ ├── proc-macro2 v1.0.92 (*) + │ │ ├── quote v1.0.37 (*) + │ │ └── syn v2.0.90 (*) + │ └── syn v2.0.90 (*) + ├── git2 v0.19.0 + │ ├── bitflags v2.6.0 + │ ├── libc v0.2.168 + │ ├── libgit2-sys v0.17.0+1.8.1 + │ │ ├── libc v0.2.168 + │ │ └── libz-sys v1.1.20 + │ │ └── libc v0.2.168 + │ │ [build-dependencies] + │ │ ├── cc v1.2.3 (*) + │ │ ├── pkg-config v0.3.31 + │ │ └── vcpkg v0.2.15 + │ │ [build-dependencies] + │ │ ├── cc v1.2.3 (*) + │ │ └── pkg-config v0.3.31 + │ ├── log v0.4.22 + │ └── url v2.5.4 + │ ├── form_urlencoded v1.2.1 + │ │ └── percent-encoding v2.3.1 + │ ├── idna v1.0.3 (*) + │ └── percent-encoding v2.3.1 + ├── time v0.3.37 + │ ├── deranged v0.3.11 (*) + │ ├── itoa v1.0.14 + │ ├── libc v0.2.168 + │ ├── num-conv v0.1.0 + │ ├── num_threads v0.1.7 + │ │ └── libc v0.2.168 + │ ├── powerfmt v0.2.0 + │ └── time-core v0.1.2 + ├── vergen v9.0.2 + │ ├── anyhow v1.0.94 + │ ├── derive_builder v0.20.2 (*) + │ ├── time v0.3.37 (*) + │ └── vergen-lib v0.1.5 + │ ├── anyhow v1.0.94 + │ └── derive_builder v0.20.2 (*) + │ [build-dependencies] + │ └── rustversion v1.0.18 (proc-macro) + │ [build-dependencies] + │ └── rustversion v1.0.18 (proc-macro) + └── vergen-lib v0.1.5 (*) + [build-dependencies] + └── rustversion v1.0.18 (proc-macro) +[dev-dependencies] +├── anyhow v1.0.94 +├── ethers v2.0.14 (*) +├── rand v0.8.5 (*) +├── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) +├── xmtp_id v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_id) (*) +└── xmtp_mls v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_mls) (*) + +xdbg v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_debug) +├── chrono v0.4.39 (*) +├── clap v4.5.23 (*) +├── clap-verbosity-flag v3.0.1 +│ ├── clap v4.5.23 (*) +│ └── log v0.4.22 +├── color-eyre v0.6.3 +│ ├── backtrace v0.3.71 +│ │ ├── addr2line v0.21.0 +│ │ │ └── gimli v0.28.1 +│ │ ├── cfg-if v1.0.0 +│ │ ├── libc v0.2.168 +│ │ ├── miniz_oxide v0.7.4 +│ │ │ └── adler v1.0.2 +│ │ ├── object v0.32.2 +│ │ │ └── memchr v2.7.4 +│ │ └── rustc-demangle v0.1.24 +│ │ [build-dependencies] +│ │ └── cc v1.2.3 (*) +│ ├── color-spantrace v0.2.1 +│ │ ├── once_cell v1.20.2 +│ │ ├── owo-colors v3.5.0 +│ │ ├── tracing-core v0.1.33 (*) +│ │ └── tracing-error v0.2.1 +│ │ ├── tracing v0.1.41 (*) +│ │ └── tracing-subscriber v0.3.19 (*) +│ ├── eyre v0.6.12 (*) +│ ├── indenter v0.3.3 +│ ├── once_cell v1.20.2 +│ ├── owo-colors v3.5.0 +│ └── tracing-error v0.2.1 (*) +├── const_format v0.2.34 (*) +├── directories v5.0.1 +│ └── dirs-sys v0.4.1 +│ ├── libc v0.2.168 +│ └── option-ext v0.2.0 +├── ecdsa v0.16.9 (*) +├── ethers v2.0.14 (*) +├── fdlimit v0.3.0 +│ ├── libc v0.2.168 +│ └── thiserror v1.0.69 (*) +├── fs_extra v1.3.0 +├── hex v0.4.3 +├── indicatif v0.17.9 +│ ├── console v0.15.8 +│ │ ├── lazy_static v1.5.0 +│ │ ├── libc v0.2.168 +│ │ └── unicode-width v0.1.14 +│ ├── number_prefix v0.4.0 +│ ├── portable-atomic v1.10.0 +│ └── unicode-width v0.2.0 +├── k256 v0.13.4 (*) +├── lipsum v0.9.1 +│ ├── rand v0.8.5 (*) +│ └── rand_chacha v0.3.1 (*) +├── miniserde v0.1.41 +│ ├── itoa v1.0.14 +│ ├── mini-internal v0.1.41 (proc-macro) +│ │ ├── proc-macro2 v1.0.92 (*) +│ │ ├── quote v1.0.37 (*) +│ │ └── syn v2.0.90 (*) +│ └── ryu v1.0.18 +├── openmls v0.6.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +├── owo-colors v4.1.0 +├── prost v0.13.4 (*) +├── rand v0.8.5 (*) +├── redb v2.4.0 +│ └── libc v0.2.168 +├── speedy v0.8.7 +│ ├── memoffset v0.9.1 +│ │ [build-dependencies] +│ │ └── autocfg v1.4.0 +│ └── speedy-derive v0.8.7 (proc-macro) +│ ├── proc-macro2 v1.0.92 (*) +│ ├── quote v1.0.37 (*) +│ └── syn v2.0.90 (*) +├── tempfile v3.15.0 (*) +├── thiserror v2.0.6 (*) +├── tokio v1.42.0 (*) +├── tracing v0.1.41 (*) +├── tracing-appender v0.2.3 +│ ├── crossbeam-channel v0.5.13 +│ │ └── crossbeam-utils v0.8.20 +│ ├── thiserror v1.0.69 (*) +│ ├── time v0.3.37 (*) +│ └── tracing-subscriber v0.3.19 (*) +├── tracing-logfmt v0.3.5 +│ ├── time v0.3.37 (*) +│ ├── tracing v0.1.41 (*) +│ ├── tracing-core v0.1.33 (*) +│ └── tracing-subscriber v0.3.19 (*) +├── tracing-subscriber v0.3.19 (*) +├── url v2.5.4 (*) +├── valuable v0.1.0 (*) +├── xmtp_api_grpc v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_grpc) (*) +├── xmtp_cryptography v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_cryptography) (*) +├── xmtp_id v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_id) (*) +├── xmtp_mls v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_mls) (*) +├── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +└── xxhash-rust v0.8.12 +[build-dependencies] +└── vergen-git2 v1.0.2 (*) + +xmtp_api_grpc v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_grpc) (*) + +xmtp_api_http v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_http) (*) + +xmtp_cli v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/examples/cli) +├── chrono v0.4.39 (*) +├── clap v4.5.23 (*) +├── color-eyre v0.6.3 (*) +├── ethers v2.0.14 (*) +├── futures v0.3.31 (*) +├── hex v0.4.3 +├── openmls v0.6.0 (https://github.com/xmtp/openmls?rev=043b347cb18d528647df36f500725ab57c41c7db#043b347c) (*) +├── owo-colors v4.1.0 +├── prost v0.13.4 (*) +├── serde v1.0.215 (*) +├── serde_json v1.0.133 (*) +├── thiserror v2.0.6 (*) +├── timeago v0.4.2 +│ ├── chrono v0.4.39 (*) +│ └── isolang v2.4.0 +│ └── phf v0.11.2 +│ └── phf_shared v0.11.2 +│ └── siphasher v0.3.11 +├── tokio v1.42.0 (*) +├── tracing v0.1.41 (*) +├── tracing-subscriber v0.3.19 (*) +├── valuable v0.1.0 (*) +├── valuable-serde v0.1.0 (*) +├── xmtp_api_grpc v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_grpc) (*) +├── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) +├── xmtp_content_types v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_content_types) (*) +├── xmtp_cryptography v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_cryptography) (*) +├── xmtp_id v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_id) (*) +├── xmtp_mls v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_mls) (*) +└── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) + +xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) + +xmtp_content_types v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_content_types) (*) + +xmtp_cryptography v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_cryptography) (*) + +xmtp_id v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_id) (*) + +xmtp_mls v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_mls) (*) + +xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) + +xmtp_user_preferences v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_user_preferences) +├── base64 v0.22.1 +├── prost v0.13.4 (*) +├── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +└── xmtp_v2 v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_v2) (*) +[dev-dependencies] +├── libsecp256k1 v0.7.1 +│ ├── arrayref v0.3.9 +│ ├── base64 v0.13.1 +│ ├── digest v0.9.0 +│ │ └── generic-array v0.14.7 (*) +│ ├── hmac-drbg v0.3.0 +│ │ ├── digest v0.9.0 (*) +│ │ ├── generic-array v0.14.7 (*) +│ │ └── hmac v0.8.1 +│ │ ├── crypto-mac v0.8.0 +│ │ │ ├── generic-array v0.14.7 (*) +│ │ │ └── subtle v2.6.1 +│ │ └── digest v0.9.0 (*) +│ ├── libsecp256k1-core v0.3.0 +│ │ ├── crunchy v0.2.2 +│ │ ├── digest v0.9.0 (*) +│ │ └── subtle v2.6.1 +│ ├── rand v0.8.5 (*) +│ ├── serde v1.0.215 (*) +│ ├── sha2 v0.9.9 +│ │ ├── block-buffer v0.9.0 +│ │ │ └── generic-array v0.14.7 (*) +│ │ ├── cfg-if v1.0.0 +│ │ ├── cpufeatures v0.2.16 (*) +│ │ ├── digest v0.9.0 (*) +│ │ └── opaque-debug v0.3.1 +│ └── typenum v1.17.0 +│ [build-dependencies] +│ ├── libsecp256k1-gen-ecmult v0.3.0 +│ │ └── libsecp256k1-core v0.3.0 +│ │ ├── crunchy v0.2.2 +│ │ ├── digest v0.9.0 (*) +│ │ └── subtle v2.6.1 +│ └── libsecp256k1-gen-genmult v0.3.0 +│ └── libsecp256k1-core v0.3.0 (*) +└── rand v0.8.5 (*) + +xmtp_v2 v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_v2) (*) + +xmtpv3 v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/bindings_ffi) +├── futures v0.3.31 (*) +├── parking_lot v0.12.3 (*) +├── prost v0.13.4 (*) +├── thiserror v2.0.6 (*) +├── tokio v1.42.0 (*) +├── tracing v0.1.41 (*) +├── tracing-subscriber v0.3.19 (*) +├── uniffi v0.28.3 +│ ├── anyhow v1.0.94 +│ ├── cargo_metadata v0.15.4 +│ │ ├── camino v1.1.9 (*) +│ │ ├── cargo-platform v0.1.9 (*) +│ │ ├── semver v1.0.23 (*) +│ │ ├── serde v1.0.215 (*) +│ │ ├── serde_json v1.0.133 (*) +│ │ └── thiserror v1.0.69 (*) +│ ├── uniffi_bindgen v0.28.3 +│ │ ├── anyhow v1.0.94 +│ │ ├── askama v0.12.1 +│ │ │ ├── askama_derive v0.12.5 (proc-macro) +│ │ │ │ ├── askama_parser v0.2.1 +│ │ │ │ │ └── nom v7.1.3 +│ │ │ │ │ ├── memchr v2.7.4 +│ │ │ │ │ └── minimal-lexical v0.2.1 +│ │ │ │ ├── basic-toml v0.1.9 +│ │ │ │ │ └── serde v1.0.215 (*) +│ │ │ │ ├── mime v0.3.17 +│ │ │ │ ├── mime_guess v2.0.5 (*) +│ │ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ │ ├── quote v1.0.37 (*) +│ │ │ │ ├── serde v1.0.215 (*) +│ │ │ │ └── syn v2.0.90 (*) +│ │ │ └── askama_escape v0.10.3 +│ │ ├── camino v1.1.9 (*) +│ │ ├── cargo_metadata v0.15.4 (*) +│ │ ├── fs-err v2.11.0 +│ │ │ [build-dependencies] +│ │ │ └── autocfg v1.4.0 +│ │ ├── glob v0.3.1 +│ │ ├── goblin v0.8.2 +│ │ │ ├── log v0.4.22 +│ │ │ ├── plain v0.2.3 +│ │ │ └── scroll v0.12.0 +│ │ │ └── scroll_derive v0.12.0 (proc-macro) +│ │ │ ├── proc-macro2 v1.0.92 (*) +│ │ │ ├── quote v1.0.37 (*) +│ │ │ └── syn v2.0.90 (*) +│ │ ├── heck v0.5.0 +│ │ ├── once_cell v1.20.2 +│ │ ├── paste v1.0.15 (proc-macro) +│ │ ├── serde v1.0.215 (*) +│ │ ├── textwrap v0.16.1 +│ │ │ └── smawk v0.3.2 +│ │ ├── toml v0.5.11 +│ │ │ └── serde v1.0.215 (*) +│ │ ├── uniffi_meta v0.28.3 +│ │ │ ├── anyhow v1.0.94 +│ │ │ ├── bytes v1.9.0 (*) +│ │ │ ├── siphasher v0.3.11 +│ │ │ └── uniffi_checksum_derive v0.28.3 (proc-macro) +│ │ │ ├── quote v1.0.37 (*) +│ │ │ └── syn v2.0.90 (*) +│ │ ├── uniffi_testing v0.28.3 +│ │ │ ├── anyhow v1.0.94 +│ │ │ ├── camino v1.1.9 (*) +│ │ │ ├── cargo_metadata v0.15.4 (*) +│ │ │ ├── fs-err v2.11.0 (*) +│ │ │ └── once_cell v1.20.2 +│ │ └── uniffi_udl v0.28.3 +│ │ ├── anyhow v1.0.94 +│ │ ├── textwrap v0.16.1 (*) +│ │ ├── uniffi_meta v0.28.3 (*) +│ │ ├── uniffi_testing v0.28.3 (*) +│ │ └── weedle2 v5.0.0 +│ │ └── nom v7.1.3 (*) +│ ├── uniffi_core v0.28.3 +│ │ ├── anyhow v1.0.94 +│ │ ├── async-compat v0.2.4 +│ │ │ ├── futures-core v0.3.31 +│ │ │ ├── futures-io v0.3.31 +│ │ │ ├── once_cell v1.20.2 +│ │ │ ├── pin-project-lite v0.2.15 +│ │ │ └── tokio v1.42.0 (*) +│ │ ├── bytes v1.9.0 (*) +│ │ ├── log v0.4.22 +│ │ ├── once_cell v1.20.2 +│ │ ├── paste v1.0.15 (proc-macro) +│ │ └── static_assertions v1.1.0 +│ └── uniffi_macros v0.28.3 (proc-macro) +│ ├── bincode v1.3.3 (*) +│ ├── camino v1.1.9 (*) +│ ├── fs-err v2.11.0 (*) +│ ├── once_cell v1.20.2 +│ ├── proc-macro2 v1.0.92 (*) +│ ├── quote v1.0.37 (*) +│ ├── serde v1.0.215 (*) +│ ├── syn v2.0.90 (*) +│ ├── toml v0.5.11 (*) +│ └── uniffi_meta v0.28.3 (*) +├── xmtp_api_grpc v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_grpc) (*) +├── xmtp_common v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/common) (*) +├── xmtp_content_types v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_content_types) (*) +├── xmtp_cryptography v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_cryptography) (*) +├── xmtp_id v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_id) (*) +├── xmtp_mls v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_mls) (*) +├── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) +├── xmtp_user_preferences v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_user_preferences) (*) +└── xmtp_v2 v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_v2) (*) +[build-dependencies] +└── uniffi v0.28.3 + ├── anyhow v1.0.94 + ├── cargo_metadata v0.15.4 (*) + ├── uniffi_build v0.28.3 + │ ├── anyhow v1.0.94 + │ ├── camino v1.1.9 (*) + │ └── uniffi_bindgen v0.28.3 + │ ├── anyhow v1.0.94 + │ ├── askama v0.12.1 (*) + │ ├── camino v1.1.9 (*) + │ ├── fs-err v2.11.0 (*) + │ ├── glob v0.3.1 + │ ├── goblin v0.8.2 (*) + │ ├── heck v0.5.0 + │ ├── once_cell v1.20.2 + │ ├── paste v1.0.15 (proc-macro) + │ ├── serde v1.0.215 (*) + │ ├── textwrap v0.16.1 (*) + │ ├── toml v0.5.11 (*) + │ ├── uniffi_meta v0.28.3 (*) + │ └── uniffi_udl v0.28.3 (*) + ├── uniffi_core v0.28.3 + │ ├── anyhow v1.0.94 + │ ├── bytes v1.9.0 (*) + │ ├── log v0.4.22 + │ ├── once_cell v1.20.2 + │ ├── paste v1.0.15 (proc-macro) + │ └── static_assertions v1.1.0 + └── uniffi_macros v0.28.3 (proc-macro) (*) +[dev-dependencies] +├── ethers v2.0.14 (*) +├── rand v0.8.5 (*) +├── tokio v1.42.0 (*) +├── uniffi v0.28.3 (*) +├── uuid v1.11.0 (*) +├── xmtp_api_grpc v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_api_grpc) (*) +├── xmtp_mls v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_mls) (*) +└── xmtp_proto v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xmtp_proto) (*) + +xtask v0.1.0 (/Users/insipx/Projects/xmtp/workspace-libxmtp/insipx/browser-streaming/xtask) +├── color-eyre v0.6.3 (*) +├── spinach v3.0.0 +├── xflags v0.3.2 +│ └── xflags-macros v0.3.2 (proc-macro) +└── xshell v0.2.7 + └── xshell-macros v0.2.7 (proc-macro) diff --git a/xmtp_api_grpc/Cargo.toml b/xmtp_api_grpc/Cargo.toml index b69a0d0c4..67ea6fb16 100644 --- a/xmtp_api_grpc/Cargo.toml +++ b/xmtp_api_grpc/Cargo.toml @@ -8,7 +8,7 @@ version.workspace = true async-stream.workspace = true async-trait = "0.1" base64.workspace = true -futures.workspace = true +futures = { workspace = true, features = ["alloc"] } hex.workspace = true prost = { workspace = true, features = ["prost-derive"] } tokio = { workspace = true, features = ["macros", "time"] } diff --git a/xmtp_api_http/Cargo.toml b/xmtp_api_http/Cargo.toml index b26a414a9..09a6a9214 100644 --- a/xmtp_api_http/Cargo.toml +++ b/xmtp_api_http/Cargo.toml @@ -8,16 +8,17 @@ license.workspace = true crate-type = ["cdylib", "rlib"] [dependencies] -async-stream.workspace = true futures = { workspace = true } tracing.workspace = true reqwest = { version = "0.12.5", features = ["json", "stream"] } serde = { workspace = true } serde_json = { workspace = true } -thiserror = "2.0" +thiserror.workspace = true tokio = { workspace = true, features = ["sync", "rt", "macros"] } xmtp_proto = { path = "../xmtp_proto", features = ["proto_full"] } async-trait = "0.1" +bytes = "1.9" +pin-project-lite = "0.2.15" [dev-dependencies] xmtp_proto = { path = "../xmtp_proto", features = ["test-utils"] } diff --git a/xmtp_api_http/src/http_stream.rs b/xmtp_api_http/src/http_stream.rs new file mode 100644 index 000000000..8e969b0c4 --- /dev/null +++ b/xmtp_api_http/src/http_stream.rs @@ -0,0 +1,231 @@ +//! Streams that work with HTTP POST requests + +use crate::util::GrpcResponse; +use futures::{ + stream::{self, Stream, StreamExt}, + Future, +}; +use reqwest::Response; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::Deserializer; +use std::{marker::PhantomData, pin::Pin, task::Poll}; +use xmtp_proto::{Error, ErrorKind}; + +#[derive(Deserialize, Serialize, Debug)] +pub(crate) struct SubscriptionItem { + pub result: T, +} + +#[cfg(target_arch = "wasm32")] +pub type BytesStream = stream::LocalBoxStream<'static, Result>; + +// #[cfg(not(target_arch = "wasm32"))] +// pub type BytesStream = Pin> + Send>>; + +#[cfg(not(target_arch = "wasm32"))] +pub type BytesStream = stream::BoxStream<'static, Result>; + +pin_project_lite::pin_project! { + #[project = PostStreamProject] + enum HttpPostStream { + NotStarted{#[pin] fut: F}, + // `Reqwest::bytes_stream` returns `impl Stream` rather than a type generic, + // so we can't use a type generic here + // this makes wasm a bit tricky. + Started { + #[pin] http: BytesStream, + remaining: Vec, + _marker: PhantomData, + }, + } +} + +impl Stream for HttpPostStream +where + F: Future>, + for<'de> R: Send + Deserialize<'de>, +{ + type Item = Result; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + use std::task::Poll::*; + match self.as_mut().project() { + PostStreamProject::NotStarted { fut } => match fut.poll(cx) { + Ready(response) => { + let s = response.unwrap().bytes_stream(); + self.set(Self::started(s)); + self.as_mut().poll_next(cx) + } + Pending => { + cx.waker().wake_by_ref(); + Pending + } + }, + PostStreamProject::Started { + ref mut http, + ref mut remaining, + .. + } => { + let mut pinned = std::pin::pin!(http); + let next = pinned.as_mut().poll_next(cx); + Self::on_bytes(next, remaining, cx) + } + } + } +} + +impl HttpPostStream +where + R: Send, +{ + #[cfg(not(target_arch = "wasm32"))] + fn started( + http: impl Stream> + Send + 'static, + ) -> Self { + Self::Started { + http: http.boxed(), + remaining: Vec::new(), + _marker: PhantomData, + } + } + + #[cfg(target_arch = "wasm32")] + fn started(http: impl Stream> + 'static) -> Self { + Self::Started { + http: http.boxed_local(), + remaining: Vec::new(), + _marker: PhantomData, + } + } +} + +impl HttpPostStream +where + F: Future>, + for<'de> R: Deserialize<'de> + DeserializeOwned + Send, +{ + fn new(request: F) -> Self { + Self::NotStarted { fut: request } + } + + fn on_bytes( + p: Poll>>, + remaining: &mut Vec, + cx: &mut std::task::Context<'_>, + ) -> Poll::Item>> { + use futures::task::Poll::*; + match p { + Ready(Some(bytes)) => { + let bytes = bytes.map_err(|e| { + Error::new(ErrorKind::SubscriptionUpdateError).with(e.to_string()) + })?; + let bytes = &[remaining.as_ref(), bytes.as_ref()].concat(); + let de = Deserializer::from_slice(bytes); + let mut stream = de.into_iter::>(); + 'messages: loop { + tracing::debug!("Waiting on next response ..."); + let response = stream.next(); + let res = match response { + Some(Ok(GrpcResponse::Ok(response))) => Ok(response), + Some(Ok(GrpcResponse::SubscriptionItem(item))) => Ok(item.result), + Some(Ok(GrpcResponse::Err(e))) => { + Err(Error::new(ErrorKind::MlsError).with(e.message)) + } + Some(Err(e)) => { + if e.is_eof() { + *remaining = (&**bytes)[stream.byte_offset()..].to_vec(); + return Pending; + } else { + Err(Error::new(ErrorKind::MlsError).with(e.to_string())) + } + } + Some(Ok(GrpcResponse::Empty {})) => continue 'messages, + None => return Ready(None), + }; + return Ready(Some(res)); + } + } + Ready(None) => Ready(None), + Pending => { + cx.waker().wake_by_ref(); + Pending + } + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl HttpPostStream +where + F: Future> + Unpin, + for<'de> R: Deserialize<'de> + DeserializeOwned + Send, +{ + /// Establish the initial HTTP Stream connection + fn establish(&mut self) -> () { + // we need to poll the future once to progress the future state & + // establish the initial POST request. + // It should always be pending + let noop_waker = futures::task::noop_waker(); + let mut cx = std::task::Context::from_waker(&noop_waker); + // let mut this = Pin::new(self); + let mut this = Pin::new(self); + let _ = this.poll_next_unpin(&mut cx); + } +} + +#[cfg(target_arch = "wasm32")] +impl HttpPostStream +where + F: Future>, + for<'de> R: Deserialize<'de> + DeserializeOwned + Send, +{ + fn establish(&mut self) -> () { + // we need to poll the future once to progress the future state & + // establish the initial POST request. + // It should always be pending + let noop_waker = futures::task::noop_waker(); + let mut cx = std::task::Context::from_waker(&noop_waker); + let mut this = unsafe { Pin::new_unchecked(self) }; + let _ = this.poll_next_unpin(&mut cx); + } +} + +#[cfg(target_arch = "wasm32")] +pub fn create_grpc_stream( + request: T, + endpoint: String, + http_client: reqwest::Client, +) -> stream::LocalBoxStream<'static, Result> { + create_grpc_stream_inner(request, endpoint, http_client).boxed_local() +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn create_grpc_stream( + request: T, + endpoint: String, + http_client: reqwest::Client, +) -> stream::BoxStream<'static, Result> +where + T: Serialize + 'static, + R: DeserializeOwned + Send + 'static, +{ + create_grpc_stream_inner(request, endpoint, http_client).boxed() +} + +fn create_grpc_stream_inner( + request: T, + endpoint: String, + http_client: reqwest::Client, +) -> impl Stream> +where + T: Serialize + 'static, + R: DeserializeOwned + Send + 'static, +{ + let request = http_client.post(endpoint).json(&request).send(); + let mut http = HttpPostStream::new(request); + http.establish(); + http +} diff --git a/xmtp_api_http/src/lib.rs b/xmtp_api_http/src/lib.rs index 80489fb3c..8a3f972c4 100755 --- a/xmtp_api_http/src/lib.rs +++ b/xmtp_api_http/src/lib.rs @@ -1,11 +1,13 @@ #![warn(clippy::unwrap_used)] pub mod constants; +mod http_stream; mod util; use futures::stream; +use http_stream::create_grpc_stream; use reqwest::header; -use util::{create_grpc_stream, handle_error}; +use util::handle_error; use xmtp_proto::api_client::{ClientWithMetadata, XmtpIdentityClient}; use xmtp_proto::xmtp::identity::api::v1::{ GetIdentityUpdatesRequest as GetIdentityUpdatesV2Request, diff --git a/xmtp_api_http/src/util.rs b/xmtp_api_http/src/util.rs index 8a839fc56..34c878c4a 100644 --- a/xmtp_api_http/src/util.rs +++ b/xmtp_api_http/src/util.rs @@ -1,9 +1,5 @@ -use futures::{ - stream::{self, StreamExt}, - Stream, -}; +use crate::http_stream::SubscriptionItem; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use serde_json::Deserializer; use std::io::Read; use xmtp_proto::{Error, ErrorKind}; @@ -23,11 +19,6 @@ pub(crate) struct ErrorResponse { details: Vec, } -#[derive(Deserialize, Serialize, Debug)] -pub(crate) struct SubscriptionItem { - pub result: T, -} - /// handle JSON response from gRPC, returning either /// the expected deserialized response object or a gRPC [`Error`] pub fn handle_error(reader: R) -> Result @@ -43,78 +34,6 @@ where } } -#[cfg(target_arch = "wasm32")] -pub fn create_grpc_stream< - T: Serialize + Send + 'static, - R: DeserializeOwned + Send + std::fmt::Debug + 'static, ->( - request: T, - endpoint: String, - http_client: reqwest::Client, -) -> stream::LocalBoxStream<'static, Result> { - create_grpc_stream_inner(request, endpoint, http_client).boxed_local() -} - -#[cfg(not(target_arch = "wasm32"))] -pub fn create_grpc_stream< - T: Serialize + Send + 'static, - R: DeserializeOwned + Send + std::fmt::Debug + 'static, ->( - request: T, - endpoint: String, - http_client: reqwest::Client, -) -> stream::BoxStream<'static, Result> { - create_grpc_stream_inner(request, endpoint, http_client).boxed() -} - -pub fn create_grpc_stream_inner< - T: Serialize + Send + 'static, - R: DeserializeOwned + Send + std::fmt::Debug + 'static, ->( - request: T, - endpoint: String, - http_client: reqwest::Client, -) -> impl Stream> { - async_stream::stream! { - let request = http_client - .post(endpoint) - .json(&request) - .send() - .await - .map_err(|e| Error::new(ErrorKind::MlsError).with(e))?; - - let mut remaining = vec![]; - for await bytes in request.bytes_stream() { - let bytes = bytes - .map_err(|e| Error::new(ErrorKind::SubscriptionUpdateError).with(e.to_string()))?; - let bytes = &[remaining.as_ref(), bytes.as_ref()].concat(); - let de = Deserializer::from_slice(bytes); - let mut stream = de.into_iter::>(); - 'messages: loop { - let response = stream.next(); - let res = match response { - Some(Ok(GrpcResponse::Ok(response))) => Ok(response), - Some(Ok(GrpcResponse::SubscriptionItem(item))) => Ok(item.result), - Some(Ok(GrpcResponse::Err(e))) => { - Err(Error::new(ErrorKind::MlsError).with(e.message)) - } - Some(Err(e)) => { - if e.is_eof() { - remaining = (&**bytes)[stream.byte_offset()..].to_vec(); - break 'messages; - } else { - Err(Error::new(ErrorKind::MlsError).with(e.to_string())) - } - } - Some(Ok(GrpcResponse::Empty {})) => continue 'messages, - None => break 'messages, - }; - yield res; - } - } - } -} - #[cfg(feature = "test-utils")] #[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)] #[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))] diff --git a/xmtp_mls/Cargo.toml b/xmtp_mls/Cargo.toml index f933391fd..506e0b698 100644 --- a/xmtp_mls/Cargo.toml +++ b/xmtp_mls/Cargo.toml @@ -49,7 +49,7 @@ async-stream.workspace = true async-trait.workspace = true bincode.workspace = true diesel_migrations.workspace = true -futures.workspace = true +futures = { workspace = true, features = ["alloc"] } hex.workspace = true hkdf.workspace = true openmls_rust_crypto = { workspace = true } @@ -70,6 +70,7 @@ tracing.workspace = true trait-variant.workspace = true xmtp_common.workspace = true zeroize.workspace = true +pin-project-lite.workspace = true # XMTP/Local xmtp_content_types = { path = "../xmtp_content_types" } diff --git a/xmtp_mls/src/api/mls.rs b/xmtp_mls/src/api/mls.rs index 3994cd8fa..86b206121 100644 --- a/xmtp_mls/src/api/mls.rs +++ b/xmtp_mls/src/api/mls.rs @@ -274,10 +274,10 @@ where Ok(()) } - pub async fn subscribe_group_messages( - &self, + pub(crate) async fn subscribe_group_messages<'a>( + &'a self, filters: Vec, - ) -> Result> + '_, ApiError> + ) -> Result<::GroupMessageStream<'a>, ApiError> where ApiClient: XmtpMlsStreams, { @@ -289,11 +289,11 @@ where .await } - pub async fn subscribe_welcome_messages( - &self, + pub(crate) async fn subscribe_welcome_messages<'a>( + &'a self, installation_key: &[u8], id_cursor: Option, - ) -> Result> + '_, ApiError> + ) -> Result<::WelcomeMessageStream<'a>, ApiError> where ApiClient: XmtpMlsStreams, { diff --git a/xmtp_mls/src/client.rs b/xmtp_mls/src/client.rs index 69be71c2e..05d820012 100644 --- a/xmtp_mls/src/client.rs +++ b/xmtp_mls/src/client.rs @@ -151,7 +151,7 @@ pub struct Client> { pub(crate) api_client: Arc>, pub(crate) context: Arc, pub(crate) history_sync_url: Option, - pub(crate) local_events: broadcast::Sender>, + pub(crate) local_events: broadcast::Sender, /// The method of verifying smart contract wallet signatures for this Client pub(crate) scw_verifier: Arc, @@ -528,7 +528,9 @@ where )?; // notify streams of our new group - let _ = self.local_events.send(LocalEvents::NewGroup(group.clone())); + let _ = self + .local_events + .send(LocalEvents::NewGroup(group.group_id.clone())); Ok(group) } @@ -559,9 +561,7 @@ where { Some(id) => id, None => { - return Err(ClientError::Storage(StorageError::NotFound( - NotFound::InboxIdForAddress(account_address), - ))); + return Err(NotFound::InboxIdForAddress(account_address).into()); } }; @@ -587,7 +587,9 @@ where .await?; // notify any streams of the new group - let _ = self.local_events.send(LocalEvents::NewGroup(group.clone())); + let _ = self + .local_events + .send(LocalEvents::NewGroup(group.group_id.clone())); Ok(group) } @@ -859,7 +861,7 @@ where return Err(ProcessIntentError::AlreadyProcessed(cursor).into()); } let result = MlsGroup::create_from_encrypted_welcome( - Arc::new(self.clone()), + self, provider, welcome.hpke_public_key.as_slice(), &welcome.data, diff --git a/xmtp_mls/src/groups/device_sync.rs b/xmtp_mls/src/groups/device_sync.rs index 734aad132..befe89447 100644 --- a/xmtp_mls/src/groups/device_sync.rs +++ b/xmtp_mls/src/groups/device_sync.rs @@ -144,9 +144,7 @@ pub struct SyncWorker { client: Client, /// The sync events stream #[allow(clippy::type_complexity)] - stream: Pin< - Box>, SubscribeError>> + Send>, - >, + stream: Pin> + Send>>, init: OnceCell<()>, retry: Retry, @@ -719,7 +717,7 @@ where pub fn get_sync_group(&self, conn: &DbConnection) -> Result, GroupError> { let sync_group_id = conn .latest_sync_group()? - .ok_or(GroupError::GroupNotFound)? + .ok_or(NotFound::SyncGroup(self.installation_public_key()))? .id; let sync_group = self.group_with_conn(conn, sync_group_id.clone())?; diff --git a/xmtp_mls/src/groups/mod.rs b/xmtp_mls/src/groups/mod.rs index 70f9ae97c..ace258c43 100644 --- a/xmtp_mls/src/groups/mod.rs +++ b/xmtp_mls/src/groups/mod.rs @@ -105,8 +105,8 @@ use xmtp_common::retry::RetryableError; #[derive(Debug, Error)] pub enum GroupError { - #[error("group not found")] - GroupNotFound, + #[error(transparent)] + NotFound(#[from] NotFound), #[error("Max user limit exceeded.")] UserLimitExceeded, #[error("api error: {0}")] @@ -239,7 +239,7 @@ impl RetryableError for GroupError { Self::LockUnavailable => true, Self::LockFailedToAcquire => true, Self::SyncFailedToWait => true, - Self::GroupNotFound + Self::NotFound(_) | Self::GroupMetadata(_) | Self::GroupMutableMetadata(_) | Self::GroupMutablePermissions(_) @@ -356,7 +356,24 @@ impl MlsGroup { Self::new_from_arc(Arc::new(client), group_id, created_at_ns) } - pub fn new_from_arc(client: Arc, group_id: Vec, created_at_ns: i64) -> Self { + // Creates a new group instance. Validate that the group exists in the DB + pub fn new_validated( + client: ScopedClient, + group_id: Vec, + provider: &XmtpOpenMlsProvider, + ) -> Result<(Self, StoredGroup), GroupError> { + if let Some(group) = provider.conn_ref().find_group(&group_id)? { + Ok(( + Self::new_from_arc(Arc::new(client), group_id, group.created_at_ns), + group, + )) + } else { + tracing::error!("Failed to validate existence of group"); + return Err(NotFound::GroupById(group_id).into()); + } + } + + fn new_from_arc(client: Arc, group_id: Vec, created_at_ns: i64) -> Self { let mut mutexes = client.context().mutexes.clone(); Self { group_id: group_id.clone(), @@ -394,8 +411,8 @@ impl MlsGroup { // Load the MLS group let mls_group = OpenMlsGroup::load(provider.storage(), &GroupId::from_slice(&self.group_id)) - .map_err(|_| GroupError::GroupNotFound)? - .ok_or(GroupError::GroupNotFound)?; + .map_err(|_| NotFound::MlsGroup)? + .ok_or(NotFound::MlsGroup)?; // Perform the operation with the MLS group operation(mls_group) @@ -536,12 +553,15 @@ impl MlsGroup { // Create a group from a decrypted and decoded welcome message // If the group already exists in the store, overwrite the MLS state and do not update the group entry async fn create_from_welcome( - client: Arc, + client: &ScopedClient, provider: &XmtpOpenMlsProvider, welcome: MlsWelcome, added_by_inbox: String, welcome_id: i64, - ) -> Result { + ) -> Result + where + ScopedClient: Clone, + { tracing::info!("Creating from welcome"); let mls_welcome = StagedWelcome::new_from_welcome(provider, &build_group_join_config(), welcome, None)?; @@ -564,7 +584,7 @@ impl MlsGroup { dm_members, ), ConversationType::Dm => { - validate_dm_group(client.as_ref(), &mls_group, &added_by_inbox)?; + validate_dm_group(&client, &mls_group, &added_by_inbox)?; StoredGroup::new_from_welcome( group_id.clone(), now_ns(), @@ -588,13 +608,13 @@ impl MlsGroup { // Ensure that the list of members in the group's MLS tree matches the list of inboxes specified // in the `GroupMembership` extension. - validate_initial_group_membership(client.as_ref(), provider.conn_ref(), &mls_group).await?; + validate_initial_group_membership(&client, provider.conn_ref(), &mls_group).await?; // Insert or replace the group in the database. // Replacement can happen in the case that the user has been removed from and subsequently re-added to the group. let stored_group = provider.conn_ref().insert_or_replace_group(to_store)?; - Ok(Self::new_from_arc( + Ok(Self::new( client.clone(), stored_group.id, stored_group.created_at_ns, @@ -603,12 +623,15 @@ impl MlsGroup { /// Decrypt a welcome message using HPKE and then create and save a group from the stored message pub async fn create_from_encrypted_welcome( - client: Arc, + client: &ScopedClient, provider: &XmtpOpenMlsProvider, hpke_public_key: &[u8], encrypted_welcome_bytes: &[u8], welcome_id: i64, - ) -> Result { + ) -> Result + where + ScopedClient: Clone, + { tracing::info!("Trying to decrypt welcome"); let welcome_bytes = decrypt_welcome(provider, hpke_public_key, encrypted_welcome_bytes)?; @@ -1162,23 +1185,22 @@ impl MlsGroup { /// Find the `inbox_id` of the group member who added the member to the group pub fn added_by_inbox_id(&self) -> Result { let conn = self.context().store().conn()?; - conn.find_group(self.group_id.clone()) - .map_err(GroupError::from) - .and_then(|fetch_result| { - fetch_result - .map(|group| group.added_by_inbox_id.clone()) - .ok_or_else(|| GroupError::GroupNotFound) - }) + let group = conn + .find_group(&self.group_id)? + .ok_or_else(|| NotFound::GroupById(self.group_id.clone()))?; + Ok(group.added_by_inbox_id) } /// Find the `inbox_id` of the group member who is the peer of this dm pub fn dm_inbox_id(&self) -> Result { let conn = self.context().store().conn()?; let group = conn - .find_group(self.group_id.clone())? - .ok_or(GroupError::GroupNotFound)?; + .find_group(&self.group_id)? + .ok_or_else(|| NotFound::GroupById(self.group_id.clone()))?; let inbox_id = self.client.inbox_id(); - let dm_id = &group.dm_id.ok_or(GroupError::GroupNotFound)?; + let dm_id = &group + .dm_id + .ok_or_else(|| NotFound::GroupById(self.group_id.clone()))?; Ok(dm_id.other_inbox_id(inbox_id)) } diff --git a/xmtp_mls/src/groups/scoped_client.rs b/xmtp_mls/src/groups/scoped_client.rs index e745114a1..6383d2085 100644 --- a/xmtp_mls/src/groups/scoped_client.rs +++ b/xmtp_mls/src/groups/scoped_client.rs @@ -29,7 +29,7 @@ pub trait LocalScopedGroupClient: Send + Sync + Sized { self.context_ref().store() } - fn local_events(&self) -> &broadcast::Sender>; + fn local_events(&self) -> &broadcast::Sender; fn history_sync_url(&self) -> &Option; @@ -95,7 +95,7 @@ pub trait ScopedGroupClient: Sized { self.context_ref().store() } - fn local_events(&self) -> &broadcast::Sender>; + fn local_events(&self) -> &broadcast::Sender; fn history_sync_url(&self) -> &Option; @@ -161,7 +161,7 @@ where &self.api_client } - fn local_events(&self) -> &broadcast::Sender> { + fn local_events(&self) -> &broadcast::Sender { &self.local_events } @@ -244,7 +244,7 @@ where (**self).api() } - fn local_events(&self) -> &broadcast::Sender> { + fn local_events(&self) -> &broadcast::Sender { (**self).local_events() } @@ -338,7 +338,7 @@ where (**self).store() } - fn local_events(&self) -> &broadcast::Sender> { + fn local_events(&self) -> &broadcast::Sender { (**self).local_events() } diff --git a/xmtp_mls/src/storage/encrypted_store/group.rs b/xmtp_mls/src/storage/encrypted_store/group.rs index f8307ca3d..9a9f04df6 100644 --- a/xmtp_mls/src/storage/encrypted_store/group.rs +++ b/xmtp_mls/src/storage/encrypted_store/group.rs @@ -333,14 +333,15 @@ impl DbConnection { } /// Return a single group that matches the given ID - pub fn find_group(&self, id: Vec) -> Result, StorageError> { - let mut query = dsl::groups.order(dsl::created_at_ns.asc()).into_boxed(); - - query = query.limit(1).filter(dsl::id.eq(id)); - let groups: Vec = self.raw_query(|conn| query.load(conn))?; + pub fn find_group(&self, id: &[u8]) -> Result, StorageError> { + let query = dsl::groups + .order(dsl::created_at_ns.asc()) + .limit(1) + .filter(dsl::id.eq(id)); - // Manually extract the first element - Ok(groups.into_iter().next()) + Ok(self + .raw_query(|conn| query.load(conn)) + .map(|mut g| g.pop())?) } /// Return a single group that matches the given welcome ID @@ -352,12 +353,14 @@ impl DbConnection { .order(dsl::created_at_ns.asc()) .filter(dsl::welcome_id.eq(welcome_id)); - let groups: Vec = self.raw_query(|conn| query.load(conn))?; + let mut groups = self.raw_query(|conn| query.load(conn))?; if groups.len() > 1 { - tracing::error!("More than one group found for welcome_id {}", welcome_id); + tracing::warn!( + welcome_id, + "More than one group found for welcome_id {welcome_id}" + ); } - - Ok(groups.into_iter().next()) + Ok(groups.pop()) } pub fn find_dm_group( @@ -370,12 +373,12 @@ impl DbConnection { .filter(dsl::dm_id.eq(Some(dm_id))) .order(dsl::last_message_ns.desc()); - let groups: Vec = self.raw_query(|conn| query.load(conn))?; + let mut groups: Vec = self.raw_query(|conn| query.load(conn))?; if groups.len() > 1 { tracing::info!("More than one group found for dm_inbox_id {members:?}"); } - Ok(groups.into_iter().next()) + Ok(groups.pop()) } /// Updates group membership state @@ -478,6 +481,22 @@ impl DbConnection { Ok(stored_group) } + + /// Get all the welcome ids turned into groups + pub(crate) fn group_welcome_ids(&self) -> Result, StorageError> { + self.raw_query(|conn| { + Ok::<_, StorageError>( + dsl::groups + .filter(dsl::welcome_id.is_not_null()) + .select(dsl::welcome_id) + .load::>(conn)? + .into_iter() + .map(|id| id.expect("SQL explicity filters for none")) + .collect(), + ) + }) + .map_err(Into::into) + } } #[repr(i32)] @@ -619,6 +638,25 @@ pub(crate) mod tests { ) } + /// Generate a test group with welcome + pub fn generate_group_with_welcome( + state: Option, + welcome_id: Option, + ) -> StoredGroup { + let id = rand_vec::<24>(); + let created_at_ns = now_ns(); + let membership_state = state.unwrap_or(GroupMembershipState::Allowed); + StoredGroup::new_from_welcome( + id, + created_at_ns, + membership_state, + "placeholder_address".to_string(), + welcome_id.unwrap_or(xmtp_common::rand_i64()), + ConversationType::Group, + None, + ) + } + /// Generate a test consent fn generate_consent_record( entity_type: ConsentType, @@ -952,4 +990,22 @@ pub(crate) mod tests { }) .await } + + #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)] + #[cfg_attr(not(target_arch = "wasm32"), tokio::test)] + async fn test_get_group_welcome_ids() { + with_connection(|conn| { + let mls_groups = vec![ + generate_group_with_welcome(None, Some(30)), + generate_group(None), + generate_group(None), + generate_group_with_welcome(None, Some(10)), + ]; + for g in mls_groups.iter() { + g.store(conn).unwrap(); + } + assert_eq!(vec![30, 10], conn.group_welcome_ids().unwrap()); + }) + .await + } } diff --git a/xmtp_mls/src/storage/encrypted_store/mod.rs b/xmtp_mls/src/storage/encrypted_store/mod.rs index fed9e31b2..96ba6ab69 100644 --- a/xmtp_mls/src/storage/encrypted_store/mod.rs +++ b/xmtp_mls/src/storage/encrypted_store/mod.rs @@ -294,16 +294,18 @@ pub mod private { // ensuring we have only one strong reference let result = fun(provider).await; let local_connection = provider.conn_ref().inner_ref(); - if Arc::strong_count(&local_connection) > 1 { - tracing::warn!( - "More than 1 strong connection references still exist during async transaction" + let (strong, weak) = ( + Arc::strong_count(&local_connection), + Arc::weak_count(&local_connection), + ); + if strong > 1 { + tracing::debug!( + "{strong} strong connection references still exist during async transaction" ); } - if Arc::weak_count(&local_connection) > 1 { - tracing::warn!( - "More than 1 weak connection references still exist during transaction" - ); + if weak > 1 { + tracing::debug!("{weak} weak connection references still exist during transaction"); } // after the closure finishes, `local_provider` should have the only reference ('strong') @@ -797,7 +799,7 @@ pub(crate) mod tests { let groups = store .conn() .unwrap() - .find_group(b"should not exist".to_vec()) + .find_group(b"should not exist") .unwrap(); assert_eq!(groups, None); } @@ -844,7 +846,7 @@ pub(crate) mod tests { let conn = store.conn().unwrap(); // this group should not exist because of the rollback - let groups = conn.find_group(b"should not exist".to_vec()).unwrap(); + let groups = conn.find_group(b"should not exist").unwrap(); assert_eq!(groups, None); } } diff --git a/xmtp_mls/src/storage/encrypted_store/user_preferences.rs b/xmtp_mls/src/storage/encrypted_store/user_preferences.rs index 98170d52f..ef273fe76 100644 --- a/xmtp_mls/src/storage/encrypted_store/user_preferences.rs +++ b/xmtp_mls/src/storage/encrypted_store/user_preferences.rs @@ -55,9 +55,9 @@ impl StoredUserPreferences { Ok(result.pop().unwrap_or_default()) } - pub fn new_hmac_key( + pub fn new_hmac_key( conn: &DbConnection, - local_events: &Sender>, + local_events: &Sender, ) -> Result, StorageError> { let mut preferences = Self::load(conn)?; diff --git a/xmtp_mls/src/storage/errors.rs b/xmtp_mls/src/storage/errors.rs index 8351acbe6..e9e9d3539 100644 --- a/xmtp_mls/src/storage/errors.rs +++ b/xmtp_mls/src/storage/errors.rs @@ -7,7 +7,7 @@ use super::{ refresh_state::EntityKind, sql_key_store::{self, SqlKeyStoreError}, }; -use crate::groups::intents::IntentError; +use crate::{groups::intents::IntentError, types::InstallationId}; use xmtp_common::{retryable, RetryableError}; pub struct Mls; @@ -79,6 +79,10 @@ pub enum NotFound { RefreshStateByIdAndKind(Vec, EntityKind), #[error("Cipher salt for db at [`{0}`] not found")] CipherSalt(String), + #[error("Sync Group for installation {0} not found")] + SyncGroup(InstallationId), + #[error("MLS Group Not Found")] + MlsGroup, } #[derive(Error, Debug)] diff --git a/xmtp_mls/src/subscriptions.rs b/xmtp_mls/src/subscriptions/mod.rs similarity index 75% rename from xmtp_mls/src/subscriptions.rs rename to xmtp_mls/src/subscriptions/mod.rs index 97f538504..4515dd7bb 100644 --- a/xmtp_mls/src/subscriptions.rs +++ b/xmtp_mls/src/subscriptions/mod.rs @@ -1,6 +1,10 @@ use futures::{FutureExt, Stream, StreamExt}; use prost::Message; -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; +use stream_conversations::StreamConversations; use tokio::{ sync::{broadcast, oneshot}, task::JoinHandle, @@ -10,23 +14,25 @@ use tracing::instrument; use xmtp_id::scw_verifier::SmartContractSignatureVerifier; use xmtp_proto::{api_client::XmtpMlsStreams, xmtp::mls::api::v1::WelcomeMessage}; +// mod stream_all; +mod stream_conversations; + use crate::{ client::{extract_welcome_message, ClientError}, groups::{ - device_sync::preference_sync::UserPreferenceUpdate, group_metadata::GroupMetadata, - mls_sync::GroupMessageProcessingError, scoped_client::ScopedGroupClient as _, + device_sync::preference_sync::UserPreferenceUpdate, mls_sync::GroupMessageProcessingError, subscriptions, GroupError, MlsGroup, }, storage::{ consent_record::StoredConsentRecord, group::{ConversationType, GroupQueryArgs, StoredGroup}, group_message::StoredGroupMessage, - StorageError, + NotFound, StorageError, }, - Client, XmtpApi, XmtpOpenMlsProvider, + Client, XmtpApi, }; use thiserror::Error; -use xmtp_common::{retry_async, retryable, Retry, RetryableError}; +use xmtp_common::{retryable, RetryableError}; #[derive(Debug, Error)] pub enum LocalEventError { @@ -51,9 +57,9 @@ pub struct StreamHandle { /// Events local to this client /// are broadcast across all senders/receivers of streams #[derive(Clone)] -pub enum LocalEvents { +pub enum LocalEvents { // a new group was created - NewGroup(MlsGroup), + NewGroup(Vec), SyncMessage(SyncMessage), OutgoingPreferenceUpdates(Vec), IncomingPreferenceUpdate(Vec), @@ -65,8 +71,8 @@ pub enum SyncMessage { Reply { message_id: Vec }, } -impl LocalEvents { - fn group_filter(self) -> Option> { +impl LocalEvents { + fn group_filter(self) -> Option> { use LocalEvents::*; // this is just to protect against any future variants match self { @@ -143,8 +149,8 @@ impl LocalEvents { } } -pub(crate) trait StreamMessages { - fn stream_sync_messages(self) -> impl Stream, SubscribeError>>; +pub(crate) trait StreamMessages { + fn stream_sync_messages(self) -> impl Stream>; fn stream_consent_updates( self, ) -> impl Stream, SubscribeError>>; @@ -153,12 +159,9 @@ pub(crate) trait StreamMessages { ) -> impl Stream, SubscribeError>>; } -impl StreamMessages for broadcast::Receiver> -where - C: Clone + Send + Sync + 'static, -{ +impl StreamMessages for broadcast::Receiver { #[instrument(level = "trace", skip_all)] - fn stream_sync_messages(self) -> impl Stream, SubscribeError>> { + fn stream_sync_messages(self) -> impl Stream> { BroadcastStream::new(self).filter_map(|event| async { xmtp_common::optify!(event, "Missed message due to event queue lag") .and_then(LocalEvents::sync_filter) @@ -220,6 +223,13 @@ impl From for (Vec, MessagesStreamInfo) { } } +// TODO: REMOVE BEFORE MERGING +// TODO: REMOVE BEFORE MERGING +// TODO: REMOVE BEFORE MERGING +pub(self) mod temp { + pub(super) type Result = std::result::Result; +} + #[derive(thiserror::Error, Debug)] pub enum SubscribeError { #[error("failed to start new messages stream {0}")] @@ -228,6 +238,9 @@ pub enum SubscribeError { Client(#[from] ClientError), #[error(transparent)] Group(#[from] GroupError), + #[error(transparent)] + NotFound(#[from] NotFound), + // TODO: Add this to `NotFound` #[error("group message expected in database but is missing")] GroupMessageNotFound, #[error("processing group message in stream: {0}")] @@ -255,6 +268,7 @@ impl RetryableError for SubscribeError { Storage(e) => retryable!(e), Api(e) => retryable!(e), Decode(_) => false, + NotFound(e) => retryable!(e), } } } @@ -264,135 +278,35 @@ where ApiClient: XmtpApi + Send + Sync + 'static, V: SmartContractSignatureVerifier + Send + Sync + 'static, { - async fn process_streamed_welcome( - &self, - provider: &XmtpOpenMlsProvider, - welcome: WelcomeMessage, - ) -> Result, ClientError> { - let welcome_v1 = extract_welcome_message(welcome)?; - let creation_result = retry_async!( - Retry::default(), - (async { - tracing::info!( - installation_id = &welcome_v1.id, - "Trying to process streamed welcome" - ); - let welcome_v1 = &welcome_v1; - self.context - .store() - .transaction_async(provider, |provider| async move { - MlsGroup::create_from_encrypted_welcome( - Arc::new(self.clone()), - provider, - welcome_v1.hpke_public_key.as_slice(), - &welcome_v1.data, - welcome_v1.id as i64, - ) - .await - }) - .await - }) - ); - - if let Some(err) = creation_result.as_ref().err() { - let conn = provider.conn_ref(); - let result = conn.find_group_by_welcome_id(welcome_v1.id as i64); - match result { - Ok(Some(group)) => { - tracing::info!( - inbox_id = self.inbox_id(), - group_id = hex::encode(&group.id), - welcome_id = ?group.welcome_id, - "Loading existing group for welcome_id: {:?}", - group.welcome_id - ); - return Ok(MlsGroup::new(self.clone(), group.id, group.created_at_ns)); - } - Ok(None) => return Err(ClientError::Generic(err.to_string())), - Err(e) => return Err(ClientError::Generic(e.to_string())), - } - } - - Ok(creation_result?) - } - pub async fn process_streamed_welcome_message( &self, envelope_bytes: Vec, - ) -> Result, ClientError> { + ) -> Result, SubscribeError> { let provider = self.mls_provider()?; + let conn = provider.conn_ref(); let envelope = WelcomeMessage::decode(envelope_bytes.as_slice()) .map_err(|e| ClientError::Generic(e.to_string()))?; - - let welcome = self.process_streamed_welcome(&provider, envelope).await?; - Ok(welcome) + let known_welcomes = HashSet::from_iter(conn.group_welcome_ids()?.into_iter()); + let (group, _) = StreamConversations::<_, ()>::on_welcome( + &known_welcomes, + self.clone(), + &provider, + envelope, + ) + .await?; + Ok(group) } - #[tracing::instrument(level = "debug", skip_all)] + // #[tracing::instrument(level = "debug", skip_all)] pub async fn stream_conversations<'a>( &'a self, conversation_type: Option, - ) -> Result, SubscribeError>> + 'a, ClientError> + ) -> Result, SubscribeError>> + 'a, SubscribeError> where ApiClient: XmtpMlsStreams, { - let installation_key = self.installation_public_key(); - let id_cursor = 0; - - tracing::info!(inbox_id = self.inbox_id(), "Setting up conversation stream"); - let subscription = self - .api_client - .subscribe_welcome_messages(installation_key.as_ref(), Some(id_cursor)) - .await? - .map(WelcomeOrGroup::::Welcome); - - let event_queue = - tokio_stream::wrappers::BroadcastStream::new(self.local_events.subscribe()) - .filter_map(|event| async { - xmtp_common::optify!(event, "Missed messages due to event queue lag") - .and_then(LocalEvents::group_filter) - .map(Result::Ok) - }) - .map(WelcomeOrGroup::::Group); - - let stream = futures::stream::select(event_queue, subscription); - let stream = stream.filter_map(move |group_or_welcome| async move { - tracing::info!( - inbox_id = self.inbox_id(), - installation_id = %self.installation_id(), - "Received conversation streaming payload" - ); - let filtered = self.process_streamed_convo(group_or_welcome).await; - let filtered = filtered.map(|(metadata, group)| { - conversation_type - .map_or(true, |ct| ct == metadata.conversation_type) - .then_some(group) - }); - filtered.transpose() - }); - - Ok(stream) + StreamConversations::new(self, conversation_type).await } - - async fn process_streamed_convo( - &self, - welcome_or_group: WelcomeOrGroup, - ) -> Result<(GroupMetadata, MlsGroup>), SubscribeError> { - let provider = self.mls_provider()?; - let group = match welcome_or_group { - WelcomeOrGroup::Welcome(welcome) => { - self.process_streamed_welcome(&provider, welcome?).await? - } - WelcomeOrGroup::Group(group) => group?, - }; - let metadata = group.metadata(&provider).await?; - Ok((metadata, group)) - } -} - -enum WelcomeOrGroup { - Group(Result>, SubscribeError>), - Welcome(Result), } impl Client @@ -404,7 +318,7 @@ where client: Arc>, conversation_type: Option, mut convo_callback: impl FnMut(Result, SubscribeError>) + Send + 'static, - ) -> impl crate::StreamHandle> { + ) -> impl crate::StreamHandle> { let (tx, rx) = oneshot::channel(); crate::spawn(Some(rx), async move { @@ -416,7 +330,7 @@ where convo_callback(convo) } tracing::debug!("`stream_conversations` stream ended, dropping stream"); - Ok::<_, ClientError>(()) + Ok::<_, SubscribeError>(()) }) } @@ -454,9 +368,9 @@ where futures::pin_mut!(messages_stream); let convo_stream = self.stream_conversations(conversation_type).await?; - futures::pin_mut!(convo_stream); + tracing::info!("\n\n Waiting on messages \n\n"); let mut extra_messages = Vec::new(); loop { @@ -609,42 +523,47 @@ pub(crate) mod tests { use xmtp_cryptography::utils::generate_local_wallet; use xmtp_id::InboxOwner; - #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] - async fn test_stream_welcomes() { - let alice = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); - let bob = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); - let alice_bob_group = alice - .create_group(None, GroupMetadataOptions::default()) - .unwrap(); - - // FIXME:insipx we run into an issue where the reqwest::post().send() request - // blocks the executor and we cannot progress the runtime if we dont `tokio::spawn` this. - // A solution might be to use `hyper` instead, and implement a custom connection pool with - // `deadpool`. This is a bit more work but shouldn't be too complicated since - // we're only using `post` requests. It would be nice for all streams to work - // w/o spawning a separate task. - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - let mut stream = tokio_stream::wrappers::UnboundedReceiverStream::new(rx); - let bob_ptr = bob.clone(); - crate::spawn(None, async move { - let bob_stream = bob_ptr.stream_conversations(None).await.unwrap(); - futures::pin_mut!(bob_stream); - while let Some(item) = bob_stream.next().await { - let _ = tx.send(item); - } - }); - - let group_id = alice_bob_group.group_id.clone(); - alice_bob_group - .add_members_by_inbox_id(&[bob.inbox_id()]) - .await - .unwrap(); + /// A macro for asserting that a stream yields a specific decrypted message. + /// + /// # Example + /// ```rust + /// assert_msg!(stream, b"first"); + /// ``` + #[macro_export] + macro_rules! assert_msg { + ($stream:expr, $expected:expr) => { + assert_eq!( + $stream + .next() + .await + .unwrap() + .unwrap() + .decrypted_message_bytes, + $expected.as_bytes() + ); + }; + } - let bob_received_groups = stream.next().await.unwrap().unwrap(); - assert_eq!(bob_received_groups.group_id, group_id); + /// A macro for asserting that a stream yields a specific decrypted message. + /// + /// # Example + /// ```rust + /// assert_msg!(stream, b"first"); + /// ``` + #[macro_export] + macro_rules! assert_msg_exists { + ($stream:expr) => { + assert!(!$stream + .next() + .await + .unwrap() + .unwrap() + .decrypted_message_bytes + .is_empty()); + }; } - #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "current_thread"))] async fn test_stream_messages() { xmtp_common::logger(); let alice = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); @@ -653,6 +572,7 @@ pub(crate) mod tests { let alice_group = alice .create_group(None, GroupMetadataOptions::default()) .unwrap(); + tracing::info!("Group Id = [{}]", hex::encode(&alice_group.group_id)); alice_group .add_members_by_inbox_id(&[bob.inbox_id()]) @@ -664,33 +584,16 @@ pub(crate) mod tests { .unwrap(); let bob_group = bob_groups.first().unwrap(); - let notify = Delivery::new(None); - let notify_ptr = notify.clone(); - let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); - crate::spawn(None, async move { - let stream = alice_group.stream().await.unwrap(); - futures::pin_mut!(stream); - while let Some(item) = stream.next().await { - let _ = tx.send(item); - notify_ptr.notify_one(); - } - }); - - let stream = tokio_stream::wrappers::UnboundedReceiverStream::new(rx); - // let stream = alice_group.stream().await.unwrap(); + let stream = alice_group.stream().await.unwrap(); futures::pin_mut!(stream); bob_group.send_message(b"hello").await.unwrap(); - tracing::debug!("Bob Sent Message!, waiting for delivery"); - // notify.wait_for_delivery().await.unwrap(); + let message = stream.next().await.unwrap().unwrap(); assert_eq!(message.decrypted_message_bytes, b"hello"); bob_group.send_message(b"hello2").await.unwrap(); - // notify.wait_for_delivery().await.unwrap(); let message = stream.next().await.unwrap().unwrap(); assert_eq!(message.decrypted_message_bytes, b"hello2"); - - // assert_eq!(bob_received_groups.group_id, alice_bob_group.group_id); } #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] @@ -714,40 +617,20 @@ pub(crate) mod tests { .add_members_by_inbox_id(&[caro.inbox_id()]) .await .unwrap(); - xmtp_common::time::sleep(core::time::Duration::from_millis(100)).await; - let messages: Arc>> = Arc::new(Mutex::new(Vec::new())); - let messages_clone = messages.clone(); + let stream = caro.stream_all_messages(None).await.unwrap(); + futures::pin_mut!(stream); + bo_group.send_message(b"first").await.unwrap(); + assert_msg!(stream, "first"); - let notify = Delivery::new(None); - let notify_pointer = notify.clone(); - let mut handle = Client::::stream_all_messages_with_callback( - Arc::new(caro), - None, - move |message| { - (*messages_clone.lock()).push(message.unwrap()); - notify_pointer.notify_one(); - }, - ); - handle.wait_for_ready().await; + bo_group.send_message(b"second").await.unwrap(); + assert_msg!(stream, "second"); - alix_group.send_message("first".as_bytes()).await.unwrap(); - notify - .wait_for_delivery() - .await - .expect("didn't get `first`"); - bo_group.send_message("second".as_bytes()).await.unwrap(); - notify.wait_for_delivery().await.unwrap(); - alix_group.send_message("third".as_bytes()).await.unwrap(); - notify.wait_for_delivery().await.unwrap(); - bo_group.send_message("fourth".as_bytes()).await.unwrap(); - notify.wait_for_delivery().await.unwrap(); + alix_group.send_message(b"third").await.unwrap(); + assert_msg!(stream, "third"); - let messages = messages.lock(); - assert_eq!(messages[0].decrypted_message_bytes, b"first"); - assert_eq!(messages[1].decrypted_message_bytes, b"second"); - assert_eq!(messages[2].decrypted_message_bytes, b"third"); - assert_eq!(messages[3].decrypted_message_bytes, b"fourth"); + bo_group.send_message(b"fourth").await.unwrap(); + assert_msg!(stream, "fourth"); } #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] @@ -765,39 +648,21 @@ pub(crate) mod tests { .await .unwrap(); - let messages: Arc>> = Arc::new(Mutex::new(Vec::new())); - let messages_clone = messages.clone(); - let delivery = Delivery::new(None); - let delivery_pointer = delivery.clone(); - let mut handle = Client::::stream_all_messages_with_callback( - caro.clone(), - None, - move |message| { - delivery_pointer.notify_one(); - (*messages_clone.lock()).push(message.unwrap()); - }, - ); - handle.wait_for_ready().await; + let stream = caro.stream_all_messages(None).await.unwrap(); + futures::pin_mut!(stream); + tracing::info!("\n\nSENDING FIRST MESSAGE\n\n"); alix_group.send_message(b"first").await.unwrap(); - delivery - .wait_for_delivery() - .await - .expect("timed out waiting for `first`"); + assert_msg!(stream, "first"); let bo_group = bo.create_dm(caro_wallet.get_address()).await.unwrap(); + assert_msg_exists!(stream); bo_group.send_message(b"second").await.unwrap(); - delivery - .wait_for_delivery() - .await - .expect("timed out waiting for `second`"); + assert_msg!(stream, "second"); alix_group.send_message(b"third").await.unwrap(); - delivery - .wait_for_delivery() - .await - .expect("timed out waiting for `third`"); + assert_msg!(stream, "third"); let alix_group_2 = alix .create_group(None, GroupMetadataOptions::default()) @@ -808,36 +673,10 @@ pub(crate) mod tests { .unwrap(); alix_group.send_message(b"fourth").await.unwrap(); - delivery - .wait_for_delivery() - .await - .expect("timed out waiting for `fourth`"); + assert_msg!(stream, "fourth"); alix_group_2.send_message(b"fifth").await.unwrap(); - delivery - .wait_for_delivery() - .await - .expect("timed out waiting for `fifth`"); - - { - let messages = messages.lock(); - assert_eq!(messages.len(), 5); - } - - let a = handle.abort_handle(); - a.end(); - let _ = handle.join().await; - assert!(a.is_finished()); - - alix_group - .send_message("should not show up".as_bytes()) - .await - .unwrap(); - xmtp_common::time::sleep(core::time::Duration::from_millis(100)).await; - - let messages = messages.lock(); - - assert_eq!(messages.len(), 5); + assert_msg!(stream, "fifth"); } #[ignore] diff --git a/xmtp_mls/src/subscriptions/stream_all.rs b/xmtp_mls/src/subscriptions/stream_all.rs new file mode 100644 index 000000000..7ebe33d4f --- /dev/null +++ b/xmtp_mls/src/subscriptions/stream_all.rs @@ -0,0 +1,87 @@ +use std::{collections::HashMap, sync::Arc}; + +use crate::{ + client::ClientError, + groups::scoped_client::ScopedGroupClient, + groups::subscriptions, + storage::{ + group::{ConversationType, GroupQueryArgs}, + group_message::StoredGroupMessage, + }, + Client, +}; +use futures::{ + stream::{self, Stream, StreamExt}, + Future, +}; +use xmtp_id::scw_verifier::SmartContractSignatureVerifier; +use xmtp_proto::api_client::{trait_impls::XmtpApi, XmtpMlsStreams}; + +use super::{MessagesStreamInfo, SubscribeError}; +pub struct StreamAllMessages<'a, C, Welcomes, Messages> { + /// The monolithic XMTP Client + client: &'a C, + /// Type of conversation to stream + conversation_type: Option, + /// Conversations that are being actively streamed + active_conversations: HashMap, MessagesStreamInfo>, + /// Welcomes Stream + welcomes: Welcomes, + /// Messages Stream + messages: Messages, + /// Extra messages from message stream, when the stream switches because + /// of a new group received. + extra_messages: Vec, +} + +impl<'a, A, V, Welcomes, Messages> StreamAllMessages<'a, Client, Welcomes, Messages> +where + A: XmtpApi + XmtpMlsStreams + Send + Sync + 'static, + V: SmartContractSignatureVerifier + Send + Sync + 'static, +{ + pub async fn new( + client: &'a Client, + conversation_type: Option, + ) -> Result { + let mut active_conversations = async { + let provider = client.mls_provider()?; + client.sync_welcomes(&provider).await?; + + let active_conversations = provider + .conn_ref() + .find_groups(GroupQueryArgs::default().maybe_conversation_type(conversation_type))? + .into_iter() + .map(Into::into) + .collect::, MessagesStreamInfo>>(); + Ok::<_, ClientError>(active_conversations) + } + .await?; + + let messages = + subscriptions::stream_messages(client, Arc::new(active_conversations.clone())).await?; + let welcomes = client.stream_conversations(conversation_type).await?; + + Self { + client, + conversation_type, + messages, + welcomes, + active_conversations, + extra_messages: Vec::new(), + } + } +} + +impl<'a, C, Welcomes, Messages> Stream for StreamAllMessages<'a, C> +where + C: ScopedGroupClient, +{ + type Item = Result; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + todo!() + } +} diff --git a/xmtp_mls/src/subscriptions/stream_conversations.rs b/xmtp_mls/src/subscriptions/stream_conversations.rs new file mode 100644 index 000000000..285284e3f --- /dev/null +++ b/xmtp_mls/src/subscriptions/stream_conversations.rs @@ -0,0 +1,414 @@ +use std::{collections::HashSet, future::Future, pin::Pin, task::Poll}; + +use crate::{ + groups::{scoped_client::ScopedGroupClient, MlsGroup}, + storage::{group::ConversationType, NotFound}, + Client, XmtpOpenMlsProvider, +}; +use futures::{future::FutureExt, prelude::stream::Select, Stream}; +use pin_project_lite::pin_project; +use tokio_stream::wrappers::BroadcastStream; +use xmtp_common::{retry_async, Retry}; +use xmtp_id::scw_verifier::SmartContractSignatureVerifier; +use xmtp_proto::{ + api_client::{trait_impls::XmtpApi, XmtpMlsStreams}, + xmtp::mls::api::v1::{welcome_message::V1 as WelcomeMessageV1, WelcomeMessage}, +}; + +use super::{temp::Result, LocalEvents, SubscribeError}; + +#[derive(Debug)] +pub(super) enum WelcomeOrGroup { + Group(Vec), + Welcome(Result), +} + +pin_project! { + /// Broadcast stream filtered + mapped to WelcomeOrGroup + pub(super) struct BroadcastGroupStream { + #[pin] inner: BroadcastStream, + } +} + +impl BroadcastGroupStream { + fn new(inner: BroadcastStream) -> Self { + Self { inner } + } +} + +impl Stream for BroadcastGroupStream { + type Item = WelcomeOrGroup; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + use std::task::Poll::*; + let this = self.project(); + + match this.inner.poll_next(cx) { + Ready(Some(event)) => { + let ev = xmtp_common::optify!(event, "Missed messages due to event queue lag") + .and_then(LocalEvents::group_filter); + if let Some(g) = ev { + Ready(Some(WelcomeOrGroup::Group(g))) + } else { + // skip this item since it was either missed due to lag, or not a group + Pending + } + } + Pending => Pending, + Ready(None) => Ready(None), + } + } +} + +pin_project! { + /// Subscription Stream mapped to WelcomeOrGroup + pub(super) struct SubscriptionStream { + #[pin] inner: S, + } +} + +impl SubscriptionStream { + fn new(inner: S) -> Self { + Self { inner } + } +} + +impl Stream for SubscriptionStream +where + S: Stream>, +{ + type Item = WelcomeOrGroup; + + fn poll_next( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + use std::task::Poll::*; + let this = self.project(); + + match this.inner.poll_next(cx) { + Ready(Some(welcome)) => { + let welcome = welcome.map_err(SubscribeError::from); + Ready(Some(WelcomeOrGroup::Welcome(welcome))) + } + Pending => Pending, + Ready(None) => Ready(None), + } + } +} + +pin_project! { + pub struct StreamConversations<'a, C, Subscription> { + client: C, + #[pin] inner: Subscription, + conversation_type: Option, + known_welcome_ids: HashSet, + #[pin] state: ProcessState<'a, C>, + } +} + +pin_project! { + #[project = ProcessProject] + enum ProcessState<'a, C> { + /// State where we are waiting on the next Message from the network + Waiting, + /// State where we are waiting on an IO/Network future to finish processing the current message + /// before moving on to the next one + Processing { + #[pin] future: FutureWrapper<'a, C> + } + } +} + +// we can't avoid the cfg(target_arch) without making the entire +// 'process_new_item' flow a Future, which makes this code +// significantly more difficult to modify. The other option is storing a +// anonymous stack type in a struct that would be returned from an async fn +// struct Foo { +// inner: impl Future +// } +// or some equivalent, which does not exist in rust. +// +// Another option is to make processing a welcome syncronous which +// might be possible with some kind of a cached identity strategy + +// Wrappers to deal with Send Bounds +#[cfg(not(target_arch = "wasm32"))] +pub struct FutureWrapper<'a, C> { + inner: Pin, Option)>> + Send + 'a>>, +} + +#[cfg(target_arch = "wasm32")] +pub struct FutureWrapper<'a, C> { + inner: Pin, Option)>> + 'a>>, +} + +impl<'a, C> Future for FutureWrapper<'a, C> { + type Output = Result<(MlsGroup, Option)>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + let inner = &mut self.inner; + futures::pin_mut!(inner); + inner.as_mut().poll(cx) + } +} + +impl<'a, C> FutureWrapper<'a, C> { + #[cfg(not(target_arch = "wasm32"))] + pub fn new(future: F) -> Self + where + F: Future, Option)>> + Send + 'a, + { + Self { + inner: future.boxed(), + } + } + + #[cfg(target_arch = "wasm32")] + pub fn new(future: F) -> Self + where + F: Future, Option)>> + 'a, + { + Self { + inner: future.boxed_local(), + } + } +} + +impl<'a, C> Default for ProcessState<'a, C> { + fn default() -> Self { + ProcessState::Waiting + } +} + +type MultiplexedSelect = Select>; + +impl<'a, A, V> + StreamConversations< + 'a, + Client, + MultiplexedSelect<::WelcomeMessageStream<'a>>, + > +where + A: XmtpApi + XmtpMlsStreams + Send + Sync + 'static, + V: SmartContractSignatureVerifier + Send + Sync + 'static, +{ + pub async fn new( + client: &'a Client, + conversation_type: Option, + ) -> Result { + let provider = client.mls_provider()?; + let conn = provider.conn_ref(); + let installation_key = client.installation_public_key(); + let id_cursor = 0; + tracing::info!( + inbox_id = client.inbox_id(), + "Setting up conversation stream" + ); + + let events = + BroadcastGroupStream::new(BroadcastStream::new(client.local_events.subscribe())); + + let subscription = client + .api_client + .subscribe_welcome_messages(installation_key.as_ref(), Some(id_cursor)) + .await?; + let subscription = SubscriptionStream::new(subscription); + let known_welcome_ids = HashSet::from_iter(conn.group_welcome_ids()?.into_iter()); + + let stream = futures::stream::select(events, subscription); + + Ok(Self { + client: client.clone(), + inner: stream, + known_welcome_ids, + conversation_type, + state: ProcessState::Waiting, + }) + } +} + +impl<'a, C, Subscription> Stream for StreamConversations<'a, C, Subscription> +where + C: ScopedGroupClient + Clone + 'a, + Subscription: Stream + 'a, +{ + type Item = Result>; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + use std::task::Poll::*; + let mut this = self.as_mut().project(); + + match this.state.as_mut().project() { + ProcessProject::Waiting => { + match this.inner.poll_next(cx) { + Ready(Some(item)) => { + let future = + // need to clone client into Arc<> here b/c: + // otherwise the `'1` ref for `Pin<&mut Self>` in arg to `poll_next` needs to + // live as long as `'a` ref for `Client`. + // This is because we're boxing this future (i.e `Box`). + // There maybe a way to avoid it, but we need to `Box<>` the type + // b/c there's no way to get the anonymous future type on the stack generated by an + // `async fn`. If we can somehow store `impl Trait` on a struct (or + // something similar), we could avoid the `Clone` + `Arc`ing. + // TODO: try ref here? + Self::process_new_item(this.known_welcome_ids.clone(), this.client.clone(), item); + + this.state.set(ProcessState::Processing { + future: FutureWrapper::new(future), + }); + cx.waker().wake_by_ref(); + Pending + } + // stream ended + Ready(None) => Ready(None), + Pending => { + cx.waker().wake_by_ref(); + Pending + } + } + } + ProcessProject::Processing { future } => match future.poll(cx) { + Ready(Ok((group, welcome_id))) => { + if let Some(id) = welcome_id { + this.known_welcome_ids.insert(id); + } + this.state.set(ProcessState::Waiting); + Ready(Some(Ok(group))) + } + Ready(Err(e)) => Ready(Some(Err(e))), + Pending => Pending, + }, + } + } +} + +/// bulk of the processing for a new welcome/group +impl<'a, C, Subscription> StreamConversations<'a, C, Subscription> +where + C: ScopedGroupClient + Clone, +{ + async fn process_new_item( + known_welcome_ids: HashSet, + client: C, + item: WelcomeOrGroup, + ) -> Result<(MlsGroup, Option)> { + use WelcomeOrGroup::*; + let provider = client.context().mls_provider()?; + match item { + Welcome(w) => Self::on_welcome(&known_welcome_ids, client, &provider, w?) + .await + .map(|(g, w_id)| (g, Some(w_id))), + Group(id) => { + let (group, stored_group) = MlsGroup::new_validated(client, id, &provider)?; + Ok((group, stored_group.welcome_id)) + } + } + } + + /// process a new welcome, returning the Group & Welcome ID + pub(super) async fn on_welcome( + known_welcome_ids: &HashSet, + client: C, + provider: &XmtpOpenMlsProvider, + welcome: WelcomeMessage, + ) -> Result<(MlsGroup, i64)> { + let WelcomeMessageV1 { + id, + created_ns: _, + ref installation_key, + ref data, + ref hpke_public_key, + } = super::extract_welcome_message(welcome)?; + let id = id as i64; + + // TODO: Test multiple streams at once + if known_welcome_ids.contains(&id) { + let conn = provider.conn_ref(); + let group = conn + .find_group_by_welcome_id(id)? + .ok_or(NotFound::GroupByWelcome(id))?; + tracing::info!( + inbox_id = client.inbox_id(), + group_id = hex::encode(&group.id), + welcome_id = ?group.welcome_id, + "Loading existing group for welcome_id: {:?}", + group.welcome_id + ); + return Ok(( + MlsGroup::new(client.clone(), group.id, group.created_at_ns), + id, + )); + } + + let c = &client; + let mls_group = retry_async!( + Retry::default(), + (async { + tracing::info!( + installation_id = hex::encode(installation_key), + welcome_id = &id, + "Trying to process streamed welcome" + ); + + client + .context() + .store() + .transaction_async(provider, |provider| async move { + MlsGroup::create_from_encrypted_welcome( + c, + provider, + hpke_public_key.as_slice(), + data, + id, + ) + .await + }) + .await + }) + )?; + + Ok((mls_group, id)) + } +} + +#[cfg(test)] +mod test { + use std::sync::Arc; + + use super::*; + use crate::builder::ClientBuilder; + use crate::groups::GroupMetadataOptions; + use futures::StreamExt; + use wasm_bindgen_test::wasm_bindgen_test; + use xmtp_cryptography::utils::generate_local_wallet; + + #[cfg(target_arch = "wasm32")] + wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_dedicated_worker); + + #[wasm_bindgen_test(unsupported = tokio::test(flavor = "multi_thread", worker_threads = 10))] + async fn test_stream_welcomes() { + let alice = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); + let bob = Arc::new(ClientBuilder::new_test_client(&generate_local_wallet()).await); + let alice_bob_group = alice + .create_group(None, GroupMetadataOptions::default()) + .unwrap(); + + let mut stream = StreamConversations::new(&bob, None).await.unwrap(); + // futures::pin_mut!(stream); + let group_id = alice_bob_group.group_id.clone(); + alice_bob_group + .add_members_by_inbox_id(&[bob.inbox_id()]) + .await + .unwrap(); + let bob_received_groups = stream.next().await.unwrap().unwrap(); + assert_eq!(bob_received_groups.group_id, group_id); + } +}