use nostr::{EventId, Timestamp, UnsignedEvent};
use nrc_mls_storage::groups::types as group_types;
use nrc_mls_storage::welcomes::types as welcome_types;
use nrc_mls_storage::NostrMlsStorageProvider;
use openmls::prelude::*;
use tls_codec::Deserialize as TlsDeserialize;
use crate::error::Error;
use crate::extension::NostrGroupDataExtension;
use crate::NostrMls;
#[derive(Debug)]
pub struct WelcomePreview {
pub staged_welcome: StagedWelcome,
pub nostr_group_data: NostrGroupDataExtension,
}
#[derive(Debug)]
pub struct JoinedGroupResult {
pub mls_group: MlsGroup,
pub nostr_group_data: NostrGroupDataExtension,
}
impl<Storage> NostrMls<Storage>
where
Storage: NostrMlsStorageProvider,
{
pub fn get_welcome(&self, event_id: &EventId) -> Result<Option<welcome_types::Welcome>, Error> {
let welcome = self
.storage()
.find_welcome_by_event_id(event_id)
.map_err(|e| Error::Welcome(e.to_string()))?;
Ok(welcome)
}
pub fn get_pending_welcomes(&self) -> Result<Vec<welcome_types::Welcome>, Error> {
let welcomes = self
.storage()
.pending_welcomes()
.map_err(|e| Error::Welcome(e.to_string()))?;
Ok(welcomes)
}
pub fn process_welcome(
&self,
wrapper_event_id: &EventId,
rumor_event: &UnsignedEvent,
) -> Result<welcome_types::Welcome, Error> {
if self.is_welcome_processed(wrapper_event_id)? {
let processed_welcome = self
.storage()
.find_processed_welcome_by_event_id(wrapper_event_id)
.map_err(|e| Error::Welcome(e.to_string()))?;
return match processed_welcome {
Some(processed_welcome) => {
if let Some(welcome_event_id) = processed_welcome.welcome_event_id {
self.storage()
.find_welcome_by_event_id(&welcome_event_id)
.map_err(|e| Error::Welcome(e.to_string()))?
.ok_or(Error::MissingWelcomeForProcessedWelcome)
} else {
Err(Error::MissingWelcomeForProcessedWelcome)
}
}
None => Err(Error::ProcessedWelcomeNotFound),
};
}
let welcome_preview = self.preview_welcome(wrapper_event_id, rumor_event)?;
let group = group_types::Group {
mls_group_id: welcome_preview
.staged_welcome
.group_context()
.group_id()
.clone(),
nostr_group_id: welcome_preview.nostr_group_data.nostr_group_id,
name: welcome_preview.nostr_group_data.name.clone(),
description: welcome_preview.nostr_group_data.description.clone(),
image_url: welcome_preview.nostr_group_data.image_url.clone(),
image_key: welcome_preview.nostr_group_data.image_key.clone(),
image_nonce: welcome_preview.nostr_group_data.image_nonce.clone(),
admin_pubkeys: welcome_preview.nostr_group_data.admins.clone(),
last_message_id: None,
last_message_at: None,
epoch: welcome_preview
.staged_welcome
.group_context()
.epoch()
.as_u64(),
state: group_types::GroupState::Pending,
};
let mls_group_id: GroupId = group.mls_group_id.clone();
self.storage()
.save_group(group)
.map_err(|e| Error::Group(e.to_string()))?;
self.storage()
.replace_group_relays(
&mls_group_id,
welcome_preview.nostr_group_data.relays.clone(),
)
.map_err(|e| Error::Group(e.to_string()))?;
let processed_welcome = welcome_types::ProcessedWelcome {
wrapper_event_id: *wrapper_event_id,
welcome_event_id: rumor_event.id,
processed_at: Timestamp::now(),
state: welcome_types::ProcessedWelcomeState::Processed,
failure_reason: None,
};
let welcome = welcome_types::Welcome {
id: rumor_event.id.unwrap(),
event: rumor_event.clone(),
mls_group_id: welcome_preview
.staged_welcome
.group_context()
.group_id()
.clone(),
nostr_group_id: welcome_preview.nostr_group_data.nostr_group_id,
group_name: welcome_preview.nostr_group_data.name,
group_description: welcome_preview.nostr_group_data.description,
group_image_url: welcome_preview.nostr_group_data.image_url,
group_image_key: welcome_preview.nostr_group_data.image_key,
group_image_nonce: welcome_preview.nostr_group_data.image_nonce,
group_admin_pubkeys: welcome_preview.nostr_group_data.admins,
group_relays: welcome_preview.nostr_group_data.relays,
welcomer: rumor_event.pubkey,
member_count: welcome_preview.staged_welcome.members().count() as u32,
state: welcome_types::WelcomeState::Pending,
wrapper_event_id: *wrapper_event_id,
};
self.storage()
.save_processed_welcome(processed_welcome)
.map_err(|e| Error::Welcome(e.to_string()))?;
self.storage()
.save_welcome(welcome.clone())
.map_err(|e| Error::Welcome(e.to_string()))?;
Ok(welcome)
}
pub fn accept_welcome(&self, welcome: &welcome_types::Welcome) -> Result<(), Error> {
let welcome_preview = self.preview_welcome(&welcome.wrapper_event_id, &welcome.event)?;
let mls_group = welcome_preview.staged_welcome.into_group(&self.provider)?;
let mut welcome = welcome.clone();
welcome.state = welcome_types::WelcomeState::Accepted;
self.storage()
.save_welcome(welcome)
.map_err(|e| Error::Welcome(e.to_string()))?;
if let Some(mut group) = self.get_group(mls_group.group_id())? {
let mls_group_id: GroupId = group.mls_group_id.clone();
group.state = group_types::GroupState::Active;
self.storage().save_group(group).map_err(
|e: nrc_mls_storage::groups::error::GroupError| Error::Group(e.to_string()),
)?;
self.storage()
.replace_group_relays(&mls_group_id, welcome_preview.nostr_group_data.relays)
.map_err(|e| Error::Group(e.to_string()))?;
}
Ok(())
}
pub fn decline_welcome(&self, welcome: &welcome_types::Welcome) -> Result<(), Error> {
let welcome_preview = self.preview_welcome(&welcome.wrapper_event_id, &welcome.event)?;
let mls_group_id = welcome_preview.staged_welcome.group_context().group_id();
let mut welcome = welcome.clone();
welcome.state = welcome_types::WelcomeState::Declined;
self.storage()
.save_welcome(welcome)
.map_err(|e| Error::Welcome(e.to_string()))?;
if let Some(mut group) = self.get_group(mls_group_id)? {
group.state = group_types::GroupState::Inactive;
self.storage()
.save_group(group)
.map_err(|e| Error::Group(e.to_string()))?;
}
Ok(())
}
fn parse_serialized_welcome(
&self,
mut welcome_message: &[u8],
) -> Result<(StagedWelcome, NostrGroupDataExtension), Error> {
let welcome_message_in = MlsMessageIn::tls_deserialize(&mut welcome_message)?;
let welcome: Welcome = match welcome_message_in.extract() {
MlsMessageBodyIn::Welcome(welcome) => welcome,
_ => return Err(Error::InvalidWelcomeMessage),
};
let mls_group_config = MlsGroupJoinConfig::builder()
.use_ratchet_tree_extension(true)
.build();
let staged_welcome =
StagedWelcome::new_from_welcome(&self.provider, &mls_group_config, welcome, None)?;
let nostr_group_data =
NostrGroupDataExtension::from_group_context(staged_welcome.group_context())?;
Ok((staged_welcome, nostr_group_data))
}
fn preview_welcome(
&self,
wrapper_event_id: &EventId,
welcome_event: &UnsignedEvent,
) -> Result<WelcomePreview, Error> {
let hex_content = match hex::decode(&welcome_event.content) {
Ok(content) => content,
Err(e) => {
let error_string = format!("Error hex decoding welcome event: {:?}", e);
let processed_welcome = welcome_types::ProcessedWelcome {
wrapper_event_id: *wrapper_event_id,
welcome_event_id: welcome_event.id,
processed_at: Timestamp::now(),
state: welcome_types::ProcessedWelcomeState::Failed,
failure_reason: Some(error_string.clone()),
};
self.storage()
.save_processed_welcome(processed_welcome)
.map_err(|e| Error::Welcome(e.to_string()))?;
tracing::error!(target: "nostr_mls::welcomes::process_welcome", "Error processing welcome: {}", error_string);
return Err(Error::Welcome(error_string));
}
};
let welcome_preview = match self.parse_serialized_welcome(&hex_content) {
Ok((staged_welcome, nostr_group_data)) => WelcomePreview {
staged_welcome,
nostr_group_data,
},
Err(e) => {
let error_string = format!("Error previewing welcome: {:?}", e);
let processed_welcome = welcome_types::ProcessedWelcome {
wrapper_event_id: *wrapper_event_id,
welcome_event_id: welcome_event.id,
processed_at: Timestamp::now(),
state: welcome_types::ProcessedWelcomeState::Failed,
failure_reason: Some(error_string.clone()),
};
self.storage()
.save_processed_welcome(processed_welcome)
.map_err(|e| Error::Welcome(e.to_string()))?;
tracing::error!(target: "nostr_mls::welcomes::process_welcome", "Error processing welcome: {}", error_string);
return Err(Error::Welcome(error_string));
}
};
Ok(welcome_preview)
}
fn is_welcome_processed(&self, wrapper_event_id: &EventId) -> Result<bool, Error> {
let processed_welcome = self
.storage()
.find_processed_welcome_by_event_id(wrapper_event_id)
.map_err(|e| Error::Welcome(e.to_string()))?;
Ok(processed_welcome.is_some())
}
}