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:
AZ Computer Guru
2025-12-21 17:18:05 -07:00
commit 33893ea73b
38 changed files with 7724 additions and 0 deletions

148
server/src/session/mod.rs Normal file
View 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()
}
}