openrtc 0.2.0

OpenRTC: a Rust-first P2P runtime for device discovery, signaling, and iroh/QUIC networking.
Documentation
//! Native device heartbeat / stale-device sweep for `NativeFirestoreSignalingBackend`.
//!
//! Extracted from `native_signaling.rs` to keep that file focused on discovery,
//! presence updates, session signaling, and the subscribe listeners.
//!
//! `sweep_stale_devices` is called both inline during device list/search queries
//! and from a background cleanup task spawned inside `subscribe_devices`.

use super::device_registry::{is_device_record_expired, offline_device_expires_at_ms};
use super::native_signaling::{DeviceFields, NativeFirestoreSignalingBackend};
use anyhow::Result;

impl NativeFirestoreSignalingBackend {
    /// Mark all stale online device documents for this user as offline.
    ///
    /// A document is "stale" when it has been online past its TTL without a
    /// heartbeat update. Sets `online = false` and extends `expiresAt` by the
    /// offline-retention window so the document is not deleted immediately.
    ///
    /// Returns the number of documents that were swept.
    pub(super) async fn sweep_stale_devices(
        &self,
        user_id: &str,
        stale_after_ms: i64,
    ) -> Result<usize> {
        let parent = self.user_parent(user_id);
        let parent_for_sweep = parent.clone();
        let expected_tag = self.app_tag().to_string();
        let stale_after_ms = stale_after_ms.max(0);

        self.with_auth_retry(move |db| {
            let parent = parent_for_sweep.clone();
            let expected_tag = expected_tag.clone();
            async move {
                let docs = db
                    .fluent()
                    .select()
                    .from("devices")
                    .parent(&parent)
                    .query()
                    .await?;

                let now_ms = std::time::SystemTime::now()
                    .duration_since(std::time::UNIX_EPOCH)?
                    .as_millis() as i64;

                let stale_doc_ids: Vec<String> = docs
                    .into_iter()
                    .filter_map(|doc| {
                        let id = doc.name.split('/').last().unwrap_or("").to_string();
                        let fields = doc.fields;
                        let shared = Self::shared_device_record_from_fields(&fields);

                        if shared.effective_app_tag() != Some(expected_tag.as_str()) {
                            return None;
                        }

                        if !shared.online {
                            return None;
                        }

                        if is_device_record_expired(&shared, now_ms, stale_after_ms) {
                            return Some(id);
                        }

                        None
                    })
                    .collect();

                if stale_doc_ids.is_empty() {
                    return Ok(0usize);
                }

                let offline_update = DeviceFields {
                    app_tag: None,
                    user_id: None,
                    device_id: None,
                    device_name: None,
                    node_id: None,
                    platform_type: None,
                    session_id: None,
                    tag: None,
                    kind: None,
                    capabilities: None,
                    metadata: None,
                    online: Some(false),
                    ticket: None,
                    last_seen_at: None,
                    updated_at: Some(now_ms),
                    created_at: None,
                    expires_at: Some(offline_device_expires_at_ms(now_ms)),
                    excluded_peers: vec![],
                };

                for doc_id in &stale_doc_ids {
                    db.fluent()
                        .update()
                        .fields(vec!["online", "updatedAt", "expiresAt"])
                        .in_col("devices")
                        .document_id(doc_id)
                        .parent(&parent)
                        .object(&offline_update)
                        .execute::<()>()
                        .await?;
                }

                Ok(stale_doc_ids.len())
            }
        })
        .await
    }
}