From db89295cae5bc8cb8c50220c9bd0d84ff198e25a Mon Sep 17 00:00:00 2001 From: Eric Wout van der Steen <92432523+ericwout-overheid@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:57:58 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9E=95=20Nieuwe=20berichten=20komen=20ov?= =?UTF-8?q?er=20een=20stroom,=20periodiek=20navragen=20is=20de=20terugval?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Staat de berichtenbox open en komt er een bericht binnen, dan meldt het stelsel dat nu zelf (`GET /api/v1/berichten/_volgen`, MinBZK/moza-poc-fbs- berichtenbox#336). De berichtenbox hoeft niet meer elke vijftien seconden de lijst op te vragen. De stroom begint met `volgen-gestart`. Dan haalt de berichtenbox één keer de lijst op, langs hetzelfde pad als een polltik. Daarna komt elk bericht als `bericht-bijgekomen`, met de naam van de afzender erbij. Het gaat als gewijzigde lijst naar de bron, die er een binnenkomer van maakt. Ontdubbelen gebeurt op `berichtId`, want een bericht op het grensvlak kan in allebei zitten. Een bericht dat binnenkomt terwijl die lijst nog onderweg is, blijft staan; anders legt de lijst zich eroverheen en verdwijnt het weer. Een waakhond breekt de verbinding af na 45 seconden zonder hartslag, want na een slaapstand merkt de browser het niet. Opnieuw verbinden gebeurt na 1, 2, 5, 10 en daarna 30 seconden, met spreiding; `volgen-gestart` zet die reeks terug. Na `sessie-verlopen` en bij een 409 draait het bestaande herstel. Een 503 wacht `Retry-After` af. Kent de keten `_volgen` niet (404, 405, of 406 zoals de publieke omgeving nu antwoordt), of komen drie verbindingen op rij niet tot stand, dan valt de berichtenbox stil terug op het periodiek navragen. Geen melding: de lijst werkt gewoon, alleen verschijnen nieuwe berichten wat later. `pagehide` sluit de stroom en `pageshow` verbindt opnieuw, behalve als er intussen een andere persona gekozen is. Een verborgen tabblad blijft verbonden. `?volgen=0` of `setting:berichtenbox-volgen` op 0 zet de stroom uit, voor een demonstratie van allebei. De SSE-parser van `_ophalen` is nu `leesSse` en wordt door beide gedeeld. De dev-proxy `server/proxy.js` kapte elke stroom af: zijn `proxyTimeout` van 15 seconden is korter dan de hartslag van 20. Voor `text/event-stream` staan de timeouts nu uit, en die antwoorden worden niet meer in het geheugen verzameld. Helpt MinBZK/MijnOverheidZakelijk#939 Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 2 +- assets/javascript/berichtenbox-keten.js | 400 ++++++++++++++-- assets/javascript/berichtenbox/keten-bron.js | 5 +- server/proxy.js | 10 + tests/berichtenbox/keten-harnas.js | 5 +- tests/berichtenbox/keten-volgen.test.js | 476 +++++++++++++++++++ 6 files changed, 844 insertions(+), 54 deletions(-) create mode 100644 tests/berichtenbox/keten-volgen.test.js diff --git a/CLAUDE.md b/CLAUDE.md index 4aef8f1a..bd4f3d21 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,7 +73,7 @@ MijnOverheid Zakelijk (MOZa) is een HTML/CSS/JS prototype gebouwd met Eleventy e - Berichtenbox, datalaag en rendering gescheiden: `assets/javascript/berichtenbox/` bevat zes modules zonder DOM-kennis — `datum.js` (datumnotatie), `state.js` (localStorage-state en de vragen daarover), `lijst.js` (filteren, sorteren, pagineren als pure functies), `bron.js` (bronregister) `keten-bron.js` (het Federatief Berichtenstelsel als bron) en `dataset-bron.js` (de gegenereerde dataset als bron, inclusief het nagebootste federatieve gedrag: de ophaalanimatie bij het eerste bezoek, de binnendruppelende berichten en de gesimuleerde bronuitval). `berichtenbox.js` is de render-laag: die leest uit de datalaag en muteert de bron nooit. Een bron is `{ naam, geldtVoor(persona), laad(), start?(meld), inhoudVan?(berichtId), volgVoortgang?(kijker), herhaalOphalen?(klaar) }`. `inhoudVan` levert `{ inhoud, bijlagen }` of `{ fout }` en werpt niet: de detailpagina moet altijd iets kunnen tonen, en een lege pagina zou de bezoeker laten denken dat het bericht zelf leeg is. De berichtenuitvraag van het stelsel geeft alleen kopgegevens — afzender, onderwerp, datum; de inhoud staat achter een eigen adres per bericht (`GET /api/v1/berichten/{berichtId}`) en wordt pas opgehaald als de bezoeker het bericht opent. Dat is geen besparing maar het gedrag van een federatief stelsel: de inhoud blijft bij de organisatie tot iemand erom vraagt. Een bijlage is een gewone link naar `/api/v1/berichten/{berichtId}/bijlagen/{bijlageId}`, geen fetch: een `` en een `` kunnen geen eigen header meesturen, en het stelsel eist `X-Ontvanger`. De keten-bron zet die ontvanger daarom in een cookie (`ontvanger`, rauw en `SameSite=Strict`) en de proxy maakt er in `container/default.conf.template` weer een header van — zelfde patroon als `X-Test-User` bij de Digitale Assistent. In het cookie en niet in de URL: de ontvanger hoort bij de zitting, niet bij het adres van het document. Dat maakt niets veiliger; een cookie is net zo goed aan te passen als een header. `personas.js` wist het cookie bij een persona-wissel, anders haalt een klik de bijlage van de vorige persona op. Berichten uit de keten hebben geen server-gerenderde detailpagina — die worden bij de build uit de dataset gegenereerd — en landen daarom op `bericht-demo.html`. `volgVoortgang` meldt `{ bevraagd, klaar, gevonden }` zolang er opgehaald wordt en `null` als er niets meer te melden valt; de render-laag abonneert zich daarop vóór de bronkeuze, want `geldtVoor` wacht een ronde af. `herhaalOphalen` draait een nieuwe ronde op verzoek van de bezoeker. Beide zijn generiek: de dataset-bron vult ze met een nabootsing, het stelsel met gemeten getallen, en de render-laag hoort het verschil niet te kunnen zien; de volgorde van registreren is de voorrang en de dataset-bron staat achteraan als vangnet. Één weg naar het scherm: `toonBerichten()` filtert de berichten, neemt het paginavenster en bouwt die rijen. De inbox rendert geen rijen vooraf: de tbody begint leeg en wordt gevuld uit de datalaag, net als het archief en de prullenbak. Dat is een bewuste afwijking van principe 5 (HTML waar het kan): dit prototype simuleert een federatief stelsel dat berichten bij verschillende organisaties ophaalt, en die zijn er per definitie niet synchroon. Vooraf een volledige lijst renderen zou beweren dat ze dat wél waren — precies wat de voortgangsanimatie tegenspreekt. Wie geen JavaScript heeft krijgt een `