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