Skip to content
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
60 changes: 32 additions & 28 deletions Cargo.lock

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

10 changes: 5 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ lru = "0.18"
mockall = { version = "0.14" }
mockall_double = "0.3.1"
once_cell = "1.21.4"
openmls = { git = "https://github.com/xmtp/openmls", rev = "e125674303a715b00bf133ceba903862304eac83", default-features = false, features = [
openmls = { git = "https://github.com/xmtp/openmls", rev = "9cdd5e798cb9217ef246964885e87d371ea553b3", default-features = false, features = [
"extensions-draft-08",
] }
openmls_basic_credential = { git = "https://github.com/xmtp/openmls", rev = "e125674303a715b00bf133ceba903862304eac83" }
openmls_libcrux_crypto = { git = "https://github.com/xmtp/openmls", rev = "e125674303a715b00bf133ceba903862304eac83", features = [
openmls_basic_credential = { git = "https://github.com/xmtp/openmls", rev = "9cdd5e798cb9217ef246964885e87d371ea553b3" }
openmls_libcrux_crypto = { git = "https://github.com/xmtp/openmls", rev = "9cdd5e798cb9217ef246964885e87d371ea553b3", features = [
"extensions-draft-08",
] }
openmls_rust_crypto = { git = "https://github.com/xmtp/openmls", rev = "e125674303a715b00bf133ceba903862304eac83" }
openmls_traits = { git = "https://github.com/xmtp/openmls", rev = "e125674303a715b00bf133ceba903862304eac83", features = [
openmls_rust_crypto = { git = "https://github.com/xmtp/openmls", rev = "9cdd5e798cb9217ef246964885e87d371ea553b3" }
openmls_traits = { git = "https://github.com/xmtp/openmls", rev = "9cdd5e798cb9217ef246964885e87d371ea553b3", features = [
"extensions-draft-08",
] }
owo-colors = { version = "4.1" }
Expand Down
85 changes: 85 additions & 0 deletions bindings/node/src/conversation/external_invite.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use crate::{ErrorWrapper, conversation::Conversation};
use napi::bindgen_prelude::{BigInt, Error, Result, Uint8Array};
use napi_derive::napi;
use xmtp_mls::groups::external_invite::{
CreateExternalInviteOpts as XmtpCreateExternalInviteOpts,
CreateExternalInviteOutput as XmtpCreateExternalInviteOutput,
};

/// Options for [`Conversation::createExternalInvite`]. Mirrors
/// [`xmtp_mls::groups::external_invite::CreateExternalInviteOpts`].
#[napi(object)]
#[derive(Default)]
pub struct CreateExternalInviteOpts {
/// Opaque application bytes identifying the service location for the
/// encrypted GroupInfo blob (URL, service ID, deep link, ...). libxmtp
/// does not interpret this; the receiving application does.
pub service_pointer: Option<Uint8Array>,
/// Advisory expiry as nanoseconds since UNIX epoch. `None` means no
/// expiry; the storage service is the hard enforcement point.
pub expires_at_ns: Option<BigInt>,
}

/// Output of [`Conversation::createExternalInvite`]. Both fields are raw
/// protobuf-serialized bytes; the application picks its own encoding for
/// transport.
#[napi(object)]
pub struct CreateExternalInviteOutput {
/// Serialized `ExternalInvitePayload` proto. The application encodes
/// this however it wants (hex, base64, raw QR, NFC, deep link, ...)
/// and embeds it in the shareable invite.
pub invite_payload: Uint8Array,
/// Serialized `EncryptedGroupInfoBlob` proto. The application uploads
/// this to its service indexed by the payload's `group_id_hash`.
pub encrypted_group_info: Uint8Array,
}

impl From<XmtpCreateExternalInviteOutput> for CreateExternalInviteOutput {
fn from(value: XmtpCreateExternalInviteOutput) -> Self {
Self {
invite_payload: Uint8Array::from(value.invite_payload),
encrypted_group_info: Uint8Array::from(value.encrypted_group_info),
}
}
}

#[napi]
impl Conversation {
/// Produce a QR-invite payload + encrypted GroupInfo blob for the
/// current epoch of this group. Pair with an external service that
/// stores the encrypted blob keyed by the payload's `group_id_hash`.
#[napi]
pub async fn create_external_invite(
&self,
opts: Option<CreateExternalInviteOpts>,
) -> Result<CreateExternalInviteOutput> {
let opts = opts.unwrap_or_default();

let service_pointer = opts.service_pointer.map(|b| b.to_vec()).unwrap_or_default();

let expires_at_ns = match opts.expires_at_ns {
Some(big) => {
let (signed, value, lossless) = big.get_u64();
if signed {
return Err(Error::from_reason("`expiresAtNs` must be non-negative"));
}
if !lossless {
return Err(Error::from_reason("`expiresAtNs` is too large for u64"));
}
Some(value)
}
None => None,
};

let group = self.create_mls_group();
let output = group
.create_external_invite(XmtpCreateExternalInviteOpts {
service_pointer,
blob_expires_at_ns: expires_at_ns,
})
.await
.map_err(ErrorWrapper::from)?;

Ok(output.into())
}
}
1 change: 1 addition & 0 deletions bindings/node/src/conversation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub mod content_types;
pub mod debug;
pub mod disappearing_messages;
pub mod dm;
pub mod external_invite;
pub mod hmac_key;
pub mod membership;
pub mod messages;
Expand Down
Loading
Loading