diff --git a/Communities/openreli/matrix-wordpress.md b/Communities/openreli/matrix-wordpress.md new file mode 100644 index 0000000..75ed310 --- /dev/null +++ b/Communities/openreli/matrix-wordpress.md @@ -0,0 +1,705 @@ +# Matrix Chat Widget – Dokumentation + +**Projekt:** Öffentliche Leseanzeige eines Matrix-Raums auf einer WordPress-Seite +**Homeserver:** `matrix.rpi-virtuell.de` +**Element-Client:** `element.rpi-virtuell.de` +**Raum-ID:** `!NQPmoqtLSjGzdtLaXO:rpi-virtuell.de` +**Stand:** März 2026 + +--- + +## Kontext und Problemlage + +Ziel war es, einen öffentlichen Matrix-Raum als Leseanzeige (kein Login für Besucher:innen erforderlich) in eine WordPress-Seite einzubetten. Dabei wurden folgende Wege geprüft und verworfen: + +**Matrix Public Archive / Matrix Viewer** – wurde eingestellt. + +**matrix-live (live.hello-matrix.net)** – funktioniert nur mit Homeservern, die Guest Access aktiviert haben. + +**Guest Access** – auf `matrix.rpi-virtuell.de` nicht aktiviert; auf `matrix.org` seit Januar 2025 abgeschaltet. + +**Element Web als iFrame** – `app.element.io` und die meisten selbst gehosteten Instanzen setzen `X-Frame-Options: DENY` und können nicht eingebettet werden. + +**Gewählte Lösung:** Ein eigenständiges HTML/JS-Widget, das direkt die Matrix Client-Server API abfragt. Voraussetzung ist ein dedizierter Bot-Account mit Lesezugriff, dessen Token fest im Widget hinterlegt ist. Das ist unkritisch, da der Token nur Lesezugriff auf einen öffentlichen Raum hat. + +--- + +## Voraussetzungen + +### 1. Raum auf „world_readable" setzen + +In Element: **Raumeinstellungen → Sicherheit & Datenschutz → „Wer kann den Verlauf lesen?" → „Jeder"** + +Ohne diese Einstellung antwortet die API mit HTTP 403. + +### 2. Bot-Account anlegen und Token holen + +Einen dedizierten Account (z. B. `@openrelibot:rpi-virtuell.de`) anlegen und einmalig per API einloggen: + +```bash +curl -X POST https://matrix.rpi-virtuell.de/_matrix/client/v3/login \ + -H "Content-Type: application/json" \ + -d '{"type":"m.login.password","user":"openrelibot","password":"PASSWORT"}' +``` + +Die Antwort enthält `access_token` – dieser wird im Widget als `TOKEN` eingetragen. + +> **Hinweis:** Da der Token in einem Chatprotokoll sichtbar war, sollte er bei nächster Gelegenheit rotiert werden (siehe Abschnitt Token-Rotation). + +--- + +## Einbindung in WordPress + +Gutenberg-Editor → Block **„Benutzerdefiniertes HTML"** → vollständigen Widget-Code einfügen → Speichern. + +Kein Plugin erforderlich. Der Code ist vollständig eigenständig (kein externes CDN, keine Abhängigkeiten). + +--- + +## Vollständiger Code + +```html + + +
+
+
+
+ Chat wird geladen… +
+ +
+
+ +
+ + + + +``` + +--- + +## Technische Dokumentation + +### Architektur + +Das Widget ist eine einzelne, vollständig eigenständige HTML-Datei ohne externe Abhängigkeiten. Es kommuniziert direkt mit der Matrix Client-Server API (v3) des Homeservers per `fetch()`. + +``` +Browser → fetch() → matrix.rpi-virtuell.de/_matrix/client/v3/... + → /_matrix/media/v3/download/... (Bilder) +``` + +### Verwendete API-Endpunkte + +| Endpunkt | Zweck | +|---|---| +| `GET /rooms/{roomId}/state/m.room.name/` | Raumname laden | +| `GET /rooms/{roomId}/state/m.room.avatar/` | Raumlogo laden (mxc-URL) | +| `GET /rooms/{roomId}/messages?dir=b&limit=N` | Letzte Nachrichten abrufen | +| `GET /profile/{userId}/avatar_url` | Benutzer-Avatar laden (mxc-URL) | +| `GET /_matrix/media/v3/download/{server}/{id}` | Mediendatei (Bild) abrufen | + +Alle Requests senden den `Authorization: Bearer {TOKEN}` Header, da der Homeserver auch für world-readable Räume Authentifizierung verlangt (HTTP 401 ohne Token). + +### Konfigurationsparameter + +```javascript +const HOMESERVER = 'https://matrix.rpi-virtuell.de'; // Matrix-Homeserver +const ROOM_ID = '!NQPmoqtLSjGzdtLaXO:rpi-virtuell.de'; // Raum-ID +const LIMIT = 40; // Anzahl angezeigter Top-Level-Nachrichten +const POLL_SEC = 30; // Polling-Intervall in Sekunden +const TOKEN = '...'; // Access Token des Bot-Accounts +``` + +### Nachrichten-Typen und Darstellung + +Das Widget unterscheidet vier Typen von Events anhand des `m.relates_to`-Feldes: + +**Normale Nachricht** – kein `m.relates_to` → wird auf Top-Level gerendert. + +**Bearbeitete Nachricht** (`rel_type: "m.replace"`) → wird nicht als separate Nachricht angezeigt. Stattdessen überschreibt das neueste Replace-Event den Inhalt der Originalnachricht. In der Zeitzeile erscheint kursiv *(bearbeitet)*. + +**Klassische Reply** (`m.in_reply_to`, kein `rel_type: m.thread`) → wird um 46 px eingerückt dargestellt, mit grauer linker Borderlinie und einer grünen Zitiervorschau des Originaltexts. Element bettet den zitierten Text als `> Zitat\n\nAntwort` in den `body` ein – das Widget extrahiert dieses Muster per `split('\n\n')`. + +**Thread-Reply** (`rel_type: "m.thread"`, `event_id` = Root-Event-ID) → wird unter der Root-Nachricht gesammelt und erst nach Klick auf den Toggle-Button sichtbar. Der aufgeklappte Thread-Container hat eine grüne linke Borderlinie und kleinere Avatare (26 px statt 36 px). + +### Rendering-Pipeline + +``` +fetchMessages() + → API: /messages?dir=b&limit=120 + → events.reverse() // chronologisch sortieren + → Edit-Deduplizierung: + latestEdit{} sammeln (m.replace) + Replace-Events herausfiltern + Originale mit m.new_content patchen + _edited:true + → patchedEvents.slice(-40) // letzte 40 behalten + → getUserAvatarUrl() parallel // Avatare vorladen + → Sortierung: byId{}, threads{}, topLevel[] + → topLevel.forEach → buildMsgEl() + → threads[e.event_id] → Toggle + threadEl + → Orphan-Thread-Kinder anhängen +``` + +Das Ladefenster (`limit = LIMIT * 3 = 120`) ist größer als die angezeigten 40 Nachrichten, um sicherzustellen, dass zu den angezeigten Top-Level-Nachrichten auch ihre zugehörigen Edit- und Thread-Events im selben API-Response enthalten sind. + +### mxc-URLs + +Matrix speichert Mediendateien intern als `mxc://server/id`. Die Funktion `mxcToHttp()` konvertiert diese in reguläre HTTPS-URLs: + +``` +mxc://rpi-virtuell.de/AbcXyz +→ https://matrix.rpi-virtuell.de/_matrix/media/v3/download/rpi-virtuell.de/AbcXyz +``` + +### Avatar-Caching + +Um bei jedem Polling-Zyklus unnötige API-Aufrufe zu vermeiden, werden User-Avatare in einem In-Memory-Objekt `avatarCache` gespeichert (`mxid → URL | null`). Pro Rendering-Durchlauf werden alle einzigartigen Absender parallel vorgeladen (`Promise.all`), bevor die Nachrichten gerendert werden. + +### Fallback-Logik für Avatare + +Ist kein Profilbild hinterlegt oder schlägt das Laden fehl (`onerror`), zeigt das Widget einen farbigen Kreis mit den ersten zwei Buchstaben des Localparts. Die Farbe wird deterministisch per Hash-Funktion aus der vollständigen MXID berechnet – dieselbe Person hat immer dieselbe Farbe. + +### XSS-Schutz + +Alle aus der API stammenden Texte (Absendername, Nachrichtentext, Zitiervorschau) werden durch `escapeHtml()` bereinigt, bevor sie per `innerHTML` eingefügt werden. Bilder werden nur als `` eingebettet, nie als HTML aus dem Event-Content übernommen. + +### Statusanzeige + +Der kleine Punkt oben rechts im Header zeigt den Verbindungsstatus: + +- 🟡 pulsierend – Ladevorgang +- 🟢 grün – letzter API-Aufruf erfolgreich +- 🔴 rot – Fehler (403 Forbidden oder Netzwerkfehler) + +### Polling statt WebSocket + +Das Widget verwendet Polling alle 30 Sekunden statt einer persistenten WebSocket-Verbindung (`/sync`). Begründung: WordPress-Seiten haben oft viele gleichzeitige Besucher:innen. Ein dauerhafter Sync-Kanal pro Seitenaufruf würde den Homeserver stark belasten. Polling ist hier ausreichend und deutlich ressourcenschonender. + +--- + +## Anpassungsmöglichkeiten + +**Höhe des Chat-Bereichs** – CSS: `#matrix-messages { height: 420px; }` + +**Anzahl der Nachrichten** – `const LIMIT = 40;` + +**Polling-Frequenz** – `const POLL_SEC = 30;` (nicht unter 10 setzen) + +**Farbe des Headers** – `#matrix-chat-header { background: #0dbd8b; }` (Matrix-Grün) + +**„Mitschreiben"-Link** – URL im `` im HTML-Teil anpassen + +--- + +## Token-Rotation + +Da der Token im Klartext im Code steht, sollte er bei Bedarf rotiert werden: + +```bash +# Alten Token invalidieren +curl -X POST https://matrix.rpi-virtuell.de/_matrix/client/v3/logout \ + -H "Authorization: Bearer ALTERTOKEN" + +# Neuen Token holen +curl -X POST https://matrix.rpi-virtuell.de/_matrix/client/v3/login \ + -H "Content-Type: application/json" \ + -d '{"type":"m.login.password","user":"openrelibot","password":"PASSWORT"}' +``` + +Neuen `access_token` im Code als `TOKEN` einsetzen und WordPress-Seite aktualisieren.