# 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:

  1. Remote-Select-Cache wird für das Model invalidiert (invalidateRemoteSelectCacheForModel)
  2. TanStack Query Cache wird für die Resource invalidiert (invalidateResource)
  3. Registrierte Callbacks werden aufgerufen (onWebSocketChange)
  4. 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:

  1. Öffne das Debug Lab (V2-Programm)
  2. Wähle den Tab WebSocket
  3. Dort siehst du alle eingehenden Nachrichten

Alternativ im Browser:

// In der Browser-Konsole
const runtimeStore = useRuntimeStore();
console.log(runtimeStore.webSocketLogs);

# Siehe auch

Last Updated: 4/16/2026, 8:54:41 AM