Add API for propagating cursor locations #61

Merged
schmelczer merged 30 commits from asch/show-cursors into main 2025-06-08 20:20:53 +01:00
6 changed files with 28 additions and 16 deletions
Showing only changes of commit a628ff348e - Show all commits

Send spans instead of indexes

Andras Schmelczer 2025-06-07 11:32:20 +01:00
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C

View file

@ -15,17 +15,24 @@ pub struct WebSocketHandshake {
pub last_seen_vault_update_id: Option<VaultUpdateId>, pub last_seen_vault_update_id: Option<VaultUpdateId>,
} }
#[derive(TS, Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct CursorSpan {
pub start: usize,
pub end: usize,
}
#[derive(TS, Deserialize, Clone, Debug)] #[derive(TS, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct CursorPositionFromClient { pub struct CursorPositionFromClient {
pub document_to_cursors: HashMap<String, Vec<usize>>, pub document_to_cursors: HashMap<String, Vec<CursorSpan>>,
} }
#[derive(TS, Serialize, Clone, Debug)] #[derive(TS, Serialize, Clone, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ClientCursors { pub struct ClientCursors {
pub device_id: DeviceId, pub device_id: DeviceId,
pub cursors: HashMap<String, Vec<usize>>, pub cursors: HashMap<String, Vec<CursorSpan>>,
} }
#[derive(TS, Serialize, Clone, Debug)] #[derive(TS, Serialize, Clone, Debug)]

View file

@ -59,9 +59,9 @@ async fn websocket(
stream: WebSocket, stream: WebSocket,
vault_id: VaultId, vault_id: VaultId,
) -> Result<(), SyncServerError> { ) -> Result<(), SyncServerError> {
let (mut sender, mut receiver) = stream.split(); let (mut sender, mut websocket_receiver) = stream.split();
let handshake = if let Some(Ok(message)) = receiver.next().await { let handshake = if let Some(Ok(message)) = websocket_receiver.next().await {
get_handshake(&state, &vault_id, message)? get_handshake(&state, &vault_id, message)?
} else { } else {
return Err(unauthenticated_error(anyhow::anyhow!( return Err(unauthenticated_error(anyhow::anyhow!(
@ -69,7 +69,7 @@ async fn websocket(
))); )));
}; };
let mut rx = state.broadcasts.get_receiver(vault_id.clone()).await; let mut broadcast_receiver = state.broadcasts.get_receiver(vault_id.clone()).await;
send_update_over_websocket( send_update_over_websocket(
&WebSocketServerMessage::VaultUpdate(WebSocketVaultUpdate { &WebSocketServerMessage::VaultUpdate(WebSocketVaultUpdate {
@ -91,7 +91,7 @@ async fn websocket(
let device_id = handshake.device_id.clone(); let device_id = handshake.device_id.clone();
let mut send_task = tokio::spawn(async move { let mut send_task = tokio::spawn(async move {
while let Ok(update) = rx.recv().await { while let Ok(update) = broadcast_receiver.recv().await {
if Some(&device_id) == update.origin_device_id.as_ref() { if Some(&device_id) == update.origin_device_id.as_ref() {
continue; continue;
} }
@ -103,10 +103,10 @@ async fn websocket(
}); });
let device_id = handshake.device_id.clone(); let device_id = handshake.device_id.clone();
let mut recv_task = tokio::spawn(async move { let mut receive_task = tokio::spawn(async move {
while let Some(Ok(Message::Text(message))) = receiver.next().await { while let Some(Ok(Message::Text(message))) = websocket_receiver.next().await {
let message: WebSocketClientMessage = serde_json::from_str(&message) let message: WebSocketClientMessage = serde_json::from_str(&message)
.context("Failed to parse message") .context("Failed to parse WebSocket message from client")
.map_err(server_error)?; .map_err(server_error)?;
match message { match message {
@ -128,8 +128,8 @@ async fn websocket(
}); });
tokio::select! { tokio::select! {
_ = &mut send_task => recv_task.abort(), _ = &mut send_task => receive_task.abort(),
_ = &mut recv_task => send_task.abort(), _ = &mut receive_task => send_task.abort(),
}; };
send_task send_task
@ -137,7 +137,7 @@ async fn websocket(
.context("WebSocket send task failed") .context("WebSocket send task failed")
.map_err(server_error)??; .map_err(server_error)??;
recv_task receive_task
.await .await
.context("WebSocket receive task failed") .context("WebSocket receive task failed")
.map_err(server_error)??; .map_err(server_error)??;

View file

@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CursorSpan } from "./CursorSpan";
export type ClientCursors = { deviceId: string, cursors: { [key in string]?: Array<number> }, }; export type ClientCursors = { deviceId: string, cursors: { [key in string]?: Array<CursorSpan> }, };

View file

@ -1,3 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CursorSpan } from "./CursorSpan";
export type CursorPositionFromClient = { documentToCursors: { [key in string]?: Array<number> }, }; export type CursorPositionFromClient = { documentToCursors: { [key in string]?: Array<CursorSpan> }, };

View file

@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type CursorSpan = { start: number; end: number };

View file

@ -2,8 +2,6 @@
set -e set -e
./scripts/utils/wait-for-server.sh
rm -rf backend/sync_server/bindings rm -rf backend/sync_server/bindings
cd backend cd backend
cargo test export_bindings cargo test export_bindings
@ -11,5 +9,7 @@ cd -
cp -r backend/sync_server/bindings/* frontend/sync-client/src/services/types/ cp -r backend/sync_server/bindings/* frontend/sync-client/src/services/types/
./scripts/utils/wait-for-server.sh
npm install -g openapi-typescript npm install -g openapi-typescript
openapi-typescript http://localhost:3000/api.json --output frontend/sync-client/src/services/types/http-api.ts openapi-typescript http://localhost:3000/api.json --output frontend/sync-client/src/services/types/http-api.ts