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 pub art_base_url: Option<String>,
83}
84
85impl ServerState {
86 pub fn new(
87 server_key: String,
88 max_rooms: usize,
89 official_key: Option<String>,
90 analytics: AnalyticsHandle,
91 deck_play_events: DeckPlayEventHandle,
92 hub_jwks_url: Option<String>,
93 ) -> Self {
94 ServerState {
95 players: DashMap::new(),
96 rooms: DashMap::new(),
97 server_key,
98 max_rooms,
99 official_key,
100 analytics,
101 deck_play_events,
102 identity: IdentityVerifier::new(hub_jwks_url),
103 art_base_url: None,
104 }
105 }
106
107 pub fn with_art_base_url(mut self, url: Option<String>) -> Self {
111 self.art_base_url = url;
112 self
113 }
114
115 pub fn session_by_username(&self, username: &str) -> Option<UsernameSession> {
116 let mut connected = None;
117 for entry in self.players.iter() {
118 let player = entry.value();
119 if player.username != username {
120 continue;
121 }
122 if !player.connected {
123 return Some(UsernameSession::from(player));
124 }
125 connected.get_or_insert_with(|| UsernameSession::from(player));
126 }
127 connected
128 }
129}