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}