pub trait GroupEventHandler: Send + Sync {
// Required methods
fn on_outbound<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
packet: OutboundPacket,
) -> Pin<Box<dyn Future<Output = Result<String, CoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn on_app_message<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
message: AppMessage,
) -> Pin<Box<dyn Future<Output = Result<(), CoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn on_leave_group<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), CoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn on_joined_group<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), CoreError>> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait;
fn on_error<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
operation: &'life2 str,
error: &'life3 str,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>
where Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait;
}Expand description
Trait for handling output events from group operations.
This is the main trait you need to implement to integrate DE-MLS with your application. It receives callbacks for all significant output events:
- Network packets that need to be sent
- Application messages for UI display
- Group membership changes (join/leave)
- Background operation errors
§Thread Safety
This trait requires Send + Sync because callbacks may be invoked from
async contexts and multiple groups may be processed concurrently.
§Example
use async_trait::async_trait;
use de_mls::core::{GroupEventHandler, CoreError};
use de_mls::protos::de_mls::messages::v1::AppMessage;
use ds::transport::OutboundPacket;
struct MyHandler {
transport: MyTransport,
ui_sender: mpsc::Sender<UiEvent>,
}
#[async_trait]
impl GroupEventHandler for MyHandler {
async fn on_outbound(
&self,
group_name: &str,
packet: OutboundPacket,
) -> Result<String, CoreError> {
self.transport.send(packet).await
.map_err(|e| CoreError::DeliveryError(e.to_string()))
}
async fn on_app_message(
&self,
group_name: &str,
message: AppMessage,
) -> Result<(), CoreError> {
self.ui_sender.send(UiEvent::Message { group_name, message }).await
.map_err(|e| CoreError::HandlerError(e.to_string()))
}
async fn on_leave_group(&self, group_name: &str) -> Result<(), CoreError> {
self.ui_sender.send(UiEvent::GroupRemoved(group_name.to_string())).await
.map_err(|e| CoreError::HandlerError(e.to_string()))
}
async fn on_joined_group(&self, group_name: &str) -> Result<(), CoreError> {
self.ui_sender.send(UiEvent::GroupJoined(group_name.to_string())).await
.map_err(|e| CoreError::HandlerError(e.to_string()))
}
async fn on_error(&self, group_name: &str, operation: &str, error: &str) {
tracing::error!("Error in {operation} for group {group_name}: {error}");
}
}Required Methods§
Sourcefn on_outbound<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
packet: OutboundPacket,
) -> Pin<Box<dyn Future<Output = Result<String, CoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn on_outbound<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
packet: OutboundPacket,
) -> Pin<Box<dyn Future<Output = Result<String, CoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Called when a packet needs to be sent to the network.
This is the primary output for MLS-encrypted messages. The packet contains the encrypted payload, subtopic, group name, and app ID.
§Arguments
group_name- The name of the group this packet is forpacket- The outbound packet to send
§Returns
A message ID or identifier from the transport layer (if available).
§Implementation Notes
- This should send the packet via your transport layer (Waku, libp2p, etc.)
- The packet’s
subtopicdetermines the message type (welcome vs app) - Failures should be returned as
CoreError::DeliveryError
Sourcefn on_app_message<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
message: AppMessage,
) -> Pin<Box<dyn Future<Output = Result<(), CoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn on_app_message<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
message: AppMessage,
) -> Pin<Box<dyn Future<Output = Result<(), CoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Called when an application message should be delivered to the UI.
This includes:
- Chat messages (
ConversationMessage) - Vote requests (
VotePayload) - Proposal notifications (
ProposalAdded) - Ban requests (
BanRequest)
§Arguments
group_name- The name of the group this message is frommessage- The application message (seeapp_message::Payloadvariants)
§Implementation Notes
- Dispatch based on
message.payloadvariant VotePayloadshould trigger UI for user to approve/rejectConversationMessageshould be displayed in chat
Sourcefn on_leave_group<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), CoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn on_leave_group<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), CoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Called when the user has been removed from a group.
This is called for both:
- Voluntary leave (after the removal commit is processed)
- Forced removal (when another member removes you)
§Arguments
group_name- The name of the group to leave
§Implementation Notes
- Remove the group from your registry
- Stop any background tasks for this group
- Notify the UI that the group was removed
Sourcefn on_joined_group<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), CoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
fn on_joined_group<'life0, 'life1, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
) -> Pin<Box<dyn Future<Output = Result<(), CoreError>> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
Called when the user successfully joined a group.
This is called after a welcome message is processed and the MLS state is initialized.
§Arguments
group_name- The name of the group joined
§Implementation Notes
- Update UI to show the user is now a member
- Start any background tasks for this group (epoch timer, etc.)
Sourcefn on_error<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
operation: &'life2 str,
error: &'life3 str,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
fn on_error<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
group_name: &'life1 str,
operation: &'life2 str,
error: &'life3 str,
) -> Pin<Box<dyn Future<Output = ()> + Send + 'async_trait>>where
Self: 'async_trait,
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Called when a background operation fails.
This is used for operations that run in spawned tasks, such as voting requests, where errors can’t be returned directly.
§Arguments
group_name- The name of the groupoperation- Description of the failed operation (e.g., “start_voting”)error- The error message
§Implementation Notes
- Log the error
- Optionally notify the UI