Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add consent & user preference streaming to WASM bindings #1647

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cargo/nextest.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[profile.default]
default-filter = "not test(test_stream_all_messages_does_not_lose_messages)"
retries = 3
retries = 1
50 changes: 50 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bindings_wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ serde-wasm-bindgen = "0.6.5"
tracing.workspace = true
tracing-subscriber = { workspace = true, features = ["env-filter", "json"] }
tracing-web = "0.1"
tsify-next = "0.5"
wasm-bindgen.workspace = true
wasm-bindgen-futures.workspace = true
xmtp_api.workspace = true
Expand All @@ -26,7 +27,6 @@ xmtp_cryptography.workspace = true
xmtp_id.workspace = true
xmtp_mls = { workspace = true, features = ["test-utils", "http-api"] }
xmtp_proto = { workspace = true, features = ["proto_full"] }

[dev-dependencies]
wasm-bindgen-test.workspace = true
xmtp_common = { workspace = true, features = ["test-utils"] }
Expand Down
5 changes: 5 additions & 0 deletions bindings_wasm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@
- `yarn`: Installs all dependencies (required before building)
- `yarn build`: Build a release version of the WASM bindings for the current platform
- `yarn lint`: Run cargo clippy and fmt checks
- `yarn check:macos`: Run cargo check for macOS (requires Homebrew and `llvm` to be installed)

# Publishing

