1use dashmap::DashMap;
2use std::sync::Mutex;
3use std::time::Instant;
4use tokio::sync::mpsc;
5use tokio_tungstenite::tungstenite::Message;
6
7use crate::analytics::AnalyticsHandle;
8use crate::chat::ChatHistory;
9use crate::client_build::ClientBuild;
10use crate::identity::{IdentityVerifier, SessionIdentity};
11use crate::protocol::identity_token::GUEST_SUBJECT_PREFIX;
12use crate::protocol::LocalGameKind;
13use crate::room::Room;
14use crate::seal::MessageSealer;
15
16pub struct ConnectedPlayer {
17 pub player_id: String,
18 pub username: String,
19 pub room_id: Option<String>,
20 pub sender: mpsc::UnboundedSender<Message>,
21 pub last_client_rtt_ms: Option<u32>,
25 pub connected: bool,
26 pub generation: u64,
27 pub last_seen: Instant,
28 pub disconnected_at: Option<Instant>,
29 pub is_service: bool,
30 pub identity: Vec<SessionIdentity>,
31 pub name_verified: bool,
32 pub qualification: Option<String>,
33 pub avatar_url: Option<String>,
34 pub client: ClientBuild,
37 pub local_game: Option<LocalGameKind>,
40 pub last_chat_at: Option<Instant>,
41 pub client_ip: String,
42 pub seal: Option<String>,
43}
44
45impl ConnectedPlayer {
46 pub fn verified(&self) -> bool {
47 self.name_verified
48 && self.identity.iter().any(|identity| {
49 matches!(identity, SessionIdentity::Account(sub) if !sub.starts_with(GUEST_SUBJECT_PREFIX))
50 })
51 }
52}
53
54pub struct UsernameSession {
55 pub player_id: String,
56 pub room_id: Option<String>,
57 pub generation: u64,
58 pub connected: bool,
59 pub sender_closed: bool,
60 pub identity: Vec<SessionIdentity>,
61 pub name_verified: bool,
62}
63
64impl From<&ConnectedPlayer> for UsernameSession {
65 fn from(player: &ConnectedPlayer) -> Self {
66 UsernameSession {
67 player_id: player.player_id.clone(),
68 room_id: player.room_id.clone(),
69 generation: player.generation,
70 connected: player.connected,
71 sender_closed: player.sender.is_closed(),
72 identity: player.identity.clone(),
73 name_verified: player.name_verified,
74 }
75 }
76}
77
78pub struct ServerState {
79 pub players: DashMap<String, ConnectedPlayer>,
80 pub rooms: DashMap<String, Room>,
81 pub server_key: String,
82 pub max_rooms: usize,
83 pub official_key: Option<String>,
84 pub analytics: AnalyticsHandle,
85 pub identity: IdentityVerifier,
86 pub direct_transport: bool,
88 pub ice_servers: Vec<crate::protocol::IceServer>,
90 pub lobby_chat: Mutex<ChatHistory>,
91 pub seal: Option<MessageSealer>,
92 pub art_base_url: Option<String>,
93}
94
95impl ServerState {
96 #[allow(clippy::too_many_arguments)]
97 pub fn new(
98 server_key: String,
99 max_rooms: usize,
100 official_key: Option<String>,
101 analytics: AnalyticsHandle,
102 hub_jwks_url: Option<String>,
103 seal: Option<MessageSealer>,
104 ) -> Self {
105 ServerState {
106 players: DashMap::new(),
107 rooms: DashMap::new(),
108 server_key,
109 max_rooms,
110 official_key,
111 analytics,
112 identity: IdentityVerifier::new(hub_jwks_url),
113 direct_transport: false,
114 ice_servers: Vec::new(),
115 lobby_chat: Mutex::new(ChatHistory::default()),
116 seal,
117 art_base_url: None,
118 }
119 }
120
121 pub fn with_direct_transport(
122 mut self,
123 enabled: bool,
124 ice_servers: Vec<crate::protocol::IceServer>,
125 ) -> Self {
126 self.direct_transport = enabled;
127 self.ice_servers = ice_servers;
128 self
129 }
130
131 pub fn with_art_base_url(mut self, url: Option<String>) -> Self {
135 self.art_base_url = url;
136 self
137 }
138
139 pub fn session_by_username(&self, username: &str) -> Option<UsernameSession> {
140 let mut connected = None;
141 for entry in self.players.iter() {
142 let player = entry.value();
143 if player.username != username {
144 continue;
145 }
146 if !player.connected {
147 return Some(UsernameSession::from(player));
148 }
149 connected.get_or_insert_with(|| UsernameSession::from(player));
150 }
151 connected
152 }
153}