feat(xmpp): natieve calls F3 — MUC (XEP-0045) join/occupants + companion-fundament #1132

Merged
brenno merged 6 commits from feat/native-calls-f3-muc-join into main 2026-08-02 15:58:25 +00:00
Owner

De MUC-slice van fase F3 (docs/design/NATIVE_CALLS.md §5.1): XEP-0045 Multi-User Chat bovenop de persistente XMPP-sessie (#1128). Dit is de primitief onder zowel de Jitsi-conferentiekamer (media-occupants) als OciDecks companion-kamer (§5.1, waar OciDeck-gebruikers elkaar via presence herkennen).

Wat

  • lib/xmpp/xmpp_muc.dartXmppMuc.join() stuurt een presence naar room@service/nick, bevestigt op de self-presence (status code 110), en volgt occupants uit presence (available voegt toe/werkt bij, unavailable verwijdert) als een live occupants-roster (MucOccupant: affiliation/role/realJid). XEP-0045-error-presences → getypeerde MucJoinFailure. Draagt bewust alleen presence/roster — geen media, geen OciDeck-stuurpayload.
  • XmppStanzaChannel (in xmpp_session.dart) — de kleine send/ontvang/bound-JID-interface waar XmppMuc aan hangt (en die XmppSession implementeert), zodat de join-logica los te unit-testen is tegen een fake-kanaal — geen socket. Schone laagrichting: MUC → interface, niet andersom.
  • De test-dialoog krijgt een optioneel kamer-veld: bij invullen verbindt hij, joint de MUC, toont een momentopname van de aanwezigen (of een vertaalde join-fout), verlaat de kamer weer en sluit de sessie. Bewaart nog steeds niets.

Waarborgen

  • Reviews, beide GO: beveiligingsarchitect (presence-herkomstfilter sound — alleen presence met bare-JID == roomJid; self-detectie niet te vervalsen door een peer; geen nieuwe egress; geen nieuw XML-parseoppervlak; opruiming idempotent + fail-closed) en kernwaardenbewaker ("bewaart niets" klopt; layering; alleen-presence/roster trouw aan §5.1; realJid wordt niet getoond — privacy-minimaal). Twee review-punten doorgevoerd (robuustheid tegen een buggy/kwaadaardige server): een stray error-presence ná join sloopt de live roster niet meer (gate op !_joined), en leave() na een gefaalde join stuurt geen overbodige unavailable.
  • Geen nieuwe egress/dependency: de MUC-presence rijdt op de bestaande NetGuard-gepinde sessie; package:xml was al aanwezig. Secrets/SAST schoon.
  • Tests (12, fake-kanaal, geen socket): join-succes + roster, non-anonieme realJid-onthulling, error-condities (conflict/members-only), occupant-vertrek, leave, session-drop, timeout, dubbel-join, en de twee review-fixes. Plus 5 dialoog-widget-tests (incl. kamer-join + join-fout-weergave).
  • Docs: SOURCE_MAP (xmpp_muc + XmppStanzaChannel + dialoog-entry). l10n: 6 nieuwe strings × 31 talen.

Poortstand

Op verse origin/main (na #1129, dat de welcome-/XMPP-drift herstelde) gerebased. make check volledig groen — alle statische poorten, de volledige testsuite én de dekkingsvloeren (per-bestand geen enkel bestand onder 34%). Secrets-scan (gitleaks/trufflehog) en SAST (semgrep) schoon. Geen pubspec/SBOM-wijziging.

De MUC-slice van fase F3 (`docs/design/NATIVE_CALLS.md` §5.1): **XEP-0045 Multi-User Chat** bovenop de persistente XMPP-sessie (#1128). Dit is de primitief onder zowel de Jitsi-conferentiekamer (media-occupants) als OciDecks **companion-kamer** (§5.1, waar OciDeck-gebruikers elkaar via presence herkennen). ## Wat - **`lib/xmpp/xmpp_muc.dart`** — `XmppMuc.join()` stuurt een presence naar `room@service/nick`, bevestigt op de **self-presence** (status code 110), en volgt occupants uit presence (available voegt toe/werkt bij, `unavailable` verwijdert) als een live **`occupants`**-roster (`MucOccupant`: affiliation/role/realJid). XEP-0045-error-presences → getypeerde **`MucJoinFailure`**. Draagt bewust **alleen presence/roster** — geen media, geen OciDeck-stuurpayload. - **`XmppStanzaChannel`** (in `xmpp_session.dart`) — de kleine send/ontvang/bound-JID-interface waar `XmppMuc` aan hangt (en die `XmppSession` implementeert), zodat de join-logica los te unit-testen is tegen een fake-kanaal — geen socket. Schone laagrichting: MUC → interface, niet andersom. - De **test-dialoog** krijgt een optioneel kamer-veld: bij invullen verbindt hij, joint de MUC, toont een momentopname van de aanwezigen (of een vertaalde join-fout), verlaat de kamer weer en sluit de sessie. Bewaart nog steeds niets. ## Waarborgen - **Reviews, beide GO**: beveiligingsarchitect (presence-herkomstfilter sound — alleen presence met bare-JID == roomJid; self-detectie niet te vervalsen door een peer; geen nieuwe egress; geen nieuw XML-parseoppervlak; opruiming idempotent + fail-closed) en kernwaardenbewaker ("bewaart niets" klopt; layering; alleen-presence/roster trouw aan §5.1; `realJid` wordt niet getoond — privacy-minimaal). Twee review-punten doorgevoerd (robuustheid tegen een buggy/kwaadaardige server): een **stray error-presence ná join** sloopt de live roster niet meer (gate op `!_joined`), en **`leave()` na een gefaalde join** stuurt geen overbodige `unavailable`. - **Geen nieuwe egress/dependency**: de MUC-presence rijdt op de bestaande NetGuard-gepinde sessie; `package:xml` was al aanwezig. Secrets/SAST schoon. - **Tests** (12, fake-kanaal, geen socket): join-succes + roster, non-anonieme realJid-onthulling, error-condities (conflict/members-only), occupant-vertrek, `leave`, session-drop, timeout, dubbel-join, en de twee review-fixes. Plus 5 dialoog-widget-tests (incl. kamer-join + join-fout-weergave). - **Docs**: SOURCE_MAP (`xmpp_muc` + `XmppStanzaChannel` + dialoog-entry). l10n: 6 nieuwe strings × 31 talen. ## Poortstand Op verse `origin/main` (na #1129, dat de welcome-/XMPP-drift herstelde) gerebased. **`make check` volledig groen** — alle statische poorten, de volledige testsuite én de dekkingsvloeren (per-bestand geen enkel bestand onder 34%). Secrets-scan (gitleaks/trufflehog) en SAST (semgrep) schoon. Geen pubspec/SBOM-wijziging.
XmppMuc hangt aan de kleine interface XmppStanzaChannel (die XmppSession
implementeert), dus de join-logica is los te unit-testen tegen een fake-
kanaal — geen socket. join() stuurt een presence naar room/nick, bevestigt
op de self-presence (status 110), volgt occupants uit presence (available
voegt toe/werkt bij, unavailable verwijdert) en biedt ze als een live
occupants-roster (MucOccupant: affiliation/role/realJid). XEP-0045-error-
presences → getypeerde MucJoinFailure. Dit is de primitief onder zowel de
Jitsi-conferentiekamer als OciDecks companion-kamer (§5.1): alleen
presence/roster, geen media of OciDeck-stuurpayload. 10 tests (fake-kanaal).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Een optioneel kamer-veld: bij invullen verbindt de dialoog, joint de MUC,
toont een momentopname van de aanwezigen (of een vertaalde join-fout), en
verlaat de kamer weer. Bewaart nog steeds niets. AlertDialog nu scrollable
(vier velden + resultaat kunnen hoog worden).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Beveiligingsarchitect-review (robuustheid tegen een buggy/kwaadaardige
server, beide fail-closed):
- Een error-presence NÁ een geslaagde join sloopt de live roster niet
  meer stil: de error-tak gate't nu op !_joined. Een spurious of
  kwaadaardige post-join error kan de roster-stroom niet dichttrekken
  zonder de consument te informeren.
- leave() na een gefaalde join stuurt geen overbodige unavailable meer:
  _teardown zet _left, zodat een latere leave() geen presence naar een
  nooit-betreden kamer stuurt.
Twee tests dekken beide af.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Zes nieuwe d('…')-bronstrings uit xmpp_test_connection_dialog.dart
(MUC-kamer joinen + deelnemers tonen) vertaald in alle 31 niet-
Nederlandse talen via de _dutchSourceAdd<Lang>-overlays.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
style(xmpp): dart format F3 MUC
Some checks failed
scans / scans (pull_request) Successful in 6m17s
static-gate / static-gate (pull_request) Has been cancelled
8bab08d12e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno merged commit 529329562d into main 2026-08-02 15:58:25 +00:00
Sign in to join this conversation.
No description provided.