Skip to content

Commit

Permalink
Add 1:1 groups to node bindings (#1199)
Browse files Browse the repository at this point in the history
* Add inner client accessor on client

* Move inbox id methods into separate mod

* Move consent state methods into mod

* Move inbox state method into mod

* Add sort direction to NapiListMessagesOptions

* Add dm_peer_inbox_id to NapiGroup

* Update NapiListConversationsOptions

* Add create_dm to NapiConversations

* Add list_groups and list_dms

* Update conversation streaming methods

* Add error logging during tests

* Add signatures mod

* Add tests

* Clippy updates

* Remove .only on some tests

* Update bindings_node/src/conversations.rs

Co-authored-by: Andrew Plaza <github.tech@liquidthink.net>

---------

Co-authored-by: Andrew Plaza <github.tech@liquidthink.net>
  • Loading branch information
rygine and insipx authored Oct 29, 2024
1 parent 34f90be commit 3a33b29
Show file tree
Hide file tree
Showing 11 changed files with 711 additions and 267 deletions.
34 changes: 34 additions & 0 deletions bindings_node/src/consent_state.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use napi::bindgen_prelude::Result;
use napi_derive::napi;
use xmtp_mls::storage::consent_record::{ConsentState, ConsentType, StoredConsentRecord};

use crate::{mls_client::NapiClient, ErrorWrapper};

#[napi]
pub enum NapiConsentState {
Unknown,
Expand Down Expand Up @@ -61,3 +64,34 @@ impl From<NapiConsent> for StoredConsentRecord {
}
}
}

#[napi]
impl NapiClient {
#[napi]
pub async fn set_consent_states(&self, records: Vec<NapiConsent>) -> Result<()> {
let stored_records: Vec<StoredConsentRecord> =
records.into_iter().map(StoredConsentRecord::from).collect();

self
.inner_client()
.set_consent_states(stored_records)
.await
.map_err(ErrorWrapper::from)?;
Ok(())
}

#[napi]
pub async fn get_consent_state(
&self,
entity_type: NapiConsentEntityType,
entity: String,
) -> Result<NapiConsentState> {
let result = self
.inner_client()
.get_consent_state(entity_type.into(), entity)
.await
.map_err(ErrorWrapper::from)?;

Ok(result.into())
}
}
166 changes: 148 additions & 18 deletions bindings_node/src/conversations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,91 @@ use napi_derive::napi;
use xmtp_mls::client::FindGroupParams;
use xmtp_mls::groups::group_metadata::ConversationType;
use xmtp_mls::groups::{GroupMetadataOptions, PreconfiguredPolicies};
use xmtp_mls::storage::group::GroupMembershipState;

use crate::messages::NapiMessage;
use crate::permissions::NapiGroupPermissionsOptions;
use crate::ErrorWrapper;
use crate::{groups::NapiGroup, mls_client::RustXmtpClient, streams::NapiStreamCloser};

#[napi]
#[derive(Debug)]
pub enum NapiConversationType {
Dm = 0,
Group = 1,
Sync = 2,
}

impl From<ConversationType> for NapiConversationType {
fn from(ct: ConversationType) -> Self {
match ct {
ConversationType::Dm => NapiConversationType::Dm,
ConversationType::Group => NapiConversationType::Group,
ConversationType::Sync => NapiConversationType::Sync,
}
}
}

impl From<NapiConversationType> for ConversationType {
fn from(nct: NapiConversationType) -> Self {
match nct {
NapiConversationType::Dm => ConversationType::Dm,
NapiConversationType::Group => ConversationType::Group,
NapiConversationType::Sync => ConversationType::Sync,
}
}
}

#[napi]
#[derive(Debug)]
pub enum NapiGroupMembershipState {
Allowed = 0,
Rejected = 1,
Pending = 2,
}

impl From<GroupMembershipState> for NapiGroupMembershipState {
fn from(gms: GroupMembershipState) -> Self {
match gms {
GroupMembershipState::Allowed => NapiGroupMembershipState::Allowed,
GroupMembershipState::Rejected => NapiGroupMembershipState::Rejected,
GroupMembershipState::Pending => NapiGroupMembershipState::Pending,
}
}
}

impl From<NapiGroupMembershipState> for GroupMembershipState {
fn from(ngms: NapiGroupMembershipState) -> Self {
match ngms {
NapiGroupMembershipState::Allowed => GroupMembershipState::Allowed,
NapiGroupMembershipState::Rejected => GroupMembershipState::Rejected,
NapiGroupMembershipState::Pending => GroupMembershipState::Pending,
}
}
}

