1/** 2 * Meldungen bei GEOEFFNETER Seite. 3 * 4 * Das Gegenstueck zu push-sw.js. Beide zeigen dasselbe an, nur unter 5 * verschiedenen Bedingungen, und genau das ist die Abgrenzung: 6 * 7 * diese Datei der Empfaenger hat eine Seite offen â die Meldung kommt 8 * ueber die Vermittlung und ist sofort da 9 * push-sw.js er hat keine â die Meldung kommt mit Nachlauf ueber den 10 * Zustelldienst des Browserherstellers 11 * 12 * Dass es nie beides zugleich gibt, entscheidet der Server: Der Arbeitslauf 13 * ueberspringt jeden, der an der Vermittlung haengt. Wer âauch wenn ich 14 * woanders angemeldet bin" gesetzt hat, dreht das um â dann sendet der Server, 15 * und DIESE Datei schweigt (siehe darfEinblenden()). Ohne die zweite Haelfte 16 * kaemen zwei Meldungen fuer eine Sache auf demselben Geraet. 17 * 18 * Fuer JEDEN Angemeldeten geladen, nicht nur fuer Chat-Berechtigte: Der Chat 19 * ist nur der erste Bereich, der melde() benutzt. 20 * 21 * =================================================================== 22 * WARUM DER CHAT HIER NICHT AUFTAUCHT 23 * =================================================================== 24 * 25 * Er hat seinen eigenen Weg an offene Seiten (Hub::nachricht auf `chat:<cid>`) 26 * und blendet in comms-hub.js selbst ein â weil er dort weiss, welche 27 * Unterhaltung gerade offen vor einem liegt. Das kann diese Datei nicht 28 * nachbilden: Alle Unterhaltungen liegen unter derselben Adresse, ein 29 * Vergleich der Pfade wuerde also den ganzen Chat stummschalten, sobald die 30 * App offen ist. Deshalb ruft send.php melde() mit `sofort: false` auf. 31 */ 32 33(function () { 34 'use strict'; 35 36 if (!window.indiceComm) return; 37 38 // ------------------------------------------------------------------ 39 // Persoenliche Wuensche. Bis sie da sind, gilt: alles an â eine langsame 40 // oder fehlgeschlagene Abfrage darf niemanden stumm schalten. 41 // ------------------------------------------------------------------ 42 var wuensche = { ton: true, popup: true, auchWennOffen: false, bereiche: {} }; 43 44 /** 45 * Nichts wird entschieden, bevor die Wuensche da sind. 46 * 47 * Ein Vorgabewert waere hier keine Loesung, sondern ein Fehler mit 48 * Zeitfenster: Er gaelte nur in der ersten Sekunde nach dem Laden, und 49 * einen Fehler, den es nur in der ersten Sekunde gibt, findet niemand. Also 50 * WARTET die Entscheidung, statt zu raten â verloren geht dabei nichts, die 51 * Meldung wird bloss um Millisekunden spaeter gefaellt. 52 */ 53 var bereit = (function () { 54 if (!window.indicePush || typeof window.indicePush.einstellungen !== 'function') { 55 return Promise.resolve(); // ohne Web Push kann niemand doppelt melden 56 } 57 58 return window.indicePush.einstellungen().then(function (a) { 59 if (a && a.success && a.result) { 60 wuensche.ton = a.result.ton !== false; 61 wuensche.popup = a.result.popup !== false; 62 wuensche.auchWennOffen = a.result.auch_wenn_offen === true; 63 wuensche.bereiche = a.result.bereiche || {}; 64 } 65 }).catch(function () {}); 66 })(); 67 68 function bereichErlaubt(bereich) { 69 return wuensche.bereiche[bereich] !== false; 70 } 71 72 function darfEinblenden() { 73 return wuensche.popup 74 && !wuensche.auchWennOffen 75 && ('Notification' in window) 76 && Notification.permission === 'granted'; 77 } 78 79 // ------------------------------------------------------------------ 80 // Eine Meldung je AUSLOESUNG, auch bei mehreren Reitern. 81 // 82 // Wer den Auftritt mehrfach offen hat, hat auch diese Datei mehrfach 83 // laufen; jeder Reiter bekommt dasselbe Ereignis. Die Absprache laeuft 84 // ueber localStorage: Jeder traegt sich mit einem eigenen Kennzeichen ein 85 // und sieht kurz darauf nach, ob es noch dasteht â der zuletzt Schreibende 86 // gewinnt, alle anderen schweigen. Ein blosses âsteht schon etwas da?" 87 // waere ein Wettlauf; das Nachsehen entscheidet ihn. 88 // 89 // Verglichen wird `nr`, die Kennung der Ausloesung â NICHT die Marke. Die 90 // Marke ist die der SACHE und wiederholt sich absichtlich: Zwei Nachrichten 91 // in derselben Unterhaltung tragen dieselbe. Wer danach abspricht, zeigt 92 // die zweite nicht mehr an, und zwar nur innerhalb von acht Sekunden â ein 93 // Ausfall, den man beim Nachstellen von Hand nie trifft. 94 // ------------------------------------------------------------------ 95 var SCHLUESSEL = 'indice_meldung'; 96 97 function nurEinReiter(sache, dann) { 98 var kennung = String(Date.now()) + '-' + Math.random().toString(36).slice(2); 99 100 try { 101 var roh = window.localStorage.getItem(SCHLUESSEL); 102 var da = roh ? JSON.parse(roh) : null; 103 104 if (da && da.sache === sache && (Date.now() - da.zeit) < 8000 && da.kennung !== kennung) { 105 return; 106 } 107 108 window.localStorage.setItem(SCHLUESSEL, JSON.stringify({ 109 sache: sache, zeit: Date.now(), kennung: kennung 110 })); 111 112 setTimeout(function () { 113 try { 114 var jetzt = JSON.parse(window.localStorage.getItem(SCHLUESSEL) || '{}'); 115 if (jetzt.kennung === kennung) dann(); 116 } catch (e) { dann(); } 117 }, 80); 118 } catch (e) { 119 // Kein localStorage (privates Fenster, gesperrte Seitendaten): 120 // lieber doppelt melden als gar nicht. 121 dann(); 122 } 123 } 124 125 /** Kurzer Zweiklang. Absichtlich ohne Datei â ein Ton, kein Download. */ 126 function ton() { 127 try { 128 var AC = window.AudioContext || window.webkitAudioContext; 129 if (!AC) return; 130 var ctx = ton.ctx || (ton.ctx = new AC()); 131 if (ctx.state === 'suspended') return; // ohne Nutzergeste nicht erlaubt 132 133 var jetzt = ctx.currentTime; 134 var lauf = ctx.createGain(); 135 lauf.gain.setValueAtTime(0.0001, jetzt); 136 lauf.gain.exponentialRampToValueAtTime(0.08, jetzt + 0.02); 137 lauf.gain.exponentialRampToValueAtTime(0.0001, jetzt + 0.30); 138 lauf.connect(ctx.destination); 139
140 [880, 1320].forEach(function (hz, i) { 141 var o = ctx.createOscillator(); 142 o.type = 'sine'; 143 o.frequency.setValueAtTime(hz, jetzt + i * 0.12); 144 o.connect(lauf); 145 o.start(jetzt + i * 0.12); 146 o.stop(jetzt + i * 0.12 + 0.14); 147 }); 148 } catch (e) {} 149 } 150 151 /** 152 * Schaue ich gerade genau darauf? 153 * 154 * Die generische Fassung der Frage, die der Chat fuer sich selbst 155 * beantwortet: Ist diese Seite sichtbar und zeigt sie bereits den Pfad, auf 156 * den die Meldung zeigt, dann sieht der Empfaenger die Sache ohnehin. 157 * 158 * Der Vergleich geht ueber den PFAD, nicht ueber die volle Adresse: Die 159 * Sprungmarke (#c17) ist genau das, was sich noch aendern soll. 160 */ 161 function schaueDarauf(url) { 162 var sichtbar = (typeof document.hidden !== 'undefined') ? !document.hidden : true; 163 if (!sichtbar) return false; 164 165 try { 166 var ziel = new URL(url, window.location.origin); 167 return ziel.origin === window.location.origin 168 && ziel.pathname === window.location.pathname; 169 } catch (e) { 170 return false; 171 } 172 } 173 174 function zeigen(m) { 175 try { 176 var n = new Notification(m.titel, { 177 body: m.text || '', 178 // Dieselbe Marke wie beim Push: Kommt beides an, ERSETZEN sie 179 // einander, statt sich zu stapeln. 180 tag: m.marke || (m.bereich + ':neu'), 181 renotify: !!m.marke, 182 icon: m.bild || undefined 183 }); 184 185 n.onclick = function () { 186 window.focus(); 187 n.close(); 188 if (!m.url) return; 189 try { 190 var ziel = new URL(m.url, window.location.origin); 191 if (ziel.pathname === window.location.pathname) { 192 if (window.location.hash !== ziel.hash) window.location.hash = ziel.hash; 193 } else { 194 window.location.href = ziel.href; 195 } 196 } catch (e) {} 197 }; 198 } catch (e) {} 199 } 200 201 window.indiceComm.on('meldung', function (m) { 202 if (!m || !m.titel) return; 203 204 // IMMER zuerst: Eine offene App soll auf die Sache reagieren koennen â 205 // eine Liste nachladen, einen Zaehler hochsetzen â unabhaengig davon, 206 // ob ein Fenster erscheint. Das ist der Teil, den jede App selbst 207 // ausbaut; das Fenster ist nur die Grundausstattung. 208 try { 209 window.dispatchEvent(new CustomEvent('indice:meldung', { detail: m })); 210 } catch (e) {} 211 212 // Kennung DIESER Ausloesung. Sie kommt vom Server (melde()); fehlt sie, 213 // taugt der Wortlaut als Ersatz â zwei wortgleiche Meldungen kurz 214 // hintereinander sind selten, und der Browser wuerde sie ueber die 215 // Marke ohnehin ersetzen. 216 var ausloesung = m.nr || ((m.bereich || '?') + ':' + (m.marke || '') + ':' + (m.text || '')); 217 218 bereit.then(function () { 219 if (!bereichErlaubt(m.bereich)) return; 220 if (schaueDarauf(m.url)) return; 221 222 nurEinReiter(ausloesung, function () { 223 if (wuensche.ton) ton(); 224 if (darfEinblenden()) zeigen(m); 225 }); 226 }); 227 }); 228 229 // Den Tonkanal bei der ersten beliebigen Geste entsperren. Ohne das bleibt 230 // die erste Meldung stumm, und zwar ausgerechnet auf einer Seite, die man 231 // gerade erst geoeffnet hat. 232 ['click', 'keydown', 'touchstart'].forEach(function (art) { 233 document.addEventListener(art, function los() { 234 document.removeEventListener(art, los, true); 235 try { 236 var AC = window.AudioContext || window.webkitAudioContext; 237 if (!AC) return; 238 var ctx = ton.ctx || (ton.ctx = new AC()); 239 if (ctx.state === 'suspended') ctx.resume(); 240 } catch (e) {} 241 }, true); 242 }); 243})();
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.