Skip to main content

de_mls/core/
events.rs

1//! Event handler trait for group operations.
2//!
3//! The [`GroupEventHandler`] trait is the primary integration point between
4//! the DE-MLS core and your application. Implement this trait to handle
5//! output events from group operations.
6
7use async_trait::async_trait;
8
9use crate::core::error::CoreError;
10use crate::ds::OutboundPacket;
11use crate::protos::de_mls::messages::v1::AppMessage;
12
13/// Trait for handling output events from group operations.
14///
15/// This is the main trait you need to implement to integrate DE-MLS with
16/// your application. It receives callbacks for all significant output events:
17///
18/// - Network packets that need to be sent
19/// - Application messages for UI display
20/// - Group membership changes (join/leave)
21/// - Background operation errors
22///
23/// # Thread Safety
24///
25/// This trait requires `Send + Sync` because callbacks may be invoked from
26/// async contexts and multiple groups may be processed concurrently.
27///
28/// # Example
29///
30/// ```ignore
31/// use async_trait::async_trait;
32/// use de_mls::core::{GroupEventHandler, CoreError};
33/// use de_mls::protos::de_mls::messages::v1::AppMessage;
34/// use ds::transport::OutboundPacket;
35///
36/// struct MyHandler {
37///     transport: MyTransport,
38///     ui_sender: mpsc::Sender<UiEvent>,
39/// }
40///
41/// #[async_trait]
42/// impl GroupEventHandler for MyHandler {
43///     async fn on_outbound(
44///         &self,
45///         group_name: &str,
46///         packet: OutboundPacket,
47///     ) -> Result<String, CoreError> {
48///         self.transport.send(packet).await
49///             .map_err(|e| CoreError::DeliveryError(e.to_string()))
50///     }
51///
52///     async fn on_app_message(
53///         &self,
54///         group_name: &str,
55///         message: AppMessage,
56///     ) -> Result<(), CoreError> {
57///         self.ui_sender.send(UiEvent::Message { group_name, message }).await
58///             .map_err(|e| CoreError::HandlerError(e.to_string()))
59///     }
60///
61///     async fn on_leave_group(&self, group_name: &str) -> Result<(), CoreError> {
62///         self.ui_sender.send(UiEvent::GroupRemoved(group_name.to_string())).await
63///             .map_err(|e| CoreError::HandlerError(e.to_string()))
64///     }
65///
66///     async fn on_joined_group(&self, group_name: &str) -> Result<(), CoreError> {
67///         self.ui_sender.send(UiEvent::GroupJoined(group_name.to_string())).await
68///             .map_err(|e| CoreError::HandlerError(e.to_string()))
69///     }
70///
71///     async fn on_error(&self, group_name: &str, operation: &str, error: &str) {
72///         tracing::error!("Error in {operation} for group {group_name}: {error}");
73///     }
74/// }
75/// ```
76#[async_trait]
77pub trait GroupEventHandler: Send + Sync {
78    /// Called when a packet needs to be sent to the network.
79    ///
80    /// This is the primary output for MLS-encrypted messages. The packet
81    /// contains the encrypted payload, subtopic, group name, and app ID.
82    ///
83    /// # Arguments
84    /// * `group_name` - The name of the group this packet is for
85    /// * `packet` - The outbound packet to send
86    ///
87    /// # Returns
88    /// A message ID or identifier from the transport layer (if available).
89    ///
90    /// # Implementation Notes
91    /// - This should send the packet via your transport layer (Waku, libp2p, etc.)
92    /// - The packet's `subtopic` determines the message type (welcome vs app)
93    /// - Failures should be returned as `CoreError::DeliveryError`
94    async fn on_outbound(
95        &self,
96        group_name: &str,
97        packet: OutboundPacket,
98    ) -> Result<String, CoreError>;
99
100    /// Called when an application message should be delivered to the UI.
101    ///
102    /// This includes:
103    /// - Chat messages (`ConversationMessage`)
104    /// - Vote requests (`VotePayload`)
105    /// - Proposal notifications (`ProposalAdded`)
106    /// - Ban requests (`BanRequest`)
107    ///
108    /// # Arguments
109    /// * `group_name` - The name of the group this message is from
110    /// * `message` - The application message (see `app_message::Payload` variants)
111    ///
112    /// # Implementation Notes
113    /// - Dispatch based on `message.payload` variant
114    /// - `VotePayload` should trigger UI for user to approve/reject
115    /// - `ConversationMessage` should be displayed in chat
116    async fn on_app_message(&self, group_name: &str, message: AppMessage) -> Result<(), CoreError>;
117
118    /// Called when the user has been removed from a group.
119    ///
120    /// This is called for both:
121    /// - Voluntary leave (after the removal commit is processed)
122    /// - Forced removal (when another member removes you)
123    ///
124    /// # Arguments
125    /// * `group_name` - The name of the group to leave
126    ///
127    /// # Implementation Notes
128    /// - Remove the group from your registry
129    /// - Stop any background tasks for this group
130    /// - Notify the UI that the group was removed
131    async fn on_leave_group(&self, group_name: &str) -> Result<(), CoreError>;
132
133    /// Called when the user successfully joined a group.
134    ///
135    /// This is called after a welcome message is processed and the MLS
136    /// state is initialized.
137    ///
138    /// # Arguments
139    /// * `group_name` - The name of the group joined
140    ///
141    /// # Implementation Notes
142    /// - Update UI to show the user is now a member
143    /// - Start any background tasks for this group (epoch timer, etc.)
144    async fn on_joined_group(&self, group_name: &str) -> Result<(), CoreError>;
145
146    /// Called when a background operation fails.
147    ///
148    /// This is used for operations that run in spawned tasks, such as
149    /// voting requests, where errors can't be returned directly.
150    ///
151    /// # Arguments
152    /// * `group_name` - The name of the group
153    /// * `operation` - Description of the failed operation (e.g., "start_voting")
154    /// * `error` - The error message
155    ///
156    /// # Implementation Notes
157    /// - Log the error
158    /// - Optionally notify the UI
159    async fn on_error(&self, group_name: &str, operation: &str, error: &str);
160}