at main 121 lines 3.7 kB view raw
1//! Collab coordinator types and helpers. 2//! 3//! Provides shared types for collab coordination that can be used by both 4//! Rust UI frameworks (Dioxus) and JS bindings. 5 6use smol_str::SmolStr; 7 8/// Session record TTL in minutes. 9pub const SESSION_TTL_MINUTES: u32 = 15; 10 11/// How often to refresh session record (ms). 12pub const SESSION_REFRESH_INTERVAL_MS: u32 = 5 * 60 * 1000; // 5 minutes 13 14/// How often to poll for new peers (ms). 15pub const PEER_DISCOVERY_INTERVAL_MS: u32 = 30 * 1000; // 30 seconds 16 17/// Coordinator state machine states. 18/// 19/// Tracks the lifecycle of a collab session from initialization through 20/// active collaboration. UI can use this to show appropriate status indicators. 21#[derive(Debug, Clone, PartialEq)] 22pub enum CoordinatorState { 23 /// Initial state - waiting for worker to be ready. 24 Initializing, 25 /// Creating session record on PDS. 26 CreatingSession { 27 /// The iroh node ID for this session. 28 node_id: SmolStr, 29 /// Optional relay URL for NAT traversal. 30 relay_url: Option<SmolStr>, 31 }, 32 /// Active collab session. 33 Active { 34 /// The AT URI of the session record on PDS. 35 session_uri: SmolStr, 36 }, 37 /// Error state. 38 Error(SmolStr), 39} 40 41impl Default for CoordinatorState { 42 fn default() -> Self { 43 Self::Initializing 44 } 45} 46 47impl CoordinatorState { 48 /// Returns true if the coordinator is in an error state. 49 pub fn is_error(&self) -> bool { 50 matches!(self, Self::Error(_)) 51 } 52 53 /// Returns true if the coordinator is actively collaborating. 54 pub fn is_active(&self) -> bool { 55 matches!(self, Self::Active { .. }) 56 } 57 58 /// Returns the error message if in error state. 59 pub fn error_message(&self) -> Option<&str> { 60 match self { 61 Self::Error(msg) => Some(msg.as_str()), 62 _ => None, 63 } 64 } 65 66 /// Returns the session URI if active. 67 pub fn session_uri(&self) -> Option<&str> { 68 match self { 69 Self::Active { session_uri } => Some(session_uri.as_str()), 70 _ => None, 71 } 72 } 73} 74 75/// Compute the gossip topic hash for a resource URI. 76/// 77/// The topic is a blake3 hash of the resource URI bytes, used to identify 78/// the gossip swarm for collaborative editing of that resource. 79pub fn compute_collab_topic(resource_uri: &str) -> [u8; 32] { 80 let hash = weaver_common::blake3::hash(resource_uri.as_bytes()); 81 *hash.as_bytes() 82} 83 84#[cfg(test)] 85mod tests { 86 use super::*; 87 88 #[test] 89 fn test_coordinator_state_default() { 90 assert_eq!(CoordinatorState::default(), CoordinatorState::Initializing); 91 } 92 93 #[test] 94 fn test_coordinator_state_is_error() { 95 assert!(!CoordinatorState::Initializing.is_error()); 96 assert!(CoordinatorState::Error("test".into()).is_error()); 97 } 98 99 #[test] 100 fn test_coordinator_state_is_active() { 101 assert!(!CoordinatorState::Initializing.is_active()); 102 assert!(CoordinatorState::Active { 103 session_uri: "at://test".into() 104 } 105 .is_active()); 106 } 107 108 #[test] 109 fn test_compute_collab_topic_deterministic() { 110 let topic1 = compute_collab_topic("at://did:plc:test/app.weaver.notebook.entry/abc"); 111 let topic2 = compute_collab_topic("at://did:plc:test/app.weaver.notebook.entry/abc"); 112 assert_eq!(topic1, topic2); 113 } 114 115 #[test] 116 fn test_compute_collab_topic_different_uris() { 117 let topic1 = compute_collab_topic("at://did:plc:test/app.weaver.notebook.entry/abc"); 118 let topic2 = compute_collab_topic("at://did:plc:test/app.weaver.notebook.entry/def"); 119 assert_ne!(topic1, topic2); 120 } 121}