source: Klonkt/src/assets/js/mod/lib.js@ 59f404a

main
Last change on this file since 59f404a was 59f404a, checked in by Robin <roboburr@…>, 4 days ago

De laatste twee scripts uit gewisselde inhoud (shaer-0i6)

shaer-bqr haalde de eigen logica van paid-gate en paid-passkey al uit de
pagina naar een module, maar liet de WebAuthn-bibliotheek als losse tag in
de body staan. Beide pagina's gaan via renderPage, dus bij een navigatie
binnen de site komen ze als fragment binnen -- met de nonce van dat
verzoek, die het al geladen document niet kent. Precies de vorm die Bart
destijds gemeld zag.

Het gevolg was stil: de module laadde wel, vond window.SimpleWebAuthn-
Browser niet, en viel terug op "geen WebAuthn hier". De passkey-knop deed
niets; de ontgrendelknop verdween. Alleen na een harde herlading werkte
het, en dat is precies waarom dit lang onzichtbaar bleef.

De modules halen de bibliotheek nu zelf op met loadWebAuthn() in lib.js.
Een dynamische import vanuit een module die de shell met een geldige nonce
startte is toegestaan -- daar is strict-dynamic voor. De UMD-bundel zet
zichzelf zonder CommonJS of AMD op globalThis, dus de global staat er na
afloop. init() wacht erop voordat hij bedraadt, anders zet de terugval de
knop uit terwijl er niets aan de hand is.

Verder nagelopen wat nog meer een scripttag draagt: vijf views hebben er
een van type application/json (een datablok, dat voert de browser nooit
uit) en twee gaan via res.render in plaats van renderPage -- losse
documenten die htmx nooit inwisselt. Die staan als uitzondering in de
nieuwe toets, met een tweede toets die controleert dat ze nog bestaan,
zodat een hernoeming de uitzondering niet stilletjes uitholt.

MOD_V naar 63: er wijzigde iets in mod/.

  • Property mode set to 100644
File size: 3.7 KB
Line 
1// Gedeeld gereedschap voor de pagina-modules (shaer-bqr).
2
3/**
4 * De servergegevens van deze pagina.
5 *
6 * Een module is een statisch bestand, dus er kan geen EJS in. Wat het script van
7 * de server nodig heeft -- vertalingen, ids, instellingen -- zet de pagina in
8 * een <script type="application/json"> via partials/page-data.ejs, en dit leest
9 * het terug.
10 *
11 * ELKE KEER OPNIEUW LEZEN en niet eenmalig onthouden: bij een htmx-navigatie
12 * wisselt de inhoud terwijl de module blijft leven, dus een gegevensblok dat je
13 * bij het laden vasthoudt is een pagina later verouderd.
14 *
15 * Nooit een uitzondering: een module die valt over ontbrekende gegevens neemt de
16 * hele pagina mee. Wat er niet is, is een leeg object.
17 */
18export function pageData() {
19 try {
20 var el = document.querySelector('script[type="application/json"][data-page-data]');
21 return el ? (JSON.parse(el.textContent) || {}) : {};
22 } catch (e) {
23 return {};
24 }
25}
26
27/**
28 * Een enkele waarde, met een terugval. Scheelt in elke module hetzelfde
29 * gedoe met ontbrekende sleutels.
30 */
31export function pageValue(key, fallback) {
32 var d = pageData();
33 return (d && Object.prototype.hasOwnProperty.call(d, key)) ? d[key] : fallback;
34}
35
36/**
37 * Listeners op document en window overleven een paginawissel; de pagina zelf
38 * niet. Een module die per navigatie opnieuw start (export init) registreert
39 * zulke listeners hier, en veegt bij elke start de vorige lichting weg --
40 * anders stapelen ze, elk met een closure naar elementen die al weg zijn.
41 *
42 * Listeners op ELEMENTEN hoeven hier niet doorheen: die sterven met hun
43 * element bij de swap. Alleen wat het document overleeft, moet geveegd.
44 */
45export function makeSweeper() {
46 let list = [];
47 return {
48 on(target, ev, fn, opts) { target.addEventListener(ev, fn, opts); list.push([target, ev, fn, opts]); },
49 sweep() { for (const [t, ev, fn, opts] of list) t.removeEventListener(ev, fn, opts); list = []; },
50 };
51}
52
53/**
54 * Een tekst veilig in HTML zetten.
55 *
56 * De modules bouwen op sommige plekken HTML met stringplakwerk. Vroeger ging een
57 * vertaling daar door de escapende EJS-tag heen; nu niet meer. Zonder deze functie
58 * breekt een apostrof in een vertaling het attribuut af waar hij in staat -- het
59 * soort fout dat pas in een andere taal opvalt.
60 */
61export function esc(s) {
62 return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
63 return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
64 });
65}
66
67/**
68 * De WebAuthn-bibliotheek erbij halen, vanuit JavaScript (shaer-0i6).
69 *
70 * Hij stond als `<script src>` in paid-gate.ejs en paid-passkey.ejs, en dat
71 * werkt alleen bij een volledige laadbeurt. Kom je op zo'n pagina via een link
72 * BINNEN de site, dan is het een htmx-fragment met de nonce van een ANDER
73 * verzoek, en weigert de CSP hem. Het gevolg was stil: de module laadde wel,
74 * vond `window.SimpleWebAuthnBrowser` niet, en de knop deed niets -- op het
75 * passkey- en betaalpad.
76 *
77 * Een dynamische import vanuit deze module mag wel: de shell startte hem met
78 * een geldige nonce, en `strict-dynamic` bestaat er juist voor dat vertrouwde
79 * scripts verder mogen laden. Het bestand is een UMD-bundel; zonder CommonJS
80 * of AMD zet die zichzelf op globalThis, dus na afloop staat de global er.
81 *
82 * Faalt het laden, dan geeft dit undefined terug en niet een fout: beide
83 * aanroepers hebben al een nette terugval voor "geen WebAuthn hier".
84 */
85export async function loadWebAuthn() {
86 if (window.SimpleWebAuthnBrowser) return window.SimpleWebAuthnBrowser;
87 try { await import('/assets/vendor/simplewebauthn-browser.umd.min.js'); }
88 catch (e) { console.warn('[paid] WebAuthn-bibliotheek laadt niet:', e && e.message); }
89 return window.SimpleWebAuthnBrowser;
90}
Note: See TracBrowser for help on using the repository browser.