Skip to content

WebSocket Collaboration

  • URL: wss://<frontend-domain>/ws
  • Backend upgrades requests in backend/routes.go.
  • Clients must include a valid JWT (same token issued by POST /auth/login).
const token = localStorage.getItem('leaflock_jwt');
const ws = new WebSocket(`wss://app.leaflock.example/ws?token=${token}`);
ws.addEventListener('open', () => console.log('collaboration ready'));
ws.addEventListener('close', () => console.log('disconnected'));

middleware.JWTMiddleware validates the token before the connection reaches the hub.

All frames are JSON objects containing a type string and a payload object.

{
"type": "note_update",
"payload": {
"note_id": "b9fa1ed4-9d03-4d56-94f1-8b2b50f5b9de",
"content_encrypted": "BASE64...",
"user_id": "user-123",
"timestamp": "2025-01-15T10:30:00Z"
}
}

The payload stays encrypted—clients decrypt with the note key they already hold in memory.

note_update

Server → Client. Fired when collaborators save a new encrypted note payload. Handler: websocket/messages.go:BroadcastNoteUpdate.

cursor_position

Bidirectional. Tracks caret positions for live presence. Stored transiently inside the hub instance.

user_joined / user_left

Server → Client notifications emitted by websocket/hub.go when peers connect or disconnect.

presence

Client → Server heartbeat. Use every 20 seconds to keep the connection alive (websocket/handlers.go updates last-seen timestamps).

// Broadcast encrypted editor update
ws.send(JSON.stringify({
type: 'note_update',
payload: {
note_id: noteId,
content_encrypted: await cryptoService.encryptData(editorState),
version: 12
}
}));
// Receive updates
ws.addEventListener('message', async (event) => {
const message = JSON.parse(event.data);
if (message.type === 'note_update') {
const decrypted = await cryptoService.decryptData(message.payload.content_encrypted);
applyRemoteUpdate(decrypted);
}
});

Only collaborators listed in the collaborations table can join a note channel. Share-link viewers are excluded intentionally.

  • websocket/hub.go → central fan-out loop that keeps track of rooms and connections.
  • websocket/client.go → per-connection goroutine for reading/writing frames with timeouts.
  • handlers/collaboration.go → ensures collaborators have RSA keys needed to decrypt shared note content.