| 1 | // Pushmeldingen in beheer (pages/admin-push.ejs) -- verplaatst uit inline script, shaer-bqr.
|
|---|
| 2 | //
|
|---|
| 3 | // Inline script in een pagina wordt door de CSP geweigerd zodra je die pagina via
|
|---|
| 4 | // een link BINNEN de site opent: de nonce rouleert per verzoek (shaer-0i6). Dit
|
|---|
| 5 | // bestand wordt door de bootstrap in shell.ejs geladen en heeft dat probleem niet.
|
|---|
| 6 | //
|
|---|
| 7 | // Alles hier hoort GEDELEGEERD te luisteren (op document, niet op een element dat
|
|---|
| 8 | // er nu staat) en tegen een tweede aanroep te kunnen.
|
|---|
| 9 |
|
|---|
| 10 | // Element-bedrading leeft zo lang als de pagina; de bootstrap roept init()
|
|---|
| 11 | // aan bij elke paginawissel waarop deze module actief is (shaer-5s1).
|
|---|
| 12 | export function init() { run(); }
|
|---|
| 13 |
|
|---|
| 14 | function run() {
|
|---|
| 15 | (function () {
|
|---|
| 16 | var cfg = JSON.parse(document.getElementById('np-data').textContent);
|
|---|
| 17 | var elState = document.getElementById('np-state');
|
|---|
| 18 | var btnOn = document.getElementById('np-on'), btnOff = document.getElementById('np-off'), btnTest = document.getElementById('np-test');
|
|---|
| 19 | var alertsBox = document.getElementById('np-alerts'), savedMsg = document.getElementById('np-saved');
|
|---|
| 20 | var currentEndpoint = null;
|
|---|
| 21 |
|
|---|
| 22 | var isIos = /iPad|iPhone|iPod/.test(navigator.userAgent);
|
|---|
| 23 | var standalone = window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true;
|
|---|
| 24 | if (isIos && !standalone) document.getElementById('np-ios-hint').hidden = false;
|
|---|
| 25 |
|
|---|
| 26 | if (!('serviceWorker' in navigator) || !('PushManager' in window) || !('Notification' in window)) {
|
|---|
| 27 | document.getElementById('np-unsupported').hidden = false;
|
|---|
| 28 | elState.textContent = cfg.i18n.unsupported;
|
|---|
| 29 | return;
|
|---|
| 30 | }
|
|---|
| 31 |
|
|---|
| 32 | function b64ToU8(s) {
|
|---|
| 33 | var pad = '='.repeat((4 - (s.length % 4)) % 4);
|
|---|
| 34 | var raw = atob((s + pad).replace(/-/g, '+').replace(/_/g, '/'));
|
|---|
| 35 | var out = new Uint8Array(raw.length);
|
|---|
| 36 | for (var i = 0; i < raw.length; i++) out[i] = raw.charCodeAt(i);
|
|---|
| 37 | return out;
|
|---|
| 38 | }
|
|---|
| 39 | function post(url, body) {
|
|---|
| 40 | return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body || {}) });
|
|---|
| 41 | }
|
|---|
| 42 | function deviceLabel() {
|
|---|
| 43 | var ua = navigator.userAgent;
|
|---|
| 44 | var browser = /Firefox\//.test(ua) ? 'Firefox' : /Edg\//.test(ua) ? 'Edge' : /Chrome\//.test(ua) ? 'Chrome' : /Safari\//.test(ua) ? 'Safari' : 'Browser';
|
|---|
| 45 | var os = /Android/.test(ua) ? 'Android' : /iPad|iPhone|iPod/.test(ua) ? 'iOS' : /Mac/.test(ua) ? 'macOS' : /Win/.test(ua) ? 'Windows' : /Linux/.test(ua) ? 'Linux' : '';
|
|---|
| 46 | return (browser + (os ? ' op ' + os : ''));
|
|---|
| 47 | }
|
|---|
| 48 | function readAlertBoxes() {
|
|---|
| 49 | var out = {};
|
|---|
| 50 | alertsBox.querySelectorAll('input[data-alert]').forEach(function (cb) { out[cb.getAttribute('data-alert')] = cb.checked ? 1 : 0; });
|
|---|
| 51 | return out;
|
|---|
| 52 | }
|
|---|
| 53 | function setAlertBoxes(alerts) {
|
|---|
| 54 | alertsBox.querySelectorAll('input[data-alert]').forEach(function (cb) {
|
|---|
| 55 | cb.checked = !!alerts[cb.getAttribute('data-alert')];
|
|---|
| 56 | });
|
|---|
| 57 | }
|
|---|
| 58 |
|
|---|
| 59 | function render(sub) {
|
|---|
| 60 | currentEndpoint = sub ? sub.endpoint : null;
|
|---|
| 61 | elState.textContent = sub ? cfg.i18n.on : (Notification.permission === 'denied' ? cfg.i18n.denied : cfg.i18n.off);
|
|---|
| 62 | btnOn.hidden = !!sub || Notification.permission === 'denied';
|
|---|
| 63 | btnOff.hidden = !sub;
|
|---|
| 64 | btnTest.hidden = !sub;
|
|---|
| 65 | alertsBox.hidden = !sub;
|
|---|
| 66 | }
|
|---|
| 67 |
|
|---|
| 68 | navigator.serviceWorker.ready.then(function (reg) {
|
|---|
| 69 | return reg.pushManager.getSubscription();
|
|---|
| 70 | }).then(function (sub) {
|
|---|
| 71 | // Show this device's SAVED prefs when we know them, defaults otherwise.
|
|---|
| 72 | setAlertBoxes((sub && cfg.saved[sub.endpoint]) ? Object.assign({}, cfg.alerts, cfg.saved[sub.endpoint]) : cfg.alerts);
|
|---|
| 73 | render(sub);
|
|---|
| 74 | }).catch(function () { elState.textContent = cfg.i18n.unknown; });
|
|---|
| 75 |
|
|---|
| 76 | btnOn.addEventListener('click', function () {
|
|---|
| 77 | btnOn.disabled = true;
|
|---|
| 78 | Notification.requestPermission().then(function (perm) {
|
|---|
| 79 | if (perm !== 'granted') { btnOn.disabled = false; render(null); return; }
|
|---|
| 80 | navigator.serviceWorker.ready.then(function (reg) {
|
|---|
| 81 | return reg.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: b64ToU8(cfg.vapid) });
|
|---|
| 82 | }).then(function (sub) {
|
|---|
| 83 | return post('/push/subscribe', { subscription: sub.toJSON(), alerts: readAlertBoxes(), uaLabel: deviceLabel() })
|
|---|
| 84 | .then(function (r) { if (!r.ok) throw new Error('subscribe failed'); render(sub); location.reload(); });
|
|---|
| 85 | }).catch(function () { btnOn.disabled = false; elState.textContent = cfg.i18n.failed; });
|
|---|
| 86 | });
|
|---|
| 87 | });
|
|---|
| 88 |
|
|---|
| 89 | btnOff.addEventListener('click', function () {
|
|---|
| 90 | navigator.serviceWorker.ready.then(function (reg) { return reg.pushManager.getSubscription(); }).then(function (sub) {
|
|---|
| 91 | if (!sub) { render(null); return; }
|
|---|
| 92 | var ep = sub.endpoint;
|
|---|
| 93 | sub.unsubscribe().then(function () { return post('/push/unsubscribe', { endpoint: ep }); })
|
|---|
| 94 | .then(function () { location.reload(); });
|
|---|
| 95 | });
|
|---|
| 96 | });
|
|---|
| 97 |
|
|---|
| 98 | btnTest.addEventListener('click', function () {
|
|---|
| 99 | btnTest.disabled = true;
|
|---|
| 100 | post('/push/test').then(function () { setTimeout(function () { btnTest.disabled = false; }, 2000); });
|
|---|
| 101 | });
|
|---|
| 102 |
|
|---|
| 103 | alertsBox.addEventListener('change', function () {
|
|---|
| 104 | if (!currentEndpoint) return;
|
|---|
| 105 | post('/push/alerts', { endpoint: currentEndpoint, alerts: readAlertBoxes() }).then(function (r) {
|
|---|
| 106 | if (r.ok) { savedMsg.hidden = false; setTimeout(function () { savedMsg.hidden = true; }, 1500); }
|
|---|
| 107 | });
|
|---|
| 108 | });
|
|---|
| 109 |
|
|---|
| 110 | document.querySelectorAll('.np-remove').forEach(function (btn) {
|
|---|
| 111 | btn.addEventListener('click', function () {
|
|---|
| 112 | post('/push/unsubscribe', { endpoint: btn.getAttribute('data-endpoint') }).then(function () { location.reload(); });
|
|---|
| 113 | });
|
|---|
| 114 | });
|
|---|
| 115 | })();
|
|---|
| 116 | }
|
|---|