# WebSocket & Echtzeit-Events
Wie die WebSocket-Verbindung funktioniert und wie du Echtzeit-Updates in deinen Erweiterungen nutzt.
# Übersicht
SeminarPlan nutzt eine WebSocket-Verbindung, um Änderungen in Echtzeit an alle verbundenen Clients zu übertragen. Die Verbindung wird beim Login aufgebaut und bleibt während der gesamten Sitzung aktiv.
| Eigenschaft | Wert |
|---|---|
| Protokoll | ws:// bzw. wss:// |
| Subprotocol | ['token', '<jwt>'] |
| Heartbeat | online-Nachricht alle 30 Sekunden |
| Auto-Reconnect | Ja, nach 5 Sekunden |
| Timeout-Erkennung | Bei > 5 Minuten ohne Nachricht → Reload-Dialog |
# Verbindung herstellen
Die Verbindung wird über das Composable useWebSocket() verwaltet:
import { useWebSocket } from '@/composables/webSocket';
const { connect, close, sendObj, isOpen, isClosed } = useWebSocket();
// Verbindung aufbauen (geschieht automatisch beim Login)
connect();
// Prüfen ob verbunden
if (isOpen.value) { ... }
Das Token wird als WebSocket-Subprotocol übergeben:
ws = new WebSocket(config.socketUrl, ['token', userStore.token]);
# Nachrichtenformat
Alle Nachrichten werden als JSON übertragen. Jede Nachricht hat ein task-Feld:
{
"task": "change",
...
}
# Event-Typen
# online - Heartbeat & Online-Status
Wird alle 30 Sekunden gesendet und empfangen. Enthält die Liste der aktuell verbundenen Benutzer.
Empfangen:
{
"task": "online",
"members": [
{ "_id": "user1", "name": "Max Mustermann" },
{ "_id": "user2", "name": "Anna Schmidt" }
]
}
Die Online-Liste wird im RuntimeStore gespeichert.
# change - Datenänderung
Das wichtigste Event. Wird ausgelöst, wenn ein Benutzer einen Datensatz erstellt, aktualisiert oder löscht.
Empfangen:
{
"task": "change",
"model": "bookings",
"what": "update",
"payload": { "_id": "abc123", "title": "Neuer Titel", "__v": 5 },
"userId": "user1"
}
| Feld | Beschreibung |
|---|---|
model | Entity-Name (z.B. bookings, contacts, seminars) |
what | Art der Änderung: create, update, delete, create_many |
payload | Der geänderte Datensatz (bei update: nur geänderte Felder + _id) |
userId | ID des Benutzers, der die Änderung vorgenommen hat |
Verfügbare what-Werte:
| Wert | Beschreibung | Store-Aktion |
|---|---|---|
create | Neuer Eintrag erstellt | addItemToStore() |
update | Eintrag aktualisiert | Object.assign() auf bestehendes Item |
delete | Eintrag gelöscht (Soft Delete) | Store wird neu geladen |
create_many | Mehrere Einträge erstellt (z.B. Import) | Store wird neu geladen |
# lock / unlock - Optimistisches Locking
Zeigt an, dass ein Benutzer einen Datensatz gerade bearbeitet.
Senden:
const { sendLock, sendUnlock } = useWebSocket();
// Wenn ein Benutzer einen Dialog öffnet
sendLock('abc123');
// Wenn der Dialog geschlossen wird
sendUnlock('abc123');
Empfangen (lock):
{
"task": "lock",
"lock": {
"_id": "abc123",
"userId": "user1",
"createdAt": 1710000000000
}
}
Empfangen (unlock):
{
"task": "unlock",
"_id": "abc123"
}
Locks werden im RuntimeStore verwaltet und in der UI als Hinweis angezeigt (z.B. "Wird gerade von Max bearbeitet").
# activity - Neue Aktivität
Wird ausgelöst, wenn eine Benutzeraktion protokolliert wird (z.B. Buchung erstellt, E-Mail versendet).
{
"task": "activity",
"payload": {
"_id": "act123",
"verb": "created",
"model": "bookings",
"userId": "user1",
"createdAt": "2026-01-15T10:30:00Z"
}
}
# id - WebSocket-ID zuweisen
Wird direkt nach dem Verbindungsaufbau vom Server gesendet. Die ID wird als HTTP-Header ws-id bei API-Requests mitgeschickt, damit der Server weiß, welcher Client die Änderung vorgenommen hat (und ihm kein eigenes Change-Event zurückschickt).
{
"task": "id",
"id": "ws-abc123"
}
# schedule_update - Software-Update
Informiert den Benutzer, dass eine neue Version von SeminarPlan verfügbar ist.
{
"task": "schedule_update",
"version": "3.5.0"
}
# ai_*_generation_update - KI-Generierung
Events für laufende KI-Generierungen:
| Task | Beschreibung |
|---|---|
ai_seminar_generation_update | Seminar-Inhalte werden generiert |
ai_category_generation_update | Kategorie-Inhalte werden generiert |
ai_static_page_generation_update | Statische-Seite-Inhalte werden generiert |
# emergency_reload - Notfall-Reload
Erzwingt ein sofortiges Neuladen der Seite bei allen Clients.
{
"task": "emergency_reload"
}
# Change-Propagation
Wenn ein change-Event empfangen wird, passiert folgendes:
- Remote-Select-Cache wird für das Model invalidiert (
invalidateRemoteSelectCacheForModel) - TanStack Query Cache wird für die Resource invalidiert (
invalidateResource) - Registrierte Callbacks werden aufgerufen (
onWebSocketChange) - Store wird aktualisiert (je nach
what-Wert)
# Model-to-Store-Mapping
Das modelToStoreKeys-Mapping in storeHelper.ts ordnet Server-Model-Namen den Frontend-Stores zu:
| Server-Model | Frontend-Store |
|---|---|
bookings | bookingStore |
contacts | contactStore |
fixeddates | fixedDateStore |
invoices | invoiceStore |
seminars | seminarStore |
| ... | ... |
Insgesamt sind über 30 Models gemappt.
# Auf Änderungen reagieren
Du kannst dich für Change-Events registrieren:
import { onWebSocketChange } from '@/composables/webSocketChangeHelper';
// Callback registrieren
const unsubscribe = onWebSocketChange((model, what) => {
if (model === 'bookings' && what === 'create') {
console.log('Neue Buchung wurde erstellt!');
}
});
// Callback abmelden
unsubscribe();
# Eigene Task-Handler registrieren
Für V2-Programme oder Extensions kannst du eigene WebSocket-Tasks definieren:
import {
registerWebSocketTaskHandler,
unregisterWebSocketTaskHandler
} from '@/composables/webSocket';
// Handler registrieren (wird VOR den eingebauten Handlern geprüft)
registerWebSocketTaskHandler('my_custom_task', (json) => {
console.log('Custom task received:', json);
});
// Handler abmelden
unregisterWebSocketTaskHandler('my_custom_task');
Reihenfolge
Externe Handler werden vor den eingebauten Handlern geprüft. Wenn du einen eingebauten Task-Namen verwendest (z.B. change), wird der eingebaute Handler nicht ausgeführt.
# Debugging
WebSocket-Nachrichten werden im RuntimeStore protokolliert:
- Öffne das Debug Lab (V2-Programm)
- Wähle den Tab WebSocket
- Dort siehst du alle eingehenden Nachrichten
Alternativ im Browser:
// In der Browser-Konsole
const runtimeStore = useRuntimeStore();
console.log(runtimeStore.webSocketLogs);
# Siehe auch
- API-Dokumentation - REST-Endpunkte und Authentifizierung
- Neuen Store anlegen - Store-Integration mit WebSocket
- Extensions - API mit eigenen Plugins erweitern