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