1pub mod client;
2pub mod session;
3
4pub use client::FunnelClient;
5pub use session::{
6 FunnelEffect, FunnelOfferKind, FunnelPackageOffer, FunnelPackagePrice, FunnelPeriodUnit,
7 FunnelSession, FunnelStep,
8};
9
10use crate::logging::Logger;
11use std::sync::{Arc, Mutex};
12
13#[derive(Debug, Clone)]
17pub struct FunnelClaimResult {
18 pub subscriber_id: String,
19 pub funnel_answers_json: String,
20}
21
22#[derive(Debug, Clone)]
25pub struct ClaimInstallParams {
26 pub platform: String,
27 pub locale: String,
28 pub timezone: String,
29 pub screen_dims: String,
30 pub device_model: Option<String>,
31 pub install_referrer: Option<String>,
32}
33
34pub trait FunnelClaimListener: Send + Sync {
36 fn on_funnel_claim_resolved(&self, result: FunnelClaimResult);
37}
38
39#[derive(Default)]
42pub struct FunnelClaimBus {
43 subs: Mutex<Vec<Arc<dyn FunnelClaimListener>>>,
44 logger: Option<Arc<Logger>>,
45}
46
47impl FunnelClaimBus {
48 pub fn with_logger(mut self, logger: Arc<Logger>) -> Self {
49 self.logger = Some(logger);
50 self
51 }
52
53 pub fn register(&self, l: Arc<dyn FunnelClaimListener>) {
54 self.subs.lock().unwrap_or_else(|e| e.into_inner()).push(l);
55 }
56
57 pub fn emit(&self, result: FunnelClaimResult) {
58 let guard = self.subs.lock().unwrap_or_else(|e| e.into_inner());
59 for s in guard.iter() {
60 let s = s.clone();
61 let r = result.clone();
62 if std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || {
63 s.on_funnel_claim_resolved(r);
64 }))
65 .is_err()
66 {
67 if let Some(l) = self.logger.as_ref() {
68 l.warn("funnel_claim_listener.on_funnel_claim_resolved panicked; skipping");
69 }
70 }
71 }
72 }
73}