src/server/websocket.rs handshake/catch-up rewrite, app_state/cursors.rs,
app_state/websocket/{broadcasts,models,utils}.rs.
116 lines
3.6 KiB
Rust
116 lines
3.6 KiB
Rust
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<VaultUpdateId>,
|
|
}
|
|
|
|
#[derive(TS, Deserialize, Clone, Debug)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct CursorPositionFromClient {
|
|
pub documents_with_cursors: Vec<DocumentWithCursors>,
|
|
}
|
|
|
|
#[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<VaultUpdateId>,
|
|
|
|
pub document_id: DocumentId,
|
|
pub relative_path: String,
|
|
pub cursors: Vec<CursorSpan>,
|
|
}
|
|
|
|
#[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<DocumentWithCursors>,
|
|
}
|
|
|
|
#[derive(TS, Serialize, Clone, Debug)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct CursorPositionFromServer {
|
|
pub clients: Vec<ClientCursors>,
|
|
}
|
|
|
|
// 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<DeviceId>,
|
|
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,
|
|
}
|
|
}
|
|
}
|