rovenue/funnel/
mod.rs

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/// FFI-facing result of a resolved funnel claim. `funnel_answers_json` is the
14/// raw `funnel_answers` object serialized to a JSON string (uniffi can't carry
15/// arbitrary JSON — façades parse it).
16#[derive(Debug, Clone)]
17pub struct FunnelClaimResult {
18    pub subscriber_id: String,
19    pub funnel_answers_json: String,
20}
21
22/// Inputs for `claim_install`. Device fields are caller-supplied; the core
23/// fills `install_id` itself.
24#[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
34/// Implemented by façades to receive the resolved claim. Mirrors `Observer`.
35pub trait FunnelClaimListener: Send + Sync {
36    fn on_funnel_claim_resolved(&self, result: FunnelClaimResult);
37}
38
39/// Holds the registered listener(s) and fans out claim resolutions. Mirrors
40/// `ObserverBus` (the FFI passes a `Box<dyn FunnelClaimListener>` once).
41#[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}