Initial GuruConnect implementation - Phase 1 MVP
- Agent: DXGI/GDI screen capture, mouse/keyboard input, WebSocket transport - Server: Axum relay, session management, REST API - Dashboard: React viewer components with TypeScript - Protocol: Protobuf definitions for all message types 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
148
server/src/session/mod.rs
Normal file
148
server/src/session/mod.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
//! Session management for GuruConnect
|
||||
//!
|
||||
//! Manages active remote desktop sessions, tracking which agents
|
||||
//! are connected and which viewers are watching them.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Unique identifier for a session
|
||||
pub type SessionId = Uuid;
|
||||
|
||||
/// Unique identifier for an agent
|
||||
pub type AgentId = String;
|
||||
|
||||
/// Session state
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Session {
|
||||
pub id: SessionId,
|
||||
pub agent_id: AgentId,
|
||||
pub agent_name: String,
|
||||
pub started_at: chrono::DateTime<chrono::Utc>,
|
||||
pub viewer_count: usize,
|
||||
}
|
||||
|
||||
/// Channel for sending frames from agent to viewers
|
||||
pub type FrameSender = broadcast::Sender<Vec<u8>>;
|
||||
pub type FrameReceiver = broadcast::Receiver<Vec<u8>>;
|
||||
|
||||
/// Channel for sending input events from viewer to agent
|
||||
pub type InputSender = tokio::sync::mpsc::Sender<Vec<u8>>;
|
||||
pub type InputReceiver = tokio::sync::mpsc::Receiver<Vec<u8>>;
|
||||
|
||||
/// Internal session data with channels
|
||||
struct SessionData {
|
||||
info: Session,
|
||||
/// Channel for video frames (agent -> viewers)
|
||||
frame_tx: FrameSender,
|
||||
/// Channel for input events (viewer -> agent)
|
||||
input_tx: InputSender,
|
||||
input_rx: Option<InputReceiver>,
|
||||
}
|
||||
|
||||
/// Manages all active sessions
|
||||
#[derive(Clone)]
|
||||
pub struct SessionManager {
|
||||
sessions: Arc<RwLock<HashMap<SessionId, SessionData>>>,
|
||||
agents: Arc<RwLock<HashMap<AgentId, SessionId>>>,
|
||||
}
|
||||
|
||||
impl SessionManager {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sessions: Arc::new(RwLock::new(HashMap::new())),
|
||||
agents: Arc::new(RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a new agent and create a session
|
||||
pub async fn register_agent(&self, agent_id: AgentId, agent_name: String) -> (SessionId, FrameSender, InputReceiver) {
|
||||
let session_id = Uuid::new_v4();
|
||||
|
||||
// Create channels
|
||||
let (frame_tx, _) = broadcast::channel(16); // Buffer 16 frames
|
||||
let (input_tx, input_rx) = tokio::sync::mpsc::channel(64); // Buffer 64 input events
|
||||
|
||||
let session = Session {
|
||||
id: session_id,
|
||||
agent_id: agent_id.clone(),
|
||||
agent_name,
|
||||
started_at: chrono::Utc::now(),
|
||||
viewer_count: 0,
|
||||
};
|
||||
|
||||
let session_data = SessionData {
|
||||
info: session,
|
||||
frame_tx: frame_tx.clone(),
|
||||
input_tx,
|
||||
input_rx: None, // Will be taken by the agent handler
|
||||
};
|
||||
|
||||
let mut sessions = self.sessions.write().await;
|
||||
sessions.insert(session_id, session_data);
|
||||
|
||||
let mut agents = self.agents.write().await;
|
||||
agents.insert(agent_id, session_id);
|
||||
|
||||
(session_id, frame_tx, input_rx)
|
||||
}
|
||||
|
||||
/// Get a session by agent ID
|
||||
pub async fn get_session_by_agent(&self, agent_id: &str) -> Option<Session> {
|
||||
let agents = self.agents.read().await;
|
||||
let session_id = agents.get(agent_id)?;
|
||||
|
||||
let sessions = self.sessions.read().await;
|
||||
sessions.get(session_id).map(|s| s.info.clone())
|
||||
}
|
||||
|
||||
/// Get a session by session ID
|
||||
pub async fn get_session(&self, session_id: SessionId) -> Option<Session> {
|
||||
let sessions = self.sessions.read().await;
|
||||
sessions.get(&session_id).map(|s| s.info.clone())
|
||||
}
|
||||
|
||||
/// Join a session as a viewer
|
||||
pub async fn join_session(&self, session_id: SessionId) -> Option<(FrameReceiver, InputSender)> {
|
||||
let mut sessions = self.sessions.write().await;
|
||||
let session_data = sessions.get_mut(&session_id)?;
|
||||
|
||||
session_data.info.viewer_count += 1;
|
||||
|
||||
let frame_rx = session_data.frame_tx.subscribe();
|
||||
let input_tx = session_data.input_tx.clone();
|
||||
|
||||
Some((frame_rx, input_tx))
|
||||
}
|
||||
|
||||
/// Leave a session as a viewer
|
||||
pub async fn leave_session(&self, session_id: SessionId) {
|
||||
let mut sessions = self.sessions.write().await;
|
||||
if let Some(session_data) = sessions.get_mut(&session_id) {
|
||||
session_data.info.viewer_count = session_data.info.viewer_count.saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a session (when agent disconnects)
|
||||
pub async fn remove_session(&self, session_id: SessionId) {
|
||||
let mut sessions = self.sessions.write().await;
|
||||
if let Some(session_data) = sessions.remove(&session_id) {
|
||||
let mut agents = self.agents.write().await;
|
||||
agents.remove(&session_data.info.agent_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// List all active sessions
|
||||
pub async fn list_sessions(&self) -> Vec<Session> {
|
||||
let sessions = self.sessions.read().await;
|
||||
sessions.values().map(|s| s.info.clone()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SessionManager {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user