breez_sdk_spark/
events.rs

1use core::fmt;
2use std::{
3    collections::BTreeMap,
4    sync::atomic::{AtomicU64, Ordering},
5};
6
7use serde::Serialize;
8use tokio::sync::RwLock;
9use uuid::Uuid;
10
11use crate::{DepositInfo, Payment};
12
13/// Events emitted by the SDK
14#[allow(clippy::large_enum_variant)]
15#[derive(Debug, Clone, Serialize)]
16#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
17pub enum SdkEvent {
18    /// Emitted when the wallet has been synchronized with the network
19    Synced,
20    /// Emitted when data was pushed and/or pulled to/from real-time sync storage.
21    DataSynced {
22        /// Value indicating whether new data was pulled through real-time sync.
23        did_pull_new_records: bool,
24    },
25    /// Emitted when the SDK was unable to claim deposits
26    UnclaimedDeposits {
27        unclaimed_deposits: Vec<DepositInfo>,
28    },
29    ClaimedDeposits {
30        claimed_deposits: Vec<DepositInfo>,
31    },
32    PaymentSucceeded {
33        payment: Payment,
34    },
35    PaymentPending {
36        payment: Payment,
37    },
38    PaymentFailed {
39        payment: Payment,
40    },
41}
42
43impl fmt::Display for SdkEvent {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        match self {
46            SdkEvent::Synced => write!(f, "Synced"),
47            SdkEvent::DataSynced {
48                did_pull_new_records,
49            } => {
50                write!(
51                    f,
52                    "DataSynced: {} new records",
53                    match did_pull_new_records {
54                        true => "with",
55                        false => "no",
56                    }
57                )
58            }
59            SdkEvent::UnclaimedDeposits { unclaimed_deposits } => {
60                write!(f, "UnclaimedDeposits: {unclaimed_deposits:?}")
61            }
62            SdkEvent::ClaimedDeposits { claimed_deposits } => {
63                write!(f, "ClaimedDeposits: {claimed_deposits:?}")
64            }
65            SdkEvent::PaymentSucceeded { payment } => {
66                write!(f, "PaymentSucceeded: {payment:?}")
67            }
68            SdkEvent::PaymentPending { payment } => {
69                write!(f, "PaymentPending: {payment:?}")
70            }
71            SdkEvent::PaymentFailed { payment } => {
72                write!(f, "PaymentFailed: {payment:?}")
73            }
74        }
75    }
76}
77
78/// Trait for event listeners
79#[cfg_attr(feature = "uniffi", uniffi::export(callback_interface))]
80#[macros::async_trait]
81pub trait EventListener: Send + Sync {
82    /// Called when an event occurs
83    async fn on_event(&self, event: SdkEvent);
84}
85
86/// Event publisher that manages event listeners
87pub struct EventEmitter {
88    listener_index: AtomicU64,
89    listeners: RwLock<BTreeMap<String, Box<dyn EventListener>>>,
90}
91
92impl EventEmitter {
93    /// Create a new event emitter
94    pub fn new() -> Self {
95        Self {
96            listener_index: AtomicU64::new(0),
97            listeners: RwLock::new(BTreeMap::new()),
98        }
99    }
100
101    /// Add a listener to receive events
102    ///
103    /// # Arguments
104    ///
105    /// * `listener` - The listener to add
106    ///
107    /// # Returns
108    ///
109    /// A unique identifier for the listener, which can be used to remove it later
110    pub async fn add_listener(&self, listener: Box<dyn EventListener>) -> String {
111        let index = self.listener_index.fetch_add(1, Ordering::Relaxed);
112        let id = format!("listener_{}-{}", index, Uuid::new_v4());
113        let mut listeners = self.listeners.write().await;
114        listeners.insert(id.clone(), listener);
115        id
116    }
117
118    /// Remove a listener by its ID
119    ///
120    /// # Arguments
121    ///
122    /// * `id` - The ID returned from `add_listener`
123    ///
124    /// # Returns
125    ///
126    /// `true` if the listener was found and removed, `false` otherwise
127    pub async fn remove_listener(&self, id: &str) -> bool {
128        let mut listeners = self.listeners.write().await;
129        listeners.remove(id).is_some()
130    }
131
132    /// Emit an event to all registered listeners
133    pub async fn emit(&self, event: &SdkEvent) {
134        // Get a read lock on the listeners
135        let listeners = self.listeners.read().await;
136
137        // Emit the event to each listener
138        for listener in listeners.values() {
139            listener.on_event(event.clone()).await;
140        }
141    }
142}
143
144impl Default for EventEmitter {
145    fn default() -> Self {
146        Self::new()
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use std::sync::Arc;
154    use std::sync::atomic::{AtomicBool, Ordering};
155
156    use macros::async_test_all;
157
158    #[cfg(feature = "browser-tests")]
159    wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
160
161    struct TestListener {
162        received: Arc<AtomicBool>,
163    }
164
165    #[macros::async_trait]
166    impl EventListener for TestListener {
167        async fn on_event(&self, _event: SdkEvent) {
168            self.received.store(true, Ordering::Relaxed);
169        }
170    }
171
172    #[async_test_all]
173    async fn test_event_emission() {
174        let emitter = EventEmitter::new();
175        let received = Arc::new(AtomicBool::new(false));
176
177        // Create the listener with a shared reference to the atomic boolean
178        let listener = Box::new(TestListener {
179            received: received.clone(),
180        });
181
182        let _ = emitter.add_listener(listener).await;
183
184        let event = SdkEvent::Synced {};
185
186        emitter.emit(&event).await;
187
188        // Check if event was received using the shared reference
189        assert!(received.load(Ordering::Relaxed));
190    }
191
192    #[async_test_all]
193    async fn test_remove_listener() {
194        let emitter = EventEmitter::new();
195
196        // Create shared atomic booleans to track event reception
197        let received1 = Arc::new(AtomicBool::new(false));
198        let received2 = Arc::new(AtomicBool::new(false));
199
200        // Create listeners with their own shared references
201        let listener1 = Box::new(TestListener {
202            received: received1.clone(),
203        });
204
205        let listener2 = Box::new(TestListener {
206            received: received2.clone(),
207        });
208
209        let id1 = emitter.add_listener(listener1).await;
210        let id2 = emitter.add_listener(listener2).await;
211
212        // Remove the first listener
213        assert!(emitter.remove_listener(&id1).await);
214
215        // Emit an event
216        let event = SdkEvent::Synced {};
217        emitter.emit(&event).await;
218
219        // The first listener should not receive the event
220        assert!(!received1.load(Ordering::Relaxed));
221
222        // The second listener should receive the event
223        assert!(received2.load(Ordering::Relaxed));
224
225        // Remove the second listener
226        assert!(emitter.remove_listener(&id2).await);
227
228        // Try to remove a non-existent listener
229        assert!(!emitter.remove_listener("non-existent-id").await);
230    }
231}