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 client: ClientBuild,
33}
34
35impl ConnectedPlayer {
36 pub fn verified(&self) -> bool {
37 self.name_verified
38 && self.identity.iter().any(|identity| {
39 matches!(identity, SessionIdentity::Account(sub) if !sub.starts_with(GUEST_SUBJECT_PREFIX))
40 })
41 }
42}
43
44pub struct UsernameSession {
45 pub player_id: String,
46 pub room_id: Option<String>,
47 pub generation: u64,
48 pub connected: bool,
49 pub sender_closed: bool,
50 pub identity: Vec<SessionIdentity>,
51 pub name_verified: bool,
52}
53
54impl From<&ConnectedPlayer> for UsernameSession {
55 fn from(player: &ConnectedPlayer) -> Self {
56 UsernameSession {
57 player_id: player.player_id.clone(),
58 room_id: player.room_id.clone(),
59 generation: player.generation,
60 connected: player.connected,
61 sender_closed: player.sender.is_closed(),
62 identity: player.identity.clone(),
63 name_verified: player.name_verified,
64 }
65 }
66}
67
68pub struct ServerState {
69 pub players: DashMap<String, ConnectedPlayer>,
70 pub rooms: DashMap<String, Room>,
71 pub server_key: String,
72 pub max_rooms: usize,
73 pub official_key: Option<String>,
74 pub analytics: AnalyticsHandle,
75 pub deck_play_events: DeckPlayEventHandle,
76 pub identity: IdentityVerifier,
77}
78
79impl ServerState {
80 pub fn new(
81 server_key: String,
82 max_rooms: usize,
83 official_key: Option<String>,
84 analytics: AnalyticsHandle,
85 deck_play_events: DeckPlayEventHandle,
86 hub_jwks_url: Option<String>,
87 ) -> Self {
88 ServerState {
89 players: DashMap::new(),
90 rooms: DashMap::new(),
91 server_key,
92 max_rooms,
93 official_key,
94 analytics,
95 deck_play_events,
96 identity: IdentityVerifier::new(hub_jwks_url),
97 }
98 }
99
100 pub fn session_by_username(&self, username: &str) -> Option<UsernameSession> {
101 let mut connected = None;
102 for entry in self.players.iter() {
103 let player = entry.value();
104 if player.username != username {
105 continue;
106 }
107 if !player.connected {
108 return Some(UsernameSession::from(player));
109 }
110 connected.get_or_insert_with(|| UsernameSession::from(player));
111 }
112 connected
113 }
114}