Skip to content

Commit

Permalink
Add content types to node bindings (#1706)
Browse files Browse the repository at this point in the history
* Add content_types dependency

* Add content_types mod

* Add react content type

* Add multi remote attachment content type

* Add content_types to ListMessagesOptions

* Add find_messages_with_reactions to Conversation

* Remove unnecessary calls to as_slice()
  • Loading branch information
rygine authored Mar 5, 2025
1 parent 53682fc commit ea26c14
Show file tree
Hide file tree
Showing 8 changed files with 334 additions and 4 deletions.
1 change: 1 addition & 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_node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ tracing-subscriber = { workspace = true, features = [
xmtp_api.workspace = true
xmtp_api_grpc.workspace = true
xmtp_common.workspace = true
xmtp_content_types.workspace = true
xmtp_cryptography.workspace = true
xmtp_id.workspace = true
xmtp_mls.workspace = true
xmtp_proto = { workspace = true, features = ["proto_full"] }

[build-dependencies]
napi-build = "2.0.1"

Expand Down
36 changes: 36 additions & 0 deletions bindings_node/src/content_types/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use napi_derive::napi;
use xmtp_mls::storage::group_message::ContentType as XmtpContentType;

pub mod multi_remote_attachment;
pub mod reaction;

#[napi]
pub enum ContentType {
Unknown,
Text,
GroupMembershipChange,
GroupUpdated,
Reaction,
ReadReceipt,
Reply,
Attachment,
RemoteAttachment,
TransactionReference,
}

impl From<ContentType> for XmtpContentType {
fn from(value: ContentType) -> Self {
match value {
ContentType::Unknown => XmtpContentType::Unknown,
ContentType::Text => XmtpContentType::Text,
ContentType::GroupMembershipChange => XmtpContentType::GroupMembershipChange,
ContentType::GroupUpdated => XmtpContentType::GroupUpdated,
ContentType::Reaction => XmtpContentType::Reaction,
ContentType::ReadReceipt => XmtpContentType::ReadReceipt,
ContentType::Reply => XmtpContentType::Reply,
ContentType::Attachment => XmtpContentType::Attachment,
ContentType::RemoteAttachment => XmtpContentType::RemoteAttachment,
ContentType::TransactionReference => XmtpContentType::TransactionReference,
}
}
}
113 changes: 113 additions & 0 deletions bindings_node/src/content_types/multi_remote_attachment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use napi::bindgen_prelude::{Result, Uint8Array};
use napi_derive::napi;
use prost::Message;
use xmtp_content_types::multi_remote_attachment::MultiRemoteAttachmentCodec;
use xmtp_content_types::ContentCodec;
use xmtp_proto::xmtp::mls::message_contents::content_types::{
MultiRemoteAttachment as XmtpMultiRemoteAttachment,
RemoteAttachmentInfo as XmtpRemoteAttachmentInfo,
};
use xmtp_proto::xmtp::mls::message_contents::EncodedContent;

use crate::ErrorWrapper;

#[napi(object)]
pub struct RemoteAttachmentInfo {
pub secret: Uint8Array,
pub content_digest: String,
pub nonce: Uint8Array,
pub scheme: String,
pub url: String,
pub salt: Uint8Array,
pub content_length: Option<u32>,
pub filename: Option<String>,
}

impl From<RemoteAttachmentInfo> for XmtpRemoteAttachmentInfo {
fn from(remote_attachment_info: RemoteAttachmentInfo) -> Self {
XmtpRemoteAttachmentInfo {
content_digest: remote_attachment_info.content_digest,
secret: remote_attachment_info.secret.to_vec(),
nonce: remote_attachment_info.nonce.to_vec(),
salt: remote_attachment_info.salt.to_vec(),
scheme: remote_attachment_info.scheme,
url: remote_attachment_info.url,
content_length: remote_attachment_info.content_length,
filename: remote_attachment_info.filename,
}
}
}

impl From<XmtpRemoteAttachmentInfo> for RemoteAttachmentInfo {
fn from(remote_attachment_info: XmtpRemoteAttachmentInfo) -> Self {
RemoteAttachmentInfo {
secret: Uint8Array::from(remote_attachment_info.secret),
content_digest: remote_attachment_info.content_digest,
nonce: Uint8Array::from(remote_attachment_info.nonce),
scheme: remote_attachment_info.scheme,
url: remote_attachment_info.url,
salt: Uint8Array::from(remote_attachment_info.salt),
content_length: remote_attachment_info.content_length,
filename: remote_attachment_info.filename,
}
}
}

#[napi(object)]
pub struct MultiRemoteAttachment {
pub attachments: Vec<RemoteAttachmentInfo>,
}

impl From<MultiRemoteAttachment> for XmtpMultiRemoteAttachment {
fn from(multi_remote_attachment: MultiRemoteAttachment) -> Self {
XmtpMultiRemoteAttachment {
attachments: multi_remote_attachment
.attachments
.into_iter()
.map(Into::into)
.collect(),
}
}
}

impl From<XmtpMultiRemoteAttachment> for MultiRemoteAttachment {
fn from(multi_remote_attachment: XmtpMultiRemoteAttachment) -> Self {
MultiRemoteAttachment {
attachments: multi_remote_attachment
.attachments
.into_iter()
.map(Into::into)
.collect(),
}
}
}

#[napi]
pub fn encode_multi_remote_attachment(
multi_remote_attachment: MultiRemoteAttachment,
) -> Result<Uint8Array> {
// Convert MultiRemoteAttachment to MultiRemoteAttachment
let multi_remote_attachment: XmtpMultiRemoteAttachment = multi_remote_attachment.into();

// Use MultiRemoteAttachmentCodec to encode the attachments
let encoded =
MultiRemoteAttachmentCodec::encode(multi_remote_attachment).map_err(ErrorWrapper::from)?;

// Encode the EncodedContent to bytes
let mut buf = Vec::new();
encoded.encode(&mut buf).map_err(ErrorWrapper::from)?;

Ok(Uint8Array::from(buf))
}

#[napi]
pub fn decode_multi_remote_attachment(bytes: Uint8Array) -> Result<MultiRemoteAttachment> {
// Decode bytes into EncodedContent
let encoded_content =
EncodedContent::decode(bytes.to_vec().as_slice()).map_err(ErrorWrapper::from)?;

// Use MultiRemoteAttachmentCodec to decode into MultiRemoteAttachment and convert to MultiRemoteAttachment
MultiRemoteAttachmentCodec::decode(encoded_content)
.map(Into::into)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}
118 changes: 118 additions & 0 deletions bindings_node/src/content_types/reaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use napi::bindgen_prelude::{Result, Uint8Array};
use napi_derive::napi;
use prost::Message;
use xmtp_content_types::reaction::ReactionCodec;
use xmtp_content_types::ContentCodec;
use xmtp_proto::xmtp::mls::message_contents::content_types::ReactionV2;
use xmtp_proto::xmtp::mls::message_contents::EncodedContent;

use crate::ErrorWrapper;

#[napi(object)]
pub struct Reaction {
pub reference: String,
pub reference_inbox_id: String,
pub action: ReactionAction,
pub content: String,
pub schema: ReactionSchema,
}

impl From<Reaction> for ReactionV2 {
fn from(reaction: Reaction) -> Self {
ReactionV2 {
reference: reaction.reference,
reference_inbox_id: reaction.reference_inbox_id,
action: reaction.action.into(),
content: reaction.content,
schema: reaction.schema.into(),
}
}
}

impl From<ReactionV2> for Reaction {
fn from(reaction: ReactionV2) -> Self {
Reaction {
reference: reaction.reference,
reference_inbox_id: reaction.reference_inbox_id,
action: match reaction.action {
1 => ReactionAction::Added,
2 => ReactionAction::Removed,
_ => ReactionAction::Unknown,
},
content: reaction.content,
schema: match reaction.schema {
1 => ReactionSchema::Unicode,
2 => ReactionSchema::Shortcode,
3 => ReactionSchema::Custom,
_ => ReactionSchema::Unknown,
},
}
}
}

#[napi]
pub fn encode_reaction(reaction: Reaction) -> Result<Uint8Array> {
// Convert Reaction to Reaction
let reaction: ReactionV2 = reaction.into();

// Use ReactionCodec to encode the reaction
let encoded = ReactionCodec::encode(reaction).map_err(ErrorWrapper::from)?;

// Encode the EncodedContent to bytes
let mut buf = Vec::new();
encoded.encode(&mut buf).map_err(ErrorWrapper::from)?;

Ok(Uint8Array::from(buf.as_slice()))
}

#[napi]
pub fn decode_reaction(bytes: Uint8Array) -> Result<Reaction> {
// Decode bytes into EncodedContent
let encoded_content =
EncodedContent::decode(bytes.to_vec().as_slice()).map_err(ErrorWrapper::from)?;

// Use ReactionCodec to decode into Reaction and convert to Reaction
ReactionCodec::decode(encoded_content)
.map(Into::into)
.map_err(|e| napi::Error::from_reason(e.to_string()))
}

#[napi]
#[derive(Default, PartialEq, Debug)]
pub enum ReactionAction {
Unknown,
#[default]
Added,
Removed,
}

impl From<ReactionAction> for i32 {
fn from(action: ReactionAction) -> Self {
match action {
ReactionAction::Unknown => 0,
ReactionAction::Added => 1,
ReactionAction::Removed => 2,
}
}
}

#[napi]
#[derive(Default, PartialEq)]
pub enum ReactionSchema {
Unknown,
#[default]
Unicode,
Shortcode,
Custom,
}

impl From<ReactionSchema> for i32 {
fn from(schema: ReactionSchema) -> Self {
match schema {
ReactionSchema::Unknown => 0,
ReactionSchema::Unicode => 1,
ReactionSchema::Shortcode => 2,
ReactionSchema::Custom => 3,
}
}
}
38 changes: 37 additions & 1 deletion bindings_node/src/conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
consent_state::ConsentState,
conversations::{HmacKey, MessageDisappearingSettings},
encoded_content::EncodedContent,
message::{ListMessagesOptions, Message},
message::{ListMessagesOptions, Message, MessageWithReactions},
permissions::{GroupPermissions, MetadataField, PermissionPolicy, PermissionUpdateType},
streams::StreamCloser,
ErrorWrapper,
Expand Down Expand Up @@ -189,6 +189,42 @@ impl Conversation {
Ok(messages)
}

#[napi]
pub async fn find_messages_with_reactions(
&self,
opts: Option<ListMessagesOptions>,
) -> Result<Vec<MessageWithReactions>> {
let opts = opts.unwrap_or_default();
let group = MlsGroup::new(
self.inner_client.clone(),
self.group_id.clone(),
self.created_at_ns,
);
let provider = group.mls_provider().map_err(ErrorWrapper::from)?;
let conversation_type = group
.conversation_type(&provider)
.await
.map_err(ErrorWrapper::from)?;
let kind = match conversation_type {
ConversationType::Group => None,
ConversationType::Dm => None,
ConversationType::Sync => None,
};
let opts = MsgQueryArgs {
kind,
..opts.into()
};

let messages: Vec<MessageWithReactions> = group
.find_messages_with_reactions(&opts)
.map_err(ErrorWrapper::from)?
.into_iter()
.map(Into::into)
.collect();

Ok(messages)
}

#[napi]
pub async fn process_streamed_group_message(
&self,
Expand Down
1 change: 1 addition & 0 deletions bindings_node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

pub mod client;
mod consent_state;
pub mod content_types;
mod conversation;
mod conversations;
mod encoded_content;
Expand Down
Loading

0 comments on commit ea26c14

Please sign in to comment.