Files
MindSift/Doku/api/websocket.md
T
sandrodach 6343d62bab
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s
feat(realtime): implement SSE fallback, dual-transport hub & proxy resilience (resolves FLAW-03)
2026-10-26 16:55:11 +01:00

11 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 & Proxy-Resilienz (Dual-Transport Architektur)

In restriktiven Firmennetzwerken, hinter Unternehmens-Proxies (z.B. Zscaler, BlueCoat) oder fehlerhaft konfigurierten Reverse-Proxies wird der Upgrade: websocket-Header häufig verworfen (NS_ERROR_CONNECTION_REFUSED, Code 1006 / 1002 / 1008).

MindSift löst dieses Problem durch eine vollwertige Dual-Transport-Architektur (FLAW-03):

                                  ┌───────────────────────────────┐
                                  │      MindSift Frontend        │
                                  └───────────────┬───────────────┘
                                                  │
                            ┌─────────────────────┴─────────────────────┐
                            ▼                                           ▼
                 Standard: WebSockets                        Fallback: SSE + REST
            (wss://.../api/v1/ws)                       (https://.../api/v1/events)
                            │                                           │
                            │  [Proxy Upgrade Block (Code 1006/Timeout)]│
                            │ ────────────────────────────────────────> │
                            ▼                                           ▼
               ┌────────────────────────┐                  ┌────────────────────────┐
               │    Gorilla WS Conn     │                  │  Server-Sent Events    │
               │   Bidirektionaler      │                  │  (text/event-stream)   │
               │    TCP-WebSocket       │                  │  Standard HTTPS Port   │
               └────────────┬───────────┘                  └────────────┬───────────┘
                            │                                           │
                            └─────────────────────┬─────────────────────┘
                                                  ▼
                                   ┌─────────────────────────────┐
                                   │       Realtime Hub          │
                                   │   (Multiplexing WS & SSE)   │
                                   └─────────────────────────────┘

1. Automatischer SSE-Fallback (GET /api/v1/events)

  • Scheitert der WebSocket-Handshake oder blockiert ein Proxy das Upgrade, wechselt MindSift innerhalb von 3,5 Sekunden automatisch auf einen Server-Sent Events (SSE) Stream.
  • Da SSE ein standardmäßiger HTTP/1.1- bzw. HTTP/2-GET-Request über Standard-Port 443/80 ist (Content-Type: text/event-stream), wird er von jedem Proxy und jeder Firewall ungehindert durchgeleitet.
  • Header X-Accel-Buffering: no und Cache-Control: no-cache verhindern Pufferung im Proxy; periodische Ping-Kommentare (: ping\n\n) alle 15 Sekunden halten Timeouts fern.
  • Der Client bleibt dadurch vollständig echtzeitfähig (Live (SSE)).

2. REST-Endpunkte für Upstream-Aktionen im SSE-Modus

Da SSE unidirektional (Server ➔ Client) ist, stellt MindSift für Schreibvorgänge im SSE-Modus REST-Äquivalente bereit:

  • PUT /api/v1/documents/:id/crdt: Unterstützt sowohl volles state (Compaction) als auch inkrementelles update (Delta). Der Hub persistiert das Update und broadcastet es sofort an alle anderen WS- und SSE-Clients im Dokumentenraum.
  • POST /api/v1/documents/:id/awareness: Überträgt Cursor- und Blockfokus-Präsenzen.
  • DELETE /api/v1/documents/:id/awareness: Informiert Peers beim Verlassen des Dokuments.
  • POST /api/v1/events/rooms: Erlaubt SSE-Clients, Dokument- oder Listenräumen dynamisch beizutreten (join) oder sie zu verlassen (leave).

3. Verbindungs-Indikator

  • Grün pulsierend: Live verbunden über WebSockets (Live (WebSocket)).
  • Türkis pulsierend: Live verbunden über Server-Sent Events (Live (Server-Sent Events / Proxy-Stream)).
  • Gelb/Amber: Temporäre Unterbrechung mit automatischem HTTP-Polling-Schutz.

Ausführliche Konfigurationsbeispiele für Nginx, Traefik, Caddy, Cloudflare und Apache finden sich im separaten Handbuch: 👉 Reverse-Proxy-Konfigurationshandbuch