* make guest status a flag on users * add logout handlers * add logout notification for other users
289 lines
8.4 KiB
Rust
289 lines
8.4 KiB
Rust
//! WebSocket message protocol for channel presence.
|
|
//!
|
|
//! Shared message types used by both server and WASM client.
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use uuid::Uuid;
|
|
|
|
use crate::models::{AvatarRenderData, ChannelMemberInfo, ChannelMemberWithAvatar, ForcedAvatarReason, LooseProp};
|
|
|
|
/// Default function for serde that returns true (for is_same_scene field).
|
|
/// Must be pub for serde derive macro to access via full path.
|
|
pub fn default_is_same_scene() -> bool {
|
|
true
|
|
}
|
|
|
|
/// WebSocket configuration sent to client on connect.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct WsConfig {
|
|
/// Interval for client to send ping to keep connection alive (seconds).
|
|
pub ping_interval_secs: u64,
|
|
}
|
|
|
|
/// WebSocket close codes (custom range: 4000-4999).
|
|
pub mod close_codes {
|
|
/// Scene change (user navigating to different scene).
|
|
pub const SCENE_CHANGE: u16 = 4000;
|
|
/// Server timeout (no message received within timeout period).
|
|
pub const SERVER_TIMEOUT: u16 = 4001;
|
|
/// User explicitly logged out.
|
|
pub const LOGOUT: u16 = 4003;
|
|
}
|
|
|
|
/// Reason for member disconnect.
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum DisconnectReason {
|
|
/// Graceful disconnect (browser close, normal WebSocket close).
|
|
Graceful,
|
|
/// Scene navigation (custom close code 4000).
|
|
SceneChange,
|
|
/// Timeout (connection lost, no ping response).
|
|
Timeout,
|
|
}
|
|
|
|
/// Client-to-server WebSocket messages.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "type", rename_all = "snake_case")]
|
|
pub enum ClientMessage {
|
|
/// Update position in the channel.
|
|
UpdatePosition {
|
|
/// X coordinate in scene space.
|
|
x: f64,
|
|
/// Y coordinate in scene space.
|
|
y: f64,
|
|
},
|
|
|
|
/// Update emotion by name.
|
|
UpdateEmotion {
|
|
/// Emotion name (e.g., "happy", "sad", "neutral").
|
|
emotion: String,
|
|
},
|
|
|
|
/// Ping to keep connection alive.
|
|
Ping,
|
|
|
|
/// Send a chat message to the channel or directly to a user.
|
|
SendChatMessage {
|
|
/// Message content (max 500 chars).
|
|
content: String,
|
|
/// Target display name for direct whisper. None = broadcast to scene.
|
|
#[serde(default)]
|
|
target_display_name: Option<String>,
|
|
},
|
|
|
|
/// Drop a prop from inventory to the canvas.
|
|
DropProp {
|
|
/// Inventory item ID to drop.
|
|
inventory_item_id: Uuid,
|
|
},
|
|
|
|
/// Pick up a loose prop from the canvas.
|
|
PickUpProp {
|
|
/// Loose prop ID to pick up.
|
|
loose_prop_id: Uuid,
|
|
},
|
|
|
|
/// Request to broadcast avatar appearance to other users.
|
|
SyncAvatar,
|
|
|
|
/// Request to teleport to a different scene.
|
|
Teleport {
|
|
/// Scene ID to teleport to.
|
|
scene_id: Uuid,
|
|
},
|
|
|
|
/// Moderator command (only processed if sender is a moderator).
|
|
ModCommand {
|
|
/// Subcommand name ("summon", "avatar", "teleport", "ban", etc.).
|
|
subcommand: String,
|
|
/// Arguments for the subcommand.
|
|
args: Vec<String>,
|
|
},
|
|
|
|
/// Request to refresh identity after registration (guest → user conversion).
|
|
/// Server will fetch updated user data and broadcast to all members.
|
|
RefreshIdentity,
|
|
}
|
|
|
|
/// Server-to-client WebSocket messages.
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
#[serde(tag = "type", rename_all = "snake_case")]
|
|
pub enum ServerMessage {
|
|
/// Welcome message with initial state after connection.
|
|
Welcome {
|
|
/// This user's member info.
|
|
member: ChannelMemberInfo,
|
|
/// All current members with avatars.
|
|
members: Vec<ChannelMemberWithAvatar>,
|
|
/// WebSocket configuration for the client.
|
|
config: WsConfig,
|
|
},
|
|
|
|
/// A member joined the channel.
|
|
MemberJoined {
|
|
/// The member that joined.
|
|
member: ChannelMemberWithAvatar,
|
|
},
|
|
|
|
/// A member left the channel.
|
|
MemberLeft {
|
|
/// User ID of the member who left.
|
|
user_id: Uuid,
|
|
/// Reason for disconnect.
|
|
reason: DisconnectReason,
|
|
},
|
|
|
|
/// A member updated their position.
|
|
PositionUpdated {
|
|
/// User ID of the member.
|
|
user_id: Uuid,
|
|
/// New X coordinate.
|
|
x: f64,
|
|
/// New Y coordinate.
|
|
y: f64,
|
|
},
|
|
|
|
/// A member changed their emotion.
|
|
EmotionUpdated {
|
|
/// User ID of the member.
|
|
user_id: Uuid,
|
|
/// Emotion name (e.g., "happy", "sad", "neutral").
|
|
emotion: String,
|
|
/// Asset paths for all 9 positions of the new emotion layer.
|
|
emotion_layer: [Option<String>; 9],
|
|
},
|
|
|
|
/// Pong response to client ping.
|
|
Pong,
|
|
|
|
/// Error message.
|
|
Error {
|
|
/// Error code.
|
|
code: String,
|
|
/// Error message.
|
|
message: String,
|
|
},
|
|
|
|
/// A chat message was received.
|
|
ChatMessageReceived {
|
|
/// Unique message ID.
|
|
message_id: Uuid,
|
|
/// User ID of sender.
|
|
user_id: Uuid,
|
|
/// Display name of sender.
|
|
display_name: String,
|
|
/// Message content.
|
|
content: String,
|
|
/// Emotion name for bubble styling (e.g., "happy", "sad", "neutral").
|
|
emotion: String,
|
|
/// Sender's X position at time of message.
|
|
x: f64,
|
|
/// Sender's Y position at time of message.
|
|
y: f64,
|
|
/// Server timestamp (milliseconds since epoch).
|
|
timestamp: i64,
|
|
/// Whether this is a whisper (direct message).
|
|
/// Default: false (broadcast message).
|
|
#[serde(default)]
|
|
is_whisper: bool,
|
|
/// Whether sender is in the same scene as recipient.
|
|
/// For whispers: true = same scene (show as bubble), false = cross-scene (show as notification).
|
|
/// For broadcasts: always true.
|
|
/// Default: true (same scene).
|
|
#[serde(default = "self::default_is_same_scene")]
|
|
is_same_scene: bool,
|
|
},
|
|
|
|
/// Initial list of loose props when joining channel.
|
|
LoosePropsSync {
|
|
/// All current loose props in the channel.
|
|
props: Vec<LooseProp>,
|
|
},
|
|
|
|
/// A prop was dropped on the canvas.
|
|
PropDropped {
|
|
/// The dropped prop.
|
|
prop: LooseProp,
|
|
},
|
|
|
|
/// A prop was picked up from the canvas.
|
|
PropPickedUp {
|
|
/// ID of the prop that was picked up.
|
|
prop_id: Uuid,
|
|
/// User ID who picked it up.
|
|
picked_up_by_user_id: Uuid,
|
|
},
|
|
|
|
/// A prop expired and was removed.
|
|
PropExpired {
|
|
/// ID of the expired prop.
|
|
prop_id: Uuid,
|
|
},
|
|
|
|
/// A member updated their avatar appearance.
|
|
AvatarUpdated {
|
|
/// User ID of the member.
|
|
user_id: Uuid,
|
|
/// Updated avatar render data.
|
|
avatar: AvatarRenderData,
|
|
},
|
|
|
|
/// Teleport approved - client should disconnect and reconnect to new scene.
|
|
TeleportApproved {
|
|
/// Scene ID to navigate to.
|
|
scene_id: Uuid,
|
|
/// Scene slug for URL.
|
|
scene_slug: String,
|
|
},
|
|
|
|
/// User has been summoned by a moderator - triggers teleport.
|
|
Summoned {
|
|
/// Scene ID to teleport to.
|
|
scene_id: Uuid,
|
|
/// Scene slug for URL.
|
|
scene_slug: String,
|
|
/// Display name of the moderator who summoned.
|
|
summoned_by: String,
|
|
},
|
|
|
|
/// Result of a moderator command.
|
|
ModCommandResult {
|
|
/// Whether the command succeeded.
|
|
success: bool,
|
|
/// Human-readable result message.
|
|
message: String,
|
|
},
|
|
|
|
/// A member's identity was updated (e.g., guest registered as user).
|
|
MemberIdentityUpdated {
|
|
/// User ID of the member.
|
|
user_id: Uuid,
|
|
/// New display name.
|
|
display_name: String,
|
|
/// Whether the member is still a guest.
|
|
is_guest: bool,
|
|
},
|
|
|
|
/// A user's avatar was forcibly changed (by moderator or scene entry).
|
|
AvatarForced {
|
|
/// User ID whose avatar was forced.
|
|
user_id: Uuid,
|
|
/// The forced avatar render data.
|
|
avatar: AvatarRenderData,
|
|
/// Why the avatar was forced.
|
|
reason: ForcedAvatarReason,
|
|
/// Display name of who forced the avatar (if mod command).
|
|
forced_by: Option<String>,
|
|
},
|
|
|
|
/// A user's forced avatar was cleared (returned to their chosen avatar).
|
|
AvatarCleared {
|
|
/// User ID whose forced avatar was cleared.
|
|
user_id: Uuid,
|
|
/// The user's original avatar render data (restored).
|
|
avatar: AvatarRenderData,
|
|
/// Display name of who cleared the forced avatar (if mod command).
|
|
cleared_by: Option<String>,
|
|
},
|
|
}
|