#[napi(object)]
#[derive(Debug, Default)]
pub struct NapiListConversationsOptions {
pub allowed_states: Option<Vec<NapiGroupMembershipState>>,
pub created_after_ns: Option<i64>,
pub created_before_ns: Option<i64>,
pub limit: Option<i64>,
pub conversation_type: Option<NapiConversationType>,
}

impl From<NapiListConversationsOptions> for FindGroupParams {
fn from(opts: NapiListConversationsOptions) -> Self {
FindGroupParams {
allowed_states: opts
.allowed_states
.map(|states| states.into_iter().map(From::from).collect()),
conversation_type: opts.conversation_type.map(|ct| ct.into()),
created_after_ns: opts.created_after_ns,
created_before_ns: opts.created_before_ns,
limit: opts.limit,
}
}
}

#[napi(object)]
Expand Down Expand Up @@ -99,6 +173,17 @@ impl NapiConversations {
Ok(convo.into())
}

#[napi]
pub async fn create_dm(&self, account_address: String) -> Result<NapiGroup> {
let convo = self
.inner_client
.create_dm(account_address)
.await
.map_err(ErrorWrapper::from)?;

Ok(convo.into())
}

#[napi]
pub fn find_group_by_id(&self, group_id: String) -> Result<NapiGroup> {
let group_id = hex::decode(group_id).map_err(ErrorWrapper::from)?;
Expand Down Expand Up @@ -159,22 +244,13 @@ impl NapiConversations {

#[napi]
pub async fn list(&self, opts: Option<NapiListConversationsOptions>) -> Result<Vec<NapiGroup>> {
let opts = match opts {
Some(options) => options,
None => NapiListConversationsOptions {
created_after_ns: None,
created_before_ns: None,
limit: None,
},
};
// let opts = match opts {
// Some(options) => options,
// None => NapiListConversationsOptions::default(),
// };
let convo_list: Vec<NapiGroup> = 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()
})
.find_groups(opts.unwrap_or_default().into())
.map_err(ErrorWrapper::from)?
.into_iter()
.map(NapiGroup::from)
Expand All @@ -183,13 +259,43 @@ impl NapiConversations {
Ok(convo_list)
}

#[napi]
pub async fn list_groups(
&self,
opts: Option<NapiListConversationsOptions>,
) -> Result<Vec<NapiGroup>> {
self
.list(Some(NapiListConversationsOptions {
conversation_type: Some(NapiConversationType::Group),
..opts.unwrap_or_default()
}))
.await
}

#[napi]
pub async fn list_dms(
&self,
opts: Option<NapiListConversationsOptions>,
) -> Result<Vec<NapiGroup>> {
self
.list(Some(NapiListConversationsOptions {
conversation_type: Some(NapiConversationType::Dm),
..opts.unwrap_or_default()
}))
.await
}

#[napi(ts_args_type = "callback: (err: null | Error, result: NapiGroup) => void")]
pub fn stream(&self, callback: JsFunction) -> Result<NapiStreamCloser> {
pub fn stream(
&self,
callback: JsFunction,
conversation_type: Option<NapiConversationType>,
) -> Result<NapiStreamCloser> {
let tsfn: ThreadsafeFunction<NapiGroup, ErrorStrategy::CalleeHandled> =
callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value]))?;
let stream_closer = RustXmtpClient::stream_conversations_with_callback(
self.inner_client.clone(),
Some(ConversationType::Group),
conversation_type.map(|ct| ct.into()),
move |convo| {
tsfn.call(
convo
Expand All @@ -204,13 +310,27 @@ impl NapiConversations {
Ok(NapiStreamCloser::new(stream_closer))
}

#[napi(ts_args_type = "callback: (err: null | Error, result: NapiGroup) => void")]
pub fn stream_groups(&self, callback: JsFunction) -> Result<NapiStreamCloser> {
self.stream(callback, Some(NapiConversationType::Group))
}

#[napi(ts_args_type = "callback: (err: null | Error, result: NapiGroup) => void")]
pub fn stream_dms(&self, callback: JsFunction) -> Result<NapiStreamCloser> {
self.stream(callback, Some(NapiConversationType::Dm))
}

#[napi(ts_args_type = "callback: (err: null | Error, result: NapiMessage) => void")]
pub fn stream_all_messages(&self, callback: JsFunction) -> Result<NapiStreamCloser> {
pub fn stream_all_messages(
&self,
callback: JsFunction,
conversation_type: Option<NapiConversationType>,
) -> Result<NapiStreamCloser> {
let tsfn: ThreadsafeFunction<NapiMessage, ErrorStrategy::CalleeHandled> =
callback.create_threadsafe_function(0, |ctx| Ok(vec![ctx.value]))?;
let stream_closer = RustXmtpClient::stream_all_messages_with_callback(
self.inner_client.clone(),
Some(ConversationType::Group),
conversation_type.map(Into::into),
move |message| {
tsfn.call(
message
Expand All @@ -224,4 +344,14 @@ impl NapiConversations {

Ok(NapiStreamCloser::new(stream_closer))
}

#[napi(ts_args_type = "callback: (err: null | Error, result: NapiMessage) => void")]
pub fn stream_all_group_messages(&self, callback: JsFunction) -> Result<NapiStreamCloser> {
self.stream_all_messages(callback, Some(NapiConversationType::Group))
}

#[napi(ts_args_type = "callback: (err: null | Error, result: NapiMessage) => void")]
pub fn stream_all_dm_messages(&self, callback: JsFunction) -> Result<NapiStreamCloser> {
self.stream_all_messages(callback, Some(NapiConversationType::Dm))
}
}
25 changes: 15 additions & 10 deletions bindings_node/src/groups.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,7 @@ impl NapiGroup {

#[napi]
pub fn find_messages(&self, opts: Option<NapiListMessagesOptions>) -> Result<Vec<NapiMessage>> {
let opts = match opts {
Some(options) => options,
None => NapiListMessagesOptions {
sent_before_ns: None,
sent_after_ns: None,
limit: None,
delivery_status: None,
},
};
let opts = opts.unwrap_or_default();

let group = MlsGroup::new(
self.inner_client.clone(),
Expand All @@ -177,14 +169,16 @@ impl NapiGroup {
);

let delivery_status = opts.delivery_status.map(|status| status.into());
let direction = opts.direction.map(|dir| dir.into());

let messages: Vec<NapiMessage> = group
.find_messages(
&MsgQueryArgs::default()
.maybe_sent_before_ns(opts.sent_before_ns)
.maybe_sent_after_ns(opts.sent_after_ns)
.maybe_delivery_status(delivery_status)
.maybe_limit(opts.limit),
.maybe_limit(opts.limit)
.maybe_direction(direction),
)
.map_err(ErrorWrapper::from)?
.into_iter()
Expand Down Expand Up @@ -644,4 +638,15 @@ impl NapiGroup {

Ok(())
}

#[napi]
pub fn dm_peer_inbox_id(&self) -> Result<String> {
let group = MlsGroup::new(
self.inner_client.clone(),
self.group_id.clone(),
self.created_at_ns,
);

Ok(group.dm_inbox_id().map_err(ErrorWrapper::from)?)
}
}
37 changes: 37 additions & 0 deletions bindings_node/src/inbox_id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use crate::ErrorWrapper;
use napi::bindgen_prelude::Result;
use napi_derive::napi;
use xmtp_api_grpc::grpc_api_helper::Client as TonicApiClient;
use xmtp_id::associations::generate_inbox_id as xmtp_id_generate_inbox_id;
use xmtp_mls::api::ApiClientWrapper;
use xmtp_mls::retry::Retry;

#[napi]
pub async fn get_inbox_id_for_address(
host: String,
is_secure: bool,
account_address: String,
) -> Result<Option<String>> {
let account_address = account_address.to_lowercase();
let api_client = ApiClientWrapper::new(
TonicApiClient::create(host.clone(), is_secure)
.await
.map_err(ErrorWrapper::from)?,
Retry::default(),
);

let results = api_client
.get_inbox_ids(vec![account_address.clone()])
.await
.map_err(ErrorWrapper::from)?;

Ok(results.get(&account_address).cloned())
}

#[napi]
pub fn generate_inbox_id(account_address: String) -> String {
let account_address = account_address.to_lowercase();
// ensure that the nonce is always 1 for now since this will only be used for the
// create_client function above, which also has a hard-coded nonce of 1
xmtp_id_generate_inbox_id(&account_address, &1)
}
Loading

0 comments on commit 3a33b29

Please sign in to comment.