6.7 KiB
📡 WebSocket & Echtzeit-Synchronisation
MindSift implementiert eine bidirektionale Echtzeit-Synchronisationsschicht auf Basis von WebSockets (internal/ws/hub.go). Sie sorgt dafür, dass Änderungen an Aufgaben, Triage-Karten, Notizen und geteilten Einkaufslisten ohne manuelles Neuladen sofort auf allen geöffneten Geräten und bei allen Kollaborationspartnern sichtbar werden.
🔌 1. Verbindung & Handshake
Verbindungs-Endpunkte
wss://<domain>/api/v1/ws?token=<jwt-token>
wss://<domain>/api/v1/ws/lists/<listId>?token=<jwt-token>
Authentifizierung & Upgrade
- Die Authentifizierung erfolgt über das JWT-Token im Query-Parameter
?token=...oder denAuthorization: Bearer-Header während des HTTP-Upgrade-Requests. - Der Server extrahiert die
userIDund registriert die Verbindung im internenHub. - Der
Upgraderakzeptiert alle Origins (CheckOrigin: return true), um volle Kompatibilität mit Reverse-Proxies (Traefik, Nginx, Caddy, Cloudflare) und Docker-Netzwerken zu gewährleisten.
🏛️ 2. Hub-Architektur & Rooms
Der WebSocket-Hub unterteilt Verbindungen in drei Abstraktionsebenen:
┌─────────────────────────────────────────────────────────────────┐
│ WebSocket Hub │
├─────────────────┬─────────────────────────────┬─────────────────┤
│ allClients │ userRooms │ rooms │
│ (Alle aktiven │ (Pro Benutzer-ID) │ (Pro Liste/ │
│ Verbindungen) │ Cross-Device Sync für │ Space) │
│ │ Tasks, Triage & Workspace │ Geteilte Liste │
└────────┬────────┴──────────────┬──────────────┴────────┬────────┘
▼ ▼ ▼
Presence Stats TASK_CREATED ITEM_TOGGLED
DOC_LOCKED TASK_UPDATED ITEM_CREATED
DOC_UNLOCKED DOC_UPDATED LIST_UPDATED
allClients(Global): Hält jede aktive Client-Verbindung. Ermöglicht Broadcasts systemweiter Ereignisse (z.B. globale Dokument-Sperrungen).userRooms[userID](Benutzer-Räume): Sendet Events an alle Geräte desselben Benutzers (z.B. Desktop und Smartphone). Erzeugt ein Webhook oder Quick Dump auf dem Smartphone einen Eintrag, taucht dieser zeitgleich auf dem Desktop auf.rooms[listID](Kollaborations-Räume): Multiplexing für geteilte Listen. Alle Mitglieder einer Liste empfangen Aktionen (wie Abhaken, Löschen, Umsortieren) in Echtzeit.
📨 3. Nachrichten-Format (Envelope)
Alle WebSocket-Nachrichten werden als JSON mit einheitlicher Hülle übertragen:
{
"event": "ITEM_TOGGLED",
"listId": "lst_12345",
"payload": {
"itemId": "itm_987",
"isCompleted": true,
"completedBy": "usr_sandro",
"completedByName": "Sandro"
},
"timestamp": "2026-10-26T13:30:00Z"
}
⚡ 4. CRDT-Echtzeitprotokoll (Yjs Collaborative Editing)
Seit Version 2.1 unterstützt MindSift echte, konfliktfreie Mehrbenutzer-Kollaboration für Notizen via Yjs CRDT. Clients tauschen binäre Delta-Updates und Live-Awareness-Informationen über dedizierte WebSocket-Nachrichten aus:
CRDT-Nachrichtentypen
| Event | Richtung | Zweck / Payload |
|---|---|---|
CRDT_JOIN |
Client ➔ Server | Tritt dem Dokumentenraum (doc:{docId}) bei. |
CRDT_INIT |
Server ➔ Client | Sendet den aktuellen Basiszustand (stateBlob) und offene Delta-Updates (updates[]) an den Client. |
CRDT_UPDATE |
Bidirektional | Überträgt inkrementelle binäre Yjs-Updates (Base64-kodiert). Server speichert in document_crdt_updates und verteilt an alle anderen Raumnutzer. |
CRDT_COMPACT |
Client ➔ Server | Komprimiert mehrere Updates zu einem konsolidierten Snapshot in document_crdt_state. |
CRDT_AWARENESS |
Bidirektional | Überträgt Live-Präsenz (Fokus auf blockId, Benutzername, Farbe). |
CRDT_AWARENESS_LEAVE |
Bidirektional | Signalisiert, dass ein Nutzer den Fokus verloren hat oder das Dokument verlassen hat. |
CRDT_LEAVE |
Client ➔ Server | Verlässt den Dokumentenraum explizit. |
sequenceDiagram
autonumber
actor UserA as Benutzer A
participant WS as WebSocket Hub & SQLite
actor UserB as Benutzer B
UserA->>WS: CRDT_JOIN (docId: "doc_123")
WS-->>UserA: CRDT_INIT (stateBlob, updates)
UserB->>WS: CRDT_JOIN (docId: "doc_123")
WS-->>UserB: CRDT_INIT (stateBlob, updates)
UserA->>WS: CRDT_UPDATE (docId, updateBlob)
WS->>WS: Append in document_crdt_updates
WS->>UserB: CRDT_UPDATE (docId, updateBlob)
Note over UserB: Yjs wendet Delta atomar an (0 Konflikte)
UserA->>WS: CRDT_AWARENESS (docId, focusedBlockId)
WS->>UserB: CRDT_AWARENESS (docId, user, focusedBlockId)
Note over UserB: Avatar-Badge erscheint am Block
🔒 5. Abwärtskompatibles Document-Lease-Locking
Vor Version 2.1 nutzte MindSift ein serverseitiges Lease-Locking mit 30-Sekunden-Heartbeats. Durch die CRDT-Engine wurde die blockierende Bearbeitungssperre (isLockedByOther) abgelöst:
- Aktuelles Verhalten: Mehrere Benutzer mit Bearbeitungsrechten können zeitgleich dasselbe Dokument bearbeiten.
- Presence & Locks: Die Events
LOCK_DOCundHEARTBEAT_DOCbleiben abwärtskompatibel erhalten und dienen nun der reinen Präsenzanzeige (z.B. gelber Statusindikator „wird gerade von X angesehen"). - Schreibschutz: Ein echter Schreibschutz (
isReadOnly) greift ausschließlich noch dann, wenn der Nutzer nur über Leserechte (permission === 'read') für die Notiz oder den Ordner verfügt.
🔄 5. Fallback-Strategie & Reconnect
In strikten Firmennetzwerken oder bei Proxy-Problemen (z.B. fehlende Upgrade-Header bei HTTP/1.1) kann eine WebSocket-Verbindung fehlschlagen:
- Exponentielles Backoff: Das Frontend (
frontend/src/lib/api.ts) versucht automatisch die Wiederverbindung nach 1s, 2s, 4s, 8s, bis maximal 30s. - HTTP-Polling Fallback: Geteilte Listen und Notizen aktivieren im Hintergrund ein periodisches Polling (5s Intervall), falls der WebSocket-Status nicht
CONNECTEDist. - Status-Indikator: Ein dezenter grüner bzw. gelber Indikator oben rechts im Header zeigt dem Benutzer jederzeit den aktuellen Live-Verbindungsstatus an.