//! Typing indicator and presence event handlers on `MessageService`. //! //! These are pure broadcast events (no persistence). Typing indicators show //! "user is typing…" in the channel. Presence indicates online/offline status. use std::sync::Arc; use uuid::Uuid; use crate::ImksError; use crate::socket::socket::Socket; use super::message::MessageService; impl MessageService { /// Handle `typing:start` — broadcast to the channel room. pub async fn typing_start( &self, socket: Arc, data: &serde_json::Value, ) -> crate::ImksResult<()> { let user_id = self.user_id(&socket)?; let channel_id: Uuid = self.parse_channel_id(data)?; let channel_id_str = channel_id.to_string(); let user_id_str = user_id.to_string(); self.ensure_readable(&channel_id_str, &user_id_str).await?; self.ensure_member(&channel_id_str, &user_id_str).await?; if let Some(ns) = self.namespaces.get_namespace(&socket.namespace) { ns.emit_to_room( &channel_id.to_string(), "typing", serde_json::json!({ "channel_id": channel_id.to_string(), "user_id": user_id.to_string(), "typing": true, }), ) .await; } Ok(()) } /// Handle `typing:stop` — broadcast to the channel room. pub async fn typing_stop( &self, socket: Arc, data: &serde_json::Value, ) -> crate::ImksResult<()> { let user_id = self.user_id(&socket)?; let channel_id: Uuid = self.parse_channel_id(data)?; let channel_id_str = channel_id.to_string(); let user_id_str = user_id.to_string(); self.ensure_readable(&channel_id_str, &user_id_str).await?; self.ensure_member(&channel_id_str, &user_id_str).await?; if let Some(ns) = self.namespaces.get_namespace(&socket.namespace) { ns.emit_to_room( &channel_id.to_string(), "typing", serde_json::json!({ "channel_id": channel_id.to_string(), "user_id": user_id.to_string(), "typing": false, }), ) .await; } Ok(()) } /// Handle `presence:update` — broadcast online status to all shared channels. /// In a full implementation this would track which channels a user is in /// and broadcast to all of them. For now it broadcasts to the specified channel. pub async fn presence_update( &self, socket: Arc, data: &serde_json::Value, ) -> crate::ImksResult<()> { let user_id = self.user_id(&socket)?; let channel_id: Uuid = self.parse_channel_id(data)?; let online: bool = Self::parse_optional(Self::first_payload(data)?, "online")?.unwrap_or(true); let channel_id_str = channel_id.to_string(); let user_id_str = user_id.to_string(); self.ensure_readable(&channel_id_str, &user_id_str).await?; self.ensure_member(&channel_id_str, &user_id_str).await?; if let Some(ns) = self.namespaces.get_namespace(&socket.namespace) { ns.emit_to_room( &channel_id.to_string(), "presence:update", serde_json::json!({ "user_id": user_id.to_string(), "online": online, }), ) .await; } Ok(()) } fn parse_channel_id(&self, data: &serde_json::Value) -> crate::ImksResult { let arr = data .as_array() .and_then(|a| a.first()) .ok_or_else(|| ImksError::InvalidInput("Expected [payload] array".into()))?; Self::parse_field(arr, "channel_id") } }