source: Klonkt/src/assets/js/mod/lib.js@ 369aa60

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

Cache-busting reikt nu ook tot wat de modules zelf ophalen (shaer-724)

De bootstrap hing al ?v= aan elke module die hij zelf laadt. Twee soorten
paden ontsnapten daaraan, en /assets wordt buiten ontwikkeling een jaar
gecachet:

een import BINNEN een module is relatief, en zo\x27n specifier erft de
query niet: ./lib.js naast post.js?v=63 wordt gewoon
/assets/js/mod/lib.js. Elf modules importeren lib.js zo, en juist dat
bestand is gedeeld -- een fout erin overleefde elke MOD_V-bump.

een vendorbestand dat een module zelf ophaalt. read.js deed het goed met
?v=VENDOR_V; lib.js (mijn eigen import van gisteren) en de twee
cropper-verwijzingen in post-edit deden het niet.

Een importmap in de head lost het eerste op zonder die elf imports aan te
raken: hij vertaalt de opgeloste URL naar zijn geversioneerde vorm. MOD_V
staat daarvoor nu als EJS-variabele bovenaan de shell, zodat de importmap
en de bootstrap niet twee nummers kunnen worden. VENDOR_V staat in lib.js
en wordt door read.js en post-edit gedeeld, om dezelfde reden.

Drie toetsen die de REGEL bewaken en niet deze ene plek: elke relatieve
import tussen modules heeft een ingang in de importmap, elk vendorpad
draagt een versie, en de twee nummers zijn er een. Tegenbewijs: haal de
ingang weg of de versie eraf en precies die toets valt.

Volle suite 1248 groen.

  • Property mode set to 100644
File size: 4.4 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 */
85/**
86 * De cache-buster voor alles in /assets/vendor (shaer-724).
87 *
88 * Die map valt onder dezelfde max-age van een jaar als de rest van /assets, en
89 * een vendorbestand heeft geen versie in zijn naam. Zonder query houdt een
90 * browser die hem ooit zag hem dus een jaar vast, ook na een update van de
91 * bibliotheek.
92 *
93 * EEN nummer voor de hele map, hier, en niet drie losse per module: read.js had
94 * er al een en lib.js en post-edit.js hadden er geen, en dan is "ophogen als de
95 * bibliotheek wijzigt" een opdracht die je op drie plekken moet onthouden. Dat
96 * kost read.js wel een import van dit bestand op de leespagina; die is klein en
97 * staat na de eerste pagina in de cache.
98 */
99export const VENDOR_V = 1;
100
101export async function loadWebAuthn() {
102 if (window.SimpleWebAuthnBrowser) return window.SimpleWebAuthnBrowser;
103 try { await import(`/assets/vendor/simplewebauthn-browser.umd.min.js?v=${VENDOR_V}`); }
104 catch (e) { console.warn('[paid] WebAuthn-bibliotheek laadt niet:', e && e.message); }
105 return window.SimpleWebAuthnBrowser;
106}
Note: See TracBrowser for help on using the repository browser.