update to support user expire, timeout, and disconnect
This commit is contained in:
parent
fe65835f4a
commit
5fcd49e847
16 changed files with 744 additions and 238 deletions
|
|
@ -6,11 +6,32 @@
|
|||
use leptos::prelude::*;
|
||||
use leptos::reactive::owner::LocalStorage;
|
||||
|
||||
use chattyness_db::models::{ChannelMemberWithAvatar, EmotionState, LooseProp};
|
||||
use chattyness_db::ws_messages::{ClientMessage, ServerMessage};
|
||||
use chattyness_db::models::{ChannelMemberWithAvatar, LooseProp};
|
||||
#[cfg(feature = "hydrate")]
|
||||
use chattyness_db::models::EmotionState;
|
||||
use chattyness_db::ws_messages::ClientMessage;
|
||||
#[cfg(feature = "hydrate")]
|
||||
use chattyness_db::ws_messages::{DisconnectReason, ServerMessage, WsConfig};
|
||||
|
||||
use super::chat_types::ChatMessage;
|
||||
|
||||
/// Close code for scene change (must match server constant).
|
||||
pub const SCENE_CHANGE_CLOSE_CODE: u16 = 4000;
|
||||
|
||||
/// Duration for fade-out animation in milliseconds.
|
||||
pub const FADE_DURATION_MS: i64 = 5000;
|
||||
|
||||
/// A member that is currently fading out after a timeout disconnect.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct FadingMember {
|
||||
/// The member data.
|
||||
pub member: ChannelMemberWithAvatar,
|
||||
/// Timestamp when the fade started (milliseconds since epoch).
|
||||
pub fade_start: i64,
|
||||
/// Duration of the fade in milliseconds.
|
||||
pub fade_duration: i64,
|
||||
}
|
||||
|
||||
/// WebSocket connection state.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum WsState {
|
||||
|
|
@ -44,6 +65,7 @@ pub fn use_channel_websocket(
|
|||
on_loose_props_sync: Callback<Vec<LooseProp>>,
|
||||
on_prop_dropped: Callback<LooseProp>,
|
||||
on_prop_picked_up: Callback<uuid::Uuid>,
|
||||
on_member_fading: Callback<FadingMember>,
|
||||
) -> (Signal<WsState>, WsSenderStorage) {
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
|
@ -139,6 +161,11 @@ pub fn use_channel_websocket(
|
|||
let on_loose_props_sync_clone = on_loose_props_sync.clone();
|
||||
let on_prop_dropped_clone = on_prop_dropped.clone();
|
||||
let on_prop_picked_up_clone = on_prop_picked_up.clone();
|
||||
let on_member_fading_clone = on_member_fading.clone();
|
||||
// For starting heartbeat on Welcome
|
||||
let ws_ref_for_heartbeat = ws_ref.clone();
|
||||
let heartbeat_started: Rc<RefCell<bool>> = Rc::new(RefCell::new(false));
|
||||
let heartbeat_started_clone = heartbeat_started.clone();
|
||||
let onmessage = Closure::wrap(Box::new(move |e: MessageEvent| {
|
||||
if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
|
||||
let text: String = text.into();
|
||||
|
|
@ -146,6 +173,28 @@ pub fn use_channel_websocket(
|
|||
web_sys::console::log_1(&format!("[WS<-Server] {}", text).into());
|
||||
|
||||
if let Ok(msg) = serde_json::from_str::<ServerMessage>(&text) {
|
||||
// Check for Welcome message to start heartbeat with server-provided config
|
||||
if let ServerMessage::Welcome { ref config, .. } = msg {
|
||||
if !*heartbeat_started_clone.borrow() {
|
||||
*heartbeat_started_clone.borrow_mut() = true;
|
||||
let ping_interval_ms = config.ping_interval_secs * 1000;
|
||||
let ws_ref_ping = ws_ref_for_heartbeat.clone();
|
||||
#[cfg(debug_assertions)]
|
||||
web_sys::console::log_1(
|
||||
&format!("[WS] Starting heartbeat with interval {}ms", ping_interval_ms).into(),
|
||||
);
|
||||
let heartbeat = gloo_timers::callback::Interval::new(ping_interval_ms as u32, move || {
|
||||
if let Some(ws) = ws_ref_ping.borrow().as_ref() {
|
||||
if ws.ready_state() == WebSocket::OPEN {
|
||||
if let Ok(json) = serde_json::to_string(&ClientMessage::Ping) {
|
||||
let _ = ws.send_with_str(&json);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
std::mem::forget(heartbeat);
|
||||
}
|
||||
}
|
||||
handle_server_message(
|
||||
msg,
|
||||
&members_for_msg,
|
||||
|
|
@ -154,6 +203,7 @@ pub fn use_channel_websocket(
|
|||
&on_loose_props_sync_clone,
|
||||
&on_prop_dropped_clone,
|
||||
&on_prop_picked_up_clone,
|
||||
&on_member_fading_clone,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -199,6 +249,7 @@ fn handle_server_message(
|
|||
on_loose_props_sync: &Callback<Vec<LooseProp>>,
|
||||
on_prop_dropped: &Callback<LooseProp>,
|
||||
on_prop_picked_up: &Callback<uuid::Uuid>,
|
||||
on_member_fading: &Callback<FadingMember>,
|
||||
) {
|
||||
let mut members_vec = members.borrow_mut();
|
||||
|
||||
|
|
@ -206,6 +257,7 @@ fn handle_server_message(
|
|||
ServerMessage::Welcome {
|
||||
member: _,
|
||||
members: initial_members,
|
||||
config: _, // Config is handled in the caller for heartbeat setup
|
||||
} => {
|
||||
*members_vec = initial_members;
|
||||
on_update.run(members_vec.clone());
|
||||
|
|
@ -222,11 +274,31 @@ fn handle_server_message(
|
|||
ServerMessage::MemberLeft {
|
||||
user_id,
|
||||
guest_session_id,
|
||||
reason,
|
||||
} => {
|
||||
// Find the member before removing
|
||||
let leaving_member = members_vec
|
||||
.iter()
|
||||
.find(|m| m.member.user_id == user_id && m.member.guest_session_id == guest_session_id)
|
||||
.cloned();
|
||||
|
||||
// Always remove from active members list
|
||||
members_vec.retain(|m| {
|
||||
m.member.user_id != user_id || m.member.guest_session_id != guest_session_id
|
||||
});
|
||||
on_update.run(members_vec.clone());
|
||||
|
||||
// For timeout disconnects, trigger fading animation
|
||||
if reason == DisconnectReason::Timeout {
|
||||
if let Some(member) = leaving_member {
|
||||
let fading = FadingMember {
|
||||
member,
|
||||
fade_start: js_sys::Date::now() as i64,
|
||||
fade_duration: FADE_DURATION_MS,
|
||||
};
|
||||
on_member_fading.run(fading);
|
||||
}
|
||||
}
|
||||
}
|
||||
ServerMessage::PositionUpdated {
|
||||
user_id,
|
||||
|
|
@ -333,6 +405,7 @@ pub fn use_channel_websocket(
|
|||
_on_loose_props_sync: Callback<Vec<LooseProp>>,
|
||||
_on_prop_dropped: Callback<LooseProp>,
|
||||
_on_prop_picked_up: Callback<uuid::Uuid>,
|
||||
_on_member_fading: Callback<FadingMember>,
|
||||
) -> (Signal<WsState>, WsSenderStorage) {
|
||||
let (ws_state, _) = signal(WsState::Disconnected);
|
||||
let sender: WsSenderStorage = StoredValue::new_local(None);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue