MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 8s
Remove outdated claims (CRDT, DocRooms, presence, .md files on disk) and document the actual architecture, API, permissions and known flaws. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
56 lines
3.5 KiB
Markdown
56 lines
3.5 KiB
Markdown
# Echtzeit: WebSocket und SSE
|
|
|
|
Der Server verteilt Änderungen als JSON-Ereignisse an verbundene Clients. Die Ereignisse entstehen **nach** einer REST-Änderung; es gibt keinen Schreibkanal über die Verbindung. Implementierung: [hub.go](../../internal/ws/hub.go), Client: [api.ts](../../frontend/src/lib/api.ts).
|
|
|
|
> Keine CRDTs, keine Sperren, keine Cursor-Presence. Frühere Fassungen dieser Doku beschrieben DocRooms, `clientId`-Echo-Schutz und Yjs; das existiert nicht (mehr).
|
|
|
|
## Transporte
|
|
|
|
| Transport | Endpunkt | Hinweise |
|
|
|---|---|---|
|
|
| WebSocket | `GET /api/v1/ws` (auch `/ws/lists/{id}`, `/ws/spaces/{id}`) | Ping alle 30 s, Lesefrist 60 s, Nachrichten bis 4 MB |
|
|
| SSE | `GET /api/v1/events` | Kommentar-Ping alle 15 s, Header `X-Accel-Buffering: no` |
|
|
|
|
Der Client versucht zuerst WebSocket. Gelingt der Aufbau nicht innerhalb von 2,5 s oder bricht die Verbindung unsauber ab, wechselt er auf SSE und bleibt dabei (Firmen-Proxys). Dort liefert der Server `SSE_CONNECTED` mit einer `sseId`; Räume werden über `POST /api/v1/events/rooms` (`sseId`, `room`, `action`: `join`/`leave`) abonniert.
|
|
|
|
**Authentifizierung:** Token als `?token=<JWT|API-Key>` oder `Authorization`-Header. Ohne gültiges Token wird die Verbindung als `anonymous` akzeptiert ([FLAW-01](../flaws.md), [FLAW-02](../flaws.md)). Das Token wird nur beim Verbinden geprüft.
|
|
|
|
## Nachrichtenformat
|
|
|
|
```json
|
|
{ "event": "TASK_UPDATED", "listId": "", "payload": { … }, "timestamp": "2026-10-27T12:00:00Z" }
|
|
```
|
|
|
|
Client → Server versteht der Server nur: `{"event":"JOIN_ROOM","room":"<id>"}` und `{"event":"LEAVE_ROOM","room":"<id>"}`. Räume heißen `doc:<docId>` für Notizen oder `<listId>` für geteilte Listen.
|
|
|
|
## Zustellung
|
|
|
|
| Kanal | Wer erhält es |
|
|
|---|---|
|
|
| Benutzer-Kanal (`BroadcastToUser`) | alle Verbindungen eines Benutzers |
|
|
| Raum (`BroadcastRoom`, `Broadcast`) | Verbindungen im Raum |
|
|
| Alle (`BroadcastToAll`) | **jede** Verbindung, auch unangemeldete |
|
|
|
|
## Ereignisse
|
|
|
|
| Ereignis | Auslöser | Kanal |
|
|
|---|---|---|
|
|
| `TASK_CREATED` | `POST /inbox` | Benutzer |
|
|
| `TASK_UPDATED` | `PATCH /thoughts/{id}` (Triage: Benutzer) | alle |
|
|
| `TASK_DELETED` | `DELETE /thoughts/{id}` | alle |
|
|
| `TASK_REORDERED` | Aufgaben umsortieren | alle |
|
|
| `TASK_LIST_UPDATED` / `TASK_LIST_DELETED` | Liste ändern, (un)teilen, löschen | alle |
|
|
| `DOC_CREATED`, `DOC_UPDATED`, `DOC_DELETED` | Notiz anlegen/ändern/löschen, Freigaben | berechtigte Benutzer |
|
|
| `DOC_BLOCKS_PATCHED` | `PATCH /documents/{id}/blocks` (`docId`, `updatedBy`, `upserted`, `deletedIds`, `blockOrder`) | Raum `doc:<id>` und berechtigte Benutzer |
|
|
| `WORKSPACE_FOLDERS_UPDATED` | Ordner/Mounts ändern | Benutzer |
|
|
| `LIST_CREATED`, `LIST_UPDATED`, `LIST_DELETED`, `LIST_SHARED`, `LIST_UNSHARED`, `LIST_CLEARED` | Listenverwaltung | Benutzer bzw. Raum der Liste |
|
|
| `ITEM_CREATED`, `ITEM_UPDATED`, `ITEM_TOGGLED`, `ITEM_DELETED`, `ITEM_REORDERED` | Listeneinträge | Raum der Liste |
|
|
| `USER_PRESENCE` | Verbindung auf-/abgebaut (`onlineCount`) | Raum |
|
|
| `SSE_CONNECTED` | Nur SSE, direkt nach Verbindung | Verbindung |
|
|
|
|
## Verhalten im Frontend
|
|
|
|
- Die globale Verbindung (ein Singleton) leitet jede Nachricht als Browser-Ereignis `mindsift:ws` weiter; Ansichten (`TasksView`, `WorkspaceView`, `SharedListsView`) hören darauf und laden bzw. patchen ihren Zustand.
|
|
- Nach einem Abbruch verbindet sich der Client mit Verzögerung neu.
|
|
- Weil Notizen nur vom Eigentümer geschrieben werden, dient `DOC_BLOCKS_PATCHED` vor allem dazu, Lesenden und weiteren Geräten des Eigentümers Änderungen zu zeigen. Es gibt keine Konfliktauflösung (letzter Schreiber gewinnt).
|