Zum Inhalt springen

Echtzeit & Chat

Der Chat aktualisiert sich in Echtzeit und fühlt sich dabei sofort und flackerfrei an. Dahinter steht ein bewusst gewähltes Muster.

Neue Ereignisse (Senden, Bearbeiten, Löschen, Reaktion) werden auf zwei Wegen verteilt:

  1. Client → Client direkt per Broadcast (broadcastFamily) — minimale Latenz.
  2. DB-Trigger-Broadcast als Fallback, falls der direkte Weg jemanden nicht erreicht (z. B. gerade offline).

Mutationen werden sofort lokal angezeigt und in getrennten Overlays gehalten (pending, pendingEdits, pendingDels, pendingReactions). Diese Overlays werden zuletzt über die Serverliste gelegt — so überlebt eine optimistische Änderung auch einen zwischenzeitlichen, noch veralteten Server-Reload. Die Merge-Logik (mergeThread, mergeReactions, applyReactionOverlay, receiptState) ist als reine, getestete Funktionen ausgelagert.

Realtime läuft über private Kanäle (Migration 0082):

const ch = sb.channel(topic, { config: { private: true } });
await sb.realtime.setAuth(token); // vor subscribe

RLS auf realtime.messages regelt Empfangen (SELECT) und Senden (INSERT). Wichtig: Der Abo-Umfang muss exakt dem RLS-Umfang entsprechen (memberFamilyIds). Abonniert ein Client Kanäle fremder Familien, führt das zu einer „Unauthorized“-Rejoin-Schleife und damit zu einseitiger Verzögerung.

  • Idempotenz: Die Nachrichten-ID wird clientseitig erzeugt (crypto.randomUUID()); ein 23505 (Unique-Verletzung) gilt als Erfolg.
  • Hintergrund-Retry mit Timeout (Promise.race) — kurzer Timeout für Text, längerer für Anhänge.

markRead / markDelivered schreiben nur vorwärts (per last_x.is.null or last_x.lt.<iso>); receiptState leitet daraus den Status ab (gelesen > zugestellt > gesendet).