PageSourceSearch

https://bpmbezwaartje.nl/assets/klantlink.js?v=f9e602f565

js bpmbezwaartje.nl collected 2026-09-24 14:05:18 UTC 12,348 bytes, 189 lines download raw bytes

1/* DE KLANTLINK (Gerd 2026-09-22 16:1x): "ik wil een bezwaar kunnen maken en dan met een link naar een klant kunnen appen via
2 * WhatsApp; de klant krijgt de keuze voor zelf printen en op de post, of via de postservice, betaalt via Mollie, en Postbode.nu
3 * doet zijn ding."
4 *
5 * DE MAKER (bezwaar-maken.html, eindpagina): "Stuur naar de klant". De browser versleutelt de brief (AES-GCM 256, WebCrypto)
6 * en zet alleen het versleutelde blok op de server (api/klantlink.php). De sleutel staat in de link achter het hekje; die gaat
7 * mee in het WhatsApp-bericht en nooit naar onze server. Naam en adres van de klant zijn daardoor op de server niet te lezen.
8 *
9 * DE KLANT (klant.html): leest zijn bezwaar, tekent met zijn vinger, en kiest:
10 *   - zelf printen en posten: de PDF met zijn handtekening;
11 *   - de verzendservice: hetzelfde blok als op de eindpagina (assets/verzend-ui.js): e-mailadres, Mollie, daarna Postbode.nu.
12 * De handtekening van de maker gaat niet mee: tekenen doet degene wiens naam onder de brief staat.
13 */
14(function (root) {
15  'use strict';
16
17  function doc() { return typeof document !== 'undefined' ? document : null; }
18  function el(tag, a, kids) {
19    var e = doc().createElement(tag);
20    Object.keys(a || {}).forEach(function (k) {
21      if (k === 'text') e.textContent = a[k];
22      else if (k === 'html') e.innerHTML = a[k];
23      else if (k === 'onclick') e.addEventListener('click', a[k]);
24      else e.setAttribute(k, a[k]);
25    });
26    (kids || []).forEach(function (c) { if (c) e.appendChild(c); });
27    return e;
28  }
29  function apiBasis() { return (typeof root.rzIsNative === 'function' && root.rzIsNative() && root.BZT_API) ? root.BZT_API : ''; }
30  function siteBasis() {
31    var o = (root.location && root.location.origin) || '';
32    return /^https?:\/\//.test(o) && !/localhost|127\.0\.0\.1/.test(o) ? o : (root.BZT_SITE || 'https://bpmbezwaartje.nl');
33  }
34  function eindpunt() { return apiBasis() + '/api/klantlink.php'; }
35
36  /* ---------------------------------------------------------------- versleutelen */
37
38  function b64(buf) { var s = '', a = new Uint8Array(buf); for (var i = 0; i < a.length; i++) s += String.fromCharCode(a[i]); return btoa(s); }
39  function unb64(s) { var b = atob(s), a = new Uint8Array(b.length); for (var i = 0; i < b.length; i++) a[i] = b.charCodeAt(i); return a; }
40  function b64url(buf) { return b64(buf).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); }
41  function unb64url(s) { s = String(s).replace(/-/g, '+').replace(/_/g, '/'); while (s.length % 4) s += '='; return unb64(s); }
42
43  function versleutel(obj) {
44    var c = root.crypto.subtle;
45    var iv = root.crypto.getRandomValues(new Uint8Array(12));
46    var sleutel;
47    return c.generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt']).then(function (k) {
48      sleutel = k;
49      return c.encrypt({ name: 'AES-GCM', iv: iv }, k, new TextEncoder().encode(JSON.stringify(obj)));
50    }).then(function (ct) {
51      var alles = new Uint8Array(12 + ct.byteLength);
52      alles.set(iv, 0); alles.set(new Uint8Array(ct), 12);
53      return c.exportKey('raw', sleutel).then(function (raw) { return { data: b64(alles), sleutel: b64url(raw) }; });
54    });
55  }
56  function ontsleutel(data, sleutelUrl) {
57    var c = root.crypto.subtle;
58    var alles = unb64(data);
59    return c.importKey('raw', unb64url(sleutelUrl), { name: 'AES-GCM' }, false, ['decrypt']).then(function (k) {
60      return c.decrypt({ name: 'AES-GCM', iv: alles.slice(0, 12) }, k, alles.slice(12));
61    }).then(function (pt) { return JSON.parse(new TextDecoder().decode(pt)); });
62  }
63
64  /* ---------------------------------------------------------------- de maker */
65
66  /* opties.pakket(): { tekst, opties (voor bztMakeLetterPDF, zonder handtekening), bestandsnaam, deadlineIso, magGewoon, naam } */
67  function maker(doosId, opties) {
68    var d = doc();
69    var doos = d && d.getElementById(doosId);
70    if (!doos || d.getElementById(doosId + '-kl')) return false;
71    var stand = el('p', { class: 'sr-hulp sr-tel-stand', 'aria-live': 'polite' });
72    var paneel = el('div', { class: 'sr-tel-paneel', hidden: 'hidden' });
73    var knop = el('button', { type: 'button', class: 'sr-knop sr-knop-2 sr-klein', id: doosId + '-klknop', text: 'Stuur naar de klant (WhatsApp)' });
74    doos.appendChild(el('div', { class: 'sr-tel', id: doosId + '-kl' }, [
75      el('p', { class: 'sr-hulp', text: 'Maakt u dit bezwaar voor iemand anders? Stuur het hem of haar als link. Diegene leest het op de telefoon, tekent, en kiest zelf: printen en posten, of laten versturen via de postservice (betalen met iDEAL).' }),
76      knop, paneel, stand]));
77
78    knop.addEventListener('click', function () {
79      var pakket = opties && opties.pakket ? opties.pakket() : null;
80      if (!pakket || !pakket.tekst) { stand.textContent = 'Er is nog geen brief om te versturen.'; return; }
81      if (!(root.crypto && root.crypto.subtle)) { stand.textContent = 'Deze browser kan de link niet veilig maken. Gebruik een recente browser.'; return; }
82      knop.disabled = true; stand.textContent = 'De link wordt gemaakt...';
83      versleutel(pakket).then(function (v) {
84        return fetch(eindpunt(), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ actie: 'zet', data: v.data }), cache: 'no-store' })
85          .then(function (r) { return r.json(); })
86          .then(function (j) { return { j: j, sleutel: v.sleutel }; });
87      }).then(function (u) {
88        knop.disabled = false;
89        if (!u.j || !u.j.ok || !/^[0-9a-f]{32}$/.test(u.j.id || '')) { stand.textContent = 'Dat lukte nu niet. Probeer het zo nog eens.'; return; }
90        var link = (opties.linkBasis || siteBasis()) + '/klant.html#id=' + u.j.id + '&k=' + u.sleutel;
91        while (paneel.firstChild) paneel.removeChild(paneel.firstChild);
92        var bericht = 'Uw bezwaar staat klaar. Lees het, zet uw handtekening en kies hoe het verstuurd wordt: ' + link;
93        var veld = el('input', { type: 'text', class: 'sr-tel-link', readonly: 'readonly', value: link, 'aria-label': 'Link voor de klant' });
94        var kopie = el('button', { type: 'button', class: 'sr-knop sr-knop-2 sr-klein', text: 'Kopieer de link', onclick: function () {
95          veld.select();
96          var gelukt = function () { kopie.textContent = 'Gekopieerd'; };
97          if (root.navigator && root.navigator.clipboard) root.navigator.clipboard.writeText(link).then(gelukt, function () { try { d.execCommand('copy'); gelukt(); } catch (e) { } });
98          else { try { d.execCommand('copy'); gelukt(); } catch (e) { } }
99        } });
100        paneel.appendChild(el('div', { class: 'sr-knoppen sr-hdt-knoppen' }, [
101          el('a', { class: 'sr-knop sr-klein', id: doosId + '-klwa', target: '_blank', rel: 'noopener', href: 'https://wa.me/?text=' + encodeURIComponent(bericht), text: 'Open WhatsApp met de link' }),
102          kopie]));
103        paneel.appendChild(veld);
104        paneel.appendChild(el('p', { class: 'sr-hulp', text: 'De link werkt 14 dagen. Wie de link heeft, kan het bezwaar lezen: stuur hem alleen naar de klant zelf.' }));
105        paneel.hidden = false;
106        stand.textContent = '';
107      }, function () { knop.disabled = false; stand.textContent = 'Dat lukte nu niet. Probeer het zo nog eens.'; });
108    });
109    return true;
110  }
111
112  /* ---------------------------------------------------------------- de klant */
113
114  function klant(doosId) {
115    var d = doc();
116    var doos = d && d.getElementById(doosId);
117    if (!doos) return false;
118    var h = (root.location && root.location.hash) || '';
119    var id = (/(?:^|[#&])id=([0-9a-f]{32})/.exec(h) || [])[1] || '';
120    var k = (/(?:^|[#&])k=([A-Za-z0-9_-]{43})/.exec(h) || [])[1] || '';
121    /* Het kenmerk en de sleutel uit de adresbalk (bewaren of doorsturen van de pagina neemt ze niet mee). De pagina onthoudt ze
122       in dit tabblad, zodat herladen blijft werken. */
123    try {
124      if (id && k) { sessionStorage.setItem('bzt_kl', id + '|' + k); if (root.history && root.history.replaceState) root.history.replaceState(null, '', root.location.pathname); }
125      else { var o = (sessionStorage.getItem('bzt_kl') || '').split('|'); if (o.length === 2) { id = o[0]; k = o[1]; } }
126    } catch (e) { }
127    var melding = function (t, klasse) { while (doos.firstChild) doos.removeChild(doos.firstChild); doos.appendChild(el('p', { class: 'sr-melding ' + (klasse || 'sr-let'), text: t })); };
128    if (!id || !k) { melding('Deze link is niet compleet. Vraag degene die uw bezwaar maakte om de link opnieuw te sturen.'); return false; }
129    if (!(root.crypto && root.crypto.subtle)) { melding('Deze browser kan uw bezwaar niet veilig openen. Gebruik een recente browser.'); return false; }
130    melding('Uw bezwaar wordt geopend...', 'sr-ok');
131
132    fetch(eindpunt() + '?actie=haal&id=' + id, { cache: 'no-store' }).then(function (r) { return r.json(); }).then(function (j) {
133      if (!j || !j.ok || !j.data) { melding('Deze link is verlopen (een link werkt 14 dagen). Vraag degene die uw bezwaar maakte om een nieuwe.'); return; }
134      return ontsleutel(j.data, k).then(function (p) { toon(p); }, function () { melding('Deze link klopt niet (de sleutel past niet). Vraag om de link opnieuw.'); });
135    }, function () { melding('Geen verbinding. Probeer het zo nog eens.'); });
136
137    function pdf(p) {
138      var o = Object.assign({}, p.opties || {});
139      var hdt = root.bztHandtekening && root.bztHandtekening.waarde();
140      if (hdt) {
141        o.handtekening = hdt;
142        var m = (root.bztHandtekening.maat && root.bztHandtekening.maat()) || {};
143        if (m.breedte && m.hoogte) { o.handtekeningBreedte = m.breedte; o.handtekeningHoogte = m.hoogte; }
144      }
145      return root.bztMakeLetterPDF(p.tekst, o);
146    }
147
148    function toon(p) {
149      while (doos.firstChild) doos.removeChild(doos.firstChild);
150      doos.appendChild(el('h2', { text: '1. Lees uw bezwaar' }));
151      doos.appendChild(el('p', { class: 'sr-hulp', text: 'Klopt er iets niet, bel of app dan eerst degene die het voor u maakte. Teken pas als alles klopt.' }));
152      doos.appendChild(el('pre', { class: 'sr-kl-tekst', id: doosId + '-tekst', text: p.tekst }));
153      doos.appendChild(el('h2', { text: '2. Zet uw handtekening' }));
154      var vak = el('div', { id: doosId + '-hdt', class: 'sr-hdt' });
155      doos.appendChild(vak);
156      if (root.bztHandtekening) root.bztHandtekening.render(doosId + '-hdt', { el: el });
157      doos.appendChild(el('h2', { text: '3. Versturen' }));
158      var err = el('p', { class: 'sr-hulp', 'aria-live': 'polite' });
159      doos.appendChild(el('div', { class: 'sr-knoppen' }, [
160        el('button', { type: 'button', class: 'sr-knop sr-knop-2', id: doosId + '-pdf', text: 'Download de PDF (zelf printen en posten)', onclick: function () {
161          try {
162            var docu = pdf(p);
163            var blob = docu.output('blob');
164            var a = el('a', { href: URL.createObjectURL(blob), download: p.bestandsnaam || 'bezwaar.pdf' });
165            d.body.appendChild(a); a.click(); setTimeout(function () { URL.revokeObjectURL(a.href); a.remove(); }, 1000);
166            err.textContent = root.bztHandtekening && root.bztHandtekening.waarde() ? '' : 'Let op: u hebt nog niet getekend. Teken dan op het geprinte stuk.';
167          } catch (e) { err.textContent = 'De PDF kon niet worden gemaakt. Probeer het nog eens.'; }
168        } })]));
169      doos.appendChild(err);
170      var vz = el('div', { id: doosId + '-verzend', class: 'sr-verzend' });
171      doos.appendChild(vz);
172      if (typeof root.bztVerzendBlok === 'function') {
173        root.bztVerzendBlok({
174          container: doosId + '-verzend',
175          getPdfB64: function () { try { return pdf(p).output('datauristring').split(',')[1] || null; } catch (e) { return null; } },
176          getDeadlineIso: function () { return p.deadlineIso || ''; },
177          handtekeningOntbreekt: function () { return !(root.bztHandtekening && root.bztHandtekening.waarde()); },
178          magGewoon: !!p.magGewoon,
179          emailVoorstel: function () { return ''; }
180        });
181      }
182    }
183    return true;
184  }
185
186  var api = { maker: maker, klant: klant, _versleutel: versleutel, _ontsleutel: ontsleutel };
187  if (typeof module !== 'undefined' && module.exports) module.exports = api;
188  else root.bztKlantlink = api;
189})(typeof window !== 'undefined' ? window : this);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.