Implement session replacement and WebSocket keepalive features
Enhanced the session management by allowing a reconnect with the same username to replace the existing session, sending a logout message to the previous session. Introduced WebSocket keepalive functionality using Ping/Pong messages to detect stale connections. Updated documentation to reflect these changes and improve user experience during reconnections.
This commit is contained in:
160
src/commands.rs
160
src/commands.rs
@@ -211,83 +211,95 @@ async fn handle_init_command(
|
||||
return;
|
||||
}
|
||||
|
||||
let (token, user_name, actual_room_name, old_room_name) = {
|
||||
let mut guard = state.write().await;
|
||||
if guard.logged_in_names.contains(&requested_user_name)
|
||||
&& guard
|
||||
.clients
|
||||
.iter()
|
||||
.any(|(id, c)| *id != client_id && c.logged_in && c.user_name == requested_user_name)
|
||||
{
|
||||
drop(guard);
|
||||
send_error(client_id, Arc::clone(&state), "loggedin").await;
|
||||
return;
|
||||
}
|
||||
if !guard.room_meta.contains_key(&resolved_room_name) {
|
||||
drop(guard);
|
||||
if room_debug_enabled() {
|
||||
eprintln!(
|
||||
"[yourchat2][room-debug][init] client_id={client_id} resolved_room='{resolved_room_name}' vanished_before_join"
|
||||
);
|
||||
let (token, user_name, actual_room_name, old_room_name) = loop {
|
||||
let replacement_needed = {
|
||||
let mut guard = state.write().await;
|
||||
if let Some(existing_client_id) = guard.clients.iter().find_map(|(id, c)| {
|
||||
if *id != client_id && c.logged_in && c.user_name == requested_user_name {
|
||||
Some(*id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
Some(existing_client_id)
|
||||
} else {
|
||||
if !guard.room_meta.contains_key(&resolved_room_name) {
|
||||
drop(guard);
|
||||
if room_debug_enabled() {
|
||||
eprintln!(
|
||||
"[yourchat2][room-debug][init] client_id={client_id} resolved_room='{resolved_room_name}' vanished_before_join"
|
||||
);
|
||||
}
|
||||
send_error(client_id, Arc::clone(&state), "room_not_found").await;
|
||||
return;
|
||||
}
|
||||
|
||||
let (old_room, old_name, was_logged_in, user_name, token, new_token) = {
|
||||
let Some(client) = guard.clients.get_mut(&client_id) else {
|
||||
return;
|
||||
};
|
||||
let old_room = client.room.clone();
|
||||
let old_name = client.user_name.clone();
|
||||
let was_logged_in = client.logged_in;
|
||||
|
||||
client.user_name = profile.display_name.clone();
|
||||
client.color = profile.color.clone();
|
||||
client.falukant_user_id = profile.falukant_user_id;
|
||||
client.chat_user_id = profile.chat_user_id;
|
||||
client.gender_id = profile.gender_id;
|
||||
client.age = profile.age;
|
||||
client.rights = profile.rights.clone();
|
||||
client.right_type_ids = profile.right_type_ids.clone();
|
||||
client.logged_in = true;
|
||||
client.room = resolved_room_name.clone();
|
||||
|
||||
let mut new_token = None;
|
||||
if client.token.is_none() {
|
||||
let generated = Uuid::new_v4().to_string();
|
||||
client.token = Some(generated.clone());
|
||||
new_token = Some(generated);
|
||||
}
|
||||
|
||||
(
|
||||
old_room,
|
||||
old_name,
|
||||
was_logged_in,
|
||||
client.user_name.clone(),
|
||||
client.token.clone().unwrap_or_default(),
|
||||
new_token,
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(generated) = new_token {
|
||||
guard.tokens.insert(generated, client_id);
|
||||
}
|
||||
if was_logged_in {
|
||||
guard.logged_in_names.remove(&old_name);
|
||||
}
|
||||
guard.logged_in_names.insert(user_name.clone());
|
||||
if !old_room.is_empty() {
|
||||
if let Some(members) = guard.rooms.get_mut(&old_room) {
|
||||
members.remove(&client_id);
|
||||
}
|
||||
}
|
||||
guard
|
||||
.rooms
|
||||
.entry(resolved_room_name.clone())
|
||||
.or_default()
|
||||
.insert(client_id);
|
||||
break (token, user_name, resolved_room_name.clone(), old_room);
|
||||
}
|
||||
send_error(client_id, Arc::clone(&state), "room_not_found").await;
|
||||
return;
|
||||
}
|
||||
|
||||
let (old_room, old_name, was_logged_in, user_name, token, new_token) = {
|
||||
let Some(client) = guard.clients.get_mut(&client_id) else {
|
||||
return;
|
||||
};
|
||||
let old_room = client.room.clone();
|
||||
let old_name = client.user_name.clone();
|
||||
let was_logged_in = client.logged_in;
|
||||
|
||||
client.user_name = profile.display_name.clone();
|
||||
client.color = profile.color.clone();
|
||||
client.falukant_user_id = profile.falukant_user_id;
|
||||
client.chat_user_id = profile.chat_user_id;
|
||||
client.gender_id = profile.gender_id;
|
||||
client.age = profile.age;
|
||||
client.rights = profile.rights.clone();
|
||||
client.right_type_ids = profile.right_type_ids.clone();
|
||||
client.logged_in = true;
|
||||
client.room = resolved_room_name.clone();
|
||||
|
||||
let mut new_token = None;
|
||||
if client.token.is_none() {
|
||||
let generated = Uuid::new_v4().to_string();
|
||||
client.token = Some(generated.clone());
|
||||
new_token = Some(generated);
|
||||
}
|
||||
|
||||
(
|
||||
old_room,
|
||||
old_name,
|
||||
was_logged_in,
|
||||
client.user_name.clone(),
|
||||
client.token.clone().unwrap_or_default(),
|
||||
new_token,
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(generated) = new_token {
|
||||
guard.tokens.insert(generated, client_id);
|
||||
if let Some(old_client_id) = replacement_needed {
|
||||
state::send_to_client(
|
||||
old_client_id,
|
||||
Arc::clone(&state),
|
||||
json!({"type":5, "message":"logout", "reason":"replaced_by_new_login"}),
|
||||
)
|
||||
.await;
|
||||
state::disconnect_client(old_client_id, Arc::clone(&state)).await;
|
||||
continue;
|
||||
}
|
||||
if was_logged_in {
|
||||
guard.logged_in_names.remove(&old_name);
|
||||
}
|
||||
guard.logged_in_names.insert(user_name.clone());
|
||||
if !old_room.is_empty() {
|
||||
if let Some(members) = guard.rooms.get_mut(&old_room) {
|
||||
members.remove(&client_id);
|
||||
}
|
||||
}
|
||||
guard
|
||||
.rooms
|
||||
.entry(resolved_room_name.clone())
|
||||
.or_default()
|
||||
.insert(client_id);
|
||||
(token, user_name, resolved_room_name, old_room)
|
||||
};
|
||||
if !old_room_name.is_empty() {
|
||||
state::mark_room_possibly_empty(&old_room_name, Arc::clone(&state)).await;
|
||||
|
||||
Reference in New Issue
Block a user