PageSourceSearch

https://includes.seguras.de/js/external-embed-guard.js?v=1790583202

js seguras.de collected 2026-10-08 01:45:33 UTC 9,430 bytes, 320 lines download raw bytes

1/**
2 * external-embed-guard.js
3 *
4 * Gegenstueck zu global/cms/ExternalEmbedGuard.php.
5 *
6 * Der PHP-Guard prueft beim Ausliefern der Seite, ob der Host einer externen
7 * Einbettung (z.B. ein Vergleichsrechner im iframe) ueberhaupt antwortet. Diese
8 * Datei deckt den zweiten Fall ab: der Host antwortet dem Server, aber der
9 * iframe laedt beim Besucher trotzdem nicht. Ohne Guard rendert der Browser
10 * dann seine eigene Timeout-Fehlerseite mitten im Seitenlayout.
11 *
12 * Ablauf pro Wrapper (div.external-embed[data-embed-guard]):
13 * - Timer starten (data-embed-timeout, Standard 15 Sekunden)
14 * - laedt der iframe rechtzeitig, passiert nichts
15 * - laeuft der Timer ab, wird der Host zuerst gegengeprueft; nur wenn auch das
16 *   fehlschlaegt, ersetzt die Hinweis-Box den iframe
17 *
18 * Die Gegenpruefung ist wichtig, weil der Guard sonst bei langsamen Verbindungen
19 * oder bei einem bereits fertig geladenen iframe faelschlich zuschlagen wuerde
20 * (das load-Event ist dann schon vorbei, bevor diese Datei ueberhaupt laeuft).
21 *
22 * Der Original-iframe steht immer in data-embed-html, damit "Erneut versuchen"
23 * ihn neu einsetzen kann. Ein falsch-negatives Pruefergebnis kostet also nie
24 * den Inhalt.
25 *
26 * Jedes Einblenden der Hinweis-Box wird an data-embed-log gemeldet
27 * (process-embed-guard-log.php), sonst bliebe genau dieser Fall unsichtbar:
28 * der Server erreicht den fremden Host ja, nur der Besucher nicht. Gemeldet
29 * wird einmal je Wrapper und Seitenaufruf; die Drosselung der Log-Eintraege
30 * passiert serverseitig.
31 */
32(function ($)
33{
34    'use strict';
35
36    if (typeof $ === 'undefined')
37    {
38        return;
39    }
40
41    var DEFAULT_TIMEOUT = 15000;
42    var PROBE_TIMEOUT = 5000;
43
44    /**
45     * Prueft, ob der fremde Host erreichbar ist.
46     *
47     * Hier ist bewusst fetch statt $.ajax im Einsatz: nur fetch kann mit
48     * mode 'no-cors' eine fremde Domain anfragen, ohne dass die CORS-Regeln
49     * greifen. $.ajax wuerde bei jeder fremden Domain einen Fehler melden -
50     * ununterscheidbar davon, dass der Server wirklich tot ist. Die opaque
51     * Response ist nicht lesbar, aber genau das reicht: es geht nur um die
52     * Frage, ob ueberhaupt eine Verbindung zustande kommt.
53     *
54     * @param {String} url zu pruefende Adresse
55     * @param {Function} callback erhaelt true, wenn der Host antwortet
56     */
57    function probe(url, callback)
58    {
59        if (!url || typeof window.fetch !== 'function')
60        {
61            // Ohne Pruefmoeglichkeit lieber nichts tun als faelschlich ausblenden
62            callback(true);
63            return;
64        }
65
66        var settled = false;
67        var finish = function (reachable)
68        {
69            if (!settled)
70            {
71                settled = true;
72                callback(reachable);
73            }
74        };
75
76        var controller = (typeof window.AbortController === 'function') ? new window.AbortController() : null;
77        var options = { mode: 'no-cors', cache: 'no-store', credentials: 'omit' };
78        if (controller)
79        {
80            options.signal = controller.signal;
81        }
82
83        window.setTimeout(function ()
84        {
85            if (controller)
86            {
87                try
88                {
89                    controller.abort();
90                }
91                catch (error)
92                {
93                    // Abbruch ist optional - das Timeout entscheidet ohnehin
94                }
95            }
96            finish(false);
97        }, PROBE_TIMEOUT);
98
99        window.fetch(url, options).then(function ()
100        {
101            finish(true);
102        }).catch(function ()
103        {
104            finish(false);
105        });
106    }
107
108    /**
109     * Meldet dem Server, dass dem Besucher der Hinweis gezeigt werden musste.
110     *
111     * sendBeacon mit text/plain, damit der Cross-Domain-Aufruf ohne
112     * CORS-Preflight auskommt und den Seitenwechsel ueberlebt. Faellt auf
113     * fetch/no-cors zurueck, wo sendBeacon fehlt. Die Antwort interessiert
114     * nicht - schlaegt die Meldung fehl, sieht der Besucher trotzdem den
115     * Hinweis.
116     *
117     * @param {jQuery} $wrapper
118     * @param {String} reason Kurzkennung des Ausloesers
119     */
120    function report($wrapper, reason)
121    {
122        var url = $wrapper.attr('data-embed-log');
123        if (!url || $wrapper.attr('data-embed-reported') === '1')
124        {
125            return;
126        }
127        $wrapper.attr('data-embed-reported', '1');
128
129        var payload = JSON.stringify({
130            src: $wrapper.attr('data-embed-src') || '',
131            page: window.location.href,
132            reason: reason || ''
133        });
134
135        try
136        {
137            if (navigator && typeof navigator.sendBeacon === 'function')
138            {
139                navigator.sendBeacon(url, new Blob([payload], { type: 'text/plain;charset=UTF-8' }));
140                return;
141            }
142
143            if (typeof window.fetch === 'function')
144            {
145                window.fetch(url, {
146                    method: 'POST',
147                    mode: 'no-cors',
148                    cache: 'no-store',
149                    credentials: 'omit',
150                    keepalive: true,
151                    headers: { 'Content-Type': 'text/plain;charset=UTF-8' },
152                    body: payload
153                }).catch(function ()
154                {
155                    // Meldung ist Nebensache, der Besucher merkt davon nichts
156                });
157            }
158        }
159        catch (error)
160        {
161            // s.o. - ein fehlgeschlagener Log-Versuch darf nichts weiter tun
162        }
163    }
164
165    /**
166     * Setzt die Schaltflaeche waehrend eines Ladeversuchs auf "beschaeftigt".
167     *
168     * @param {jQuery} $wrapper
169     * @param {Boolean} busy
170     */
171    function setRetryBusy($wrapper, busy)
172    {
173        var $button = $wrapper.find('[data-embed-retry]');
174        if (!$button.length)
175        {
176            return;
177        }
178
179        if (busy)
180        {
181            $button.prop('disabled', true).text('Wird geladen …');
182        }
183        else
184        {
185            $button.prop('disabled', false).text('Erneut versuchen');
186        }
187    }
188
189    /**
190     * Blendet die Hinweis-Box ein und entfernt den haengenden iframe.
191     *
192     * @param {jQuery} $wrapper
193     * @param {String} reason Kurzkennung des Ausloesers fuers Log
194     */
195    function showNotice($wrapper, reason)
196    {
197        // src leeren, damit der Browser den haengenden Request aufgibt
198        $wrapper.find('iframe').attr('src', 'about:blank').remove();
199        $wrapper.find('[data-embed-notice]').removeAttr('hidden').css('display', 'block');
200        setRetryBusy($wrapper, false);
201        report($wrapper, reason);
202    }
203
204    /**
205     * Versteckt die Hinweis-Box.
206     *
207     * @param {jQuery} $wrapper
208     */
209    function hideNotice($wrapper)
210    {
211        $wrapper.find('[data-embed-notice]').attr('hidden', 'hidden').css('display', 'none');
212    }
213
214    /**
215     * Ueberwacht einen iframe auf Ladeerfolg.
216     *
217     * @param {jQuery} $wrapper
218     * @param {jQuery} $iframe
219     */
220    function watch($wrapper, $iframe)
221    {
222        var timeout = parseInt($wrapper.attr('data-embed-timeout'), 10);
223        if (!timeout || timeout < 1000)
224        {
225            timeout = DEFAULT_TIMEOUT;
226        }
227
228        var settled = false;
229
230        var timer = window.setTimeout(function ()
231        {
232            if (settled)
233            {
234                return;
235            }
236
237            probe($wrapper.attr('data-embed-src'), function (reachable)
238            {
239                if (settled || reachable)
240                {
241                    // Erreichbar heisst: der iframe braucht nur laenger
242                    setRetryBusy($wrapper, false);
243                    return;
244                }
245                settled = true;
246                showNotice($wrapper, 'Timeout nach ' + timeout + ' ms, Host antwortet dem Browser nicht');
247            });
248        }, timeout);
249
250        $iframe.on('load', function ()
251        {
252            settled = true;
253            window.clearTimeout(timer);
254            setRetryBusy($wrapper, false);
255        });
256    }
257
258    /**
259     * Setzt den Original-iframe neu ein ("Erneut versuchen").
260     *
261     * @param {jQuery} $wrapper
262     */
263    function reload($wrapper)
264    {
265        var html = $wrapper.attr('data-embed-html');
266        if (!html)
267        {
268            return;
269        }
270
271        setRetryBusy($wrapper, true);
272        $wrapper.find('iframe').remove();
273        hideNotice($wrapper);
274        $wrapper.prepend(html);
275
276        var $iframe = $wrapper.find('iframe');
277        if ($iframe.length)
278        {
279            watch($wrapper, $iframe);
280        }
281        else
282        {
283            showNotice($wrapper, 'iframe-Markup fehlt');
284        }
285    }
286
287    /**
288     * Bereitet einen einzelnen Wrapper vor.
289     *
290     * @param {jQuery} $wrapper
291     */
292    function initWrapper($wrapper)
293    {
294        if ($wrapper.attr('data-embed-ready') === '1')
295        {
296            return;
297        }
298        $wrapper.attr('data-embed-ready', '1');
299
300        $wrapper.find('[data-embed-retry]').on('click', function ()
301        {
302            reload($wrapper);
303        });
304
305        var $iframe = $wrapper.find('iframe');
306        if ($iframe.length)
307        {
308            watch($wrapper, $iframe);
309        }
310    }
311
312    $(function ()
313    {
314        $('[data-embed-guard]').each(function ()
315        {
316            initWrapper($(this));
317        });
318    });
319
320})(window.jQuery);

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.