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 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 & 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: noundCache-Control: no-cacheverhindern 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 vollesstate(Compaction) als auch inkrementellesupdate(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