Skip to content

Commit

Permalink
Update WASM bindings (#1122)
Browse files Browse the repository at this point in the history
* Update VSCode workspace settings

* Add WASM bindings

* Clippy fixes

* Use tokio Mutex
  • Loading branch information
rygine authored Oct 7, 2024
1 parent a9d086e commit abdd3de
Show file tree
Hide file tree
Showing 14 changed files with 1,555 additions and 119 deletions.
7 changes: 1 addition & 6 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
{
"rust-analyzer.cargo.sysroot": "discover",
"rust-analyzer.linkedProjects": [
"bindings_ffi/Cargo.toml",
"bindings_node/Cargo.toml",
"bindings_wasm/Cargo.toml",
"examples/cli/Cargo.toml"
],
"rust-analyzer.linkedProjects": ["Cargo.toml"],
"rust-analyzer.procMacro.enable": true,
"rust-analyzer.procMacro.attributes.enable": true,
"rust-analyzer.procMacro.ignored": {
Expand Down
5 changes: 5 additions & 0 deletions Cargo.lock

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

13 changes: 10 additions & 3 deletions bindings_wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,22 @@ version.workspace = true
crate-type = ["cdylib", "rlib"]

[dependencies]
hex.workspace = true
js-sys.workspace = true
prost.workspace = true
serde-wasm-bindgen = "0.6.5"
wasm-bindgen.workspace = true
serde.workspace = true
tokio.workspace = true
wasm-bindgen-futures.workspace = true
wasm-bindgen.workspace = true
xmtp_api_http = { path = "../xmtp_api_http" }
xmtp_cryptography = { path = "../xmtp_cryptography" }
xmtp_id = { path = "../xmtp_id" }
xmtp_mls = { path = "../xmtp_mls", features = ["message-history"] }
xmtp_mls = { path = "../xmtp_mls", features = [
"message-history",
"test-utils",
] }
xmtp_proto = { path = "../xmtp_proto", features = ["proto_full"] }

[dev-dependencies]
wasm-bindgen-test.workspace = true

65 changes: 65 additions & 0 deletions bindings_wasm/src/consent_state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use wasm_bindgen::prelude::wasm_bindgen;
use xmtp_mls::storage::consent_record::{ConsentState, ConsentType, StoredConsentRecord};

#[wasm_bindgen]
#[derive(Clone, serde::Serialize)]
pub enum WasmConsentState {
Unknown,
Allowed,
Denied,
}

impl From<ConsentState> for WasmConsentState {
fn from(state: ConsentState) -> Self {
match state {
ConsentState::Unknown => WasmConsentState::Unknown,
ConsentState::Allowed => WasmConsentState::Allowed,
ConsentState::Denied => WasmConsentState::Denied,
}
}
}

impl From<WasmConsentState> for ConsentState {
fn from(state: WasmConsentState) -> Self {
match state {
WasmConsentState::Unknown => ConsentState::Unknown,
WasmConsentState::Allowed => ConsentState::Allowed,
WasmConsentState::Denied => ConsentState::Denied,
}
}
}

#[wasm_bindgen]
#[derive(Clone)]
pub enum WasmConsentEntityType {
GroupId,
InboxId,
Address,
}

impl From<WasmConsentEntityType> for ConsentType {
fn from(entity_type: WasmConsentEntityType) -> Self {
match entity_type {
WasmConsentEntityType::GroupId => ConsentType::GroupId,
WasmConsentEntityType::InboxId => ConsentType::InboxId,
WasmConsentEntityType::Address => ConsentType::Address,
}
}
}

#[wasm_bindgen(getter_with_clone)]
pub struct WasmConsent {
pub entity_type: WasmConsentEntityType,
pub state: WasmConsentState,
pub entity: String,
}

impl From<WasmConsent> for StoredConsentRecord {
fn from(consent: WasmConsent) -> Self {
Self {
entity_type: consent.entity_type.into(),
state: consent.state.into(),
entity: consent.entity,
}
}
}
176 changes: 176 additions & 0 deletions bindings_wasm/src/conversations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
use std::sync::Arc;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsError, JsValue};
use xmtp_mls::client::FindGroupParams;
use xmtp_mls::groups::{GroupMetadataOptions, PreconfiguredPolicies};

use crate::messages::WasmMessage;
use crate::permissions::WasmGroupPermissionsOptions;
use crate::{groups::WasmGroup, mls_client::RustXmtpClient};

#[wasm_bindgen(getter_with_clone)]
pub struct WasmListConversationsOptions {
pub created_after_ns: Option<i64>,
pub created_before_ns: Option<i64>,
pub limit: Option<i64>,
}

#[wasm_bindgen(getter_with_clone)]
#[derive(Clone)]
pub struct WasmCreateGroupOptions {
pub permissions: Option<WasmGroupPermissionsOptions>,
pub group_name: Option<String>,
pub group_image_url_square: Option<String>,
pub group_description: Option<String>,
pub group_pinned_frame_url: Option<String>,
}

impl WasmCreateGroupOptions {
pub fn into_group_metadata_options(self) -> GroupMetadataOptions {
GroupMetadataOptions {
name: self.group_name,
image_url_square: self.group_image_url_square,
description: self.group_description,
pinned_frame_url: self.group_pinned_frame_url,
}
}
}

#[wasm_bindgen]
pub struct WasmConversations {
inner_client: Arc<RustXmtpClient>,
}

impl WasmConversations {
pub fn new(inner_client: Arc<RustXmtpClient>) -> Self {
Self { inner_client }
}
}

#[wasm_bindgen]
impl WasmConversations {
#[wasm_bindgen]
pub async fn create_group(
&self,
account_addresses: Vec<String>,
options: Option<WasmCreateGroupOptions>,
) -> Result<WasmGroup, JsError> {
let options = match options {
Some(options) => options,
None => WasmCreateGroupOptions {
permissions: None,
group_name: None,
group_image_url_square: None,
group_description: None,
group_pinned_frame_url: None,
},
};

let group_permissions = match options.permissions {
Some(WasmGroupPermissionsOptions::AllMembers) => {
Some(PreconfiguredPolicies::AllMembers.to_policy_set())
}
Some(WasmGroupPermissionsOptions::AdminOnly) => {
Some(PreconfiguredPolicies::AdminsOnly.to_policy_set())
}
_ => None,
};

let metadata_options = options.clone().into_group_metadata_options();

let convo = if account_addresses.is_empty() {
self
.inner_client
.create_group(group_permissions, metadata_options)
.map_err(|e| JsError::new(format!("{}", e).as_str()))?
} else {
self
.inner_client
.create_group_with_members(account_addresses, group_permissions, metadata_options)
.await
.map_err(|e| JsError::new(format!("{}", e).as_str()))?
};

let out = WasmGroup::new(
self.inner_client.clone(),
convo.group_id,
convo.created_at_ns,
);

Ok(out)
}

#[wasm_bindgen]
pub fn find_group_by_id(&self, group_id: String) -> Result<WasmGroup, JsError> {
let group_id = hex::decode(group_id).map_err(|e| JsError::new(format!("{}", e).as_str()))?;

let group = self
.inner_client
.group(group_id)
.map_err(|e| JsError::new(format!("{}", e).as_str()))?;

Ok(WasmGroup::new(
self.inner_client.clone(),
group.group_id,
group.created_at_ns,
))
}

#[wasm_bindgen]
pub fn find_message_by_id(&self, message_id: String) -> Result<WasmMessage, JsError> {
let message_id =
hex::decode(message_id).map_err(|e| JsError::new(format!("{}", e).as_str()))?;

let message = self
.inner_client
.message(message_id)
.map_err(|e| JsError::new(format!("{}", e).as_str()))?;

Ok(WasmMessage::from(message))
}

#[wasm_bindgen]
pub async fn sync(&self) -> Result<(), JsError> {
self
.inner_client
.sync_welcomes()
.await
.map_err(|e| JsError::new(format!("{}", e).as_str()))?;
Ok(())
}

#[wasm_bindgen]
pub async fn list(
&self,
opts: Option<WasmListConversationsOptions>,
) -> Result<js_sys::Array, JsError> {
let opts = match opts {
Some(options) => options,
None => WasmListConversationsOptions {
created_after_ns: None,
created_before_ns: None,
limit: None,
},
};
let convo_list: js_sys::Array = self
.inner_client
.find_groups(FindGroupParams {
created_after_ns: opts.created_after_ns,
created_before_ns: opts.created_before_ns,
limit: opts.limit,
..FindGroupParams::default()
})
.map_err(|e| JsError::new(format!("{}", e).as_str()))?
.into_iter()
.map(|group| {
JsValue::from(WasmGroup::new(
self.inner_client.clone(),
group.group_id,
group.created_at_ns,
))
})
.collect();

Ok(convo_list)
}
}
73 changes: 73 additions & 0 deletions bindings_wasm/src/encoded_content.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use js_sys::Uint8Array;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::JsValue;
use xmtp_proto::xmtp::mls::message_contents::{ContentTypeId, EncodedContent};

#[wasm_bindgen(getter_with_clone)]
#[derive(Clone)]
pub struct WasmContentTypeId {
pub authority_id: String,
pub type_id: String,
pub version_major: u32,
pub version_minor: u32,
}

impl From<ContentTypeId> for WasmContentTypeId {
fn from(content_type_id: ContentTypeId) -> WasmContentTypeId {
WasmContentTypeId {
authority_id: content_type_id.authority_id,
type_id: content_type_id.type_id,
version_major: content_type_id.version_major,
version_minor: content_type_id.version_minor,
}
}
}

impl From<WasmContentTypeId> for ContentTypeId {
fn from(content_type_id: WasmContentTypeId) -> Self {
ContentTypeId {
authority_id: content_type_id.authority_id,
type_id: content_type_id.type_id,
version_major: content_type_id.version_major,
version_minor: content_type_id.version_minor,
}
}
}

#[wasm_bindgen(getter_with_clone)]
#[derive(Clone)]
pub struct WasmEncodedContent {
pub r#type: Option<WasmContentTypeId>,
pub parameters: JsValue,
pub fallback: Option<String>,
pub compression: Option<i32>,
pub content: Uint8Array,
}

impl From<EncodedContent> for WasmEncodedContent {
fn from(content: EncodedContent) -> WasmEncodedContent {
let r#type = content.r#type.map(|v| v.into());

WasmEncodedContent {
r#type,
parameters: serde_wasm_bindgen::to_value(&content.parameters).unwrap(),
fallback: content.fallback,
compression: content.compression,
content: content.content.as_slice().into(),
}
}
}

impl From<WasmEncodedContent> for EncodedContent {
fn from(content: WasmEncodedContent) -> Self {
let r#type = content.r#type.map(|v| v.into());

EncodedContent {
r#type,
parameters: serde_wasm_bindgen::from_value(content.parameters).unwrap(),
fallback: content.fallback,
compression: content.compression,
content: content.content.to_vec(),
}
}
}
Loading

0 comments on commit abdd3de

Please sign in to comment.