1use dashmap::DashMap;
2use std::time::Instant;
3use tokio::sync::mpsc;
4use tokio_tungstenite::tungstenite::Message;
5
6use crate::analytics::AnalyticsHandle;
7use crate::client_build::ClientBuild;
8use crate::deck_play_events::DeckPlayEventHandle;
9use crate::identity::{IdentityVerifier, SessionIdentity};
10use crate::protocol::identity_token::GUEST_SUBJECT_PREFIX;
11use crate::room::Room;
12
13pub struct ConnectedPlayer {
14 pub player_id: String,
15 pub username: String,
16 pub room_id: Option<String>,
17 pub sender: mpsc::UnboundedSender<Message>,
18 pub last_client_rtt_ms: Option<u32>,
22 pub connected: bool,
23 pub generation: u64,
24 pub last_seen: Instant,
25 pub disconnected_at: Option<Instant>,
26 pub is_service: bool,
27 pub identity: Vec<SessionIdentity>,
28 pub name_verified: bool,
29 pub qualification: Option<String>,
30 pub avatar_url: Option<String>,
31 pub client: ClientBuild,
34}
35
36impl ConnectedPlayer {
37 pub fn verified(&self) -> bool {
38 self.name_verified
39 && self.identity.iter().any(|identity| {
40 matches!(identity, SessionIdentity::Account(sub) if !sub.starts_with(GUEST_SUBJECT_PREFIX))
41 })
42 }
43}
44
45pub struct UsernameSession {
46 pub player_id: String,
47 pub room_id: Option<String>,
48 pub generation: u64,
49 pub connected: bool,
50 pub sender_closed: bool,
51 pub identity: Vec<SessionIdentity>,
52 pub name_verified: bool,
53}
54
55impl From<&ConnectedPlayer> for UsernameSession {
56 fn from(player: &ConnectedPlayer) -> Self {
57 UsernameSession {
58 player_id: player.player_id.clone(),
59 room_id: player.room_id.clone(),
60 generation: player.generation,
61 connected: player.connected,
62 sender_closed: player.sender.is_closed(),
63 identity: player.identity.clone(),
64 name_verified: player.name_verified,
65 }
66 }
67}
68
69pub struct ServerState {
70 pub players: DashMap<String, ConnectedPlayer>,
71 pub rooms: DashMap<String, Room>,
72 pub server_key: String,
73 pub max_rooms: usize,
74 pub official_key: Option<String>,
75 pub analytics: AnalyticsHandle,
76 pub deck_play_events: DeckPlayEventHandle,
77 pub identity: IdentityVerifier,
78}
79
80impl ServerState {
81 pub fn new(
82 server_key: String,
83 max_rooms: usize,
84 official_key: Option<String>,
85 analytics: AnalyticsHandle,
86 deck_play_events: DeckPlayEventHandle,
87 hub_jwks_url: Option<String>,
88 ) -> Self {
89 ServerState {
90 players: DashMap::new(),
91 rooms: DashMap::new(),
92 server_key,
93 max_rooms,
94 official_key,
95 analytics,
96 deck_play_events,
97 identity: IdentityVerifier::new(hub_jwks_url),
98 }
99 }
100
101 pub fn session_by_username(&self, username: &str) -> Option<UsernameSession> {
102 let mut connected = None;
103 for entry in self.players.iter() {
104 let player = entry.value();
105 if player.username != username {
106 continue;
107 }
108 if !player.connected {
109 return Some(UsernameSession::from(player));
110 }
111 connected.get_or_insert_with(|| UsernameSession::from(player));
112 }
113 connected
114 }
115}