Changeset 7cfe35a in Klonkt for src/routes/guardian.js


Ignore:
Timestamp:
08/07/2026 01:56:58 PM (5 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
ee78e03
Parents:
fb9a8ad (diff), a29c8c8 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent.
git-author:
proef <proef@…> (08/07/2026 01:54:50 PM)
git-committer:
Robin <roboburr@…> (08/07/2026 01:56:58 PM)
Message:

Geen inline scripts meer in de pagina's (shaer-bqr)

De CSP staat op default-src 'none' met strict-dynamic en een nonce per
verzoek. Bij een htmx-navigatie komt er nieuwe inhoud binnen zonder nieuwe
nonce, dus een inline script in geswapte inhoud werd geweigerd -- zichtbaar
als een geblokkeerde reply-editor op /messages.

Alle 24 pagina's en partials gaan nu naar een module onder assets/js/mod/.
Een bootstrap in de shell leest data-js van de body en importeert wat daar
staat, ook opnieuw na elke pagInawissel. Servergegevens gaan mee via
partials/page-data.ejs, een script met type application/json: dat wordt niet
uitgevoerd en niet opgehaald, dus script-src heeft er niets over te zeggen.

lib.js heeft esc(), omdat de modules op plekken HTML met stringplakwerk
bouwen waar de escapende EJS-tag vroeger het werk deed.

embed-player.ejs houdt zijn inline script: eigen render, eigen CSP, altijd
een volledige laadbeurt.

Co-Authored-By: Claude Opus 5 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/guardian.js

    rfb9a8ad r7cfe35a  
    6363    'gate_kind_setting', 'gate_kind_perRequest', 'gate_kind_handover',
    6464    'gate_unknown', 'gate_threshold', 'gate_threshold_unknown',
    65     'gate_irreversible', 'gate_waiting', 'gate_blocked', 'gate_propose'];
     65    'gate_irreversible', 'gate_waiting', 'gate_blocked', 'gate_propose',
     66    // Oppikken en afhandelen van een hulpvraag (shaer-lgo).
     67    'help_pick', 'help_close', 'help_picked_by', 'help_handled_by', 'help_handled_note',
     68    'help_close_ask', 'help_close_yes', 'help_just_now', 'help_hours', 'help_days'];
    6669  const s = Object.fromEntries(keys.map((k) => [k, i18nT(L, `guardian.${k}`)]));
    6770  s.wave = i18nT(L, 'guardian.wave');
     
    7780            emoji_json, actor_emoji_json, media_json, quote_json, embed_json
    7881     FROM ap_mentions WHERE slug = ? AND help_request = 1 ORDER BY created_at DESC LIMIT 50`
    79   ).all(site.slug).map((h) => ({
     82  ).all(site.slug);
     83  // De gedeelde staat in EEN query (shaer-lgo): wie er al op af is en of het is
     84  // afgesloten. Per kaart vragen zou hier een N+1 opleveren, en dit is precies
     85  // het scherm dat een guardian in een haast openslaat.
     86  const helpStaat = Guardianship.help.statusFor(help.map((h) => h.object_uri));
     87  const helpItems = help.map((h) => ({
    8088    ...h,
     89    // Bij twijfel OPEN. Een hulpvraag die er afgehandeld uitziet terwijl hij dat
     90    // niet is, is de gevaarlijke fout -- niet andersom.
     91    state: helpStaat.get(h.object_uri) || { open: true, pickedUpBy: [], handled: null, ageMs: null },
    8192    // The dashboard is built in the browser, so it gets the body finished: the
    8293    // same partial de Krant and Berichten use. A 🛟 often carries a screenshot
     
    123134      id: r.id, ward: r.ward_uri, proposer: r.proposer, feature: r.feature, value: !!r.value,
    124135    })),
    125     help,
     136    help: helpItems,
    126137    strings: uiStrings(L),
    127138  };
     
    336347  if (!r) return res.status(502).json({ error: 'delivery' });
    337348  res.json({ ok: true, delivered: r.delivered });
     349});
     350
     351// ── Een hulpvraag oppikken of afsluiten (shaer-lgo) ───────────────
     352// Gaat naar de WARD en naar de MEDE-GUARDIANS. De ward hoort te weten dat er
     353// iemand komt -- dat is de helft van de gerustheid -- en de anderen dat het
     354// loopt, zodat niemand denkt dat de ander het al doet.
     355//
     356// OPPIKKEN mag stapelen: twee mensen die tegelijk reageren is geen probleem.
     357// AFSLUITEN kent geen terugdraai; leeft de vraag nog, dan wordt hij opnieuw
     358// gesteld. De stevige bevestiging zit in de client, net als bij het loslaten van
     359// een ward: nooit een window.confirm.
     360router.post('/api/help/:kind', requireAuth, express.json({ limit: '2kb' }), async (req, res) => {
     361  const site = siteForUser(req);
     362  if (!site) return res.status(404).json({ error: 'no_site' });
     363  const kind = req.params.kind === 'handled' ? 'handled' : 'pickup';
     364  const noteUri = String(req.body?.note || '').trim();
     365  const wardUri = String(req.body?.ward || '').trim();
     366  if (!noteUri || !/^https?:\/\//i.test(noteUri)) return res.status(400).json({ error: 'no_note' });
     367  // Alleen over een hulpvraag van een kind dat je echt bewaakt.
     368  const isWard = Guardianship.listWards(site.slug).some((w) => w.other_uri === wardUri);
     369  if (!isWard) return res.status(403).json({ error: 'not_your_ward' });
     370
     371  const me = AP.actorId((process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, ''), site.slug);
     372  // Onze eigen kopie meteen, zonder op bezorging te wachten: het scherm van
     373  // degene die klikt hoort niet te liegen omdat een andere server traag is.
     374  Guardianship.help.record(noteUri, me, kind, null);
     375
     376  const anderen = Guardianship.listGuardians(wardUri.replace(/.*\/ap\/users\//, '')) || [];
     377  const ontvangers = [wardUri, ...anderen.map((g) => g.other_uri)].filter((u) => u && u !== me);
     378  const r = await AP.deliverDirectNote(site, {
     379    recipients: ontvangers,
     380    text: kind === 'handled' ? 'Deze hulpvraag is afgehandeld.' : 'Ik kijk hiernaar.',
     381    helpMark: { kind, noteUri },
     382  }).catch(() => null);
     383  // Bezorging kan mislukken; de eigen staat staat er dan toch. Dat melden we,
     384  // want "verstuurd" zeggen terwijl het niet aankwam is hier het ergste soort
     385  // stilte.
     386  res.json({ ok: true, delivered: r ? r.delivered : 0, recipients: ontvangers.length });
    338387});
    339388
Note: See TracChangeset for help on using the changeset viewer.