1use core::fmt;
2use std::{
3 collections::BTreeMap,
4 sync::atomic::{AtomicBool, AtomicU64, Ordering},
5};
6
7use platform_utils::time::Instant;
8use serde::Serialize;
9use tokio::sync::{Mutex, RwLock};
10use tracing::info;
11use uuid::Uuid;
12
13use crate::{DepositInfo, LightningAddressInfo, Payment, sdk::RuntimeEvent};
14
15#[allow(clippy::large_enum_variant)]
17#[derive(Debug, Clone, Serialize)]
18#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
19pub enum SdkEvent {
20 Synced,
22 UnclaimedDeposits {
25 unclaimed_deposits: Vec<DepositInfo>,
26 },
27 ClaimedDeposits { claimed_deposits: Vec<DepositInfo> },
30 PaymentSucceeded { payment: Payment },
33 PaymentPending { payment: Payment },
36 PaymentFailed { payment: Payment },
38 PaymentMetadataUpdated { payment: Payment },
42 AutoOptimization {
49 optimization_event: AutoOptimizationEvent,
51 },
52 LightningAddressChanged {
55 lightning_address: Option<LightningAddressInfo>,
56 },
57 NewDeposits { new_deposits: Vec<DepositInfo> },
60 UnilateralExitStateChanged,
63}
64
65impl SdkEvent {
66 pub(crate) fn from_payment(payment: Payment) -> Self {
67 match payment.status {
68 crate::PaymentStatus::Completed => SdkEvent::PaymentSucceeded { payment },
69 crate::PaymentStatus::Pending => SdkEvent::PaymentPending { payment },
70 crate::PaymentStatus::Failed => SdkEvent::PaymentFailed { payment },
71 }
72 }
73}
74
75impl fmt::Display for SdkEvent {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 match self {
78 SdkEvent::Synced => write!(f, "Synced"),
79 SdkEvent::UnclaimedDeposits { unclaimed_deposits } => {
80 write!(f, "UnclaimedDeposits[{}]", unclaimed_deposits.len())
81 }
82 SdkEvent::ClaimedDeposits { claimed_deposits } => {
83 write!(f, "ClaimedDeposits[{}]", claimed_deposits.len())
84 }
85 SdkEvent::PaymentSucceeded { payment } => {
86 write!(f, "PaymentSucceeded({})", payment.id)
87 }
88 SdkEvent::PaymentPending { payment } => {
89 write!(f, "PaymentPending({})", payment.id)
90 }
91 SdkEvent::PaymentFailed { payment } => {
92 write!(f, "PaymentFailed({})", payment.id)
93 }
94 SdkEvent::PaymentMetadataUpdated { payment } => {
95 write!(f, "PaymentMetadataUpdated({})", payment.id)
96 }
97 SdkEvent::AutoOptimization {
98 optimization_event: event,
99 } => {
100 write!(f, "AutoOptimization: {event:?}")
101 }
102 SdkEvent::LightningAddressChanged { .. } => {
103 write!(f, "LightningAddressChanged")
104 }
105 SdkEvent::NewDeposits { new_deposits } => {
106 write!(f, "NewDeposits[{}]", new_deposits.len())
107 }
108 SdkEvent::UnilateralExitStateChanged => write!(f, "UnilateralExitStateChanged"),
109 }
110 }
111}
112
113#[derive(Debug, Clone, Serialize)]
114#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
115pub enum AutoOptimizationEvent {
116 Started { total_rounds: u32 },
118 RoundCompleted {
120 current_round: u32,
121 total_rounds: u32,
122 },
123 Completed,
125 Cancelled,
127 Failed { error: String },
129 Skipped,
131}
132
133#[allow(clippy::struct_excessive_bools)]
134#[derive(Debug, Default)]
135pub struct InternalSyncedEvent {
136 pub wallet: bool,
137 pub wallet_state: bool,
138 pub deposits: bool,
139 pub lnurl_metadata: bool,
140 pub storage_incoming: Option<u32>,
141}
142
143impl InternalSyncedEvent {
144 pub fn any(&self) -> bool {
145 self.wallet
146 || self.wallet_state
147 || self.deposits
148 || self.lnurl_metadata
149 || self.storage_incoming.is_some()
150 }
151
152 pub fn any_non_zero(&self) -> bool {
153 self.wallet
154 || self.wallet_state
155 || self.deposits
156 || self.lnurl_metadata
157 || self.storage_incoming.is_some_and(|v| v > 0)
158 }
159
160 pub fn merge(&self, other: &InternalSyncedEvent) -> Self {
161 Self {
162 wallet: self.wallet || other.wallet,
163 wallet_state: self.wallet_state || other.wallet_state,
164 deposits: self.deposits || other.deposits,
165 lnurl_metadata: self.lnurl_metadata || other.lnurl_metadata,
166 storage_incoming: self
167 .storage_incoming
168 .zip(other.storage_incoming)
169 .map(|(a, b)| a.saturating_add(b))
170 .or(self.storage_incoming)
171 .or(other.storage_incoming),
172 }
173 }
174}
175
176#[cfg_attr(feature = "uniffi", uniffi::export(callback_interface))]
178#[macros::async_trait]
179pub trait EventListener: Send + Sync {
180 async fn on_event(&self, event: SdkEvent);
182}
183
184#[macros::async_trait]
192pub trait EventMiddleware: Send + Sync {
193 async fn process(&self, event: SdkEvent) -> Option<SdkEvent>;
195}
196
197pub struct EventEmitter {
204 has_real_time_sync: bool,
205 rtsync_failed: AtomicBool,
206 listener_index: AtomicU64,
207 runtime_event_handlers: RwLock<Vec<Box<dyn RuntimeEventHandler>>>,
208 internal_listeners: RwLock<BTreeMap<String, Box<dyn EventListener>>>,
210 middleware: RwLock<Vec<Box<dyn EventMiddleware>>>,
212 external_listeners: RwLock<BTreeMap<String, Box<dyn EventListener>>>,
214 synced_event_buffer: Mutex<Option<InternalSyncedEvent>>,
215}
216
217#[macros::async_trait]
223pub(crate) trait RuntimeEventHandler: Send + Sync {
224 async fn handle(&self, emitter: &EventEmitter, event: RuntimeEvent);
225}
226
227impl EventEmitter {
228 pub fn new(has_real_time_sync: bool) -> Self {
230 Self {
231 has_real_time_sync,
232 rtsync_failed: AtomicBool::new(false),
233 listener_index: AtomicU64::new(0),
234 runtime_event_handlers: RwLock::new(Vec::new()),
235 internal_listeners: RwLock::new(BTreeMap::new()),
236 middleware: RwLock::new(Vec::new()),
237 external_listeners: RwLock::new(BTreeMap::new()),
238 synced_event_buffer: Mutex::new(Some(InternalSyncedEvent::default())),
239 }
240 }
241
242 pub async fn add_external_listener(&self, listener: Box<dyn EventListener>) -> String {
252 let index = self.listener_index.fetch_add(1, Ordering::Relaxed);
253 let id = format!("listener_{}-{}", index, Uuid::new_v4());
254 let mut listeners = self.external_listeners.write().await;
255 listeners.insert(id.clone(), listener);
256 id
257 }
258
259 pub async fn remove_external_listener(&self, id: &str) -> bool {
269 let mut listeners = self.external_listeners.write().await;
270 listeners.remove(id).is_some()
271 }
272
273 pub async fn clear_external_listeners(&self) {
279 let mut listeners = self.external_listeners.write().await;
280 listeners.clear();
281 }
282
283 pub async fn add_internal_listener(&self, listener: Box<dyn EventListener>) -> String {
288 let index = self.listener_index.fetch_add(1, Ordering::Relaxed);
289 let id = format!("internal_{}-{}", index, Uuid::new_v4());
290 let mut listeners = self.internal_listeners.write().await;
291 listeners.insert(id.clone(), listener);
292 id
293 }
294
295 pub async fn remove_internal_listener(&self, id: &str) -> bool {
297 let mut listeners = self.internal_listeners.write().await;
298 listeners.remove(id).is_some()
299 }
300
301 pub async fn add_middleware(&self, middleware: Box<dyn EventMiddleware>) {
311 let mut mw = self.middleware.write().await;
312 mw.push(middleware);
313 }
314
315 pub(crate) async fn add_runtime_event_handler(&self, handler: Box<dyn RuntimeEventHandler>) {
316 let mut handlers = self.runtime_event_handlers.write().await;
317 handlers.push(handler);
318 }
319
320 pub(crate) async fn emit_runtime_event(&self, event: RuntimeEvent) {
321 let handlers = self.runtime_event_handlers.read().await;
322 for handler in handlers.iter() {
323 handler.handle(self, event.clone()).await;
324 }
325 }
326
327 pub async fn emit(&self, event: &SdkEvent) {
332 let start = Instant::now();
333 let event_label = format!("{event}");
334 let mut internal_total = std::time::Duration::ZERO;
335 let mut middleware_total = std::time::Duration::ZERO;
336 let mut external_total = std::time::Duration::ZERO;
337
338 let internal = self.internal_listeners.read().await;
340 let internal_count = internal.len();
341 for (id, listener) in internal.iter() {
342 let t = Instant::now();
343 listener.on_event(event.clone()).await;
344 let dt = t.elapsed();
345 internal_total = internal_total.saturating_add(dt);
346 info!("emit({event_label}) internal listener {id}: {dt:?}");
347 }
348 drop(internal);
349
350 let mut event = Some(event.clone());
352 let middleware = self.middleware.read().await;
353 let middleware_count = middleware.len();
354 for (i, mw) in middleware.iter().enumerate() {
355 if let Some(e) = event {
356 let t = Instant::now();
357 event = mw.process(e).await;
358 let dt = t.elapsed();
359 middleware_total = middleware_total.saturating_add(dt);
360 info!("emit({event_label}) middleware #{i}: {dt:?}");
361 } else {
362 break;
363 }
364 }
365 drop(middleware);
366
367 let mut external_count = 0;
369 if let Some(ref event) = event {
370 let listeners = self.external_listeners.read().await;
371 external_count = listeners.len();
372 for (id, listener) in listeners.iter() {
373 let t = Instant::now();
374 listener.on_event(event.clone()).await;
375 let dt = t.elapsed();
376 external_total = external_total.saturating_add(dt);
377 info!("emit({event_label}) external listener {id}: {dt:?}");
378 }
379 }
380
381 info!(
382 "emit({event_label}) completed in {:?} (internal[{}]={:?}, middleware[{}]={:?}, external[{}]={:?})",
383 start.elapsed(),
384 internal_count,
385 internal_total,
386 middleware_count,
387 middleware_total,
388 external_count,
389 external_total
390 );
391 }
392
393 pub async fn emit_synced(&self, synced: &InternalSyncedEvent) {
394 if !synced.any() {
395 return;
397 }
398
399 let mut mtx = self.synced_event_buffer.lock().await;
400
401 let is_first_event = if let Some(buffered) = &*mtx {
402 let merged = buffered.merge(synced);
403
404 if merged.wallet
407 && (!self.has_real_time_sync
408 || merged.storage_incoming.is_some()
409 || self.rtsync_failed.load(Ordering::Relaxed))
410 {
411 *mtx = None;
412 } else {
413 *mtx = Some(merged);
414 return;
415 }
416
417 true
418 } else {
419 false
420 };
421
422 drop(mtx);
423
424 if !is_first_event && !synced.any_non_zero() {
426 return;
427 }
428
429 self.emit(&SdkEvent::Synced).await;
431 }
432
433 pub async fn notify_rtsync_failed(&self) {
437 self.rtsync_failed.store(true, Ordering::Relaxed);
438
439 let mut mtx = self.synced_event_buffer.lock().await;
440 if let Some(buffered) = &*mtx
441 && buffered.wallet
442 {
443 *mtx = None;
444 drop(mtx);
445 self.emit(&SdkEvent::Synced).await;
446 }
447 }
448}
449
450impl Default for EventEmitter {
451 fn default() -> Self {
452 Self::new(false)
453 }
454}
455
456#[cfg(test)]
457mod tests {
458 use super::*;
459 use std::sync::Arc;
460 use std::sync::atomic::{AtomicBool, Ordering};
461
462 use macros::async_test_all;
463
464 #[cfg(feature = "browser-tests")]
465 wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
466
467 struct TestListener {
468 received: Arc<AtomicBool>,
469 }
470
471 #[macros::async_trait]
472 impl EventListener for TestListener {
473 async fn on_event(&self, _event: SdkEvent) {
474 self.received.store(true, Ordering::Relaxed);
475 }
476 }
477
478 #[async_test_all]
479 async fn test_event_emission() {
480 let emitter = EventEmitter::new(false);
481 let received = Arc::new(AtomicBool::new(false));
482
483 let listener = Box::new(TestListener {
485 received: received.clone(),
486 });
487
488 let _ = emitter.add_external_listener(listener).await;
489
490 let event = SdkEvent::Synced {};
491
492 emitter.emit(&event).await;
493
494 assert!(received.load(Ordering::Relaxed));
496 }
497
498 #[async_test_all]
499 async fn test_remove_listener() {
500 let emitter = EventEmitter::new(false);
501
502 let received1 = Arc::new(AtomicBool::new(false));
504 let received2 = Arc::new(AtomicBool::new(false));
505
506 let listener1 = Box::new(TestListener {
508 received: received1.clone(),
509 });
510
511 let listener2 = Box::new(TestListener {
512 received: received2.clone(),
513 });
514
515 let id1 = emitter.add_external_listener(listener1).await;
516 let id2 = emitter.add_external_listener(listener2).await;
517
518 assert!(emitter.remove_external_listener(&id1).await);
520
521 let event = SdkEvent::Synced {};
523 emitter.emit(&event).await;
524
525 assert!(!received1.load(Ordering::Relaxed));
527
528 assert!(received2.load(Ordering::Relaxed));
530
531 assert!(emitter.remove_external_listener(&id2).await);
533
534 assert!(!emitter.remove_external_listener("non-existent-id").await);
536 }
537
538 #[async_test_all]
539 async fn test_clear_external_listeners() {
540 let emitter = EventEmitter::new(false);
541
542 let received1 = Arc::new(AtomicBool::new(false));
543 let received2 = Arc::new(AtomicBool::new(false));
544
545 let id1 = emitter
546 .add_external_listener(Box::new(TestListener {
547 received: received1.clone(),
548 }))
549 .await;
550 let id2 = emitter
551 .add_external_listener(Box::new(TestListener {
552 received: received2.clone(),
553 }))
554 .await;
555
556 emitter.clear_external_listeners().await;
557
558 emitter.emit(&SdkEvent::Synced).await;
559
560 assert!(!received1.load(Ordering::Relaxed));
561 assert!(!received2.load(Ordering::Relaxed));
562
563 assert!(!emitter.remove_external_listener(&id1).await);
565 assert!(!emitter.remove_external_listener(&id2).await);
566 }
567
568 #[async_test_all]
569 async fn test_synced_event_only_emitted_with_wallet_sync() {
570 let emitter = EventEmitter::new(false);
571 let received = Arc::new(AtomicBool::new(false));
572
573 let listener = Box::new(TestListener {
574 received: received.clone(),
575 });
576
577 emitter.add_external_listener(listener).await;
578
579 emitter
581 .emit_synced(&InternalSyncedEvent {
582 wallet: false,
583 wallet_state: true,
584 deposits: true,
585 lnurl_metadata: true,
586 storage_incoming: None,
587 })
588 .await;
589
590 assert!(!received.load(Ordering::Relaxed));
591
592 emitter
594 .emit_synced(&InternalSyncedEvent {
595 wallet: true,
596 wallet_state: false,
597 deposits: false,
598 lnurl_metadata: false,
599 storage_incoming: Some(1),
600 })
601 .await;
602
603 assert!(received.load(Ordering::Relaxed));
604 }
605
606 #[async_test_all]
607 async fn test_has_real_time_sync_synced_event_only_emitted_with_wallet_and_storage_sync() {
608 let emitter = EventEmitter::new(true);
609 let received = Arc::new(AtomicBool::new(false));
610
611 let listener = Box::new(TestListener {
612 received: received.clone(),
613 });
614
615 emitter.add_external_listener(listener).await;
616
617 emitter
619 .emit_synced(&InternalSyncedEvent {
620 wallet: false,
621 wallet_state: false,
622 deposits: false,
623 lnurl_metadata: false,
624 storage_incoming: Some(0),
625 })
626 .await;
627
628 assert!(!received.load(Ordering::Relaxed));
629
630 emitter
632 .emit_synced(&InternalSyncedEvent {
633 wallet: true,
634 wallet_state: false,
635 deposits: false,
636 lnurl_metadata: false,
637 storage_incoming: None,
638 })
639 .await;
640
641 assert!(received.load(Ordering::Relaxed));
642 }
643
644 #[async_test_all]
645 async fn test_has_real_time_sync_synced_event_only_emitted_with_wallet_and_storage_sync_reverse()
646 {
647 let emitter = EventEmitter::new(true);
648 let received = Arc::new(AtomicBool::new(false));
649
650 let listener = Box::new(TestListener {
651 received: received.clone(),
652 });
653
654 emitter.add_external_listener(listener).await;
655
656 emitter
658 .emit_synced(&InternalSyncedEvent {
659 wallet: true,
660 wallet_state: false,
661 deposits: false,
662 lnurl_metadata: false,
663 storage_incoming: None,
664 })
665 .await;
666
667 assert!(!received.load(Ordering::Relaxed));
668
669 emitter
671 .emit_synced(&InternalSyncedEvent {
672 wallet: false,
673 wallet_state: false,
674 deposits: false,
675 lnurl_metadata: false,
676 storage_incoming: Some(0),
677 })
678 .await;
679
680 assert!(received.load(Ordering::Relaxed));
681 }
682
683 #[async_test_all]
684 async fn test_rtsync_failed_emits_synced_on_wallet_alone() {
685 let emitter = EventEmitter::new(true);
686 let received = Arc::new(AtomicBool::new(false));
687
688 let listener = Box::new(TestListener {
689 received: received.clone(),
690 });
691
692 emitter.add_external_listener(listener).await;
693
694 emitter
696 .emit_synced(&InternalSyncedEvent {
697 wallet: true,
698 wallet_state: false,
699 deposits: false,
700 lnurl_metadata: false,
701 storage_incoming: None,
702 })
703 .await;
704
705 assert!(!received.load(Ordering::Relaxed));
706
707 emitter.notify_rtsync_failed().await;
709
710 assert!(received.load(Ordering::Relaxed));
711 }
712
713 #[async_test_all]
714 async fn test_rtsync_failed_before_wallet_sync_emits_on_wallet() {
715 let emitter = EventEmitter::new(true);
716 let received = Arc::new(AtomicBool::new(false));
717
718 let listener = Box::new(TestListener {
719 received: received.clone(),
720 });
721
722 emitter.add_external_listener(listener).await;
723
724 emitter.notify_rtsync_failed().await;
726
727 assert!(!received.load(Ordering::Relaxed));
728
729 emitter
731 .emit_synced(&InternalSyncedEvent {
732 wallet: true,
733 wallet_state: false,
734 deposits: false,
735 lnurl_metadata: false,
736 storage_incoming: None,
737 })
738 .await;
739
740 assert!(received.load(Ordering::Relaxed));
741 }
742
743 #[async_test_all]
744 async fn test_synced_event_buffers_until_wallet_sync() {
745 let emitter = EventEmitter::new(false);
746 let received = Arc::new(AtomicBool::new(false));
747
748 let listener = Box::new(TestListener {
749 received: received.clone(),
750 });
751
752 emitter.add_external_listener(listener).await;
753
754 emitter
756 .emit_synced(&InternalSyncedEvent {
757 wallet: false,
758 wallet_state: true,
759 deposits: false,
760 lnurl_metadata: false,
761 storage_incoming: None,
762 })
763 .await;
764
765 assert!(!received.load(Ordering::Relaxed));
766
767 emitter
768 .emit_synced(&InternalSyncedEvent {
769 wallet: false,
770 wallet_state: false,
771 deposits: true,
772 lnurl_metadata: false,
773 storage_incoming: None,
774 })
775 .await;
776
777 assert!(!received.load(Ordering::Relaxed));
778
779 emitter
780 .emit_synced(&InternalSyncedEvent {
781 wallet: false,
782 wallet_state: false,
783 deposits: false,
784 lnurl_metadata: true,
785 storage_incoming: None,
786 })
787 .await;
788
789 assert!(!received.load(Ordering::Relaxed));
790
791 emitter
792 .emit_synced(&InternalSyncedEvent {
793 wallet: false,
794 wallet_state: false,
795 deposits: false,
796 lnurl_metadata: false,
797 storage_incoming: None,
798 })
799 .await;
800
801 assert!(!received.load(Ordering::Relaxed));
802
803 emitter
805 .emit_synced(&InternalSyncedEvent {
806 wallet: true,
807 wallet_state: false,
808 deposits: false,
809 lnurl_metadata: false,
810 storage_incoming: None,
811 })
812 .await;
813
814 assert!(received.load(Ordering::Relaxed));
815 }
816
817 #[async_test_all]
818 async fn test_synced_event_all_true() {
819 let emitter = EventEmitter::new(false);
820 let received = Arc::new(AtomicBool::new(false));
821
822 let listener = Box::new(TestListener {
823 received: received.clone(),
824 });
825
826 emitter.add_external_listener(listener).await;
827
828 emitter
830 .emit_synced(&InternalSyncedEvent {
831 wallet: true,
832 wallet_state: true,
833 deposits: true,
834 lnurl_metadata: true,
835 storage_incoming: Some(1),
836 })
837 .await;
838
839 assert!(received.load(Ordering::Relaxed));
840 }
841
842 #[async_test_all]
843 async fn test_synced_event_empty_does_not_emit() {
844 let emitter = EventEmitter::new(false);
845 let received = Arc::new(AtomicBool::new(false));
846
847 let listener = Box::new(TestListener {
848 received: received.clone(),
849 });
850
851 emitter.add_external_listener(listener).await;
852
853 emitter
855 .emit_synced(&InternalSyncedEvent {
856 wallet: false,
857 wallet_state: false,
858 deposits: false,
859 lnurl_metadata: false,
860 storage_incoming: None,
861 })
862 .await;
863
864 assert!(!received.load(Ordering::Relaxed));
865 }
866
867 #[async_test_all]
868 async fn test_subsequent_syncs_after_wallet_emit_immediately() {
869 use std::sync::atomic::AtomicUsize;
870
871 struct CountingListener {
872 count: Arc<AtomicUsize>,
873 }
874
875 #[macros::async_trait]
876 impl EventListener for CountingListener {
877 async fn on_event(&self, event: SdkEvent) {
878 if matches!(event, SdkEvent::Synced) {
879 self.count.fetch_add(1, Ordering::Relaxed);
880 }
881 }
882 }
883
884 let emitter = EventEmitter::new(true);
885 let count = Arc::new(AtomicUsize::new(0));
886
887 let listener = Box::new(CountingListener {
888 count: count.clone(),
889 });
890
891 emitter.add_external_listener(listener).await;
892
893 emitter
895 .emit_synced(&InternalSyncedEvent {
896 wallet: true,
897 wallet_state: false,
898 deposits: false,
899 lnurl_metadata: false,
900 storage_incoming: Some(0),
901 })
902 .await;
903
904 assert_eq!(count.load(Ordering::Relaxed), 1);
905
906 emitter
908 .emit_synced(&InternalSyncedEvent {
909 wallet: false,
910 wallet_state: true,
911 deposits: false,
912 lnurl_metadata: false,
913 storage_incoming: None,
914 })
915 .await;
916
917 assert_eq!(count.load(Ordering::Relaxed), 2);
918
919 emitter
921 .emit_synced(&InternalSyncedEvent {
922 wallet: false,
923 wallet_state: false,
924 deposits: true,
925 lnurl_metadata: false,
926 storage_incoming: None,
927 })
928 .await;
929
930 assert_eq!(count.load(Ordering::Relaxed), 3);
931
932 emitter
933 .emit_synced(&InternalSyncedEvent {
934 wallet: false,
935 wallet_state: false,
936 deposits: false,
937 lnurl_metadata: true,
938 storage_incoming: None,
939 })
940 .await;
941
942 assert_eq!(count.load(Ordering::Relaxed), 4);
943
944 emitter
945 .emit_synced(&InternalSyncedEvent {
946 wallet: false,
947 wallet_state: false,
948 deposits: false,
949 lnurl_metadata: false,
950 storage_incoming: Some(1),
951 })
952 .await;
953
954 assert_eq!(count.load(Ordering::Relaxed), 5);
955
956 emitter
958 .emit_synced(&InternalSyncedEvent {
959 wallet: false,
960 wallet_state: false,
961 deposits: false,
962 lnurl_metadata: false,
963 storage_incoming: Some(0),
964 })
965 .await;
966
967 assert_eq!(count.load(Ordering::Relaxed), 5);
968
969 emitter
970 .emit_synced(&InternalSyncedEvent {
971 wallet: true,
972 wallet_state: false,
973 deposits: false,
974 lnurl_metadata: false,
975 storage_incoming: None,
976 })
977 .await;
978
979 assert_eq!(count.load(Ordering::Relaxed), 6);
980 }
981
982 struct RecordingListener {
986 events: Arc<Mutex<Vec<String>>>,
987 }
988
989 impl RecordingListener {
990 fn new() -> (Self, Arc<Mutex<Vec<String>>>) {
991 let events = Arc::new(Mutex::new(Vec::new()));
992 (
993 Self {
994 events: events.clone(),
995 },
996 events,
997 )
998 }
999 }
1000
1001 #[macros::async_trait]
1002 impl EventListener for RecordingListener {
1003 async fn on_event(&self, event: SdkEvent) {
1004 self.events.lock().await.push(format!("{event}"));
1005 }
1006 }
1007
1008 struct SuppressSyncedMiddleware;
1010
1011 #[macros::async_trait]
1012 impl EventMiddleware for SuppressSyncedMiddleware {
1013 async fn process(&self, event: SdkEvent) -> Option<SdkEvent> {
1014 match event {
1015 SdkEvent::Synced => None,
1016 other => Some(other),
1017 }
1018 }
1019 }
1020
1021 struct DowngradePaymentMiddleware;
1023
1024 #[macros::async_trait]
1025 impl EventMiddleware for DowngradePaymentMiddleware {
1026 async fn process(&self, event: SdkEvent) -> Option<SdkEvent> {
1027 match event {
1028 SdkEvent::PaymentSucceeded { payment } => {
1029 Some(SdkEvent::PaymentPending { payment })
1030 }
1031 other => Some(other),
1032 }
1033 }
1034 }
1035
1036 struct SuppressAllMiddleware;
1038
1039 #[macros::async_trait]
1040 impl EventMiddleware for SuppressAllMiddleware {
1041 async fn process(&self, _event: SdkEvent) -> Option<SdkEvent> {
1042 None
1043 }
1044 }
1045
1046 fn test_payment() -> Payment {
1047 Payment {
1048 id: "test-id".to_string(),
1049 payment_type: crate::PaymentType::Receive,
1050 status: crate::PaymentStatus::Completed,
1051 amount: 1000,
1052 fees: 10,
1053 timestamp: 123_456,
1054 method: crate::PaymentMethod::Spark,
1055 details: None,
1056 conversion_details: None,
1057 }
1058 }
1059
1060 #[async_test_all]
1063 async fn test_internal_listener_receives_events() {
1064 let emitter = EventEmitter::new(false);
1065 let (listener, events) = RecordingListener::new();
1066
1067 emitter.add_internal_listener(Box::new(listener)).await;
1068
1069 emitter.emit(&SdkEvent::Synced).await;
1070
1071 let log = events.lock().await;
1072 assert_eq!(log.len(), 1);
1073 assert!(log[0].contains("Synced"));
1074 }
1075
1076 #[async_test_all]
1077 async fn test_remove_internal_listener() {
1078 let emitter = EventEmitter::new(false);
1079 let (listener, events) = RecordingListener::new();
1080
1081 let id = emitter.add_internal_listener(Box::new(listener)).await;
1082
1083 assert!(emitter.remove_internal_listener(&id).await);
1084 assert!(!emitter.remove_internal_listener(&id).await);
1085
1086 emitter.emit(&SdkEvent::Synced).await;
1087
1088 assert!(events.lock().await.is_empty());
1089 }
1090
1091 #[async_test_all]
1094 async fn test_middleware_suppresses_event_for_external() {
1095 let emitter = EventEmitter::new(false);
1096 let (ext, ext_events) = RecordingListener::new();
1097
1098 emitter.add_external_listener(Box::new(ext)).await;
1099 emitter
1100 .add_middleware(Box::new(SuppressSyncedMiddleware))
1101 .await;
1102
1103 emitter.emit(&SdkEvent::Synced).await;
1104
1105 assert!(ext_events.lock().await.is_empty());
1107 }
1108
1109 #[async_test_all]
1110 async fn test_middleware_transforms_event() {
1111 let emitter = EventEmitter::new(false);
1112 let (ext, ext_events) = RecordingListener::new();
1113
1114 emitter.add_external_listener(Box::new(ext)).await;
1115 emitter
1116 .add_middleware(Box::new(DowngradePaymentMiddleware))
1117 .await;
1118
1119 let event = SdkEvent::PaymentSucceeded {
1120 payment: test_payment(),
1121 };
1122 emitter.emit(&event).await;
1123
1124 let log = ext_events.lock().await;
1125 assert_eq!(log.len(), 1);
1126 assert!(log[0].contains("PaymentPending"));
1127 }
1128
1129 #[async_test_all]
1130 async fn test_middleware_passthrough_unmatched_events() {
1131 let emitter = EventEmitter::new(false);
1132 let (ext, ext_events) = RecordingListener::new();
1133
1134 emitter.add_external_listener(Box::new(ext)).await;
1135 emitter
1136 .add_middleware(Box::new(SuppressSyncedMiddleware))
1137 .await;
1138
1139 emitter.emit(&SdkEvent::Synced).await;
1141 let event = SdkEvent::PaymentSucceeded {
1142 payment: test_payment(),
1143 };
1144 emitter.emit(&event).await;
1145
1146 let log = ext_events.lock().await;
1147 assert_eq!(log.len(), 1);
1148 assert!(log[0].contains("PaymentSucceeded"));
1149 }
1150
1151 #[async_test_all]
1152 async fn test_middleware_chain_ordering() {
1153 let emitter = EventEmitter::new(false);
1154 let (ext, ext_events) = RecordingListener::new();
1155
1156 emitter.add_external_listener(Box::new(ext)).await;
1157
1158 emitter
1160 .add_middleware(Box::new(DowngradePaymentMiddleware))
1161 .await;
1162 emitter
1164 .add_middleware(Box::new(SuppressSyncedMiddleware))
1165 .await;
1166
1167 let event = SdkEvent::PaymentSucceeded {
1168 payment: test_payment(),
1169 };
1170 emitter.emit(&event).await;
1171
1172 let log = ext_events.lock().await;
1173 assert_eq!(log.len(), 1);
1174 assert!(log[0].contains("PaymentPending"));
1175 }
1176
1177 #[async_test_all]
1178 async fn test_suppress_all_middleware_stops_chain() {
1179 let emitter = EventEmitter::new(false);
1180 let (ext, ext_events) = RecordingListener::new();
1181
1182 emitter.add_external_listener(Box::new(ext)).await;
1183
1184 emitter
1186 .add_middleware(Box::new(SuppressAllMiddleware))
1187 .await;
1188 emitter
1189 .add_middleware(Box::new(DowngradePaymentMiddleware))
1190 .await;
1191
1192 emitter.emit(&SdkEvent::Synced).await;
1193 let event = SdkEvent::PaymentSucceeded {
1194 payment: test_payment(),
1195 };
1196 emitter.emit(&event).await;
1197
1198 assert!(ext_events.lock().await.is_empty());
1199 }
1200
1201 #[async_test_all]
1204 async fn test_three_phase_emit_flow() {
1205 let emitter = EventEmitter::new(false);
1206 let (int, int_events) = RecordingListener::new();
1207 let (ext, ext_events) = RecordingListener::new();
1208
1209 emitter.add_internal_listener(Box::new(int)).await;
1210 emitter.add_external_listener(Box::new(ext)).await;
1211 emitter
1212 .add_middleware(Box::new(SuppressSyncedMiddleware))
1213 .await;
1214
1215 emitter.emit(&SdkEvent::Synced).await;
1217
1218 assert_eq!(int_events.lock().await.len(), 1);
1219 assert!(ext_events.lock().await.is_empty());
1220
1221 let event = SdkEvent::PaymentSucceeded {
1223 payment: test_payment(),
1224 };
1225 emitter.emit(&event).await;
1226
1227 assert_eq!(int_events.lock().await.len(), 2);
1228 assert_eq!(ext_events.lock().await.len(), 1);
1229 }
1230
1231 #[async_test_all]
1232 async fn test_internal_sees_raw_event_external_sees_transformed() {
1233 let emitter = EventEmitter::new(false);
1234 let (int, int_events) = RecordingListener::new();
1235 let (ext, ext_events) = RecordingListener::new();
1236
1237 emitter.add_internal_listener(Box::new(int)).await;
1238 emitter.add_external_listener(Box::new(ext)).await;
1239 emitter
1240 .add_middleware(Box::new(DowngradePaymentMiddleware))
1241 .await;
1242
1243 let event = SdkEvent::PaymentSucceeded {
1244 payment: test_payment(),
1245 };
1246 emitter.emit(&event).await;
1247
1248 let int_log = int_events.lock().await;
1249 let ext_log = ext_events.lock().await;
1250
1251 assert_eq!(int_log.len(), 1);
1253 assert!(int_log[0].contains("PaymentSucceeded"));
1254
1255 assert_eq!(ext_log.len(), 1);
1257 assert!(ext_log[0].contains("PaymentPending"));
1258 }
1259
1260 #[async_test_all]
1261 async fn test_no_listeners_no_middleware_does_not_panic() {
1262 let emitter = EventEmitter::new(false);
1263 emitter.emit(&SdkEvent::Synced).await;
1264 }
1266
1267 #[async_test_all]
1268 async fn test_empty_event_does_not_emit_after_wallet_sync() {
1269 use std::sync::atomic::AtomicUsize;
1270
1271 struct CountingListener {
1272 count: Arc<AtomicUsize>,
1273 }
1274
1275 #[macros::async_trait]
1276 impl EventListener for CountingListener {
1277 async fn on_event(&self, event: SdkEvent) {
1278 if matches!(event, SdkEvent::Synced) {
1279 self.count.fetch_add(1, Ordering::Relaxed);
1280 }
1281 }
1282 }
1283
1284 let emitter = EventEmitter::new(false);
1285 let count = Arc::new(AtomicUsize::new(0));
1286
1287 let listener = Box::new(CountingListener {
1288 count: count.clone(),
1289 });
1290
1291 emitter.add_external_listener(listener).await;
1292
1293 emitter
1295 .emit_synced(&InternalSyncedEvent {
1296 wallet: true,
1297 wallet_state: false,
1298 deposits: false,
1299 lnurl_metadata: false,
1300 storage_incoming: None,
1301 })
1302 .await;
1303
1304 assert_eq!(count.load(Ordering::Relaxed), 1);
1305
1306 emitter
1308 .emit_synced(&InternalSyncedEvent {
1309 wallet: false,
1310 wallet_state: false,
1311 deposits: false,
1312 lnurl_metadata: false,
1313 storage_incoming: None,
1314 })
1315 .await;
1316
1317 assert_eq!(count.load(Ordering::Relaxed), 1); emitter
1321 .emit_synced(&InternalSyncedEvent {
1322 wallet: false,
1323 wallet_state: true,
1324 deposits: false,
1325 lnurl_metadata: false,
1326 storage_incoming: None,
1327 })
1328 .await;
1329
1330 assert_eq!(count.load(Ordering::Relaxed), 2); }
1332}