PageSourceSearch

https://www.mk.kg/sw/pwa-ui-sw-bridge.js

js mk.kg collected 2026-10-02 07:28:12 UTC 20,632 bytes, 553 lines download raw bytes

1(function (root) {
2    'use strict';
3
4    root.MKPwaUi = root.MKPwaUi || { layers: {} };
5
6    root.MKPwaUi.layers.createSwBridgeLayer = function createSwBridgeLayer(context) {
7        // Keep disabled until cross-browser notice delivery and UX are settled.
8        var STALE_HOME_UI_ENABLED = context.config.staleHomeUiEnabled === true;
9        var HOME_PATH = '/';
10        var STALE_HOME_MIN_AGE_MS = 60 * 60 * 1000;
11        var WARMUP_URL_LIMIT = 20;
12        var WARMUP_SOURCE_TIMEOUT_MS = 10 * 1000;
13        var WARMUP_SELECTORS = [
14            '.news-listing__day-list .news-listing__item-link',
15            '.top10__item .article-preview__content'
16        ];
17        var staleHomeBanner = null;
18        var staleHomeDismissed = false;
19        var warmupSourcePromise = null;
20        var registration = null;
21        var cacheErrorGoalSent = false;
22        var pendingCacheErrorGoal = null;
23
24        function isStandaloneClient() {
25            return !!(context.install && context.install.isStandalone && context.install.isStandalone());
26        }
27
28        function resolveServiceWorkerTarget(preferredTarget) {
29            if (preferredTarget && typeof preferredTarget.postMessage === 'function') {
30                return preferredTarget;
31            }
32
33            if (root.navigator.serviceWorker.controller) {
34                return root.navigator.serviceWorker.controller;
35            }
36
37            if (!registration) {
38                return null;
39            }
40
41            return registration.active || null;
42        }
43
44        function postToServiceWorker(message, preferredTarget) {
45            var target = resolveServiceWorkerTarget(preferredTarget);
46            if (!target) {
47                return false;
48            }
49
50            try {
51                target.postMessage(message);
52                return true;
53            } catch (_) {
54                return false;
55            }
56        }
57
58        function announceClientReady(preferredTarget, standaloneOverride) {
59            postToServiceWorker({
60                type: 'PWA_CLIENT_READY',
61                standalone: typeof standaloneOverride === 'boolean' ? standaloneOverride : isStandaloneClient(),
62                url: root.location.href
63            }, preferredTarget);
64        }
65
66        function requestStaleHomeNotice() {
67            if (root.location.pathname === HOME_PATH) {
68                announceClientReady(null, false);
69            }
70        }
71
72        function normalizeWarmupUrl(rawUrl) {
73            var url;
74
75            if (!rawUrl) {
76                return '';
77            }
78
79            try {
80                url = new root.URL(rawUrl, root.location.origin);
81            } catch (_) {
82                return '';
83            }
84
85            if (url.origin !== root.location.origin) {
86                return '';
87            }
88
89            url.hash = '';
90            return url.href;
91        }
92
93        function findAnchor(node) {
94            if (!node) {
95                return null;
96            }
97
98            if (String(node.tagName || '').toLowerCase() === 'a' && node.getAttribute('href')) {
99                return node;
100            }
101
102            return node.querySelector ? node.querySelector('a[href]') : null;
103        }
104
105        function extractWarmupUrls(html) {
106            var parser;
107            var doc;
108            var seen = {};
109            var urls = [];
110            var nodes;
111            var nodeIndex;
112            var anchor;
113            var normalizedUrl;
114
115            if (typeof root.DOMParser !== 'function') {
116                throw new Error('dom_parser_unavailable');
117            }
118
119            parser = new root.DOMParser();
120            doc = parser.parseFromString(html, 'text/html');
121            if (!doc || typeof doc.querySelectorAll !== 'function') {
122                throw new Error('dom_parse_failed');
123            }
124
125            nodes = doc.querySelectorAll(WARMUP_SELECTORS.join(','));
126            for (nodeIndex = 0; nodeIndex < nodes.length && urls.length < WARMUP_URL_LIMIT; nodeIndex += 1) {
127                anchor = findAnchor(nodes[nodeIndex]);
128                normalizedUrl = anchor ? normalizeWarmupUrl(anchor.getAttribute('href')) : '';
129
130                if (!normalizedUrl || seen[normalizedUrl]) {
131                    continue;
132                }
133
134                seen[normalizedUrl] = true;
135                urls.push(normalizedUrl);
136            }
137
138            return urls;
139        }
140
141        function reportWarmupSourceFailure(target, reason) {
142            postToServiceWorker({
143                type: 'PWA_WARMUP_SOURCE_FAILED',
144                reason: reason || 'unknown'
145            }, target);
146        }
147
148        function provideWarmupUrls(target) {
149            var requestOptions;
150            var abortController = null;
151            var timeoutId = null;
152
153            if (!isStandaloneClient()) {
154                reportWarmupSourceFailure(target, 'client_not_standalone');
155                return;
156            }
157
158            if (!warmupSourcePromise) {
159                requestOptions = {
160                    method: 'GET',
161                    cache: 'no-store',
162                    credentials: 'same-origin',
163                    headers: {
164                        Accept: 'text/html,application/xhtml+xml'
165                    }
166                };
167
168                if (typeof root.AbortController === 'function') {
169                    abortController = new root.AbortController();
170                    requestOptions.signal = abortController.signal;
171                    timeoutId = root.setTimeout(function () {
172                        abortController.abort();
173                    }, WARMUP_SOURCE_TIMEOUT_MS);
174                }
175
176                warmupSourcePromise = root.fetch(HOME_PATH, requestOptions).then(function (response) {
177                    if (!response || !response.ok) {
178                        throw new Error('http_status_' + (response ? response.status : 'no_response'));
179                    }
180                    return response.text();
181                }).then(function (html) {
182                    return extractWarmupUrls(html);
183                }).then(function (urls) {
184                    if (timeoutId !== null) {
185                        root.clearTimeout(timeoutId);
186                    }
187                    return urls;
188                }, function (error) {
189                    if (timeoutId !== null) {
190                        root.clearTimeout(timeoutId);
191                    }
192                    throw error;
193                });
194
195                warmupSourcePromise.then(function () {
196                    warmupSourcePromise = null;
197                }, function () {
198                    warmupSourcePromise = null;
199                });
200            }
201
202            warmupSourcePromise.then(function (urls) {
203                postToServiceWorker({
204                    type: 'PWA_WARMUP_URLS',
205                    urls: urls
206                }, target);
207            }).catch(function (error) {
208                reportWarmupSourceFailure(target, String((error && error.message) || 'source_failed'));
209            });
210        }
211
212        function formatCacheDateTime(timestamp) {
213            var date = new Date(timestamp);
214
215            function pad(value) {
216                return value < 10 ? '0' + value : String(value);
217            }
218
219            if (!isFinite(date.getTime())) {
220                return '';
221            }
222
223            return [
224                pad(date.getDate()),
225                pad(date.getMonth() + 1),
226                pad(date.getFullYear() % 100)
227            ].join('.') + ' ' + [
228                pad(date.getHours()),
229                pad(date.getMinutes())
230            ].join(':');
231        }
232
233        function isHomeUrl(rawUrl) {
234            var url;
235
236            if (!rawUrl) {
237                return false;
238            }
239
240            try {
241                url = new root.URL(rawUrl, root.location.origin);
242            } catch (_) {
243                return false;
244            }
245
246            return url.origin === root.location.origin && url.pathname === HOME_PATH;
247        }
248
249        function setStaleHomeScrollLocked(doc, locked) {
250            var method = locked ? 'add' : 'remove';
251
252            if (doc.documentElement && doc.documentElement.classList) {
253                doc.documentElement.classList[method]('mk-pwa-stale-home-open');
254            }
255            if (doc.body && doc.body.classList) {
256                doc.body.classList[method]('mk-pwa-stale-home-open');
257            }
258        }
259
260        function closeStaleHomeBanner() {
261            var banner = staleHomeBanner;
262            var previousFocus;
263
264            if (!banner) {
265                return;
266            }
267
268            staleHomeDismissed = true;
269            staleHomeBanner = null;
270            previousFocus = banner.previousFocus;
271            banner.document.removeEventListener('keydown', banner.keydownListener);
272            setStaleHomeScrollLocked(banner.document, false);
273
274            if (banner.element.parentNode) {
275                banner.element.parentNode.removeChild(banner.element);
276            }
277
278            if (previousFocus && typeof previousFocus.focus === 'function') {
279                try {
280                    previousFocus.focus();
281                } catch (_) {
282                    // The previously focused node may have left the document.
283                }
284            }
285        }
286
287        function handleStaleHomeKeydown(event) {
288            var banner = staleHomeBanner;
289            var activeElement;
290
291            if (!banner || !event) {
292                return;
293            }
294
295            if (event.key === 'Escape' || event.keyCode === 27) {
296                event.preventDefault();
297                closeStaleHomeBanner();
298                return;
299            }
300
301            if (event.key !== 'Tab' && event.keyCode !== 9) {
302                return;
303            }
304
305            activeElement = banner.document.activeElement;
306            if (event.shiftKey && (activeElement === banner.closeButton || !banner.dialog.contains(activeElement))) {
307                event.preventDefault();
308                banner.refreshButton.focus();
309                return;
310            }
311
312            if (!event.shiftKey && (activeElement === banner.refreshButton || !banner.dialog.contains(activeElement))) {
313                event.preventDefault();
314                banner.closeButton.focus();
315            }
316        }
317
318        function ensureStaleHomeBanner() {
319            var doc = root.document;
320            var style;
321            var overlay;
322            var dialog;
323            var text;
324            var title;
325            var date;
326            var status;
327            var closeButton;
328            var refreshButton;
329            var previousFocus;
330
331            if (staleHomeBanner || !doc || !doc.body) {
332                return staleHomeBanner;
333            }
334
335            if (!doc.getElementById('mk-pwa-stale-home-style')) {
336                style = doc.createElement('style');
337                style.id = 'mk-pwa-stale-home-style';
338                style.textContent = [
339                    'html.mk-pwa-stale-home-open,body.mk-pwa-stale-home-open{overflow:hidden!important;}',
340                    '.mk-pwa-stale-home{position:fixed;z-index:2147483647;inset:0;display:flex;align-items:center;justify-content:center;box-sizing:border-box;padding:14px;background:rgba(0,0,0,.55);}',
341                    '.mk-pwa-stale-home__dialog{position:relative;display:flex;flex-direction:column;align-items:center;box-sizing:border-box;width:356px;max-width:100%;min-height:161px;padding:18px 24px 16px;border-radius:8px;background:linear-gradient(91.2deg,#e13836 0%,#952524 100%);
341box-shadow:0 4px 14.1px rgba(0,0,0,.25);color:#fff;text-align:center;font-family:Arial,Helvetica,sans-serif;}',
342                    '.mk-pwa-stale-home__text{margin:0;color:#fff;font-size:16px;line-height:26px;font-weight:400;}',
343                    '.mk-pwa-stale-home__text>span,.mk-pwa-stale-home__text>strong{display:block;}',
344                    '.mk-pwa-stale-home__date{font-weight:700;}',
345                    '.mk-pwa-stale-home__close{position:absolute;top:2px;right:2px;width:40px;height:40px;margin:0;padding:0;border:0;background:transparent;color:#fff;cursor:pointer;}',
346                    '.mk-pwa-stale-home__close:before,.mk-pwa-stale-home__close:after{content:"";position:absolute;top:19px;left:10px;width:20px;height:2px;border-radius:1px;background:#fff;}',
347                    '.mk-pwa-stale-home__close:before{transform:rotate(45deg);}',
348                    '.mk-pwa-stale-home__close:after{transform:rotate(-45deg);}',
349                    '.mk-pwa-stale-home__refresh{flex:0 0 auto;box-sizing:border-box;width:165px;height:29px;margin-top:19px;padding:0;border:0;border-radius:14.5px;background:#fff;color:#962524;font:400 12px/29px Arial,Helvetica,sans-serif;cursor:pointer;}',
350                    '.mk-pwa-stale-home__close:focus,.mk-pwa-stale-home__refresh:focus{outline:none;}',
351                    '.mk-pwa-stale-home__close:focus-visible,.mk-pwa-stale-home__refresh:focus-visible{outline:2px solid #fff;outline-offset:2px;}',
352                    '@media(max-width:380px){.mk-pwa-stale-home{padding:12px}.mk-pwa-stale-home__dialog{padding-right:18px;padding-left:18px}.mk-pwa-stale-home__text{font-size:15px;}}'
353                ].join('');
354                (doc.head || doc.documentElement).appendChild(style);
355            }
356
357            previousFocus = doc.activeElement;
358
359            overlay = doc.createElement('div');
360            overlay.className = 'mk-pwa-stale-home';
361
362            dialog = doc.createElement('div');
363            dialog.className = 'mk-pwa-stale-home__dialog';
364            dialog.setAttribute('role', 'dialog');
365            dialog.setAttribute('aria-modal', 'true');
366            dialog.setAttribute('aria-labelledby', 'mk-pwa-stale-home-title');
367            dialog.setAttribute('aria-describedby', 'mk-pwa-stale-home-date mk-pwa-stale-home-status');
368
369            text = doc.createElement('p');
370            text.className = 'mk-pwa-stale-home__text';
371
372            title = doc.createElement('span');
373            title.id = 'mk-pwa-stale-home-title';
374            title.textContent = 'Показана версия сайта';
375
376            date = doc.createElement('strong');
377            date.id = 'mk-pwa-stale-home-date';
378            date.className = 'mk-pwa-stale-home__date';
379
380            status = doc.createElement('span');
381            status.id = 'mk-pwa-stale-home-status';
382            status.textContent = 'нет доступа в Интернет.';
383
384            closeButton = doc.createElement('button');
385            closeButton.type = 'button';
386            closeButton.className = 'mk-pwa-stale-home__close';
387            closeButton.setAttribute('aria-label', 'Закрыть');
388            closeButton.addEventListener('click', closeStaleHomeBanner);
389
390            refreshButton = doc.createElement('button');
391            refreshButton.type = 'button';
392            refreshButton.className = 'mk-pwa-stale-home__refresh';
393            refreshButton.textContent = 'ОБНОВИТЬ';
394            refreshButton.addEventListener('click', function () {
395                root.location.reload();
396            });
397
398            text.appendChild(title);
399            text.appendChild(date);
400            text.appendChild(status);
401            dialog.appendChild(closeButton);
402            dialog.appendChild(text);
403            dialog.appendChild(refreshButton);
404            overlay.appendChild(dialog);
405            doc.body.appendChild(overlay);
406            setStaleHomeScrollLocked(doc, true);
407            doc.addEventListener('keydown', handleStaleHomeKeydown);
408
409            staleHomeBanner = {
410                element: overlay,
411                dialog: dialog,
412                date: date,
413                closeButton: closeButton,
414                refreshButton: refreshButton,
415                previousFocus: previousFocus,
416                keydownListener: handleStaleHomeKeydown,
417                document: doc
418            };
419
420            try {
421                closeButton.focus();
422            } catch (_) {
423                // Focus management is an enhancement; rendering must survive.
424            }
425
426            return staleHomeBanner;
427        }
428
429        function showStaleHomeBanner(notice) {
430            var cachedAt = Number(notice && notice.cachedAt) || 0;
431            var ageMs = Number(notice && (notice.ageMs || notice.cacheAgeMs)) || 0;
432            var formattedDate;
433            var banner;
434
435            if (staleHomeDismissed) {
436                return;
437            }
438
439            if (root.location.pathname !== HOME_PATH) {
440                return;
441            }
442
443            if (notice && (notice.url || notice.cacheKey) && !isHomeUrl(notice.url || notice.cacheKey)) {
444                return;
445            }
446
447            if (!ageMs && cachedAt > 0) {
448                ageMs = Math.max(0, Date.now() - cachedAt);
449            }
450
451            if (!cachedAt || ageMs <= STALE_HOME_MIN_AGE_MS) {
452                return;
453            }
454
455            formattedDate = formatCacheDateTime(cachedAt);
456            if (!formattedDate) {
457                return;
458            }
459
460            banner = ensureStaleHomeBanner();
461            if (!banner) {
462                return;
463            }
464
465            banner.date.textContent = 'от ' + formattedDate + ',';
466        }
467
468        function flushCacheErrorGoal() {
469            if (cacheErrorGoalSent || !pendingCacheErrorGoal || typeof root.ym !== 'function') {
470                return;
471            }
472            try {
473                root.ym(1598573, 'reachGoal', 'cache_storage_error_pwa', pendingCacheErrorGoal);
474                cacheErrorGoalSent = true;
475                pendingCacheErrorGoal = null;
476            } catch (_) {
477                // Analytics must never break the page or worker bridge.
478            }
479        }
480
481        root.addEventListener('load', flushCacheErrorGoal);
482        root.addEventListener('pageshow', flushCacheErrorGoal);
483
484        root.navigator.serviceWorker.addEventListener('message', function (event) {
485            var data = event && event.data ? event.data : null;
486
487            if (!data || !data.type) {
488                return;
489            }
490
491            if (data.type === 'PWA_CACHE_STORAGE_ERROR') {
492                if (!cacheErrorGoalSent && !pendingCacheErrorGoal) {
493                    pendingCacheErrorGoal = {
494                        sw_version: String(data.version || '').slice(0, 64),
495                        cache: String(data.cache || '').slice(0, 64),
496                        operation: String(data.operation || '').slice(0, 24),
497                        error_name: String(data.errorName || '').slice(0, 32)
498                    };
499                }
500                flushCacheErrorGoal();
501                return;
502            }
503
504            if (data.type === 'PWA_WARMUP_REQUEST') {
505                provideWarmupUrls(event.source);
506                return;
507            }
508
509            if (data.type === 'PWA_STALE_HOME') {
510                if (STALE_HOME_UI_ENABLED) {
511                    showStaleHomeBanner(data);
512                }
513                return;
514            }
515        });
516
517        function register() {
518            // An existing controller can deliver an offline-page notice even
519            // if the registration update itself cannot reach the network.
520            announceClientReady();
521            root.navigator.serviceWorker.register(context.config.swUrl, {
522                scope: '/',
523                updateViaCache: 'none'
524            }).then(function (readyRegistration) {
525                registration = readyRegistration;
526                announceClientReady();
527
528                root.navigator.serviceWorker.ready.then(function (activeRegistration) {
529                    registration = activeRegistration;
530                    announceClientReady(activeRegistration.active);
531                }).catch(function () {
532                    // Registration errors are handled by the outer promise.
533                });
534
535                root.navigator.serviceWorker.addEventListener('controllerchange', function () {
536                    announceClientReady();
537                });
538
539                context.logger.info('[PWA]', 'sw.registered', readyRegistration.scope);
540            }).catch(function (error) {
541                context.logger.warn('[PWA]', 'sw.register.failed', {
542                    name: error && error.name,
543                    message: error && error.message
544                });
545            });
546        }
547
548        return {
549            register: register,
550            requestStaleHomeNotice: requestStaleHomeNotice
551        };
552    };
553})(window);

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.