use serde::{Deserialize, Serialize}; use ts_rs::TS; use crate::app_state::database::models::{ DeviceId, DocumentId, DocumentVersionWithoutContent, VaultUpdateId, }; #[derive(TS, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct WebSocketHandshake { pub token: String, pub device_id: DeviceId, #[ts(type = "number | null")] pub last_seen_vault_update_id: Option, } #[derive(TS, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct CursorPositionFromClient { pub documents_with_cursors: Vec, } #[derive(TS, Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct DocumentWithCursors { // It's None in case the document is dirty. // We still want to sync the cursor to mark // that it exists and can be client-side // interpolated. However, the actual // position is meaningless. #[ts(type = "number | null")] pub vault_update_id: Option, pub document_id: DocumentId, pub relative_path: String, pub cursors: Vec, } #[derive(TS, Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct CursorSpan { pub start: usize, pub end: usize, } #[derive(TS, Serialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct ClientCursors { pub user_name: String, pub device_id: DeviceId, pub documents_with_cursors: Vec, } #[derive(TS, Serialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct CursorPositionFromServer { pub clients: Vec, } // One committed version. Non-delete updates are broadcast to every // connected client *except* the device that authored them — that // device already has the new state via its HTTP response. Deletes are // broadcast to every client including the author: the author keeps // the document in its sync queue until this receipt arrives so a late // remote update can't sneak in between the HTTP response and the // queue cleanup. The server also emits these one-at-a-time to catch // up a freshly-connected client on versions committed while it was // offline, in ascending `vault_update_id` order. #[derive(TS, Serialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct WebSocketVaultUpdate { pub document: DocumentVersionWithoutContent, } #[derive(TS, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase", tag = "type")] #[ts(export)] pub enum WebSocketClientMessage { Handshake(WebSocketHandshake), CursorPositions(CursorPositionFromClient), } #[derive(TS, Serialize, Clone, Debug)] #[serde(rename_all = "camelCase", tag = "type")] #[ts(export)] pub enum WebSocketServerMessage { VaultUpdate(WebSocketVaultUpdate), CursorPositions(CursorPositionFromServer), } /// Broadcast envelope carrying the message plus the device that produced /// it. The per-recipient send task compares `origin_device_id` against /// its own device id to fill in `originates_from_self` before the message /// is serialized on the wire. #[derive(Clone, Debug)] pub struct WebSocketServerMessageWithOrigin { pub origin_device_id: Option, pub message: WebSocketServerMessage, } impl WebSocketServerMessageWithOrigin { pub fn new(message: WebSocketServerMessage) -> Self { Self { origin_device_id: None, message, } } pub fn with_origin(origin_device_id: DeviceId, message: WebSocketServerMessage) -> Self { Self { origin_device_id: Some(origin_device_id), message, } } }