Files
MindSift/Doku/api/websocket.md
T
sandrodach ef8cf8f022
MindSift CI & Docker Release / Build, Test in Container & Push (push) Failing after 29s
feat(workspace): implement Yjs CRDT real-time collaborative editing (resolves FLAW-01)
2026-10-26 15:18:48 +01:00

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 den Authorization: Bearer-Header während des HTTP-Upgrade-Requests.
  • Der Server extrahiert die userID und registriert die Verbindung im internen Hub.
  • Der Upgrader akzeptiert 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
  1. allClients (Global): Hält jede aktive Client-Verbindung. Ermöglicht Broadcasts systemweiter Ereignisse (z.B. globale Dokument-Sperrungen).
  2. 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.
  3. 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_DOC und HEARTBEAT_DOC bleiben 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:

  1. Exponentielles Backoff: Das Frontend (frontend/src/lib/api.ts) versucht automatisch die Wiederverbindung nach 1s, 2s, 4s, 8s, bis maximal 30s.
  2. HTTP-Polling Fallback: Geteilte Listen und Notizen aktivieren im Hintergrund ein periodisches Polling (5s Intervall), falls der WebSocket-Status nicht CONNECTED ist.
  3. Status-Indikator: Ein dezenter grüner bzw. gelber Indikator oben rechts im Header zeigt dem Benutzer jederzeit den aktuellen Live-Verbindungsstatus an.