Skip to main content

nodedb_lite/sync/client/
config.rs

1//! Sync client configuration types.
2
3use std::sync::Arc;
4use std::time::Duration;
5
6/// Token provider callback type.
7///
8/// Called when the sync client needs a fresh JWT token — either proactively
9/// before expiry or reactively after an auth rejection. The provider should
10/// return a fresh JWT token string.
11///
12/// # Example
13/// ```ignore
14/// let provider: TokenProvider = Arc::new(|| Box::pin(async {
15///     my_auth_service.get_token().await
16/// }));
17/// ```
18pub type TokenProvider = Arc<
19    dyn Fn() -> std::pin::Pin<Box<dyn std::future::Future<Output = Option<String>> + Send>>
20        + Send
21        + Sync,
22>;
23
24/// Sync client configuration.
25#[derive(Clone)]
26pub struct SyncConfig {
27    /// WebSocket URL to the Origin sync endpoint (e.g., `wss://api.nodedb.cloud/sync`).
28    pub url: String,
29    /// JWT bearer token for initial authentication.
30    pub jwt_token: String,
31    /// Client version string (sent in handshake).
32    pub client_version: String,
33    /// Minimum backoff on reconnect.
34    pub min_backoff: Duration,
35    /// Maximum backoff on reconnect.
36    pub max_backoff: Duration,
37    /// Keepalive ping interval.
38    pub ping_interval: Duration,
39    /// Maximum deltas to batch in a single push.
40    pub max_batch_size: usize,
41    /// Token provider for automatic refresh. If `None`, no auto-refresh occurs.
42    pub token_provider: Option<TokenProvider>,
43    /// Token lifetime in seconds (used to schedule proactive refresh at 80%).
44    /// If 0, no proactive refresh occurs — only reactive on auth rejection.
45    pub token_lifetime_secs: u64,
46    /// Tenant the session authenticates as. Synthesized collection
47    /// descriptors carry this tenant. Defaults to 1.
48    pub tenant_id: u64,
49}
50
51impl std::fmt::Debug for SyncConfig {
52    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53        f.debug_struct("SyncConfig")
54            .field("url", &self.url)
55            .field("jwt_token", &"[REDACTED]")
56            .field("client_version", &self.client_version)
57            .field("min_backoff", &self.min_backoff)
58            .field("max_backoff", &self.max_backoff)
59            .field("ping_interval", &self.ping_interval)
60            .field("max_batch_size", &self.max_batch_size)
61            .field("token_provider", &self.token_provider.is_some())
62            .field("token_lifetime_secs", &self.token_lifetime_secs)
63            .field("tenant_id", &self.tenant_id)
64            .finish()
65    }
66}
67
68impl SyncConfig {
69    pub fn new(url: impl Into<String>, jwt_token: impl Into<String>) -> Self {
70        Self {
71            url: url.into(),
72            jwt_token: jwt_token.into(),
73            client_version: env!("CARGO_PKG_VERSION").to_string(),
74            min_backoff: Duration::from_secs(1),
75            max_backoff: Duration::from_secs(60),
76            ping_interval: Duration::from_secs(30),
77            max_batch_size: 100,
78            token_provider: None,
79            token_lifetime_secs: 0,
80            tenant_id: 1,
81        }
82    }
83
84    /// Set a token provider for automatic JWT refresh.
85    pub fn with_token_provider(mut self, provider: TokenProvider, lifetime_secs: u64) -> Self {
86        self.token_provider = Some(provider);
87        self.token_lifetime_secs = lifetime_secs;
88        self
89    }
90
91    /// Set the tenant the session authenticates as.
92    pub fn with_tenant_id(mut self, tenant_id: u64) -> Self {
93        self.tenant_id = tenant_id;
94        self
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn default_tenant_id_is_one() {
104        let config = SyncConfig::new("wss://localhost:9090/sync", "t");
105        assert_eq!(config.tenant_id, 1);
106    }
107
108    #[test]
109    fn with_tenant_id_sets_tenant() {
110        let config = SyncConfig::new("wss://localhost:9090/sync", "t").with_tenant_id(42);
111        assert_eq!(config.tenant_id, 42);
112    }
113}
114
115/// Connection state.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum SyncState {
118    /// Not connected, not trying.
119    Disconnected,
120    /// Attempting to connect.
121    Connecting,
122    /// Connected and authenticated.
123    Connected,
124    /// Connection lost, backing off before retry.
125    Reconnecting,
126}