breez_sdk_spark/
events.rs

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