PageSourceSearch

https://www.wrocklage.de/js/sys/meldungen.js?1788510267

js wrocklage.de collected 2026-10-05 18:44:03 UTC 10,348 bytes, 243 lines download raw bytes

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.