note_update
Server → Client. Fired when collaborators save a new encrypted note payload. Handler: websocket/messages.go:BroadcastNoteUpdate.
wss://<frontend-domain>/wsbackend/routes.go.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 updatews.send(JSON.stringify({type: 'note_update',payload: { note_id: noteId, content_encrypted: await cryptoService.encryptData(editorState), version: 12}}));
// Receive updatesws.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.