To release a new version of the bindings, update the version in `package.json` with the appropriate semver value and add an entry to the CHANGELOG.md file. Once merged, manually trigger the `Release WASM Bindings` workflow to build and publish the bindings.
1 change: 1 addition & 0 deletions bindings_wasm/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"build": "yarn clean && yarn build:web && yarn build:copy && yarn clean:release",
"build:web": "cargo xtask build BindingsWasm --out-dir ./dist --plain -- --release",
"build:copy": "node scripts/copyFiles.js",
"check:macos": "CC_wasm32_unknown_unknown=/opt/homebrew/opt/llvm/bin/clang cargo check --target wasm32-unknown-unknown",
"clean:release": "rm -f ./dist/package.json",
"clean": "rm -rf ./dist",
"lint": "yarn lint:clippy && yarn lint:fmt",
Expand Down
20 changes: 18 additions & 2 deletions bindings_wasm/src/consent_state.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use serde::{Deserialize, Serialize};
use wasm_bindgen::{prelude::wasm_bindgen, JsError};
use xmtp_mls::storage::consent_record::{
ConsentState as XmtpConsentState, ConsentType as XmtpConsentType, StoredConsentRecord,
Expand All @@ -6,7 +7,7 @@ use xmtp_mls::storage::consent_record::{
use crate::{client::Client, conversation::Conversation};

#[wasm_bindgen]
#[derive(Clone, serde::Serialize)]
#[derive(Clone, Serialize, Deserialize)]
pub enum ConsentState {
Unknown,
Allowed,
Expand Down Expand Up @@ -34,7 +35,7 @@ impl From<ConsentState> for XmtpConsentState {
}

#[wasm_bindgen]
#[derive(Clone)]
#[derive(Clone, Serialize, Deserialize)]
pub enum ConsentEntityType {
GroupId,
InboxId,
Expand All @@ -52,6 +53,7 @@ impl From<ConsentEntityType> for XmtpConsentType {
}

#[wasm_bindgen(getter_with_clone)]
#[derive(Clone, Serialize, Deserialize)]
pub struct Consent {
#[wasm_bindgen(js_name = entityType)]
pub entity_type: ConsentEntityType,
Expand Down Expand Up @@ -81,6 +83,20 @@ impl From<Consent> for StoredConsentRecord {
}
}

impl From<StoredConsentRecord> for Consent {
fn from(value: StoredConsentRecord) -> Self {
Self {
entity: value.entity,
entity_type: match value.entity_type {
XmtpConsentType::Address => ConsentEntityType::Address,
XmtpConsentType::ConversationId => ConsentEntityType::GroupId,
XmtpConsentType::InboxId => ConsentEntityType::InboxId,
},
state: value.state.into(),
}
}
}

#[wasm_bindgen]
impl Client {
#[wasm_bindgen(js_name = setConsentStates)]
Expand Down
29 changes: 28 additions & 1 deletion bindings_wasm/src/conversations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ use xmtp_mls::storage::group::ConversationType as XmtpConversationType;
use xmtp_mls::storage::group::GroupMembershipState as XmtpGroupMembershipState;
use xmtp_mls::storage::group::GroupQueryArgs;

use crate::consent_state::ConsentState;
use crate::consent_state::{Consent, ConsentState};
use crate::messages::Message;
use crate::permissions::{GroupPermissionsOptions, PermissionPolicySet};
use crate::streams::{StreamCallback, StreamCloser};
use crate::user_preferences::UserPreference;
use crate::{client::RustXmtpClient, conversation::Conversation};

use xmtp_mls::groups::group_mutable_metadata::MessageDisappearingSettings as XmtpMessageDisappearingSettings;
Expand Down Expand Up @@ -641,4 +642,30 @@ impl Conversations {
);
Ok(StreamCloser::new(stream_closer))
}

#[wasm_bindgen(js_name = "streamConsent")]
pub fn stream_consent(&self, callback: StreamCallback) -> Result<StreamCloser, JsError> {
let stream_closer =
RustXmtpClient::stream_consent_with_callback(self.inner_client.clone(), move |message| {
match message {
Ok(m) => callback.on_consent_update(m.into_iter().map(Consent::from).collect()),
Err(e) => callback.on_error(JsError::from(e)),
}
});
Ok(StreamCloser::new(stream_closer))
}

#[wasm_bindgen(js_name = "streamConsent")]
pub fn stream_preferences(&self, callback: StreamCallback) -> Result<StreamCloser, JsError> {
let stream_closer =
RustXmtpClient::stream_preferences_with_callback(self.inner_client.clone(), move |message| {
match message {
Ok(m) => {
callback.on_user_preference_update(m.into_iter().map(UserPreference::from).collect())
}
Err(e) => callback.on_error(JsError::from(e)),
}
});
Ok(StreamCloser::new(stream_closer))
}
}
1 change: 1 addition & 0 deletions bindings_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod messages;
pub mod permissions;
pub mod signatures;
pub mod streams;
mod user_preferences;

fn error(e: impl std::error::Error) -> JsError {
JsError::new(&format!("{}", e))
Expand Down
9 changes: 9 additions & 0 deletions bindings_wasm/src/streams.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::consent_state::Consent;
use crate::conversation::Conversation;
use crate::messages::Message;
use crate::user_preferences::UserPreference;
use std::{cell::RefCell, rc::Rc};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsError;
Expand All @@ -18,6 +20,13 @@ extern "C" {
#[wasm_bindgen(structural, method)]
pub fn on_message(this: &StreamCallback, item: Message);

#[wasm_bindgen(structural, method)]
pub fn on_consent_update(this: &StreamCallback, item: Vec<Consent>);

#[wasm_bindgen(structural, method)]
pub fn on_user_preference_update(this: &StreamCallback, item: Vec<UserPreference>);

// pub fn on_preference_update(this: &StreamCallback, item: Vec<)
#[wasm_bindgen(structural, method)]
pub fn on_conversation(this: &StreamCallback, item: Conversation);

Expand Down
21 changes: 21 additions & 0 deletions bindings_wasm/src/user_preferences.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::consent_state::Consent;
use serde::{Deserialize, Serialize};
use tsify_next::Tsify;
use wasm_bindgen::prelude::wasm_bindgen;
use xmtp_mls::groups::device_sync::preference_sync::UserPreferenceUpdate;

#[derive(Tsify, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub enum UserPreference {
Consent(Consent),
HmacKeyUpdate { key: Vec<u8> },
}

impl From<UserPreferenceUpdate> for UserPreference {
fn from(v: UserPreferenceUpdate) -> UserPreference {
match v {
UserPreferenceUpdate::ConsentUpdate(c) => UserPreference::Consent(Consent::from(c)),
UserPreferenceUpdate::HmacKeyUpdate { key } => UserPreference::HmacKeyUpdate { key },
}
}
}
2 changes: 0 additions & 2 deletions xmtp_api_grpc/src/replication_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,6 @@ impl XmtpMlsClient for ClientV4 {
async fn send_group_messages(&self, req: SendGroupMessagesRequest) -> Result<(), Self::Error> {
self.publish_envelopes_to_payer(req.messages)
.await
.map_err(GrpcError::from)
.map_err(Error::from)
}

Expand All @@ -289,7 +288,6 @@ impl XmtpMlsClient for ClientV4 {
) -> Result<(), Self::Error> {
self.publish_envelopes_to_payer(req.messages)
.await
.map_err(GrpcError::from)
.map_err(Error::from)
}

Expand Down
4 changes: 1 addition & 3 deletions xmtp_id/src/associations/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,7 @@ pub fn verify_signed_with_public_context(
public_key: &[u8; 32],
) -> Result<(), SignatureError> {
let verifying_key = VerifyingKey::from_bytes(public_key)?;
verifying_key
.credential_verify::<PublicContext>(signature_text, signature_bytes)
.map_err(Into::into)
verifying_key.credential_verify::<PublicContext>(signature_text, signature_bytes)
}

#[derive(Clone, Debug, PartialEq)]
Expand Down
12 changes: 10 additions & 2 deletions xmtp_mls/src/subscriptions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,11 @@ where

pub fn stream_consent_with_callback(
client: Arc<Client<ApiClient, V>>,
mut callback: impl FnMut(Result<Vec<StoredConsentRecord>>) + Send + 'static,
#[cfg(not(target_arch = "wasm32"))] mut callback: impl FnMut(Result<Vec<StoredConsentRecord>>)
+ Send
+ 'static,
#[cfg(target_arch = "wasm32")] mut callback: impl FnMut(Result<Vec<StoredConsentRecord>>)
+ 'static,
) -> impl StreamHandle<StreamOutput = Result<()>> {
let (tx, rx) = oneshot::channel();

Expand All @@ -346,7 +350,11 @@ where

pub fn stream_preferences_with_callback(
client: Arc<Client<ApiClient, V>>,
mut callback: impl FnMut(Result<Vec<UserPreferenceUpdate>>) + Send + 'static,
#[cfg(not(target_arch = "wasm32"))] mut callback: impl FnMut(Result<Vec<UserPreferenceUpdate>>)
+ Send
+ 'static,
#[cfg(target_arch = "wasm32")] mut callback: impl FnMut(Result<Vec<UserPreferenceUpdate>>)
+ 'static,
) -> impl StreamHandle<StreamOutput = Result<()>> {
let (tx, rx) = oneshot::channel();

Expand Down
Loading